Implement task 6.1: Create PUT /api/auth/profile endpoint for updating user display name

- Create src/app/api/auth/profile/route.ts with PUT handler
- Validates user is authenticated (returns 401 if not)
- Validates request body has a non-empty name field
- Updates user's name in the database
- Returns 200 with updated user data

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marko Djordjevic 2026-02-20 10:20:20 +01:00
parent d4e92cf88f
commit c36ab7c146
27 changed files with 2699 additions and 2 deletions

View 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`