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
- func CurrentAccountID(ctx *tango.Context, store *db.Store, cookieName string) (int64, bool, error)
- func New(store *db.Store, opts ...Option) tango.App
- func RequireLogin(store *db.Store, cookieName string, loginPath string, next tango.View) tango.View
- func RequireVerified(store *db.Store, cookieName string, loginPath string, next tango.View) tango.View
- type Account
- type AccountSession
- type AccountToken
- type MailConfig
- type Option
- type TokenPurpose
Constants ¶
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.
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.
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 ¶
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 ¶
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 ¶
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.
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 ¶
WithSessionCookieName overrides the session cookie's name, which defaults to "tango_account_session".
func WithSessionDuration ¶
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
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.