auth

package
v0.18.0 Latest Latest
Warning

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

Go to latest
Published: Aug 17, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package auth implements passwordless, allowlist-closed authentication for team mode: magic-link issuance/verification and server-side cookie sessions. It is a library — no HTTP types cross its boundary; the gateway adapts it to endpoints.

Index

Constants

View Source
const (
	DefaultTokenTTL   = 15 * time.Minute
	DefaultSessionTTL = 30 * 24 * time.Hour
)

Defaults for token and session lifetimes.

View Source
const MinPasswordLen = 8

MinPasswordLen is the minimum accepted password length.

Variables

View Source
var ErrAccountDisabled = errors.New("account is disabled")

ErrAccountDisabled is returned when an SSO login resolves to a disabled user.

View Source
var ErrInvalidCredentials = errors.New("invalid email or password")

ErrInvalidCredentials is returned for any password-login failure — unknown email, no password set, disabled account, or wrong password — deliberately indistinguishable so the endpoint cannot be used to enumerate accounts.

View Source
var ErrInvalidSession = errors.New("invalid or expired session")

ErrInvalidSession is returned for an unknown or expired session.

View Source
var ErrInvalidToken = errors.New("invalid or expired token")

ErrInvalidToken is returned for any magic-link failure (unknown, expired, or already consumed) — deliberately indistinguishable.

View Source
var ErrNotAllowed = errors.New("email not allowed")

ErrNotAllowed is returned when an SSO login resolves a verified email that is not allowlisted. No user or identity row is created — SSO never bypasses the allowlist.

View Source
var ErrWeakPassword = errors.New("password too short")

ErrWeakPassword is returned when a password fails the minimum-length policy.

Functions

This section is empty.

Types

type Config

type Config struct {
	// BaseURL is the externally visible origin; magic links are built as
	// BaseURL + "/api/auth/verify?token=...".
	BaseURL string
	// AppName is shown in the login email.
	AppName string
	// TokenTTL is the magic-link lifetime (default 15m).
	TokenTTL time.Duration
	// SessionTTL is the cookie session lifetime (default 30d).
	SessionTTL time.Duration
	// Logger defaults to slog.Default().
	Logger *slog.Logger
}

Config configures the auth service.

type GitHubProvider added in v0.17.0

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

GitHubProvider drives GitHub OAuth2 sign-in. Satisfies the gateway package's SSOProvider interface structurally (no import of gateway here).

func NewGitHubProvider added in v0.17.0

func NewGitHubProvider(clientID, clientSecret, redirectURL string) *GitHubProvider

NewGitHubProvider builds a GitHub OAuth2 client. Unlike Google, no discovery call is made — this never fails.

func (*GitHubProvider) AuthURL added in v0.17.0

func (g *GitHubProvider) AuthURL(state, _ string) string

AuthURL returns the GitHub consent-screen redirect URL for state (CSRF). GitHub's plain OAuth2 flow has no nonce/ID-token concept; nonce is unused.

func (*GitHubProvider) Exchange added in v0.17.0

func (g *GitHubProvider) Exchange(ctx context.Context, code, _ string) (subject, verifiedEmail string, err error)

Exchange trades an authorization code for GitHub's verified subject (numeric account id) and primary verified email.

type GoogleProvider added in v0.17.0

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

GoogleProvider drives Google OIDC sign-in. Satisfies the gateway package's SSOProvider interface structurally (no import of gateway here).

func NewGoogleProvider added in v0.17.0

func NewGoogleProvider(ctx context.Context, clientID, clientSecret, redirectURL string) (*GoogleProvider, error)

NewGoogleProvider performs OIDC discovery against accounts.google.com — a real network call. Bound it with a context deadline; a discovery failure should be treated as a fatal startup error (an operator who configured Google SSO intends it to work, not silently never offer the button).

func (*GoogleProvider) AuthURL added in v0.17.0

func (g *GoogleProvider) AuthURL(state, nonce string) string

AuthURL returns the Google consent-screen redirect URL for state (CSRF) and nonce (OIDC replay protection).

func (*GoogleProvider) Exchange added in v0.17.0

func (g *GoogleProvider) Exchange(ctx context.Context, code, nonce string) (subject, verifiedEmail string, err error)

Exchange trades an authorization code for Google's verified subject and email, checking the nonce echoes what AuthURL sent.

type Principal

type Principal struct {
	UserID     uuid.UUID
	Username   string
	Email      string
	Superadmin bool
	Disabled   bool
}

Principal is the authenticated identity resolved from a session.

func (*Principal) Actor

func (p *Principal) Actor() team.Actor

Actor converts a Principal into a team.Actor for service calls.

type Service

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

Service performs authentication over the team store.

func NewService

func NewService(st *store.Store, teamSvc *team.Service, mailer mail.Mailer, cfg Config) (*Service, error)

NewService creates the auth service.

func (*Service) Authenticate

func (s *Service) Authenticate(ctx context.Context, rawSessionToken string) (*Principal, error)

Authenticate resolves a session cookie token to a Principal, sliding the expiry when it is stale. Returns ErrInvalidSession for unknown/expired sessions or disabled users.

func (*Service) CompleteSSOLogin added in v0.17.0

func (s *Service) CompleteSSOLogin(ctx context.Context, provider identity.Provider, subject, verifiedEmail, userAgent, ip string) (sessionToken string, u *ent.User, err error)

CompleteSSOLogin resolves a provider's verified identity to a user and opens a session, mirroring VerifyMagicLink's session-issuance shape. subject is the provider's stable id (OIDC sub / GitHub numeric account id); verifiedEmail must already be provider-verified — this trusts it.

A returning identity (provider, subject already linked) logs into its user directly, with no allowlist re-check — they are already a member. A first-time identity is only linked after IsEmailAllowed passes; a non-allowlisted email is rejected before any user or identity row is created (ErrNotAllowed). Because linking resolves the user by email via team.Service.EnsureUser, a magic-link user who later signs in via SSO with the same email lands in their existing account.

func (*Service) LoginWithPassword added in v0.18.0

func (s *Service) LoginWithPassword(ctx context.Context, email, plaintext, userAgent, ip string) (sessionToken string, u *ent.User, err error)

LoginWithPassword verifies an email+password credential and, on success, opens a session (mirroring VerifyMagicLink's shape). Every failure — unknown email, no password set, disabled account, or a wrong password — returns ErrInvalidCredentials so callers cannot enumerate accounts. Login is inherently allowlist-gated: a password only exists on an already-created (therefore allowlisted) user.

func (*Service) Logout

func (s *Service) Logout(ctx context.Context, rawSessionToken string) error

Logout revokes the session identified by its raw cookie token. Unknown tokens are a no-op (idempotent logout).

func (s *Service) RequestMagicLink(ctx context.Context, email, clientIP string) error

RequestMagicLink issues and emails a login link when the email is allowed.

The response is uniform by contract: this returns nil for both allowed and non-allowed emails (doing nothing in the latter case), so callers cannot enumerate the allowlist. Only malformed input yields an error. Internal failures (token store, mail delivery) are logged, not surfaced — the operator sees them, the requester does not.

func (*Service) SetPassword added in v0.18.0

func (s *Service) SetPassword(ctx context.Context, actor team.Actor, userID uuid.UUID, current, newPlain string) error

SetPassword sets or replaces a user's password. Authorization mirrors the team user mutations: a user may set their own (userID == actor.UserID), or a superadmin may set anyone's. When a user changes their own password and one is already set, the current password must be supplied and verified; a first-time self-set (no existing password, e.g. after magic-link) and any superadmin set skip that check. newPlain must meet MinPasswordLen.

func (s *Service) VerifyMagicLink(ctx context.Context, rawToken string) (sessionToken string, u *ent.User, err error)

VerifyMagicLink consumes a token and returns a new session's raw token (to be set as the cookie) plus the resolved user. Any failure returns ErrInvalidToken. A disabled user is rejected.

Jump to

Keyboard shortcuts

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