Change Password
Authenticated password change. Requires current password. All sessions invalidated on success.
POST /api/v1/auth/change-password — 🔑 User-auth (Bearer JWT) · Rate limit: 3 req / hour
Authenticated password change. Verifies the current password before applying the new one. All existing sessions are revoked on success — user is logged out everywhere except the device that performed the change.
This endpoint requires a valid JWT (Authorization: Bearer <accessToken>). It overrides the controller-level @Public() decorator via @SetMetadata(IS_PUBLIC_KEY, false).
Request
Headers
| Header | Value | Notes |
|---|---|---|
Authorization | Bearer <accessToken> | Required — JWT from /auth/login |
Content-Type | application/json | Required |
Body — ChangePasswordDto
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
currentPassword | string | ✓ | min 1 char | bcrypt-compared against User.passwordHash |
newPassword | string | ✓ | 8–128 chars; must include upper + lower + digit | Must differ from current; bcrypt hashed |
Response
200 OK — SuccessOnlyResponseDto
{ "success": true }Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
400 | (DTO validation) | New password fails policy (length, character class) |
400 | auth.change_password.same_as_current | New password matches current password |
401 | auth.change_password.invalid_current | currentPassword does not match User.passwordHash |
401 | (no JWT or invalid) | Not authenticated |
429 | (throttle) | Rate limit exceeded (3 req/hour) |
Side effects
- Verify
currentPasswordagainstUser.passwordHash(bcrypt compare). - Confirm new password is different from current (bcrypt compare again).
- Hash new password (bcrypt).
- Atomic transaction: update
User.passwordHash, revoke ALLSessionrecords EXCEPT the current one (the device performing the change stays logged in). - Send security alert email: "Your password was changed on
{date}from{device}". - Audit log:
auth.change_password.success.
Code samples
curl -X POST https://api.bio.re/api/v1/auth/change-password \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <accessToken>' \
-d '{
"currentPassword": "OldP@ss123",
"newPassword": "NewSecureP@ss456"
}'async function changePassword(currentPassword: string, newPassword: string, accessToken: string): Promise<void> {
const res = await fetch('https://api.bio.re/api/v1/auth/change-password', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
},
body: JSON.stringify({ currentPassword, newPassword }),
});
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Change failed'), {
code: json?.error?.code,
});
}
}import { useMutation } from '@tanstack/react-query';
export function useChangePassword() {
return useMutation({
mutationFn: async (input: { currentPassword: string; newPassword: string }) => {
const res = await fetch('/api/v1/auth/change-password', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getAccessToken()}`,
},
body: JSON.stringify(input),
});
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Change failed'), {
code: json?.error?.code,
i18nKey: json?.error?.i18nKey,
});
}
},
onSuccess: () => {
toast.success(t('auth.change_password.success'));
},
});
}Try it
Authorization
bearer In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
curl -X POST "https://loading/api/v1/auth/change-password" \ -H "Content-Type: application/json" \ -d '{ "currentPassword": "string", "newPassword": "stringst" }'{
"success": true
}{
"success": false,
"error": {
"code": "AUTH_UNAUTHORIZED",
"message": "Invalid credentials",
"i18nKey": "auth.login.invalid_credentials",
"i18nVars": {
"field": "email"
},
"details": [
{
"message": "email must be an email"
}
],
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}{
"success": false,
"error": {
"code": "AUTH_UNAUTHORIZED",
"message": "Invalid credentials",
"i18nKey": "auth.login.invalid_credentials",
"i18nVars": {
"field": "email"
},
"details": [
{
"message": "email must be an email"
}
],
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}{
"success": false,
"error": {
"code": "AUTH_UNAUTHORIZED",
"message": "Invalid credentials",
"i18nKey": "auth.login.invalid_credentials",
"i18nVars": {
"field": "email"
},
"details": [
{
"message": "email must be an email"
}
],
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}Source
| Source | Path | Lines |
|---|---|---|
| Controller | apps/api-core/src/modules/auth/auth.controller.ts | 225–243 |
| DTO (request) | apps/api-core/src/modules/auth/dto/index.ts | 123–135 (ChangePasswordDto) |
| Service | apps/api-core/src/modules/auth/auth.service.ts | changePassword() |
| Prisma models | packages/prisma/prisma/schema.prisma | User, Session |