feat: add auth.api.verifyPassword (#6934)

Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
Saviru
2026-01-02 14:21:06 +08:00
committed by GitHub
co-authored by Alex Yang
parent 00897dcaad
commit 9dae1b4ea2
6 changed files with 303 additions and 3 deletions
+19 -2
View File
@@ -140,6 +140,21 @@ await auth.api.setPassword({
headers: await headers() // headers containing the user's session token
});
```
### Verify Password
The `verifyPassword` function allows you to verify a user's current password. This is useful for confirming user identity before performing sensitive operations like updating security settings. This function can only be called from the server.
```ts
await auth.api.verifyPassword({
body: {
password: "user-password" // required
},
headers: await headers() // headers containing the user's session token
});
```
<Callout type="info">
For OAuth users who don't have passwords, consider using email verification or fresh session checks for sensitive operations instead.
</Callout>
## Delete User
@@ -453,8 +468,10 @@ Users already signed in can manually link their account to additional social pro
```ts
await auth.api.setPassword({
headers: /* headers containing the user's session token */,
password: /* new password */
body: {
newPassword: "new-password", // required
},
headers: await headers() // headers containing the user's session token
});
```
+2
View File
@@ -42,6 +42,7 @@ import {
unlinkAccount,
updateUser,
verifyEmail,
verifyPassword,
} from "./routes";
import { toAuthEndpoints } from "./to-auth-endpoints";
@@ -216,6 +217,7 @@ export function getEndpoints<Option extends BetterAuthOptions>(
signUpEmail: signUpEmail<Option>(),
signInEmail: signInEmail<Option>(),
resetPassword,
verifyPassword,
verifyEmail,
sendVerificationEmail,
changeEmail,
+1 -1
View File
@@ -3,7 +3,7 @@ export * from "./callback";
export * from "./email-verification";
export * from "./error";
export * from "./ok";
export * from "./reset-password";
export * from "./password";
export * from "./session";
export * from "./sign-in";
export * from "./sign-out";
@@ -1,3 +1,4 @@
import { APIError } from "better-call";
import { describe, expect, vi } from "vitest";
import { getTestInstance } from "../../test-utils/test-instance";
import type { Account } from "../../types";
@@ -402,3 +403,77 @@ describe("revoke sessions on password reset", async (it) => {
});
});
});
describe("verify password", async (it) => {
const { testUser, auth } = await getTestInstance({
emailAndPassword: {
enabled: true,
},
});
const getSessionHeaders = async () => {
const signInRes = await auth.api.signInEmail({
body: {
email: testUser.email,
password: testUser.password,
},
returnHeaders: true,
});
const headers = new Headers();
headers.set("cookie", signInRes.headers.getSetCookie()[0]!);
return headers;
};
it("should verify password with correct password", async () => {
const headers = await getSessionHeaders();
const verifyRes = await auth.api.verifyPassword({
body: {
password: testUser.password,
},
headers,
});
expect(verifyRes).toMatchObject({
status: true,
});
});
it("should fail to verify password with incorrect password", async () => {
const headers = await getSessionHeaders();
try {
await auth.api.verifyPassword({
body: {
password: "wrong-password",
},
headers,
});
expect.fail("Should have thrown an error");
} catch (error) {
expect(error).toBeInstanceOf(APIError);
if (error instanceof APIError) {
expect(error.status).toBe("BAD_REQUEST");
expect(error.message).toBe("Invalid password");
}
}
});
it("should require a session to verify password", async () => {
try {
await auth.api.verifyPassword({
body: {
password: testUser.password,
},
headers: new Headers(),
});
expect.fail("Should have thrown an error");
} catch (error) {
expect(error).toBeInstanceOf(APIError);
if (error instanceof APIError) {
expect(error.status).toBe("UNAUTHORIZED");
}
}
});
});
@@ -4,7 +4,9 @@ import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
import * as z from "zod";
import { generateId } from "../../utils";
import { getDate } from "../../utils/date";
import { validatePassword } from "../../utils/password";
import { originCheck } from "../middlewares";
import { sensitiveSessionMiddleware } from "./session";
function redirectError(
ctx: AuthContext,
@@ -327,3 +329,60 @@ export const resetPassword = createAuthEndpoint(
});
},
);
export const verifyPassword = createAuthEndpoint(
"/verify-password",
{
method: "POST",
body: z.object({
/**
* The password to verify
*/
password: z.string().meta({
description: "The password to verify",
}),
}),
metadata: {
scope: "server",
openapi: {
operationId: "verifyPassword",
description: "Verify the current user's password",
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
properties: {
status: {
type: "boolean",
},
},
},
},
},
},
},
},
},
use: [sensitiveSessionMiddleware],
},
async (ctx) => {
const { password } = ctx.body;
const session = ctx.context.session;
const isValid = await validatePassword(ctx, {
password,
userId: session.user.id,
});
if (!isValid) {
throw new APIError("BAD_REQUEST", BASE_ERROR_CODES.INVALID_PASSWORD);
}
return ctx.json({
status: true,
});
},
);
@@ -4663,6 +4663,153 @@ exports[`open-api > should generate OpenAPI schema > openAPISchema 1`] = `
],
},
},
"/verify-password": {
"post": {
"description": "Verify the current user's password",
"operationId": "verifyPassword",
"parameters": [],
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"password": {
"description": "The password to verify",
"type": "string",
},
},
"required": [
"password",
],
"type": "object",
},
},
},
"required": true,
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"status": {
"type": "boolean",
},
},
"type": "object",
},
},
},
"description": "Success",
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"message": {
"type": "string",
},
},
"required": [
"message",
],
"type": "object",
},
},
},
"description": "Bad Request. Usually due to missing parameters, or invalid parameters.",
},
"401": {
"content": {
"application/json": {
"schema": {
"properties": {
"message": {
"type": "string",
},
},
"required": [
"message",
],
"type": "object",
},
},
},
"description": "Unauthorized. Due to missing or invalid authentication.",
},
"403": {
"content": {
"application/json": {
"schema": {
"properties": {
"message": {
"type": "string",
},
},
"type": "object",
},
},
},
"description": "Forbidden. You do not have permission to access this resource or to perform this action.",
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"message": {
"type": "string",
},
},
"type": "object",
},
},
},
"description": "Not Found. The requested resource was not found.",
},
"429": {
"content": {
"application/json": {
"schema": {
"properties": {
"message": {
"type": "string",
},
},
"type": "object",
},
},
},
"description": "Too Many Requests. You have exceeded the rate limit. Try again later.",
},
"500": {
"content": {
"application/json": {
"schema": {
"properties": {
"message": {
"type": "string",
},
},
"type": "object",
},
},
},
"description": "Internal Server Error. This is a problem with the server that you cannot fix.",
},
},
"security": [
{
"bearerAuth": [],
},
],
"tags": [
"Default",
],
},
},
},
"security": [
{