Documentation
¶
Overview ¶
Package identityservice owns remote account and credential policies.
Index ¶
- Constants
- Variables
- type Account
- type AccountSummary
- type Audience
- type CreatedToken
- type GoogleClient
- type GoogleFlow
- type GoogleStore
- type Invite
- type InviteStatus
- type Method
- type Option
- type PasswordCredential
- type Purpose
- type Service
- func (service *Service) AcceptPasswordInvite(ctx context.Context, secret, email, password string) (Session, error)
- func (service *Service) AccountSummaries(ctx context.Context) ([]AccountSummary, error)
- func (service *Service) Accounts(ctx context.Context) ([]Account, error)
- func (service *Service) AuthenticateSession(ctx context.Context, audience Audience, secret string) (Session, error)
- func (service *Service) AuthenticateToken(ctx context.Context, secret string) (TokenIdentity, error)
- func (service *Service) ChangePassword(ctx context.Context, accountID, current, password string) error
- func (service *Service) CompleteGoogle(ctx context.Context, audience Audience, state, binding, code string) (Session, error)
- func (service *Service) CompletePasswordReset(ctx context.Context, audience Audience, secret, password string) error
- func (service *Service) CompleteSetup(ctx context.Context, secret, email, password string) (Session, error)
- func (service *Service) CreateInvite(ctx context.Context, email string) (Invite, string, error)
- func (service *Service) CreateToken(ctx context.Context, accountID, name string, purpose Purpose, ...) (CreatedToken, error)
- func (service *Service) DeleteAccount(ctx context.Context, actorID, accountID string) error
- func (service *Service) DisableAccount(ctx context.Context, actorID, accountID string) error
- func (service *Service) EnableAccount(ctx context.Context, actorID, accountID string) error
- func (service *Service) GoogleEnabled() bool
- func (service *Service) Invites(ctx context.Context) ([]Invite, error)
- func (service *Service) LoadSettings(ctx context.Context) (Settings, error)
- func (service *Service) Login(ctx context.Context, audience Audience, email, password string) (Session, error)
- func (service *Service) Logout(ctx context.Context, secret string) error
- func (service *Service) PendingInvite(ctx context.Context, secret string) (Invite, error)
- func (service *Service) PendingPasswordReset(ctx context.Context, audience Audience, secret string) (Account, error)
- func (service *Service) PrepareSetup(ctx context.Context) (string, error)
- func (service *Service) PutSettings(ctx context.Context, actorID string, settings Settings) error
- func (service *Service) ResetPassword(ctx context.Context, accountID string) (Account, string, error)
- func (service *Service) ResetPasswordByEmail(ctx context.Context, email string) (Account, string, error)
- func (service *Service) RevokeInvite(ctx context.Context, id string) error
- func (service *Service) RevokeToken(ctx context.Context, accountID, tokenID string) error
- func (service *Service) SetAdmin(ctx context.Context, actorID, accountID string, admin bool) error
- func (service *Service) Settings() Settings
- func (service *Service) SetupRequired(ctx context.Context) (bool, error)
- func (service *Service) SignUp(ctx context.Context, email, password string) (Session, error)
- func (service *Service) StartGoogle(ctx context.Context, inviteSecret string) (string, string, error)
- func (service *Service) StartGoogleSetup(ctx context.Context, setupSecret string) (string, string, error)
- func (service *Service) Tokens(ctx context.Context, accountID string) ([]Token, error)
- type Session
- type Settings
- type Store
- type Token
- type TokenIdentity
Constants ¶
const ( InviteLifetime = 48 * time.Hour GoogleFlowLifetime = 10 * time.Minute )
Variables ¶
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 // while sign-up is closed. 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") )
var ( ErrAuthentication = errors.New("invalid credentials") ErrInvalidInput = errors.New("invalid identity input") ErrInvalidEmail = fmt.Errorf("%w: invalid email address", ErrInvalidInput) ErrConflict = errors.New("email already has an account") ErrNotFound = errors.New("identity not found") // ErrForbidden refuses an administrative action an admin takes on itself. ErrForbidden = errors.New("identity operation not permitted") // ErrLastAdmin refuses an action that would leave no enabled admin. ErrLastAdmin = errors.New("the last admin cannot be removed") // ErrDisabled reports a sign-up or sign-in method this deployment turned off. ErrDisabled = errors.New("identity method is disabled") )
Functions ¶
This section is empty.
Types ¶
type Account ¶
type Account struct {
ID string
// Email identifies the account: the canonical password sign-in email, or
// the latest one Google reported for a Google account.
Email string
Admin bool
Method Method
}
Account is an authenticated owner. Admin marks accounts that may also sign in to the console and manage other accounts.
type AccountSummary ¶ added in v0.2.2
AccountSummary is the administrative view of an account.
type Audience ¶ added in v0.2.4
type Audience string
Audience is the surface a credential is presented to. Every account is a member of the web app and its APIs; admins also use the console.
type CreatedToken ¶
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
SetupHash string
ExpiresAt time.Time
}
GoogleFlow is one started authorization, bound to the starting browser. It may accept an invite or complete first-admin setup.
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 email 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
// AcceptPasswordInvite creates a password account and uses the pending
// invite in one transaction, refusing with ErrDisabled unless every setting
// required is on. A taken email is ErrConflict; an unavailable invite is ErrNotFound.
AcceptPasswordInvite(ctx context.Context, inviteID string, account Account, passwordHash string, now time.Time, required Settings) error
// CreateGoogleAccount creates an account and its Google identity without an
// invite, refusing with ErrDisabled unless every setting required is on. A
// taken email is ErrConflict; an already linked subject is ErrNotFound.
CreateGoogleAccount(ctx context.Context, account Account, identity google.Identity, now time.Time, required Settings) error
// CreateGoogleAdmin consumes the setup secret and creates the first admin
// with its Google identity in one transaction. A taken email is
// ErrConflict; a missing, expired or used secret or a linked subject is ErrNotFound.
CreateGoogleAdmin(ctx context.Context, account Account, identity google.Identity, setupHash string, now time.Time) error
// PromoteSetupAdmin consumes the setup secret and makes an existing,
// enabled account the first admin; otherwise it is ErrNotFound.
PromoteSetupAdmin(ctx context.Context, accountID, setupHash string, 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)
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 email of the account that accepted the invite while it exists.
UsedBy string
RevokedAt *time.Time
}
Invite admits one email to the web app as a member.
type InviteStatus ¶ added in v0.2.2
type InviteStatus string
const ( InvitePending InviteStatus = "pending" InviteUsed InviteStatus = "used" InviteExpired InviteStatus = "expired" InviteRevoked InviteStatus = "revoked" )
type Method ¶ added in v0.2.4
type Method string
Method is how an account signs in. It is fixed when the account is created: an account with a Google identity uses Google, every other one a password.
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.
type PasswordCredential ¶
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
func (*Service) AcceptPasswordInvite ¶ added in v0.2.4
func (service *Service) AcceptPasswordInvite(ctx context.Context, secret, email, password string) (Session, error)
AcceptPasswordInvite creates a password account from an invite link and signs it in while password signup is on. Holding the link is the proof; the invited email is not verified.
func (*Service) AccountSummaries ¶ added in v0.2.2
func (service *Service) AccountSummaries(ctx context.Context) ([]AccountSummary, error)
func (*Service) Accounts ¶
Accounts lists enabled members, the owners the ingestion worker may serve.
func (*Service) AuthenticateSession ¶
func (service *Service) AuthenticateSession(ctx context.Context, audience Audience, secret string) (Session, error)
AuthenticateSession resolves a browser session for an audience; sessions of accounts the audience does not admit fail like unknown ones.
func (*Service) AuthenticateToken ¶
func (service *Service) AuthenticateToken(ctx context.Context, secret string) (TokenIdentity, error)
AuthenticateToken resolves an API token, a member credential.
func (*Service) ChangePassword ¶
func (*Service) CompleteGoogle ¶ added in v0.2.2
func (service *Service) CompleteGoogle(ctx context.Context, audience Audience, state, binding, code string) (Session, error)
CompleteGoogle finishes authorization in the browser that started it. A linked Google subject signs in to an audience that admits it, keeping its account when the email changes. For the web app, an invite flow creates the account when Google verified the invited email, and open signup creates one for any verified email. The console creates only the first admin, from a setup flow.
func (*Service) CompletePasswordReset ¶ added in v0.2.4
func (service *Service) CompletePasswordReset(ctx context.Context, audience Audience, secret, password string) error
CompletePasswordReset sets a new password from a reset link and revokes the account's browser sessions; its API tokens are unaffected.
func (*Service) CompleteSetup ¶ added in v0.2.2
func (service *Service) CompleteSetup(ctx context.Context, secret, email, 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
CreateInvite issues a single-use 48 hour member invite for one email, replacing any pending invite for it. The secret is returned once and stored hashed. Invites work whether or not signup is open; the settings decide whether a password can accept it.
func (*Service) CreateToken ¶
func (*Service) DeleteAccount ¶ added in v0.2.2
DeleteAccount removes the account and every review, token and integration it owns.
func (*Service) DisableAccount ¶ added in v0.2.2
DisableAccount, EnableAccount, DeleteAccount and SetAdmin are an admin's actions on another account; an admin never acts on itself, so it cannot lock itself out.
func (*Service) EnableAccount ¶ added in v0.2.2
func (*Service) GoogleEnabled ¶ added in v0.2.2
func (*Service) LoadSettings ¶ added in v0.2.4
LoadSettings reads the settings and keeps them as this process's snapshot.
func (*Service) Login ¶
func (service *Service) Login(ctx context.Context, audience Audience, email, password string) (Session, error)
Login signs a password account in to an audience it is admitted to. Every failure is the same ErrAuthentication, so it never reveals which accounts exist, use Google or belong to another audience.
func (*Service) PendingInvite ¶ added in v0.2.2
PendingInvite resolves an invite link for the invite page.
func (*Service) PendingPasswordReset ¶ added in v0.2.4
func (service *Service) PendingPasswordReset(ctx context.Context, audience Audience, secret string) (Account, error)
PendingPasswordReset resolves a reset link for an audience's reset page.
func (*Service) PrepareSetup ¶ added in v0.2.2
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) PutSettings ¶ added in v0.2.4
PutSettings is an admin's change to the settings.
func (*Service) ResetPassword ¶
func (service *Service) ResetPassword(ctx context.Context, accountID string) (Account, string, error)
ResetPassword issues a one-time reset secret for a password account, replacing its pending one. It returns the account so the caller links the secret to the surface the account signs in to. Google accounts are ErrNotFound.
func (*Service) ResetPasswordByEmail ¶ added in v0.2.5
func (service *Service) ResetPasswordByEmail(ctx context.Context, email string) (Account, string, error)
ResetPasswordByEmail is ResetPassword for offline recovery by email.
func (*Service) RevokeInvite ¶ added in v0.2.2
func (*Service) RevokeToken ¶
func (*Service) SetAdmin ¶ added in v0.2.4
SetAdmin grants or removes the admin role. Removing it ends the account's console sessions at their next request.
func (*Service) Settings ¶ added in v0.2.4
Settings is the latest loaded snapshot; before the first load every setting is off. Account creation re-checks the stored settings.
func (*Service) SetupRequired ¶ added in v0.2.2
func (*Service) SignUp ¶ added in v0.2.4
SignUp creates a password member and signs it in, while both signup and password signup are on.
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.
type Settings ¶ added in v0.2.4
type Settings struct {
// Signup lets people create accounts without an invite.
Signup bool
// PasswordSignup lets new accounts choose a password, by signup or invite.
// Existing password accounts always sign in, whatever its value.
PasswordSignup bool
}
Settings decide how people join the web app; admins change them in the console. They never affect existing accounts or the console.
type Store ¶
type Store interface {
// PutAdminSetup replaces the pending setup secret while no admin exists and
// reports whether it was stored. Setup creates the first admin only.
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
// PendingAdminSetup reports whether the setup secret is unexpired while no admin exists.
PendingAdminSetup(ctx context.Context, setupHash string, now time.Time) (bool, error)
// CreateAccount commits a member and its password hash; a taken email
// is ErrConflict. It reads the settings in its transaction and refuses with
// ErrDisabled unless every setting required is on.
CreateAccount(ctx context.Context, account Account, passwordHash string, created time.Time, required Settings) error
AppSettings(context.Context) (Settings, error)
// PutAppSettings replaces the settings and notifies every replica at commit.
PutAppSettings(ctx context.Context, settings Settings, actorID string, at time.Time) error
AccountSummaries(context.Context) ([]AccountSummary, error)
// SetAccountDisabled revokes sessions and tokens when disabling. It,
// DeleteAccount and SetAdmin refuse with ErrLastAdmin when the change would
// leave no enabled admin, deciding atomically with concurrent changes.
SetAccountDisabled(ctx context.Context, accountID string, disabled bool, at time.Time) error
DeleteAccount(context.Context, string) error
SetAdmin(ctx context.Context, accountID string, admin bool) error
// AccountPassword resolves an enabled account by its canonical email.
AccountPassword(context.Context, string) (PasswordCredential, error)
AccountPasswordByID(context.Context, string) (PasswordCredential, error)
ReplacePassword(context.Context, string, string, string) error
// PutPasswordReset replaces the account's pending reset secret.
PutPasswordReset(ctx context.Context, accountID, tokenHash string, expires time.Time) error
// PasswordReset resolves an unexpired reset of an enabled account to its credential.
PasswordReset(ctx context.Context, tokenHash string, now time.Time) (PasswordCredential, error)
// CompletePasswordReset consumes the unexpired reset, replaces the password
// fenced on the expected hash and revokes browser sessions in one
// transaction; API tokens are independent. A missing reset is ErrNotFound.
CompletePasswordReset(ctx context.Context, tokenHash, expected, replacement string, now time.Time) 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.