backupauth

package
v0.0.11 Latest Latest
Warning

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

Go to latest
Published: Jan 3, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

README

Backup Authentication & Recovery Plugin

Enterprise-grade backup authentication and account recovery system with multiple verification methods.

Overview

The Backup Authentication plugin provides comprehensive account recovery mechanisms to ensure users can regain access to their accounts even when they lose their primary authentication credentials (devices, passwords, etc.).

Features

Core Recovery Methods
  1. Recovery Codes (10+ one-time use codes)
  2. Security Questions (customizable challenge questions)
  3. Trusted Contacts (emergency contact verification)
  4. Email Verification (code-based verification)
  5. SMS Verification (code-based verification)
  6. Video Verification (live video session with admin)
  7. Document Verification (ID upload with OCR/AI verification)
Multi-Step Recovery Flows
  • Risk-based step requirements (low, medium, high risk)
  • Configurable minimum steps
  • User choice between available methods
  • Session-based recovery process
Security Features
  • Risk Assessment: Analyze device, location, IP, velocity, history
  • Rate Limiting: Prevent brute-force recovery attempts
  • Admin Review: Optional manual approval for high-risk recoveries
  • Audit Trail: Immutable logs of all recovery attempts
  • Session Expiry: Time-limited recovery sessions
Multi-Tenancy Support
  • Organization-scoped configurations
  • Per-organization recovery settings
  • Tenant isolation for all recovery data

Installation

import (
	"github.com/xraph/authsome"
	"github.com/xraph/authsome/plugins/enterprise/backupauth"
)

func main() {
	auth := authsome.New()
	
	// Register plugin
	plugin := backupauth.NewPlugin()
	auth.RegisterPlugin(plugin)
	
	// Configure providers (optional)
	plugin.SetEmailProvider(myEmailProvider)
	plugin.SetSMSProvider(mySMSProvider)
	plugin.SetDocumentProvider(myDocProvider)
	
	auth.Mount("/auth", app)
}

Configuration

YAML Configuration
auth:
  backupauth:
    enabled: true
    
    # Recovery codes
    recoveryCodes:
      enabled: true
      codeCount: 10
      codeLength: 12
      autoRegenerate: true
      format: "alphanumeric"
      
    # Security questions
    securityQuestions:
      enabled: true
      minimumQuestions: 3
      requiredToRecover: 2
      allowCustomQuestions: true
      caseSensitive: false
      maxAttempts: 3
      lockoutDuration: 30m
      
    # Trusted contacts
    trustedContacts:
      enabled: true
      minimumContacts: 1
      maximumContacts: 5
      requiredToRecover: 1
      requireVerification: true
      verificationExpiry: 168h # 7 days
      cooldownPeriod: 1h
      
    # Email verification
    emailVerification:
      enabled: true
      codeExpiry: 15m
      codeLength: 6
      maxAttempts: 5
      
    # SMS verification
    smsVerification:
      enabled: true
      codeExpiry: 10m
      codeLength: 6
      maxAttempts: 3
      provider: "twilio"
      maxSmsPerDay: 5
      cooldownPeriod: 5m
      
    # Video verification
    videoVerification:
      enabled: false # Enterprise feature
      provider: "zoom"
      requireScheduling: true
      minScheduleAdvance: 2h
      sessionDuration: 30m
      requireLivenessCheck: true
      livenessThreshold: 0.85
      recordSessions: true
      recordingRetention: 2160h # 90 days
      requireAdminReview: true
      
    # Document verification
    documentVerification:
      enabled: false # Enterprise feature
      provider: "stripe_identity"
      acceptedDocuments: ["passport", "drivers_license", "national_id"]
      requireSelfie: true
      requireBothSides: true
      minConfidenceScore: 0.85
      requireManualReview: false
      storageProvider: "s3"
      storagePath: "/var/lib/authsome/backup/documents"
      retentionPeriod: 2160h # 90 days
      encryptAtRest: true
      
    # Multi-step recovery
    multiStepRecovery:
      enabled: true
      minimumSteps: 2
      lowRiskSteps: ["recovery_codes", "email_verification"]
      mediumRiskSteps: ["security_questions", "email_verification", "sms_verification"]
      highRiskSteps: ["security_questions", "trusted_contact", "video_verification"]
      allowUserChoice: true
      sessionExpiry: 30m
      allowStepSkip: false
      requireAdminApproval: false
      
    # Risk assessment
    riskAssessment:
      enabled: true
      newDeviceWeight: 0.25
      newLocationWeight: 0.20
      newIpWeight: 0.15
      velocityWeight: 0.20
      historyWeight: 0.20
      lowRiskThreshold: 30.0
      mediumRiskThreshold: 60.0
      highRiskThreshold: 80.0
      blockHighRisk: false
      requireReviewAbove: 85.0
      
    # Rate limiting
    rateLimiting:
      enabled: true
      maxAttemptsPerHour: 5
      maxAttemptsPerDay: 10
      lockoutAfterAttempts: 5
      lockoutDuration: 24h
      exponentialBackoff: true
      maxAttemptsPerIp: 20
      ipCooldownPeriod: 1h
      
    # Audit
    audit:
      enabled: true
      logAllAttempts: true
      logSuccessful: true
      logFailed: true
      immutableLogs: true
      retentionDays: 2555 # 7 years
      archiveOldLogs: true
      archiveInterval: 2160h # 90 days
      logIpAddress: true
      logUserAgent: true
      logDeviceInfo: true
      
    # Notifications
    notifications:
      enabled: true
      notifyOnRecoveryStart: true
      notifyOnRecoveryComplete: true
      notifyOnRecoveryFailed: true
      notifyAdminOnHighRisk: true
      notifyAdminOnReviewNeeded: true
      channels: ["email"]
      securityOfficerEmail: "security@example.com"

Usage

1. Setup Recovery Methods (User Configuration)
Generate Recovery Codes
POST /auth/recovery-codes/generate
Authorization: Bearer <token>
Content-Type: application/json

{
  "count": 10,
  "format": "alphanumeric"
}

Response:

{
  "codes": [
    "ABCD-1234-EFGH",
    "IJKL-5678-MNOP",
    ...
  ],
  "count": 10,
  "generatedAt": "2024-01-01T00:00:00Z",
  "warning": "Store these codes securely. Each can only be used once."
}
Setup Security Questions
POST /auth/security-questions/setup
Authorization: Bearer <token>
Content-Type: application/json

{
  "questions": [
    {
      "questionId": 1,
      "answer": "fluffy"
    },
    {
      "questionId": 4,
      "answer": "springfield elementary"
    },
    {
      "customText": "What is your favorite programming language?",
      "answer": "golang"
    }
  ]
}
Add Trusted Contact
POST /auth/trusted-contacts/add
Authorization: Bearer <token>
Content-Type: application/json

{
  "name": "John Doe",
  "email": "john@example.com",
  "relationship": "friend"
}
2. Account Recovery Flow
Step 1: Start Recovery
POST /auth/recovery/start
Content-Type: application/json

{
  "userId": "user_123",
  "email": "user@example.com",
  "preferredMethod": "recovery_codes"
}

Response:

{
  "sessionId": "session_abc123",
  "status": "pending",
  "availableMethods": [
    "recovery_codes",
    "security_questions",
    "email_verification",
    "trusted_contact"
  ],
  "requiredSteps": 2,
  "completedSteps": 0,
  "expiresAt": "2024-01-01T00:30:00Z",
  "riskScore": 45.5,
  "requiresReview": false
}
Step 2: Continue with Method
POST /auth/recovery/continue
Content-Type: application/json

{
  "sessionId": "session_abc123",
  "method": "recovery_codes"
}

Response:

{
  "sessionId": "session_abc123",
  "method": "recovery_codes",
  "currentStep": 1,
  "totalSteps": 2,
  "instructions": "Enter one of your recovery codes",
  "data": {},
  "expiresAt": "2024-01-01T00:30:00Z"
}
Step 3: Verify Recovery Code
POST /auth/recovery-codes/verify
Content-Type: application/json

{
  "sessionId": "session_abc123",
  "code": "ABCD-1234-EFGH"
}

Response:

{
  "valid": true,
  "message": "Recovery code verified successfully"
}
Step 4: Complete Additional Steps (if required)

Repeat steps 2-3 for each required method.

Step 5: Complete Recovery
POST /auth/recovery/complete
Content-Type: application/json

{
  "sessionId": "session_abc123"
}

Response:

{
  "sessionId": "session_abc123",
  "status": "completed",
  "completedAt": "2024-01-01T00:25:00Z",
  "token": "recovery_token_xyz",
  "message": "Recovery completed successfully. Use the token to reset your password."
}
3. Admin Operations
List Recovery Sessions
GET /auth/admin/sessions?status=pending&requiresReview=true
Authorization: Bearer <admin_token>
Approve Recovery
POST /auth/admin/sessions/{sessionId}/approve
Authorization: Bearer <admin_token>
Content-Type: application/json

{
  "notes": "Verified identity via video call"
}
Get Recovery Statistics
GET /auth/admin/stats?startDate=2024-01-01&endDate=2024-01-31
Authorization: Bearer <admin_token>

Security Considerations

Best Practices
  1. Always enable rate limiting to prevent brute-force attacks
  2. Use multi-step recovery for sensitive accounts
  3. Enable audit logging and monitor recovery attempts
  4. Configure risk assessment to detect suspicious activity
  5. Require admin review for high-risk recoveries
  6. Store recovery codes securely (encrypted at rest)
  7. Implement MFA before allowing recovery code generation
Risk Factors

The plugin assesses risk based on:

  • Device: New or unknown device
  • Location: Geographic anomaly
  • IP Address: Known VPN/proxy, suspicious region
  • Velocity: Multiple rapid attempts
  • History: Past recovery failures
Admin Review

Enable admin review for:

  • Risk score > 85
  • First recovery attempt
  • Multiple failed verifications
  • Document verification required
  • Video verification enabled

Provider Integration

Email Provider
type MyEmailProvider struct{}

func (p *MyEmailProvider) SendVerificationEmail(ctx context.Context, to, code string, expiresIn time.Duration) error {
	// Implement email sending
	return nil
}

func (p *MyEmailProvider) SendRecoveryNotification(ctx context.Context, to, subject, body string) error {
	// Implement notification sending
	return nil
}

// Register provider
plugin.SetEmailProvider(&MyEmailProvider{})
SMS Provider
type MySMSProvider struct{}

func (p *MySMSProvider) SendVerificationSMS(ctx context.Context, to, code string, expiresIn time.Duration) error {
	// Implement SMS sending via Twilio, Vonage, etc.
	return nil
}

// Register provider
plugin.SetSMSProvider(&MySMSProvider{})
Document Verification Provider
type MyDocumentProvider struct{}

func (p *MyDocumentProvider) VerifyDocument(ctx context.Context, req *DocumentVerificationRequest) (*DocumentVerificationResult, error) {
	// Integrate with Stripe Identity, Onfido, Jumio, etc.
	return &DocumentVerificationResult{
		VerificationID: "verify_123",
		Status: "verified",
		ConfidenceScore: 0.95,
	}, nil
}

// Register provider
plugin.SetDocumentProvider(&MyDocumentProvider{})

Testing

func TestRecoveryFlow(t *testing.T) {
	// Initialize test environment
	plugin := backupauth.NewPlugin()
	
	// Setup test user with recovery methods
	userID := xid.New()
	orgID := "test_org"
	
	// Generate recovery codes
	codes, err := plugin.Service().GenerateRecoveryCodes(ctx, userID, orgID, &GenerateRecoveryCodesRequest{
		Count: 10,
	})
	assert.NoError(t, err)
	assert.Equal(t, 10, len(codes.Codes))
	
	// Start recovery
	session, err := plugin.Service().StartRecovery(ctx, &StartRecoveryRequest{
		UserID: userID.String(),
	})
	assert.NoError(t, err)
	assert.NotNil(t, session)
	
	// Verify recovery code
	result, err := plugin.Service().VerifyRecoveryCode(ctx, &VerifyRecoveryCodeRequest{
		SessionID: session.SessionID,
		Code: codes.Codes[0],
	})
	assert.NoError(t, err)
	assert.True(t, result.Valid)
}

API Reference

See API.md for complete API documentation.

Compliance

The plugin is designed to support:

  • GDPR: Right to data portability, audit trail
  • SOC 2: Security controls, audit logging
  • HIPAA: Access controls, audit trail
  • PCI DSS: Strong authentication, audit logging

Performance

  • Database Queries: Optimized with indexes on user_id, organization_id, status
  • Session Storage: In-database with automatic cleanup
  • Rate Limiting: In-memory + Redis for distributed systems
  • Audit Logging: Async writes to avoid blocking
  • Scalability: Supports millions of users per organization

Troubleshooting

Common Issues

Recovery session expired:

  • Increase multiStepRecovery.sessionExpiry in config
  • User needs to start a new recovery session

Rate limit exceeded:

  • User attempted recovery too many times
  • Wait for lockout period to expire
  • Admin can manually reset lockout

Method not available:

  • User hasn't configured the recovery method
  • Method is disabled in configuration
  • Check availableMethods in start recovery response

High risk detected:

  • Risk score exceeds threshold
  • Enable admin review or video verification
  • Adjust risk assessment weights in config

Roadmap

  • WebAuthn recovery (biometric fallback)
  • Hardware security key recovery
  • Social recovery (friends verify identity)
  • Blockchain-based recovery
  • AI-powered risk assessment
  • Machine learning anomaly detection
  • Integration with identity verification services
  • Mobile app deep linking

License

Enterprise Plugin - See LICENSE file

Support

For issues and feature requests, please contact enterprise-support@authsome.dev

Contributing

See CONTRIBUTING.md for guidelines on contributing to this plugin.

Documentation

Index

Constants

View Source
const (
	PluginID      = "backupauth"
	PluginName    = "Backup Authentication & Recovery"
	PluginVersion = "1.0.0"
)

Variables

View Source
var (
	// Recovery session errors
	ErrRecoverySessionNotFound      = errors.New("recovery session not found")
	ErrRecoverySessionExpired       = errors.New("recovery session expired")
	ErrRecoverySessionCancelled     = errors.New("recovery session cancelled")
	ErrRecoverySessionInProgress    = errors.New("recovery session already in progress")
	ErrRecoverySessionCompleted     = errors.New("recovery session already completed")
	ErrRecoverySessionLocked        = errors.New("recovery session locked due to too many attempts")
	ErrRecoveryMethodNotEnabled     = errors.New("recovery method not enabled")
	ErrRecoveryStepRequired         = errors.New("recovery step required")
	ErrRecoveryStepAlreadyCompleted = errors.New("recovery step already completed")

	// Recovery codes errors
	ErrInvalidRecoveryCode      = errors.New("invalid recovery code")
	ErrRecoveryCodeAlreadyUsed  = errors.New("recovery code already used")
	ErrRecoveryCodeExpired      = errors.New("recovery code expired")
	ErrNoRecoveryCodesAvailable = errors.New("no recovery codes available")

	// Security questions errors
	ErrSecurityQuestionNotFound      = errors.New("security question not found")
	ErrInvalidSecurityAnswer         = errors.New("invalid security answer")
	ErrSecurityQuestionAlreadyExists = errors.New("security question already exists")
	ErrInsufficientSecurityQuestions = errors.New("insufficient security questions configured")
	ErrSecurityQuestionLocked        = errors.New("security question locked due to failed attempts")
	ErrCommonAnswer                  = errors.New("answer is too common, please choose a more unique answer")
	ErrAnswerTooShort                = errors.New("answer is too short")
	ErrAnswerTooLong                 = errors.New("answer is too long")

	// Trusted contacts errors
	ErrTrustedContactNotFound           = errors.New("trusted contact not found")
	ErrTrustedContactNotVerified        = errors.New("trusted contact not verified")
	ErrTrustedContactAlreadyExists      = errors.New("trusted contact already exists")
	ErrInsufficientTrustedContacts      = errors.New("insufficient trusted contacts configured")
	ErrTrustedContactLimitExceeded      = errors.New("trusted contact limit exceeded")
	ErrTrustedContactCooldown           = errors.New("trusted contact notification cooldown active")
	ErrTrustedContactNotificationFailed = errors.New("failed to notify trusted contact")

	// Email/SMS verification errors
	ErrInvalidVerificationCode         = errors.New("invalid verification code")
	ErrVerificationCodeExpired         = errors.New("verification code expired")
	ErrVerificationCodeAlreadyUsed     = errors.New("verification code already used")
	ErrMaxVerificationAttemptsExceeded = errors.New("maximum verification attempts exceeded")
	ErrEmailNotVerified                = errors.New("email not verified")
	ErrPhoneNotVerified                = errors.New("phone not verified")

	// Video verification errors
	ErrVideoSessionNotFound     = errors.New("video session not found")
	ErrVideoSessionNotScheduled = errors.New("video session not scheduled")
	ErrVideoSessionExpired      = errors.New("video session expired")
	ErrLivenessCheckFailed      = errors.New("liveness check failed")
	ErrVideoVerificationFailed  = errors.New("video verification failed")
	ErrVideoVerificationPending = errors.New("video verification pending review")

	// Document verification errors
	ErrDocumentVerificationNotFound = errors.New("document verification not found")
	ErrInvalidDocumentType          = errors.New("invalid document type")
	ErrDocumentVerificationFailed   = errors.New("document verification failed")
	ErrDocumentVerificationPending  = errors.New("document verification pending review")
	ErrDocumentExpired              = errors.New("document expired")
	ErrDocumentImageRequired        = errors.New("document image required")
	ErrSelfieRequired               = errors.New("selfie required")
	ErrConfidenceScoreTooLow        = errors.New("confidence score too low")

	// Rate limiting errors
	ErrRateLimitExceeded = errors.New("rate limit exceeded")
	ErrTooManyAttempts   = errors.New("too many recovery attempts")
	ErrAccountLocked     = errors.New("account locked due to too many recovery attempts")
	ErrCooldownActive    = errors.New("cooldown period active, please wait before retrying")

	// Risk assessment errors
	ErrHighRiskDetected    = errors.New("high risk detected, additional verification required")
	ErrRiskScoreTooHigh    = errors.New("risk score too high, recovery blocked")
	ErrAdminReviewRequired = errors.New("admin review required for recovery")

	// Configuration errors
	ErrRecoveryNotConfigured = errors.New("backup recovery not configured")
	ErrInvalidConfiguration  = errors.New("invalid configuration")
	ErrProviderNotConfigured = errors.New("provider not configured")

	// Authorization errors
	ErrUnauthorized     = errors.New("unauthorized")
	ErrInvalidSession   = errors.New("invalid session")
	ErrPermissionDenied = errors.New("permission denied")

	// Validation errors
	ErrInvalidInput         = errors.New("invalid input")
	ErrMissingRequiredField = errors.New("missing required field")
	ErrInvalidEmail         = errors.New("invalid email")
	ErrInvalidPhone         = errors.New("invalid phone")

	// Provider errors
	ErrProviderError       = errors.New("provider error")
	ErrProviderTimeout     = errors.New("provider timeout")
	ErrProviderUnavailable = errors.New("provider unavailable")
	ErrProviderAuthFailed  = errors.New("provider authentication failed")

	// Storage errors
	ErrStorageError     = errors.New("storage error")
	ErrFileUploadFailed = errors.New("file upload failed")
	ErrFileNotFound     = errors.New("file not found")
	ErrEncryptionFailed = errors.New("encryption failed")
	ErrDecryptionFailed = errors.New("decryption failed")
)

Common errors

Functions

This section is empty.

Types

type AddTrustedContactRequest

type AddTrustedContactRequest struct {
	Name         string `json:"name"`
	Email        string `json:"email,omitempty"`
	Phone        string `json:"phone,omitempty"`
	Relationship string `json:"relationship,omitempty"`
}

AddTrustedContactRequest adds a trusted contact

type AddTrustedContactResponse

type AddTrustedContactResponse struct {
	ContactID xid.ID    `json:"contactId"`
	Name      string    `json:"name"`
	Email     string    `json:"email,omitempty"`
	Phone     string    `json:"phone,omitempty"`
	Verified  bool      `json:"verified"`
	AddedAt   time.Time `json:"addedAt"`
	Message   string    `json:"message"`
}

AddTrustedContactResponse returns added contact

type ApproveRecoveryRequest

type ApproveRecoveryRequest struct {
	SessionID xid.ID `json:"sessionId"`
	Notes     string `json:"notes,omitempty"`
}

ApproveRecoveryRequest approves a recovery session (admin)

type ApproveRecoveryResponse

type ApproveRecoveryResponse struct {
	SessionID  xid.ID    `json:"sessionId"`
	Approved   bool      `json:"approved"`
	ApprovedAt time.Time `json:"approvedAt"`
	Message    string    `json:"message"`
}

ApproveRecoveryResponse returns approval result

type AuditConfig

type AuditConfig struct {
	Enabled        bool `json:"enabled" yaml:"enabled"`
	LogAllAttempts bool `json:"logAllAttempts" yaml:"logAllAttempts"`
	LogSuccessful  bool `json:"logSuccessful" yaml:"logSuccessful"`
	LogFailed      bool `json:"logFailed" yaml:"logFailed"`

	// Immutability
	ImmutableLogs bool `json:"immutableLogs" yaml:"immutableLogs"`

	// Retention
	RetentionDays   int           `json:"retentionDays" yaml:"retentionDays"`
	ArchiveOldLogs  bool          `json:"archiveOldLogs" yaml:"archiveOldLogs"`
	ArchiveInterval time.Duration `json:"archiveInterval" yaml:"archiveInterval"`

	// Detailed logging
	LogIPAddress  bool `json:"logIpAddress" yaml:"logIpAddress"`
	LogUserAgent  bool `json:"logUserAgent" yaml:"logUserAgent"`
	LogDeviceInfo bool `json:"logDeviceInfo" yaml:"logDeviceInfo"`
}

AuditConfig configures audit logging

type BackupAuthCodesResponse

type BackupAuthCodesResponse struct {
	Codes []string `json:"codes" example:"[\"code1\",\"code2\"]"`
}

type BackupAuthConfigResponse

type BackupAuthConfigResponse struct {
	Config interface{} `json:"config"`
}

type BackupAuthContactResponse

type BackupAuthContactResponse struct {
	ID string `json:"id" example:"contact_123"`
}

type BackupAuthContactsResponse

type BackupAuthContactsResponse struct {
	Contacts []interface{} `json:"contacts"`
}

type BackupAuthDocumentResponse

type BackupAuthDocumentResponse struct {
	ID string `json:"id" example:"doc_123"`
}

type BackupAuthQuestionsResponse

type BackupAuthQuestionsResponse struct {
	Questions []string `json:"questions"`
}

type BackupAuthRecoveryResponse

type BackupAuthRecoveryResponse struct {
	SessionID string `json:"session_id" example:"session_123"`
}

type BackupAuthSessionsResponse

type BackupAuthSessionsResponse struct {
	Sessions []interface{} `json:"sessions"`
}

type BackupAuthStatsResponse

type BackupAuthStatsResponse struct {
	Stats interface{} `json:"stats"`
}

type BackupAuthStatusResponse

type BackupAuthStatusResponse struct {
	Status string `json:"status" example:"success"`
}

DTOs for backupauth routes

type BackupAuthVideoResponse

type BackupAuthVideoResponse struct {
	SessionID string `json:"session_id" example:"video_123"`
}

type BunRepository

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

BunRepository implements Repository using Bun ORM

func (*BunRepository) CountActiveTrustedContacts

func (r *BunRepository) CountActiveTrustedContacts(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID) (int, error)

func (*BunRepository) CreateDocumentVerification

func (r *BunRepository) CreateDocumentVerification(ctx context.Context, dv *DocumentVerification) error

func (*BunRepository) CreateRecoveryCodeUsage

func (r *BunRepository) CreateRecoveryCodeUsage(ctx context.Context, rcu *RecoveryCodeUsage) error

func (*BunRepository) CreateRecoveryConfig

func (r *BunRepository) CreateRecoveryConfig(ctx context.Context, rc *RecoveryConfiguration) error

func (*BunRepository) CreateRecoveryLog

func (r *BunRepository) CreateRecoveryLog(ctx context.Context, log *RecoveryAttemptLog) error

func (*BunRepository) CreateRecoverySession

func (r *BunRepository) CreateRecoverySession(ctx context.Context, rs *RecoverySession) error

func (*BunRepository) CreateSecurityQuestion

func (r *BunRepository) CreateSecurityQuestion(ctx context.Context, q *SecurityQuestion) error

func (*BunRepository) CreateTrustedContact

func (r *BunRepository) CreateTrustedContact(ctx context.Context, tc *TrustedContact) error

func (*BunRepository) CreateVideoSession

func (r *BunRepository) CreateVideoSession(ctx context.Context, vs *VideoVerificationSession) error

func (*BunRepository) DeleteDocumentVerification

func (r *BunRepository) DeleteDocumentVerification(ctx context.Context, id xid.ID) error

func (*BunRepository) DeleteRecoverySession

func (r *BunRepository) DeleteRecoverySession(ctx context.Context, id xid.ID) error

func (*BunRepository) DeleteSecurityQuestion

func (r *BunRepository) DeleteSecurityQuestion(ctx context.Context, id xid.ID) error

func (*BunRepository) DeleteTrustedContact

func (r *BunRepository) DeleteTrustedContact(ctx context.Context, id xid.ID) error

func (*BunRepository) DeleteVideoSession

func (r *BunRepository) DeleteVideoSession(ctx context.Context, id xid.ID) error

func (*BunRepository) ExpireRecoverySessions

func (r *BunRepository) ExpireRecoverySessions(ctx context.Context, before time.Time) (int, error)

func (*BunRepository) GetActiveRecoverySession

func (r *BunRepository) GetActiveRecoverySession(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID) (*RecoverySession, error)

func (*BunRepository) GetDocumentVerification

func (r *BunRepository) GetDocumentVerification(ctx context.Context, id xid.ID) (*DocumentVerification, error)

func (*BunRepository) GetDocumentVerificationByRecovery

func (r *BunRepository) GetDocumentVerificationByRecovery(ctx context.Context, recoveryID xid.ID) (*DocumentVerification, error)

func (*BunRepository) GetRecentRecoveryAttempts

func (r *BunRepository) GetRecentRecoveryAttempts(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID, since time.Time) (int, error)

func (*BunRepository) GetRecoveryCodeUsage

func (r *BunRepository) GetRecoveryCodeUsage(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID, codeHash string) (*RecoveryCodeUsage, error)

func (*BunRepository) GetRecoveryConfig

func (r *BunRepository) GetRecoveryConfig(ctx context.Context, appID xid.ID, userOrganizationID *xid.ID) (*RecoveryConfiguration, error)

func (*BunRepository) GetRecoveryLogs

func (r *BunRepository) GetRecoveryLogs(ctx context.Context, recoveryID xid.ID) ([]*RecoveryAttemptLog, error)

func (*BunRepository) GetRecoveryLogsByUser

func (r *BunRepository) GetRecoveryLogsByUser(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID, limit int) ([]*RecoveryAttemptLog, error)

func (*BunRepository) GetRecoverySession

func (r *BunRepository) GetRecoverySession(ctx context.Context, id xid.ID) (*RecoverySession, error)

func (*BunRepository) GetRecoveryStats

func (r *BunRepository) GetRecoveryStats(ctx context.Context, appID xid.ID, userOrganizationID *xid.ID, startDate, endDate time.Time) (map[string]interface{}, error)

func (*BunRepository) GetSecurityQuestion

func (r *BunRepository) GetSecurityQuestion(ctx context.Context, id xid.ID) (*SecurityQuestion, error)

func (*BunRepository) GetSecurityQuestionsByUser

func (r *BunRepository) GetSecurityQuestionsByUser(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID) ([]*SecurityQuestion, error)

func (*BunRepository) GetTrustedContact

func (r *BunRepository) GetTrustedContact(ctx context.Context, id xid.ID) (*TrustedContact, error)

func (*BunRepository) GetTrustedContactByToken

func (r *BunRepository) GetTrustedContactByToken(ctx context.Context, token string) (*TrustedContact, error)

func (*BunRepository) GetTrustedContactsByUser

func (r *BunRepository) GetTrustedContactsByUser(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID) ([]*TrustedContact, error)

func (*BunRepository) GetVideoSession

func (r *BunRepository) GetVideoSession(ctx context.Context, id xid.ID) (*VideoVerificationSession, error)

func (*BunRepository) GetVideoSessionByRecovery

func (r *BunRepository) GetVideoSessionByRecovery(ctx context.Context, recoveryID xid.ID) (*VideoVerificationSession, error)

func (*BunRepository) IncrementQuestionFailedAttempts

func (r *BunRepository) IncrementQuestionFailedAttempts(ctx context.Context, id xid.ID) error

func (*BunRepository) IncrementSessionAttempts

func (r *BunRepository) IncrementSessionAttempts(ctx context.Context, id xid.ID) error

func (*BunRepository) ListRecoverySessions

func (r *BunRepository) ListRecoverySessions(ctx context.Context, appID xid.ID, userOrganizationID *xid.ID, status RecoveryStatus, requiresReview bool, limit, offset int) ([]*RecoverySession, int, error)

func (*BunRepository) UpdateDocumentVerification

func (r *BunRepository) UpdateDocumentVerification(ctx context.Context, dv *DocumentVerification) error

func (*BunRepository) UpdateRecoveryConfig

func (r *BunRepository) UpdateRecoveryConfig(ctx context.Context, rc *RecoveryConfiguration) error

func (*BunRepository) UpdateRecoverySession

func (r *BunRepository) UpdateRecoverySession(ctx context.Context, rs *RecoverySession) error

func (*BunRepository) UpdateSecurityQuestion

func (r *BunRepository) UpdateSecurityQuestion(ctx context.Context, q *SecurityQuestion) error

func (*BunRepository) UpdateTrustedContact

func (r *BunRepository) UpdateTrustedContact(ctx context.Context, tc *TrustedContact) error

func (*BunRepository) UpdateVideoSession

func (r *BunRepository) UpdateVideoSession(ctx context.Context, vs *VideoVerificationSession) error

type CancelRecoveryRequest

type CancelRecoveryRequest struct {
	SessionID xid.ID `json:"sessionId"`
	Reason    string `json:"reason,omitempty"`
}

CancelRecoveryRequest cancels a recovery session

type CompleteRecoveryRequest

type CompleteRecoveryRequest struct {
	SessionID xid.ID `json:"sessionId"`
}

CompleteRecoveryRequest finalizes recovery

type CompleteRecoveryResponse

type CompleteRecoveryResponse struct {
	SessionID   xid.ID         `json:"sessionId"`
	Status      RecoveryStatus `json:"status"`
	CompletedAt time.Time      `json:"completedAt"`
	Token       string         `json:"token,omitempty"` // Temporary token to reset password
	Message     string         `json:"message"`
}

CompleteRecoveryResponse returns recovery completion details

type CompleteVideoSessionRequest

type CompleteVideoSessionRequest struct {
	VideoSessionID     xid.ID  `json:"videoSessionId"`
	VerificationResult string  `json:"verificationResult"` // approved, rejected
	Notes              string  `json:"notes,omitempty"`
	LivenessPassed     bool    `json:"livenessPassed"`
	LivenessScore      float64 `json:"livenessScore,omitempty"`
}

CompleteVideoSessionRequest completes video verification (admin)

type CompleteVideoSessionResponse

type CompleteVideoSessionResponse struct {
	VideoSessionID xid.ID    `json:"videoSessionId"`
	Result         string    `json:"result"`
	CompletedAt    time.Time `json:"completedAt"`
	Message        string    `json:"message"`
}

CompleteVideoSessionResponse returns completion result

type Config

type Config struct {
	// Enable backup authentication plugin
	Enabled bool `json:"enabled" yaml:"enabled"`

	// Recovery codes configuration
	RecoveryCodes RecoveryCodesConfig `json:"recoveryCodes" yaml:"recoveryCodes"`

	// Security questions configuration
	SecurityQuestions SecurityQuestionsConfig `json:"securityQuestions" yaml:"securityQuestions"`

	// Trusted contacts configuration
	TrustedContacts TrustedContactsConfig `json:"trustedContacts" yaml:"trustedContacts"`

	// Email verification fallback
	EmailVerification EmailVerificationConfig `json:"emailVerification" yaml:"emailVerification"`

	// SMS verification fallback
	SMSVerification SMSVerificationConfig `json:"smsVerification" yaml:"smsVerification"`

	// Video verification
	VideoVerification VideoVerificationConfig `json:"videoVerification" yaml:"videoVerification"`

	// Document verification
	DocumentVerification DocumentVerificationConfig `json:"documentVerification" yaml:"documentVerification"`

	// Multi-step recovery flows
	MultiStepRecovery MultiStepRecoveryConfig `json:"multiStepRecovery" yaml:"multiStepRecovery"`

	// Risk assessment
	RiskAssessment RiskAssessmentConfig `json:"riskAssessment" yaml:"riskAssessment"`

	// Rate limiting
	RateLimiting RateLimitingConfig `json:"rateLimiting" yaml:"rateLimiting"`

	// Audit and logging
	Audit AuditConfig `json:"audit" yaml:"audit"`

	// Notifications
	Notifications NotificationsConfig `json:"notifications" yaml:"notifications"`
}

Config holds the backup authentication plugin configuration

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig returns the default backup authentication configuration

func (*Config) Validate

func (c *Config) Validate()

Validate ensures configuration has sensible defaults

type ContinueRecoveryRequest

type ContinueRecoveryRequest struct {
	SessionID xid.ID         `json:"sessionId"`
	Method    RecoveryMethod `json:"method"`
}

ContinueRecoveryRequest continues a recovery session with method selection

type ContinueRecoveryResponse

type ContinueRecoveryResponse struct {
	SessionID    xid.ID                 `json:"sessionId"`
	Method       RecoveryMethod         `json:"method"`
	CurrentStep  int                    `json:"currentStep"`
	TotalSteps   int                    `json:"totalSteps"`
	Instructions string                 `json:"instructions"`
	Data         map[string]interface{} `json:"data,omitempty"`
	ExpiresAt    time.Time              `json:"expiresAt"`
}

ContinueRecoveryResponse provides next steps

type DefaultProviderRegistry

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

DefaultProviderRegistry provides default implementations

func NewDefaultProviderRegistry

func NewDefaultProviderRegistry() *DefaultProviderRegistry

NewDefaultProviderRegistry creates a new provider registry

func (*DefaultProviderRegistry) DocumentProvider

func (r *DefaultProviderRegistry) DocumentProvider() DocumentProvider

func (*DefaultProviderRegistry) EmailProvider

func (r *DefaultProviderRegistry) EmailProvider() EmailProvider

func (*DefaultProviderRegistry) NotificationProvider

func (r *DefaultProviderRegistry) NotificationProvider() NotificationProvider

func (*DefaultProviderRegistry) SMSProvider

func (r *DefaultProviderRegistry) SMSProvider() SMSProvider

func (*DefaultProviderRegistry) SetDocumentProvider

func (r *DefaultProviderRegistry) SetDocumentProvider(provider DocumentProvider)

func (*DefaultProviderRegistry) SetEmailProvider

func (r *DefaultProviderRegistry) SetEmailProvider(provider EmailProvider)

func (*DefaultProviderRegistry) SetNotificationProvider

func (r *DefaultProviderRegistry) SetNotificationProvider(provider NotificationProvider)

func (*DefaultProviderRegistry) SetSMSProvider

func (r *DefaultProviderRegistry) SetSMSProvider(provider SMSProvider)

func (*DefaultProviderRegistry) SetVideoProvider

func (r *DefaultProviderRegistry) SetVideoProvider(provider VideoProvider)

func (*DefaultProviderRegistry) VideoProvider

func (r *DefaultProviderRegistry) VideoProvider() VideoProvider

type DocumentProvider

type DocumentProvider interface {
	VerifyDocument(ctx context.Context, req *DocumentVerificationRequest) (*DocumentVerificationResult, error)
	GetVerificationStatus(ctx context.Context, verificationID string) (*DocumentVerificationResult, error)
}

DocumentProvider handles document verification

type DocumentVerification

type DocumentVerification struct {
	schema.AuditableModel
	bun.BaseModel `bun:"table:backup_document_verifications,alias:bdv"`

	ID                 xid.ID  `bun:"id,pk,type:varchar(20)"`
	RecoveryID         xid.ID  `bun:"recovery_id,notnull,type:varchar(20)"`
	UserID             xid.ID  `bun:"user_id,notnull,type:varchar(20)"`
	AppID              xid.ID  `bun:"app_id,notnull,type:varchar(20)"`       // Platform app (required)
	UserOrganizationID *xid.ID `bun:"user_organization_id,type:varchar(20)"` // User-created org (optional)

	// Document details
	DocumentType   string `bun:"document_type,notnull"` // passport, drivers_license, national_id, etc.
	DocumentNumber string `bun:"document_number"`       // Encrypted
	FrontImageURL  string `bun:"front_image_url"`
	BackImageURL   string `bun:"back_image_url"`
	SelfieURL      string `bun:"selfie_url"`

	// OCR/Extraction results
	ExtractedData map[string]interface{} `bun:"extracted_data,type:jsonb"`

	// Verification
	VerificationStatus string     `bun:"verification_status,notnull"` // pending, verified, rejected
	ConfidenceScore    float64    `bun:"confidence_score"`
	VerifiedAt         *time.Time `bun:"verified_at"`
	VerifiedBy         *xid.ID    `bun:"verified_by,type:varchar(20)"`

	// Provider integration (Stripe Identity, Onfido, etc.)
	ProviderName     string                 `bun:"provider_name"`
	ProviderID       string                 `bun:"provider_id"`
	ProviderResponse map[string]interface{} `bun:"provider_response,type:jsonb"`

	RejectionReason string    `bun:"rejection_reason"`
	ExpiresAt       time.Time `bun:"expires_at,notnull"`

	Metadata map[string]string `bun:"metadata,type:jsonb"`
}

DocumentVerification stores ID document uploads for verification Updated for V2 architecture: App → Environment → Organization

type DocumentVerificationConfig

type DocumentVerificationConfig struct {
	Enabled  bool   `json:"enabled" yaml:"enabled"`
	Provider string `json:"provider" yaml:"provider"` // stripe_identity, onfido, jumio

	// Accepted document types
	AcceptedDocuments []string `json:"acceptedDocuments" yaml:"acceptedDocuments"`

	// Requirements
	RequireSelfie    bool `json:"requireSelfie" yaml:"requireSelfie"`
	RequireBothSides bool `json:"requireBothSides" yaml:"requireBothSides"`

	// Verification
	MinConfidenceScore  float64 `json:"minConfidenceScore" yaml:"minConfidenceScore"`
	RequireManualReview bool    `json:"requireManualReview" yaml:"requireManualReview"`

	// Storage
	StorageProvider string        `json:"storageProvider" yaml:"storageProvider"` // s3, gcs, azure
	StoragePath     string        `json:"storagePath" yaml:"storagePath"`
	RetentionPeriod time.Duration `json:"retentionPeriod" yaml:"retentionPeriod"`

	// Encryption
	EncryptAtRest bool   `json:"encryptAtRest" yaml:"encryptAtRest"`
	EncryptionKey string `json:"encryptionKey" yaml:"encryptionKey"`
}

DocumentVerificationConfig configures document verification

type DocumentVerificationRequest

type DocumentVerificationRequest struct {
	UserID       xid.ID
	DocumentType string
	FrontImage   []byte
	BackImage    []byte
	Selfie       []byte
}

DocumentVerificationRequest contains document verification request

type DocumentVerificationResult

type DocumentVerificationResult struct {
	VerificationID   string
	Status           string // pending, verified, rejected
	ConfidenceScore  float64
	ExtractedData    map[string]interface{}
	ProviderResponse map[string]interface{}
	RejectionReason  string
}

DocumentVerificationResult contains verification result

type EmailProvider

type EmailProvider interface {
	SendVerificationEmail(ctx context.Context, to, code string, expiresIn time.Duration) error
	SendRecoveryNotification(ctx context.Context, to, subject, body string) error
}

EmailProvider handles email sending

type EmailVerificationConfig

type EmailVerificationConfig struct {
	Enabled     bool          `json:"enabled" yaml:"enabled"`
	CodeExpiry  time.Duration `json:"codeExpiry" yaml:"codeExpiry"`
	CodeLength  int           `json:"codeLength" yaml:"codeLength"`
	MaxAttempts int           `json:"maxAttempts" yaml:"maxAttempts"`

	// Require email ownership proof
	RequireEmailProof bool `json:"requireEmailProof" yaml:"requireEmailProof"`

	// Template configuration
	EmailTemplate string `json:"emailTemplate" yaml:"emailTemplate"`
	FromAddress   string `json:"fromAddress" yaml:"fromAddress"`
	FromName      string `json:"fromName" yaml:"fromName"`
}

EmailVerificationConfig configures email verification fallback

type ErrorResponse

type ErrorResponse struct {
	Error   string                 `json:"error"`
	Message string                 `json:"message"`
	Code    string                 `json:"code,omitempty"`
	Details map[string]interface{} `json:"details,omitempty"`
}

ErrorResponse represents an error response

type GenerateRecoveryCodesRequest

type GenerateRecoveryCodesRequest struct {
	Count  int    `json:"count,omitempty"`
	Format string `json:"format,omitempty"` // alphanumeric, numeric, hex
}

GenerateRecoveryCodesRequest generates new recovery codes

type GenerateRecoveryCodesResponse

type GenerateRecoveryCodesResponse struct {
	Codes       []string  `json:"codes"`
	Count       int       `json:"count"`
	GeneratedAt time.Time `json:"generatedAt"`
	Warning     string    `json:"warning"`
}

GenerateRecoveryCodesResponse returns generated codes

type GetDocumentVerificationRequest

type GetDocumentVerificationRequest struct {
	DocumentID xid.ID `json:"documentId"`
}

GetDocumentVerificationRequest gets verification status

type GetDocumentVerificationResponse

type GetDocumentVerificationResponse struct {
	DocumentID      xid.ID     `json:"documentId"`
	Status          string     `json:"status"` // pending, verified, rejected
	ConfidenceScore float64    `json:"confidenceScore,omitempty"`
	VerifiedAt      *time.Time `json:"verifiedAt,omitempty"`
	RejectionReason string     `json:"rejectionReason,omitempty"`
	Message         string     `json:"message"`
}

GetDocumentVerificationResponse returns verification status

type GetRecoveryConfigResponse

type GetRecoveryConfigResponse struct {
	EnabledMethods       []RecoveryMethod `json:"enabledMethods"`
	RequireMultipleSteps bool             `json:"requireMultipleSteps"`
	MinimumStepsRequired int              `json:"minimumStepsRequired"`
	RequireAdminReview   bool             `json:"requireAdminReview"`
	RiskScoreThreshold   float64          `json:"riskScoreThreshold"`
}

GetRecoveryConfigResponse returns configuration

type GetRecoveryStatsRequest

type GetRecoveryStatsRequest struct {
	OrganizationID string    `json:"organizationId,omitempty"`
	StartDate      time.Time `json:"startDate"`
	EndDate        time.Time `json:"endDate"`
}

GetRecoveryStatsRequest gets recovery statistics

type GetRecoveryStatsResponse

type GetRecoveryStatsResponse struct {
	TotalAttempts        int                    `json:"totalAttempts"`
	SuccessfulRecoveries int                    `json:"successfulRecoveries"`
	FailedRecoveries     int                    `json:"failedRecoveries"`
	PendingRecoveries    int                    `json:"pendingRecoveries"`
	SuccessRate          float64                `json:"successRate"`
	MethodStats          map[RecoveryMethod]int `json:"methodStats"`
	AverageRiskScore     float64                `json:"averageRiskScore"`
	HighRiskAttempts     int                    `json:"highRiskAttempts"`
	AdminReviewsRequired int                    `json:"adminReviewsRequired"`
}

GetRecoveryStatsResponse returns statistics

type GetSecurityQuestionsRequest

type GetSecurityQuestionsRequest struct {
	SessionID xid.ID `json:"sessionId"`
}

GetSecurityQuestionsRequest gets user's security questions

type GetSecurityQuestionsResponse

type GetSecurityQuestionsResponse struct {
	Questions []SecurityQuestionInfo `json:"questions"`
}

GetSecurityQuestionsResponse returns questions

type Handler

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

Handler provides HTTP handlers for backup authentication

func NewHandler

func NewHandler(service *Service) *Handler

NewHandler creates a new handler

func (*Handler) AddTrustedContact

func (h *Handler) AddTrustedContact(c forge.Context) error

AddTrustedContact handles POST /trusted-contacts/add

func (*Handler) ApproveRecovery

func (h *Handler) ApproveRecovery(c forge.Context) error

ApproveRecovery handles POST /admin/sessions/:id/approve (admin)

func (*Handler) CancelRecovery

func (h *Handler) CancelRecovery(c forge.Context) error

CancelRecovery handles POST /recovery/cancel

func (*Handler) CompleteRecovery

func (h *Handler) CompleteRecovery(c forge.Context) error

CompleteRecovery handles POST /recovery/complete

func (*Handler) CompleteVideoSession

func (h *Handler) CompleteVideoSession(c forge.Context) error

CompleteVideoSession handles POST /video/complete (admin)

func (*Handler) ContinueRecovery

func (h *Handler) ContinueRecovery(c forge.Context) error

ContinueRecovery handles POST /recovery/continue

func (*Handler) GenerateRecoveryCodes

func (h *Handler) GenerateRecoveryCodes(c forge.Context) error

GenerateRecoveryCodes handles POST /recovery-codes/generate

func (*Handler) GetDocumentVerification

func (h *Handler) GetDocumentVerification(c forge.Context) error

GetDocumentVerification handles GET /documents/:id

func (*Handler) GetRecoveryConfig

func (h *Handler) GetRecoveryConfig(c forge.Context) error

GetRecoveryConfig handles GET /admin/config (admin)

func (*Handler) GetRecoveryStats

func (h *Handler) GetRecoveryStats(c forge.Context) error

GetRecoveryStats handles GET /admin/stats (admin)

func (*Handler) GetSecurityQuestions

func (h *Handler) GetSecurityQuestions(c forge.Context) error

GetSecurityQuestions handles POST /security-questions/get

func (*Handler) HealthCheck

func (h *Handler) HealthCheck(c forge.Context) error

HealthCheck handles GET /health

func (*Handler) ListRecoverySessions

func (h *Handler) ListRecoverySessions(c forge.Context) error

ListRecoverySessions handles GET /admin/sessions (admin)

func (*Handler) ListTrustedContacts

func (h *Handler) ListTrustedContacts(c forge.Context) error

ListTrustedContacts handles GET /trusted-contacts

func (*Handler) RejectRecovery

func (h *Handler) RejectRecovery(c forge.Context) error

RejectRecovery handles POST /admin/sessions/:id/reject (admin)

func (*Handler) RemoveTrustedContact

func (h *Handler) RemoveTrustedContact(c forge.Context) error

RemoveTrustedContact handles DELETE /trusted-contacts/:id

func (*Handler) RequestTrustedContactVerification

func (h *Handler) RequestTrustedContactVerification(c forge.Context) error

RequestTrustedContactVerification handles POST /trusted-contacts/request-verification

func (*Handler) ReviewDocument

func (h *Handler) ReviewDocument(c forge.Context) error

ReviewDocument handles POST /documents/:id/review (admin)

func (*Handler) ScheduleVideoSession

func (h *Handler) ScheduleVideoSession(c forge.Context) error

ScheduleVideoSession handles POST /video/schedule

func (*Handler) SendVerificationCode

func (h *Handler) SendVerificationCode(c forge.Context) error

SendVerificationCode handles POST /verification/send

func (*Handler) SetupSecurityQuestions

func (h *Handler) SetupSecurityQuestions(c forge.Context) error

SetupSecurityQuestions handles POST /security-questions/setup

func (*Handler) StartRecovery

func (h *Handler) StartRecovery(c forge.Context) error

StartRecovery handles POST /recovery/start

func (*Handler) StartVideoSession

func (h *Handler) StartVideoSession(c forge.Context) error

StartVideoSession handles POST /video/start

func (*Handler) UpdateRecoveryConfig

func (h *Handler) UpdateRecoveryConfig(c forge.Context) error

UpdateRecoveryConfig handles PUT /admin/config (admin)

func (*Handler) UploadDocument

func (h *Handler) UploadDocument(c forge.Context) error

UploadDocument handles POST /documents/upload

func (*Handler) VerifyCode

func (h *Handler) VerifyCode(c forge.Context) error

VerifyCode handles POST /verification/verify

func (*Handler) VerifyRecoveryCode

func (h *Handler) VerifyRecoveryCode(c forge.Context) error

VerifyRecoveryCode handles POST /recovery-codes/verify

func (*Handler) VerifySecurityAnswers

func (h *Handler) VerifySecurityAnswers(c forge.Context) error

VerifySecurityAnswers handles POST /security-questions/verify

func (*Handler) VerifyTrustedContact

func (h *Handler) VerifyTrustedContact(c forge.Context) error

VerifyTrustedContact handles POST /trusted-contacts/verify

type HealthCheckResponse

type HealthCheckResponse struct {
	Healthy         bool              `json:"healthy"`
	Version         string            `json:"version"`
	EnabledMethods  []RecoveryMethod  `json:"enabledMethods"`
	ProvidersStatus map[string]string `json:"providersStatus,omitempty"`
	Message         string            `json:"message,omitempty"`
}

HealthCheckResponse returns plugin health status

type ListRecoverySessionsRequest

type ListRecoverySessionsRequest struct {
	OrganizationID string         `json:"organizationId,omitempty"`
	Status         RecoveryStatus `json:"status,omitempty"`
	RequiresReview bool           `json:"requiresReview,omitempty"`
	Page           int            `json:"page,omitempty"`
	PageSize       int            `json:"pageSize,omitempty"`
}

ListRecoverySessionsRequest lists recovery sessions (admin)

type ListRecoverySessionsResponse

type ListRecoverySessionsResponse struct {
	Sessions   []RecoverySessionInfo `json:"sessions"`
	TotalCount int                   `json:"totalCount"`
	Page       int                   `json:"page"`
	PageSize   int                   `json:"pageSize"`
}

ListRecoverySessionsResponse returns sessions

type ListTrustedContactsResponse

type ListTrustedContactsResponse struct {
	Contacts []TrustedContactInfo `json:"contacts"`
	Count    int                  `json:"count"`
}

ListTrustedContactsResponse returns user's trusted contacts

type MultiStepRecoveryConfig

type MultiStepRecoveryConfig struct {
	Enabled      bool `json:"enabled" yaml:"enabled"`
	MinimumSteps int  `json:"minimumSteps" yaml:"minimumSteps"`

	// Step requirements by risk level
	LowRiskSteps    []RecoveryMethod `json:"lowRiskSteps" yaml:"lowRiskSteps"`
	MediumRiskSteps []RecoveryMethod `json:"mediumRiskSteps" yaml:"mediumRiskSteps"`
	HighRiskSteps   []RecoveryMethod `json:"highRiskSteps" yaml:"highRiskSteps"`

	// Flow configuration
	AllowUserChoice bool          `json:"allowUserChoice" yaml:"allowUserChoice"`
	SessionExpiry   time.Duration `json:"sessionExpiry" yaml:"sessionExpiry"`
	AllowStepSkip   bool          `json:"allowStepSkip" yaml:"allowStepSkip"`

	// Completion
	RequireAdminApproval bool `json:"requireAdminApproval" yaml:"requireAdminApproval"`
}

MultiStepRecoveryConfig configures multi-step recovery flows

type NoOpDocumentProvider

type NoOpDocumentProvider struct{}

NoOpDocumentProvider is a no-op implementation

func (*NoOpDocumentProvider) GetVerificationStatus

func (p *NoOpDocumentProvider) GetVerificationStatus(ctx context.Context, verificationID string) (*DocumentVerificationResult, error)

func (*NoOpDocumentProvider) VerifyDocument

type NoOpEmailProvider

type NoOpEmailProvider struct{}

NoOpEmailProvider is a no-op implementation

func (*NoOpEmailProvider) SendRecoveryNotification

func (p *NoOpEmailProvider) SendRecoveryNotification(ctx context.Context, to, subject, body string) error

func (*NoOpEmailProvider) SendVerificationEmail

func (p *NoOpEmailProvider) SendVerificationEmail(ctx context.Context, to, code string, expiresIn time.Duration) error

type NoOpNotificationProvider

type NoOpNotificationProvider struct{}

NoOpNotificationProvider is a no-op implementation

func (*NoOpNotificationProvider) NotifyAdminReviewRequired

func (p *NoOpNotificationProvider) NotifyAdminReviewRequired(ctx context.Context, sessionID xid.ID, userID xid.ID, riskScore float64) error

func (*NoOpNotificationProvider) NotifyHighRiskAttempt

func (p *NoOpNotificationProvider) NotifyHighRiskAttempt(ctx context.Context, userID xid.ID, riskScore float64) error

func (*NoOpNotificationProvider) NotifyRecoveryCompleted

func (p *NoOpNotificationProvider) NotifyRecoveryCompleted(ctx context.Context, userID xid.ID, sessionID xid.ID) error

func (*NoOpNotificationProvider) NotifyRecoveryFailed

func (p *NoOpNotificationProvider) NotifyRecoveryFailed(ctx context.Context, userID xid.ID, sessionID xid.ID, reason string) error

func (*NoOpNotificationProvider) NotifyRecoveryStarted

func (p *NoOpNotificationProvider) NotifyRecoveryStarted(ctx context.Context, userID xid.ID, sessionID xid.ID, method RecoveryMethod) error

type NoOpSMSProvider

type NoOpSMSProvider struct{}

NoOpSMSProvider is a no-op implementation

func (*NoOpSMSProvider) SendRecoveryNotification

func (p *NoOpSMSProvider) SendRecoveryNotification(ctx context.Context, to, message string) error

func (*NoOpSMSProvider) SendVerificationSMS

func (p *NoOpSMSProvider) SendVerificationSMS(ctx context.Context, to, code string, expiresIn time.Duration) error

type NoOpVideoProvider

type NoOpVideoProvider struct{}

NoOpVideoProvider is a no-op implementation

func (*NoOpVideoProvider) CancelSession

func (p *NoOpVideoProvider) CancelSession(ctx context.Context, sessionID string) error

func (*NoOpVideoProvider) CompleteSession

func (p *NoOpVideoProvider) CompleteSession(ctx context.Context, sessionID string, result VideoSessionResult) error

func (*NoOpVideoProvider) CreateSession

func (p *NoOpVideoProvider) CreateSession(ctx context.Context, userID xid.ID, scheduledAt time.Time) (*VideoSessionInfo, error)

func (*NoOpVideoProvider) GetSession

func (p *NoOpVideoProvider) GetSession(ctx context.Context, sessionID string) (*VideoSessionInfo, error)

func (*NoOpVideoProvider) StartSession

func (p *NoOpVideoProvider) StartSession(ctx context.Context, sessionID string) (*VideoSessionInfo, error)

type NotificationProvider

type NotificationProvider interface {
	NotifyRecoveryStarted(ctx context.Context, userID xid.ID, sessionID xid.ID, method RecoveryMethod) error
	NotifyRecoveryCompleted(ctx context.Context, userID xid.ID, sessionID xid.ID) error
	NotifyRecoveryFailed(ctx context.Context, userID xid.ID, sessionID xid.ID, reason string) error
	NotifyAdminReviewRequired(ctx context.Context, sessionID xid.ID, userID xid.ID, riskScore float64) error
	NotifyHighRiskAttempt(ctx context.Context, userID xid.ID, riskScore float64) error
}

NotificationProvider handles notifications

type NotificationsConfig

type NotificationsConfig struct {
	Enabled bool `json:"enabled" yaml:"enabled"`

	// When to notify user
	NotifyOnRecoveryStart    bool `json:"notifyOnRecoveryStart" yaml:"notifyOnRecoveryStart"`
	NotifyOnRecoveryComplete bool `json:"notifyOnRecoveryComplete" yaml:"notifyOnRecoveryComplete"`
	NotifyOnRecoveryFailed   bool `json:"notifyOnRecoveryFailed" yaml:"notifyOnRecoveryFailed"`

	// Admin notifications
	NotifyAdminOnHighRisk     bool `json:"notifyAdminOnHighRisk" yaml:"notifyAdminOnHighRisk"`
	NotifyAdminOnReviewNeeded bool `json:"notifyAdminOnReviewNeeded" yaml:"notifyAdminOnReviewNeeded"`

	// Channels
	Channels []string `json:"channels" yaml:"channels"` // email, sms, webhook

	// Security officer notifications
	SecurityOfficerEmail string `json:"securityOfficerEmail" yaml:"securityOfficerEmail"`
}

NotificationsConfig configures notifications

type Plugin

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

Plugin implements the AuthSome plugin interface for backup authentication

func NewPlugin

func NewPlugin() *Plugin

NewPlugin creates a new backup authentication plugin instance

func (*Plugin) Description

func (p *Plugin) Description() string

Description returns the plugin description

func (*Plugin) Health

func (p *Plugin) Health(ctx context.Context) error

Health checks plugin health

func (*Plugin) ID

func (p *Plugin) ID() string

ID returns the unique plugin identifier

func (*Plugin) Init

func (p *Plugin) Init(auth interface{}) error

Init initializes the plugin with dependencies from AuthSome

func (*Plugin) Migrate

func (p *Plugin) Migrate() error

Migrate performs database migrations

func (*Plugin) Name

func (p *Plugin) Name() string

Name returns the human-readable plugin name

func (*Plugin) RegisterHooks

func (p *Plugin) RegisterHooks(hookRegistry *hooks.HookRegistry) error

RegisterHooks registers plugin hooks with the hook registry

func (*Plugin) RegisterRoutes

func (p *Plugin) RegisterRoutes(router forge.Router) error

RegisterRoutes registers HTTP routes for the plugin

func (*Plugin) RegisterServiceDecorators

func (p *Plugin) RegisterServiceDecorators(services *registry.ServiceRegistry) error

RegisterServiceDecorators allows plugins to replace core services with decorated versions

func (*Plugin) Service

func (p *Plugin) Service() *Service

Service returns the backup auth service for programmatic access (optional public method)

func (*Plugin) SetDocumentProvider

func (p *Plugin) SetDocumentProvider(provider DocumentProvider)

SetDocumentProvider sets a custom document verification provider

func (*Plugin) SetEmailProvider

func (p *Plugin) SetEmailProvider(provider EmailProvider)

SetEmailProvider sets a custom email provider

func (*Plugin) SetNotificationProvider

func (p *Plugin) SetNotificationProvider(provider NotificationProvider)

SetNotificationProvider sets a custom notification provider

func (*Plugin) SetProviders

func (p *Plugin) SetProviders(providers ProviderRegistry)

SetProviders allows setting custom providers

func (*Plugin) SetSMSProvider

func (p *Plugin) SetSMSProvider(provider SMSProvider)

SetSMSProvider sets a custom SMS provider

func (*Plugin) SetVideoProvider

func (p *Plugin) SetVideoProvider(provider VideoProvider)

SetVideoProvider sets a custom video verification provider

func (*Plugin) Shutdown

func (p *Plugin) Shutdown(ctx context.Context) error

Shutdown gracefully shuts down the plugin

func (*Plugin) Version

func (p *Plugin) Version() string

Version returns the plugin version

type ProviderRegistry

type ProviderRegistry interface {
	// Email/SMS providers
	EmailProvider() EmailProvider
	SMSProvider() SMSProvider

	// Video verification providers
	VideoProvider() VideoProvider

	// Document verification providers
	DocumentProvider() DocumentProvider

	// Notification providers
	NotificationProvider() NotificationProvider
}

ProviderRegistry manages external verification service providers

type RateLimitingConfig

type RateLimitingConfig struct {
	Enabled bool `json:"enabled" yaml:"enabled"`

	// Per-user limits
	MaxAttemptsPerHour int `json:"maxAttemptsPerHour" yaml:"maxAttemptsPerHour"`
	MaxAttemptsPerDay  int `json:"maxAttemptsPerDay" yaml:"maxAttemptsPerDay"`

	// Lockout
	LockoutAfterAttempts int           `json:"lockoutAfterAttempts" yaml:"lockoutAfterAttempts"`
	LockoutDuration      time.Duration `json:"lockoutDuration" yaml:"lockoutDuration"`
	ExponentialBackoff   bool          `json:"exponentialBackoff" yaml:"exponentialBackoff"`

	// Per-IP limits (prevent abuse)
	MaxAttemptsPerIP int           `json:"maxAttemptsPerIp" yaml:"maxAttemptsPerIp"`
	IPCooldownPeriod time.Duration `json:"ipCooldownPeriod" yaml:"ipCooldownPeriod"`
}

RateLimitingConfig configures rate limiting

type RecoveryAttemptLog

type RecoveryAttemptLog struct {
	schema.AuditableModel
	bun.BaseModel `bun:"table:backup_recovery_logs,alias:brl"`

	ID                 xid.ID  `bun:"id,pk,type:varchar(20)"`
	RecoveryID         xid.ID  `bun:"recovery_id,notnull,type:varchar(20)"`
	UserID             xid.ID  `bun:"user_id,notnull,type:varchar(20)"`
	AppID              xid.ID  `bun:"app_id,notnull,type:varchar(20)"`       // Platform app (required)
	UserOrganizationID *xid.ID `bun:"user_organization_id,type:varchar(20)"` // User-created org (optional)

	Action string         `bun:"action,notnull"` // started, step_completed, verified, failed, etc.
	Method RecoveryMethod `bun:"method,notnull"`
	Step   int            `bun:"step"`

	Success       bool   `bun:"success"`
	FailureReason string `bun:"failure_reason"`

	IPAddress string `bun:"ip_address"`
	UserAgent string `bun:"user_agent"`
	DeviceID  string `bun:"device_id"`

	Metadata map[string]interface{} `bun:"metadata,type:jsonb"`
}

RecoveryAttemptLog provides immutable audit trail of recovery attempts Updated for V2 architecture: App → Environment → Organization

type RecoveryCodeUsage

type RecoveryCodeUsage struct {
	schema.AuditableModel
	bun.BaseModel `bun:"table:backup_code_usage,alias:bcu"`

	ID                 xid.ID  `bun:"id,pk,type:varchar(20)"`
	UserID             xid.ID  `bun:"user_id,notnull,type:varchar(20)"`
	AppID              xid.ID  `bun:"app_id,notnull,type:varchar(20)"`       // Platform app (required)
	UserOrganizationID *xid.ID `bun:"user_organization_id,type:varchar(20)"` // User-created org (optional)
	RecoveryID         xid.ID  `bun:"recovery_id,notnull,type:varchar(20)"`

	CodeHash  string    `bun:"code_hash,notnull"`
	UsedAt    time.Time `bun:"used_at,notnull"`
	IPAddress string    `bun:"ip_address"`
	UserAgent string    `bun:"user_agent"`
	DeviceID  string    `bun:"device_id"`
}

RecoveryCodeUsage tracks when recovery codes are used (separate from 2FA backup codes) Updated for V2 architecture: App → Environment → Organization

type RecoveryCodesConfig

type RecoveryCodesConfig struct {
	Enabled    bool `json:"enabled" yaml:"enabled"`
	CodeCount  int  `json:"codeCount" yaml:"codeCount"`
	CodeLength int  `json:"codeLength" yaml:"codeLength"`

	// Automatically regenerate after use
	AutoRegenerate  bool `json:"autoRegenerate" yaml:"autoRegenerate"`
	RegenerateCount int  `json:"regenerateCount" yaml:"regenerateCount"` // New codes to generate

	// Format: alphanumeric, numeric, hex
	Format string `json:"format" yaml:"format"`

	// Allow printing/downloading
	AllowPrint    bool `json:"allowPrint" yaml:"allowPrint"`
	AllowDownload bool `json:"allowDownload" yaml:"allowDownload"`
}

RecoveryCodesConfig configures recovery codes

type RecoveryConfiguration

type RecoveryConfiguration struct {
	schema.AuditableModel
	bun.BaseModel `bun:"table:backup_recovery_configs,alias:brc"`

	ID                 xid.ID  `bun:"id,pk,type:varchar(20)"`
	AppID              xid.ID  `bun:"app_id,notnull,type:varchar(20)"`       // Platform app (required)
	UserOrganizationID *xid.ID `bun:"user_organization_id,type:varchar(20)"` // User-created org (optional)

	// Enabled methods
	EnabledMethods []RecoveryMethod `bun:"enabled_methods,type:jsonb"`

	// Multi-step requirements
	RequireMultipleSteps bool `bun:"require_multiple_steps,notnull,default:true"`
	MinimumStepsRequired int  `bun:"minimum_steps_required,notnull,default:2"`

	// Security settings
	RequireAdminReview bool    `bun:"require_admin_review,default:false"`
	RiskScoreThreshold float64 `bun:"risk_score_threshold,default:70.0"`

	// Time limits
	SessionExpiryMinutes int `bun:"session_expiry_minutes,default:30"`
	CodeExpiryMinutes    int `bun:"code_expiry_minutes,default:15"`

	// Rate limiting
	MaxAttemptsPerDay int           `bun:"max_attempts_per_day,default:3"`
	LockoutDuration   time.Duration `bun:"lockout_duration,default:24h"`

	Settings map[string]interface{} `bun:"settings,type:jsonb"`
}

RecoveryConfiguration stores organization-level recovery settings Updated for V2 architecture: App → Environment → Organization

type RecoveryMethod

type RecoveryMethod string

RecoveryMethod represents different recovery authentication methods

const (
	RecoveryMethodCodes          RecoveryMethod = "recovery_codes"
	RecoveryMethodSecurityQ      RecoveryMethod = "security_questions"
	RecoveryMethodTrustedContact RecoveryMethod = "trusted_contact"
	RecoveryMethodEmail          RecoveryMethod = "email_verification"
	RecoveryMethodSMS            RecoveryMethod = "sms_verification"
	RecoveryMethodVideo          RecoveryMethod = "video_verification"
	RecoveryMethodDocument       RecoveryMethod = "document_upload"
)

type RecoverySession

type RecoverySession struct {
	schema.AuditableModel
	bun.BaseModel `bun:"table:backup_recovery_sessions,alias:brs"`

	ID                 xid.ID  `bun:"id,pk,type:varchar(20)"`
	UserID             xid.ID  `bun:"user_id,notnull,type:varchar(20)"`
	AppID              xid.ID  `bun:"app_id,notnull,type:varchar(20)"`       // Platform app (required)
	UserOrganizationID *xid.ID `bun:"user_organization_id,type:varchar(20)"` // User-created org (optional)

	Status RecoveryStatus `bun:"status,notnull"`
	Method RecoveryMethod `bun:"method,notnull"`

	// Multi-step flow tracking
	RequiredSteps  []string `bun:"required_steps,type:jsonb"` // Methods required to complete
	CompletedSteps []string `bun:"completed_steps,type:jsonb"`
	CurrentStep    int      `bun:"current_step,notnull,default:0"`

	// Verification data
	VerificationCode string     `bun:"verification_code"` // For email/SMS verification
	CodeExpiresAt    *time.Time `bun:"code_expires_at"`
	Attempts         int        `bun:"attempts,notnull,default:0"`
	MaxAttempts      int        `bun:"max_attempts,notnull,default:5"`

	// Security
	IPAddress string  `bun:"ip_address"`
	UserAgent string  `bun:"user_agent"`
	DeviceID  string  `bun:"device_id"`
	RiskScore float64 `bun:"risk_score"`

	// Completion
	CompletedAt *time.Time `bun:"completed_at"`
	ExpiresAt   time.Time  `bun:"expires_at,notnull"`
	CancelledAt *time.Time `bun:"cancelled_at"`

	// Admin review (for high-risk recoveries)
	RequiresReview bool       `bun:"requires_review,notnull,default:false"`
	ReviewedBy     *xid.ID    `bun:"reviewed_by,type:varchar(20)"`
	ReviewedAt     *time.Time `bun:"reviewed_at"`
	ReviewNotes    string     `bun:"review_notes"`

	Metadata map[string]interface{} `bun:"metadata,type:jsonb"`
}

RecoverySession represents an account recovery attempt Updated for V2 architecture: App → Environment → Organization

type RecoverySessionInfo

type RecoverySessionInfo struct {
	ID             xid.ID         `json:"id"`
	UserID         xid.ID         `json:"userId"`
	UserEmail      string         `json:"userEmail,omitempty"`
	Status         RecoveryStatus `json:"status"`
	Method         RecoveryMethod `json:"method"`
	CurrentStep    int            `json:"currentStep"`
	TotalSteps     int            `json:"totalSteps"`
	RiskScore      float64        `json:"riskScore"`
	RequiresReview bool           `json:"requiresReview"`
	CreatedAt      time.Time      `json:"createdAt"`
	ExpiresAt      time.Time      `json:"expiresAt"`
	CompletedAt    *time.Time     `json:"completedAt,omitempty"`
}

RecoverySessionInfo provides session information

type RecoveryStatus

type RecoveryStatus string

RecoveryStatus represents the status of a recovery attempt

const (
	RecoveryStatusPending    RecoveryStatus = "pending"
	RecoveryStatusInProgress RecoveryStatus = "in_progress"
	RecoveryStatusCompleted  RecoveryStatus = "completed"
	RecoveryStatusFailed     RecoveryStatus = "failed"
	RecoveryStatusExpired    RecoveryStatus = "expired"
	RecoveryStatusCancelled  RecoveryStatus = "cancelled"
)

type RejectRecoveryRequest

type RejectRecoveryRequest struct {
	SessionID xid.ID `json:"sessionId"`
	Reason    string `json:"reason"`
	Notes     string `json:"notes,omitempty"`
}

RejectRecoveryRequest rejects a recovery session (admin)

type RejectRecoveryResponse

type RejectRecoveryResponse struct {
	SessionID  xid.ID    `json:"sessionId"`
	Rejected   bool      `json:"rejected"`
	RejectedAt time.Time `json:"rejectedAt"`
	Reason     string    `json:"reason"`
	Message    string    `json:"message"`
}

RejectRecoveryResponse returns rejection result

type RemoveTrustedContactRequest

type RemoveTrustedContactRequest struct {
	ContactID xid.ID `json:"contactId"`
}

RemoveTrustedContactRequest removes a trusted contact

type Repository

type Repository interface {
	// Security Questions
	CreateSecurityQuestion(ctx context.Context, q *SecurityQuestion) error
	GetSecurityQuestion(ctx context.Context, id xid.ID) (*SecurityQuestion, error)
	GetSecurityQuestionsByUser(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID) ([]*SecurityQuestion, error)
	UpdateSecurityQuestion(ctx context.Context, q *SecurityQuestion) error
	DeleteSecurityQuestion(ctx context.Context, id xid.ID) error
	IncrementQuestionFailedAttempts(ctx context.Context, id xid.ID) error

	// Trusted Contacts
	CreateTrustedContact(ctx context.Context, tc *TrustedContact) error
	GetTrustedContact(ctx context.Context, id xid.ID) (*TrustedContact, error)
	GetTrustedContactsByUser(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID) ([]*TrustedContact, error)
	GetTrustedContactByToken(ctx context.Context, token string) (*TrustedContact, error)
	UpdateTrustedContact(ctx context.Context, tc *TrustedContact) error
	DeleteTrustedContact(ctx context.Context, id xid.ID) error
	CountActiveTrustedContacts(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID) (int, error)

	// Recovery Sessions
	CreateRecoverySession(ctx context.Context, rs *RecoverySession) error
	GetRecoverySession(ctx context.Context, id xid.ID) (*RecoverySession, error)
	UpdateRecoverySession(ctx context.Context, rs *RecoverySession) error
	DeleteRecoverySession(ctx context.Context, id xid.ID) error
	GetActiveRecoverySession(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID) (*RecoverySession, error)
	ListRecoverySessions(ctx context.Context, appID xid.ID, userOrganizationID *xid.ID, status RecoveryStatus, requiresReview bool, limit, offset int) ([]*RecoverySession, int, error)
	ExpireRecoverySessions(ctx context.Context, before time.Time) (int, error)
	IncrementSessionAttempts(ctx context.Context, id xid.ID) error

	// Video Verification
	CreateVideoSession(ctx context.Context, vs *VideoVerificationSession) error
	GetVideoSession(ctx context.Context, id xid.ID) (*VideoVerificationSession, error)
	GetVideoSessionByRecovery(ctx context.Context, recoveryID xid.ID) (*VideoVerificationSession, error)
	UpdateVideoSession(ctx context.Context, vs *VideoVerificationSession) error
	DeleteVideoSession(ctx context.Context, id xid.ID) error

	// Document Verification
	CreateDocumentVerification(ctx context.Context, dv *DocumentVerification) error
	GetDocumentVerification(ctx context.Context, id xid.ID) (*DocumentVerification, error)
	GetDocumentVerificationByRecovery(ctx context.Context, recoveryID xid.ID) (*DocumentVerification, error)
	UpdateDocumentVerification(ctx context.Context, dv *DocumentVerification) error
	DeleteDocumentVerification(ctx context.Context, id xid.ID) error

	// Recovery Attempt Logs
	CreateRecoveryLog(ctx context.Context, log *RecoveryAttemptLog) error
	GetRecoveryLogs(ctx context.Context, recoveryID xid.ID) ([]*RecoveryAttemptLog, error)
	GetRecoveryLogsByUser(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID, limit int) ([]*RecoveryAttemptLog, error)

	// Recovery Configuration
	CreateRecoveryConfig(ctx context.Context, rc *RecoveryConfiguration) error
	GetRecoveryConfig(ctx context.Context, appID xid.ID, userOrganizationID *xid.ID) (*RecoveryConfiguration, error)
	UpdateRecoveryConfig(ctx context.Context, rc *RecoveryConfiguration) error

	// Recovery Code Usage
	CreateRecoveryCodeUsage(ctx context.Context, rcu *RecoveryCodeUsage) error
	GetRecoveryCodeUsage(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID, codeHash string) (*RecoveryCodeUsage, error)
	GetRecentRecoveryAttempts(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID, since time.Time) (int, error)

	// Analytics
	GetRecoveryStats(ctx context.Context, appID xid.ID, userOrganizationID *xid.ID, startDate, endDate time.Time) (map[string]interface{}, error)
}

Repository provides persistence for backup authentication entities

func NewBunRepository

func NewBunRepository(db *bun.DB) Repository

NewBunRepository creates a new Bun repository

type RequestTrustedContactVerificationRequest

type RequestTrustedContactVerificationRequest struct {
	SessionID xid.ID `json:"sessionId"`
	ContactID xid.ID `json:"contactId"`
}

RequestTrustedContactVerificationRequest requests contact verification

type RequestTrustedContactVerificationResponse

type RequestTrustedContactVerificationResponse struct {
	ContactID   xid.ID    `json:"contactId"`
	ContactName string    `json:"contactName"`
	NotifiedAt  time.Time `json:"notifiedAt"`
	ExpiresAt   time.Time `json:"expiresAt"`
	Message     string    `json:"message"`
}

RequestTrustedContactVerificationResponse returns request result

type ReviewDocumentRequest

type ReviewDocumentRequest struct {
	DocumentID      xid.ID `json:"documentId"`
	Approved        bool   `json:"approved"`
	RejectionReason string `json:"rejectionReason,omitempty"`
	Notes           string `json:"notes,omitempty"`
}

ReviewDocumentRequest reviews document (admin)

type RiskAssessmentConfig

type RiskAssessmentConfig struct {
	Enabled bool `json:"enabled" yaml:"enabled"`

	// Risk factors and weights
	NewDeviceWeight   float64 `json:"newDeviceWeight" yaml:"newDeviceWeight"`
	NewLocationWeight float64 `json:"newLocationWeight" yaml:"newLocationWeight"`
	NewIPWeight       float64 `json:"newIpWeight" yaml:"newIpWeight"`
	VelocityWeight    float64 `json:"velocityWeight" yaml:"velocityWeight"`
	HistoryWeight     float64 `json:"historyWeight" yaml:"historyWeight"`

	// Thresholds
	LowRiskThreshold    float64 `json:"lowRiskThreshold" yaml:"lowRiskThreshold"`
	MediumRiskThreshold float64 `json:"mediumRiskThreshold" yaml:"mediumRiskThreshold"`
	HighRiskThreshold   float64 `json:"highRiskThreshold" yaml:"highRiskThreshold"`

	// Actions
	BlockHighRisk      bool    `json:"blockHighRisk" yaml:"blockHighRisk"`
	RequireReviewAbove float64 `json:"requireReviewAbove" yaml:"requireReviewAbove"`
}

RiskAssessmentConfig configures risk scoring

type SMSProvider

type SMSProvider interface {
	SendVerificationSMS(ctx context.Context, to, code string, expiresIn time.Duration) error
	SendRecoveryNotification(ctx context.Context, to, message string) error
}

SMSProvider handles SMS sending

type SMSVerificationConfig

type SMSVerificationConfig struct {
	Enabled     bool          `json:"enabled" yaml:"enabled"`
	CodeExpiry  time.Duration `json:"codeExpiry" yaml:"codeExpiry"`
	CodeLength  int           `json:"codeLength" yaml:"codeLength"`
	MaxAttempts int           `json:"maxAttempts" yaml:"maxAttempts"`

	// Provider configuration
	Provider string `json:"provider" yaml:"provider"` // twilio, vonage, aws_sns

	// Template configuration
	MessageTemplate string `json:"messageTemplate" yaml:"messageTemplate"`

	// Rate limiting (SMS costs money)
	MaxSMSPerDay   int           `json:"maxSmsPerDay" yaml:"maxSmsPerDay"`
	CooldownPeriod time.Duration `json:"cooldownPeriod" yaml:"cooldownPeriod"`
}

SMSVerificationConfig configures SMS verification fallback

type ScheduleVideoSessionRequest

type ScheduleVideoSessionRequest struct {
	SessionID   xid.ID    `json:"sessionId"`
	ScheduledAt time.Time `json:"scheduledAt"`
	TimeZone    string    `json:"timeZone,omitempty"`
}

ScheduleVideoSessionRequest schedules a video verification

type ScheduleVideoSessionResponse

type ScheduleVideoSessionResponse struct {
	VideoSessionID xid.ID    `json:"videoSessionId"`
	ScheduledAt    time.Time `json:"scheduledAt"`
	JoinURL        string    `json:"joinUrl,omitempty"`
	Instructions   string    `json:"instructions"`
	Message        string    `json:"message"`
}

ScheduleVideoSessionResponse returns scheduled session

type SecurityQuestion

type SecurityQuestion struct {
	schema.AuditableModel
	bun.BaseModel `bun:"table:backup_security_questions,alias:bsq"`

	ID                 xid.ID  `bun:"id,pk,type:varchar(20)"`
	UserID             xid.ID  `bun:"user_id,notnull,type:varchar(20),unique:user_question"`
	AppID              xid.ID  `bun:"app_id,notnull,type:varchar(20)"`       // Platform app (required)
	UserOrganizationID *xid.ID `bun:"user_organization_id,type:varchar(20)"` // User-created org (optional)

	QuestionID int    `bun:"question_id,notnull,unique:user_question"` // Reference to predefined question
	CustomText string `bun:"custom_text"`                              // For custom questions
	AnswerHash string `bun:"answer_hash,notnull"`                      // Hashed answer
	Salt       string `bun:"salt,notnull"`

	IsActive       bool       `bun:"is_active,notnull,default:true"`
	LastUsedAt     *time.Time `bun:"last_used_at"`
	FailedAttempts int        `bun:"failed_attempts,notnull,default:0"`
}

SecurityQuestion stores user's security questions and hashed answers Updated for V2 architecture: App → Environment → Organization

type SecurityQuestionInfo

type SecurityQuestionInfo struct {
	ID           xid.ID `json:"id"`
	QuestionID   int    `json:"questionId,omitempty"`
	QuestionText string `json:"questionText"`
	IsCustom     bool   `json:"isCustom"`
}

SecurityQuestionInfo provides question info without answer

type SecurityQuestionsConfig

type SecurityQuestionsConfig struct {
	Enabled           bool `json:"enabled" yaml:"enabled"`
	MinimumQuestions  int  `json:"minimumQuestions" yaml:"minimumQuestions"`
	RequiredToRecover int  `json:"requiredToRecover" yaml:"requiredToRecover"`

	// Allow custom questions
	AllowCustomQuestions bool     `json:"allowCustomQuestions" yaml:"allowCustomQuestions"`
	PredefinedQuestions  []string `json:"predefinedQuestions" yaml:"predefinedQuestions"`

	// Security
	CaseSensitive   bool          `json:"caseSensitive" yaml:"caseSensitive"`
	MaxAnswerLength int           `json:"maxAnswerLength" yaml:"maxAnswerLength"`
	MaxAttempts     int           `json:"maxAttempts" yaml:"maxAttempts"`
	LockoutDuration time.Duration `json:"lockoutDuration" yaml:"lockoutDuration"`

	// Answer complexity
	RequireMinLength    int  `json:"requireMinLength" yaml:"requireMinLength"`
	ForbidCommonAnswers bool `json:"forbidCommonAnswers" yaml:"forbidCommonAnswers"`
}

SecurityQuestionsConfig configures security questions

type SendVerificationCodeRequest

type SendVerificationCodeRequest struct {
	SessionID xid.ID         `json:"sessionId"`
	Method    RecoveryMethod `json:"method"`           // email_verification or sms_verification
	Target    string         `json:"target,omitempty"` // Email or phone if different from user's
}

SendVerificationCodeRequest sends a verification code

type SendVerificationCodeResponse

type SendVerificationCodeResponse struct {
	Sent         bool      `json:"sent"`
	MaskedTarget string    `json:"maskedTarget"` // e.g., "j***@example.com" or "+1***5678"
	ExpiresAt    time.Time `json:"expiresAt"`
	Message      string    `json:"message"`
}

SendVerificationCodeResponse returns send result

type Service

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

Service provides backup authentication operations

func NewService

func NewService(repo Repository, config *Config, providers ProviderRegistry) *Service

NewService creates a new backup authentication service

func (*Service) AddTrustedContact

func (s *Service) AddTrustedContact(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID, req *AddTrustedContactRequest) (*AddTrustedContactResponse, error)

AddTrustedContact adds a trusted contact for account recovery

func (*Service) CancelRecovery

func (s *Service) CancelRecovery(ctx context.Context, req *CancelRecoveryRequest) error

CancelRecovery cancels a recovery session

func (*Service) CompleteRecovery

func (s *Service) CompleteRecovery(ctx context.Context, req *CompleteRecoveryRequest) (*CompleteRecoveryResponse, error)

CompleteRecovery finalizes a recovery session

func (*Service) ContinueRecovery

func (s *Service) ContinueRecovery(ctx context.Context, req *ContinueRecoveryRequest) (*ContinueRecoveryResponse, error)

ContinueRecovery continues a recovery session with a chosen method

func (*Service) GenerateRecoveryCodes

func (s *Service) GenerateRecoveryCodes(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID, req *GenerateRecoveryCodesRequest) (*GenerateRecoveryCodesResponse, error)

GenerateRecoveryCodes generates new recovery codes for a user

func (*Service) GetSecurityQuestions

GetSecurityQuestions retrieves security questions for verification

func (*Service) ListTrustedContacts

func (s *Service) ListTrustedContacts(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID) (*ListTrustedContactsResponse, error)

ListTrustedContacts lists user's trusted contacts

func (*Service) RemoveTrustedContact

func (s *Service) RemoveTrustedContact(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID, req *RemoveTrustedContactRequest) error

RemoveTrustedContact removes a trusted contact

func (*Service) RequestTrustedContactVerification

RequestTrustedContactVerification requests verification from a trusted contact

func (*Service) SetupSecurityQuestions

func (s *Service) SetupSecurityQuestions(ctx context.Context, userID xid.ID, appID xid.ID, userOrganizationID *xid.ID, req *SetupSecurityQuestionsRequest) (*SetupSecurityQuestionsResponse, error)

SetupSecurityQuestions sets up security questions for a user

func (*Service) StartRecovery

func (s *Service) StartRecovery(ctx context.Context, req *StartRecoveryRequest) (*StartRecoveryResponse, error)

StartRecovery initiates a new recovery session

func (*Service) VerifyRecoveryCode

VerifyRecoveryCode verifies a recovery code

func (*Service) VerifySecurityAnswers

VerifySecurityAnswers verifies security question answers

func (*Service) VerifyTrustedContact

VerifyTrustedContact verifies a trusted contact

type SetupSecurityQuestionRequest

type SetupSecurityQuestionRequest struct {
	QuestionID int    `json:"questionId,omitempty"` // ID of predefined question
	CustomText string `json:"customText,omitempty"` // For custom questions
	Answer     string `json:"answer"`
}

SetupSecurityQuestionRequest sets up a security question

type SetupSecurityQuestionsRequest

type SetupSecurityQuestionsRequest struct {
	Questions []SetupSecurityQuestionRequest `json:"questions"`
}

SetupSecurityQuestionsRequest sets up multiple questions

type SetupSecurityQuestionsResponse

type SetupSecurityQuestionsResponse struct {
	Count   int       `json:"count"`
	Message string    `json:"message"`
	SetupAt time.Time `json:"setupAt"`
}

SetupSecurityQuestionsResponse returns setup result

type StartRecoveryRequest

type StartRecoveryRequest struct {
	UserID          string         `json:"userId"`
	Email           string         `json:"email,omitempty"`
	PreferredMethod RecoveryMethod `json:"preferredMethod,omitempty"`
	DeviceID        string         `json:"deviceId,omitempty"`
}

StartRecoveryRequest initiates a recovery session

type StartRecoveryResponse

type StartRecoveryResponse struct {
	SessionID        xid.ID           `json:"sessionId"`
	Status           RecoveryStatus   `json:"status"`
	AvailableMethods []RecoveryMethod `json:"availableMethods"`
	RequiredSteps    int              `json:"requiredSteps"`
	CompletedSteps   int              `json:"completedSteps"`
	ExpiresAt        time.Time        `json:"expiresAt"`
	RiskScore        float64          `json:"riskScore,omitempty"`
	RequiresReview   bool             `json:"requiresReview"`
}

StartRecoveryResponse returns recovery session details

type StartVideoSessionRequest

type StartVideoSessionRequest struct {
	VideoSessionID xid.ID `json:"videoSessionId"`
}

StartVideoSessionRequest starts a video session

type StartVideoSessionResponse

type StartVideoSessionResponse struct {
	VideoSessionID xid.ID    `json:"videoSessionId"`
	SessionURL     string    `json:"sessionUrl"`
	StartedAt      time.Time `json:"startedAt"`
	ExpiresAt      time.Time `json:"expiresAt"`
	Message        string    `json:"message"`
}

StartVideoSessionResponse returns session details

type SuccessResponse

type SuccessResponse struct {
	Success bool   `json:"success"`
	Message string `json:"message"`
}

SuccessResponse represents a generic success response

type TrustedContact

type TrustedContact struct {
	schema.AuditableModel
	bun.BaseModel `bun:"table:backup_trusted_contacts,alias:btc"`

	ID                 xid.ID  `bun:"id,pk,type:varchar(20)"`
	UserID             xid.ID  `bun:"user_id,notnull,type:varchar(20)"`
	AppID              xid.ID  `bun:"app_id,notnull,type:varchar(20)"`       // Platform app (required)
	UserOrganizationID *xid.ID `bun:"user_organization_id,type:varchar(20)"` // User-created org (optional)

	ContactName  string `bun:"contact_name,notnull"`
	ContactEmail string `bun:"contact_email"`
	ContactPhone string `bun:"contact_phone"`
	Relationship string `bun:"relationship"` // friend, family, colleague, etc.

	VerificationToken string     `bun:"verification_token"`
	VerifiedAt        *time.Time `bun:"verified_at"`
	IsActive          bool       `bun:"is_active,notnull,default:true"`
	LastNotifiedAt    *time.Time `bun:"last_notified_at"`

	// Metadata for verification
	IPAddress string            `bun:"ip_address"`
	UserAgent string            `bun:"user_agent"`
	Metadata  map[string]string `bun:"metadata,type:jsonb"`
}

TrustedContact stores emergency contact information for account recovery Updated for V2 architecture: App → Environment → Organization

type TrustedContactInfo

type TrustedContactInfo struct {
	ID           xid.ID     `json:"id"`
	Name         string     `json:"name"`
	Email        string     `json:"email,omitempty"`
	Phone        string     `json:"phone,omitempty"`
	Relationship string     `json:"relationship,omitempty"`
	Verified     bool       `json:"verified"`
	VerifiedAt   *time.Time `json:"verifiedAt,omitempty"`
	Active       bool       `json:"active"`
}

TrustedContactInfo provides contact information

type TrustedContactsConfig

type TrustedContactsConfig struct {
	Enabled           bool `json:"enabled" yaml:"enabled"`
	MinimumContacts   int  `json:"minimumContacts" yaml:"minimumContacts"`
	MaximumContacts   int  `json:"maximumContacts" yaml:"maximumContacts"`
	RequiredToRecover int  `json:"requiredToRecover" yaml:"requiredToRecover"`

	// Verification
	RequireVerification bool          `json:"requireVerification" yaml:"requireVerification"`
	VerificationExpiry  time.Duration `json:"verificationExpiry" yaml:"verificationExpiry"`

	// Contact methods
	AllowEmailContacts bool `json:"allowEmailContacts" yaml:"allowEmailContacts"`
	AllowPhoneContacts bool `json:"allowPhoneContacts" yaml:"allowPhoneContacts"`

	// Notification throttling
	CooldownPeriod         time.Duration `json:"cooldownPeriod" yaml:"cooldownPeriod"`
	MaxNotificationsPerDay int           `json:"maxNotificationsPerDay" yaml:"maxNotificationsPerDay"`
}

TrustedContactsConfig configures trusted contacts

type UpdateRecoveryConfigRequest

type UpdateRecoveryConfigRequest struct {
	EnabledMethods       []RecoveryMethod `json:"enabledMethods,omitempty"`
	RequireMultipleSteps bool             `json:"requireMultipleSteps,omitempty"`
	MinimumStepsRequired int              `json:"minimumStepsRequired,omitempty"`
	RequireAdminReview   bool             `json:"requireAdminReview,omitempty"`
	RiskScoreThreshold   float64          `json:"riskScoreThreshold,omitempty"`
}

UpdateRecoveryConfigRequest updates recovery configuration (admin)

type UploadDocumentRequest

type UploadDocumentRequest struct {
	SessionID    xid.ID `json:"sessionId"`
	DocumentType string `json:"documentType"`        // passport, drivers_license, etc.
	FrontImage   string `json:"frontImage"`          // Base64 encoded
	BackImage    string `json:"backImage,omitempty"` // Base64 encoded
	Selfie       string `json:"selfie,omitempty"`    // Base64 encoded
}

UploadDocumentRequest uploads verification documents

type UploadDocumentResponse

type UploadDocumentResponse struct {
	DocumentID     xid.ID    `json:"documentId"`
	Status         string    `json:"status"`
	UploadedAt     time.Time `json:"uploadedAt"`
	ProcessingTime string    `json:"processingTime,omitempty"`
	Message        string    `json:"message"`
}

UploadDocumentResponse returns upload result

type VerifyCodeRequest

type VerifyCodeRequest struct {
	SessionID xid.ID `json:"sessionId"`
	Code      string `json:"code"`
}

VerifyCodeRequest verifies a sent code

type VerifyCodeResponse

type VerifyCodeResponse struct {
	Valid        bool   `json:"valid"`
	AttemptsLeft int    `json:"attemptsLeft"`
	Message      string `json:"message"`
}

VerifyCodeResponse returns verification result

type VerifyRecoveryCodeRequest

type VerifyRecoveryCodeRequest struct {
	SessionID xid.ID `json:"sessionId"`
	Code      string `json:"code"`
}

VerifyRecoveryCodeRequest verifies a recovery code

type VerifyRecoveryCodeResponse

type VerifyRecoveryCodeResponse struct {
	Valid          bool   `json:"valid"`
	RemainingCodes int    `json:"remainingCodes,omitempty"`
	Message        string `json:"message"`
}

VerifyRecoveryCodeResponse returns verification result

type VerifySecurityAnswersRequest

type VerifySecurityAnswersRequest struct {
	SessionID xid.ID            `json:"sessionId"`
	Answers   map[string]string `json:"answers"` // questionID -> answer
}

VerifySecurityAnswersRequest verifies security answers

type VerifySecurityAnswersResponse

type VerifySecurityAnswersResponse struct {
	Valid           bool   `json:"valid"`
	CorrectAnswers  int    `json:"correctAnswers"`
	RequiredAnswers int    `json:"requiredAnswers"`
	AttemptsLeft    int    `json:"attemptsLeft"`
	Message         string `json:"message"`
}

VerifySecurityAnswersResponse returns verification result

type VerifyTrustedContactRequest

type VerifyTrustedContactRequest struct {
	Token string `json:"token"`
}

VerifyTrustedContactRequest verifies a trusted contact

type VerifyTrustedContactResponse

type VerifyTrustedContactResponse struct {
	ContactID  xid.ID    `json:"contactId"`
	Verified   bool      `json:"verified"`
	VerifiedAt time.Time `json:"verifiedAt"`
	Message    string    `json:"message"`
}

VerifyTrustedContactResponse returns verification result

type VideoProvider

type VideoProvider interface {
	CreateSession(ctx context.Context, userID xid.ID, scheduledAt time.Time) (*VideoSessionInfo, error)
	GetSession(ctx context.Context, sessionID string) (*VideoSessionInfo, error)
	StartSession(ctx context.Context, sessionID string) (*VideoSessionInfo, error)
	CompleteSession(ctx context.Context, sessionID string, result VideoSessionResult) error
	CancelSession(ctx context.Context, sessionID string) error
}

VideoProvider handles video verification sessions

type VideoSessionInfo

type VideoSessionInfo struct {
	SessionID      string
	JoinURL        string
	RecordingURL   string
	Status         string
	ScheduledAt    time.Time
	StartedAt      *time.Time
	CompletedAt    *time.Time
	LivenessScore  float64
	LivenessPassed bool
}

VideoSessionInfo contains video session details

type VideoSessionResult

type VideoSessionResult struct {
	Approved       bool
	LivenessPassed bool
	LivenessScore  float64
	Notes          string
	VerifierID     string
}

VideoSessionResult contains verification result

type VideoVerificationConfig

type VideoVerificationConfig struct {
	Enabled  bool   `json:"enabled" yaml:"enabled"`
	Provider string `json:"provider" yaml:"provider"` // zoom, teams, custom

	// Scheduling
	RequireScheduling  bool          `json:"requireScheduling" yaml:"requireScheduling"`
	MinScheduleAdvance time.Duration `json:"minScheduleAdvance" yaml:"minScheduleAdvance"`
	SessionDuration    time.Duration `json:"sessionDuration" yaml:"sessionDuration"`

	// Verification requirements
	RequireLivenessCheck bool    `json:"requireLivenessCheck" yaml:"requireLivenessCheck"`
	LivenessThreshold    float64 `json:"livenessThreshold" yaml:"livenessThreshold"`

	// Recording
	RecordSessions     bool          `json:"recordSessions" yaml:"recordSessions"`
	RecordingRetention time.Duration `json:"recordingRetention" yaml:"recordingRetention"`

	// Admin review
	RequireAdminReview bool `json:"requireAdminReview" yaml:"requireAdminReview"`
}

VideoVerificationConfig configures video verification

type VideoVerificationSession

type VideoVerificationSession struct {
	schema.AuditableModel
	bun.BaseModel `bun:"table:backup_video_sessions,alias:bvs"`

	ID                 xid.ID  `bun:"id,pk,type:varchar(20)"`
	RecoveryID         xid.ID  `bun:"recovery_id,notnull,type:varchar(20)"`
	UserID             xid.ID  `bun:"user_id,notnull,type:varchar(20)"`
	AppID              xid.ID  `bun:"app_id,notnull,type:varchar(20)"`       // Platform app (required)
	UserOrganizationID *xid.ID `bun:"user_organization_id,type:varchar(20)"` // User-created org (optional)

	// Video session details
	SessionURL        string `bun:"session_url"`
	RecordingURL      string `bun:"recording_url"`
	ProviderSessionID string `bun:"provider_session_id"` // Zoom, Teams, etc.

	ScheduledAt time.Time  `bun:"scheduled_at,notnull"`
	StartedAt   *time.Time `bun:"started_at"`
	CompletedAt *time.Time `bun:"completed_at"`

	// Verification
	VerifierID         *xid.ID    `bun:"verifier_id,type:varchar(20)"` // Admin who verified
	VerifiedAt         *time.Time `bun:"verified_at"`
	VerificationResult string     `bun:"verification_result"` // approved, rejected, pending
	VerificationNotes  string     `bun:"verification_notes"`

	// Liveness checks
	LivenessCheckPassed bool    `bun:"liveness_check_passed,default:false"`
	LivenessScore       float64 `bun:"liveness_score"`

	Status   string            `bun:"status,notnull"` // scheduled, in_progress, completed, failed
	Metadata map[string]string `bun:"metadata,type:jsonb"`
}

VideoVerificationSession stores video verification details Updated for V2 architecture: App → Environment → Organization

Jump to

Keyboard shortcuts

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