Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,23 @@ At minimum:
- avoid leaking stack traces or internal errors to clients
- follow least-privilege principles

For MVP authentication:

- use Google OIDC for registered-user login
- use server-side sessions stored in PostgreSQL
- place only an opaque, cryptographically random session ID in an `HttpOnly`
cookie
- set `Secure` in production and choose `SameSite`, domain, path, and expiration
settings intentionally
- do not introduce JWT-based application authentication unless the documented
architecture is deliberately changed
- enforce authentication and resource authorization on the server for every
protected request
- invalidate the server-side session on logout and reject expired sessions
- implement CSRF protection appropriate for cookie-based authentication
- use maintained OIDC and session-management libraries rather than implementing
security protocols or session primitives from scratch

Never commit:

- passwords
Expand Down Expand Up @@ -503,4 +520,4 @@ with:
Build the simplest production-quality solution that satisfies the current
requirements and leaves reasonable room for future evolution.

Production quality does not mean maximum complexity.
Production quality does not mean maximum complexity.
69 changes: 60 additions & 9 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -646,23 +646,74 @@ Never commit real credentials or secrets.

# 27. Authentication

MVP authentication should support:
Registered users authenticate with Google using OpenID Connect (OIDC).

- Google authentication
- guest access
After a successful OIDC flow, the API finds or creates the corresponding
GoForLift user and creates a server-side application session. Session records are
stored in PostgreSQL. The browser receives only a cryptographically random,
opaque session ID in an `HttpOnly` cookie; application authentication does not
use JWTs for the MVP.

The exact authentication implementation/library has not yet been finalized.
The authentication flow is conceptually:

Do not choose or implement an authentication provider solely based on assumption.
Browser
|
| Google OIDC login
v
Express API
|
| find/create user + create session
v
PostgreSQL

Browser <- HttpOnly session ID cookie <- Express API

The MVP authentication stack is:

Authentication architecture should be finalized when the authentication feature
is implemented.
- `openid-client` for OIDC discovery and Authorization Code Flow
- `express-session` for session lifecycle and cookie handling
- `connect-pg-simple` for PostgreSQL session persistence using the existing
`pg` connection pool
- `csrf-sync` for synchronizer-token CSRF protection

Drizzle owns the session-table migration. Runtime table creation through the
session store must remain disabled.

Do not add Passport around `openid-client` for the MVP. It would introduce an
additional authentication abstraction without solving a current requirement.
Do not retain Google provider tokens after login unless a later feature requires
access to a Google API.

The full decision, alternatives, and consequences are recorded in:

docs/decisions/0001-authentication-libraries.md

Session cookies must use:

- `HttpOnly`
- `Secure` in production
- an intentionally selected `SameSite` policy compatible with the deployment
topology and OIDC redirect flow
- a narrow path and domain scope where practical
- a defined expiration consistent with the server-side session

Logout invalidates the server-side session and clears the cookie. Expired and
invalid session IDs must not authenticate a request. Because authentication uses
cookies, the implementation must include appropriate CSRF protection for
state-changing requests.

Guest access remains unauthenticated and does not create a persistent session or
registered-user data.


## 27.1 Authorization

Authorization must occur on the backend.

Every protected request must resolve a valid server-side session and enforce
resource ownership or other applicable authorization rules. Authentication alone
does not grant access to every registered user's resources.

A user must not be able to access or modify another user's:

- routines
Expand Down Expand Up @@ -917,7 +968,7 @@ requirements.
| Structure | Feature-based / vertical |
| Architecture | Modular monolith |
| Active workout | Client-side until finish/completion |
| Authentication | Google + guest; implementation TBD |
| Authentication | Google OIDC + PostgreSQL-backed server-side sessions; opaque session ID in an HttpOnly cookie; no application JWT for MVP |
| Deployment | TBD |
| Android | Future client using the same API |

Expand All @@ -930,4 +981,4 @@ When choosing between two solutions, prefer the one that:
2. is easy to understand,
3. is easy to test,
4. preserves important future options,
5. introduces the least unnecessary complexity.
5. introduces the least unnecessary complexity.
50 changes: 48 additions & 2 deletions docs/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ otherwise make those features unnecessarily difficult.
At a high level:

User
|
+---- Authentication Sessions
|
+---- Routines
|
Expand Down Expand Up @@ -99,6 +101,38 @@ Guest users are not required to have persistent database records.
than assuming email is always the canonical external identity.


## 4.1 Authentication Sessions

### Purpose

Stores server-side sessions for registered users authenticated through Google
OIDC. The browser stores only an opaque, cryptographically random session ID in
an `HttpOnly` cookie. GoForLift does not use JWTs for application authentication
in the MVP.

### Fields

| Field | Type | Purpose |
|---|---|---|
| `id` | UUID or sufficiently strong opaque identifier | Session identifier placed in the browser cookie. If the implementation stores only a hash of the cookie value, this field contains that hash instead. |
| `user_id` | UUID, FK → `users.id` | User authenticated by the session. |
| `expires_at` | TIMESTAMPTZ | Absolute time after which the session is invalid. |
| `created_at` | TIMESTAMPTZ | When the session was created. |
| `last_accessed_at` | TIMESTAMPTZ, nullable | Optional timestamp for idle-expiration or session-management behavior if adopted during implementation. |

### Constraints and Lifecycle

- `id` is the primary key or uniquely indexed lookup value.
- `user_id` references `users.id`.
- Session identifiers must be generated using a cryptographically secure source.
- Expired sessions must not authenticate requests and should be removed
periodically.
- Logout deletes or invalidates the matching session.
- Deleting a user must invalidate that user's sessions.
- An index on `user_id` supports user-wide session revocation.
- An index on `expires_at` may support expiration cleanup.


# 5. Exercises

## Purpose
Expand Down Expand Up @@ -443,6 +477,7 @@ The initial persistent model contains:
| Table | Purpose |
|---|---|
| `users` | Registered user identity. |
| `sessions` | Server-side authentication sessions for registered users. |
| `exercises` | Built-in and user-created exercises. |
| `routines` | User-created workout routines. |
| `routine_exercises` | Exercise configuration within a routine. |
Expand All @@ -451,13 +486,20 @@ The initial persistent model contains:
| `workout_sets` | Sets actually completed during workouts. |
| `user_settings` | Persisted UI/user preferences. |

This gives the MVP **8 core tables**.
This gives the MVP **9 core tables**.

Do not add a progression table because progression is not part of the MVP.


# 15. Relationship Summary

users
|
| 1:N
v
sessions


users
|
| 1:N
Expand Down Expand Up @@ -577,6 +619,10 @@ Likely query patterns should guide additional indexes.

Potential candidates include:

sessions.user_id

sessions.expires_at

routines.user_id

exercises.owner_user_id
Expand Down Expand Up @@ -772,4 +818,4 @@ and:
↓
WORKOUT HISTORY

That distinction is fundamental to the GoForLift data model.
That distinction is fundamental to the GoForLift data model.
Loading