app

package
v0.8.2 Latest Latest
Warning

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

Go to latest
Published: Oct 17, 2026 License: MIT Imports: 27 Imported by: 0

Documentation

Index

Constants

View Source
const (
	CodeSealKeyMissing = "E_SEAL_KEY_MISSING"

	CodeSealFailed = "E_SEAL_FAILED"
)
View Source
const FedLoginCookie = "tk_fed_oauth"
View Source
const FedProviderPrefix = "oidc_fed:"
View Source
const FedSecretMask = "***"
View Source
const OAuthStateCookie = "tk_oauth_state"

Variables

View Source
var (
	ErrMFANotEnrolled     = errors.New("identity: mfa not enrolled")
	ErrMFAAlreadyEnabled  = errors.New("identity: mfa already enabled")
	ErrMFAPendingNotReady = errors.New("identity: mfa enrollment pending confirmation")
	ErrMFASealUnavailable = errors.New("identity: seal key not configured; mfa secrets must be stored sealed")
)

ErrMFANotEnrolled and ErrMFAAlreadyConfirmed are the flow-level MFA states handlers translate into webx error envelopes.

View Source
var ErrDuplicate = errors.New("duplicate")
View Source
var ErrNotFound = errors.New("not found")

Functions

func FedProviderKey

func FedProviderKey(workspaceID string) string

func GenerateTOTPSecret added in v0.5.0

func GenerateTOTPSecret() ([]byte, error)

GenerateTOTPSecret returns 160 bits of fresh TOTP key material (RFC 4226 §4 recommends ≥160 bits; all mainstream authenticator apps accept it).

func HashPassword

func HashPassword(password string) (string, error)

func OTPAuthURI added in v0.5.0

func OTPAuthURI(issuer, account, secretB32 string) string

OTPAuthURI builds the otpauth:// enrollment URI (the QR payload the client renders): otpauth://totp/<issuer>:<account>?secret=…&issuer=…&algorithm=SHA1&digits=6&period=30

func ParseTOTPBase32 added in v0.5.0

func ParseTOTPBase32(encoded string) ([]byte, error)

ParseTOTPBase32 decodes the unpadded base32 form back to key material.

func TOTPAt added in v0.5.0

func TOTPAt(secret []byte, at time.Time) string

TOTPAt computes the code a legitimate authenticator would display for the secret at the given instant. Servers never generate user-facing codes in production paths — this is the reference computation for tests, seeding, and cross-checks.

func TOTPBase32 added in v0.5.0

func TOTPBase32(secret []byte) string

TOTPBase32 renders a secret in the unpadded base32 form every authenticator app's manual-entry field expects.

func VerifyPassword

func VerifyPassword(password, phc string) bool

func VerifyTOTP added in v0.5.0

func VerifyTOTP(secret []byte, code string, now time.Time) bool

VerifyTOTP checks a 6-digit code against the secret at ±1 step around now (constant-time compares; leading zeros accepted — the code is a string).

Types

type AccountConfig

type AccountConfig struct {
	SecretPepper string

	LinkBaseURL string
}

type AccountService

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

func NewAccountService

func NewAccountService(repo Repo, mail EmailSender, cfg AccountConfig, now Clock) *AccountService

func (*AccountService) AdminCreateUser added in v0.5.0

func (s *AccountService) AdminCreateUser(ctx context.Context, actor *webx.Principal, rawEmail, displayName, initialPassword string) (User, error)

AdminCreateUser provisions a user from the admin surface (aifengkong #1): no email is sent, no session is established, and the signup gate does not apply — this is the no-SMTP / invite-only provisioning path self-service Register cannot express. The initial password is chosen by the caller (typically generated, shown to the admin exactly once); the framework only hashes and stores it. Fires AfterRegister transactionally so products can bind roles inside the same tx.

func (*AccountService) AdminSetPassword added in v0.5.0

func (s *AccountService) AdminSetPassword(ctx context.Context, actor *webx.Principal, targetUserID, newPassword string) error

AdminSetPassword sets a target user's password directly from the admin surface (aifengkong #2): hash + store + revoke every session of that user + audit. No email is involved (the enterprise intranet path); the new password is caller-generated and shown to the admin exactly once. Whether a platform-admin target may be reset is the caller's policy decision.

func (*AccountService) DeleteAccount

func (s *AccountService) DeleteAccount(ctx context.Context, p *webx.Principal) error

func (*AccountService) DeleteAccountFor

func (s *AccountService) DeleteAccountFor(ctx context.Context, actor *webx.Principal, targetUserID string) error

func (*AccountService) ListUsersByIDs added in v0.5.0

func (s *AccountService) ListUsersByIDs(ctx context.Context, ids []string) ([]User, error)

ListUsersByIDs batch-resolves users for display echo (aifengkong #7): the created_by audit-trail rendering every admin surface needs. Missing ids are skipped (a stale reference renders as absent, not as an error).

func (*AccountService) MarkVerified

func (s *AccountService) MarkVerified(ctx context.Context, targetUserID string) error

func (*AccountService) RequestPasswordReset

func (s *AccountService) RequestPasswordReset(ctx context.Context, rawEmail string) error

func (*AccountService) ResendVerificationFor

func (s *AccountService) ResendVerificationFor(ctx context.Context, email string) error

func (*AccountService) ResetPassword

func (s *AccountService) ResetPassword(ctx context.Context, rawEmail, token, newPassword string) error

func (*AccountService) ResetPasswordFor

func (s *AccountService) ResetPasswordFor(ctx context.Context, targetUserID string) error

func (*AccountService) SendVerification

func (s *AccountService) SendVerification(ctx context.Context, p *webx.Principal) error

func (*AccountService) VerifyEmail

func (s *AccountService) VerifyEmail(ctx context.Context, rawEmail, token string) error

func (*AccountService) WithAuditor

func (s *AccountService) WithAuditor(a Auditor) *AccountService

func (*AccountService) WithIdentityHooks added in v0.5.0

func (s *AccountService) WithIdentityHooks(h identity.IdentityHooks) *AccountService

WithIdentityHooks attaches the identity hook surface (AfterRegister etc.) for the admin provisioning paths.

type Auditor

type Auditor interface {
	Record(ctx context.Context, workspaceID *string, p *webx.Principal, action, resourceType, resourceID string, meta map[string]any)
}

type Challenge

type Challenge struct {
	ID         string
	Email      string
	Kind       string
	SecretHash string
	Attempts   int
	ExpiresAt  time.Time
	CreatedAt  time.Time
}

type Clock

type Clock func() time.Time

type EmailSender

type EmailSender interface {
	SendLoginCode(ctx context.Context, to, code string) error
	SendInvite(ctx context.Context, to, workspaceName, invitationID string) error
	SendPasswordReset(ctx context.Context, to, link string) error
	SendEmailVerification(ctx context.Context, to, link string) error
}

type Enrollment added in v0.5.0

type Enrollment struct {
	OTPAuthURI string `json:"otpauth_uri"`
	Secret     string `json:"secret"`
	Issuer     string `json:"issuer"`
}

Enrollment is the enroll-start payload: the otpauth:// URI (the QR payload) plus the base32 secret for manual entry. The secret is returned exactly once — only the sealed form is stored.

type FedOIDCProvider

type FedOIDCProvider interface {
	Name() string

	AuthURL(state, redirectURI, verifier string) string

	Exchange(ctx context.Context, code, redirectURI, verifier string) (OAuthIdentity, error)
}

type FedOption

type FedOption func(*FedService)

func WithSecrets

func WithSecrets(s Secrets) FedOption

type FedProviderRow

type FedProviderRow struct {
	WorkspaceID  string    `json:"workspace_id"`
	IssuerURL    string    `json:"issuer_url"`
	ClientID     string    `json:"client_id"`
	ClientSecret string    `json:"client_secret"`
	Scopes       string    `json:"scopes"`
	UpdatedAt    time.Time `json:"updated_at"`
}

type FedResolver

type FedResolver interface {
	Resolve(ctx context.Context, issuerURL, clientID, clientSecret, scopes string) (FedOIDCProvider, error)
}

type FedService

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

func NewFedService

func NewFedService(repo Repo, slugLookup WorkspaceSlugLookup, resolver FedResolver, now Clock, opts ...FedOption) *FedService

func (*FedService) Callback

func (s *FedService) Callback(ctx context.Context, r *http.Request, code, state string, gate SignupGateCheck) (ThirdPartyLinkResult, error)

func (*FedService) DeleteProvider

func (s *FedService) DeleteProvider(ctx context.Context, workspaceID string) error

func (*FedService) GetProvider

func (s *FedService) GetProvider(ctx context.Context, workspaceID string) (*FedProviderRow, bool, error)

func (*FedService) Start

func (s *FedService) Start(w http.ResponseWriter, r *http.Request, slug string) error

func (*FedService) UpsertProvider

func (s *FedService) UpsertProvider(ctx context.Context, workspaceID, issuerURL, clientID, clientSecret, scopes string) (*FedProviderRow, error)

func (*FedService) WithSecureCookie

func (s *FedService) WithSecureCookie(secure bool) *FedService

type LoginResult

type LoginResult struct {
	Token string    `json:"-"`
	Exp   time.Time `json:"expires_at"`
	User  User      `json:"user"`
}

type MFARepo added in v0.5.0

type MFARepo interface {
	// UpsertPendingMFA stores a freshly generated (sealed) secret for the
	// user, replacing any previous enrollment — pending or confirmed. Callers
	// must refuse to overwrite a confirmed enrollment (re-enrollment goes
	// through disable first).
	UpsertPendingMFA(ctx context.Context, userID, secretSealed string, now time.Time) error

	// GetMFA returns the sealed secret and the confirmation stamp;
	// ok=false when the user has no enrollment row.
	GetMFA(ctx context.Context, userID string) (secretSealed string, confirmedAt *time.Time, ok bool, err error)

	// ConfirmMFA stamps confirmed_at (the enroll→enable transition).
	ConfirmMFA(ctx context.Context, userID string, now time.Time) error

	// DeleteMFA removes the enrollment and all backup codes (disable).
	DeleteMFA(ctx context.Context, userID string) error

	// ReplaceBackupCodes swaps the pending backup-code set (regenerate, and
	// the initial set at enable time).
	ReplaceBackupCodes(ctx context.Context, userID string, codeHashes []string, now time.Time) error

	// ConsumeBackupCode marks one pending code consumed; ok=false when no
	// pending code matches the hash. Single UPDATE — consume-once is atomic.
	ConsumeBackupCode(ctx context.Context, userID, codeHash string, now time.Time) (ok bool, err error)

	// ConfirmSessionMFA stamps mfa_confirmed_at on a live session (the
	// step-up / born-confirmed stamp对称列 of ConfirmSessionPassword).
	ConfirmSessionMFA(ctx context.Context, sessionID string, now time.Time) error
}

MFARepo is the persistence seam of the TOTP + backup-code flows (ADR 0013 batch 2). Deliberately separate from Repo: identity consumers that never mount MFA keep their existing fakes intact.

type MFAService added in v0.5.0

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

MFAService implements the TOTP + backup-code flows: enroll (start) / enable (verify first code) / disable (step-up guarded at the mount site) / regenerate backup codes (ditto) / verify-mfa (the AAL2 step-up stamping endpoint). Secrets are sealed through the same Secrets seam as every other stored credential — with no seal key configured enrollment is refused (E_SEAL_KEY_MISSING), per the framework's tenant-credential constraint.

Scope discipline (ADR 0013): strictly TOTP + backup codes — WebAuthn, recovery flows, and SMS are not here; login-integrated MFA (a challenge inside the login flow) is the next consumer's step.

func NewMFAService added in v0.5.0

func NewMFAService(repo MFARepo, sessions *SessionService, secrets Secrets, issuer string, now Clock) *MFAService

func (*MFAService) Disable added in v0.5.0

func (s *MFAService) Disable(ctx context.Context, p *webx.Principal) error

Disable removes the enrollment and every backup code. It is mount-site step-up guarded (MFAChangeGuard in identityhttp.Deps) — mirroring GitHub's sudo-guarded 2FA settings — and it revokes all the user's sessions so a device holding a stolen MFA-proofed session keeps nothing.

func (*MFAService) Enable added in v0.5.0

func (s *MFAService) Enable(ctx context.Context, ipHash string, p *webx.Principal, code string) ([]string, error)

Enable verifies the first code against the pending secret, confirms the enrollment, issues the initial backup-code set (returned once), and stamps the current session — completing the proof this very code constitutes.

func (*MFAService) Enabled added in v0.5.0

func (s *MFAService) Enabled(ctx context.Context, userID string) (bool, error)

Enabled reports whether the user has a confirmed enrollment.

func (*MFAService) RegenerateBackupCodes added in v0.5.0

func (s *MFAService) RegenerateBackupCodes(ctx context.Context, p *webx.Principal) ([]string, error)

RegenerateBackupCodes swaps in a fresh backup-code set, returning the plaintext codes exactly once.

func (*MFAService) StartEnrollment added in v0.5.0

func (s *MFAService) StartEnrollment(ctx context.Context, p *webx.Principal, accountEmail string) (Enrollment, error)

StartEnrollment generates and stores a pending (unconfirmed) TOTP secret. Re-enrollment while an enrollment is already confirmed is refused — disable first (the GitHub-shaped flow; keeps a hijacked session from silently swapping a victim's factor).

func (*MFAService) Status added in v0.5.0

func (s *MFAService) Status(ctx context.Context, userID string) (enabled, pending bool, err error)

Status is the settings-page view: enabled (confirmed enrollment) and pending (enrollment started, first code not yet verified).

func (*MFAService) VerifyMFA added in v0.5.0

func (s *MFAService) VerifyMFA(ctx context.Context, ipHash string, p *webx.Principal, code string) (time.Time, error)

VerifyMFA is the AAL2 step-up stamp: verify a TOTP or a backup code, then stamp mfa_confirmed_at on the caller's session. Failed attempts go through the same attempt limiter as login and confirm-password, so a hijacked session cannot brute-force codes. Returns the stamp time.

type NoopEmailSender added in v0.4.1

type NoopEmailSender struct{}

NoopEmailSender satisfies EmailSender with every send as a no-op (orkome F12): products that only use code login embed it and override exactly the sends they need, instead of hand-writing no-op stubs for SendInvite / SendPasswordReset / SendEmailVerification on every adapter.

type DevMailer struct {
    app.NoopEmailSender // invites/resets/verifications silently dropped
}
func (DevMailer) SendLoginCode(ctx context.Context, to, code string) error { ... }

func (NoopEmailSender) SendEmailVerification added in v0.4.1

func (NoopEmailSender) SendEmailVerification(ctx context.Context, to, link string) error

func (NoopEmailSender) SendInvite added in v0.4.1

func (NoopEmailSender) SendInvite(ctx context.Context, to, workspaceName, invitationID string) error

func (NoopEmailSender) SendLoginCode added in v0.4.1

func (NoopEmailSender) SendLoginCode(ctx context.Context, to, code string) error

func (NoopEmailSender) SendPasswordReset added in v0.4.1

func (NoopEmailSender) SendPasswordReset(ctx context.Context, to, link string) error

type OAuthIdentity

type OAuthIdentity struct {
	Provider      string
	Subject       string
	Email         string
	Name          string
	EmailVerified bool
}

type OAuthProvider

type OAuthProvider interface {
	Name() string

	AuthURL(state, redirectURI string) string

	Exchange(ctx context.Context, code, redirectURI string) (OAuthIdentity, error)
}

type OAuthService

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

func NewOAuthService

func NewOAuthService(repo Repo, providers []OAuthProvider, now Clock) *OAuthService

func (*OAuthService) Callback

func (s *OAuthService) Callback(ctx context.Context, r *http.Request, providerName, code, state string, gate SignupGateCheck) (ThirdPartyLinkResult, error)

func (*OAuthService) Names

func (s *OAuthService) Names() []string

func (*OAuthService) Start

func (s *OAuthService) Start(w http.ResponseWriter, r *http.Request, providerName string) error

func (*OAuthService) WithSecureCookie

func (s *OAuthService) WithSecureCookie(secure bool) *OAuthService

type PAT

type PAT struct {
	ID         string     `json:"id"`
	Name       string     `json:"name"`
	Prefix     string     `json:"prefix"`
	ExpiresAt  *time.Time `json:"expires_at,omitempty"`
	LastUsedAt *time.Time `json:"last_used_at,omitempty"`
	CreatedAt  time.Time  `json:"created_at"`

	Scope *webx.CredentialScope `json:"scope,omitempty"`
}

type Repo

type Repo interface {
	UpsertUserByEmail(ctx context.Context, email string, now time.Time) (User, error)
	GetUser(ctx context.Context, id string) (User, error)
	GetUserByEmail(ctx context.Context, email string) (User, bool, error)

	// ListUsersByIDs batch-resolves users by id (missing ids skipped) — the
	// display echo for created_by audit trails on admin surfaces.
	ListUsersByIDs(ctx context.Context, ids []string) ([]User, error)
	UpdateProfile(ctx context.Context, id, displayName, avatarURL string) (User, error)
	CreateUserWithPassword(ctx context.Context, email, passwordHash, displayName string) (User, error)
	SetPasswordHash(ctx context.Context, userID, passwordHash string, now time.Time) error
	SetEmailVerified(ctx context.Context, userID string, at time.Time) error

	FindOAuthAccount(ctx context.Context, provider, subject string) (userID string, ok bool, err error)

	LinkOAuthAccount(ctx context.Context, provider, subject, userID, emailAtLink string, now time.Time) error

	GetFedProvider(ctx context.Context, workspaceID string) (*FedProviderRow, bool, error)

	UpsertFedProvider(ctx context.Context, row *FedProviderRow, now time.Time) error

	DeleteFedProvider(ctx context.Context, workspaceID string) error

	CreateChallenge(ctx context.Context, email, kind, secretHash string, expiresAt time.Time) error
	LatestPendingChallenge(ctx context.Context, email, kind string) (*Challenge, error)
	IncChallengeAttempts(ctx context.Context, id string) error
	ConsumeChallenge(ctx context.Context, id string) error

	CreateSession(ctx context.Context, in webx.SessionCreate) (token string, exp time.Time, err error)
	VerifySession(ctx context.Context, token string, now time.Time) (*webx.Principal, error)

	// ConfirmSessionPassword stamps password_confirmed_at on a live session
	// (step-up): POST /auth/confirm-password → RequireRecentAuth unblocks.
	ConfirmSessionPassword(ctx context.Context, sessionID string, now time.Time) error

	RenewSession(ctx context.Context, token string, now time.Time) (exp time.Time, renewed bool, err error)
	RevokeSession(ctx context.Context, token string) error
	RevokeAllUserSessions(ctx context.Context, userID string) error
	ListUserSessions(ctx context.Context, userID string) ([]SessionInfo, error)

	RevokeUserSessionByID(ctx context.Context, userID, sessionID string, now time.Time) error

	CreatePAT(ctx context.Context, userID, name, tokenHash, prefix string, expiresAt *time.Time, scope *webx.CredentialScope) (PAT, error)
	ResolvePAT(ctx context.Context, token string, now time.Time) (*webx.Principal, error)
	ListPATs(ctx context.Context, userID string) ([]PAT, error)
	RevokePAT(ctx context.Context, userID, patID string, now time.Time) error

	CountRecentAttempts(ctx context.Context, ipHash, identity string, since time.Time) (int, error)
	RecordAttempt(ctx context.Context, ipHash, identity string, success bool, at time.Time) error
	CleanupAttempts(ctx context.Context, before time.Time) error

	RunInTx(ctx context.Context, fn func(ctx context.Context, tx pgx.Tx) error) error
	CreateUserWithPasswordTx(ctx context.Context, tx pgx.Tx, email, passwordHash, displayName string) (User, error)

	SoftDeleteUserTx(ctx context.Context, tx pgx.Tx, userID string, now time.Time) error
}

type Secrets

type Secrets interface {
	Seal(plain string) (string, error)
	Unseal(stored string) (string, error)
}

type SessionConfig

type SessionConfig struct {
	SecretPepper string

	AllowSignup    bool
	AllowedEmails  []string
	AllowedDomains []string

	DevCode string

	Production bool

	MaxAttemptsPerIP int
	AttemptWindow    time.Duration
}

type SessionInfo

type SessionInfo struct {
	ID         string    `json:"id"`
	IPHash     string    `json:"ip_hash"`
	UserAgent  string    `json:"user_agent"`
	LastSeenAt time.Time `json:"last_seen_at"`
	ExpiresAt  time.Time `json:"expires_at"`
	CreatedAt  time.Time `json:"created_at"`

	Current bool `json:"current"`

	ImpersonatedBy string `json:"impersonated_by,omitempty"`
}

type SessionService

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

func NewSessionService

func NewSessionService(repo Repo, mail EmailSender, cfg SessionConfig, sessionTTL, _ time.Duration, now Clock) *SessionService

func (*SessionService) ChangePassword

func (s *SessionService) ChangePassword(ctx context.Context, p *webx.Principal, oldPassword, newPassword string) (*LoginResult, error)

func (*SessionService) CompleteThirdPartyLogin

func (s *SessionService) CompleteThirdPartyLogin(ctx context.Context, userID string, jitNew bool, ipHash, userAgent string) (*LoginResult, error)

func (*SessionService) ConfirmPassword added in v0.4.0

func (s *SessionService) ConfirmPassword(ctx context.Context, ipHash string, p *webx.Principal, password string) (time.Time, error)

ConfirmPassword re-verifies the caller's password and stamps the current session (step-up). It backs the two-phase protocol of webx.RequireRecentAuth: 403 E_REAUTH_REQUIRED → confirm here → retry the original request. Failed confirmations go through the same attempt limiter as login (a hijacked session must not brute-force the password).

func (*SessionService) FireOnLoginFailed

func (s *SessionService) FireOnLoginFailed(ctx context.Context, email, ipHash string)

func (*SessionService) ListSessions

func (s *SessionService) ListSessions(ctx context.Context, p *webx.Principal) ([]SessionInfo, error)

func (*SessionService) ListSessionsFor

func (s *SessionService) ListSessionsFor(ctx context.Context, targetUserID string) ([]SessionInfo, error)

func (*SessionService) LoginWithPassword

func (s *SessionService) LoginWithPassword(ctx context.Context, ipHash, userAgent, rawEmail, password string) (*LoginResult, error)

func (*SessionService) Logout

func (s *SessionService) Logout(ctx context.Context, token string) error

func (*SessionService) Me

func (*SessionService) Register

func (s *SessionService) Register(ctx context.Context, ipHash, userAgent, rawEmail, password, displayName string) (*LoginResult, error)

func (*SessionService) RevokeAllSessions

func (s *SessionService) RevokeAllSessions(ctx context.Context, p *webx.Principal) error

func (*SessionService) RevokeAllSessionsFor

func (s *SessionService) RevokeAllSessionsFor(ctx context.Context, targetUserID string) error

func (*SessionService) RevokeSessionByID

func (s *SessionService) RevokeSessionByID(ctx context.Context, p *webx.Principal, sessionID string) error

func (*SessionService) RevokeSessionByIDFor

func (s *SessionService) RevokeSessionByIDFor(ctx context.Context, targetUserID, sessionID string) error

func (*SessionService) SendCode

func (s *SessionService) SendCode(ctx context.Context, ipHash, rawEmail string) error

func (*SessionService) SignupBlocked

func (s *SessionService) SignupBlocked(email string) bool

func (*SessionService) UpdateProfile

func (s *SessionService) UpdateProfile(ctx context.Context, p *webx.Principal, displayName, avatarURL string) (User, error)

func (*SessionService) VerifyCode

func (s *SessionService) VerifyCode(ctx context.Context, ipHash, userAgent, rawEmail, code string) (*LoginResult, error)

func (*SessionService) WithHooks

func (*SessionService) WithSignupGate

func (s *SessionService) WithSignupGate(gate SignupGate) *SessionService

type SignupGate

type SignupGate = func() (allowed bool, domains []string)

type SignupGateCheck

type SignupGateCheck = func(email string) bool

type ThirdPartyLinkResult

type ThirdPartyLinkResult struct {
	UserID string
	Email  string
	JITNew bool
}

type TokenService

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

func NewTokenService

func NewTokenService(repo Repo, auditor Auditor, now Clock) *TokenService

func (*TokenService) CreatePAT

func (s *TokenService) CreatePAT(ctx context.Context, p *webx.Principal, name string, ttl time.Duration, scope *webx.CredentialScope) (PAT, string, error)

func (*TokenService) ListPATs

func (s *TokenService) ListPATs(ctx context.Context, p *webx.Principal) ([]PAT, error)

func (*TokenService) ListPATsFor

func (s *TokenService) ListPATsFor(ctx context.Context, targetUserID string) ([]PAT, error)

func (*TokenService) RevokePAT

func (s *TokenService) RevokePAT(ctx context.Context, p *webx.Principal, patID string) error

func (*TokenService) RevokePATFor

func (s *TokenService) RevokePATFor(ctx context.Context, targetUserID, patID string) error

func (*TokenService) WithPATPrefix

func (s *TokenService) WithPATPrefix(prefix string) *TokenService

type User

type User struct {
	ID          string `json:"id"`
	Email       string `json:"email"`
	DisplayName string `json:"display_name"`
	AvatarURL   string `json:"avatar_url,omitempty"`
	Status      string `json:"status"`

	IsPlatformAdmin bool `json:"is_platform_admin"`
	EmailVerified   bool `json:"email_verified"`

	PasswordHash string    `json:"-"`
	CreatedAt    time.Time `json:"created_at"`

	// UpdatedBy is the last human modifier (self-edit = the user themself;
	// nil for system paths). The audit-trail echo for profile changes.
	UpdatedBy string `json:"updated_by,omitempty"`
}

type WorkspaceSlugLookup

type WorkspaceSlugLookup func(ctx context.Context, slug string) (string, bool, error)

Jump to

Keyboard shortcuts

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