# 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:

```jadpo
// domains/accounts/actions/create_member.jadpo
action 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 `FirstSignIn` and returns the `id` of 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 the `SignInRefused` event 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 the `jadpo_sign_in_links`
  system table, so the next sign-in skips the action. A magic link needs
  no link: Jadpo finds the user by `address:`.
- Linking a user who already exists, by their address, is the action's
  choice. Do it only when `address_verified` is `true`, 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 is `sign_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 with `user_disabled` until 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`](/docs/ways-to-sign-in.md#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:

```jadpo
use desk

authentication {
    browser_session: {
        cookie: "desk_session"
    }
    external_token: {
        issuer: config.issuer
        audience: config.audience
    }
    on_first_sign_in: desk.take_on_newcomer
}
```

```jadpo
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
        }
    }
}
```
