feat(setup): add the op.console owner onboarding wizard
The /setup route redeems the one-time token from `felis setup`, then walks the Owner through email-OTP verification and passkey enrollment before handing off to the console. It sits outside RequireAuth — the visitor arrives without a session and the redeem is what mints one — and is reload-safe: a spent token resumes from the surviving session via /auth/setup/status. Adds the Setup page and its /setup route, the setup API client methods (redeem/status), and the en-US/zh-CN onboarding strings.
This commit is contained in:
5 files changed
+450
-2
No files matched your search
@@ -94,6 +94,19 @@ async function requestRaw<T>(
|
||||
return parsed as T;
|
||||
}
|
||||
|
||||
// Setup bootstrap (spec §B). The one-time token from `felis setup` is redeemed for
|
||||
// a lockdown session; the response (and /setup/status) reports which onboarding
|
||||
// steps remain so the Setup wizard can drive email verification + passkey enrollment.
|
||||
export interface SetupState {
|
||||
user_id: string;
|
||||
username: string;
|
||||
role: string;
|
||||
email: string | null;
|
||||
email_verified: boolean;
|
||||
has_passkey: boolean;
|
||||
setup_required: boolean;
|
||||
}
|
||||
|
||||
export const api = {
|
||||
// Local-password auth (spec §B1). login sets an HttpOnly session cookie as a
|
||||
// side effect — the panel never sees it — and returns only what to route on next
|
||||
@@ -139,6 +152,15 @@ export const api = {
|
||||
new_password,
|
||||
}),
|
||||
|
||||
// Setup bootstrap (spec §B). redeem consumes the one-time token from the setup URL
|
||||
// and mints a lockdown session (Public); status re-reads progress for a reload
|
||||
// mid-wizard (SetupAllowed — the surviving session, no token needed).
|
||||
setupRedeem: (token: string) =>
|
||||
request<SetupState>("POST", "/auth/setup/redeem", { token }),
|
||||
|
||||
setupStatus: () =>
|
||||
request<SetupState>("GET", "/auth/setup/status"),
|
||||
|
||||
// Identity (spec §7 GET /me) — the tier keystone. is_admin is server-computed
|
||||
// (Principal.IsAdmin); the panel reads it but re-deriving admin-ness is the
|
||||
// backend's job. Drives nav + route guards only; every admin route 403s on its
|
||||
|
||||
Reference in new issue
Block a user