Documentation
¶
Overview ¶
Package pocketid is a thin HTTP client for the PocketID admin API.
API surface notes (verified against pocket-id/pocket-id v2.16.0, commit bb05e6922444c33eec87466919f1bcf105967825):
- Auth header is `X-API-Key` (case-insensitive). PocketID does NOT use `Authorization: Bearer`. JWT cookies and API keys are accepted by the same admin endpoints.
- Bootstrap is solved by the `STATIC_API_KEY` env variable on the PocketID container (added in v1229, ships in v2+). When set, PocketID auto-creates a "Static API User" admin on first request and accepts that env value as a valid `X-API-Key` indefinitely. There is no `/api/setup` endpoint and no first-run wizard exposed via the JSON API in the WebAuthn-only v2 line; the UI flow instead requires registering a passkey through the browser.
- Health endpoint is `/healthz` (returns 204), NOT `/api/health`.
- Group membership is keyed by group ID, not name: `PUT /api/user-groups/:id/users` with `{"userIds":[...]}`.
- `CreateUser` requires `firstName` plus a valid email if email is set.
- OIDC client registration accepts `callbackURLs` (camelCase). The client secret is created in a separate call: `POST /api/oidc/clients/:id/secrets`. StackKits supplies the secret value so it can place the same one-time response into owner custody without depending on server-side generation.
Given those findings, BootstrapInitialAdmin is implemented as a verification call against `GET /api/users` using the configured admin token. If the token works, bootstrap is considered already complete and we return ErrAlreadyBootstrapped (the canonical "static-api-key path is already provisioned" signal). Callers (Tasks 6-9) are expected to render the static API key into the PocketID container env at deploy time, so the fact that the token works is the bootstrap success criterion.
Index ¶
- Variables
- func ActivationToken(link *url.URL) (string, bool)
- func ActivationURL(origin, token string) string
- type Client
- func (c *Client) AddUserToGroup(ctx context.Context, userID, groupID string) error
- func (c *Client) BootstrapInitialAdmin(ctx context.Context, _, _, _ string) (string, error)
- func (c *Client) ConfigureEmailVerificationSMTP(ctx context.Context, smtp SMTPConfiguration) error
- func (c *Client) CreateOIDCClientSecret(ctx context.Context, clientID string) (string, error)
- func (c *Client) CreateOneTimeAccessToken(ctx context.Context, userID string, ttl time.Duration) (string, error)
- func (c *Client) CreateUser(ctx context.Context, req CreateUserRequest) (*User, error)
- func (c *Client) CreateUserGroup(ctx context.Context, req CreateUserGroupRequest) (*UserGroup, error)
- func (c *Client) DeleteUser(ctx context.Context, userID string) error
- func (c *Client) FindUsersByUsername(ctx context.Context, username string) ([]User, error)
- func (c *Client) GetGroupIDByName(ctx context.Context, name string) (string, error)
- func (c *Client) GetOIDCClient(ctx context.Context, clientID string) (*OIDCClient, error)
- func (c *Client) GetUser(ctx context.Context, userID string) (*User, error)
- func (c *Client) ListUserWebAuthnCredentials(ctx context.Context, userID string) ([]WebAuthnCredential, error)
- func (c *Client) ListUsers(ctx context.Context) ([]User, error)
- func (c *Client) OIDCIssuer(ctx context.Context) (string, error)
- func (c *Client) RegisterOIDCClient(ctx context.Context, req RegisterClientRequest) (*OIDCClient, error)
- func (c *Client) RetireOneTimeAccessToken(ctx context.Context, token string) error
- func (c *Client) UpdateOIDCClient(ctx context.Context, clientID string, req RegisterClientRequest) (*OIDCClient, error)
- func (c *Client) UpdateOIDCClientAllowedUserGroups(ctx context.Context, clientID string, groupIDs []string) (*OIDCClient, error)
- func (c *Client) UpdateUserCustomClaims(ctx context.Context, subject string, claims []CustomClaim) error
- func (c *Client) UpdateUserGroups(ctx context.Context, userID string, groupIDs []string) (*User, error)
- func (c *Client) WaitHealthy(ctx context.Context, timeout time.Duration) error
- type CreateUserGroupRequest
- type CreateUserRequest
- type CustomClaim
- type HTTPError
- type OIDCClient
- type RegisterClientRequest
- type SMTPConfiguration
- type User
- type UserGroup
- type WebAuthnCredential
Constants ¶
This section is empty.
Variables ¶
var ErrAlreadyBootstrapped = errors.New("pocketid: instance already bootstrapped")
ErrAlreadyBootstrapped is returned by BootstrapInitialAdmin when PocketID already accepts the configured admin token (typically because the STATIC_API_KEY env var is set and the static admin user has been materialized on a previous call).
var ErrAlreadyExists = errors.New("pocketid: resource already exists")
ErrAlreadyExists is returned by create-style methods (e.g. CreateUserGroup) when the resource is already present at the API. Callers can use errors.Is to detect this and treat it as success (idempotent semantics).
var ErrNotFound = errors.New("pocketid: resource not found")
ErrNotFound identifies an exact PocketID resource lookup miss.
Functions ¶
func ActivationToken ¶ added in v0.47.3
ActivationToken returns the one-time code of an exact ActivationURL link and reports whether the link has exactly that shape (no user info, fragment or extra query). Callers still check the scheme and host.
func ActivationURL ¶ added in v0.47.3
ActivationURL is the link a new owner or household member follows to enroll a passkey. PocketID v2 serves one-time codes at /lc/<code> (an alias for /login/alternative/code); it has no /setup-account route, which would drop the token and land on /login.
Types ¶
type Client ¶
Client is a thin HTTP client for the PocketID admin API.
func NewClient ¶
NewClient creates a PocketID admin client. baseURL must NOT end in a slash; it is the public origin (e.g. "https://id.stack.local"). adminToken is the value rendered into the PocketID container as STATIC_API_KEY.
func (*Client) AddUserToGroup ¶
AddUserToGroup adds a user to a group identified by ID. PocketID's API uses group IDs (UUIDs), not names, so callers must resolve the name to an ID first via GetGroupIDByName.
func (*Client) BootstrapInitialAdmin ¶
BootstrapInitialAdmin verifies that the configured AdminToken is accepted by PocketID. PocketID v2 has no /api/setup JSON endpoint; admin bootstrap is performed by setting STATIC_API_KEY on the container, after which the first authenticated request materializes a built-in "Static API User" admin. This method exercises that path and reports success or failure.
The email/username/password parameters are accepted for API symmetry with the original Phase-1 plan but are ignored — they have no equivalent in the PocketID v2 setup flow. Callers should instead invoke CreateUser to provision the human owner account after this returns.
Returns:
- ErrAlreadyBootstrapped when the token is already accepted (the normal case once STATIC_API_KEY has been provisioned).
- A wrapped HTTP/network error otherwise.
The plan-spec signature returned (adminToken string, err error). The returned token is always c.AdminToken on success — callers that need to persist a token should use the value they passed into NewClient.
func (*Client) ConfigureEmailVerificationSMTP ¶ added in v0.47.7
func (c *Client) ConfigureEmailVerificationSMTP(ctx context.Context, smtp SMTPConfiguration) error
ConfigureEmailVerificationSMTP preserves PocketID's complete current application configuration while replacing the bounded SMTP and email verification values. PocketID v2.16 exposes a full-replacement API, so a partial request would reset unrelated identity policy to defaults.
func (*Client) CreateOIDCClientSecret ¶
CreateOIDCClientSecret adds a secret to the confidential client. The raw value is chosen locally, returned once, and must remain in local private custody. Callers invoke this only when no custodied secret is available.
func (*Client) CreateOneTimeAccessToken ¶
func (c *Client) CreateOneTimeAccessToken(ctx context.Context, userID string, ttl time.Duration) (string, error)
CreateOneTimeAccessToken issues a one-time-access token for the given user that the holder redeems through ActivationURL (`/lc/<token>`) to enroll a WebAuthn credential. PocketID v2 is passkey-only, so this is the only way to bootstrap a freshly-provisioned owner account into a usable state.
PocketID v2.7 accepts the TTL as a Go-duration string in the `ttl` field. The returned string is the raw token (not a full URL); callers compose the setup URL themselves.
Endpoint: `POST /api/users/:id/one-time-access-token`.
func (*Client) CreateUser ¶
CreateUser registers a new PocketID account. Returns the created user (including server-assigned ID).
func (*Client) CreateUserGroup ¶
func (c *Client) CreateUserGroup(ctx context.Context, req CreateUserGroupRequest) (*UserGroup, error)
CreateUserGroup creates a new user group in PocketID and returns the created object. A 409 Conflict (group already exists) maps to ErrAlreadyExists so callers can treat re-runs as idempotent — chain it with errors.Is(err, pocketid.ErrAlreadyExists) at the call site.
Wire format empirically verified against PocketID v2.6.x by Task 14's integration test:
POST /api/user-groups
body: {"name":"...","friendlyName":"..."}
201 Created -> {"id":"...","name":"...","friendlyName":"...",...}
409 Conflict -> already exists (idempotent for callers)
func (*Client) DeleteUser ¶
DeleteUser removes one PocketID subject. Callers must refuse owner and break-glass identities before invoking this.
func (*Client) FindUsersByUsername ¶
FindUsersByUsername searches PocketID and returns only exact username matches. PocketID's server-side search is deliberately fuzzy, so the local owner binder must never accept a near-match as identity evidence.
func (*Client) GetGroupIDByName ¶
GetGroupIDByName resolves a user-group name to its server-side ID. Returns "" with a nil error if no group matches.
func (*Client) GetOIDCClient ¶
GetOIDCClient reads the exact client including its allowed group projection.
func (*Client) ListUserWebAuthnCredentials ¶
func (c *Client) ListUserWebAuthnCredentials(ctx context.Context, userID string) ([]WebAuthnCredential, error)
ListUserWebAuthnCredentials returns passkeys registered to one exact user.
func (*Client) ListUsers ¶
ListUsers returns every current user page. Pocket ID v2.16 clamps requests past the end back to the final page, so pagination metadata determines when to stop. Duplicate subjects or a changing collection fail closed. https://github.com/pocket-id/pocket-id/blob/v2.16.0/backend/internal/utils/list_request_util.go
func (*Client) OIDCIssuer ¶ added in v0.47.7
OIDCIssuer reads the running server's discovery identity, rather than local desired configuration. Domain migration uses it before passkey reenrollment.
func (*Client) RegisterOIDCClient ¶
func (c *Client) RegisterOIDCClient(ctx context.Context, req RegisterClientRequest) (*OIDCClient, error)
RegisterOIDCClient creates an OIDC client (e.g. TinyAuth) and immediately generates a secret only for a confidential client. Public PKCE clients never create or expose a shared secret. The returned OIDCClient.Secret is the raw value — record it; PocketID will not return it again.
func (*Client) RetireOneTimeAccessToken ¶ added in v0.47.7
RetireOneTimeAccessToken makes a previously issued one-time-access token unusable. PocketID v2.16 has no revocation endpoint and keeps a token redeemable until it is exchanged or its TTL ends, so the only way to retire a superseded link is to redeem it here: the exchange atomically deletes the token, and the session it returns is discarded unread. PocketID records the exchange as a one-time-access sign-in in its audit log.
A 401 means the token was already consumed or has expired, which is the wanted end state. The token is a secret: it never appears in an error.
Endpoint: `POST /api/one-time-access-token/:token` (unauthenticated).
func (*Client) UpdateOIDCClient ¶ added in v0.47.3
func (c *Client) UpdateOIDCClient(ctx context.Context, clientID string, req RegisterClientRequest) (*OIDCClient, error)
UpdateOIDCClient replaces the mutable settings of an existing client (name, callbacks, public/PKCE and group restriction). The secret is unchanged.
func (*Client) UpdateOIDCClientAllowedUserGroups ¶
func (c *Client) UpdateOIDCClientAllowedUserGroups( ctx context.Context, clientID string, groupIDs []string, ) (*OIDCClient, error)
UpdateOIDCClientAllowedUserGroups binds the client to the complete desired PocketID group-ID set.
func (*Client) UpdateUserCustomClaims ¶ added in v0.47.7
func (c *Client) UpdateUserCustomClaims(ctx context.Context, subject string, claims []CustomClaim) error
UpdateUserCustomClaims replaces the complete per-user claim set. Callers must merge any intended change with GetUser.CustomClaims first; Pocket ID deletes omitted keys. No group claim or user administrator flag is changed. https://github.com/pocket-id/pocket-id/blob/v2.16.0/backend/internal/service/custom_claim_service.go
type CreateUserGroupRequest ¶
type CreateUserGroupRequest struct {
Name string `json:"name"`
FriendlyName string `json:"friendlyName"`
}
CreateUserGroupRequest is the body for POST /api/user-groups. PocketID expects both `name` (machine identifier, must be unique) and `friendlyName` (display label).
type CreateUserRequest ¶
type CreateUserRequest struct {
Username string `json:"username"`
Email string `json:"email,omitempty"`
FirstName string `json:"firstName,omitempty"`
LastName string `json:"lastName,omitempty"`
DisplayName string `json:"displayName,omitempty"`
IsAdmin bool `json:"isAdmin"`
EmailVerified bool `json:"emailVerified,omitempty"`
Disabled bool `json:"disabled,omitempty"`
UserGroupIDs []string `json:"userGroupIds,omitempty"`
}
CreateUserRequest is the payload for CreateUser. Email is optional but when set must be a valid address. FirstName is recommended (PocketID stores it as a non-nullable column). IsAdmin grants admin scope.
type CustomClaim ¶ added in v0.47.7
CustomClaim is the per-user key/value DTO in pinned Pocket ID v2.16.0: https://github.com/pocket-id/pocket-id/blob/v2.16.0/backend/internal/dto/custom_claim_dto.go User.CustomClaims contains this user's own claims, without inherited groups. Pocket ID emits custom claims only when the client requests the profile scope, and user claims take precedence over group claims with the same key: https://github.com/pocket-id/pocket-id/blob/v2.16.0/backend/internal/oidc/claims_service.go https://github.com/pocket-id/pocket-id/blob/v2.16.0/backend/internal/service/custom_claim_service.go
type OIDCClient ¶
type OIDCClient struct {
Credentials struct {
FederatedIdentities []json.RawMessage `json:"federatedIdentities"`
} `json:"credentials"`
RequiresReauthentication bool `json:"requiresReauthentication"`
PkceEnabled bool `json:"pkceEnabled"`
ID string `json:"id"`
Name string `json:"name"`
CallbackURLs []string `json:"callbackURLs"`
IsPublic bool `json:"isPublic"`
IsGroupRestricted bool `json:"isGroupRestricted"`
AllowedUserGroups []UserGroup `json:"allowedUserGroups,omitempty"`
Secret string `json:"-"`
}
OIDCClient describes a registered OIDC client. Secret is only populated after CreateClientSecret returns.
type RegisterClientRequest ¶
type RegisterClientRequest struct {
RequiresReauthentication bool `json:"requiresReauthentication"`
ID string `json:"id,omitempty"`
Name string `json:"name"`
CallbackURLs []string `json:"callbackURLs"`
IsPublic bool `json:"isPublic"`
PkceEnabled bool `json:"pkceEnabled,omitempty"`
IsGroupRestricted bool `json:"isGroupRestricted"`
}
RegisterClientRequest is the payload for RegisterOIDCClient.
type SMTPConfiguration ¶ added in v0.47.7
type SMTPConfiguration struct {
Host string
Port int
From string
User string
Password string
TLS string
}
SMTPConfiguration is the owner-approved PocketID delivery configuration used for email verification. StackKits deliberately does not expose PocketID's plaintext or skip-verification modes.
type User ¶
type User struct {
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email,omitempty"`
FirstName string `json:"firstName,omitempty"`
LastName string `json:"lastName,omitempty"`
DisplayName string `json:"displayName,omitempty"`
IsAdmin bool `json:"isAdmin"`
EmailVerified bool `json:"emailVerified"`
Disabled bool `json:"disabled,omitempty"`
UserGroups []UserGroup `json:"userGroups,omitempty"`
CustomClaims []CustomClaim `json:"customClaims,omitempty"`
}
User is the subset of the PocketID user DTO we care about.
type UserGroup ¶
type UserGroup struct {
ID string `json:"id"`
Name string `json:"name"`
FriendlyName string `json:"friendlyName"`
}
UserGroup is the subset of the PocketID user-group DTO we care about.
type WebAuthnCredential ¶
WebAuthnCredential is the secret-free registration metadata returned by the admin user credential endpoint.