| | /** |
| | * The browser's half of an X sign-in. |
| | * |
| | * X returns the user by redirect, and a redirect cannot carry a credential up or a token |
| | * pair back down. So the callback settles who the user is and leaves a single-use ticket on |
| | * the return URL, which the frontend redeems over an XHR of its own. |
| | * |
| | * A ticket travelling in a URL is only safe if holding it is not enough. That is what these |
| | * two functions are for: `startXHandoff` keeps a secret on this origin before the browser |
| | * leaves, and `finishXHandoff` produces it again on the way back. A ticket lifted from |
| | * someone else's URL matches no secret the lifting browser holds. |
| | */ |
| | /** |
| | * Where the secret waits while the browser is away at X. |
| | * |
| | * The subset of `Storage` this needs, so a caller can pass `localStorage`, a test double, |
| | * or something else entirely. |
| | * |
| | * `localStorage` is the sane choice over `sessionStorage`: the flow may return in a tab |
| | * opened for it, and browsers disagree about whether a new tab inherits session storage. |
| | * Sharing across tabs costs nothing, because a ticket is bound to a secret, not to a tab. |
| | */ |
| | export type XHandoffStorage = { |
| | getItem(key: string): string | null; |
| | setItem(key: string, value: string): void; |
| | removeItem(key: string): void; |
| | }; |
| | /** |
| | * What an X flow left on the page it returned the browser to. |
| | * |
| | * A link is already done by the time the browser lands and only needs reporting. A sign-in |
| | * is not: `login-settled` carries both halves needed to redeem it, so the caller never |
| | * reaches into storage itself. |
| | */ |
| | export type XReturn = { |
| | kind: 'link-succeeded'; |
| | status: 'connected' | 'reconnected'; |
| | } | { |
| | kind: 'link-failed'; |
| | reason: string | undefined; |
| | } | { |
| | kind: 'login-settled'; |
| | ticket: string; |
| | verifier: string; |
| | } | { |
| | kind: 'login-failed'; |
| | reason: string | undefined; |
| | }; |
| | /** |
| | * Begin a sign-in: keep a secret here, and return what the server needs. |
| | * |
| | * Send both fields to `startXLogin`. The server holds the challenge against the flow and |
| | * echoes the id back on the return URL, which is how a browser that started more than one |
| | * sign-in knows which secret this outcome belongs to. |
| | */ |
| | export declare function startXHandoff(storage: XHandoffStorage): { |
| | handoffId: string; |
| | challenge: string; |
| | }; |
| | /** |
| | * Read the outcome this page is the end of, and retire the secret behind it. |
| | * |
| | * Returns undefined when the page is not the end of an X flow at all. Every sign-in outcome |
| | * clears its stored secret, success or failure, so nothing spent is left behind. |
| | * |
| | * The ticket comes out of the fragment rather than the query, which is where the callback |
| | * puts it: no browser sends a fragment to a server, so it stays out of the frontend's access |
| | * logs and out of the `Referer` of anything the page loads. |
| | */ |
| | export declare function finishXHandoff(href: string, storage: XHandoffStorage): XReturn | undefined; |
| | /** |
| | * The same page with every X parameter taken back off it. |
| | * |
| | * What the user reloads or shares should be where they were, never a spent ticket. The |
| | * fragment goes entirely: the flow put the ticket there, and any fragment the page had was |
| | * dropped when the return URL was recorded at start time. |
| | */ |
| | export declare function stripXReturn(href: string): string; |