Help us shape the future of Go! Take the 2026 Go Developer Survey

auth

package module
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 3, 2026 License: MIT Imports: 42 Imported by: 0

README

auth

Shared Go authentication module: sessions, passwords, OAuth, 2FA, and RBAC.

Status: v1. The exported Go API and the HTTP endpoints follow semantic versioning: nothing is removed or changed until v2. What changed in each release is in CHANGELOG.md.

import "github.com/atlasopsec/auth"

What it does

Built for Go APIs that own their own Postgres database, with Next.js web apps and React Native mobile apps as clients.

  • Passwords: argon2id hashing, breached-password checks, reset and email verification flows
  • Sessions: random opaque tokens (only hashes stored), cookie and bearer-token support, expiry, "log out everywhere"
  • Sign in with Google, GitHub, and Apple: OAuth with PKCE, safe account linking
  • Two-factor authentication: TOTP with recovery codes
  • Roles and permissions (RBAC): resource:action permissions, organizations, deny by default
  • Abuse protection: rate limiting, no account enumeration, security event log

The design follows the auth standard in jakefrenzel/engineering-standards.

Encryption key

Every project needs an AUTH_ENCRYPTION_KEY: 32 random bytes, base64-encoded, passed as Config.EncryptionKey. It encrypts users' 2FA secrets, and the PKCE verifiers of sign-ins in progress.

openssl rand -base64 32

Keep it in Railway's variables with the other secrets, and back it up. If it's lost, every user who has 2FA on has to set it up again. Until then they can't log in; turn their 2FA off with ResetTwoFactor.

Sign in with Google, GitHub, and Apple

Turn on a provider with its client ID and secret, keeping the secret in a Railway variable:

OAuth: auth.OAuth{
	Google: &auth.Google{ClientID: "…", ClientSecret: os.Getenv("GOOGLE_CLIENT_SECRET")},
	GitHub: &auth.GitHub{ClientID: "…", ClientSecret: os.Getenv("GITHUB_CLIENT_SECRET")},
	Apple:  &auth.Apple{BundleIDs: []string{"com.example.app"}},
},

At each provider, register these redirect URIs, both served by the web app:

  • {AppURL}/oauth/callback
  • {AppURL}/oauth/callback/mobile, which hands the result to the mobile app's deep link

Use a Google OAuth client of type "Web application". For GitHub, register {AppURL}/oauth/callback as the callback URL; GitHub accepts paths under it.

Apple is native Sign in with Apple in the iOS app only, which Apple requires when an iOS app offers Google or GitHub. It needs only the app's bundle ID: the app sends Apple's signed token to POST /v1/apple-sign-ins.

Restricting sign-ups

To let only some people sign up, for example the addresses on an allowlist, return auth.ErrSignUpNotAllowed from Hooks.OnUserCreated:

OnUserCreated: func(ctx context.Context, tx pgx.Tx, u auth.User) error {
	if !invited(ctx, tx, u.Email) {
		return auth.ErrSignUpNotAllowed
	}
	return nil
},

The sign-up is rolled back and logged at Info. In verify first mode (the default), the response is the same 202 an accepted sign-up gets, and no email is sent, so nobody can use the endpoint to learn which addresses are allowed. With AllowUnverifiedLogin, and for sign-ups with a provider, the answer is 403 sign_up_not_allowed. Any other error from the hook is logged as an error. With AllowUnverifiedLogin, and for sign-ups with a provider, it's answered with a 500. In verify first mode the 202 has already been sent: an email and password sign-up answers before creating the user, so its timing doesn't reveal whether the address has an account, and the hook runs just after.

Users locked out of 2FA

A user who has lost both their authenticator and their recovery codes can't get back in by themselves. After confirming who they are some other way, support turns their 2FA off from admin tooling:

// Also ends their sessions and emails them. ActorOf records the support
// agent in the security event log.
err := a.ResetTwoFactor(ctx, userID, a.ActorOf(r))

There's no endpoint for it, on purpose.

Rate limits and cleanup

The module rate-limits logins, sign-ups, emails, and sign-in starts per client IP, and emails per address, in Postgres, so every API instance shares the counts. Repeated wrong passwords slow down further attempts, growing to 15 minutes, but never lock an account. Limited requests get 429 rate_limited with Retry-After. The limits are in docs/api.md §10.

A background job deletes expired sessions, links, and other short-lived rows every hour. To run it on your own schedule instead, set Config.DisableCleanup and call a.Cleanup(ctx).

Security event log

Logins, failed logins, logouts, and every password, 2FA, OAuth, email, and role change are recorded in auth.auth_events, with the IP and user agent but never secrets or email addresses. They're kept for a year. Read a user's events with a.Events(ctx, userID, cursor) in admin tooling, or pass each one on, for example to Sentry or PostHog, with Hooks.OnEvent. The full list is in docs/api.md §10.

Users are emailed when they log in from a browser or app and network (IPv4 /24 or IPv6 /48) that hasn't logged in to their account in the last 90 days. Override the email with Emails.NewDeviceLogin, or turn it off with Config.DisableNewDeviceEmails.

Next.js template

templates/nextjs is the web half: the session cookie, proxy.ts, a session helper, the login, 2FA, and sign-up pages, and the pages the module's emails link to. Projects copy its auth parts into their own Next.js app.

Example

examples/api is a small Go API built on the module, showing how to wire auth.New. It serves the Next.js template in development and tests. It prints emails instead of sending them, so never deploy it.

Design

How the module is structured and how projects use it: docs/design.md. The public Go API and HTTP endpoints: docs/api.md.

Roadmap

  • Password hashing and secure token generation
  • Database schema, embedded migrations, and sessions
  • HTTP middleware, sign-up, login, and session endpoints
  • Email verification and password reset
  • Email change
  • Roles, permissions, and organizations
  • Two-factor authentication (TOTP)
  • OAuth: Google, GitHub, Apple
  • Rate limiting, security event log, and email sending
  • Next.js template
  • v1.0.0

Security

Found a vulnerability? Please report it privately. See SECURITY.md.

License

MIT

Documentation

Overview

Package auth is a shared authentication and authorization module for Go APIs: password hashing, sessions, OAuth sign-in, two-factor authentication, and role-based access control.

Its exported API follows semantic versioning: nothing is removed or changed until v2.

Index

Constants

View Source
const (
	// EventLoginSucceeded is a session started. Metadata "method" is
	// "password", "verification_link", "two_factor", "google", "github",
	// or "apple".
	EventLoginSucceeded = "login_succeeded"
	// EventLoginFailed is a wrong password or 2FA code at login. Metadata
	// "method" is "password" or "two_factor". UserID is nil for an email
	// without an account.
	EventLoginFailed      = "login_failed"
	EventLogout           = "logout"
	EventLogoutEverywhere = "logout_everywhere"
	// EventSessionEnded is a user ending one of their other sessions.
	EventSessionEnded             = "session_ended"
	EventPasswordChanged          = "password_changed"
	EventPasswordReset            = "password_reset"
	EventTwoFactorEnabled         = "two_factor_enabled"
	EventTwoFactorDisabled        = "two_factor_disabled"
	EventTwoFactorReset           = "two_factor_reset"
	EventRecoveryCodeUsed         = "recovery_code_used"
	EventRecoveryCodesRegenerated = "recovery_codes_regenerated"
	// EventOAuthLinked and EventOAuthUnlinked have metadata "provider".
	EventOAuthLinked   = "oauth_linked"
	EventOAuthUnlinked = "oauth_unlinked"
	EventEmailChanged  = "email_changed"
	// EventAppRoleChanged has metadata "role" ("" when removed).
	EventAppRoleChanged = "app_role_changed"
	// EventOrgRoleChanged and EventMemberAdded have metadata
	// "organization_id" and "role"; EventMemberRemoved has
	// "organization_id". ActorID is who made the change.
	EventOrgRoleChanged = "org_role_changed"
	EventMemberAdded    = "member_added"
	EventMemberRemoved  = "member_removed"
	// EventTwoFactorRequiredChanged has metadata "required" ("true" or
	// "false").
	EventTwoFactorRequiredChanged = "two_factor_required_changed"
)

Event types in the security event log. More may be added; existing ones never change.

View Source
const (
	PermOrganizationUpdate rbac.Permission = "organization:update"
	PermMembersRead        rbac.Permission = "members:read"
	PermMembersManage      rbac.Permission = "members:manage"
)

Permissions the module's own organization endpoints use. Projects give them to roles like any other permission; owners always have them.

View Source
const InvitationLifetime = 7 * 24 * time.Hour

InvitationLifetime is how long an invitation link works.

View Source
const OrgHeader = "X-Organization-ID"

OrgHeader names the organization a RequireOrg request is for.

View Source
const OwnerRole = "owner"

OwnerRole is the organization role that always has every permission. Config.OrgRoles must define it, and every organization keeps at least one owner.

View Source
const Schema = migrate.Schema

Schema is the Postgres schema that holds every table the module owns.

Variables

View Source
var ErrBadCursor = errors.New("auth: bad cursor")

ErrBadCursor means a cursor passed to Events wasn't one it returned.

View Source
var ErrInvalidOrgName = errors.New("auth: organization name must be 1 to 200 characters")

ErrInvalidOrgName is returned by CreateOrganization for a blank name, or one over 200 characters.

View Source
var ErrSignUpNotAllowed = errors.New("auth: sign-up not allowed")

ErrSignUpNotAllowed is returned by Hooks.OnUserCreated to refuse a sign-up, such as one from an address that isn't on the project's allowlist. The sign-up is rolled back and logged at Info, not as an error.

With email and password in verify first mode (the default), the response is exactly the one for an accepted sign-up, and no email is sent, so the endpoint doesn't reveal which addresses are allowed. With AllowUnverifiedLogin an accepted sign-up logs the user in, which can't be imitated, so a refused one gets a 403 sign_up_not_allowed. A sign-up with a provider (OAuth) also gets a 403 sign_up_not_allowed: the provider has already shown that the user owns the address.

Functions

func Can

func Can(ctx context.Context, permission rbac.Permission) bool

Can reports whether the current user has permission: against their organization role under RequireOrg, or their app role under Require. It's for finer checks inside a handler, such as whether to offer a delete button. It's false anywhere else.

func Migrate

func Migrate(ctx context.Context, pool *pgxpool.Pool) error

Migrate creates the auth schema if needed and applies any pending migrations for the module's tables. Call it before the project's own migrations, usually in the pre-deploy step. It's safe to call on every start and from several instances at once.

Types

type Actor

type Actor struct {
	// UserID is the user making the change.
	UserID *uuid.UUID
	// IP and UserAgent are the client they made it from, if known.
	IP        *netip.Addr
	UserAgent string
}

Actor is who makes a change through one of Auth's admin methods (SetAppRole, SetTwoFactorRequired, ResetTwoFactor, and CreateOrganization), and from where, for the security event log. Admin tooling handling a request gets one with ActorOf. The zero Actor records no one, for seed scripts and changes the system makes itself.

type Apple

type Apple struct {
	// BundleIDs are the iOS apps' bundle IDs, such as "com.example.app".
	// Identity tokens issued to any other app are refused.
	BundleIDs []string
}

Apple is native Sign in with Apple, for the iOS app.

type Auth

type Auth struct {
	// contains filtered or unexported fields
}

Auth serves the module's endpoints and protects the project's own. Create it with New.

func New

func New(cfg Config) (*Auth, error)

New validates cfg and returns a ready Auth. It doesn't touch the database; call Migrate first.

func (*Auth) ActorOf

func (a *Auth) ActorOf(r *http.Request) Actor

ActorOf returns the logged-in user making r, with their IP and user agent read the same way as for the module's own endpoints. Without a logged-in user, UserID is nil.

func (*Auth) Authenticated

func (a *Auth) Authenticated(h http.Handler) http.Handler

Authenticated wraps h so it only runs for a request with a valid session token in its Authorization: Bearer header. Otherwise it responds 401 with the standard error format. Inside h, UserFrom and SessionFrom return the logged-in user and their session.

A user who must have 2FA but hasn't set it up gets 403 two_factor_setup_required, here and under Require and RequireOrg.

func (*Auth) Cleanup

func (a *Auth) Cleanup(ctx context.Context) error

Cleanup deletes what has expired: sessions, email links, 2FA challenges and old wrong-code records, OAuth flows, used Apple sign-in nonces, ended rate-limit windows and login delays, and security events over a year old. Apart from the events, they're already refused, so this only frees space.

A background job runs it every hour, unless Config.DisableCleanup is set; then call it on your own schedule. Running it from several API instances at once is harmless.

func (*Auth) Close

func (a *Auth) Close(ctx context.Context) error

Close stops the cleanup job and waits for emails that are still being prepared or sent, until ctx is done. Call it when the server shuts down, after it stops accepting requests.

func (*Auth) CreateOrganization

func (a *Auth) CreateOrganization(ctx context.Context, name string, ownerID uuid.UUID, by Actor) (Organization, error)

CreateOrganization creates an organization with ownerID as its owner. It's for apps that create organizations themselves, e.g. when a client signs up, typically with Config.DisableOrganizationCreation set. by is who's creating it, for the security event log.

func (*Auth) Events

func (a *Auth) Events(ctx context.Context, userID uuid.UUID, cursor string) (EventPage, error)

Events returns a page of a user's security events, newest first, 50 at a time. Pass "" for the first page, then each page's NextCursor. It's for admin tooling and support; there's no endpoint for it.

func (*Auth) Protect

func (a *Auth) Protect(mux *http.ServeMux) http.Handler

Protect wraps the project's whole router and enforces deny by default: a request whose route wasn't wrapped by Public, Authenticated, Require, or RequireOrg gets a 500 and an error in the log, and the route's handler never runs. A forgotten permission makes a route unusable instead of open to everyone.

The access rule must be the outermost wrapper on each route: a.RequireOrg("x:y", logging(h)), not logging(a.RequireOrg("x:y", h)). Middleware for every route goes around Protect instead.

func (*Auth) Public

func (a *Auth) Public(h http.Handler) http.Handler

Public marks h as intentionally open to logged-out users. It doesn't change how h behaves; it makes the choice explicit where routes are registered, which Protect requires.

func (*Auth) Register

func (a *Auth) Register(mux *http.ServeMux)

Register adds the module's endpoints to mux. Defining a route the module already owns makes mux panic at startup, which surfaces the clash immediately.

func (*Auth) Require

func (a *Auth) Require(permission rbac.Permission, h http.Handler) http.Handler

Require wraps h so it only runs for a logged-in user whose app role (Config.AppRoles) grants permission. Otherwise it responds 401 or 403 forbidden.

It panics if no app role lists permission, which catches a misspelled permission when the route is registered rather than denying it silently.

func (*Auth) RequireOrg

func (a *Auth) RequireOrg(permission rbac.Permission, h http.Handler) http.Handler

RequireOrg wraps h so it only runs for a logged-in member of the organization named in the X-Organization-ID header, whose role there (Config.OrgRoles) grants permission. It responds 400 if the header is missing or malformed, 404 if the organization doesn't exist or the user isn't a member (so outsiders can't tell which), and 403 forbidden if the role lacks the permission. Inside h, OrgFrom returns the organization.

It panics if Config.OrgRoles isn't set, or if no role lists permission.

func (*Auth) ResetTwoFactor

func (a *Auth) ResetTwoFactor(ctx context.Context, userID uuid.UUID, by Actor) error

ResetTwoFactor turns off a user's 2FA and ends all their sessions. It's for admin tooling and support, for a user who has lost both their authenticator and their recovery codes, after confirming who they are some other way. There's no endpoint for it. The user is emailed that 2FA was turned off. It does nothing for a user without 2FA. by is who's resetting it, for the security event log.

func (*Auth) SetAppRole

func (a *Auth) SetAppRole(ctx context.Context, userID uuid.UUID, role string, by Actor) error

SetAppRole gives a user an app role from Config.AppRoles, replacing any role they had. An empty role removes it. It's for seed scripts and admin tooling; there's no endpoint for it. by is who's making the change, for the security event log.

func (*Auth) SetTwoFactorRequired

func (a *Auth) SetTwoFactorRequired(ctx context.Context, userID uuid.UUID, required bool, by Actor) error

SetTwoFactorRequired sets whether a user must have 2FA even without one of Config.TwoFactorRoles. It's for admin tooling, for individual users an admin marks. Until they set 2FA up, they can only set it up, read GET /v1/users/me, and log out. It takes effect on their next request. by is who's making the change, for the security event log.

type Config

type Config struct {
	// DB is the project's Postgres pool. Run Migrate on it first.
	DB *pgxpool.Pool

	// TrustedProxies are the networks allowed to set X-Forwarded-For and
	// X-Forwarded-User-Agent, normally the Next.js server on Railway's
	// private network. Requests from anywhere else use the connection's
	// own address and User-Agent.
	TrustedProxies []netip.Prefix

	Hooks Hooks

	// Logger receives the module's logs. Default: slog.Default().
	Logger *slog.Logger

	// Now returns the current time. Default: time.Now. Set it in tests to
	// control session expiry.
	Now func() time.Time

	// Mailer sends the module's emails. Required. Use mail/resend in
	// production.
	Mailer mail.Mailer

	// AppURL is the web app's base URL, for links in emails, e.g.
	// "https://app.example.com". Required. It must use https, except on
	// localhost.
	AppURL string

	// AppName appears in email subjects and text, e.g. "Acme", and as the
	// issuer in authenticator apps. Required.
	AppName string

	// EncryptionKey encrypts 2FA secrets: 32 random bytes, base64-encoded,
	// from an environment variable such as AUTH_ENCRYPTION_KEY. Generate one
	// with `openssl rand -base64 32`. Required. Keep a backup: if it's lost,
	// every user has to set up 2FA again.
	EncryptionKey string

	// AllowUnverifiedLogin lets users log in before verifying their email,
	// and makes sign-up log them in immediately. Off by default: new users
	// must verify their email, with their password, before they can log
	// in. See docs/api.md.
	AllowUnverifiedLogin bool

	// Emails overrides the default email templates.
	Emails Emails

	// DisableBreachCheck turns off checking new passwords against Have I
	// Been Pwned. The check is on by default; it sends only the first 5
	// characters of the password's SHA-1 hash, and is skipped if the API
	// doesn't answer within 2 seconds.
	DisableBreachCheck bool

	// OrgRoles are the roles a user can have in an organization, checked
	// by RequireOrg. They must include "owner", which always has every
	// permission. Leave nil if the app has no organizations.
	OrgRoles rbac.Roles

	// DisableOrganizationCreation removes POST /v1/organizations, for apps
	// that create organizations themselves with CreateOrganization.
	DisableOrganizationCreation bool

	// AppRoles are roles that apply across the whole app, such as staff
	// roles, checked by Require. Users have no app role until SetAppRole
	// gives them one. Leave nil if the app doesn't need them.
	AppRoles rbac.Roles

	// TwoFactorRoles are the privileged roles (the auth standard §6): staff
	// roles such as "support" always, and "owner" and "admin" when
	// organizations are shared by teams. Apps where each user gets their
	// own workspace may leave owner and admin out. A user with one of them,
	// in any organization, must have 2FA, and their sessions last at most
	// 12 hours. Until they set 2FA up, they can only set it up, read
	// GET /v1/users/me, and log out. Each name must be defined in OrgRoles
	// or AppRoles.
	TwoFactorRoles []string

	// OAuth turns on signing in with Google and GitHub.
	OAuth OAuth

	// DisableCleanup stops the module deleting expired data every hour in
	// the background. Call Auth.Cleanup on your own schedule instead.
	DisableCleanup bool

	// DisableNewDeviceEmails stops emailing users when they log in from a
	// browser or app and network they haven't used in the last 90 days.
	DisableNewDeviceEmails bool
}

Config configures the module. DB, Mailer, AppURL, AppName, and EncryptionKey are required.

type EmailData

type EmailData struct {
	// AppName is Config.AppName.
	AppName string
	// AppURL is Config.AppURL, for links that don't need a token, such as
	// the page to request a password reset.
	AppURL string
	// Link is the email's action link, with its token.
	Link string
	// ExpiresIn is how long Link works.
	ExpiresIn time.Duration
	// OrgName is the organization's name, for invitations.
	OrgName string
	// RecoveryCodesLeft is how many unused recovery codes the user has,
	// for RecoveryCodeUsed.
	RecoveryCodesLeft int
	// Provider is the sign-in provider's name, such as "Google", for
	// OAuthLinked and OAuthUnlinked.
	Provider string
	// Device is the browser or app's user agent, as it sent it, for
	// NewDeviceLogin. It's empty if it sent none. It comes from the client,
	// so treat it as untrusted text.
	Device string
	// LoggedInAt is when the login happened, in UTC, for NewDeviceLogin.
	LoggedInAt time.Time
}

EmailData is what an email template can use.

type Emails

type Emails struct {
	// VerifyEmail asks a new user to confirm their address.
	VerifyEmail func(EmailData) (subject, text, html string)
	// SignUpAttempt tells the owner of an existing account that someone
	// tried to sign up with their address. Link is a password reset link.
	SignUpAttempt func(EmailData) (subject, text, html string)
	// PasswordReset sends a password reset link.
	PasswordReset func(EmailData) (subject, text, html string)
	// PasswordChanged tells a user their password was changed or reset.
	// Link is empty.
	PasswordChanged func(EmailData) (subject, text, html string)
	// EmailChange asks a user to confirm a new address. It's sent to the
	// new address.
	EmailChange func(EmailData) (subject, text, html string)
	// EmailChanged tells the old address that the account's email was
	// changed. Link is empty.
	EmailChanged func(EmailData) (subject, text, html string)
	// Invitation invites someone to join an organization. OrgName is set.
	Invitation func(EmailData) (subject, text, html string)
	// TwoFactorEnabled, TwoFactorDisabled, and RecoveryCodesRegenerated
	// tell a user about changes to their 2FA. Link is empty.
	TwoFactorEnabled         func(EmailData) (subject, text, html string)
	TwoFactorDisabled        func(EmailData) (subject, text, html string)
	RecoveryCodesRegenerated func(EmailData) (subject, text, html string)
	// RecoveryCodeUsed tells a user a recovery code was used to log in or
	// to reset their password.
	// RecoveryCodesLeft is set. Link is empty.
	RecoveryCodeUsed func(EmailData) (subject, text, html string)
	// TwoFactorGuessing warns a user that someone got past their password
	// (or into their email, for a password reset) but then entered too
	// many wrong 2FA codes. Link is empty.
	TwoFactorGuessing func(EmailData) (subject, text, html string)
	// OAuthLinked and OAuthUnlinked tell a user a sign-in provider was
	// added to or removed from their account. Provider is set.
	OAuthLinked   func(EmailData) (subject, text, html string)
	OAuthUnlinked func(EmailData) (subject, text, html string)
	// NewDeviceLogin tells a user their account was logged in to from a
	// browser or app and network it hasn't been used from in the last 90
	// days. Device and LoggedInAt are set. Link is empty.
	NewDeviceLogin func(EmailData) (subject, text, html string)
}

Emails overrides the module's default emails. Each field returns an email's subject, plain-text body, and optional HTML body. A nil field uses the default: short, plain text, and without branding.

type Event

type Event struct {
	ID uuid.UUID
	// UserID is who it happened to. It's nil for a failed login with an
	// unknown email, and once the user is deleted.
	UserID *uuid.UUID
	// ActorID is who did it, when that's someone else.
	ActorID *uuid.UUID
	// Type is one of the Event constants.
	Type      string
	IP        *netip.Addr
	UserAgent string
	Metadata  map[string]string
	CreatedAt time.Time
}

Event is one entry in the security event log. It never contains passwords, tokens, codes, or email addresses.

type EventPage

type EventPage struct {
	Events []Event
	// NextCursor fetches the next page with Events; it's empty on the
	// last page.
	NextCursor string
}

EventPage is a page of a user's events, newest first.

type GitHub

type GitHub struct {
	ClientID string
	// ClientSecret comes from an environment variable, such as
	// GITHUB_CLIENT_SECRET.
	ClientSecret string
}

GitHub is a GitHub OAuth app.

type Google

type Google struct {
	ClientID string
	// ClientSecret comes from an environment variable, such as
	// GOOGLE_CLIENT_SECRET.
	ClientSecret string
}

Google is a Google OAuth client, of type "Web application".

type Hooks

type Hooks struct {
	// OnUserCreated runs inside the sign-up transaction. Returning an error
	// cancels the sign-up, so a project's own rows (e.g. a profile) are
	// created together with the user or not at all.
	//
	// The hook must write through tx, not the project's own pool: writes
	// through the pool aren't part of the transaction and wouldn't be
	// undone. Keep it quick, since the transaction stays open while it
	// runs.
	//
	// To refuse a sign-up, for example because the address isn't on an
	// allowlist, return ErrSignUpNotAllowed (or an error wrapping it). Any
	// other error is treated as a failure: logged as an error and, with
	// AllowUnverifiedLogin or a provider, answered with a 500.
	//
	// In verify first mode (the default), an email and password sign-up
	// answers before creating the user, so the response takes the same
	// time whether or not the address has an account. The hook then runs
	// just after the response, with the request's context values but not
	// its cancellation, and a failure can't change the 202 already sent.
	OnUserCreated func(ctx context.Context, tx pgx.Tx, u User) error

	// OnEvent runs after each security event is recorded, for projects
	// that also want auth events in their own tools, such as alerting or
	// Sentry. It runs in the request, so keep it quick and hand slow work
	// off. See the Event constants.
	OnEvent func(ctx context.Context, e Event)
}

Hooks let a project react to auth events.

type OAuth

type OAuth struct {
	Google *Google
	GitHub *GitHub
	// Apple turns on native Sign in with Apple in the iOS app. Apple isn't
	// offered on the web or Android.
	Apple *Apple
}

OAuth configures signing in with other providers. A nil provider is off.

Register these redirect URIs with each provider: {AppURL}/oauth/callback for the web app and {AppURL}/oauth/callback/mobile for the mobile app. The web app serves both pages; see docs/api.md §9.

type Org

type Org struct {
	ID   uuid.UUID
	Role string
}

Org is the organization a RequireOrg request is for, and the user's role in it.

func OrgFrom

func OrgFrom(ctx context.Context) (o Org, ok bool)

OrgFrom returns the organization the request is for. ok is false outside RequireOrg. Every query on organization data must filter by its ID.

type Organization

type Organization struct {
	ID        uuid.UUID
	Name      string
	CreatedAt time.Time
	UpdatedAt time.Time
}

Organization is an organization. Projects keep their own data about it in their own tables, referencing auth.organizations(id).

type Session

type Session struct {
	ID uuid.UUID
	// Client is "web" or "mobile".
	Client    string
	ExpiresAt time.Time
	CreatedAt time.Time
}

Session is a login session. It never contains the session token.

func SessionFrom

func SessionFrom(ctx context.Context) (s Session, ok bool)

SessionFrom returns the current session. ok is false outside the module's access rules, which is a bug in the calling code.

type User

type User struct {
	ID            uuid.UUID
	Email         string
	EmailVerified bool
	CreatedAt     time.Time
}

User is a user account. It never contains password hashes or tokens.

func UserFrom

func UserFrom(ctx context.Context) (u User, ok bool)

UserFrom returns the logged-in user. ok is false outside the module's access rules, which is a bug in the calling code.

Directories

Path Synopsis
examples
api command
Command api is an example Go API built on the auth module.
Command api is an example Go API built on the auth module.
internal
background
Package background runs work after an endpoint has responded.
Package background runs work after an endpoint has responded.
db
emailtoken
Package emailtoken creates and checks the single-use tokens sent in emails: verification, password reset, and email change.
Package emailtoken creates and checks the single-use tokens sent in emails: verification, password reset, and email change.
httpapi
Package httpapi holds the request and response plumbing shared by the module's HTTP endpoints, following the API design standard: the standard error format, strict JSON decoding, request IDs, and finding the real client behind a trusted proxy.
Package httpapi holds the request and response plumbing shared by the module's HTTP endpoints, following the API design standard: the standard error format, strict JSON decoding, request IDs, and finding the real client behind a trusted proxy.
migrate
Package migrate applies the module's embedded migrations.
Package migrate applies the module's embedded migrations.
migrations
Package migrations holds the module's SQL migrations, embedded into the binary.
Package migrations holds the module's SQL migrations, embedded into the binary.
oauth
Package oauth runs the provider side of signing in with Google and GitHub: building the authorization URL with PKCE and state, and swapping the returned code for the user's identity.
Package oauth runs the provider side of signing in with Google and GitHub: building the authorization URL with PKCE and state, and swapping the returned code for the user's identity.
oauth/oauthtest
Package oauthtest provides fake Google and GitHub servers for tests.
Package oauthtest provides fake Google and GitHub servers for tests.
password
Package password hashes and verifies passwords with argon2id, and checks new passwords against the length policy in the auth standard.
Package password hashes and verifies passwords with argon2id, and checks new passwords against the length policy in the auth standard.
pwned
Package pwned checks passwords against Have I Been Pwned's list of passwords seen in data breaches, using its range API (https://haveibeenpwned.com/API/v3#PwnedPasswords).
Package pwned checks passwords against Have I Been Pwned's list of passwords seen in data breaches, using its range API (https://haveibeenpwned.com/API/v3#PwnedPasswords).
ratelimit
Package ratelimit counts requests in fixed time windows, stored in Postgres so every API instance shares the counts.
Package ratelimit counts requests in fixed time windows, stored in Postgres so every API instance shares the counts.
recoverycode
Package recoverycode makes and checks 2FA recovery codes: single-use codes a user keeps for when they lose their authenticator.
Package recoverycode makes and checks 2FA recovery codes: single-use codes a user keeps for when they lose their authenticator.
secretbox
Package secretbox encrypts small secrets for storage, such as TOTP secrets, with AES-256-GCM.
Package secretbox encrypts small secrets for storage, such as TOTP secrets, with AES-256-GCM.
sender
Package sender sends email in the background, so endpoints respond without waiting for the email provider.
Package sender sends email in the background, so endpoints respond without waiting for the email provider.
session
Package session creates, validates, extends, and revokes login sessions.
Package session creates, validates, extends, and revokes login sessions.
testdb
Package testdb gives tests a fresh, empty Postgres database.
Package testdb gives tests a fresh, empty Postgres database.
token
Package token generates and hashes the random secrets used for sessions, email verification, and password resets.
Package token generates and hashes the random secrets used for sessions, email verification, and password resets.
totp
Package totp implements time-based one-time passwords (RFC 6238), the six-digit codes from authenticator apps.
Package totp implements time-based one-time passwords (RFC 6238), the six-digit codes from authenticator apps.
Package mail defines how the auth module sends email.
Package mail defines how the auth module sends email.
mailtest
Package mailtest provides a fake Mailer for tests.
Package mailtest provides a fake Mailer for tests.
resend
Package resend sends the auth module's email through Resend (https://resend.com), the standard transactional email provider.
Package resend sends the auth module's email through Resend (https://resend.com), the standard transactional email provider.
Package rbac defines roles and permissions for the auth module.
Package rbac defines roles and permissions for the auth module.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL