Documentation
¶
Overview ¶
Package devise is a pure-Go (no cgo) reimplementation of the module logic of Ruby's Devise authentication framework, faithful to MRI Devise on Ruby 4.0.5.
Scope ¶
This is the v0.1 module-core foundation. It ports the behavioural heart of Devise's authentication modules — the credential, token, lock, remember, confirm and tracking state machines — as plain Go operating on an injectable model, together with the Warden strategy that drives database authentication. It deliberately has no dependency on a Ruby runtime and is a sibling of the other go-ruby-* stdlib/gem ports.
Persistence (ActiveRecord), controllers, routes, views, mailers and the Rails engine are NOT reimplemented here — they are seams (see Model, Finder and the notification callbacks on Config) or roadmap items. A later go-embedded-ruby binding wires those seams to the host.
Model seam ¶
Every module operates on a Record — a Model paired with a Config. The Model interface is the whole persistence surface Devise needs: read an attribute, write an attribute, and save. The host (ActiveRecord in Rails, go-embedded-ruby's object model under rbgo) supplies it. Class-level lookups (uniqueness, find-by-token, cookie deserialisation) go through the Finder seam on Config.
Index ¶
- Constants
- Variables
- func DecodeCookie(s string) []string
- func EncodeCookie(parts []string) string
- func FriendlyToken(length int) string
- func SecureCompare(a, b string) bool
- func SerializeIntoCookie(id, rememberableValue, timestamp string) []string
- type CachingKeyGenerator
- type Config
- func (c *Config) ConfirmByToken(rawToken string) (*Record, error)
- func (c *Config) DatabaseStrategyRun() warden.StrategyRun
- func (c *Config) ResetPasswordByToken(rawToken, newPassword, newPasswordConfirmation string) (*Record, error)
- func (c *Config) SerializeFromCookie(id, rememberableValue string) (*Record, error)
- func (c *Config) UnlockAccessByToken(rawToken string) (*Record, error)
- type Credentials
- type DatabaseAuthenticatableStrategy
- type Destroyer
- type Encryptor
- type Finder
- type HMACKeyGenerator
- type KeyGenerator
- type LockStrategy
- type Model
- type PBKDF2KeyGenerator
- type Record
- func (r *Record) AccessLocked() bool
- func (r *Record) ActiveForAuthentication() bool
- func (r *Record) AuthenticatableSalt() string
- func (r *Record) Config() *Config
- func (r *Record) Confirm() error
- func (r *Record) ConfirmationPeriodExpired() bool
- func (r *Record) ConfirmationPeriodValid() bool
- func (r *Record) Confirmed() bool
- func (r *Record) DestroyWithPassword(currentPassword string) ([]ValidationError, error)
- func (r *Record) FailedAttempts() int
- func (r *Record) ForgetMe() error
- func (r *Record) GenerateConfirmationToken() string
- func (r *Record) InactiveMessage() string
- func (r *Record) LockAccess(sendInstructions bool) error
- func (r *Record) Model() Model
- func (r *Record) RememberExpired() bool
- func (r *Record) RememberMe() error
- func (r *Record) RememberableValue() string
- func (r *Record) ResetPassword(newPassword string) error
- func (r *Record) ResetPasswordPeriodValid() bool
- func (r *Record) SendConfirmationInstructions() (string, error)
- func (r *Record) SendResetPasswordInstructions() (string, error)
- func (r *Record) SetPassword(newPassword string) error
- func (r *Record) SetResetPasswordToken() (string, error)
- func (r *Record) SignInCount() int
- func (r *Record) TimedOut(lastAccess time.Time) bool
- func (r *Record) TimeoutIn() (time.Duration, bool)
- func (r *Record) UnlockAccess() error
- func (r *Record) UpdateTrackedFields(remoteIP string)
- func (r *Record) UpdateTrackedFieldsAndSave(remoteIP string) error
- func (r *Record) UpdateWithPassword(p UpdateParams) ([]ValidationError, error)
- func (r *Record) UpdateWithoutPassword(p UpdateParams) ([]ValidationError, error)
- func (r *Record) ValidForAuthentication(check func() bool) bool
- func (r *Record) ValidPassword(password string) bool
- func (r *Record) ValidatableErrors(p ValidateParams) []ValidationError
- type TokenGenerator
- type UnlockStrategy
- type UpdateParams
- type ValidateParams
- type ValidationError
Constants ¶
const ( AttrEmail = "email" AttrEncryptedPassword = "encrypted_password" AttrResetPasswordToken = "reset_password_token" AttrResetPasswordSentAt = "reset_password_sent_at" AttrRememberToken = "remember_token" AttrRememberCreatedAt = "remember_created_at" AttrConfirmationToken = "confirmation_token" AttrConfirmedAt = "confirmed_at" AttrConfirmationSentAt = "confirmation_sent_at" AttrUnconfirmedEmail = "unconfirmed_email" AttrFailedAttempts = "failed_attempts" AttrUnlockToken = "unlock_token" AttrLockedAt = "locked_at" AttrSignInCount = "sign_in_count" AttrCurrentSignInAt = "current_sign_in_at" AttrLastSignInAt = "last_sign_in_at" AttrCurrentSignInIP = "current_sign_in_ip" AttrLastSignInIP = "last_sign_in_ip" )
Devise attribute names — the database columns Devise's modules read and write. They match the column names Devise's generators create so a binding can map them straight onto ActiveRecord attributes.
const KeyGeneratorIterations = 1 << 16
KeyGeneratorIterations is the PBKDF2 iteration count ActiveSupport::KeyGenerator uses by default (2**16), and thus what Devise's token generator uses.
const KeyGeneratorKeySize = 64
KeyGeneratorKeySize is the default derived-key length in bytes (ActiveSupport::KeyGenerator#generate_key's key_size default of 64, chosen to match OpenSSL::Digest::SHA1#block_length).
const StrategyDatabaseAuthenticatable = "database_authenticatable"
StrategyDatabaseAuthenticatable is the Warden label of the database strategy, matching Devise's :database_authenticatable.
Variables ¶
var ( // ErrAlreadyConfirmed is returned by Confirm when the record is already // confirmed (Devise adds :already_confirmed on email). ErrAlreadyConfirmed = errors.New("devise: already confirmed") // ErrConfirmationPeriodExpired is returned by Confirm when the token is past // confirm_within (Devise adds :confirmation_period_expired on email). ErrConfirmationPeriodExpired = errors.New("devise: confirmation period expired") // ErrConfirmationTokenNotFound is returned by ConfirmByToken when no record // matches the token. ErrConfirmationTokenNotFound = errors.New("devise: confirmation token not found") )
Confirmable error reasons.
var ( // ErrResetTokenNotFound is returned when no record matches the reset token // (Devise adds :not_found on the token, or :invalid). ErrResetTokenNotFound = errors.New("devise: reset password token not found") // ErrResetTokenExpired is returned when the token is past // reset_password_within (Devise adds :expired). ErrResetTokenExpired = errors.New("devise: reset password token expired") )
Recoverable error reasons, mirroring the symbolic errors Devise adds to reset_password_token / password.
var DefaultEmailRegexp = regexp.MustCompile(`\A[^@\s]+@[^@\s]+\z`)
DefaultEmailRegexp is Devise.email_regexp: a non-empty local part, an @, and a non-empty domain part, neither containing whitespace or a further @.
var ErrNotDestroyable = errors.New("devise: model is not a Destroyer")
ErrNotDestroyable is returned by Record.DestroyWithPassword when the underlying Model does not implement Destroyer, i.e. cannot delete itself.
var ErrRememberCookieInvalid = errors.New("devise: invalid remember cookie")
ErrRememberCookieInvalid is returned by Config.SerializeFromCookie when the cookie is malformed or does not authenticate a record.
var ErrUnlockTokenNotFound = errors.New("devise: unlock token not found")
ErrUnlockTokenNotFound is returned by Config.UnlockAccessByToken when no record matches the unlock token.
Functions ¶
func DecodeCookie ¶
DecodeCookie splits an EncodeCookie payload back into its parts.
func EncodeCookie ¶
EncodeCookie joins a cookie payload with "/" the way Devise's cookie serializer does before signing. Provided as a convenience for a binding that stores the payload as a single string.
func FriendlyToken ¶
FriendlyToken returns a URL-safe random token, faithful to Devise.friendly_token: it draws (length*3)/4 random bytes, URL-safe-base64 encodes them without padding, then maps the ambiguous characters l, I, O and 0 to s, x, y and z. With the default length of 20 it yields a 20-character token. A non-positive length yields the empty string (no entropy requested).
func SecureCompare ¶
SecureCompare compares two strings in constant time, faithful to Devise.secure_compare (ActiveSupport::SecurityUtils.secure_compare): it returns false immediately when the byte lengths differ, and otherwise performs a fixed-length, timing-safe comparison. Two empty strings are equal.
func SerializeIntoCookie ¶
SerializeIntoCookie returns the cookie payload for a record, faithful to serialize_into_cookie: [id, rememberable_value, timestamp]. The id and timestamp are supplied by the caller (the model's to_key and Time.now.to_f in Devise), keeping this free of a persistence-identity assumption.
Types ¶
type CachingKeyGenerator ¶
type CachingKeyGenerator struct {
// contains filtered or unexported fields
}
CachingKeyGenerator memoises another KeyGenerator by salt, the port of ActiveSupport::CachingKeyGenerator. PBKDF2 at 2**16 iterations is deliberately slow, and Devise re-derives the same per-column key on every token operation, so Devise wraps its KeyGenerator in a caching one; NewDeviseTokenGenerator does the same. It is safe for the sequential use a request makes; wrap access externally if you share one across goroutines.
func NewCachingKeyGenerator ¶
func NewCachingKeyGenerator(inner KeyGenerator) *CachingKeyGenerator
NewCachingKeyGenerator wraps inner so repeated GenerateKey(salt) calls with the same salt reuse the first derivation.
func (*CachingKeyGenerator) GenerateKey ¶
func (g *CachingKeyGenerator) GenerateKey(salt string) []byte
GenerateKey returns the cached key for salt, deriving and caching it on first use, faithful to ActiveSupport::CachingKeyGenerator#generate_key.
type Config ¶
type Config struct {
// Stretches is the bcrypt cost (Devise.stretches). Devise defaults to 12,
// and to 1 in the test environment.
Stretches int
// Pepper is appended to passwords before hashing (Devise.pepper). Empty
// means no pepper.
Pepper string
// EmailRegexp validates email format (Devise.email_regexp).
EmailRegexp *regexp.Regexp
// PasswordLengthMin / PasswordLengthMax bound password length
// (Devise.password_length, default 6..128).
PasswordLengthMin int
PasswordLengthMax int
// ResetPasswordWithin is how long a reset token stays valid
// (Devise.reset_password_within, default 6 hours).
ResetPasswordWithin time.Duration
// RememberFor is how long a remember-me cookie lasts (Devise.remember_for,
// default 2 weeks).
RememberFor time.Duration
// ExpireAllRememberMeOnSignOut clears remember_created_at on sign-out
// (Devise.expire_all_remember_me_on_sign_out, default true).
ExpireAllRememberMeOnSignOut bool
// AllowUnconfirmedAccessFor is the grace window during which an unconfirmed
// account may still sign in (Devise.allow_unconfirmed_access_for). A nil
// pointer means "unlimited" (never expires); a zero duration means no grace.
AllowUnconfirmedAccessFor *time.Duration
// ConfirmWithin is how long a confirmation token stays valid
// (Devise.confirm_within). A nil pointer means tokens never expire.
ConfirmWithin *time.Duration
// MaximumAttempts is the failed-attempt threshold that triggers a lock
// (Devise.maximum_attempts, default 20).
MaximumAttempts int
// UnlockIn is the auto-unlock window for the time strategy
// (Devise.unlock_in, default 1 hour).
UnlockIn time.Duration
// UnlockStrategy and LockStrategy select the lock/unlock behaviour.
UnlockStrategy UnlockStrategy
LockStrategy LockStrategy
// TimeoutIn is the idle window after which a session times out
// (Devise.timeout_in). A nil pointer disables timeout.
TimeoutIn *time.Duration
// AuthenticationKeys are the columns used to find a record for
// authentication (Devise.authentication_keys, default ["email"]).
AuthenticationKeys []string
// FriendlyTokenLength is the default length of a [FriendlyToken]
// (Devise's friendly_token default is 20).
FriendlyTokenLength int
// Finder performs class-level record lookups. Required by uniqueness
// validation, reset-by-token, remember-cookie deserialisation and the
// database strategy; a nil Finder makes those treat "no match" as the
// result.
Finder Finder
// TokenGenerator produces and digests the raw tokens Recoverable and
// Confirmable store. Defaults to a [TokenGenerator] with a zero key; wire a
// real [KeyGenerator] in production.
TokenGenerator *TokenGenerator
// Now is the clock (defaults to time.Now().UTC()). Devise stores all
// timestamps in UTC.
Now func() time.Time
// Credentials extracts the authentication hash and password from a Rack env
// for the database strategy. Defaults to reading env["devise.credentials"]
// and env["devise.password"]; a binding overrides it to parse Rack params.
Credentials Credentials
// SendResetPasswordInstructions is invoked with the raw reset token after it
// is set, standing in for the Recoverable mailer. Optional.
SendResetPasswordInstructions func(r *Record, rawToken string)
// SendConfirmationInstructions is invoked with the raw confirmation token,
// standing in for the Confirmable mailer. Optional.
SendConfirmationInstructions func(r *Record, rawToken string)
// SendUnlockInstructions is invoked with the raw unlock token, standing in
// for the Lockable mailer. Optional.
SendUnlockInstructions func(r *Record, rawToken string)
}
Config carries the per-resource Devise settings (the knobs Devise exposes via the initializer and per-model devise :... declarations) together with the injectable seams a pure-Go core needs: the record lookup Finder, the TokenGenerator, and a clock. Build one with DefaultConfig and override fields, mirroring how a Rails app tweaks config.to_prepare / model options.
func DefaultConfig ¶
func DefaultConfig() *Config
DefaultConfig returns a Config populated with Devise's out-of-the-box defaults. Callers override individual fields (Stretches, Pepper, Finder, TokenGenerator, ...) as a Rails app would in its initializer.
func (*Config) ConfirmByToken ¶
ConfirmByToken is the class-level confirmation flow, faithful to confirm_by_token: it digests the raw token, finds the record, and confirms it.
func (*Config) DatabaseStrategyRun ¶
func (c *Config) DatabaseStrategyRun() warden.StrategyRun
DatabaseStrategyRun returns a warden.StrategyRun that dispatches the database_authenticatable label to this config's strategy, reading credentials via Config.Credentials. Plug it into a Manager with warden.WithStrategyRun(cfg.DatabaseStrategyRun()).
func (*Config) ResetPasswordByToken ¶
func (c *Config) ResetPasswordByToken(rawToken, newPassword, newPasswordConfirmation string) (*Record, error)
ResetPasswordByToken is the class-level reset flow, faithful to reset_password_by_token: it digests the raw token, finds the record, checks the token has not expired, and resets the password. It returns the updated record, or an error (ErrResetTokenNotFound / ErrResetTokenExpired) plus any validation failures from the password rules.
func (*Config) SerializeFromCookie ¶
SerializeFromCookie re-identifies a record from a cookie payload, faithful to serialize_from_cookie: it finds the record by id, then confirms it is not expired and its rememberable value matches the cookie's, via SecureCompare. A mismatch, an expired cookie, or a missing record yields ErrRememberCookieInvalid.
type Credentials ¶
Credentials is the seam that extracts the authentication hash and password from a Rack env, standing in for Warden's params parsing. The default reads env["devise.credentials"] (a map[string]any of authentication keys) and env["devise.password"] (a string); a binding overrides Config.Credentials to parse real Rack params.
type DatabaseAuthenticatableStrategy ¶
type DatabaseAuthenticatableStrategy struct {
Cfg *Config
}
DatabaseAuthenticatableStrategy is Devise's password strategy: it looks a record up by the authentication keys and verifies its password, gated by Lockable's valid_for_authentication? and Confirmable's active_for_authentication?.
func (DatabaseAuthenticatableStrategy) Run ¶
func (s DatabaseAuthenticatableStrategy) Run(authHash map[string]any, password string) warden.StrategyResult
Run executes the strategy, returning a warden.StrategyResult faithful to Devise's authenticate!: an invalid strategy is skipped (Valid=false); an unknown record fails with "not_found_in_database"; a record whose password fails or whose account is locked fails with "invalid"; an inactive (unconfirmed) record fails with its inactive message; otherwise the record is returned as the authenticated user.
func (DatabaseAuthenticatableStrategy) Valid ¶
func (s DatabaseAuthenticatableStrategy) Valid(authHash map[string]any, password string) bool
Valid reports the strategy's valid? predicate (valid_for_params_auth?): a password must be present and every configured authentication key must be present and non-empty in authHash.
type Destroyer ¶
type Destroyer interface {
Destroy() error
}
Destroyer is the optional Model capability a record needs to delete itself, mirroring ActiveRecord#destroy. Record.DestroyWithPassword requires it; a binding wires it to the ORM's delete, tests to an in-memory tombstone.
type Encryptor ¶
type Encryptor struct{}
Encryptor is Devise::Encryptor: the bcrypt-backed password hasher. Digest hashes a password at a cost, applying the pepper; Compare checks a candidate password against a stored hash in constant time. Both mirror the gem exactly, delegating the crypto to go-ruby-bcrypt.
func (Encryptor) Compare ¶
Compare reports whether password matches hashedPassword, faithful to Devise::Encryptor.compare: a blank stored hash is never a match; otherwise the candidate (with pepper applied) is hashed against the stored salt and the result is compared to the stored hash with SecureCompare. An unparseable stored hash is treated as no match.
func (Encryptor) Digest ¶
Digest hashes password with the pepper applied, at the given bcrypt cost, faithful to Devise::Encryptor.digest: when pepper is non-empty it is appended to the password before hashing, then BCrypt::Password.create(..., cost:) runs. The returned string is the "$2a$NN$...."-form hash to store in encrypted_password.
type Finder ¶
Finder is the class-level lookup seam, standing in for ActiveRecord's to_adapter.find_first / find_for_database_authentication. It returns the first record matching every attribute in attrs, and false when none matches. The binding wires it to a real query; tests wire it to an in-memory table.
type HMACKeyGenerator ¶
type HMACKeyGenerator struct {
Secret []byte
}
HMACKeyGenerator derives keys as HMAC-SHA256(Secret, salt). It is the default KeyGenerator; a zero-value (empty Secret) is valid and deterministic, which is why the token generator works out of the box in tests.
func (HMACKeyGenerator) GenerateKey ¶
func (g HMACKeyGenerator) GenerateKey(salt string) []byte
GenerateKey returns HMAC-SHA256(Secret, salt).
type KeyGenerator ¶
KeyGenerator derives a per-column signing key from a secret, mirroring the ActiveSupport::KeyGenerator that Devise::TokenGenerator is built on. Devise calls key_generator.generate_key("Devise <column>") to obtain the HMAC key for each tokenised column, so that the same raw token digests differently per column.
The default HMACKeyGenerator derives the key with HMAC-SHA256; a production binding may instead plug Rails' PBKDF2-based ActiveSupport::KeyGenerator here. Either way the resulting digests are HMAC-SHA256 hexdigests, exactly Devise's token shape.
type LockStrategy ¶
type LockStrategy string
LockStrategy selects what triggers a lock, mirroring Devise.lock_strategy.
const ( // LockFailedAttempts locks after too many failed sign-ins (Devise's default). LockFailedAttempts LockStrategy = "failed_attempts" // LockNone disables automatic locking (locks only via lock_access!). LockNone LockStrategy = "none" )
type Model ¶
Model is the persistence seam Devise operates on: a single authenticatable resource (a User row in Rails). It is the minimal surface every Devise module needs — read an attribute, write an attribute, and persist. The host wires it to ActiveRecord (in Rails) or to go-embedded-ruby's object model (under rbgo).
Get returns the current value of a database column by its Devise attribute name (see the Attr* constants); a missing / SQL-NULL value is nil. Set stages a new value. Save persists the staged values, mirroring ActiveRecord's save(validate: false) that Devise uses throughout (validation is a separate concern handled by Record.ValidatableErrors).
type PBKDF2KeyGenerator ¶
PBKDF2KeyGenerator is the byte-faithful port of Rails' ActiveSupport::KeyGenerator: it derives per-salt keys with PBKDF2-HMAC-SHA1 over a shared Secret (Devise.secret_key / the app's secret_key_base). This is the key generator Devise actually wires into Devise::TokenGenerator, so a generator built with the same Secret digests raw tokens to the exact bytes MRI Devise stores — the reset/confirm/unlock digest oracle.
The zero value is not usable (an empty Secret would derive from nothing); build one with NewPBKDF2KeyGenerator. Iterations, KeySize and the HMAC hash are exposed for the rare app that overrides ActiveSupport's defaults, but the defaults (2**16 iterations, 64-byte keys, SHA1) match a stock Rails app.
func NewPBKDF2KeyGenerator ¶
func NewPBKDF2KeyGenerator(secret []byte) PBKDF2KeyGenerator
NewPBKDF2KeyGenerator builds a PBKDF2KeyGenerator over secret with ActiveSupport's defaults (2**16 iterations, 64-byte keys, HMAC-SHA1), matching ActiveSupport::KeyGenerator.new(secret).
func (PBKDF2KeyGenerator) GenerateKey ¶
func (g PBKDF2KeyGenerator) GenerateKey(salt string) []byte
GenerateKey returns PBKDF2-HMAC(Secret, salt, Iterations, KeySize), faithful to ActiveSupport::KeyGenerator#generate_key(salt). Unset Iterations/KeySize/Hash fall back to ActiveSupport's defaults so a bare PBKDF2KeyGenerator{Secret: s} still behaves like the stock generator.
type Record ¶
type Record struct {
// contains filtered or unexported fields
}
Record is a Devise resource: a Model bound to the Config that governs it, the equivalent of an ActiveRecord instance whose self.class carries the Devise settings. All module logic hangs off methods on *Record.
func New ¶
New binds a model to a config, yielding the resource the module methods act on. A nil cfg uses DefaultConfig.
func NewWithSession ¶
NewWithSession initialises a resource from sign-up params, faithful to Registerable.new_with_session: by default it discards the session and assigns the params onto the model, returning the bound Record. An OAuth binding overrides the discard to seed attributes from the session.
func (*Record) AccessLocked ¶
AccessLocked reports whether the account is currently locked, faithful to access_locked?: locked_at is set and the lock has not expired.
func (*Record) ActiveForAuthentication ¶
ActiveForAuthentication reports whether an unconfirmed record may still sign in, faithful to Confirmable#active_for_authentication?: confirmed records are always active; unconfirmed ones are active only while [ConfirmationPeriodValid].
func (*Record) AuthenticatableSalt ¶
AuthenticatableSalt returns the salt portion of the stored hash — its first 29 characters — faithful to authenticatable_salt. It is "" when no password is set, and feeds Rememberable's cookie value and session invalidation.
func (*Record) Confirm ¶
Confirm marks the record confirmed, faithful to confirm: it fails with ErrAlreadyConfirmed when already confirmed and ErrConfirmationPeriodExpired when the token has expired, otherwise sets confirmed_at, clears confirmation_token, and saves.
func (*Record) ConfirmationPeriodExpired ¶
ConfirmationPeriodExpired reports whether the confirmation token has expired, faithful to confirmation_period_expired?: only when confirm_within is set and confirmation_sent_at is older than it.
func (*Record) ConfirmationPeriodValid ¶
ConfirmationPeriodValid reports whether the record may still access resources while unconfirmed, faithful to confirmation_period_valid?: true when allow_unconfirmed_access_for is nil (unlimited), else confirmation_sent_at is within that window.
func (*Record) Confirmed ¶
Confirmed reports whether the record has been confirmed, faithful to confirmed?: confirmed_at is set.
func (*Record) DestroyWithPassword ¶
func (r *Record) DestroyWithPassword(currentPassword string) ([]ValidationError, error)
DestroyWithPassword deletes the record only when currentPassword matches, faithful to destroy_with_password. A blank or wrong current password yields a current_password validation error ("blank" / "invalid") and no deletion. On a match it calls the model's Destroyer; a model that is not a Destroyer yields ErrNotDestroyable.
func (*Record) FailedAttempts ¶
FailedAttempts returns the current failed-attempt count (failed_attempts).
func (*Record) ForgetMe ¶
ForgetMe clears the remember state, faithful to forget_me!: it clears remember_token, clears remember_created_at when expire_all_remember_me_on_sign_out is set, and saves.
func (*Record) GenerateConfirmationToken ¶
GenerateConfirmationToken mints a confirmation token, faithful to generate_confirmation_token: it generates a (raw, enc) pair (unique via the finder), stores the enc digest and confirmation_sent_at, and returns the raw token. It does not save (Devise stages it before the record is persisted).
func (*Record) InactiveMessage ¶
InactiveMessage returns the reason an authenticated-but-inactive record is rejected, faithful to inactive_message: "unconfirmed" for a record outside its confirmation grace window, else "inactive".
func (*Record) LockAccess ¶
LockAccess locks the account, faithful to lock_access!: it stamps locked_at, mints an unlock token when the email unlock strategy is enabled, saves, and (when sendInstructions is true and the strategy allows email) invokes the unlock mailer callback.
func (*Record) RememberExpired ¶
RememberExpired reports whether the remember cookie has expired, faithful to remember_expired?: true when remember_created_at is nil or older than remember_for.
func (*Record) RememberMe ¶
RememberMe issues a remember token, faithful to remember_me!: it sets remember_token (unique via the finder) if unset, sets remember_created_at if unset, and saves. Devise only saves when the record changed; here it always saves after staging, which is observationally equivalent for a fresh token.
func (*Record) RememberableValue ¶
RememberableValue is the value stored in the cookie to re-identify the record, faithful to rememberable_value: the remember_token when present, else the authenticatable salt. It is "" only when neither is available.
func (*Record) ResetPassword ¶
ResetPassword sets a new password and clears the reset token, faithful to reset_password: it stages the hashed password, clears reset_password_token, and saves. It does not itself check the confirmation or period — the class method Config.ResetPasswordByToken orchestrates those, mirroring Devise.
func (*Record) ResetPasswordPeriodValid ¶
ResetPasswordPeriodValid reports whether the stored reset token is still within reset_password_within, faithful to reset_password_period_valid?: false when no token was ever sent.
func (*Record) SendConfirmationInstructions ¶
SendConfirmationInstructions mints a token, saves it, and invokes the mailer callback, faithful to send_confirmation_instructions. It returns the raw token.
func (*Record) SendResetPasswordInstructions ¶
SendResetPasswordInstructions mints a reset token and invokes the configured mailer callback, faithful to send_reset_password_instructions. It returns the raw token (Devise returns it too, for tests).
func (*Record) SetPassword ¶
SetPassword hashes new_password and stages it into encrypted_password, faithful to password=: a blank password is ignored (leaves the current hash untouched, as Devise does with "self.encrypted_password = ... if @password.present?"). It does not save; the caller persists, mirroring ActiveRecord.
func (*Record) SetResetPasswordToken ¶
SetResetPasswordToken mints a reset token, faithful to set_reset_password_token: it generates a (raw, enc) pair via the token generator (unique against reset_password_token through the finder), stores the enc digest and the send timestamp, saves without validation, and returns the raw token to be mailed.
func (*Record) SignInCount ¶
SignInCount returns the number of recorded sign-ins (sign_in_count).
func (*Record) TimedOut ¶
TimedOut reports whether a session whose last activity was at lastAccess has timed out, faithful to timedout?: never when timeout_in is nil, otherwise when lastAccess is at or before timeout_in ago. Devise also returns false while a valid remember cookie exists; that short-circuit is the caller's to apply via Record.RememberExpired, since it depends on request state.
func (*Record) TimeoutIn ¶
TimeoutIn returns the configured idle window, or false when timeout is disabled (timeout_in nil), faithful to Timeoutable#timeout_in.
func (*Record) UnlockAccess ¶
UnlockAccess unlocks the account, faithful to unlock_access!: it clears locked_at, resets failed_attempts to 0, clears unlock_token, and saves.
func (*Record) UpdateTrackedFields ¶
UpdateTrackedFields records a sign-in, faithful to update_tracked_fields: it shifts current_sign_in_at into last_sign_in_at (seeding last from current on the first sign-in), sets current_sign_in_at to now, does the same for the IP pair with remoteIP, and increments sign_in_count. It stages the changes without saving, exactly like Devise (the caller saves via Record.UpdateTrackedFieldsAndSave or its own save).
func (*Record) UpdateTrackedFieldsAndSave ¶
UpdateTrackedFieldsAndSave records a sign-in and persists it, faithful to update_tracked_fields! (save(validate: false)).
func (*Record) UpdateWithPassword ¶
func (r *Record) UpdateWithPassword(p UpdateParams) ([]ValidationError, error)
UpdateWithPassword edits the record only when currentPassword matches, faithful to update_with_password. It first drops a blank password (and its blank confirmation) so a user may change their email without touching their password. When the current password verifies it assigns the attributes, stages any new password, validates and saves; otherwise it still assigns and validates the attributes (to surface their errors) and adds a current_password error ("blank" when currentPassword is empty, else "invalid"). It returns the validation failures (empty on success) and a separate error for a save/hash infrastructure failure.
func (*Record) UpdateWithoutPassword ¶
func (r *Record) UpdateWithoutPassword(p UpdateParams) ([]ValidationError, error)
UpdateWithoutPassword edits the record without asking for the current password, faithful to update_without_password: it never changes the password (both password fields are dropped), then assigns the remaining attributes, validates and saves. Returns the validation failures (empty on success) and a separate error for a save failure.
func (*Record) ValidForAuthentication ¶
ValidForAuthentication runs the password check through Lockable's gate, faithful to Lockable#valid_for_authentication?. When the failed-attempts strategy is off it is just check(). Otherwise: a successful check on an unlocked account passes; any other outcome increments failed_attempts, locks the account when the threshold is reached (else saves the new count), and fails. It mirrors Devise's return of the check result, gated by the lock.
func (*Record) ValidPassword ¶
ValidPassword reports whether password matches the record's stored encrypted_password, faithful to valid_password?: it delegates to Encryptor.Compare with the config's pepper. A record without an encrypted_password never validates.
func (*Record) ValidatableErrors ¶
func (r *Record) ValidatableErrors(p ValidateParams) []ValidationError
ValidatableErrors runs Devise's Validatable checks against the record and the transient params, returning every failure in declaration order (empty when valid). Uniqueness is resolved through the Config.Finder seam: an email is "taken" when the finder returns a record whose id differs from this one — but because the model seam has no identity concept, any finder match on a different Model pointer counts as taken.
type TokenGenerator ¶
type TokenGenerator struct {
// FriendlyLength is the length passed to [FriendlyToken] for the raw token
// (Devise uses the default 20).
FriendlyLength int
// contains filtered or unexported fields
}
TokenGenerator mints and digests the raw tokens Recoverable and Confirmable persist, faithful to Devise::TokenGenerator. Generate returns a [raw, enc] pair — the raw token is mailed to the user, the enc digest is stored in the database — and Digest reproduces enc from a raw token to look the record up.
func NewDeviseTokenGenerator ¶
func NewDeviseTokenGenerator(secret []byte) *TokenGenerator
NewDeviseTokenGenerator builds the byte-faithful equivalent of Devise.token_generator for a given secret: a TokenGenerator over a CachingKeyGenerator wrapping a PBKDF2KeyGenerator, with HMAC-SHA256 digests. Pass the app's Devise.secret_key (its secret_key_base). The resulting digests are byte-identical to MRI Devise's, so a reset/confirm/unlock digest stored by this library verifies a raw token issued by the gem and vice-versa.
func NewTokenGenerator ¶
func NewTokenGenerator(keyGen KeyGenerator) *TokenGenerator
NewTokenGenerator builds a token generator over keyGen (defaulting to a zero-key HMACKeyGenerator) using HMAC-SHA256 digests, as Devise does.
func (*TokenGenerator) Digest ¶
func (t *TokenGenerator) Digest(column, value string) string
Digest reproduces the stored digest for a raw token value, faithful to Devise::TokenGenerator#digest: an empty value yields "" (Devise returns nil), otherwise the HMAC-SHA256 hexdigest of value under the column key.
func (*TokenGenerator) Generate ¶
func (t *TokenGenerator) Generate(column string, exists func(enc string) bool) (raw, enc string)
Generate returns a fresh (raw, enc) token pair for a column, faithful to Devise::TokenGenerator#generate: it draws a FriendlyToken and digests it, retrying while exists reports the digest is already taken (the uniqueness loop against the column). A nil exists never collides.
type UnlockStrategy ¶
type UnlockStrategy string
UnlockStrategy selects how a locked account may be unlocked, mirroring Devise.unlock_strategy.
const ( // UnlockTime unlocks automatically after Config.UnlockIn elapses. UnlockTime UnlockStrategy = "time" // UnlockEmail unlocks via an emailed unlock token. UnlockEmail UnlockStrategy = "email" // UnlockBoth enables both time and email unlocking (Devise's default). UnlockBoth UnlockStrategy = "both" // UnlockNone disables automatic unlocking. UnlockNone UnlockStrategy = "none" )
type UpdateParams ¶
type UpdateParams struct {
Attributes map[string]any
Password string
PasswordConfirmation string
CurrentPassword string
}
UpdateParams carries a registration edit, mirroring the params hash Devise's update_with_password / update_without_password receive. Attributes are the non-password column changes to assign; Password / PasswordConfirmation are the virtual password fields; CurrentPassword is the user's existing password, verified by Record.UpdateWithPassword.
type ValidateParams ¶
type ValidateParams struct {
// Password / PasswordConfirmation are the virtual password attributes.
Password string
PasswordConfirmation string
// PasswordProvided reports whether a password was set on this save
// (Devise's password_required? core: !persisted? || password present).
PasswordProvided bool
// EmailChanged reports whether email is being created or modified, gating
// the format and uniqueness checks (will_save_change_to_email?).
EmailChanged bool
}
ValidateParams carries the transient attributes Validatable needs that are not database columns: the plaintext password, its confirmation, and whether either the email or the password is being changed on this save. Devise reads these from the model (password / password_confirmation virtual attributes and the will_save_change_to_* dirty-tracking predicates); the host supplies them here.
type ValidationError ¶
ValidationError is a single validation failure, mirroring an entry Devise adds to the record's errors: the offending attribute and a symbolic reason matching ActiveModel's error keys (:blank, :invalid, :taken, :too_short, :too_long, :confirmation).
func (ValidationError) Error ¶
func (e ValidationError) Error() string
Error makes ValidationError usable as an error, so ResetPasswordByToken can surface a confirmation mismatch through the error return.
