pase/store/models.go
2026-05-06 00:16:39 +00:00

245 lines
5.6 KiB
Go

package store
import (
"encoding/json"
"fmt"
"time"
)
type UserStatus string
const (
UserStatusActive UserStatus = "active"
UserStatusDeactivated UserStatus = "deactivated"
UserStatusLocked UserStatus = "locked"
UserStatusBanned UserStatus = "banned"
UserStatusPendingDeletion UserStatus = "pending_deletion"
)
type User struct {
ID string
Email string
EmailVerifiedAt NullTime
Username NullString
UsernameNormalized NullString
DisplayName NullString
ProfileImageURL NullString
Status UserStatus
StatusReason NullString
StatusChangedAt NullTime
StatusExpiresAt NullTime
FailedLoginCount int
LastFailedLoginAt NullTime
CreatedAt time.Time
UpdatedAt time.Time
}
type Permission struct {
ID string
Name string
Description NullString
CreatedAt time.Time
UpdatedAt time.Time
}
type Role struct {
ID string
Name string
Description NullString
CreatedAt time.Time
UpdatedAt time.Time
}
type RolePermission struct {
RoleID string
PermissionID string
CreatedAt time.Time
}
type PermissionEffect string
const (
PermissionAllow PermissionEffect = "allow"
PermissionDeny PermissionEffect = "deny"
)
type UserPermission struct {
UserID string
PermissionID string
Effect PermissionEffect
CreatedAt time.Time
}
type Session struct {
IDHash string
UserID string
ExpiresAt time.Time
LastUsedAt time.Time
UserAgent NullString
IPAddress NullString
CreatedAt time.Time
}
type TokenPurpose string
const (
TokenPurposeMagicLink TokenPurpose = "magic_link"
TokenPurposePasswordReset TokenPurpose = "password_reset"
TokenPurposeEmailVerify TokenPurpose = "email_verify"
TokenPurposeEmailChange TokenPurpose = "email_change"
)
type Token struct {
ID string
UserID string
Purpose TokenPurpose
HashedValue string
Payload JSONB
ExpiresAt time.Time
ConsumedAt NullTime
CreatedAt time.Time
}
type CredentialType string
const (
CredentialPassword CredentialType = "password"
CredentialPasskey CredentialType = "passkey"
CredentialTOTP CredentialType = "totp"
CredentialOAuth CredentialType = "oauth"
)
type Credential struct {
ID string
UserID string
Type CredentialType
// Used by passkeys (credential ID) and OAuth (provider account id).
// Null for password and TOTP.
Identifier NullString
// Used by OAuth: "google", "github", etc. Null otherwise.
Provider NullString
// The actual secret material. Format depends on type:
// password: argon2id hash string
// passkey: COSE public key (base64)
// totp: encrypted shared secret
// oauth: null (tokens go in `data`)
Secret NullString
// Type-specific fields that don't fit elsewhere:
// passkey: { sign_count, transports, aaguid, backup_eligible }
// totp: { algorithm, digits, period }
// oauth: { access_token, refresh_token, expires_at, scope }
Data JSONB
// Human-friendly label, useful for UI ("My iPhone", "YubiKey 5C").
// Especially valuable for passkeys where users have multiple.
Name NullString
LastUsedAt time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
type PasskeyData struct {
SignCount uint32 `json:"sign_count"`
Transports []string `json:"transports"`
AAGUID string `json:"aaguid"`
BackupEligible bool `json:"backup_eligible"`
}
type OAuthData struct {
AccessToken string `json:"access_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
ExpiresAt time.Time `json:"expires_at"`
Scope string `json:"scope,omitempty"`
}
type TOTPData struct {
Algorithm string `json:"algorithm"`
Digits int `json:"digits"`
Period int `json:"period"`
}
func (c *Credential) PasskeyData() (*PasskeyData, error) {
if c.Type != CredentialPasskey {
return nil, fmt.Errorf("pase: credential is %s, not passkey", c.Type)
}
if len(c.Data) == 0 {
return &PasskeyData{}, nil
}
var d PasskeyData
if err := json.Unmarshal(c.Data, &d); err != nil {
return nil, fmt.Errorf("pase: decode passkey data: %w", err)
}
return &d, nil
}
func (c *Credential) SetPasskeyData(d *PasskeyData) error {
if c.Type != CredentialPasskey {
return fmt.Errorf("pase: credential is %s, not passkey", c.Type)
}
b, err := json.Marshal(d)
if err != nil {
return fmt.Errorf("pase: %w", err)
}
c.Data = b
return nil
}
func (c *Credential) OAuthData() (*OAuthData, error) {
if c.Type != CredentialOAuth {
return nil, fmt.Errorf("pase: credential is %s, not oauth", c.Type)
}
if len(c.Data) == 0 {
return &OAuthData{}, nil
}
var d OAuthData
if err := json.Unmarshal(c.Data, &d); err != nil {
return nil, fmt.Errorf("pase: decode oauth data: %w", err)
}
return &d, nil
}
func (c *Credential) SetOAuthData(d *OAuthData) error {
if c.Type != CredentialOAuth {
return fmt.Errorf("pase: credential is %s, not oauth", c.Type)
}
b, err := json.Marshal(d)
if err != nil {
return fmt.Errorf("pase: %w", err)
}
c.Data = b
return nil
}
func (c *Credential) TOTPData() (*TOTPData, error) {
if c.Type != CredentialTOTP {
return nil, fmt.Errorf("pase: credential is %s, not totp", c.Type)
}
if len(c.Data) == 0 {
return &TOTPData{}, nil
}
var d TOTPData
if err := json.Unmarshal(c.Data, &d); err != nil {
return nil, fmt.Errorf("pase: decode totp data: %w", err)
}
return &d, nil
}
func (c *Credential) SetTOTPData(d *TOTPData) error {
if c.Type != CredentialTOTP {
return fmt.Errorf("pase: credential is %s, not totp", c.Type)
}
b, err := json.Marshal(d)
if err != nil {
return fmt.Errorf("pase: %w", err)
}
c.Data = b
return nil
}