Skip to content

Commit c9a229b

Browse files
author
Lasim
committed
feat: add change password endpoint for authenticated users
- Implemented PUT /api/auth/email/change-password to allow users to change their password. - Added request validation using Zod schema for current and new passwords. - Integrated password hashing and verification using Argon2. - Created comprehensive error handling for various scenarios (e.g., incorrect current password, unauthorized access). - Added OpenAPI documentation for the new endpoint in api-spec.json and api-spec.yaml. - Developed end-to-end tests for the password change functionality. - Created unit tests for the change password route to ensure proper functionality and error handling.
1 parent 483fe3c commit c9a229b

File tree

10 files changed

+1216
-21
lines changed

10 files changed

+1216
-21
lines changed

services/backend/api-spec.json

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6237,6 +6237,178 @@
62376237
}
62386238
}
62396239
},
6240+
"/api/auth/email/change-password": {
6241+
"put": {
6242+
"summary": "Change user password",
6243+
"tags": [
6244+
"Authentication"
6245+
],
6246+
"description": "Allows authenticated users to change their password by providing their current password and a new password. Requires an active session.",
6247+
"requestBody": {
6248+
"content": {
6249+
"application/json": {
6250+
"schema": {
6251+
"type": "object",
6252+
"properties": {
6253+
"current_password": {
6254+
"type": "string",
6255+
"minLength": 1
6256+
},
6257+
"new_password": {
6258+
"type": "string",
6259+
"minLength": 8,
6260+
"maxLength": 100
6261+
}
6262+
},
6263+
"required": [
6264+
"current_password",
6265+
"new_password"
6266+
],
6267+
"additionalProperties": false
6268+
}
6269+
}
6270+
},
6271+
"required": true
6272+
},
6273+
"security": [
6274+
{
6275+
"cookieAuth": []
6276+
}
6277+
],
6278+
"responses": {
6279+
"200": {
6280+
"description": "Password changed successfully",
6281+
"content": {
6282+
"application/json": {
6283+
"schema": {
6284+
"type": "object",
6285+
"properties": {
6286+
"success": {
6287+
"type": "boolean",
6288+
"description": "Indicates if the password change was successful"
6289+
},
6290+
"message": {
6291+
"type": "string",
6292+
"description": "Success message"
6293+
}
6294+
},
6295+
"required": [
6296+
"success",
6297+
"message"
6298+
],
6299+
"additionalProperties": false,
6300+
"description": "Password changed successfully"
6301+
}
6302+
}
6303+
}
6304+
},
6305+
"400": {
6306+
"description": "Bad Request - Invalid input or incorrect current password",
6307+
"content": {
6308+
"application/json": {
6309+
"schema": {
6310+
"type": "object",
6311+
"properties": {
6312+
"success": {
6313+
"type": "boolean",
6314+
"description": "Indicates if the operation was successful (false for errors)",
6315+
"default": false
6316+
},
6317+
"error": {
6318+
"type": "string",
6319+
"description": "Error message describing what went wrong"
6320+
}
6321+
},
6322+
"required": [
6323+
"error"
6324+
],
6325+
"additionalProperties": false,
6326+
"description": "Bad Request - Invalid input or incorrect current password"
6327+
}
6328+
}
6329+
}
6330+
},
6331+
"401": {
6332+
"description": "Unauthorized - Authentication required",
6333+
"content": {
6334+
"application/json": {
6335+
"schema": {
6336+
"type": "object",
6337+
"properties": {
6338+
"success": {
6339+
"type": "boolean",
6340+
"description": "Indicates if the operation was successful (false for errors)",
6341+
"default": false
6342+
},
6343+
"error": {
6344+
"type": "string",
6345+
"description": "Error message describing what went wrong"
6346+
}
6347+
},
6348+
"required": [
6349+
"error"
6350+
],
6351+
"additionalProperties": false,
6352+
"description": "Unauthorized - Authentication required"
6353+
}
6354+
}
6355+
}
6356+
},
6357+
"403": {
6358+
"description": "Forbidden - Cannot change password for non-email users",
6359+
"content": {
6360+
"application/json": {
6361+
"schema": {
6362+
"type": "object",
6363+
"properties": {
6364+
"success": {
6365+
"type": "boolean",
6366+
"description": "Indicates if the operation was successful (false for errors)",
6367+
"default": false
6368+
},
6369+
"error": {
6370+
"type": "string",
6371+
"description": "Error message describing what went wrong"
6372+
}
6373+
},
6374+
"required": [
6375+
"error"
6376+
],
6377+
"additionalProperties": false,
6378+
"description": "Forbidden - Cannot change password for non-email users"
6379+
}
6380+
}
6381+
}
6382+
},
6383+
"500": {
6384+
"description": "Internal Server Error - Password change failed",
6385+
"content": {
6386+
"application/json": {
6387+
"schema": {
6388+
"type": "object",
6389+
"properties": {
6390+
"success": {
6391+
"type": "boolean",
6392+
"description": "Indicates if the operation was successful (false for errors)",
6393+
"default": false
6394+
},
6395+
"error": {
6396+
"type": "string",
6397+
"description": "Error message describing what went wrong"
6398+
}
6399+
},
6400+
"required": [
6401+
"error"
6402+
],
6403+
"additionalProperties": false,
6404+
"description": "Internal Server Error - Password change failed"
6405+
}
6406+
}
6407+
}
6408+
}
6409+
}
6410+
}
6411+
},
62406412
"/api/auth/github/login": {
62416413
"get": {
62426414
"summary": "Initiate GitHub OAuth login",

services/backend/api-spec.yaml

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4352,6 +4352,124 @@ paths:
43524352
- error
43534353
additionalProperties: false
43544354
description: Internal Server Error - An unexpected error occurred on the server.
4355+
/api/auth/email/change-password:
4356+
put:
4357+
summary: Change user password
4358+
tags:
4359+
- Authentication
4360+
description: Allows authenticated users to change their password by providing
4361+
their current password and a new password. Requires an active session.
4362+
requestBody:
4363+
content:
4364+
application/json:
4365+
schema:
4366+
type: object
4367+
properties:
4368+
current_password:
4369+
type: string
4370+
minLength: 1
4371+
new_password:
4372+
type: string
4373+
minLength: 8
4374+
maxLength: 100
4375+
required:
4376+
- current_password
4377+
- new_password
4378+
additionalProperties: false
4379+
required: true
4380+
security:
4381+
- cookieAuth: []
4382+
responses:
4383+
"200":
4384+
description: Password changed successfully
4385+
content:
4386+
application/json:
4387+
schema:
4388+
type: object
4389+
properties:
4390+
success:
4391+
type: boolean
4392+
description: Indicates if the password change was successful
4393+
message:
4394+
type: string
4395+
description: Success message
4396+
required:
4397+
- success
4398+
- message
4399+
additionalProperties: false
4400+
description: Password changed successfully
4401+
"400":
4402+
description: Bad Request - Invalid input or incorrect current password
4403+
content:
4404+
application/json:
4405+
schema:
4406+
type: object
4407+
properties:
4408+
success:
4409+
type: boolean
4410+
description: Indicates if the operation was successful (false for errors)
4411+
default: false
4412+
error:
4413+
type: string
4414+
description: Error message describing what went wrong
4415+
required:
4416+
- error
4417+
additionalProperties: false
4418+
description: Bad Request - Invalid input or incorrect current password
4419+
"401":
4420+
description: Unauthorized - Authentication required
4421+
content:
4422+
application/json:
4423+
schema:
4424+
type: object
4425+
properties:
4426+
success:
4427+
type: boolean
4428+
description: Indicates if the operation was successful (false for errors)
4429+
default: false
4430+
error:
4431+
type: string
4432+
description: Error message describing what went wrong
4433+
required:
4434+
- error
4435+
additionalProperties: false
4436+
description: Unauthorized - Authentication required
4437+
"403":
4438+
description: Forbidden - Cannot change password for non-email users
4439+
content:
4440+
application/json:
4441+
schema:
4442+
type: object
4443+
properties:
4444+
success:
4445+
type: boolean
4446+
description: Indicates if the operation was successful (false for errors)
4447+
default: false
4448+
error:
4449+
type: string
4450+
description: Error message describing what went wrong
4451+
required:
4452+
- error
4453+
additionalProperties: false
4454+
description: Forbidden - Cannot change password for non-email users
4455+
"500":
4456+
description: Internal Server Error - Password change failed
4457+
content:
4458+
application/json:
4459+
schema:
4460+
type: object
4461+
properties:
4462+
success:
4463+
type: boolean
4464+
description: Indicates if the operation was successful (false for errors)
4465+
default: false
4466+
error:
4467+
type: string
4468+
description: Error message describing what went wrong
4469+
required:
4470+
- error
4471+
additionalProperties: false
4472+
description: Internal Server Error - Password change failed
43554473
/api/auth/github/login:
43564474
get:
43574475
summary: Initiate GitHub OAuth login

services/backend/src/hooks/authHook.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { FastifyRequest, FastifyReply, HookHandlerDoneFunction } from 'fastify';
1+
import type { FastifyRequest, FastifyReply } from 'fastify';
22
import { getLucia } from '../lib/lucia';
33
import { getDbStatus, getSchema, getDb } from '../db';
44
import { eq } from 'drizzle-orm';
@@ -127,12 +127,11 @@ export async function authHook(
127127
// Example of a hook that requires authentication
128128
export async function requireAuthHook(
129129
request: FastifyRequest,
130-
reply: FastifyReply,
131-
done: HookHandlerDoneFunction
130+
reply: FastifyReply
132131
) {
133132
// This hook assumes authHook has already run and populated request.user/session
134133
if (!request.user || !request.session) {
135134
return reply.status(401).send({ error: 'Unauthorized: Authentication required.' });
136135
}
137-
return done();
136+
// No need to call done() in modern Fastify async hooks
138137
}

0 commit comments

Comments
 (0)