fix(admin): remove operation-level email reauthentication
Independent Staging Quality Gate / validate (push) Successful in 12m37s
Independent Staging Quality Gate / publish (push) Successful in 10m0s

This commit is contained in:
Jesse_Chen
2026-08-16 14:07:57 +08:00
parent 9d8570640d
commit 4f6cf5782d
30 changed files with 230 additions and 582 deletions
+1 -95
View File
@@ -39,10 +39,6 @@ export type AdminAccessResult =
export const ADMIN_MFA_PROOF_COOKIE = "jyotisha-admin.mfa";
export const ADMIN_MFA_PROOF_TTL_MS = 10 * 60 * 1_000;
export const HIGH_RISK_ADMIN_CHALLENGE_COOKIE = "jyotisha-admin.reauth-challenge";
export const HIGH_RISK_ADMIN_CHALLENGE_TTL_MS = 5 * 60 * 1_000;
export const HIGH_RISK_ADMIN_PROOF_COOKIE = "jyotisha-admin.reauth";
export const HIGH_RISK_ADMIN_PROOF_TTL_MS = 5 * 60 * 1_000;
type AdminMfaProofContext = {
userId: string;
@@ -56,21 +52,10 @@ type AdminMfaProofClaims = AdminMfaProofContext & {
expiresAt: number;
};
type HighRiskAdminProofContext = AdminMfaProofContext & {
permission: AdminPermission;
};
type HighRiskAdminProofClaims = HighRiskAdminProofContext & {
version: 1;
issuedAt: number;
expiresAt: number;
};
export type AdminMfaStatus = {
required: boolean;
enrolled: boolean;
verified: boolean;
highRiskWritesEnabled: boolean;
};
export function authorizeAdminAccess(
@@ -167,7 +152,6 @@ export function resolveAdminMfaStatus(
required,
enrolled,
verified: currentSessionVerified,
highRiskWritesEnabled: !required || currentSessionVerified,
};
}
@@ -188,7 +172,7 @@ function signProof(
function encodeProof(
purpose: string,
claims: AdminMfaProofClaims | HighRiskAdminProofClaims,
claims: AdminMfaProofClaims,
proofSecret: string,
sessionToken: string,
): string {
@@ -255,81 +239,3 @@ export function verifyAdminMfaProof(
&& claims.issuedAt! <= now
&& now < claims.expiresAt!;
}
export function issueHighRiskAdminChallenge(
context: HighRiskAdminProofContext,
proofSecret: string,
sessionToken: string,
now = Date.now(),
): string {
return encodeProof("jyotisha-admin-reauth-challenge-v1", {
version: 1,
...context,
issuedAt: now,
expiresAt: now + HIGH_RISK_ADMIN_CHALLENGE_TTL_MS,
} satisfies HighRiskAdminProofClaims, proofSecret, sessionToken);
}
export function verifyHighRiskAdminChallenge(
proof: string | undefined,
context: HighRiskAdminProofContext,
proofSecret: string,
sessionToken: string,
now = Date.now(),
): boolean {
const claims = decodeProof<HighRiskAdminProofClaims>(
proof,
"jyotisha-admin-reauth-challenge-v1",
proofSecret,
sessionToken,
);
return claims?.version === 1
&& claims.userId === context.userId
&& claims.sessionId === context.sessionId
&& claims.permission === context.permission
&& claims.origin === context.origin
&& Number.isSafeInteger(claims.issuedAt)
&& Number.isSafeInteger(claims.expiresAt)
&& claims.expiresAt! - claims.issuedAt! === HIGH_RISK_ADMIN_CHALLENGE_TTL_MS
&& claims.issuedAt! <= now
&& now < claims.expiresAt!;
}
export function issueHighRiskAdminProof(
context: HighRiskAdminProofContext,
proofSecret: string,
sessionToken: string,
now = Date.now(),
): string {
return encodeProof("jyotisha-admin-reauth-v1", {
version: 1,
...context,
issuedAt: now,
expiresAt: now + HIGH_RISK_ADMIN_PROOF_TTL_MS,
} satisfies HighRiskAdminProofClaims, proofSecret, sessionToken);
}
export function verifyHighRiskAdminProof(
proof: string | undefined,
context: HighRiskAdminProofContext,
proofSecret: string,
sessionToken: string,
now = Date.now(),
): boolean {
const claims = decodeProof<HighRiskAdminProofClaims>(
proof,
"jyotisha-admin-reauth-v1",
proofSecret,
sessionToken,
);
return claims?.version === 1
&& claims.userId === context.userId
&& claims.sessionId === context.sessionId
&& claims.permission === context.permission
&& claims.origin === context.origin
&& Number.isSafeInteger(claims.issuedAt)
&& Number.isSafeInteger(claims.expiresAt)
&& claims.expiresAt! - claims.issuedAt! === HIGH_RISK_ADMIN_PROOF_TTL_MS
&& claims.issuedAt! <= now
&& now < claims.expiresAt!;
}
-26
View File
@@ -9,11 +9,9 @@ import {
} from "./auth";
import {
ADMIN_MFA_PROOF_COOKIE,
HIGH_RISK_ADMIN_PROOF_COOKIE,
isTrustedAdminMutationRequest,
resolveAdminMfaStatus,
verifyAdminMfaProof,
verifyHighRiskAdminProof,
type AdminMfaStatus,
} from "./auth-policy";
import { adminErrorResponse } from "./admin-error-response";
@@ -86,30 +84,6 @@ export function readAdminMfaStatus(
return resolveAdminMfaStatus(session.requiresMfa, enrolled, verified);
}
export async function requireHighRiskAdminMutation(
request: Request,
permission: AdminPermission,
): Promise<AdminSession> {
const session = await requireAdminMutation(request, permission);
const origin = new URL(request.url).origin;
const valid = verifyHighRiskAdminProof(
requestCookie(request, HIGH_RISK_ADMIN_PROOF_COOKIE),
{
userId: session.user.id,
sessionId: session.identitySession.id,
permission,
origin,
},
adminProofSigningSecret(),
session.identitySession.token,
);
if (!valid) {
throw new AdminAuthorizationError("请先使用邮箱验证码重新认证", 403);
}
return session;
}
export function invalidQueryResponse(details?: unknown) {
return NextResponse.json(
{ error: "查询参数不正确", ...(details ? { details } : {}) },