mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-29 23:28:12 +00:00
feat(signup): verify identity with Fayda and prefill the form
Adds "Continue with Fayda" to the existing signup page. It is an alternative way to fill the form, not a second signup: the applicant still submits to /auth/signup-with-pwd through the same schema, the same validation and the same redirect to OTP verification, and signing up without Fayda is unchanged. The page asks the API for an authorization URL, keeps the returned handle and state in sessionStorage for the round trip, and FaydaCallbackPage hands the code back to /auth/fayda/callback. No Fayda protocol logic lives here — the PKCE verifier, the client key and the token exchange stay on the server. Once the account exists the page posts the verification token to /auth/fayda/link so the identity is recorded against it; that call is best-effort, since the account is already usable without it. Prefilled fields carry a "From Fayda" badge and stay editable, and a value that already belongs to another account is badged as such so the applicant can see which one to change rather than reading a single opaque signup error. Cancellation, an expired session, a mismatched state and an incomplete callback each get their own message. Amharic strings are a first pass and want a native speaker's review.
This commit is contained in:
81
libs/auth/src/lib/utils/fayda-session.ts
Normal file
81
libs/auth/src/lib/utils/fayda-session.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* The Fayda round trip leaves the app entirely, so the little state that has to
|
||||
* survive it lives in sessionStorage: same tab, same origin, gone when the tab
|
||||
* closes.
|
||||
*
|
||||
* Nothing secret is kept here. `transactionToken` and `verificationToken` are
|
||||
* signed by the API and are useless without it — the PKCE verifier and the
|
||||
* client secret never leave the backend.
|
||||
*/
|
||||
|
||||
const REQUEST_KEY = 'fayda:request';
|
||||
const RESULT_KEY = 'fayda:result';
|
||||
|
||||
export interface FaydaRequest {
|
||||
transactionToken: string;
|
||||
state: string;
|
||||
}
|
||||
|
||||
export interface FaydaPrefill {
|
||||
email?: string;
|
||||
phoneNumber?: string;
|
||||
nameEn?: string;
|
||||
nameAm?: string;
|
||||
/** Shown for context only — the signup form has no field for these. */
|
||||
gender?: string;
|
||||
address?: string;
|
||||
}
|
||||
|
||||
export interface FaydaResult {
|
||||
prefill: FaydaPrefill;
|
||||
/** Always true when the API returned a result at all. */
|
||||
faydaVerified: boolean;
|
||||
/** Signup fields Fayda vouched for. */
|
||||
verifiedFields: string[];
|
||||
/** Prefilled fields already taken by another account. */
|
||||
conflicts: string[];
|
||||
/** Posted to /auth/fayda/link once the account exists. */
|
||||
verificationToken: string;
|
||||
}
|
||||
|
||||
// Private browsing and locked-down browsers can throw on access, and a failure
|
||||
// here should degrade to "no Fayda prefill", never break the signup page.
|
||||
function read<T>(key: string): T | null {
|
||||
try {
|
||||
const raw = sessionStorage.getItem(key);
|
||||
return raw ? (JSON.parse(raw) as T) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function write(key: string, value: unknown): void {
|
||||
try {
|
||||
sessionStorage.setItem(key, JSON.stringify(value));
|
||||
} catch {
|
||||
/* nothing to do — the flow reports a generic failure instead */
|
||||
}
|
||||
}
|
||||
|
||||
function clear(key: string): void {
|
||||
try {
|
||||
sessionStorage.removeItem(key);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
export const faydaSession = {
|
||||
saveRequest: (request: FaydaRequest) => write(REQUEST_KEY, request),
|
||||
takeRequest: (): FaydaRequest | null => {
|
||||
const request = read<FaydaRequest>(REQUEST_KEY);
|
||||
// Single use: a stale token would otherwise be replayed against a fresh
|
||||
// callback and fail with a confusing "session expired".
|
||||
clear(REQUEST_KEY);
|
||||
return request;
|
||||
},
|
||||
|
||||
saveResult: (result: FaydaResult) => write(RESULT_KEY, result),
|
||||
peekResult: (): FaydaResult | null => read<FaydaResult>(RESULT_KEY),
|
||||
clearResult: () => clear(RESULT_KEY),
|
||||
};
|
||||
Reference in New Issue
Block a user