feat(identity): test-only fixed OTP channel and rectification candidate cards
- IDENTITY_TEST_OTP env: when set to a 6-digit code, no real email is delivered; login page surfaces the fixed code so testers can register and sign in without a mailbox. Opt-in, never set in production. - email-otp-login: test-channel notice with the pinned code. - Rectification candidate list restyled with the project design system (warm canvas, action color, display serif, soft shadow, hover lift). - Regression tests for config parsing, pinned OTP generation, login UI notice, and candidate card styles.
This commit is contained in:
@@ -39,11 +39,13 @@ export function EmailOtpLogin({
|
||||
passwordEnabled = false,
|
||||
passwordOnly = false,
|
||||
successPath = "/",
|
||||
testOtp = null,
|
||||
}: {
|
||||
provider: AuthProvider;
|
||||
passwordEnabled?: boolean;
|
||||
passwordOnly?: boolean;
|
||||
successPath?: "/" | "/admin";
|
||||
testOtp?: string | null;
|
||||
}) {
|
||||
const [mode, setMode] = useState<AuthMode>(passwordOnly ? "password" : "otp");
|
||||
const [step, setStep] = useState<AuthStep>("email");
|
||||
@@ -57,6 +59,7 @@ export function EmailOtpLogin({
|
||||
const [notice, setNotice] = useState("");
|
||||
|
||||
const canUsePassword = provider === "self-hosted" && passwordEnabled;
|
||||
const testMode = testOtp !== null && testOtp !== undefined && testOtp !== "";
|
||||
const showLoginNavigation =
|
||||
step === "email"
|
||||
&& canUsePassword
|
||||
@@ -307,6 +310,12 @@ export function EmailOtpLogin({
|
||||
<h1 id="login-title">{title}</h1>
|
||||
<p className="page-intro">{intro}</p>
|
||||
|
||||
{testMode && (
|
||||
<p className="auth-test-channel" role="status">
|
||||
测试环境:验证码固定为 <strong>{testOtp}</strong>,不会发送真实邮件。
|
||||
</p>
|
||||
)}
|
||||
|
||||
{showLoginNavigation && (
|
||||
<nav className="auth-mode-nav" aria-label="登录方式">
|
||||
<div className="auth-mode-tabs">
|
||||
|
||||
Reference in New Issue
Block a user