identityservice

package
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package identityservice owns remote account and credential policies.

Index

Constants

View Source
const (
	InviteLifetime     = 48 * time.Hour
	GoogleFlowLifetime = 10 * time.Minute
)
View Source
const (
	PurposeCollector     Purpose = "collector"
	PurposeAPI           Purpose = "api"
	SessionLifetime              = 7 * 24 * time.Hour
	DefaultTokenLifetime         = 90 * 24 * time.Hour
	MaximumTokenLifetime         = 365 * 24 * time.Hour
	SetupLifetime                = 24 * time.Hour
)

Variables

View Source
var (
	// ErrGoogleDisabled reports Google sign-in is not configured.
	ErrGoogleDisabled = errors.New("google sign-in is not configured")
	// ErrNoAccount reports a Google account without a diffx account or invite.
	ErrNoAccount = errors.New("no account for this Google account")
	// ErrInviteMismatch reports a verified Google email that differs from the invite.
	ErrInviteMismatch = errors.New("google account does not match the invite")
)
View Source
var (
	ErrAuthentication = errors.New("invalid credentials")
	ErrInvalidInput   = errors.New("invalid identity input")
	ErrConflict       = errors.New("username already exists")
	ErrNotFound       = errors.New("identity not found")
	// ErrForbidden refuses an administrative action on the admin account itself.
	ErrForbidden = errors.New("identity operation not permitted")
)

Functions

This section is empty.

Types

type Account

type Account struct {
	ID       string
	Username string
	Admin    bool
}

Account is an authenticated owner. Admin marks the single account that signs in with a password and manages other accounts.

type AccountSummary added in v0.2.2

type AccountSummary struct {
	Account
	Email     string
	Disabled  bool
	CreatedAt time.Time
}

AccountSummary is the administrative view of an account.

type CreatedToken

type CreatedToken struct {
	Token
	Secret string
}

type GoogleClient added in v0.2.2

type GoogleClient interface {
	AuthorizeURL(state, nonce, verifier string) string
	Exchange(ctx context.Context, code, verifier, nonce string) (google.Identity, error)
}

GoogleClient is the Google wire adapter this service drives.

type GoogleFlow added in v0.2.2

type GoogleFlow struct {
	BindingHash string
	Nonce       string
	Verifier    string
	InviteID    string
	ExpiresAt   time.Time
}

GoogleFlow is one started authorization, bound to the starting browser.

type GoogleStore added in v0.2.2

type GoogleStore interface {
	// CreateInvite revokes the email's pending invites in the same transaction;
	// an email linked to an account is ErrConflict.
	CreateInvite(ctx context.Context, invite Invite, tokenHash string) error
	Invites(context.Context) ([]Invite, error)
	Invite(ctx context.Context, id string) (Invite, error)
	RevokeInvite(ctx context.Context, id string, at time.Time) error
	// PendingInvite resolves an unexpired, unused, unrevoked invite by its secret.
	PendingInvite(ctx context.Context, tokenHash string, now time.Time) (Invite, error)
	// AcceptInvite creates the account and its Google identity and uses the
	// pending invite in one transaction. A taken username is ErrConflict; an
	// unavailable invite or an already linked subject is ErrNotFound.
	AcceptInvite(ctx context.Context, inviteID string, account Account, identity google.Identity, now time.Time) error
	// GoogleAccount resolves a linked subject, recording the latest email.
	GoogleAccount(ctx context.Context, identity google.Identity, now time.Time) (account Account, disabled bool, err error)
	// GoogleEmail is the latest email Google reported for the account; "" for
	// accounts without a Google identity.
	GoogleEmail(ctx context.Context, accountID string) (string, error)
	PutGoogleFlow(ctx context.Context, stateHash string, flow GoogleFlow) error
	// TakeGoogleFlow consumes an unexpired flow exactly once.
	TakeGoogleFlow(ctx context.Context, stateHash string, now time.Time) (GoogleFlow, error)
}

GoogleStore persists Google identities, invites and authorization flows.

type Invite added in v0.2.2

type Invite struct {
	ID        string
	Email     string
	CreatedAt time.Time
	ExpiresAt time.Time
	UsedAt    *time.Time
	// UsedBy is the username that accepted the invite while that account exists.
	UsedBy    string
	RevokedAt *time.Time
}

func (Invite) Status added in v0.2.2

func (invite Invite) Status(now time.Time) InviteStatus

type InviteStatus added in v0.2.2

type InviteStatus string
const (
	InvitePending InviteStatus = "pending"
	InviteUsed    InviteStatus = "used"
	InviteExpired InviteStatus = "expired"
	InviteRevoked InviteStatus = "revoked"
)

type Option added in v0.2.2

type Option func(*Service)

func WithGoogle added in v0.2.2

func WithGoogle(client GoogleClient) Option

WithGoogle enables Google sign-in and invites.

type PasswordCredential

type PasswordCredential struct {
	Account Account
	Hash    string
}

type Purpose

type Purpose string

type Service

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

func New

func New(store Store, options ...Option) *Service

func (*Service) AccountSummaries added in v0.2.2

func (service *Service) AccountSummaries(ctx context.Context) ([]AccountSummary, error)

func (*Service) Accounts

func (service *Service) Accounts(ctx context.Context) ([]Account, error)

Accounts lists enabled accounts, the owners the ingestion worker may serve.

func (*Service) AuthenticateSession

func (service *Service) AuthenticateSession(ctx context.Context, secret string) (Session, error)

func (*Service) AuthenticateToken

func (service *Service) AuthenticateToken(ctx context.Context, secret string) (TokenIdentity, error)

func (*Service) ChangePassword

func (service *Service) ChangePassword(ctx context.Context, accountID, current, password string) error

func (*Service) CompleteGoogle added in v0.2.2

func (service *Service) CompleteGoogle(ctx context.Context, state, binding, code string) (Session, error)

CompleteGoogle finishes authorization in the browser that started it. A linked Google subject signs in, keeping its account when the email changes. Otherwise an invite flow creates the account when Google verified the invited email.

func (*Service) CompleteSetup added in v0.2.2

func (service *Service) CompleteSetup(ctx context.Context, secret, username, password string) (Session, error)

CompleteSetup creates the admin from a valid setup secret and signs it in.

func (*Service) CreateInvite added in v0.2.2

func (service *Service) CreateInvite(ctx context.Context, email string) (Invite, string, error)

CreateInvite issues a single-use 48 hour invite for one email, replacing any pending invite for it. The secret is returned once and stored hashed.

func (*Service) CreateToken

func (service *Service) CreateToken(ctx context.Context, accountID, name string, purpose Purpose, expires time.Time) (CreatedToken, error)

func (*Service) DeleteAccount added in v0.2.2

func (service *Service) DeleteAccount(ctx context.Context, accountID string) error

DeleteAccount removes the account and every review, token and integration it owns.

func (*Service) DisableAccount added in v0.2.2

func (service *Service) DisableAccount(ctx context.Context, accountID string) error

func (*Service) EnableAccount added in v0.2.2

func (service *Service) EnableAccount(ctx context.Context, accountID string) error

func (*Service) GoogleEmail added in v0.2.2

func (service *Service) GoogleEmail(ctx context.Context, accountID string) (string, error)

GoogleEmail is the account's latest Google email, or "" for the admin.

func (*Service) GoogleEnabled added in v0.2.2

func (service *Service) GoogleEnabled() bool

func (*Service) Invites added in v0.2.2

func (service *Service) Invites(ctx context.Context) ([]Invite, error)

func (*Service) Login

func (service *Service) Login(ctx context.Context, username, password string) (Session, error)

Login accepts passwords only for the admin; every other failure is the same ErrAuthentication, so it never reveals which accounts exist or use Google.

func (*Service) Logout

func (service *Service) Logout(ctx context.Context, secret string) error

func (*Service) PendingInvite added in v0.2.2

func (service *Service) PendingInvite(ctx context.Context, secret string) (Invite, error)

PendingInvite resolves an invite link for the invite page.

func (*Service) PrepareSetup added in v0.2.2

func (service *Service) PrepareSetup(ctx context.Context) (string, error)

PrepareSetup issues a one-time admin setup secret while no admin exists, replacing any earlier one. It returns "" once an admin exists. The secret is stored hashed, so each start issues a new one.

func (*Service) ResetPassword

func (service *Service) ResetPassword(ctx context.Context, username, password string) error

ResetPassword recovers the admin offline; other accounts have no password.

func (*Service) RevokeInvite added in v0.2.2

func (service *Service) RevokeInvite(ctx context.Context, id string) error

func (*Service) RevokeToken

func (service *Service) RevokeToken(ctx context.Context, accountID, tokenID string) error

func (*Service) SetupRequired added in v0.2.2

func (service *Service) SetupRequired(ctx context.Context) (bool, error)

func (*Service) StartGoogle added in v0.2.2

func (service *Service) StartGoogle(ctx context.Context, inviteSecret string) (string, string, error)

StartGoogle begins Google authorization, optionally for an invite. It returns the authorization URL and a browser binding the callback must present.

func (*Service) Tokens

func (service *Service) Tokens(ctx context.Context, accountID string) ([]Token, error)

type Session

type Session struct {
	Account   Account
	Secret    string
	CSRFToken string
	ExpiresAt time.Time
}

type Store

type Store interface {
	// PutAdminSetup replaces the pending setup secret while no admin exists and
	// reports whether it was stored.
	PutAdminSetup(context.Context, string, time.Time) (bool, error)
	AdminExists(context.Context) (bool, error)
	// CreateAdmin consumes the unexpired setup secret and creates the admin in
	// one transaction; a missing, expired or used secret is ErrNotFound.
	CreateAdmin(ctx context.Context, account Account, passwordHash, setupHash string, now time.Time) error
	AccountSummaries(context.Context) ([]AccountSummary, error)
	// SetAccountDisabled revokes sessions and tokens when disabling. Both it and
	// DeleteAccount refuse the admin with ErrForbidden.
	SetAccountDisabled(ctx context.Context, accountID string, disabled bool, at time.Time) error
	DeleteAccount(context.Context, string) error
	AccountPassword(context.Context, string) (PasswordCredential, error)
	AccountPasswordByID(context.Context, string) (PasswordCredential, error)
	ReplacePassword(context.Context, string, string, string) error
	CreateSession(context.Context, string, string, string, string, time.Time) error
	ResolveSession(context.Context, string, time.Time) (Session, error)
	DeleteSession(context.Context, string) error
	CreateIdentityToken(context.Context, string, Token, string) error
	IdentityTokens(context.Context, string) ([]Token, error)
	DeleteIdentityToken(context.Context, string, string) error
	ResolveIdentityToken(context.Context, string, time.Time) (TokenIdentity, error)
	IdentityAccounts(context.Context) ([]Account, error)
	GoogleStore
}

Store operations preserve credential races atomically. Password replacement revokes sessions in its transaction; session creation compares the verified hash and refuses disabled accounts.

type Token

type Token struct {
	ID         string
	Name       string
	Purpose    Purpose
	CreatedAt  time.Time
	LastUsedAt *time.Time
	ExpiresAt  time.Time
}

type TokenIdentity

type TokenIdentity struct {
	Account   Account
	Purpose   Purpose
	ExpiresAt time.Time
}

Jump to

Keyboard shortcuts

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