accounts

package
v0.3.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: 26 Imported by: 0

Documentation

Overview

Package accounts is tanGO's first-party, optional, installable reusable app: a ready-made HTML email/password register/login/logout flow, built by composing github.com/angvp/tango/auth's primitives rather than adding to them. auth stays primitive, unopinionated machinery; accounts is the batteries-included convenience layer on top for a project that wants a conventional account system without hand-rolling one. Installing it is optional and purely additive — a project with custom identity needs keeps using auth primitives directly and simply doesn't install this package. See docs/guides/accounts.md.

Index

Constants

View Source
const DefaultSessionCookieName = "tango_account_session"

DefaultSessionCookieName is the session cookie's default name, override via WithSessionCookieName. Exported so a host calling RequireLogin or CurrentAccountID/CurrentAccount can reference it instead of hardcoding the string when accounts.New was installed with no session-cookie-name option.

View Source
const EventMailDropped = "tango.accounts.mail_dropped"

EventMailDropped is logged when the outbox is full and an email is dropped. Its only attribute is purpose. The HTTP response is unchanged.

View Source
const EventMailFailed = "tango.accounts.mail_failed"

EventMailFailed is logged when the outbox can't send an email: the Sender failed, or preparing the email did. Its attributes are purpose and a redacted error; never the address, the link or the message.

Variables

This section is empty.

Functions

func CurrentAccountID

func CurrentAccountID(ctx *tango.Context, store *db.Store, cookieName string) (int64, bool, error)

CurrentAccountID returns the requesting Account's ID for a host's own View, or ok=false for a missing, invalid, expired, or now-inactive session — consistent with RequireLogin's strict Active check (ticket 07). Thin sugar over the same accountFromToken RequireLogin uses.

cookieName must match whatever accounts.New was configured with (DefaultSessionCookieName if using the default) — mirroring auth.CurrentUserID's own explicit-cookieName shape, since this helper has no other way to know it.

func New

func New(store *db.Store, opts ...Option) tango.App

New constructs the accounts application. opts configures optional accounts-wide behavior; accounts.New(store) with no options is the baseline this milestone establishes and stays valid as later options are added.

func RequireLogin

func RequireLogin(store *db.Store, cookieName string, loginPath string, next tango.View) tango.View

RequireLogin wraps next so it only runs for a request carrying a valid, unexpired session for an Active Account — resolved fresh on every call via accountFromToken, so a deactivated Account's standing session stops working immediately. A request that fails this check (missing session, invalid/expired token, or a now-inactive Account — all treated identically, since Account has only one gate, unlike AdminUser's separate Active/IsStaff tiers) redirects to loginPath with a "next" query parameter pointing back at the original request.

cookieName must match whatever accounts.New was configured with (DefaultSessionCookieName if using the default).

func RequireVerified added in v0.2.0

func RequireVerified(store *db.Store, cookieName string, loginPath string, next tango.View) tango.View

RequireVerified wraps next so it only runs for a logged-in account with a Verified email. Like RequireLogin, a request without a valid session for an active account redirects to loginPath with a "next" parameter. A logged-in account that hasn't verified its email gets a 403 HTML page with a button to send a new verification link (which needs WithMail). Verification never stops an account from logging in; this guard is the only thing that acts on it.

Types

type Account

type Account struct {
	ID              int64  `tango:"pk"`
	Email           string `tango:"unique"`
	PasswordHash    string
	Active          bool
	CreatedAt       time.Time
	EmailVerifiedAt time.Time
}

Account is the accounts app's own Application user model — a plain identity, credential, activity state, and timestamp. tanGO deliberately ships no permission-shaped field (no IsStaff, Role, Group, or Permission): an app wanting roles or permissions writes its own View wrapper against its own domain data, including Account itself if this app is installed. See docs/guides/accounts.md.

Active gates both a fresh login and every subsequent request through an already-valid session — not just login, unlike auth.SessionUser, which has no notion of Active at all.

EmailVerifiedAt is when the owner proved they receive mail at Email; the zero time means unverified. It gates nothing by itself: an app that needs a verified email guards its Views with RequireVerified. Whoever changes Email must clear it.

func CurrentAccount

func CurrentAccount(ctx *tango.Context, store *db.Store, cookieName string) (Account, bool, error)

CurrentAccount returns the requesting Account's full row for a host's own View, or ok=false under the same conditions as CurrentAccountID.

type AccountSession

type AccountSession struct {
	ID        int64  `tango:"pk"`
	Token     string `tango:"unique"`
	UserID    int64  `tango:"fk=Account,index"`
	ExpiresAt time.Time
}

AccountSession is one active login for an Account: a session token, which Account it belongs to, and a fixed expiry. Its shape is not a free choice — it satisfies auth.CreateSession's reflection contract (Token string, UserID foreign-keyed to Account, ExpiresAt time.Time), mirroring AdminSession's shape without being built on the same code path admin uses. Deleting a session's row logs out that one login without affecting any other session belonging to the same Account.

type AccountToken added in v0.2.0

type AccountToken struct {
	ID          int64  `tango:"pk"`
	TokenHash   string `tango:"unique"`
	AccountID   int64  `tango:"fk=Account,index"`
	Purpose     TokenPurpose
	AddressHash string
	ExpiresAt   time.Time
}

AccountToken is one outstanding emailed token: a password reset or an email verification for one Account. Only hashes are stored: TokenHash is the SHA-256 of the token the email carried, and AddressHash the SHA-256 of the normalized address it was sent to, so a token stops working if the account's Email changes. A token is single-use and expires at ExpiresAt.

type MailConfig added in v0.2.0

type MailConfig struct {
	// Sender delivers the emails, for example a *mail.SMTPSender.
	Sender tangomail.Sender
	// From is the address the emails come from, such as
	// "Shop <noreply@example.com>".
	From string
	// BaseURL is the Public base URL links are built on: an absolute
	// https:// origin such as "https://example.com", or http:// for
	// localhost or a loopback IP during development. It has no path. Links
	// are never built from the request's Host.
	BaseURL string
	// Logger receives EventMailFailed and EventMailDropped. Nil uses
	// slog.Default.
	Logger *slog.Logger
}

MailConfig turns on password reset and email verification.

type Option

type Option func(*accountsConfig)

Option configures accounts.New.

func WithMail added in v0.2.0

func WithMail(config MailConfig) Option

WithMail turns on password reset and email verification, sending through config.Sender. Without it, their routes aren't mounted and accounts sends no email. config is validated when the app registers, so RunRegistration (and -check) fail on a missing Sender, a malformed From, or a BaseURL that isn't an absolute https:// origin. See ADR 0045.

func WithSessionCookieName

func WithSessionCookieName(name string) Option

WithSessionCookieName overrides the session cookie's name, which defaults to "tango_account_session".

func WithSessionDuration

func WithSessionDuration(d time.Duration) Option

WithSessionDuration sets how long a created AccountSession stays valid, overriding the default (30 days — deliberately longer than admin's fixed 24-hour session, since public-user and operator expectations differ).

func WithSignupDisabled

func WithSignupDisabled() Option

WithSignupDisabled opts an installation out of self-service registration. /accounts/register/ stays mounted and responds with a clear closed-registration message — never a bare 404 — while /accounts/login/ remains fully available. Signup is enabled by default.

func WithTrustedProxies added in v0.3.0

func WithTrustedProxies(proxies ...*net.IPNet) Option

WithTrustedProxies tells the failed-attempt limiters of login, registration, password reset and verification resend which reverse proxies sit in front of the application. Without it they count per connection address and never read a forwarded header, which is right for a direct deployment. Behind a proxy that address is the proxy's, so every user would share one limit. Naming the proxy's network here makes them use the client address the proxy saw: the rightmost X-Forwarded-For entry outside these networks, and only for a request whose connection comes from one of them. A header from any other peer is ignored.

type TokenPurpose added in v0.2.0

type TokenPurpose string

TokenPurpose is what an AccountToken proves.

const (
	PurposePasswordReset     TokenPurpose = "password_reset"
	PurposeEmailVerification TokenPurpose = "email_verification"
)

The two purposes an AccountToken can have.

Jump to

Keyboard shortcuts

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