SSO registration & setup guide
- Google, Apple, GitHub SSO login via popup OAuth flow - New /mobile/auth page with 3-click registration (Click provider → authorize → auto-logged in) - Email registration with name + email + password - /mobile/setup page with step-by-step OAuth setup guide - OAuth env vars in .env.example, docker-compose.yml - scripts/setup-sso.sh for one-command credential setup - Graceful 503 when OAuth not configured
This commit is contained in:
+8
-2
@@ -4,16 +4,22 @@ services:
|
|||||||
build: .
|
build: .
|
||||||
image: falah-mobile:latest
|
image: falah-mobile:latest
|
||||||
ports:
|
ports:
|
||||||
- "4011:3000"
|
- "4013:3000"
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- JWT_SECRET=${JWT_SECRET:-flh-dev-jwt-secret-change-in-prod}
|
- JWT_SECRET=${JWT_SECRET:-flh-dev-jwt-secret-change-in-prod}
|
||||||
- DATABASE_URL=file:./dev.db
|
- DATABASE_URL=file:./dev.db
|
||||||
|
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-}
|
||||||
|
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET:-}
|
||||||
|
- APPLE_CLIENT_ID=${APPLE_CLIENT_ID:-}
|
||||||
|
- APPLE_CLIENT_SECRET=${APPLE_CLIENT_SECRET:-}
|
||||||
|
- GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID:-}
|
||||||
|
- GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET:-}
|
||||||
volumes:
|
volumes:
|
||||||
- falah-mobile-data:/app/data
|
- falah-mobile-data:/app/data
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
|
test: ["CMD", "curl", "-f", "http://localhost:3000/mobile/api/health"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================
|
||||||
|
# FalahOS SSO Credentials Setup Script
|
||||||
|
# ============================================================
|
||||||
|
# Run this on your Contabo VPS after getting OAuth credentials.
|
||||||
|
# It safely updates .env, rebuilds, and redeploys.
|
||||||
|
#
|
||||||
|
# Usage: bash /root/falah-mobile/scripts/setup-sso.sh
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SSO_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
ENV_FILE="$SSO_DIR/.env"
|
||||||
|
COMPOSE_FILE="$SSO_DIR/docker-compose.yml"
|
||||||
|
|
||||||
|
echo "╔═══════════════════════════════════════════════╗"
|
||||||
|
echo "║ FalahOS SSO Credentials Setup ║"
|
||||||
|
echo "╚═══════════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Prompt for GitHub ---
|
||||||
|
echo "── 🐙 GitHub OAuth App ──"
|
||||||
|
read -p " Client ID : " GITHUB_CLIENT_ID
|
||||||
|
read -sp " Client Secret: " GITHUB_CLIENT_SECRET
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Prompt for Google ---
|
||||||
|
echo "── 🔵 Google OAuth ──"
|
||||||
|
read -p " Client ID : " GOOGLE_CLIENT_ID
|
||||||
|
read -sp " Client Secret: " GOOGLE_CLIENT_SECRET
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Prompt for Apple ---
|
||||||
|
echo "── 🍎 Apple Sign In ──"
|
||||||
|
read -p " Service ID : " APPLE_CLIENT_ID
|
||||||
|
read -sp " Client Secret: " APPLE_CLIENT_SECRET
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Confirm ---
|
||||||
|
echo "═══════════════════════════════════════════════"
|
||||||
|
echo " Summary:"
|
||||||
|
echo " GitHub: ${GITHUB_CLIENT_ID:0:10}..."
|
||||||
|
echo " Google: ${GOOGLE_CLIENT_ID:0:10}..."
|
||||||
|
echo " Apple: $APPLE_CLIENT_ID"
|
||||||
|
echo "═══════════════════════════════════════════════"
|
||||||
|
read -p "Apply these credentials and redeploy? (y/N) " CONFIRM
|
||||||
|
|
||||||
|
if [ "$CONFIRM" != "y" ] && [ "$CONFIRM" != "Y" ]; then
|
||||||
|
echo "Aborted. No changes made."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Backup current .env ---
|
||||||
|
cp "$ENV_FILE" "$ENV_FILE.backup"
|
||||||
|
echo "✓ Backed up .env → .env.backup"
|
||||||
|
|
||||||
|
# --- Remove old OAuth lines if they exist ---
|
||||||
|
sed -i '/^GOOGLE_CLIENT_ID=/d' "$ENV_FILE"
|
||||||
|
sed -i '/^GOOGLE_CLIENT_SECRET=/d' "$ENV_FILE"
|
||||||
|
sed -i '/^APPLE_CLIENT_ID=/d' "$ENV_FILE"
|
||||||
|
sed -i '/^APPLE_CLIENT_SECRET=/d' "$ENV_FILE"
|
||||||
|
sed -i '/^GITHUB_CLIENT_ID=/d' "$ENV_FILE"
|
||||||
|
sed -i '/^GITHUB_CLIENT_SECRET=/d' "$ENV_FILE"
|
||||||
|
|
||||||
|
# --- Append new credentials ---
|
||||||
|
cat >> "$ENV_FILE" << EOF
|
||||||
|
|
||||||
|
# --- OAuth / SSO ---
|
||||||
|
GOOGLE_CLIENT_ID="$GOOGLE_CLIENT_ID"
|
||||||
|
GOOGLE_CLIENT_SECRET="$GOOGLE_CLIENT_SECRET"
|
||||||
|
APPLE_CLIENT_ID="$APPLE_CLIENT_ID"
|
||||||
|
APPLE_CLIENT_SECRET="$APPLE_CLIENT_SECRET"
|
||||||
|
GITHUB_CLIENT_ID="$GITHUB_CLIENT_ID"
|
||||||
|
GITHUB_CLIENT_SECRET="$GITHUB_CLIENT_SECRET"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "✓ Credentials written to .env"
|
||||||
|
|
||||||
|
# --- Rebuild and deploy ---
|
||||||
|
echo ""
|
||||||
|
echo "═══ Building & Deploying ═══"
|
||||||
|
cd "$SSO_DIR"
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "═══ Rebuilding Docker image ═══"
|
||||||
|
docker compose -f "$COMPOSE_FILE" build --pull
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "═══ Restarting container ═══"
|
||||||
|
docker compose -f "$COMPOSE_FILE" up -d --force-recreate
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✓ SSO setup complete!"
|
||||||
|
echo " Test at: https://falahos.my/mobile/auth"
|
||||||
@@ -0,0 +1,582 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
export default function SetupPage() {
|
||||||
|
const [tab, setTab] = useState<'github' | 'google' | 'apple' | 'env'>('github');
|
||||||
|
const [copied, setCopied] = useState('');
|
||||||
|
|
||||||
|
const copyToClipboard = (text: string, label: string) => {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
setCopied(label);
|
||||||
|
setTimeout(() => setCopied(''), 2000);
|
||||||
|
}).catch(() => {
|
||||||
|
// Fallback
|
||||||
|
const ta = document.createElement('textarea');
|
||||||
|
ta.value = text;
|
||||||
|
document.body.appendChild(ta);
|
||||||
|
ta.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(ta);
|
||||||
|
setCopied(label);
|
||||||
|
setTimeout(() => setCopied(''), 2000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{ id: 'github' as const, label: 'GitHub', icon: '🐙' },
|
||||||
|
{ id: 'google' as const, label: 'Google', icon: '🔵' },
|
||||||
|
{ id: 'apple' as const, label: 'Apple', icon: '🍎' },
|
||||||
|
{ id: 'env' as const, label: 'Apply', icon: '⚡' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
maxWidth: '600px',
|
||||||
|
margin: '0 auto',
|
||||||
|
padding: '16px',
|
||||||
|
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
||||||
|
color: '#e0e0e0',
|
||||||
|
background: '#0a0a0a',
|
||||||
|
minHeight: '100vh',
|
||||||
|
}}>
|
||||||
|
<h1 style={{ fontSize: '24px', marginBottom: '8px', color: '#D4AF37' }}>
|
||||||
|
⚡ SSO Setup Guide
|
||||||
|
</h1>
|
||||||
|
<p style={{ color: '#888', fontSize: '14px', marginBottom: '24px' }}>
|
||||||
|
Create OAuth credentials for Google, Apple, and GitHub SSO.
|
||||||
|
Each takes ~5 minutes.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Tab bar */}
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: '8px',
|
||||||
|
marginBottom: '20px',
|
||||||
|
overflowX: 'auto',
|
||||||
|
paddingBottom: '4px',
|
||||||
|
}}>
|
||||||
|
{tabs.map(t => (
|
||||||
|
<button
|
||||||
|
key={t.id}
|
||||||
|
onClick={() => setTab(t.id)}
|
||||||
|
style={{
|
||||||
|
padding: '10px 16px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: tab === t.id ? '2px solid #D4AF37' : '1px solid #333',
|
||||||
|
background: tab === t.id ? '#1a1a1a' : '#111',
|
||||||
|
color: tab === t.id ? '#D4AF37' : '#666',
|
||||||
|
fontSize: '14px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
fontWeight: tab === t.id ? 600 : 400,
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t.icon} {t.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* GitHub Tab */}
|
||||||
|
{tab === 'github' && (
|
||||||
|
<div>
|
||||||
|
<h2 style={{ fontSize: '18px', marginBottom: '16px', color: '#fff' }}>
|
||||||
|
Create a GitHub OAuth App
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<Step number={1} title="Go to GitHub Developer Settings">
|
||||||
|
<p>Open this link in your browser:</p>
|
||||||
|
<CodeBlock
|
||||||
|
text="https://github.com/settings/developers"
|
||||||
|
label="GitHub URL"
|
||||||
|
copied={copied}
|
||||||
|
onCopy={copyToClipboard}
|
||||||
|
/>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step number={2} title="Create New OAuth App">
|
||||||
|
<p>Click <strong>"New OAuth App"</strong> button (top right).</p>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step number={3} title="Fill in the form">
|
||||||
|
<FormField label="Application name" value="FalahOS Auth" />
|
||||||
|
<FormField label="Homepage URL" value="https://falahos.my" />
|
||||||
|
<FormField label="Application description" value="FalahOS - Islamic Finance & Charity Ecosystem" />
|
||||||
|
<FormField
|
||||||
|
label="Authorization callback URL"
|
||||||
|
value="https://falahos.my/mobile/api/auth/github/callback"
|
||||||
|
/>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step number={4} title="Register Application">
|
||||||
|
<p>Click <strong>"Register application"</strong>.</p>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step number={5} title="Copy Your Credentials">
|
||||||
|
<p>You will see:</p>
|
||||||
|
<ul style={{ color: '#ccc', paddingLeft: '20px', lineHeight: 1.8 }}>
|
||||||
|
<li><strong>Client ID</strong> — copy this</li>
|
||||||
|
<li><strong>Client Secret</strong> — click <strong>"Generate a new client secret"</strong> and copy it</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p style={{ marginTop: '16px', fontWeight: 600, color: '#D4AF37' }}>
|
||||||
|
Paste them below when you have them:
|
||||||
|
</p>
|
||||||
|
<div style={{
|
||||||
|
background: '#0d0d0d',
|
||||||
|
border: '1px solid #333',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '12px',
|
||||||
|
marginTop: '8px',
|
||||||
|
}}>
|
||||||
|
<div style={{ fontSize: '12px', color: '#888', marginBottom: '4px' }}>
|
||||||
|
GITHUB_CLIENT_ID=your_client_id_here
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: '12px', color: '#888', marginBottom: '12px' }}>
|
||||||
|
GITHUB_CLIENT_SECRET=your_client_secret_here
|
||||||
|
</div>
|
||||||
|
<CopyButton
|
||||||
|
text={`GITHUB_CLIENT_ID="<your-client-id>"\nGITHUB_CLIENT_SECRET="<your-client-secret>"`}
|
||||||
|
label="github-template"
|
||||||
|
copied={copied}
|
||||||
|
onCopy={copyToClipboard}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Step>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Google Tab */}
|
||||||
|
{tab === 'google' && (
|
||||||
|
<div>
|
||||||
|
<h2 style={{ fontSize: '18px', marginBottom: '16px', color: '#fff' }}>
|
||||||
|
Create Google OAuth 2.0 Client
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<Step number={1} title="Go to Google Cloud Console">
|
||||||
|
<CodeBlock
|
||||||
|
text="https://console.cloud.google.com/apis/credentials"
|
||||||
|
label="Google URL"
|
||||||
|
copied={copied}
|
||||||
|
onCopy={copyToClipboard}
|
||||||
|
/>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step number={2} title="Select or Create a Project">
|
||||||
|
<p>If you don't have a project, click <strong>"Create Project"</strong> and name it <strong>"FalahOS"</strong>.</p>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step number={3} title="Configure Consent Screen">
|
||||||
|
<p>Click <strong>"OAuth consent screen"</strong> (left sidebar).</p>
|
||||||
|
<ul style={{ color: '#ccc', paddingLeft: '20px', lineHeight: 1.8 }}>
|
||||||
|
<li>Choose <strong>External</strong> user type</li>
|
||||||
|
<li>App name: <strong>FalahOS</strong></li>
|
||||||
|
<li>User support email: <strong>your email</strong></li>
|
||||||
|
<li>Developer Contact: <strong>your email</strong></li>
|
||||||
|
<li>Add scopes: <strong>email, profile, openid</strong></li>
|
||||||
|
<li>Add test users: <strong>your email</strong></li>
|
||||||
|
</ul>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step number={4} title="Create Credentials">
|
||||||
|
<p>Click <strong>"Create Credentials" → "OAuth client ID"</strong>.</p>
|
||||||
|
<FormField label="Application type" value="Web application" />
|
||||||
|
<FormField label="Name" value="FalahOS Web Client" />
|
||||||
|
<FormField
|
||||||
|
label="Authorized JavaScript origins"
|
||||||
|
value="https://falahos.my"
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
label="Authorized redirect URIs"
|
||||||
|
value="https://falahos.my/mobile/api/auth/google/callback"
|
||||||
|
/>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step number={5} title="Copy Your Credentials">
|
||||||
|
<p>Click <strong>"Create"</strong> and copy the popup values:</p>
|
||||||
|
<div style={{
|
||||||
|
background: '#0d0d0d',
|
||||||
|
border: '1px solid #333',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '12px',
|
||||||
|
marginTop: '8px',
|
||||||
|
}}>
|
||||||
|
<div style={{ fontSize: '12px', color: '#888', marginBottom: '4px' }}>
|
||||||
|
GOOGLE_CLIENT_ID=xxxxxxxxx.apps.googleusercontent.com
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: '12px', color: '#888', marginBottom: '12px' }}>
|
||||||
|
GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxx
|
||||||
|
</div>
|
||||||
|
<CopyButton
|
||||||
|
text={`GOOGLE_CLIENT_ID="<your-client-id>"\nGOOGLE_CLIENT_SECRET="<your-client-secret>"`}
|
||||||
|
label="google-template"
|
||||||
|
copied={copied}
|
||||||
|
onCopy={copyToClipboard}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Step>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Apple Tab */}
|
||||||
|
{tab === 'apple' && (
|
||||||
|
<div>
|
||||||
|
<h2 style={{ fontSize: '18px', marginBottom: '16px', color: '#fff' }}>
|
||||||
|
Set Up Sign In with Apple
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<Step number={1} title="Go to Apple Developer">
|
||||||
|
<CodeBlock
|
||||||
|
text="https://developer.apple.com/account/resources/identifiers/list"
|
||||||
|
label="Apple URL"
|
||||||
|
copied={copied}
|
||||||
|
onCopy={copyToClipboard}
|
||||||
|
/>
|
||||||
|
<p style={{ color: '#ff6b6b', fontSize: '13px', marginTop: '8px' }}>
|
||||||
|
⚠️ Requires an Apple Developer account ($99/year).
|
||||||
|
</p>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step number={2} title="Register a Service ID">
|
||||||
|
<ul style={{ color: '#ccc', paddingLeft: '20px', lineHeight: 1.8 }}>
|
||||||
|
<li>Click <strong>"+"</strong> → <strong>"Service IDs"</strong></li>
|
||||||
|
<li>Description: <strong>FalahOS Auth</strong></li>
|
||||||
|
<li>Identifier: <strong>com.falahos.auth</strong></li>
|
||||||
|
<li>Click <strong>"Continue"</strong> → <strong>"Register"</strong></li>
|
||||||
|
</ul>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step number={3} title="Enable Sign in with Apple">
|
||||||
|
<ul style={{ color: '#ccc', paddingLeft: '20px', lineHeight: 1.8 }}>
|
||||||
|
<li>Click on your new Service ID</li>
|
||||||
|
<li>Check <strong>"Sign in with Apple"</strong> → <strong>"Configure"</strong></li>
|
||||||
|
<li>Select <strong>"Use as default"</strong> or create a new Service</li>
|
||||||
|
<li>Web Domain: <strong>falahos.my</strong></li>
|
||||||
|
<li>Return URLs: <strong>https://falahos.my/mobile/api/auth/apple/callback</strong></li>
|
||||||
|
<li>Click <strong>"Save"</strong></li>
|
||||||
|
</ul>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step number={4} title="Create a Key">
|
||||||
|
<ul style={{ color: '#ccc', paddingLeft: '20px', lineHeight: 1.8 }}>
|
||||||
|
<li>Go to <strong>Keys</strong> → <strong>"+"</strong></li>
|
||||||
|
<li>Select <strong>"Sign in with Apple"</strong></li>
|
||||||
|
<li>Configure with your Service ID</li>
|
||||||
|
<li>Download the <strong>.p8</strong> key file</li>
|
||||||
|
<li>Note the <strong>Key ID</strong></li>
|
||||||
|
</ul>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step number={5} title="Copy Values">
|
||||||
|
<div style={{
|
||||||
|
background: '#0d0d0d',
|
||||||
|
border: '1px solid #333',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '12px',
|
||||||
|
marginTop: '8px',
|
||||||
|
}}>
|
||||||
|
<div style={{ fontSize: '12px', color: '#888', marginBottom: '4px' }}>
|
||||||
|
APPLE_CLIENT_ID=com.falahos.auth (your Service ID)
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: '12px', color: '#888', marginBottom: '12px' }}>
|
||||||
|
APPLE_CLIENT_SECRET= (generated JWT, not needed immediately)
|
||||||
|
</div>
|
||||||
|
<CopyButton
|
||||||
|
text={`APPLE_CLIENT_ID="com.falahos.auth"\nAPPLE_CLIENT_SECRET=""`}
|
||||||
|
label="apple-template"
|
||||||
|
copied={copied}
|
||||||
|
onCopy={copyToClipboard}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Step>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Apply Tab */}
|
||||||
|
{tab === 'env' && (
|
||||||
|
<div>
|
||||||
|
<h2 style={{ fontSize: '18px', marginBottom: '16px', color: '#fff' }}>
|
||||||
|
Apply Credentials & Deploy
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p style={{ color: '#ccc', marginBottom: '16px' }}>
|
||||||
|
Once you have your credentials from all providers, paste them below
|
||||||
|
and we'll configure the server.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<EnvForm />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
marginTop: '32px',
|
||||||
|
padding: '16px',
|
||||||
|
background: '#111',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #333',
|
||||||
|
}}>
|
||||||
|
<h3 style={{ color: '#D4AF37', fontSize: '16px', marginBottom: '8px' }}>
|
||||||
|
✅ Quick Summary
|
||||||
|
</h3>
|
||||||
|
<p style={{ color: '#aaa', fontSize: '14px', lineHeight: 1.6 }}>
|
||||||
|
After creating all 3 OAuth apps, you'll have:
|
||||||
|
</p>
|
||||||
|
<ul style={{ color: '#ccc', paddingLeft: '20px', lineHeight: 1.8 }}>
|
||||||
|
<li>🐙 <strong>GitHub</strong>: Client ID + Secret</li>
|
||||||
|
<li>🔵 <strong>Google</strong>: Client ID + Secret</li>
|
||||||
|
<li>🍎 <strong>Apple</strong>: Service ID (Client ID) + Key</li>
|
||||||
|
</ul>
|
||||||
|
<p style={{ color: '#aaa', fontSize: '14px', marginTop: '8px', lineHeight: 1.6 }}>
|
||||||
|
Contact me on Telegram with the values and I'll apply them instantly.
|
||||||
|
Or use the SSH command below to configure yourself.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Step({ number, title, children }: { number: number; title: string; children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
marginBottom: '20px',
|
||||||
|
padding: '12px',
|
||||||
|
background: '#111',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #222',
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '10px',
|
||||||
|
marginBottom: '8px',
|
||||||
|
}}>
|
||||||
|
<span style={{
|
||||||
|
width: '28px',
|
||||||
|
height: '28px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: '#D4AF37',
|
||||||
|
color: '#000',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: '14px',
|
||||||
|
}}>
|
||||||
|
{number}
|
||||||
|
</span>
|
||||||
|
<h3 style={{ fontSize: '15px', color: '#fff', margin: 0 }}>{title}</h3>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginLeft: '38px', fontSize: '14px', color: '#ccc', lineHeight: 1.6 }}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FormField({ label, value }: { label: string; value: string }) {
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: '8px' }}>
|
||||||
|
<div style={{ fontSize: '12px', color: '#888', marginBottom: '2px' }}>{label}</div>
|
||||||
|
<div style={{
|
||||||
|
padding: '8px 10px',
|
||||||
|
background: '#0a0a0a',
|
||||||
|
border: '1px solid #333',
|
||||||
|
borderRadius: '6px',
|
||||||
|
fontSize: '13px',
|
||||||
|
color: '#D4AF37',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
wordBreak: 'break-all',
|
||||||
|
}}>
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CodeBlock({ text, label, copied, onCopy }: {
|
||||||
|
text: string;
|
||||||
|
label: string;
|
||||||
|
copied: string;
|
||||||
|
onCopy: (text: string, label: string) => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
flex: 1,
|
||||||
|
padding: '8px 10px',
|
||||||
|
background: '#0a0a0a',
|
||||||
|
border: '1px solid #333',
|
||||||
|
borderRadius: '6px',
|
||||||
|
fontSize: '13px',
|
||||||
|
color: '#4fc3f7',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
wordBreak: 'break-all',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}>
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => onCopy(text, label)}
|
||||||
|
style={{
|
||||||
|
padding: '8px 12px',
|
||||||
|
borderRadius: '6px',
|
||||||
|
border: '1px solid #D4AF37',
|
||||||
|
background: copied === label ? '#D4AF37' : 'transparent',
|
||||||
|
color: copied === label ? '#000' : '#D4AF37',
|
||||||
|
fontSize: '12px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{copied === label ? '✓ Copied!' : 'Copy'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CopyButton({ text, label, copied, onCopy }: {
|
||||||
|
text: string;
|
||||||
|
label: string;
|
||||||
|
copied: string;
|
||||||
|
onCopy: (text: string, label: string) => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={() => onCopy(text, label)}
|
||||||
|
style={{
|
||||||
|
padding: '10px 16px',
|
||||||
|
borderRadius: '6px',
|
||||||
|
border: 'none',
|
||||||
|
background: copied === label ? '#D4AF37' : '#1a1a1a',
|
||||||
|
color: copied === label ? '#000' : '#D4AF37',
|
||||||
|
fontSize: '13px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontWeight: 600,
|
||||||
|
width: '100%',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{copied === label ? '✓ Copied!' : '📋 Copy template'}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EnvForm() {
|
||||||
|
const [ghId, setGhId] = useState('');
|
||||||
|
const [ghSecret, setGhSecret] = useState('');
|
||||||
|
const [glId, setGlId] = useState('');
|
||||||
|
const [glSecret, setGlSecret] = useState('');
|
||||||
|
const [apId, setApId] = useState('');
|
||||||
|
const [apSecret, setApSecret] = useState('');
|
||||||
|
const [submitted, setSubmitted] = useState(false);
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
// Build the env var text for copying
|
||||||
|
const text = `# Paste these into your VPS:\ncd /root/falah-mobile\ncat >> .env << 'EOF'\nGOOGLE_CLIENT_ID="${glId}"\nGOOGLE_CLIENT_SECRET="${glSecret}"\nAPPLE_CLIENT_ID="${apId}"\nAPPLE_CLIENT_SECRET="${apSecret}"\nGITHUB_CLIENT_ID="${ghId}"\nGITHUB_CLIENT_SECRET="${ghSecret}"\nEOF\ndocker compose up -d --build`;
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
setSubmitted(true);
|
||||||
|
setTimeout(() => setSubmitted(false), 3000);
|
||||||
|
}).catch(() => setSubmitted(true));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
||||||
|
<div style={{
|
||||||
|
background: '#0d0d0d',
|
||||||
|
border: '1px solid #333',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '12px',
|
||||||
|
}}>
|
||||||
|
<h4 style={{ color: '#fff', fontSize: '14px', marginBottom: '8px' }}>🐙 GitHub</h4>
|
||||||
|
<InputField label="Client ID" value={ghId} onChange={setGhId} placeholder="Iv1.xxxxxxxxxxxx" />
|
||||||
|
<InputField label="Client Secret" value={ghSecret} onChange={setGhSecret} placeholder="ghp_xxxxxxxx" secret />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
background: '#0d0d0d',
|
||||||
|
border: '1px solid #333',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '12px',
|
||||||
|
}}>
|
||||||
|
<h4 style={{ color: '#fff', fontSize: '14px', marginBottom: '8px' }}>🔵 Google</h4>
|
||||||
|
<InputField label="Client ID" value={glId} onChange={setGlId} placeholder="xxxx.apps.googleusercontent.com" />
|
||||||
|
<InputField label="Client Secret" value={glSecret} onChange={setGlSecret} placeholder="GOCSPX-xxxxxxxx" secret />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
background: '#0d0d0d',
|
||||||
|
border: '1px solid #333',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '12px',
|
||||||
|
}}>
|
||||||
|
<h4 style={{ color: '#fff', fontSize: '14px', marginBottom: '8px' }}>🍎 Apple</h4>
|
||||||
|
<InputField label="Service ID (Client ID)" value={apId} onChange={setApId} placeholder="com.falahos.auth" />
|
||||||
|
<InputField label="Client Secret (JWT)" value={apSecret} onChange={setApSecret} placeholder="(generated JWT)" secret />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={!ghId && !glId && !apId}
|
||||||
|
style={{
|
||||||
|
padding: '14px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: 'none',
|
||||||
|
background: '#D4AF37',
|
||||||
|
color: '#000',
|
||||||
|
fontSize: '15px',
|
||||||
|
fontWeight: 700,
|
||||||
|
cursor: 'pointer',
|
||||||
|
opacity: (!ghId && !glId && !apId) ? 0.5 : 1,
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{submitted ? '✓ Copied to clipboard!' : '📋 Copy config & deploy command'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<p style={{ fontSize: '12px', color: '#666', textAlign: 'center' }}>
|
||||||
|
This copies the deployment command to your clipboard. Send it to me on Telegram
|
||||||
|
and I'll run it on the VPS.
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function InputField({ label, value, onChange, placeholder, secret }: {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
onChange: (v: string) => void;
|
||||||
|
placeholder: string;
|
||||||
|
secret?: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: '8px' }}>
|
||||||
|
<div style={{ fontSize: '12px', color: '#888', marginBottom: '2px' }}>{label}</div>
|
||||||
|
<input
|
||||||
|
type={secret ? 'password' : 'text'}
|
||||||
|
value={value}
|
||||||
|
onChange={e => onChange(e.target.value)}
|
||||||
|
placeholder={placeholder}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '8px 10px',
|
||||||
|
background: '#0a0a0a',
|
||||||
|
border: '1px solid #333',
|
||||||
|
borderRadius: '6px',
|
||||||
|
fontSize: '14px',
|
||||||
|
color: '#fff',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
outline: 'none',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user