feat(identity): add gated self-hosted otp login

This commit is contained in:
Jesse_Chen
2026-07-21 17:52:25 +08:00
parent cf42430244
commit dfcc718fa7
4 changed files with 355 additions and 115 deletions
@@ -0,0 +1,84 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { createSelfHostedOtpActions } from "../src/modules/identity/client.ts";
test("login page selects the auth provider from server-only validated config", () => {
const page = readFileSync(
new URL("../src/app/login/page.tsx", import.meta.url),
"utf8",
);
assert.doesNotMatch(page, /["']use client["']/);
assert.match(page, /readIdentityConfig\(process\.env\)/);
assert.match(page, /provider=\{config\.provider\}/);
assert.doesNotMatch(page, /NEXT_PUBLIC_AUTH_PROVIDER/);
});
test("self-hosted OTP actions call Better Auth without browser token storage", async () => {
const calls: Array<{ operation: string; input: Record<string, string> }> = [];
const actions = createSelfHostedOtpActions({
emailOtp: {
async sendVerificationOtp(input) {
calls.push({ operation: "send", input });
return { data: { success: true }, error: null };
},
},
signIn: {
async emailOtp(input) {
calls.push({ operation: "verify", input });
return { data: { user: { id: "user-id" } }, error: null };
},
},
});
await actions.send(" Person@Example.com ");
await actions.verify(" Person@Example.com ", "123456");
assert.deepEqual(calls, [
{
operation: "send",
input: { email: "person@example.com", type: "sign-in" },
},
{
operation: "verify",
input: { email: "person@example.com", otp: "123456" },
},
]);
const clientSource = readFileSync(
new URL("../src/modules/identity/client.ts", import.meta.url),
"utf8",
);
assert.doesNotMatch(clientSource, /localStorage|sessionStorage/);
});
test("self-hosted OTP actions expose generic enumeration-safe errors", async () => {
const actions = createSelfHostedOtpActions({
emailOtp: {
async sendVerificationOtp() {
return {
data: null,
error: { message: "database says account does not exist" },
};
},
},
signIn: {
async emailOtp() {
return {
data: null,
error: { message: "internal OTP hash 123456 mismatch" },
};
},
},
});
await assert.rejects(
actions.send("missing@example.com"),
new Error("暂时无法发送验证码,请稍后再试"),
);
await assert.rejects(
actions.verify("missing@example.com", "123456"),
new Error("验证码错误或已过期,请重新获取"),
);
});