authtest

package
v1.5.0 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 43 Imported by: 0

Documentation

Overview

Package authtest runs AuthKit in a host's Go tests: a real Client on a scratch PostgreSQL schema, an Outbox that captures every email and SMS, an identity provider to sign in with (IdP), and helpers for the usual setup (a verified user, a signed-in session, a role, an authenticator app, a device key, a replica, a stale session).

auth, outbox := authtest.New(t, authtest.WithConfig(func(c *authkit.Config) {
	c.Roles = myapp.Roles()
}))
alice := authtest.NewUser(t, auth)
authtest.GrantRole(t, auth, iam.RootGroup(), iam.UserSubject(alice.ID), myapp.Admin)
tokens := authtest.SignIn(t, auth, alice)
// call the host's handlers with "Bearer "+tokens.AccessToken, or drive
// auth.Handler() and read codes and links from outbox.

New needs AUTHKIT_TEST_DATABASE_URL, a database where the test may create schemas. Without it the test is skipped, or fails when AUTHKIT_TEST_REQUIRE_DB=1. AUTHKIT_TEST_KEEP_DB=1 keeps each schema.

The package is covered by AuthKit's compatibility contract like the rest of the module (docs/stability.md).

Index

Constants

View Source
const (
	Issuer   = "https://example.com"
	Audience = "authtest"
)

Issuer and Audience are the token issuer and audience New configures.

View Source
const Password = "Authtest-password-1"

Password is the password NewUser gives every account.

Variables

This section is empty.

Functions

func ClientSecretSHA256 added in v1.5.0

func ClientSecretSHA256(secret string) string

ClientSecretSHA256 is the OAuthClientConfig.SecretSHA256 of secret.

func GrantRole added in v0.147.0

func GrantRole(t testing.TB, auth *authkit.Client, group iam.GroupRef, subject iam.Subject, role iam.Role)

GrantRole gives subject role in group with system authority. A role that requires MFA needs the account's second factor first (EnrollTOTP).

func PKCEChallenge added in v1.5.0

func PKCEChallenge(verifier string) string

PKCEChallenge is verifier's RFC 7636 S256 code challenge.

func Replica added in v0.147.0

func Replica(t testing.TB, auth *authkit.Client, opts ...Option) *authkit.Client

Replica builds another Client on auth's database and schema, as another replica of the deployment runs: the Config and Deps auth was built with (its Outbox included, when New built it), then opts. auth may be any Client authkit.New built, a host's own included. A different Token.Issuer makes a sibling deployment sharing the account store; different HTTPConfig serves the same accounts another way. HTTP is copied, so opts may set its fields; replace, never mutate, the maps and slices opts change: the replica shares auth's.

func RevokeRole added in v0.147.0

func RevokeRole(t testing.TB, auth *authkit.Client, group iam.GroupRef, subject iam.Subject, role iam.Role)

RevokeRole takes role in group from subject with system authority.

func SignIn added in v0.147.0

func SignIn(t testing.TB, auth *authkit.Client, u User) iam.TokenSet

SignIn signs u in with its password through auth's HTTP surface and follows the AuthResult to a session: a second factor is answered with u.TOTP (or the account's remembered app, TOTPOf); an enrollment the deployment requires adds an authenticator app with the enrollment token, which SignIn remembers for the account's later sign-ins. It returns the session's tokens.

func StaleSession added in v0.147.0

func StaleSession(t testing.TB, auth *authkit.Client, accessToken string) string

StaleSession moves the sign-in of the session behind accessToken a day into the past, as if its user signed in long ago, and returns a new access token for that session: routes that need a recent sign-in then ask it for a step-up. auth may be any Client authkit.New built.

func TOTPCode added in v0.147.0

func TOTPCode(t testing.TB, secret string, at time.Time) string

TOTPCode is the RFC 6238 code of secret (base32) at the given time: SHA-1, six digits, 30-second steps.

Types

type AuthorizationServer added in v1.5.0

type AuthorizationServer struct {
	// Client is the deployment's AuthKit Client: its users, roles and
	// sessions.
	Client *authkit.Client
	Outbox *Outbox
	// URL is the issuer: the HTTPS server's URL, without a trailing slash.
	URL string
	// contains filtered or unexported fields
}

AuthorizationServer is an AuthKit authorization server (Config. AuthorizationServer) on an HTTPS test server, for testing a resource server or an OAuth client against real sign-ins and real tokens. URL is the issuer: its metadata (iam.OpenIDConfigurationPath), JWKS and OAuth endpoints are served beneath it, as in production.

func NewAuthorizationServer added in v1.5.0

func NewAuthorizationServer(t testing.TB, opts ...Option) *AuthorizationServer

NewAuthorizationServer serves New's Client over HTTPS (an httptest TLS server) with Token.Issuer set to the server's URL, so verifiers fetch its metadata and keys as they would from a deployment. Declare its clients and resource servers with WithConfig (Config.AuthorizationServer); opts apply after the issuer is set. It needs AUTHKIT_TEST_DATABASE_URL like New, and is closed at cleanup.

func (*AuthorizationServer) Approve added in v1.5.0

func (as *AuthorizationServer) Approve(t testing.TB, accessToken, id string) string

Approve approves the pending request id with the sign-in accessToken belongs to, as the SPA does, and returns the client redirect.

func (*AuthorizationServer) Authorize added in v1.5.0

func (as *AuthorizationServer) Authorize(t testing.TB, u User, f CodeFlow) OAuthTokens

Authorize signs u in and runs the authorization code flow for it as a browser and the SPA would: the authorization request with PKCE, the SPA's approval for that sign-in, then the code's redemption at the token endpoint. It fails the test on any refusal.

func (*AuthorizationServer) AuthorizeAs added in v1.5.0

func (as *AuthorizationServer) AuthorizeAs(t testing.TB, signedIn iam.TokenSet, f CodeFlow) OAuthTokens

AuthorizeAs is Authorize for a sign-in the test already holds.

func (*AuthorizationServer) BeginAuthorization added in v1.5.0

func (as *AuthorizationServer) BeginAuthorization(t testing.TB, f CodeFlow, verifier, state string) string

BeginAuthorization sends an authorization request with verifier's S256 challenge and returns the pending request's id the server sent the browser to the SPA with.

func (*AuthorizationServer) ClientCredentials added in v1.5.0

func (as *AuthorizationServer) ClientCredentials(t testing.TB, clientID, clientSecret, resource string, scopes []string, key *DPoPKey) OAuthTokens

ClientCredentials gets a confidential client's own access token for resource; key, when set, binds it.

func (*AuthorizationServer) Exchange added in v1.5.0

Exchange runs a token exchange and fails the test on a refusal. A public client without a key gets a fresh one.

func (*AuthorizationServer) HTTPClient added in v1.5.0

func (as *AuthorizationServer) HTTPClient() *http.Client

HTTPClient trusts the server's certificate and never follows a redirect, so a test reads each Location itself.

func (*AuthorizationServer) Refresh added in v1.5.0

func (as *AuthorizationServer) Refresh(t testing.TB, clientID, clientSecret string, tokens OAuthTokens) OAuthTokens

Refresh redeems tokens' refresh token as clientID (with clientSecret for a confidential client), proving tokens' DPoP key, and returns the rotated tokens.

func (*AuthorizationServer) Revoke added in v1.5.0

func (as *AuthorizationServer) Revoke(t testing.TB, clientID, clientSecret, token string) int

Revoke posts token to the revocation endpoint (RFC 7009) as clientID and returns the status.

func (*AuthorizationServer) Token added in v1.5.0

func (as *AuthorizationServer) Token(t testing.TB, req TokenRequest) (int, []byte)

Token posts req to the token endpoint and returns the status and body.

type CodeFlow added in v1.5.0

type CodeFlow struct {
	ClientID     string
	ClientSecret string
	RedirectURI  string
	Resource     string
	Scopes       []string
	Nonce        string
	DPoP         *DPoPKey
}

CodeFlow is one authorization code request. ClientSecret is set for a confidential client; Resource and Scopes are what the client asks for. DPoP binds the tokens to a key: a public client always has one (Authorize makes it when nil).

type DPoPKey added in v1.5.0

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

DPoPKey is a client's RFC 9449 proof-of-possession key (ES256), as a browser keeps it: tokens bound to it name its Thumbprint, and every request using them carries a fresh Proof.

func NewDPoPKey added in v1.5.0

func NewDPoPKey(t testing.TB) *DPoPKey

NewDPoPKey generates a P-256 key.

func (*DPoPKey) Authorize added in v1.5.0

func (k *DPoPKey) Authorize(t testing.TB, req *http.Request, accessToken, nonce string)

Authorize sets req's DPoP-bound Authorization and a fresh proof for its method and URL (without the query).

func (*DPoPKey) Proof added in v1.5.0

func (k *DPoPKey) Proof(t testing.TB, method, target, accessToken, nonce string) string

Proof is a single-use DPoP proof for method and target (no query): accessToken is the token it accompanies ("" at a token endpoint), nonce the server's DPoP-Nonce ("" for none).

func (*DPoPKey) Thumbprint added in v1.5.0

func (k *DPoPKey) Thumbprint() string

Thumbprint is the key's RFC 7638 thumbprint: a bound token's cnf.jkt.

type DeviceKey added in v0.147.0

type DeviceKey struct {
	ID  string
	Key ed25519.PrivateKey
	// AccessToken is the enrollment's sign-in.
	AccessToken string
}

DeviceKey is a device key enrolled on an account (see package devicekey).

func EnrollDeviceKey added in v0.147.0

func EnrollDeviceKey(t testing.TB, auth *authkit.Client, outbox *Outbox, u User) DeviceKey

EnrollDeviceKey enrolls a new Ed25519 device key on u with the devicekey client, reading the emailed code from outbox and answering a second factor with u.TOTP. The Client needs Config.DeviceKeys.Enabled.

type IdP added in v1.1.0

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

IdP is an OpenID Provider on a local TLS server, for tests of signing in with an identity provider: discovery, JWKS, and the token and userinfo endpoints. A sign-in start redirects the browser to it; SignIn answers the way it would.

func NewIdP added in v1.1.0

func NewIdP(t testing.TB) *IdP

NewIdP starts an IdP, closed at the test's cleanup.

func (*IdP) Provider added in v1.1.0

func (p *IdP) Provider(name string, opts ...provider.Option) provider.Provider

Provider is an OpenID Connect provider named name for this IdP, trusted to verify email, for Deps.Providers. opts come after the defaults.

func (*IdP) SignIn added in v1.1.0

func (p *IdP) SignIn(t testing.TB, authURL string, user provider.Identity) url.Values

SignIn is the query of the IdP's redirect back to AuthKit's callback once user signs in at authURL, the IdP URL a sign-in start redirected to: its state and a code. AuthKit redeems the code for an ID token naming user's Subject, Email (verified when EmailVerified), PreferredUsername and DisplayName.

type Message added in v0.147.0

type Message = testoutbox.Message

Message is one captured email or SMS: Channel ("email" or "sms"), Kind, To, Language and what it carries (Code, Link and the Link's Token, the verification Purpose, a ContactChange or DeviceKey notice).

type OAuthTokens added in v1.5.0

type OAuthTokens struct {
	AccessToken     string   `json:"access_token"`
	TokenType       string   `json:"token_type"`
	ExpiresIn       int64    `json:"expires_in"`
	Scope           string   `json:"scope"`
	IDToken         string   `json:"id_token"`
	RefreshToken    string   `json:"refresh_token"`
	IssuedTokenType string   `json:"issued_token_type"`
	DPoP            *DPoPKey `json:"-"`
}

OAuthTokens is the token endpoint's answer. DPoP is the key the tokens are bound to, nil for bearer tokens.

type Option added in v0.147.0

type Option func(*setup)

Option adjusts what New builds.

func WithConfig added in v0.147.0

func WithConfig(fn func(*authkit.Config)) Option

WithConfig edits the Config New passes to authkit.New, after its defaults.

func WithDeps added in v0.147.0

func WithDeps(fn func(*authkit.Deps)) Option

WithDeps edits the Deps New passes to authkit.New. The Outbox's senders are already Email and SMS; set Postgres to use a pool of your own instead of AUTHKIT_TEST_DATABASE_URL.

type Outbox added in v0.147.0

type Outbox = testoutbox.Outbox

Outbox captures every email and SMS AuthKit sends, in order, so a test can complete sign-up, verification, reset and sign-in flows. New wires one; for a Client built another way, pass Email() and SMS() as Deps.Email and Deps.SMS. Its methods:

  • Last(t, kind, to) Message: the newest message of kind to that address or number ("" = anyone); the test fails when there is none.
  • Messages(kind, to) []Message: all of them, oldest first ("" = any).
  • Email() authkit.EmailSender, SMS() authkit.SMSSender: the senders.
  • SetEmailHealth(err), SetSMSHealth(err): what the senders' CheckHealth returns (nil, the default, is healthy), so a test can take a channel down and bring it back.

For example, out.Last(t, iam.MessageVerification, email).Code.

func New added in v0.147.0

func New(t testing.TB, opts ...Option) (*authkit.Client, *Outbox)

New migrates AuthKit into a fresh schema, builds a Client on it, and returns the Client with the Outbox wired as its email and SMS senders. The defaults differ from a zero Config only where a test needs them to:

  • Token: Issuer and Audience.
  • TwoFactor.TOTPSecretKey: random, so authenticator apps can enroll.
  • HTTP: served (DirectPeerIP), with every rate limit lifted (RateLimits).
  • SignIn: every limit off, since tests sign many accounts in from one address.
  • Deps.KeySource: an RSA key generated once per test binary.
  • Schema and River.Schema: the scratch schema, unless set.

The Client is not started: call Start when a test needs River's work, such as Deps.OnEvent or the deletion hooks. Cleanup closes the Client and drops the scratch schema.

type TOTP added in v0.147.0

type TOTP struct {
	Secret string
	// contains filtered or unexported fields
}

TOTP is an authenticator app enrolled on an account.

func EnrollTOTP added in v0.147.0

func EnrollTOTP(t testing.TB, auth *authkit.Client, u User) *TOTP

EnrollTOTP signs u in and adds an authenticator app through auth's HTTP surface, as a user would. SignIn uses it from now on; keeping it as u.TOTP works too.

func TOTPOf added in v0.149.0

func TOTPOf(userID string) *TOTP

TOTPOf is the authenticator app EnrollTOTP or SignIn enrolled on the account userID; nil for none.

func (*TOTP) Code added in v0.147.0

func (a *TOTP) Code(t testing.TB) string

Code returns a code AuthKit has not accepted yet. AuthKit takes each 30s step once, and at most one step ahead, so Code waits for the clock when both usable steps are spent.

type TokenExchange added in v1.5.0

type TokenExchange struct {
	ClientID     string
	ClientSecret string
	SubjectToken string
	Resource     string
	Scopes       []string
	DPoP         *DPoPKey
}

TokenExchange is an RFC 8693 request: SubjectToken, the user's AuthKit access token (SignIn's), for an access token to Resource.

type TokenRequest added in v1.5.0

type TokenRequest struct {
	ClientID     string
	ClientSecret string
	Params       url.Values
	DPoP         *DPoPKey
}

TokenRequest is one raw token endpoint request: Params as clientID (with Basic authentication when ClientSecret is set), with a DPoP proof of DPoP when set.

type User added in v0.147.0

type User struct {
	iam.User
	Email    string
	Password string
	// TOTP is the authenticator app SignIn answers a second-factor challenge
	// with. Unset, SignIn uses the app EnrollTOTP or an earlier SignIn enrolled
	// on the account (TOTPOf).
	TOTP *TOTP
}

User is an account and what SignIn needs to sign it in. Email is the account's address as text ("" for none).

func NewUser added in v0.147.0

func NewUser(t testing.TB, auth *authkit.Client) User

NewUser creates an account with a verified email, a username and Password, as the host's own code would (Client.CreateUser). The username (user plus 16 random hex digits) and email are random, so test processes sharing one schema never collide.

Jump to

Keyboard shortcuts

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