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
- func ClientSecretSHA256(secret string) string
- func GrantRole(t testing.TB, auth *authkit.Client, group iam.GroupRef, subject iam.Subject, ...)
- func PKCEChallenge(verifier string) string
- func Replica(t testing.TB, auth *authkit.Client, opts ...Option) *authkit.Client
- func RevokeRole(t testing.TB, auth *authkit.Client, group iam.GroupRef, subject iam.Subject, ...)
- func SignIn(t testing.TB, auth *authkit.Client, u User) iam.TokenSet
- func StaleSession(t testing.TB, auth *authkit.Client, accessToken string) string
- func TOTPCode(t testing.TB, secret string, at time.Time) string
- type AuthorizationServer
- func (as *AuthorizationServer) Approve(t testing.TB, accessToken, id string) string
- func (as *AuthorizationServer) Authorize(t testing.TB, u User, f CodeFlow) OAuthTokens
- func (as *AuthorizationServer) AuthorizeAs(t testing.TB, signedIn iam.TokenSet, f CodeFlow) OAuthTokens
- func (as *AuthorizationServer) BeginAuthorization(t testing.TB, f CodeFlow, verifier, state string) string
- func (as *AuthorizationServer) ClientCredentials(t testing.TB, clientID, clientSecret, resource string, scopes []string, ...) OAuthTokens
- func (as *AuthorizationServer) Exchange(t testing.TB, x TokenExchange) OAuthTokens
- func (as *AuthorizationServer) HTTPClient() *http.Client
- func (as *AuthorizationServer) Refresh(t testing.TB, clientID, clientSecret string, tokens OAuthTokens) OAuthTokens
- func (as *AuthorizationServer) Revoke(t testing.TB, clientID, clientSecret, token string) int
- func (as *AuthorizationServer) Token(t testing.TB, req TokenRequest) (int, []byte)
- type CodeFlow
- type DPoPKey
- type DeviceKey
- type IdP
- type Message
- type OAuthTokens
- type Option
- type Outbox
- type TOTP
- type TokenExchange
- type TokenRequest
- type User
Constants ¶
const ( Issuer = "https://example.com" Audience = "authtest" )
Issuer and Audience are the token issuer and audience New configures.
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
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
PKCEChallenge is verifier's RFC 7636 S256 code challenge.
func Replica ¶ added in v0.147.0
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
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
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.
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
func (as *AuthorizationServer) Exchange(t testing.TB, x TokenExchange) OAuthTokens
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
NewDPoPKey generates a P-256 key.
func (*DPoPKey) Authorize ¶ added in v1.5.0
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
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
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
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 (*IdP) Provider ¶ added in v1.1.0
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
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
WithConfig edits the Config New passes to authkit.New, after its defaults.
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
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
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.
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
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).