Invites and resets
Two jobs need an emailed link: inviting a new person to set their
first password, and letting someone who forgot theirs choose a new
one. authkit handles both with the same idea, a single use token.
A token is a random secret you put in a link. authkit returns the
secret to you once and stores only its hash, so a stolen database
gives nobody a working link. Sending the mail is your job.
Setting it up
Section titled “Setting it up”invites := authkit.NewInvites(authkit.InvitesConfig{Store: store})The store must satisfy authkit.InviteStore.
authkit/postgres already does.
Invite links last seven days and reset links one hour, and
InviteTTL and ResetTTL change that.
Inviting someone
Section titled “Inviting someone”tok, err := invites.Invite(ctx, "maria@example.com", "Maria Perez", "member")This creates an unconfirmed account with no password and returns the
token. Put tok.Token in a link and mail it. The account cannot log
in until the link is used.
When they follow the link:
id, err := invites.RedeemInvite(ctx, secret, chosenPassword)That sets their password, confirms the address, and answers the account id. Start a session for them yourself.
ResendInvite replaces a pending link with a fresh one, which
invalidates the old link. It refuses an account that is already
activated.
Resetting a password
Section titled “Resetting a password”tok, err := invites.RequestReset(ctx, "maria@example.com")Only confirmed, enabled accounts get a reset link. Every other
address answers gouncer.ErrUserNotFound, so the response cannot be
used to discover which emails have accounts.
id, err := invites.RedeemReset(ctx, secret, newPassword)That replaces the password and ends every session the account holds, so anyone already signed in as them is logged out.
By default one reset link stands at a time. Asking again while one is
live answers gouncer.ErrTokenExists, which stops someone flooding a
mailbox by repeatedly submitting the form.
That default has a cost. If the mail is lost or filtered, the person
holds no link and cannot ask for another until the first expires.
ResetTokensLive lets several links stand at once instead:
invites := authkit.NewInvites(authkit.InvitesConfig{ Store: store, ResetTokensLive: 3,})Now a repeat request mints an independent link and answers it, so a lost mail is fixed by asking again. Links already sent keep working, which matters because the reset form is usually open to anyone. If a request replaced the standing link, a stranger who knows an address could destroy the link sitting in that person’s inbox. Adding links never takes one away.
gouncer.ErrTokenExists now means the cap is reached, not that a
link exists. Spending any link of a stack retires all of them, so a
completed reset closes the whole recovery window.
Deciding how many links may stand is not the same as deciding how often to mail. Nothing here rate limits delivery, so cap the mail one address receives yourself, and do it before you mint the token.
Four rules worth knowing
Section titled “Four rules worth knowing”These are decided for you, and none of them is obvious from the function names.
Disabling an account kills its links. Every invite and reset link it holds stops working immediately, and re-enabling the account does not bring them back. Disabling is a complete revocation.
A failed redemption costs nothing. If the database errors while redeeming, the link is not spent. The same link still works. Users do not need a new one after a hiccup.
A link works once. Redeeming it consumes it, and a second attempt
answers gouncer.ErrTokenNotFound. Two people clicking the same link
at the same moment means exactly one succeeds.
Expired invites free the address. An unconfirmed account whose invite expired is deleted along with it, so the email can be invited again. An account that holds a live link is never swept.
Clearing out expired tokens
Section titled “Clearing out expired tokens”That last rule needs something to run the sweep. The
reaper
already clears expired sessions. If your store also satisfies
authkit.TokenReaper, and authkit/postgres does, the same reaper
clears expired tokens on the same schedule with no extra setup.
Without a reaper, expired tokens pile up and expired invites keep holding their addresses.