A person the app has not seen
The first time someone signs in whom your app has no user for, Jadpo
asks your app what to do. on_first_sign_in: in authentication { }
names the action that decides: create a user, link one that already
exists, or refuse. One action decides for every way in.
It runs when:
- a magic link or code is used for an address no user has;
- a provider returns an account not yet linked to a user;
- an external token names a subject not yet linked to a user.
Here the action refuses an address Google hasn't verified, and creates a member otherwise:
// domains/accounts/actions/create_member.jadpoaction create_member(first: FirstSignIn) fails SignUpClosed -> Member.id { if (first.method == SignInMethod.google and not first.address_verified) { reject SignUpClosed } var member: Member = attempt Member.create(...) return member.id}
FirstSignIn field |
Type | Holds |
|---|---|---|
method |
SignInMethod |
The way in: magic_link, each provider named under sign_in:, or external_token; a built-in enum with one value per way the app declares |
address |
Email? |
For a magic link, the address, verified; for a provider, its email claim; for an external token, none |
address_verified |
Bool |
true for a magic link; the provider's own verified claim for a provider; false for an external token |
subject |
Text? |
The provider's or token's subject; none for a magic link |
name |
Text? |
The display name a provider's profile gives, when it gives one |
- The action takes exactly one
FirstSignInand returns theidof the principal's user entity: a user it created, or one that already exists. It runs in its own transaction before the session starts. - To refuse, it fails with any failure it declares. The person always
sees the same built-in
sign_in_refused, whatever the reason. The log and theSignInRefusedevent keep the real one. - For a provider or an external token, Jadpo then records the link, from
the method and subject to the user's
id, in thejadpo_sign_in_linkssystem table, so the next sign-in skips the action. A magic link needs no link: Jadpo finds the user byaddress:. - Linking a user who already exists, by their address, is the action's
choice. Do it only when
address_verifiedistrue, so no one can take over an account by claiming its address at a provider that doesn't check it. - Without
on_first_sign_in:, only users who already exist sign in. An unknown address gets no email, and an unknown provider account or token subject issign_in_refused. - A passkey never runs it. Only a person already signed in can add a passkey, so every passkey that signs in has its user.
An external token is signed in on every request, so its first sign-in happens on the first request it comes with:
- Jadpo looks for the user whose
subject:field holds the token's subject, then for one a first sign-in linked it to, in one read. A subject that isn't of the field's type is looked for among the links only. - The action runs once for each subject. Requests that arrive together with a new subject, as a page's first load sends them, wait for the first, then sign in as the user it gave.
- The user the action gives is linked even when they aren't
active:, and is refused withuser_disableduntil they are. So an action can take someone on to wait for approval without being asked again.
Here the staff API takes the company provider's tokens, as in
External tokens in tests and jadpo dev,
and someone's first token takes them on as a librarian who waits for a
manager's approval:
use deskauthentication { browser_session: { cookie: "desk_session" } external_token: { issuer: config.issuer audience: config.audience } on_first_sign_in: desk.take_on_newcomer}
use desk/entities { Librarian }use desk/failures { SubjectTaken }/// Takes on someone the company's provider vouches for, to wait for a/// manager's approval.action take_on_newcomer(first: FirstSignIn) fails SubjectTaken -> Librarian.id { match first.subject { none -> { reject SubjectTaken } some(subject) -> { var newcomer: Librarian = attempt Librarian.create { set: { subject: subject name: "New starter" active: false } conflict: { Librarian.subject: SubjectTaken } } return newcomer.id } }}
Markdown for agents: /docs/a-person-the-app-has-not-seen.md.Built from Jadpo main at aee2e8f.