Sync user-accounts delta specs to main specs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
34d58948a3
commit
448b67199f
9 changed files with 703 additions and 6 deletions
125
openspec/specs/user-auth/spec.md
Normal file
125
openspec/specs/user-auth/spec.md
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
## ADDED Requirements
|
||||
|
||||
### Requirement: Auth.js v5 configuration
|
||||
The system SHALL configure Auth.js v5 (next-auth@5) in `src/auth.ts` with JWT session strategy, Credentials provider, and Google OAuth provider. The configuration SHALL export `handlers`, `auth`, `signIn`, and `signOut` functions.
|
||||
|
||||
#### Scenario: Auth config exports
|
||||
- **WHEN** `src/auth.ts` is imported
|
||||
- **THEN** it exports `handlers` (GET/POST), `auth` (session getter), `signIn`, and `signOut` functions
|
||||
|
||||
#### Scenario: JWT session strategy
|
||||
- **WHEN** a user authenticates successfully
|
||||
- **THEN** a JWT token is issued containing `user.id`, `user.email`, and `user.name`
|
||||
- **AND** no database session record is created
|
||||
|
||||
#### Scenario: JWT callbacks embed user ID
|
||||
- **WHEN** the JWT callback fires after sign-in
|
||||
- **THEN** the `token.id` is set to the user's UUID from the database
|
||||
- **AND** the session callback maps `token.id` to `session.user.id`
|
||||
|
||||
### Requirement: Credentials provider with email/password
|
||||
The Credentials provider SHALL accept `email` and `password` fields. The `authorize` function SHALL look up the user by email in the `users` table, verify the password against the stored `password_hash` using bcryptjs, and return the user object on success or `null` on failure.
|
||||
|
||||
#### Scenario: Valid credentials
|
||||
- **WHEN** a user submits correct email and password
|
||||
- **THEN** the authorize function returns the user object with `id`, `email`, `name`
|
||||
- **AND** a JWT session is created
|
||||
|
||||
#### Scenario: Invalid password
|
||||
- **WHEN** a user submits correct email but wrong password
|
||||
- **THEN** the authorize function returns `null`
|
||||
- **AND** no session is created
|
||||
|
||||
#### Scenario: Non-existent email
|
||||
- **WHEN** a user submits an email that does not exist in the database
|
||||
- **THEN** the authorize function returns `null`
|
||||
|
||||
#### Scenario: OAuth-only user attempts password login
|
||||
- **WHEN** a user who registered via Google (no password_hash) submits their email with any password
|
||||
- **THEN** the authorize function returns `null`
|
||||
|
||||
### Requirement: Google OAuth provider
|
||||
The Google OAuth provider SHALL be configured with `AUTH_GOOGLE_ID` and `AUTH_GOOGLE_SECRET` environment variables. On first Google sign-in, the system SHALL create a new user record with `provider: 'google'` and `provider_account_id` set to the Google sub ID. On subsequent sign-ins, the system SHALL find the existing user by email.
|
||||
|
||||
#### Scenario: First Google sign-in creates user
|
||||
- **WHEN** a user signs in with Google for the first time
|
||||
- **THEN** a new user record is created with `provider: 'google'`, `password_hash: null`, `provider_account_id` set to the Google sub ID, and `name`/`email`/`image` from the Google profile
|
||||
|
||||
#### Scenario: Returning Google sign-in
|
||||
- **WHEN** a user signs in with Google and their email already exists in the database
|
||||
- **THEN** the existing user is returned and no duplicate is created
|
||||
|
||||
#### Scenario: Missing Google credentials
|
||||
- **WHEN** `AUTH_GOOGLE_ID` or `AUTH_GOOGLE_SECRET` environment variables are not set
|
||||
- **THEN** the Google provider SHALL not be included in the providers list and email/password login remains available
|
||||
|
||||
### Requirement: Auth API route handler
|
||||
The system SHALL create `src/app/api/auth/[...nextauth]/route.ts` that exports the GET and POST handlers from `src/auth.ts`.
|
||||
|
||||
#### Scenario: Auth routes respond
|
||||
- **WHEN** a request is made to `/api/auth/signin`, `/api/auth/signout`, `/api/auth/session`, or `/api/auth/callback/google`
|
||||
- **THEN** the NextAuth handler processes the request
|
||||
|
||||
### Requirement: Registration API endpoint
|
||||
The system SHALL provide a `POST /api/auth/register` endpoint that creates a new user account. The endpoint SHALL accept `{ name, email, password }` in the request body. The password SHALL be hashed with bcryptjs (salt rounds: 10) before storage. The email SHALL be checked for uniqueness.
|
||||
|
||||
#### Scenario: Successful registration
|
||||
- **WHEN** POST /api/auth/register is called with valid name, email, and password (min 8 characters)
|
||||
- **THEN** a new user is created with hashed password, `provider: 'credentials'`
|
||||
- **AND** the response is HTTP 201 with `{ id, email, name }`
|
||||
|
||||
#### Scenario: Duplicate email
|
||||
- **WHEN** POST /api/auth/register is called with an email that already exists
|
||||
- **THEN** the response is HTTP 409 with `{ error: "Email already registered" }`
|
||||
|
||||
#### Scenario: Invalid password length
|
||||
- **WHEN** POST /api/auth/register is called with a password shorter than 8 characters
|
||||
- **THEN** the response is HTTP 400 with `{ error: "Password must be at least 8 characters" }`
|
||||
|
||||
#### Scenario: Missing required fields
|
||||
- **WHEN** POST /api/auth/register is called without email or password
|
||||
- **THEN** the response is HTTP 400 with `{ error: "Email and password are required" }`
|
||||
|
||||
### Requirement: Auth proxy for route protection
|
||||
The system SHALL create a `proxy.ts` file at the project root that uses Auth.js `auth()` to check JWT sessions. The proxy SHALL enforce authentication on protected routes.
|
||||
|
||||
#### Scenario: Unauthenticated access to /app
|
||||
- **WHEN** an unauthenticated user requests any path under `/app` or `/app/*`
|
||||
- **THEN** the proxy redirects to `/login`
|
||||
|
||||
#### Scenario: Unauthenticated access to /api
|
||||
- **WHEN** an unauthenticated user requests any path under `/api/*` except `/api/auth/*` and `/api/health`
|
||||
- **THEN** the proxy returns HTTP 401
|
||||
|
||||
#### Scenario: Authenticated access to /login
|
||||
- **WHEN** an authenticated user requests `/login` or `/register`
|
||||
- **THEN** the proxy redirects to `/app`
|
||||
|
||||
#### Scenario: Public routes pass through
|
||||
- **WHEN** any user requests `/`, `/login`, `/register`, `/api/auth/*`, or `/api/health`
|
||||
- **THEN** the proxy allows the request to proceed without auth check
|
||||
|
||||
### Requirement: Auth helper for API routes
|
||||
The system SHALL provide a `getAuthUser()` helper function in `src/lib/auth.ts` that extracts the authenticated user from the current session. All protected API routes SHALL call this function.
|
||||
|
||||
#### Scenario: Authenticated request
|
||||
- **WHEN** `getAuthUser()` is called in an API route with a valid JWT session
|
||||
- **THEN** it returns `{ id, email, name }` from the session
|
||||
|
||||
#### Scenario: Unauthenticated request
|
||||
- **WHEN** `getAuthUser()` is called in an API route without a valid session
|
||||
- **THEN** it returns `null`
|
||||
|
||||
### Requirement: SessionProvider wrapper
|
||||
The system SHALL wrap the app layout with `next-auth/react` `SessionProvider` so client components can use the `useSession()` hook to access auth state.
|
||||
|
||||
#### Scenario: Session available in client components
|
||||
- **WHEN** a client component calls `useSession()` inside the protected app layout
|
||||
- **THEN** it receives `{ data: session, status: "authenticated" }` with `session.user.id`, `session.user.email`, `session.user.name`
|
||||
|
||||
### Requirement: npm dependencies for auth
|
||||
The project SHALL add `next-auth@5` (Auth.js v5), `bcryptjs`, and `@types/bcryptjs` to the dependencies.
|
||||
|
||||
#### Scenario: Dependencies installed
|
||||
- **WHEN** `package.json` is inspected
|
||||
- **THEN** `next-auth`, `bcryptjs` are in `dependencies` and `@types/bcryptjs` is in `devDependencies`
|
||||
Loading…
Add table
Add a link
Reference in a new issue