import { validateEmail as validateEmailUtil, validatePassword as validatePasswordUtil, validateUsername as validateUsernameUtil } from "../utils/validation"; import * as ApiConfig from "./api-config"; /** * Shape of the user record returned by Entropy Softworks servers. Apps * may carry additional fields — extend the interface in the consumer if * needed and pass the extended type through `useAuth()`. */ /** * Conventional onboarding-step values produced by Entropy Softworks * servers. Apps may carry additional vendor-specific steps — keep the * field typed as `string` to stay compatible with the openapi-generated * `UserPublic` shape that vault and bootstrap import from their shared * packages. */ export type OnboardingStep = "email_verification" | "mfa_enrollment" | "import" | "complete"; export interface User { id: string; email: string; /** Optional: email-only servers (e.g. Simple Finance) don't issue usernames. */ username?: string; display_name?: string; role: "Admin" | "User"; /** * Pending step in the post-signup onboarding flow. Typed as `string` * (rather than the `OnboardingStep` union) to stay assignment-compatible * with server-generated openapi types; the auth screens compare against * the known constants and treat unknown values as "complete". */ onboarding_step?: string | null; email_verified?: boolean; mfa_enabled?: boolean; mfa_verified?: boolean; created_at?: string; /** * Tenant the account belongs to. Only the multi-tenant IdP (access) sends * these two; the single-tenant products have no tenant and carry a single * `role` rather than a role set. */ tenant_id?: string; tenant_slug?: string; /** Effective role names, as resolved by the IdP (direct, assigned, or via a group). */ roles?: string[]; } export interface AuthResponse { user: User; /** * Bearer token for native clients. Web clients receive an HttpOnly * cookie and ignore this field. */ session_token: string; } /** Public server feature flags from `GET /config`. */ export interface ServerConfig { /** New accounts must enroll a passkey/TOTP during onboarding. */ require_mfa_enrollment: boolean; registration_enabled: boolean; } export interface LoginRequest { email: string; password: string; } export interface RegisterRequest { email: string; /** Optional: omitted for email-only servers (e.g. Simple Finance). */ username?: string; password: string; display_name?: string; } export interface ErrorResponse { error: { message: string; details?: string[]; code?: string; }; } export interface MfaSetupResponse { uri: string; secret: string; recovery_codes: string[]; } /** * Auth client for Entropy Softworks servers. Apps call `configureApi` * (from `@entropy-softworks/ui/services`) at startup so storage keys and * the base URL are wired up before any of these methods run. */ declare class AuthService { static loadServerUrlOverride: typeof ApiConfig.loadServerUrlOverride; static getBaseUrl: typeof ApiConfig.getBaseUrl; static setBaseUrl: typeof ApiConfig.setBaseUrl; static resetBaseUrl: typeof ApiConfig.resetBaseUrl; static getSessionToken: typeof ApiConfig.getSessionToken; /** * Persist the session credential + user record produced by an * alternative auth path (passkey-finish, magic-link, etc.) so the * rest of `useAuth` / `getCurrentUser` sees the same state the * password `login` flow leaves behind. * * Web: session credential is a server-set HttpOnly cookie; only * the user record persists here, and it lives in process memory * (NOT sessionStorage) so an XSS exploit cannot read it. * Native: token goes through SecureStore, user record goes * through SecureStore alongside it. */ static persistAuthSession(data: AuthResponse): Promise; static login(email: string, password: string): Promise; /** * Create an account. * * There is no username. The email address is the identity, and the server * derives whatever internal handle it needs from it. * * This used to take one, and the screens built it from the email's local part: * `me@example.com` became `me`, which the server rejected as too short, and * `geoff@gmail.com` and `geoff@outlook.com` both became `geoff`, which * collided on a unique column and refused the second person's signup over a * name they had never chosen. A value the user does not type is a value the * client should not invent. */ static register(email: string, password: string, displayName?: string): Promise; static logout(): Promise; /** * The signed-in user, from local state if it is there and from the server if * it is not. * * The server fallback is the whole point. On web `loadPersistedUser` returns * a module-level variable, which is null in every freshly loaded document — * so this used to answer "nobody" on each page load and refresh while the * session cookie was perfectly valid. `useAuth.checkAuth` reads that as a * session it cannot resolve and calls `logout()`, which POSTs * `/auth/logout` and destroys the session server-side. The user is signed * out by the act of loading the page. * * Observed on Access on 2026-08-22: the portal and the admin console are * separate SPAs on one origin, so every navigation between them is a fresh * document and therefore a guaranteed logout — which presents as an infinite * sign-in loop. Its audit log recorded the logouts nobody asked for. * * Native was affected too, if less visibly: `isAuthenticated` there is * satisfied by a token in SecureStore, so a token that outlived its cached * user record produced the same spurious logout. * * It does NOT route through `isAuthenticated`. That method's web branch falls * back to calling this one, so going the other way would recurse until the * stack gave out on exactly the signed-out path this is meant to make cheap. */ static getCurrentUser(): Promise; /** * `GET /auth/me`, persisting whatever comes back. * * `logoutOn401` is a parameter rather than a fixed behaviour because the two * callers need opposite things from the same request. A *refresh* of a * session believed live that returns 401 means the session died and local * state should be cleared. A *lookup* that returns 401 means there was no * session to begin with — the ordinary state of a signed-out visitor — and * tearing down on that would fire a logout on every anonymous page load. */ private static fetchMe; static refreshUserData(): Promise; /** * Web: HttpOnly cookies aren't readable from JS. We use the * presence of the (non-HttpOnly) `csrf_token` cookie — set by the * server alongside the session cookie at login — as the * "authenticated" probe. Falls back to the in-memory user record if * cookies are unreadable (SSR / non-browser test envs). * Native: a token in SecureStore. */ static isAuthenticated(): Promise; static hasRole(role: "Admin" | "User"): Promise; static deleteAccount(password: string): Promise; static changePassword(currentPassword: string, newPassword: string, reWrappedKey?: { symmetric_key_enc: string; kdf: string; kdf_salt: string; kdf_iterations: number; kdf_memory_kib?: number; kdf_lanes?: number; }): Promise; static changeEmail(newEmail: string, password: string): Promise; static updateDisplayName(displayName: string): Promise; static setupMfa(): Promise; static verifyMfaSetup(code: string): Promise; static verifyMfa(code: string): Promise; static disableMfa(password: string): Promise; static forgotPassword(email: string): Promise; static validateResetCode(code: string): Promise; static resetPassword(code: string, newPassword: string): Promise; static verifyEmail(code: string): Promise; static resendVerification(): Promise; static completeOnboarding(): Promise; /** * Public server configuration (feature flags) — read pre-auth to adapt the * UI, e.g. whether MFA enrollment is mandatory during onboarding. Returns * permissive defaults if the endpoint is unavailable so onboarding never * hard-blocks on a missing/older server. */ static getServerConfig(): Promise; static validatePassword: typeof validatePasswordUtil; static validateEmail: typeof validateEmailUtil; static validateUsername: typeof validateUsernameUtil; } export default AuthService; //# sourceMappingURL=auth.service.d.ts.map