auth

package module
v2.1.1 Latest Latest
Warning

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

Go to latest
Published: Mar 14, 2026 License: MIT Imports: 11 Imported by: 0

README

Auth v2 - Email Authentication with OTP & Password

A production-ready Go authentication library supporting two authentication methods:

  • OTP (One-Time Password) via email
  • Email/Password with secure password reset

Features

Dual Authentication Methods

  • OTP-based authentication (6-digit codes, 5-minute expiry)
  • Email/password authentication with bcrypt hashing
  • Secure password reset tokens with CSPRN randomness

🔐 Security

  • Bcrypt password hashing (configurable cost, default: cost 10)
  • Password strength validation (uppercase, lowercase, digit, special char)
  • Password reset tokens: 256-bit CSPRNG + SHA-256 hashing
  • Atomic transactions for password updates and token management
  • Token reuse prevention via atomic marking

👥 User Management

  • User creation and authentication
  • Direct & group-based permissions
  • Permission resolution (direct + inherited from groups)
  • Super admin bootstrap on startup

📧 Email Integration

  • ZeptoMail integration for OTP and password reset emails
  • Branded HTML email templates
  • Configurable sender address

🗄️ Database

  • PostgreSQL with pgx driver
  • Embedded SQL migrations with checksum verification
  • Schema management (create/drop)
  • Automatic migration detection and rollback

🎫 JWT Tokens

  • Access tokens (15 min expiry, includes permissions/groups)
  • Refresh tokens (7 days expiry)
  • HMAC-SHA256 signing

Quick Start

Installation
go get github.com/meikuraledutech/auth/v2@v2.0.0
Basic Usage
package main

import (
	"context"
	"log"

	"github.com/jackc/pgx/v5/pgxpool"
	auth "github.com/meikuraledutech/auth/v2"
	"github.com/meikuraledutech/auth/v2/postgres"
	"github.com/meikuraledutech/auth/v2/zeptomail"
)

func main() {
	ctx := context.Background()

	// Connect to PostgreSQL
	pool, err := pgxpool.New(ctx, "postgresql://user:password@localhost/authdb")
	if err != nil {
		log.Fatal(err)
	}
	defer pool.Close()

	// Create config
	cfg := auth.DefaultConfig("your-jwt-secret", "admin@example.com")

	// Create store and mailer
	store := postgres.New(pool, cfg)
	mailer := zeptomail.New("api-key", "noreply@example.com")

	// Bootstrap (runs migrations, seeds permissions, creates super admin)
	if err := store.Bootstrap(ctx, "admin@example.com"); err != nil {
		log.Fatal(err)
	}

	// Use store for authentication operations
	// (see examples below)
}

Authentication Methods

1. OTP Authentication

Create OTP:

otp, err := store.CreateOTP(ctx, "user@example.com")
if err != nil {
	log.Fatal(err)
}

// Send email
err = mailer.SendOTP(ctx, "user@example.com", otp.Code, cfg.OTPExpiry)

Verify OTP:

user, err := store.VerifyOTP(ctx, "user@example.com", "123456")
if err != nil {
	// Handle ErrOTPInvalid, ErrOTPExpired
	log.Fatal(err)
}

// Auto-creates user if not exists
fmt.Printf("Logged in as: %s\n", user.ID)

// Generate JWT tokens
tokens, err := auth.GenerateTokenPair(cfg, user, permissions, groups)
2. Password Authentication

Register with Password:

user, err := store.RegisterWithPassword(ctx, "user@example.com", "SecurePass123!")
if err != nil {
	// Handle ErrEmailAlreadyRegistered, ErrPasswordTooWeak
	log.Fatal(err)
}

Login with Password:

user, err := store.LoginWithPassword(ctx, "user@example.com", "SecurePass123!")
if err != nil {
	// Handle ErrPasswordInvalid, ErrPasswordNotSet
	log.Fatal(err)
}

// Generate JWT tokens
tokens, err := auth.GenerateTokenPair(cfg, user, permissions, groups)

Change Password:

err := store.ChangePassword(ctx, userID, "OldPassword123!", "NewPassword456!")
if err != nil {
	// Handle ErrPasswordInvalid, ErrPasswordTooWeak, ErrPasswordNotSet
	log.Fatal(err)
}

Set Password (Admin Override):

err := store.SetPassword(ctx, userID, "AdminSetPassword123!")
if err != nil {
	log.Fatal(err)
}

Check if User Has Password:

hasPassword, err := store.HasPassword(ctx, userID)
if !hasPassword {
	// OTP-only account
}
3. Password Reset Flow

Step 1: Create Reset Token

rawToken, expiresAt, err := store.CreatePasswordReset(ctx, "user@example.com")
if err != nil {
	log.Fatal(err)
}

// Build reset URL with token
resetURL := fmt.Sprintf("https://example.com/reset?token=%s", rawToken)

// Send email
err = mailer.SendPasswordReset(ctx, "user@example.com", resetURL, cfg.PasswordResetExpiry)

Step 2: Reset Password with Token

err := store.ResetPassword(ctx, rawToken, "NewPassword789!")
if err != nil {
	// Handle ErrResetTokenInvalid, ErrResetTokenUsed, ErrPasswordTooWeak
	log.Fatal(err)
}

// User can now login with new password
user, err := store.LoginWithPassword(ctx, "user@example.com", "NewPassword789!")

Configuration

Default Config
cfg := auth.DefaultConfig(jwtSecret, superAdminEmail)

// Customize if needed
cfg.BcryptCost = 11              // Default: bcrypt.DefaultCost (10)
cfg.PasswordResetExpiry = 2*time.Hour // Default: 1 hour
cfg.MinPasswordLength = 10        // Default: 8
cfg.MaxPasswordLength = 72        // Default: 72 (bcrypt limit)
cfg.OTPLength = 8                 // Default: 6
cfg.OTPExpiry = 10*time.Minute   // Default: 5 minutes
cfg.AccessExpiry = 20*time.Minute // Default: 15 minutes
cfg.RefreshExpiry = 14*24*time.Hour // Default: 7 days
Environment Variables
# Database
DATABASE_URL=postgresql://postgres:password@localhost:5432/authdb

# JWT
JWT_SECRET=your-secret-key-min-32-chars

# Email (ZeptoMail)
ZEPTO_API_KEY=your-zeptomail-api-key
FROM_EMAIL=noreply@example.com

# Admin
SUPER_ADMIN_EMAIL=admin@example.com

Permission & Group Management

Permissions

Create Permission:

perm, err := store.CreatePermission(ctx, "forms:create", "Can create forms")

Assign to User:

err := store.AssignPermission(ctx, userID, "forms:create")

Assign to Group:

err := store.AddPermissionToGroup(ctx, groupID, "forms:create")

Check User Permission (with group inheritance):

hasAccess, err := store.HasResolvedPermission(ctx, userID, "forms:create")
Groups

Create Group:

group, err := store.CreateGroup(ctx, "Editors")

Add User to Group:

err := store.AssignUserToGroup(ctx, userID, groupID)

Add Permission to Group:

err := store.AddPermissionToGroup(ctx, groupID, "forms:edit")

JWT Token Usage

Validate Token
claims, err := auth.ValidateToken(cfg, tokenString)
if err != nil {
	// Handle invalid/expired token
	log.Fatal(err)
}

// Access claims
fmt.Printf("User ID: %s\n", claims.UserID)
fmt.Printf("Email: %s\n", claims.Email)
fmt.Printf("Type: %s\n", claims.Type) // "access" or "refresh"
fmt.Printf("Permissions: %v\n", claims.Permissions)
fmt.Printf("Groups: %v\n", claims.Groups)
Token Types
  • Access Token (15 min default)

    • Contains: user_id, email, permissions, groups
    • Use for API requests
    • Embed permissions for authorization checks
  • Refresh Token (7 days default)

    • Contains: user_id, email
    • Use to request new access token
    • Longer expiry for device persistence

Database Schema

Tables

auth_users

id           TEXT PRIMARY KEY
email        TEXT NOT NULL UNIQUE
password_hash TEXT -- NULL for OTP-only users
created_at   TIMESTAMPTZ DEFAULT NOW()

auth_otps

id         TEXT PRIMARY KEY
email      TEXT NOT NULL
code       TEXT NOT NULL
expires_at TIMESTAMPTZ NOT NULL
verified   BOOLEAN DEFAULT FALSE
created_at TIMESTAMPTZ DEFAULT NOW()

auth_password_resets

id         TEXT PRIMARY KEY
user_id    TEXT NOT NULL REFERENCES auth_users(id) ON DELETE CASCADE
token_hash TEXT NOT NULL UNIQUE
expires_at TIMESTAMPTZ NOT NULL
used       BOOLEAN DEFAULT FALSE
created_at TIMESTAMPTZ DEFAULT NOW()

auth_permissions

id          TEXT PRIMARY KEY
key         TEXT NOT NULL UNIQUE (e.g., "forms:create")
description TEXT
created_at  TIMESTAMPTZ DEFAULT NOW()

auth_user_permissions

user_id       TEXT NOT NULL REFERENCES auth_users(id) ON DELETE CASCADE
permission_id TEXT NOT NULL REFERENCES auth_permissions(id) ON DELETE CASCADE
PRIMARY KEY (user_id, permission_id)

auth_groups

id         TEXT PRIMARY KEY
name       TEXT NOT NULL UNIQUE
created_at TIMESTAMPTZ DEFAULT NOW()

auth_group_permissions

group_id      TEXT NOT NULL REFERENCES auth_groups(id) ON DELETE CASCADE
permission_id TEXT NOT NULL REFERENCES auth_permissions(id) ON DELETE CASCADE
PRIMARY KEY (group_id, permission_id)

auth_user_groups

user_id  TEXT NOT NULL REFERENCES auth_users(id) ON DELETE CASCADE
group_id TEXT NOT NULL REFERENCES auth_groups(id) ON DELETE CASCADE
PRIMARY KEY (user_id, group_id)

auth_migrations (auto-created)

id         SERIAL PRIMARY KEY
name       TEXT NOT NULL UNIQUE
applied_at TIMESTAMPTZ DEFAULT NOW()
checksum   TEXT NOT NULL

Error Handling

Authentication Errors
// OTP errors
auth.ErrOTPExpired           // OTP has expired
auth.ErrOTPInvalid           // Invalid OTP code

// Password errors
auth.ErrPasswordInvalid      // Wrong password
auth.ErrPasswordTooWeak      // Doesn't meet strength requirements
auth.ErrPasswordNotSet        // User has no password (OTP-only)
auth.ErrEmailAlreadyRegistered // Email already registered

// Reset token errors
auth.ErrResetTokenInvalid    // Token not found or expired
auth.ErrResetTokenUsed       // Token already used

// User errors
auth.ErrUserNotFound         // User not found

// Permission errors
auth.ErrPermissionNotFound   // Permission doesn't exist
auth.ErrPermissionExists     // Permission already exists

// Group errors
auth.ErrGroupNotFound        // Group doesn't exist
auth.ErrGroupExists          // Group already exists
Example Error Handling
user, err := store.LoginWithPassword(ctx, email, password)
if err != nil {
	switch err {
	case auth.ErrPasswordInvalid:
		// Wrong password
		http.Error(w, "Invalid credentials", http.StatusUnauthorized)
	case auth.ErrPasswordNotSet:
		// User registered with OTP only
		http.Error(w, "User must login with OTP", http.StatusBadRequest)
	case auth.ErrUserNotFound:
		// No user with this email
		http.Error(w, "User not found", http.StatusNotFound)
	default:
		// Other errors
		http.Error(w, "Server error", http.StatusInternalServerError)
	}
	return
}

// Successfully logged in

Security Considerations

Password Security
  • Hashing: bcrypt with configurable cost (default: cost 10)
  • Strength: Enforces uppercase, lowercase, digit, special character
  • Length: Configurable 8-72 chars (72 is bcrypt limit)
  • No plaintext: Never stored in logs or returned to client
Password Reset
  • Entropy: 32-byte CSPRNG (256 bits)
  • Format: Base64url encoded (43 characters)
  • Storage: Only SHA-256 hash stored in DB
  • Token lifecycle: Single-use, expires in 1 hour
  • Race condition prevention: Atomic marking in transaction
OTP Security
  • Code generation: Cryptographically random
  • Expiration: 5 minutes by default
  • Verification: Marked as verified after successful use
  • Auto-creation: User created automatically on first OTP verification
JWT Tokens
  • Signing: HMAC-SHA256
  • Secret: Should be min 32 characters
  • Expiry: Access (15 min) and Refresh (7 days)
  • Claims: Include permissions for authorization checks
Database Security
  • Cascading deletes: Orphaned records removed automatically
  • Unique constraints: Email, permission keys, group names
  • Indices: Query optimization for common lookups
  • Transactions: Password/token operations are atomic

Migration from v1

v2 is backward compatible with v1 OTP flow. Key differences:

Feature v1 v2
OTP Auth ✅ (unchanged)
Password Auth
Password Reset
Permission Model ✅ (same)
Database ✅ (added password columns)
Upgrade Path
  1. Deploy v2 with AutoMigrate enabled
  2. Migration 001 creates initial schema (same as v1)
  3. Migration 002 adds password_hash column + password reset table
  4. Existing OTP users continue to work
  5. New users can register with password or OTP

No data loss - password_hash is nullable for existing OTP-only users.

Testing

Run the included example:

cd v2/examples

# Set environment variables
export DATABASE_URL="postgresql://postgres:password@localhost:5432/authdb"
export JWT_SECRET="test-secret-key"
export ZEPTO_API_KEY="your-api-key"
export FROM_EMAIL="noreply@example.com"
export SUPER_ADMIN_EMAIL="admin@example.com"

# Run tests
go run main.go

Expected output:

=== Testing Auth v2 ===

1. Running migrations and bootstrap...
   ✓ Bootstrap complete

2. Creating OTP for test@example.com...
   ✓ OTP created: 123456

3. Sending OTP email...
   ✓ Email sent

4. Testing password auth...
   ✓ User registered
   ✓ Logged in

5. Testing password reset...
   ✓ Reset token created

6. Testing token generation...
   ✓ Access token generated
   ✓ Refresh token generated

=== All tests passed! ===

API Reference

Store Interface

See STORE_API.md for complete interface documentation.

Helper Functions

See HELPERS.md for password and token helper functions.

Contributing

Issues and pull requests welcome!

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrOTPExpired             = errors.New("auth: otp has expired")
	ErrOTPInvalid             = errors.New("auth: invalid otp code")
	ErrUserNotFound           = errors.New("auth: user not found")
	ErrPermissionNotFound     = errors.New("auth: permission not found")
	ErrPermissionExists       = errors.New("auth: permission already exists")
	ErrGroupNotFound          = errors.New("auth: group not found")
	ErrGroupExists            = errors.New("auth: group already exists")
	ErrPasswordInvalid        = errors.New("auth: invalid password")
	ErrPasswordTooWeak        = errors.New("auth: password does not meet strength requirements")
	ErrPasswordNotSet         = errors.New("auth: user has no password set")
	ErrEmailAlreadyRegistered = errors.New("auth: email is already registered")
	ErrResetTokenInvalid      = errors.New("auth: invalid or expired password reset token")
	ErrResetTokenUsed         = errors.New("auth: password reset token has already been used")
	ErrOrganizationNotFound   = errors.New("auth: organization not found")
	ErrOrganizationExists     = errors.New("auth: organization already exists")
	ErrInvalidOrganization    = errors.New("auth: invalid organization")
	ErrGroupMembershipFailed  = errors.New("auth: failed to update group membership")
)

Functions

func CheckPassword

func CheckPassword(plain, hash string) error

CheckPassword compares plain text against bcrypt hash. Returns ErrPasswordInvalid on mismatch.

func GenerateResetToken

func GenerateResetToken() (string, error)

GenerateResetToken returns a 32-byte CSPRNG token as base64url string (43 chars).

func HashPassword

func HashPassword(cfg Config, plain string) (string, error)

HashPassword validates password strength then bcrypt-hashes the plain text password.

func HashResetToken

func HashResetToken(rawToken string) string

HashResetToken returns the SHA-256 hex of the raw token (stored in DB, never the raw token).

func ValidatePasswordStrength

func ValidatePasswordStrength(cfg Config, plain string) error

ValidatePasswordStrength enforces password strength requirements: - min/max length (max is 72 due to bcrypt limit) - uppercase, lowercase, digit, special character

Types

type Claims

type Claims struct {
	UserID      string         `json:"user_id"`
	Email       string         `json:"email"`
	Type        string         `json:"type"` // "access" or "refresh"
	Permissions []string       `json:"permissions,omitempty"`
	Groups      []string       `json:"groups,omitempty"` // group names
	Meta        map[string]any `json:"meta,omitempty"`   // custom app-level claims (e.g. college_id)
}

Claims represents the JWT token claims.

func ValidateToken

func ValidateToken(cfg Config, tokenStr string) (*Claims, error)

ValidateToken parses and validates a JWT token, returning the claims.

type Config

type Config struct {
	JWTSecret           string
	OTPLength           int
	OTPExpiry           time.Duration
	AccessExpiry        time.Duration
	RefreshExpiry       time.Duration
	SuperAdminEmail     string
	AutoMigrate         bool // if true, Bootstrap() runs migrations automatically
	BcryptCost          int
	PasswordResetExpiry time.Duration
	MinPasswordLength   int
	MaxPasswordLength   int
}

Config holds the configuration for the auth package.

func DefaultConfig

func DefaultConfig(jwtSecret string, superAdminEmail string) Config

DefaultConfig returns a Config with sensible defaults.

type Group

type Group struct {
	ID          string       `json:"id"`
	Name        string       `json:"name"` // e.g. "Editor"
	Permissions []Permission `json:"permissions,omitempty"`
	CreatedAt   time.Time    `json:"created_at"`
}

Group represents a permission group for bulk assignment.

type Mailer

type Mailer interface {
	SendOTP(ctx context.Context, email string, code string, expiresIn time.Duration) error
	SendPasswordReset(ctx context.Context, email string, resetURL string, expiresIn time.Duration) error
	SendWelcome(ctx context.Context, user *User) error
}

Mailer defines the contract for sending auth emails. Implementors use their own templates and sender identity — auth only calls these methods.

type MigrationRecord

type MigrationRecord struct {
	Name      string
	Applied   bool
	AppliedAt *time.Time
	Checksum  string
}

MigrationRecord represents an applied database migration.

type Migrator

type Migrator interface {
	// Migrate applies all pending migrations in order.
	Migrate(ctx context.Context) error

	// Rollback rolls back the last applied migration.
	Rollback(ctx context.Context) error

	// MigrationStatus returns all migrations with their applied status.
	MigrationStatus(ctx context.Context) ([]MigrationRecord, error)
}

Migrator defines the contract for managing database migrations.

type OTP

type OTP struct {
	ID        string    `json:"id"`
	Email     string    `json:"email"`
	Code      string    `json:"code"`
	ExpiresAt time.Time `json:"expires_at"`
	Verified  bool      `json:"verified"`
	CreatedAt time.Time `json:"created_at"`
}

OTP represents a one-time password sent to a user's email.

type Organization added in v2.1.0

type Organization struct {
	ID          string       `json:"id"`
	Name        string       `json:"name"` // e.g. "trainer", "student"
	Permissions []Permission `json:"permissions,omitempty"`
	CreatedAt   time.Time    `json:"created_at"`
}

Organization represents an organizational role with base permissions.

type PasswordReset

type PasswordReset struct {
	ID        string    `json:"id"`
	UserID    string    `json:"user_id"`
	ExpiresAt time.Time `json:"expires_at"`
	Used      bool      `json:"used"`
	CreatedAt time.Time `json:"created_at"`
}

PasswordReset represents a password reset request.

type Permission

type Permission struct {
	ID          string    `json:"id"`
	Key         string    `json:"key"`         // e.g. "forms:create"
	Description string    `json:"description"` // e.g. "Can create forms"
	CreatedAt   time.Time `json:"created_at"`
}

Permission represents a single permission that can be assigned to users or groups.

type Store

type Store interface {
	// Schema
	CreateSchema(ctx context.Context) error
	DropSchema(ctx context.Context) error

	// OTP
	CreateOTP(ctx context.Context, email string) (*OTP, error)
	VerifyOTP(ctx context.Context, email string, code string) (*User, error)

	// Users
	CreateUser(ctx context.Context, email string) (*User, error)
	GetUserByID(ctx context.Context, id string) (*User, error)
	GetUserByEmail(ctx context.Context, email string) (*User, error)
	ListUsers(ctx context.Context) ([]User, error)

	// Permissions
	CreatePermission(ctx context.Context, key string, description string) (*Permission, error)
	GetPermission(ctx context.Context, key string) (*Permission, error)
	ListPermissions(ctx context.Context) ([]Permission, error)
	DeletePermission(ctx context.Context, id string) error

	// User Permissions (direct)
	AssignPermission(ctx context.Context, userID string, permissionKey string) error
	RevokePermission(ctx context.Context, userID string, permissionKey string) error
	GetUserPermissions(ctx context.Context, userID string) ([]Permission, error)
	HasPermission(ctx context.Context, userID string, permissionKey string) (bool, error)

	// Groups
	CreateGroup(ctx context.Context, name string) (*Group, error)
	GetGroup(ctx context.Context, id string) (*Group, error)
	ListGroups(ctx context.Context) ([]Group, error)
	DeleteGroup(ctx context.Context, id string) error
	AddPermissionToGroup(ctx context.Context, groupID string, permissionKey string) error
	RemovePermissionFromGroup(ctx context.Context, groupID string, permissionID string) error

	// User Groups
	AssignUserToGroup(ctx context.Context, userID string, groupID string) error
	RemoveUserFromGroup(ctx context.Context, userID string, groupID string) error
	GetUserGroups(ctx context.Context, userID string) ([]Group, error)

	// Resolved Permissions (direct + from groups + from organizations)
	GetResolvedPermissions(ctx context.Context, userID string) ([]Permission, error)
	HasResolvedPermission(ctx context.Context, userID string, permissionKey string) (bool, error)

	// Organizations
	CreateOrganizationWithPermissions(ctx context.Context, name string, permissionKeys []string) (*Organization, error)
	AssignPermissionsToOrganization(ctx context.Context, orgID string, permissionKeys []string) error
	RemovePermissionsFromOrganization(ctx context.Context, orgID string, permissionKeys []string) error
	GetOrganizationPermissions(ctx context.Context, orgID string) ([]Permission, error)
	ListOrganizations(ctx context.Context) ([]Organization, error)
	GetOrganization(ctx context.Context, id string) (*Organization, error)
	GetOrganizationByName(ctx context.Context, name string) (*Organization, error)

	// User-Organization
	CreateUserWithOrganization(ctx context.Context, email string, organization string) (*User, error)
	GetUserOrganization(ctx context.Context, userID string) (string, error)

	// Enhanced Permission Resolution
	GetAllUserPermissions(ctx context.Context, userID string) ([]Permission, error)
	HasAnyPermission(ctx context.Context, userID string, permissionKeys []string) (bool, error)

	// Bulk Group Operations
	AddUsersToGroup(ctx context.Context, groupID string, userIDs []string) error
	RemoveUsersFromGroup(ctx context.Context, groupID string, userIDs []string) error
	GetGroupMembers(ctx context.Context, groupID string) ([]User, error)

	// Bootstrap
	Bootstrap(ctx context.Context, superAdminEmail string, superAdminPassword string, organizations ...map[string][]string) error

	// Password Auth
	RegisterWithPassword(ctx context.Context, email string, plainPassword string) (*User, error)
	LoginWithPassword(ctx context.Context, email string, plainPassword string) (*User, error)
	SetPassword(ctx context.Context, userID string, plainPassword string) error
	ChangePassword(ctx context.Context, userID string, currentPassword string, newPassword string) error
	HasPassword(ctx context.Context, userID string) (bool, error)
	CreatePasswordReset(ctx context.Context, email string) (rawToken string, expiresAt time.Time, err error)
	ResetPassword(ctx context.Context, rawToken string, newPassword string) error
}

Store defines the contract for persisting and retrieving auth data.

type TokenPair

type TokenPair struct {
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
}

TokenPair holds the JWT access and refresh tokens.

func GenerateTokenPair

func GenerateTokenPair(cfg Config, user *User, permissions []string, groups []string, meta map[string]any) (*TokenPair, error)

GenerateTokenPair creates a signed access token and refresh token for the given user. meta is optional — pass nil or a map of custom claims to embed in the access token (e.g. {"college_id": "xyz"}).

func RefreshTokenPair added in v2.0.4

func RefreshTokenPair(ctx context.Context, cfg Config, store Store, refreshToken string) (*TokenPair, error)

RefreshTokenPair validates a refresh token and issues a new token pair. Fetches the latest user data and permissions from the store. Returns ErrUserNotFound if the user no longer exists.

type User

type User struct {
	ID           string    `json:"id"`
	Email        string    `json:"email"`
	Organization string    `json:"organization,omitempty"` // e.g. "trainer", "student"
	CreatedAt    time.Time `json:"created_at"`
}

User represents an authenticated user.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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