File size: 21,542 Bytes
012d0ac | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 | # Phase 5 — Authentication Architecture Plan
**Project:** Bayan (بيان)
**Date:** 2026-06-15
**Status:** Architecture plan only — no implementation
**Provider:** Supabase Auth
---
## Executive Summary
Phase 5 adds **identity only**: who is using the app. It does **not** add document persistence, cloud sync, or backend authorization for AI routes. That belongs to Phase 6.
**Supported methods:**
| Method | Supabase mechanism |
|--------|-------------------|
| Continue as Guest | `signInAnonymously()` |
| Continue with Google | `signInWithOAuth({ provider: 'google' })` |
**Explicitly out of scope:** email/password, password reset, email verification, roles, admin panel.
**Architecture principle:** Auth wraps the existing SPA shell. **Zero changes** to `renderer.js`, `selection.js`, or editor analyze/render/selection flows. Editor continues to use `getEditorText()` / `loadDocumentText()` unchanged.
---
## Current State (Baseline)
| Layer | Today |
|-------|-------|
| Frontend | Single `index.html` + vanilla JS modules, no auth |
| Editor | `editor.js` → `selection.js` + `renderer.js` |
| Backend | Flask `app.py`, open CORS, no JWT validation |
| Persistence | Browser-only (contenteditable + localStorage for theme) |
| Login UI | Non-functional marketing placeholders |
Phase 5 introduces Supabase on the **client** first. Flask AI endpoints remain public until Phase 6 optionally adds JWT checks.
---
# 1. Authentication Flow
## 1.1 Guest User Flow
**Intent:** Immediate access with zero friction; obtain a real Supabase `user.id` for Phase 6 FK relationships.
```
User opens Bayan
↓
Auth bootstrap: getSession()
↓
No session?
↓
Show Auth Gate (modal or dedicated strip — not blocking editor forever)
↓
User clicks "المتابعة كضيف"
↓
supabase.auth.signInAnonymously()
↓
Session created (anonymous user in auth.users)
↓
Ensure profile row exists (Phase 5 hook or Phase 6 trigger)
↓
Hide auth gate → showPage('editor') [recommended for demo]
↓
Editor works exactly as Phase 1/4 (local contenteditable only)
```
**Guest identity:**
- Supabase assigns `auth.users.id` (UUID)
- `is_anonymous = true` in user metadata
- Display label: **"ضيف"** or **"مستخدم ضيف"**
**Local editor content:** Unchanged — still in DOM only. Phase 6 will persist using this `user.id`.
**Optional shortcut (demo mode):** Auto-call `signInAnonymously()` on first visit without showing gate — show gate only when user explicitly wants Google. **Recommendation:** Show gate once; remember choice in `sessionStorage` flag `bayan-auth-dismissed` only for UX, not for security.
---
## 1.2 Google Login Flow
**Intent:** Upgrade guest to identified user, or sign in directly.
### Path A — Guest upgrades to Google
```
Guest session active (anonymous)
↓
User clicks "المتابعة باستخدام Google"
↓
If anonymous: linkIdentity OR signInWithOAuth with data migration plan
↓
OAuth redirect → Google consent
↓
Redirect to app callback URL
↓
Supabase exchanges code → session with Google identity
↓
Same or new user.id (see Guest-to-User upgrade in §6)
↓
Update UI: show name, avatar, "مسجل عبر Google"
↓
Phase 6: migrate local/guest documents to user.id
```
**Recommended Supabase approach:** Use **`linkIdentity({ provider: 'google' })`** when upgrading from anonymous session — preserves `user.id` and simplifies Phase 6 FK consistency.
### Path B — Direct Google (no prior session)
```
No session → User clicks Google
↓
signInWithOAuth({ provider: 'google' })
↓
OAuth redirect → return → session established
↓
Profile row created
↓
Enter app
```
**Redirect URLs required:**
- Local: `http://localhost:5000/` (or dedicated `/auth/callback` hash route)
- Production: `https://<your-domain>/`
Supabase JS v2 handles hash fragment tokens on return automatically when `detectSessionInUrl: true`.
---
## 1.3 Logout Flow
```
User opens account menu → "تسجيل الخروج"
↓
Confirm dialog (optional but recommended if editor has unsaved text)
↓
supabase.auth.signOut()
↓
Clear client auth state:
- window.__bayanUser = null
- Do NOT clear editor text (Phase 5) — warn user instead
↓
Show Auth Gate again
↓
User chooses Guest or Google again
```
**Phase 5 rule:** Logout clears **session only**, not editor content. Phase 6 adds "save before logout?" when cloud sync exists.
---
## 1.4 Session Restoration Flow
```
Page load (before or parallel to initEditor)
↓
Load Supabase client with env config
↓
const { data: { session } } = await supabase.auth.getSession()
↓
If session:
- Parse user (anonymous vs Google via app_metadata / identities)
- updateAuthUI(session.user)
- Skip auth gate
Else:
- Show auth gate
↓
supabase.auth.onAuthStateChange((event, session) => { ... })
- SIGNED_IN → update UI
- SIGNED_OUT → show gate
- TOKEN_REFRESHED → silent
- USER_UPDATED → refresh avatar/name
↓
Continue initTheme → initUI → initEditor → initDocuments
```
**Order constraint:** Auth bootstrap must not block editor script loading. Editor modules load synchronously; auth can resolve in parallel and only gate **optional** UI (nav account menu), not editor internals.
**Recommended init order in `index.html`:**
```
initAuth() → async, sets session state
initTheme() → sync
initUI() → sync (auth UI updates when session ready)
initEditor() → sync, unchanged
initDocuments() → sync, unchanged
```
---
# 2. UI Design
## 2.1 Required UI Screens
| Screen | Purpose | Blocks editor? |
|--------|---------|----------------|
| **Auth Gate** | Guest / Google choice | Soft gate — overlay or inline banner; recommend dismissible after choice |
| **Account Menu** | Logged-in state, logout | No |
| **Auth Loading** | OAuth return / session restore | Brief spinner only |
**No separate login page required** — modal/sheet aligned with Phase 2 patterns keeps changes minimal.
## 2.2 Required Buttons
| Button (Arabic) | Action |
|-----------------|--------|
| **المتابعة كضيف** | `signInAnonymously()` |
| **المتابعة باستخدام Google** | `signInWithOAuth({ provider: 'google' })` |
| **تسجيل الخروج** | `signOut()` (in account menu) |
**Optional (Phase 5):**
| Button | Action |
|--------|--------|
| **تسجيل الدخول** (nav) | Opens auth gate if no session |
| **ربط حساب Google** | Shown to guest users in account menu → `linkIdentity` |
## 2.3 User Menu Design
**Desktop (nav bar, left of theme toggle in RTL layout):**
```
[ Avatar or guest icon ▾ ]
├─ Display name / "ضيف"
├─ Auth provider badge (Google icon or "ضيف")
├─ ─────────────
└─ تسجيل الخروج
```
**Guest-specific menu item:**
```
└─ ربط حساب Google (only when is_anonymous)
```
**Styling:** Reuse Phase 2 `.doc-dropdown` / `.nav-link` patterns from `components.css`. New classes: `.auth-menu`, `.auth-gate`, `.auth-btn-google`, `.auth-btn-guest`.
## 2.4 Mobile Behavior
| Element | Mobile behavior |
|---------|-----------------|
| Auth Gate | Full-width bottom sheet (reuse `#bottom-sheet` pattern) OR centered modal |
| Account menu | Inside `#mobile-drawer` below nav links + duplicate compact trigger in nav bar |
| Google OAuth | Same redirect flow; ensure mobile browser allows popup/redirect |
| Guest button | Full-width, min-height 44px (existing touch target standard) |
**Do not** add auth to editor toolbar — keep import/export/editor actions untouched.
---
# 3. Frontend Architecture
## 3.1 New Files to Create
```
src/js/auth/
config.js # Reads SUPABASE_URL + SUPABASE_ANON_KEY (injected or meta tags)
client.js # Single Supabase client singleton
session.js # getSession, onAuthStateChange, isGuest, isGoogleUser
auth.js # signInAsGuest, signInWithGoogle, signOut, linkGoogle
auth-ui.js # Auth gate, account menu, updateAuthUI()
```
**Vendor (offline demo reliability):**
```
src/js/vendor/supabase.min.js # @supabase/supabase-js UMD build
```
**Optional Phase 5 SQL (documentation only, applied in Supabase dashboard):**
```
supabase/migrations/001_profiles.sql # profiles table + trigger — prep for Phase 6
```
## 3.2 Files to Modify
| File | Change | Risk |
|------|--------|------|
| `src/index.html` | Auth gate markup, account menu, script tags, `initAuth()` in DOMContentLoaded | Low |
| `src/css/components.css` | Auth gate, Google button, account dropdown styles | Low |
| `src/js/ui.js` | Optional: call `updateAuthUI()` from shared init — or keep all auth UI in `auth-ui.js` | Low |
| `.env.example` | Document Supabase vars | None |
## 3.3 Files That Must NOT Change (Phase 5)
| File | Reason |
|------|--------|
| `src/js/renderer.js` | Phase 1 stable |
| `src/js/selection.js` | Phase 1 stable |
| `src/js/editor.js` | No auth in analyze/render/apply paths |
| `src/js/documents/*` | Import/export unchanged |
| `src/app.py` | No JWT required in Phase 5 |
## 3.4 Integration with Editor Architecture
Auth lives **above** the editor, not inside it:
```
┌─────────────────────────────────────────┐
│ Nav + Auth (Phase 5) │
├─────────────────────────────────────────┤
│ Editor Page │
│ getEditorText() / loadDocumentText() │ ← unchanged
│ renderer.js / selection.js │ ← unchanged
├─────────────────────────────────────────┤
│ Flask /api/* (no auth Phase 5) │
└─────────────────────────────────────────┘
```
**Phase 6 hook point (prepare now, implement later):**
```javascript
// Future — NOT Phase 5
window.__bayanAuth = {
userId: session?.user?.id ?? null,
isGuest: session?.user?.is_anonymous ?? true,
getAccessToken: () => session?.access_token
};
```
Editor and documents modules **must not import** auth modules. Only nav/shell reads auth state.
---
# 4. Supabase Setup
## 4.1 Required Supabase Project Configuration
| Setting | Value |
|---------|-------|
| Project region | Closest to users (e.g. EU if MENA/Europe audience) |
| Auth providers | **Google enabled**; Email disabled |
| Anonymous sign-ins | **Enabled** (Dashboard → Authentication → Providers → Anonymous) |
| Email auth | **Disabled** |
| Phone auth | **Disabled** |
| Confirm email | N/A (email not used) |
## 4.2 Google OAuth Configuration
**Google Cloud Console:**
1. Create OAuth 2.0 Client ID (Web application)
2. Authorized JavaScript origins:
- `http://localhost:5000`
- `https://<production-domain>`
3. Authorized redirect URIs:
- `https://<project-ref>.supabase.co/auth/v1/callback`
**Supabase Dashboard → Authentication → Providers → Google:**
- Paste Google Client ID and Client Secret
- Enable Google provider
## 4.3 Redirect URL Configuration (Supabase)
**Authentication → URL Configuration:**
| Field | Example |
|-------|---------|
| Site URL | `http://localhost:5000` (dev) |
| Redirect URLs | `http://localhost:5000/**`, `https://your-domain.com/**` |
## 4.4 Required Environment Variables
**Frontend (public — safe to expose anon key):**
| Variable | Purpose |
|----------|---------|
| `SUPABASE_URL` | `https://<ref>.supabase.co` |
| `SUPABASE_ANON_KEY` | Public anon key |
**Delivery mechanism (pick one):**
| Approach | Demo-friendly? |
|----------|----------------|
| Inline `<meta name="supabase-url" content="...">` in `index.html` | ✅ Simplest for graduation |
| `src/js/auth/config.js` with placeholders replaced at deploy | ✅ |
| Flask injects vars into template | ⚠️ Requires templating change |
**Never expose:**
| Variable | Location |
|----------|----------|
| `SUPABASE_SERVICE_ROLE_KEY` | Backend only, Phase 6+ |
**Backend Phase 5:** No new env vars required. Phase 6 adds optional `SUPABASE_JWT_SECRET` for Flask JWT verification.
## 4.5 Local Development
```
# .env.example (documentation)
SUPABASE_URL=https://xxxx.supabase.co
SUPABASE_ANON_KEY=eyJ...
```
Run Flask as today: `python run_app.py` — Supabase auth runs entirely in browser.
---
# 5. Database Preparation (Phase 6 Readiness)
Phase 5 **defines schema** and optionally applies **profiles-only** migration. Full CRUD for documents/summaries/settings is Phase 6.
## 5.1 Table: `profiles`
Links public profile data to `auth.users`.
```sql
-- Conceptual — implement in Phase 5 or early Phase 6
create table public.profiles (
id uuid primary key references auth.users(id) on delete cascade,
display_name text,
avatar_url text,
auth_provider text not null default 'anonymous', -- 'anonymous' | 'google'
created_at timestamptz default now(),
updated_at timestamptz default now()
);
```
**Trigger on signup:**
```sql
-- After insert on auth.users → insert into profiles
-- Set auth_provider from raw_app_meta_data or identities
```
Phase 5 can deploy this trigger so every guest/Google user has a profile row before Phase 6.
## 5.2 Table: `documents` (Phase 6 — schema only in Phase 5 docs)
```sql
create table public.documents (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references auth.users(id) on delete cascade,
title text default 'مستند بدون عنوان',
content text not null,
word_count int,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
```
**Phase 5 prep:** Ensure `user_id` type matches Supabase auth UUID. No frontend reads/writes yet.
## 5.3 Table: `summaries` (Phase 6)
```sql
create table public.summaries (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references auth.users(id) on delete cascade,
document_id uuid references public.documents(id) on delete set null,
source_text_hash text, -- optional dedup
summary_text text not null,
length_preset text, -- short | medium | long
created_at timestamptz default now()
);
```
**Phase 5 prep:** Summarize tab in `index.html` continues calling Flask directly. Phase 6 adds optional save after generate.
## 5.4 Table: `settings` (Phase 6)
```sql
create table public.settings (
user_id uuid primary key references auth.users(id) on delete cascade,
theme text default 'dark', -- mirrors bayan-theme localStorage
language text default 'ar',
preferences jsonb default '{}',
updated_at timestamptz default now()
);
```
**Phase 5 prep:** Theme stays in `localStorage` (`bayan-theme`). Phase 6 syncs on login.
## 5.5 Row-Level Security (enable in Phase 5, policies Phase 6)
```sql
alter table public.profiles enable row level security;
-- Phase 6: create policy "users read/update own profile"
-- auth.uid() = id
```
Document/summary/settings RLS policies written in Phase 6 when tables are created.
---
# 6. Security Considerations
## 6.1 Session Handling
| Topic | Approach |
|-------|----------|
| Session storage | Supabase JS default: `localStorage` key `sb-<ref>-auth-token` |
| Token refresh | Automatic via Supabase client |
| Session lifetime | Supabase project JWT expiry settings (default 1 hour access, refresh token longer) |
| Tab sync | `onAuthStateChange` handles multi-tab |
**Risk:** XSS can steal tokens from localStorage — mitigated by Phase 5 hardening (summary XSS fix) before auth ships.
## 6.2 Token Storage
| Token | Frontend | Backend Phase 5 |
|-------|----------|-----------------|
| Access token (JWT) | Supabase client memory + localStorage | Not used |
| Refresh token | localStorage | Not used |
| Anon key | config.js / meta tag | N/A |
| Service role | **Never** | Phase 6 server only |
**Do not** manually copy JWT into cookies in Phase 5.
## 6.3 Logout Behavior
- `signOut()` clears Supabase session and localStorage auth entry
- Editor text remains (warn user)
- Phase 6: optional `signOut({ scope: 'local' })` vs global — use default global sign-out
## 6.4 Guest-to-User Upgrade Path
**Recommended: Supabase Anonymous + Link Identity**
```
Guest (anonymous UUID = abc-123)
↓
User clicks "ربط حساب Google"
↓
linkIdentity({ provider: 'google' })
↓
Same user.id = abc-123 (identity linked)
↓
Phase 6 documents.user_id = abc-123 — no migration needed
```
**Fallback if linkIdentity unavailable:**
```
signInWithOAuth (new session, new user.id = xyz-789)
↓
Phase 6 migration job: copy local documents from guest id to new id
↓
More complex — avoid if possible
```
**Phase 5 deliverable:** Document chosen strategy in code comments and Phase 6 plan.
## 6.5 AI API Security (Phase 5 stance)
Flask `/api/analyze`, `/api/summarize` remain **unauthenticated** in Phase 5.
**Rationale:** Minimize changes; models are local; graduation demo runs on localhost.
**Phase 6 option:** Optional JWT middleware — reject requests without valid Supabase JWT for rate limiting per user.
---
# 7. Migration Plan (Implementation Order)
## Step 1 — Supabase Project Setup (0.5 day)
- [ ] Create Supabase project
- [ ] Enable Anonymous auth
- [ ] Enable Google provider + Google Cloud OAuth
- [ ] Configure redirect URLs
- [ ] Copy URL + anon key to `.env.example`
**Risk:** Low
**Demo impact:** None until frontend wired
---
## Step 2 — Database Prep: Profiles Only (0.5 day)
- [ ] Apply `profiles` table migration
- [ ] Create `handle_new_user()` trigger on `auth.users`
- [ ] Enable RLS on `profiles`; policy: user reads/updates own row
- [ ] Verify guest signup creates profile with `auth_provider = 'anonymous'`
**Risk:** Low
**Prepares Phase 6:** ✅ user rows exist
---
## Step 3 — Frontend Auth Module (1 day)
- [ ] Add `supabase.min.js` vendor copy + CDN fallback
- [ ] Create `src/js/auth/*` modules
- [ ] Implement `initAuth()`, session restore, `onAuthStateChange`
- [ ] Expose `window.__bayanAuth` read-only facade
**Risk:** Low — isolated new files
**Must not touch:** renderer.js, selection.js, editor.js
---
## Step 4 — Auth UI (1 day)
- [ ] Add auth gate markup to `index.html`
- [ ] Add account menu to nav (desktop + mobile drawer)
- [ ] Style in `components.css` (match Phase 2 tokens)
- [ ] Wire buttons: Guest, Google, Logout, Link Google (guest only)
**Risk:** Low
**Demo impact:** Visible login flow
---
## Step 5 — Init Integration (0.5 day)
- [ ] Update `DOMContentLoaded`: `initAuth()` before or parallel to `initEditor()`
- [ ] Ensure auth gate does not prevent `initEditor()` / `initDocuments()`
- [ ] OAuth return: handle URL hash cleanup if needed
**Risk:** Medium — test OAuth redirect carefully
**Regression test:** `node test_renderer.js`, manual editor typing
---
## Step 6 — Guest & Google E2E Testing (0.5 day)
- [ ] Guest: anonymous session, profile row created
- [ ] Google: OAuth round-trip on localhost
- [ ] Guest → Google linkIdentity preserves user.id
- [ ] Logout → re-auth both paths
- [ ] Session restore after page refresh
- [ ] Mobile drawer account menu
**Risk:** Medium (OAuth redirect URLs)
**Demo impact:** Critical path validated
---
## Step 7 — Documentation & Phase 6 Handoff (0.5 day)
- [ ] Update README with Supabase setup steps
- [ ] Document `documents`, `summaries`, `settings` schema (not implemented)
- [ ] Document RLS policy templates for Phase 6
- [ ] Add auth section to graduation demo checklist
**Total estimate:** **4–5 days** (single developer)
---
# 8. Risk Summary
| Risk | Level | Mitigation |
|------|-------|------------|
| Breaking editor on auth init | Low | Auth module isolated; editor init unchanged |
| OAuth redirect misconfiguration | Medium | Test localhost + production URLs early |
| Anonymous users disabled in Supabase | Low | Verify dashboard setting before coding |
| XSS + localStorage token theft | Medium | Ship Priority 1 security fixes before Phase 5 |
| linkIdentity browser support | Low | Test on Chrome/Edge; document fallback |
| Scope creep (email auth, roles) | Medium | Strict scope doc; code review checklist |
---
# 9. Phase 5 Success Criteria
| Criterion | Required |
|-----------|----------|
| Guest can enter app and use editor | ✅ |
| Google login works via OAuth | ✅ |
| Logout works | ✅ |
| Session restores on refresh | ✅ |
| Guest can link Google without new user.id | ✅ (linkIdentity) |
| `renderer.js` / `selection.js` unchanged | ✅ |
| `editor.js` analyze/render/apply unchanged | ✅ |
| Import/export unchanged | ✅ |
| Profile row exists for each auth user | ✅ |
| No document DB reads/writes yet | ✅ (Phase 6) |
| No email/password UI or backend | ✅ |
---
# 10. Phase 6 Preview (Out of Scope for Phase 5)
Phase 6 will use Phase 5 `user.id` to:
1. Save/load editor content to `documents` table
2. Persist summaries to `summaries` table
3. Sync theme to `settings` table
4. Optionally protect Flask routes with JWT
5. Migrate guest local content on Google link if needed
Phase 5 must deliver a **stable `user.id`** for every session — that is the only database contract required now.
---
*End of Phase 5 Authentication Architecture Plan*
|