Account changes
Signed-in users can change their email address, unlink a
provider and delete their account. The starter
/auth/account page has a control for each, and each has a JSON endpoint
and a function for pages of your own.
All of them need a recent sign-in: a session made within
fresh_session_seconds of the policy, ten minutes
by default. Using a session does not make it recent again.
Changing the email address
-
The user signs in again, and asks for the new address with
POST /email/changeand{"email":"new@example.com"}. - A token is emailed to the new address.
-
The user pastes it, from the same session, into
POST /email/confirmwith{"token":"…"}.
Nothing changes until the token is confirmed. Then every session of the user is signed out, including this one, and the old address is sent a notice. The user keeps their id, password, fields, roles and linked providers.
Handle two more purposes in your deliver function:
-
auth.EmailChangecarries the token for the new address. It cannot be used to sign in. -
auth.EmailChangedis the notice to the old address, and its token is empty. Tell a reader who did not make the change to contact you, since that mailbox can no longer recover the account.
Asking the old address too
By default only the new mailbox is checked, so somebody who took over a fresh session could move the account to an address they control. To also require the current mailbox:
let identity = auth.with_email_change_approval(identity)
POST /email/change then emails an auth.EmailChangeApproval token to the
*current* address, and nothing to the new one. Pasting it into
POST /email/approve sends the usual EmailChange token to the new
address. All three steps must finish within fresh_session_seconds of
signing in, so consider raising it. A user who has lost their old mailbox
can then only change address through
administration.
Unlinking a provider
GET /providers lists the providers linked to the account, and
POST /providers/unlink with {"issuer":"…"} removes one.
The recent sign-in must be through a different method than the provider being removed, so a stolen Google session cannot cut off the other ways in. Unlinking signs out every session.
Deleting the account
Deleting is off until your app says what else to delete with the user:
let identity =
auth.with_account_deletion(identity, fn(conn, user) {
database.execute(conn, "DELETE FROM notes_notes WHERE user_id = $1", [
sql.string(user.id),
])
})
Your function runs inside the transaction that deletes the user, on the
conn it is given. Return an error to refuse the deletion, and everything
rolls back. Do not call other auth functions from it, or send email or
make network calls: if you need to clean up somewhere else, record the work
in the transaction and do it afterwards.
Users delete their own account with POST /account/delete, confirming
their current address:
{"email": "ada@example.com"}
Auth removes the user, their passwords, linked identities, fields, sessions, pending tokens and role assignments. Groups, roles and the audit trail are kept. Deleting is not a ban: if registration is open, the same address can register again.
Without HTTP
auth.request_email_change(identity, principal, "new@example.com")
auth.approve_email_change(identity, principal, token)
auth.confirm_email_change(identity, principal, token)
auth.linked_providers(identity, principal)
auth.unlink_provider(identity, principal, "https://accounts.google.com")
auth.delete_account(identity, principal, confirm_email: "ada@example.com")
Each checks again, inside its transaction, that the session is still live and recent and the user still active.
