emailpassword

package
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: Aug 7, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package emailpassword defines event constants and typed event payloads published by the EmailPassword plugin.

Package emailpassword provides functional options and configuration settings for the EmailPassword plugin.

Index

Constants

View Source
const (
	// PluginID is the unique string identifier for the EmailPassword plugin ("email-password").
	PluginID = "email-password"

	// CredentialProvider is the default provider key used for password-based accounts ("credential").
	CredentialProvider = "credential"
)
View Source
const (
	// EventSignUpBefore is emitted right before persisting a new user record.
	// Event listeners can inspect and mutate parameters (e.g. payload.Params.Set("role", "admin")).
	// Payload: *SignUpEventPayload
	EventSignUpBefore = "emailpassword:signup:before"

	// EventSignUpAfter is emitted immediately after successfully registering a user and creating their credential account.
	// Useful for triggering asynchronous welcome emails or provisioning external services.
	// Payload: *SignUpEventPayload
	EventSignUpAfter = "emailpassword:signup:after"

	// EventSignInBefore is emitted before validating credentials during sign-in.
	// Payload: *SignInEventPayload
	EventSignInBefore = "emailpassword:signin:before"

	// EventSignInAfter is emitted after successfully verifying user credentials and establishing a session.
	// Useful for security audit logs, geo-IP notifications, or analytics tracking.
	// Payload: *SignInEventPayload
	EventSignInAfter = "emailpassword:signin:after"

	// EventPasswordChangeBefore is emitted before updating a user's password.
	// Payload: *PasswordChangeEventPayload
	EventPasswordChangeBefore = "emailpassword:password_change:before"

	// EventPasswordChangeAfter is emitted after successfully updating a user's password in storage.
	// Payload: *PasswordChangeEventPayload
	EventPasswordChangeAfter = "emailpassword:password_change:after"

	// EventPasswordResetRequested is emitted when a secure password reset token is generated.
	// Essential for dispatching password reset emails containing the verification token link.
	// Payload: *PasswordResetRequestedEventPayload
	EventPasswordResetRequested = "emailpassword:password_reset:requested"

	// EventPasswordResetCompleted is emitted after a password has been successfully reset using a valid token.
	// Useful for sending security confirmation notifications.
	// Payload: *PasswordResetCompletedEventPayload
	EventPasswordResetCompleted = "emailpassword:password_reset:completed"
)

Variables

View Source
var (
	// ErrPasswordTooShort is returned when a password does not satisfy the configured minimum length requirement.
	ErrPasswordTooShort = errors.New("emailpassword: password does not meet the minimum length requirement")

	// ErrInvalidCredentials is returned when email or password verification fails during sign-in.
	ErrInvalidCredentials = errors.New("emailpassword: invalid credentials")

	// ErrEmailNotVerified is returned when sign-in is attempted by a user whose email has not been verified, and verification is required.
	ErrEmailNotVerified = errors.New("emailpassword: email address has not been verified")

	// ErrInvalidCurrentPass is returned during password change when the provided current password does not match stored credentials.
	ErrInvalidCurrentPass = errors.New("emailpassword: current password is incorrect")

	// ErrTokenExpired is returned when attempting to consume a verification or password reset token that has expired.
	ErrTokenExpired = errors.New("emailpassword: token has expired")
)
View Source
var (
	// ErrUserAlreadyExists is returned when attempting to register an email address that is already associated with an existing user.
	ErrUserAlreadyExists = errors.New("emailpassword: user already exists")

	// ErrUserNotFound is returned by repository methods when no user matches the given identifier or email.
	ErrUserNotFound = errors.New("emailpassword: user not found")

	// ErrAccountNotFound is returned when an authentication credentials record is not found for the specified user and provider.
	ErrAccountNotFound = errors.New("emailpassword: credential account not found")

	// ErrInvalidToken is returned when a password reset or email verification token does not exist in storage.
	ErrInvalidToken = errors.New("emailpassword: verification token invalid or expired")
)

Functions

This section is empty.

Types

type Config

type Config struct {
	// MinPasswordLength specifies the minimum acceptable length for user passwords (default: 8).
	MinPasswordLength int

	// RequireEmailVerification enforces that user.EmailVerified must be true before sign-in succeeds (default: false).
	RequireEmailVerification bool

	// ResetTokenExpiry specifies the duration for which password reset tokens remain valid (default: 15 minutes).
	ResetTokenExpiry time.Duration
}

Config defines the configurable options for the EmailPassword plugin.

type Option

type Option func(*Config)

Option defines a functional option for configuring the EmailPassword plugin.

func WithMinPasswordLength

func WithMinPasswordLength(length int) Option

WithMinPasswordLength sets the minimum required password length during registration and password change operations.

func WithRequireEmailVerification

func WithRequireEmailVerification(require bool) Option

WithRequireEmailVerification defines whether email verification is strictly required before sign-in succeeds.

func WithResetTokenExpiry

func WithResetTokenExpiry(d time.Duration) Option

WithResetTokenExpiry sets the validity duration for generated password reset tokens.

type PasswordChangeEventPayload

type PasswordChangeEventPayload struct {
	// UserID identifies the user whose password is being modified.
	UserID string
}

PasswordChangeEventPayload contains the user identifier for password change lifecycle events.

type PasswordResetCompletedEventPayload

type PasswordResetCompletedEventPayload struct {
	// UserID identifies the user whose password was reset.
	UserID string
}

PasswordResetCompletedEventPayload contains confirmation details after a password reset has completed.

type PasswordResetRequestedEventPayload

type PasswordResetRequestedEventPayload struct {
	// User is the target user entity requesting the reset.
	User *entity.User

	// Token is the secure random token generated for the password reset request.
	Token string

	// ExpiresAt specifies the exact expiration time for the reset token.
	ExpiresAt time.Time
}

PasswordResetRequestedEventPayload contains details required to dispatch a password reset email to a user.

type Plugin

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

Plugin implements the modular authentication Plugin interface for credential-based email and password workflows.

func New

func New(repo Repository, opts ...Option) *Plugin

New creates and initializes a new EmailPassword plugin instance with the specified repository and functional options.

Arguments:

  • repo: Implementation of emailpassword.Repository interface.
  • opts: Optional functional configuration options (WithMinPasswordLength, WithRequireEmailVerification, WithResetTokenExpiry).

Returns:

  • *Plugin: The configured EmailPassword plugin instance ready for registration in auth.New.

func (*Plugin) ChangePassword

func (p *Plugin) ChangePassword(ctx context.Context, input dto.ChangePasswordParams) error

ChangePassword updates an existing authenticated user's password after verifying their current password.

Brief Explanation:

Verifies the current password against stored credentials to prevent unauthorized modification,
enforces password length requirements, computes the new password hash, updates the database,
and emits EventPasswordChangeBefore and EventPasswordChangeAfter.

Function:

User settings and self-service password update workflow.

Arguments:

  • ctx: Request cancellation context.
  • input: dto.ChangePasswordParams containing:
  • UserID (string, required): Authenticated user's unique identifier.
  • CurrentPassword (string, required): Current password for authorization.
  • NewPassword (string, required): New password to set.

Returns:

  • error: ErrPasswordTooShort, ErrAccountNotFound, ErrInvalidCurrentPass, or database error.

Example:

err := epPlugin.ChangePassword(ctx, dto.ChangePasswordParams{
	UserID:          "usr_12345",
	CurrentPassword: "OldPassword123!",
	NewPassword:     "NewBrandPassword456!",
})
if err != nil {
	log.Fatalf("Password change failed: %v", err)
}

func (*Plugin) ForgotPassword

func (p *Plugin) ForgotPassword(ctx context.Context, input dto.ForgotPasswordParams) (*entity.VerificationToken, error)

ForgotPassword initiates a tokenized password recovery workflow for a user.

Brief Explanation:

Finds the user by email, generates a 32-byte cryptographically secure random token, persists the token
with an expiration timestamp, and publishes EventPasswordResetRequested so email/notification dispatchers
can send the recovery link to the user.

Function:

Initial step of forgot-password and account recovery.

Arguments:

  • ctx: Request cancellation context.
  • input: dto.ForgotPasswordParams containing:
  • Email (string, required): User email address requesting reset.

Returns:

  • *entity.VerificationToken: The generated verification token entity (containing Token string and ExpiresAt).
  • error: ErrUserNotFound or database error.

Example:

token, err := epPlugin.ForgotPassword(ctx, dto.ForgotPasswordParams{
	Email: "john.doe@example.com",
})
if err != nil {
	log.Fatalf("Forgot password failed: %v", err)
}
fmt.Printf("Reset link token: %s (expires: %v)\n", token.Token, token.ExpiresAt)

func (*Plugin) ID

func (p *Plugin) ID() string

ID returns the unique identifier for the plugin ("email-password").

func (*Plugin) Init

func (p *Plugin) Init(ctx *plugin.Context) error

Init initializes the plugin within the global GoModularAuth runtime context.

func (*Plugin) ResetPassword

func (p *Plugin) ResetPassword(ctx context.Context, input dto.ResetPasswordParams) error

ResetPassword completes a password reset by consuming a single-use token and setting a new password.

Brief Explanation:

Validates token existence and expiry, hashes the new password, updates the user's credential account,
atomically deletes the consumed token to guarantee single-use safety, and emits EventPasswordResetCompleted.

Function:

Final step of forgot-password and recovery verification.

Arguments:

  • ctx: Request cancellation context.
  • input: dto.ResetPasswordParams containing:
  • Token (string, required): Single-use recovery token from email.
  • NewPassword (string, required): New password to set.

Returns:

  • error: ErrPasswordTooShort, ErrInvalidToken, ErrTokenExpired, ErrUserNotFound, or database error.

Example:

err := epPlugin.ResetPassword(ctx, dto.ResetPasswordParams{
	Token:       "9a8b7c6d5e4f3a2b1c0d",
	NewPassword: "BrandNewSecurePassword123!",
})
if err != nil {
	log.Fatalf("Password reset failed: %v", err)
}

func (*Plugin) SignIn

func (p *Plugin) SignIn(ctx context.Context, input dto.SignInParams) (*entity.User, error)

SignIn authenticates user credentials by verifying email existence and comparing the password hash.

Brief Explanation:

Fetches the user and corresponding credentials account, securely verifies the password using constant-time
comparison, checks email verification prerequisites (if configured), and publishes EventSignInBefore and EventSignInAfter.

Function:

Primary entry point for user login authentication.

Arguments:

  • ctx: Request cancellation context.
  • input: dto.SignInParams containing:
  • Email (string, required): User email address.
  • Password (string, required): Plaintext password to compare against stored hash.

Returns:

  • *entity.User: The authenticated user profile.
  • error: ErrInvalidCredentials, ErrEmailNotVerified, or database error.

Example:

user, err := epPlugin.SignIn(ctx, dto.SignInParams{
	Email:    "john.doe@example.com",
	Password: "SuperSecretPassword123!",
})
if err != nil {
	log.Fatalf("Authentication failed: %v", err)
}

func (*Plugin) SignUp

func (p *Plugin) SignUp(ctx context.Context, input dto.SignUpParams) (*entity.User, error)

SignUp registers a new user with email and password credentials.

Brief Explanation:

Validates password constraints, ensures email uniqueness, securely hashes the password using bcrypt/argon2,
publishes the EventSignUpBefore event (enabling listeners to mutate parameters or inject dynamic metadata),
persists both the user entity and credential account, and finally publishes EventSignUpAfter.

Function:

Primary entry point for user registration.

Arguments:

  • ctx: Request cancellation context.
  • input: dto.SignUpParams containing:
  • Email (string, required): User's primary email address.
  • Password (string, required): Plaintext password to hash and validate.
  • Name (string, optional): Display name of the user.
  • Extra (map[string]any, optional): Dynamic metadata (e.g. role, organization, phone).

Returns:

  • *entity.User: The persisted user entity containing generated ID and timestamps.
  • error: ErrPasswordTooShort, ErrUserAlreadyExists, or database error.

Example:

user, err := epPlugin.SignUp(ctx, dto.SignUpParams{
	Email:    "john.doe@example.com",
	Password: "SuperSecretPassword123!",
	Name:     "John Doe",
})
if err != nil {
	log.Fatalf("Sign up failed: %v", err)
}
fmt.Printf("Created user with ID: %s\n", user.ID)

type Repository

type Repository interface {
	// GetUserByEmail retrieves a user entity matching the provided unique email address.
	//
	// Function:
	//   Used during Sign-In and Forgot-Password to check user existence and retrieve profile details.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - email: The normalized email address to query.
	//
	// Returns:
	//   - *entity.User: The matching user entity if found.
	//   - error: ErrUserNotFound if no user matches, or database failure error.
	//
	// Example SQL:
	//   SELECT id, email, name, email_verified, created_at, updated_at FROM users WHERE email = $1 LIMIT 1;
	GetUserByEmail(ctx context.Context, email string) (*entity.User, error)

	// GetUserByID retrieves a user entity matching the given unique identifier.
	//
	// Function:
	//   Used during Change-Password and Reset-Password verification flows.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - id: The unique primary key identifier of the user (e.g. UUID).
	//
	// Returns:
	//   - *entity.User: The matching user entity.
	//   - error: ErrUserNotFound if no record matches, or database failure error.
	//
	// Example SQL:
	//   SELECT id, email, name, email_verified, created_at, updated_at FROM users WHERE id = $1 LIMIT 1;
	GetUserByID(ctx context.Context, id string) (*entity.User, error)

	// CreateUser persists a new user record generated from the provided registration parameters.
	//
	// Function:
	//   Called during SignUp to create the primary user entity. Plugins may inspect or modify
	//   params.Extra before this method is called via EventSignUpBefore.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - params: Pointer to CreateUserParams containing Email, Name, PasswordHash, and Extra metadata.
	//
	// Returns:
	//   - *entity.User: The newly created user entity with populated ID and timestamps.
	//   - error: ErrUserAlreadyExists on unique violation, or database failure error.
	//
	// Example SQL:
	//   INSERT INTO users (id, email, name, password_hash, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6);
	CreateUser(ctx context.Context, params *dto.CreateUserParams) (*entity.User, error)

	// UpdateUser updates an existing user profile record in storage.
	//
	// Function:
	//   Used when updating user metadata, email verification state, or profile attributes.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - user: The updated user entity.
	//
	// Returns:
	//   - error: Nil on success, or database error on failure.
	//
	// Example SQL:
	//   UPDATE users SET email = $1, name = $2, email_verified = $3, updated_at = $4 WHERE id = $5;
	UpdateUser(ctx context.Context, user *entity.User) error

	// GetAccountByUserIDAndProvider retrieves the credential account associated with a user and authentication provider.
	//
	// Function:
	//   Used during SignIn and ChangePassword to retrieve stored hashed passwords (provider: "credential").
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - userID: The target user's ID.
	//   - provider: The authentication provider identifier (typically "credential").
	//
	// Returns:
	//   - *entity.Account: The matching credentials account containing the password hash.
	//   - error: ErrAccountNotFound if no record matches, or database failure error.
	//
	// Example SQL:
	//   SELECT id, user_id, provider, password, created_at FROM accounts WHERE user_id = $1 AND provider = $2 LIMIT 1;
	GetAccountByUserIDAndProvider(ctx context.Context, userID, provider string) (*entity.Account, error)

	// CreateAccount persists a new provider credentials record associated with a user.
	//
	// Function:
	//   Called immediately after CreateUser during SignUp to link credential passwords to the user.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - account: The credentials account entity to insert.
	//
	// Returns:
	//   - error: Nil on success, or database error on failure.
	//
	// Example SQL:
	//   INSERT INTO accounts (id, user_id, provider, password, created_at) VALUES ($1, $2, $3, $4, $5);
	CreateAccount(ctx context.Context, account *entity.Account) error

	// UpdateAccountPassword updates the hashed password for a specific account record.
	//
	// Function:
	//   Called during ChangePassword and ResetPassword to overwrite the stored password hash.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - accountID: Primary key ID of the account record to update.
	//   - hashedPassword: The newly computed bcrypt/argon2 password hash.
	//
	// Returns:
	//   - error: Nil on success, or database error on failure.
	//
	// Example SQL:
	//   UPDATE accounts SET password = $1 WHERE id = $2;
	UpdateAccountPassword(ctx context.Context, accountID, hashedPassword string) error

	// CreateVerificationToken persists a short-lived token record for password resets or email confirmations.
	//
	// Function:
	//   Called during ForgotPassword to save the generated reset token and expiration timestamp.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - token: The VerificationToken entity (Identifier/Email, Token, ExpiresAt).
	//
	// Returns:
	//   - error: Nil on success, or database error on failure.
	//
	// Example SQL:
	//   INSERT INTO verification_tokens (identifier, token, expires_at) VALUES ($1, $2, $3);
	CreateVerificationToken(ctx context.Context, token *entity.VerificationToken) error

	// GetVerificationToken retrieves an active token record by its token string.
	//
	// Function:
	//   Called during ResetPassword to validate token existence and verify whether it has expired.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - token: The raw token string submitted by the user.
	//
	// Returns:
	//   - *entity.VerificationToken: The matching token entity with its expiration timestamp.
	//   - error: ErrInvalidToken if no record is found, or database failure error.
	//
	// Example SQL:
	//   SELECT identifier, token, expires_at FROM verification_tokens WHERE token = $1 LIMIT 1;
	GetVerificationToken(ctx context.Context, token string) (*entity.VerificationToken, error)

	// DeleteVerificationToken removes a consumed or invalidated token from storage.
	//
	// Function:
	//   Called immediately upon successful password reset to guarantee single-use token consumption.
	//
	// Arguments:
	//   - ctx: Request cancellation context.
	//   - token: The token string to delete.
	//
	// Returns:
	//   - error: Nil on success, or database error on failure.
	//
	// Example SQL:
	//   DELETE FROM verification_tokens WHERE token = $1;
	DeleteVerificationToken(ctx context.Context, token string) error
}

Repository defines the persistent storage contract required by the EmailPassword plugin. Implement this interface on your custom database adapter (e.g. PostgreSQL, MySQL, SQLite, MongoDB, GORM).

Implementation Example (GORM / database/sql):

type GormAuthRepository struct {
	db *gorm.DB
}

func (r *GormAuthRepository) GetUserByEmail(ctx context.Context, email string) (*entity.User, error) {
	var m UserModel
	if err := r.db.WithContext(ctx).Where("email = ?", email).First(&m).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, emailpassword.ErrUserNotFound
		}
		return nil, err
	}
	return m.ToEntity(), nil
}

func (r *GormAuthRepository) CreateUser(ctx context.Context, params *dto.CreateUserParams) (*entity.User, error) {
	m := UserModel{
		ID:           uuid.NewString(),
		Email:        params.Email,
		Name:         params.Name,
		PasswordHash: params.PasswordHash,
		CreatedAt:    time.Now(),
		UpdatedAt:    time.Now(),
	}
	if err := r.db.WithContext(ctx).Create(&m).Error; err != nil {
		return nil, err
	}
	return m.ToEntity(), nil
}

type SignInEventPayload

type SignInEventPayload struct {
	// User is the authenticated user entity.
	User *entity.User
}

SignInEventPayload contains the authenticated user entity associated with a sign-in event.

type SignUpEventPayload

type SignUpEventPayload struct {
	// Params holds mutable user creation parameters (including dynamic Extra metadata).
	Params *dto.CreateUserParams

	// User contains the persisted user entity (populated in EventSignUpAfter).
	User *entity.User
}

SignUpEventPayload contains the parameter and entity data associated with a sign-up event.

Jump to

Keyboard shortcuts

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