import type { Persona, PersonaName } from "../types.js"; import type { Asking } from "./oracle.js"; import { type PlantOptions } from "./plant.js"; /** * Two accounts, created through the application, each proven to be who we think * it is. * * With a database behind the run, becoming a user can mean planting a session * row or minting a token against a secret — see `session.ts`. With no database * there is one door left, and it is the same door a real user comes through: * sign up, and then ask the application who it thinks you are. * * That second step is not a formality and it is not optional. A 200 from a * creation endpoint proves that a request succeeded, not that it was attributed * to anybody: an application that ignores the credential entirely answers 200 * to both personas, and a run built on that would be comparing one anonymous * user against themselves and reporting the result as a cross-user leak. So the * credential has to be confirmed *by the application* — a reply that names the * account — before a single resource is planted with it. Where it cannot be, * nothing is planted and nothing is claimed. * * The two accounts are created independently, seconds apart, and nothing in the * run ever invites one to the other's anything. That is the same premise the * data plane gets from putting its personas in separate orgs: the claim is only * ever that two unrelated accounts cannot reach each other, which needs no * knowledge of what the application meant to share. */ /** * An account this run created in the developer's application. * * A signup is a real write, and unlike a note there is almost never an endpoint * that undoes one. So these are tracked in order to be *named*: the marker went * into every field the signup asked for, including the email, which makes it the * one string a developer can search their own database for. */ export interface CreatedAccount { /** The signup endpoint it was created through. */ path: string; /** The id the application confirmed for it, when a `/me` reply named one. */ userId: string | null; /** The 128-bit value that went into the fields the signup asked for. */ marker: string; } export interface AppIdentities { personas: Record | null; /** The credential for a persona, or an anonymous client for `null`. */ headersFor(as: Asking): Record; /** How the identity was established and how it was proven. For the report. */ established: string | null; /** Why no persona could be established. Null when both were. */ unavailable: string | null; /** * Every account signup created, including one whose credential could not * afterwards be confirmed. Those are the ones most easily lost: the run moved * on to the next candidate path and the account stayed behind. */ accounts: CreatedAccount[]; } /** * An account that already exists, to be logged in as rather than created. * * An application with registration closed — most internal tools, anything * behind an invite, anything with SSO in front of it — has no signup for this * run to drive, and an agent that already has two test accounts should not be * made to create two more. Neither case relaxes the bar: what the credential * buys is a request that carries it, and it is the application naming the * account back that makes it an identity. */ export interface LoginCredential { /** What the application knows the account by: an email address, or a username. */ identifier: string; password: string; /** Anything else the login body needs, merged over the conventional shape. */ extra?: Record; } export interface IdentityOptions extends PlantOptions { /** Where a signup lives, in the order they are tried. */ signupPaths?: string[]; /** Where the application says who you are. */ mePaths?: string[]; /** Where a login lives, in the order they are tried. */ loginPaths?: string[]; /** * Two existing accounts to log in as, instead of signing two up. * * They have to be two *unrelated* accounts — that is the premise the whole * run rests on, and it is the one thing supplying credentials moves out of * this engine's hands. Signing up gets it structurally: two accounts created * seconds apart, never invited to anything of each other's. Supplied * credentials are taken as the statement they are, and the run says so on the * face of the result rather than pretending it checked. */ accounts?: [LoginCredential, LoginCredential]; } export declare function establishAppIdentities(opts: IdentityOptions): Promise;