Documentation
¶
Overview ¶
Package fiberauth provides the shared auth building blocks used by creativeyann17's Fiber apps: JWT sessions with TokenVersion revocation, login throttling, TOTP 2FA, trusted devices, single-use tickets, 6-digit email codes, and a Resend mail client. Storage-agnostic: every piece that touches a user record takes closures or plain data.
Index ¶
- Constants
- func AdminOnly(c *fiber.Ctx) error
- func AuthMiddleware(secret string, bootTime time.Time, onReject func(reason string, c *fiber.Ctx)) fiber.Handler
- func CheckBackupCode(hashed []string, input string) (matchIndex int, ok bool)
- func ClientIP(c *fiber.Ctx) string
- func CookieDeviceID(cookieValue string) string
- func DeviceCookie(name, id, plain string, ttl time.Duration) *fiber.Cookie
- func DummyVerify(pw string)
- func ExpiredDeviceCookie(name string) *fiber.Cookie
- func GenerateBackupCodes(n int) (plaintext []string, hashed []string, err error)
- func GenerateCode() (string, error)
- func GenerateTOTPSecret(cfg TOTPConfig, accountName string) (secret, uri string, err error)
- func HasRole(c *fiber.Ctx, r string) bool
- func HashPassword(pw string) (string, error)
- func IsNewIP(ip string, history []LoginRecord) bool
- func Issue(secret, uid string, roles []string, ver int64, ttl time.Duration) (string, error)
- func IssuePurpose(secret, uid, purpose string, ver int64, ttl time.Duration) (string, error)
- func NewTrustedDeviceToken() (id, plain, hashed string, err error)
- func Roles(c *fiber.Ctx) []string
- func TokenVersionMiddleware(lookup func(uid string) (currentVersion int64, found bool, err error)) fiber.Handler
- func UID(c *fiber.Ctx) string
- func ValidateDeviceCookie(cookieValue string, devices []TrustedDevice) (deviceID string, ok bool)
- func ValidateTOTPCode(secret, code string) bool
- func Ver(c *fiber.Ctx) int64
- func VerifyPassword(hash, pw string) bool
- type Claims
- type CodeCheck
- type CodeState
- type LoginRecord
- type LoginThrottle
- type LoginThrottleConfig
- type Mailer
- type TOTPConfig
- type Throttle
- type TicketStore
- type TrustedDevice
Constants ¶
const ( CtxUID ctxKey = "uid" CtxRoles ctxKey = "roles" CtxVer ctxKey = "ver" )
const RoleAdmin = "admin"
RoleAdmin is the shared role-name convention across apps.
Variables ¶
This section is empty.
Functions ¶
func AuthMiddleware ¶
func AuthMiddleware(secret string, bootTime time.Time, onReject func(reason string, c *fiber.Ctx)) fiber.Handler
AuthMiddleware validates the bearer token and stashes its claims in request locals. Header-only: tokens never ride in a URL, keeping them out of access logs (use TicketStore for browser-navigation downloads).
onReject, nil-safe, receives a short reason string for security-warn logging on suspicious rejections (bad signature, alg confusion, malformed, purpose token). Expired tokens are normal, no callback.
Tokens issued before bootTime are rejected, forcing re-login after a restart/redeployment.
func CheckBackupCode ¶
CheckBackupCode tests input against the stored bcrypt-hashed codes. On a match the caller must remove hashed[matchIndex] from their slice and persist immediately, single-use, crash-safe against replay.
func ClientIP ¶
ClientIP returns the best available client IP: X-Forwarded-For when behind a trusted reverse proxy (see fiber.Config.ProxyHeader), falling back to the raw TCP remote address in local dev. Returns "unknown" when nothing is resolvable, the sentinel LoginThrottle skips.
func CookieDeviceID ¶
CookieDeviceID returns the device id embedded in a cookie value, or "" if absent/malformed.
func DeviceCookie ¶
DeviceCookie builds the trusted-device cookie for a freshly minted token. HTTPOnly + SameSite=Strict: the token never reaches JS and never rides a cross-site request.
func DummyVerify ¶
func DummyVerify(pw string)
DummyVerify spends a bcrypt comparison against a throwaway hash. Call it on the unknown-login path so the response latency matches the wrong-password path, denying an attacker a timing oracle for username/email enumeration. The result is intentionally discarded.
func ExpiredDeviceCookie ¶
ExpiredDeviceCookie tells the browser to drop the trusted-device cookie immediately. Attributes must match DeviceCookie's so the browser targets the same cookie.
func GenerateBackupCodes ¶
GenerateBackupCodes returns n one-time recovery codes (10 hex chars each) in plaintext, shown to the user exactly once, plus their bcrypt hashes for storage.
func GenerateCode ¶
GenerateCode returns a cryptographically random 6-digit string used for email verification and password-reset codes (leading zeros allowed: argue about entropy, not display width).
func GenerateTOTPSecret ¶
func GenerateTOTPSecret(cfg TOTPConfig, accountName string) (secret, uri string, err error)
GenerateTOTPSecret mints a fresh TOTP secret and its otpauth:// URI (QR-code source) for accountName. Nothing is persisted: the app stores the secret only after the user proves enrollment via ValidateTOTPCode.
func HashPassword ¶
func IsNewIP ¶
func IsNewIP(ip string, history []LoginRecord) bool
IsNewIP reports whether ip has never appeared in history. False on empty history: a first login isn't an alert.
func IssuePurpose ¶
IssuePurpose mints a single-purpose token (e.g. a password-reset link) bound to ver, the caller's current TokenVersion at issuance, so bumping TokenVersion after use invalidates any replay of this exact token without needing separate token storage. Never accepted by AuthMiddleware as a session token (see Purpose check there).
func NewTrustedDeviceToken ¶
NewTrustedDeviceToken mints a device id plus its secret token. plain goes in the cookie (via DeviceCookie), hashed in the stored record.
func TokenVersionMiddleware ¶
func TokenVersionMiddleware(lookup func(uid string) (currentVersion int64, found bool, err error)) fiber.Handler
TokenVersionMiddleware rejects tokens older than the user's current TokenVersion (bumped on logout-all/password reset/ban). lookup fetches the stored version for uid; found=false or an error rejects the request, treating a missing user as a revoked session.
func ValidateDeviceCookie ¶
func ValidateDeviceCookie(cookieValue string, devices []TrustedDevice) (deviceID string, ok bool)
ValidateDeviceCookie checks a cookie value against the stored devices: constant-time token compare plus expiry check.
func ValidateTOTPCode ¶
ValidateTOTPCode checks a 6-digit authenticator code against secret.
func VerifyPassword ¶
Types ¶
type Claims ¶
type Claims struct {
UID string `json:"uid"`
Roles []string `json:"roles"`
Ver int64 `json:"ver,omitempty"`
// Purpose marks single-purpose tokens (e.g. "pwreset") that must never
// be accepted as a normal session bearer token. Empty = regular session.
Purpose string `json:"purp,omitempty"`
jwt.RegisteredClaims
}
type CodeCheck ¶
type CodeCheck int
const ( // CodeOK: input matches. Caller clears the three fields and persists. CodeOK CodeCheck = iota // CodeExpired: no code pending or past expiry. Caller prompts a resend. CodeExpired // CodeLocked: attempt budget exhausted. Only issuing a fresh code // (which resets Attempts) unlocks. CodeLocked // CodeWrong: mismatch. Caller increments Attempts and persists. CodeWrong )
func CheckCode ¶
CheckCode runs the shared validation decision tree for 6-digit email codes. Pure: the caller mutates its record per the result (increment Attempts on CodeWrong, clear fields on CodeOK) and persists. remaining is only meaningful for CodeWrong: attempts left after this failure, floored at 0.
type CodeState ¶
CodeState mirrors the three fields apps persist per code flow on their user record (code, expiry, wrong-attempt counter). Plain data in, the caller owns storage.
type LoginRecord ¶
type LoginRecord struct {
IP string `json:"ip"`
UserAgent string `json:"userAgent,omitempty"`
At time.Time `json:"at"`
}
LoginRecord is one successful login, kept newest-first on the app's user record for the "recent activity" view and new-IP detection.
func AppendLoginRecord ¶
func AppendLoginRecord(history []LoginRecord, ip, ua string, max int) []LoginRecord
AppendLoginRecord prepends a record and caps the history at max entries. The user agent is truncated to 120 runes so a hostile UA header can't bloat the stored record.
type LoginThrottle ¶
type LoginThrottle struct {
// contains filtered or unexported fields
}
LoginThrottle wraps Throttle with two independent dimensions, each its own namespaced key:
- per-login ("login:<name>"): blunts a targeted brute-force against one account. Includes unknown logins, so hammering a guessed username is throttled too.
- per-IP ("ip:<addr>"): blunts a password-spray that walks many usernames from one source. Higher threshold since several real users may legitimately share an IP (NAT/office).
A login is locked if *either* dimension is tripped. Both wrong-password and wrong-TOTP attempts count as a failure.
func NewLoginThrottle ¶
func NewLoginThrottle(cfg LoginThrottleConfig) *LoginThrottle
func (*LoginThrottle) Fail ¶
func (l *LoginThrottle) Fail(login, ip string) int
Fail records a failure against both dimensions and returns the running per-login count (used to pace failed-login admin email alerts).
func (*LoginThrottle) Locked ¶
func (l *LoginThrottle) Locked(login, ip string) (bool, time.Duration)
Locked reports whether either the login or the source IP is currently locked out, and the longest retry-after of the two.
func (*LoginThrottle) Succeeded ¶
func (l *LoginThrottle) Succeeded(login string)
Succeeded clears the per-login counter on a successful auth. The per-IP counter is intentionally left to age out via TTL so one valid credential among a spray can't immediately reset the IP's budget.
type LoginThrottleConfig ¶
type Mailer ¶
type Mailer struct {
APIKey string
From string
HTTPClient *http.Client
Endpoint string // overridden in tests, defaults to Resend's API
}
Mailer sends transactional email via Resend's HTTP API (https://resend.com/docs/api-reference/emails/send-email). A plain HTTP call rather than the official SDK: one endpoint, one request shape, not worth a new dependency.
APIKey-gated: an empty APIKey makes Send a no-op returning nil, so email flows degrade gracefully in dev/CI instead of erroring. Apps check Enabled() to log the code instead of sending.
type TOTPConfig ¶
type TOTPConfig struct {
Issuer string // shown in authenticator apps, e.g. "Mist Drive"
}
type Throttle ¶
type Throttle struct {
// contains filtered or unexported fields
}
Throttle is an in-memory keyed lockout. Generic core: callers pick the key namespace ("login:<name>", "ip:<addr>", "uid:<id>", ...) and the per-key threshold. No database: the map lives for the process lifetime. Stale entries are swept lazily so the map stays bounded even under a spray of random keys.
func NewThrottle ¶
func (*Throttle) Fail ¶
Fail records a failed attempt for key and returns the running count. Once the count reaches maxFails the key is locked for lockFor.
type TicketStore ¶
type TicketStore struct {
// contains filtered or unexported fields
}
TicketStore mints short-lived, single-use tickets that authorize one browser-navigation action (e.g. a streaming zip download). They exist so a plain navigation, which can't set an Authorization header, never carries the reusable session JWT in a URL where it would land in proxy/access logs.
A ticket is an opaque random string bound to {uid, scope}. It is consumed (deleted) on first use and expires after ttl. In-memory, no DB.
func NewTicketStore ¶
func NewTicketStore(ttl time.Duration) *TicketStore
type TrustedDevice ¶
type TrustedDevice struct {
ID string `json:"id"`
HashedToken string `json:"hashedToken"`
Label string `json:"label"`
CreatedAt time.Time `json:"createdAt"`
ExpiresAt time.Time `json:"expiresAt"`
}
TrustedDevice is a "remember this device" record letting a browser skip the TOTP step. The cookie value is "id:plainToken"; only the SHA-256 of the token is stored.
func PruneExpiredDevices ¶
func PruneExpiredDevices(devices []TrustedDevice) []TrustedDevice
PruneExpiredDevices drops expired entries in place.