diff --git a/.env.example b/.env.example index 5bf064a..cb3ed46 100644 --- a/.env.example +++ b/.env.example @@ -34,6 +34,11 @@ BETTER_AUTH_URL=http://localhost:3000 GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= +# GitHub OAuth2 app credentials (github.com → Settings → Developer settings → OAuth Apps) +# Callback URL: {BETTER_AUTH_URL}/v1/auth/callback/github +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= + # Must exactly match an authorized redirect URI in your Google OAuth app # Better Auth callback (for user login): {BETTER_AUTH_URL}/v1/auth/callback/google # Add this to Google Cloud Console authorized redirect URIs: diff --git a/README.md b/README.md index 99358cf..6500105 100644 --- a/README.md +++ b/README.md @@ -34,17 +34,24 @@ docker compose --build -f docker-compose.yml -f docker-compose.standalone.yml -f ### Cloud mode -Full production-like stack. Users authenticate via Google OAuth (via Better Auth); deck data -stored in their Google Drive. Requires all OAuth credentials in `.env`. +Full production-like stack. Users authenticate via Google or GitHub OAuth (via Better Auth); deck data +stored in their Google Drive. Requires OAuth credentials in `.env`. -Before running, add these URIs to your Google OAuth app in -[Google Cloud Console](https://console.cloud.google.com) → APIs & Services → Credentials: +Before running, register these callback URIs in your OAuth apps: + +**Google** ([Cloud Console](https://console.cloud.google.com) → APIs & Services → Credentials): ``` {BETTER_AUTH_URL}/v1/auth/callback/google # sign-in callback {FRONTEND_URL}/v1/me/storage/connect/google/callback # Google Drive callback ``` +**GitHub** (optional — [Settings → Developer settings → OAuth Apps](https://github.com/settings/developers)): + +``` +{BETTER_AUTH_URL}/v1/auth/callback/github # sign-in callback +``` + Set `TRUSTED_ORIGINS` in `.env` to your frontend URL(s) (comma-separated) so Better Auth accepts requests from the web UI. Defaults to `FRONTEND_URL` if unset. @@ -80,7 +87,7 @@ Authorization: Bearer ak_ Generate a key in the web UI under **Account → API Keys**, or via `POST /v1/me/api-keys`. -Account management endpoints (`/v1/me/*`) use the session cookie set by [Better Auth](https://better-auth.com) after Google OAuth login. The auth handler is mounted at `/v1/auth/*`. +Account management endpoints (`/v1/me/*`) use the session cookie set by [Better Auth](https://better-auth.com) after Google or GitHub OAuth login. The auth handler is mounted at `/v1/auth/*`. --- diff --git a/api/src/auth.ts b/api/src/auth.ts index d05cd24..26e7c3a 100644 --- a/api/src/auth.ts +++ b/api/src/auth.ts @@ -21,5 +21,9 @@ export const auth = betterAuth({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }, + github: { + clientId: process.env.GITHUB_CLIENT_ID!, + clientSecret: process.env.GITHUB_CLIENT_SECRET!, + }, }, }); diff --git a/docker-compose.yml b/docker-compose.yml index 003313f..f94d3ff 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,6 +28,8 @@ services: BETTER_AUTH_URL: ${BETTER_AUTH_URL} GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID} GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET} + GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID:-} + GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET:-} GOOGLE_DRIVE_REDIRECT_URI: ${GOOGLE_DRIVE_REDIRECT_URI} FRONTEND_URL: ${FRONTEND_URL} TRUSTED_ORIGINS: ${TRUSTED_ORIGINS:-} diff --git a/docs/SELF_HOSTING.md b/docs/SELF_HOSTING.md index 76e1092..8d72ce7 100644 --- a/docs/SELF_HOSTING.md +++ b/docs/SELF_HOSTING.md @@ -8,12 +8,15 @@ Get your own sync server running in under 10 minutes. All you need is Docker and - [Docker Desktop](https://www.docker.com/products/docker-desktop/) (or Docker Engine + Compose) - A Google account (for OAuth login and Google Drive storage) +- A GitHub account (optional — if you want GitHub sign-in) No other tools required. --- -## Step 1 — Create a Google OAuth app +## Step 1 — Create OAuth app(s) + +### Google (required — for sign-in and Google Drive storage) You need a Google OAuth 2.0 app to handle sign-in and Google Drive access. @@ -34,6 +37,16 @@ You need a Google OAuth 2.0 app to handle sign-in and Google Drive access. ``` 5. Copy the **Client ID** and **Client Secret** — you'll need them in the next step. +### GitHub (optional — for GitHub sign-in) + +1. Go to [GitHub Settings → Developer settings → OAuth Apps](https://github.com/settings/developers) and click **New OAuth App**. +2. Fill in **Application name** (e.g. `anki-cloud`), **Homepage URL** (e.g. `http://localhost:3000`), and set the **Authorization callback URL** to: + ``` + http://localhost:3000/v1/auth/callback/github + ``` +3. Click **Register application**, then click **Generate a new client secret**. +4. Copy the **Client ID** and **Client Secret** — you'll need them in the next step. + --- ## Step 2 — Clone and configure @@ -56,9 +69,13 @@ TOKEN_ENCRYPTION_KEY= # Public base URL of the API server — used by Better Auth for OAuth callbacks BETTER_AUTH_URL=http://localhost:3000 -# From Step 1 -GOOGLE_CLIENT_ID= -GOOGLE_CLIENT_SECRET= +# From Step 1 — Google +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= + +# From Step 1 — GitHub (optional) +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= # Must match the redirect URI you registered in Google Cloud Console GOOGLE_DRIVE_REDIRECT_URI=http://localhost:5173/v1/me/storage/connect/google/callback @@ -92,7 +109,7 @@ Once running: ## Step 4 — Sign in and connect Google Drive 1. Open `http://localhost:5173` in your browser. -2. Click **Sign in with Google** and complete the OAuth flow. +2. Click **Continue with Google** or **Continue with GitHub** and complete the OAuth flow. 3. Once signed in, click **Connect Google Drive** under Storage. 4. Authorize the Drive access — scope is `drive.file` (only files this app creates). 5. A `/AnkiCloudSync` folder is created in your Drive. Your deck data will live here. @@ -135,10 +152,13 @@ docker compose -f docker-compose.yml -f docker-compose.cloud.yml up ## Troubleshooting -**OAuth redirect mismatch error** +**OAuth redirect mismatch error (Google)** Verify the redirect URIs in Google Cloud Console exactly match those derived from `BETTER_AUTH_URL` and `GOOGLE_DRIVE_REDIRECT_URI` in your `.env`. For local dev: `http://localhost:3000/v1/auth/callback/google` (sign-in) and `http://localhost:5173/v1/me/storage/connect/google/callback` (Drive). Trailing slashes and `http` vs `https` matter. +**OAuth redirect mismatch error (GitHub)** +Verify the Authorization callback URL in your GitHub OAuth App exactly matches `{BETTER_AUTH_URL}/v1/auth/callback/github`. For local dev: `http://localhost:3000/v1/auth/callback/github`. Leave `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` empty in `.env` to disable the GitHub button entirely. + **Anki says "sync server not configured"** Ensure the sync URL in Anki is `http://localhost:8080` (no trailing slash) and the stack is running. diff --git a/web/src/App.tsx b/web/src/App.tsx index ef78134..d389672 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -136,6 +136,10 @@ function LoginPage() { await authClient.signIn.social({provider: "google", callbackURL: window.location.origin}); }; + const handleGitHubLogin = async () => { + await authClient.signIn.social({provider: "github", callbackURL: window.location.origin}); + }; + return (
@@ -145,6 +149,10 @@ function LoginPage() { Continue with Google +
); @@ -503,6 +511,19 @@ function SyncPasswordSection({ ); } +// ── GitHub Icon ────────────────────────────────────────────────────────────── + +function GitHubIcon() { + return ( + + + + ); +} + // ── Google Icon ─────────────────────────────────────────────────────────────── function GoogleIcon() { diff --git a/web/src/index.css b/web/src/index.css index 46ba5a3..8f810ad 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -290,6 +290,29 @@ body { background: #f9fafb; } +.btn-github { + display: flex; + align-items: center; + gap: 10px; + justify-content: center; + width: 100%; + padding: 11px 20px; + background: #24292f; + border: 1px solid #24292f; + border-radius: 6px; + font-size: 14px; + font-weight: 500; + color: #ffffff; + cursor: pointer; + text-decoration: none; + transition: background 0.1s; + margin-top: 10px; +} + +.btn-github:hover { + background: #32383f; +} + /* ── Storage Section ─────────────────────────────────────────────────────── */ .storage-row {