245 lines
5.6 KiB
Go
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
|
|
}
|