Documentation
¶
Overview ¶
Package identity defines the user profile shared by authentication, the identity API (backyard.id), presence and audit logging.
Index ¶
- Constants
- func EmailDomain(email string) string
- func NormalizeEmail(s string) string
- func ParseDirectoryCSV(r io.Reader) (map[string]*User, error)
- func ParseDirectoryJSON(r io.Reader) (map[string]*User, error)
- func ParseList(v any) []string
- func WithUser(ctx context.Context, u *User) context.Context
- type Directory
- type Summary
- type User
- type UserCache
Constants ¶
const ( DefaultEmailClaim = "email" DefaultNameClaim = "name" DefaultTitleClaim = "title" DefaultTeamClaim = "department" DefaultSlackClaim = "slack" DefaultAvatarClaim = "picture" DefaultGroupsClaim = "groups" )
Default claim names, used for fields left empty in a config.ClaimsMapping.
const ( // SystemSite is the reserved docstore namespace for system data. SystemSite = "_system" // UsersCollection holds one document per user, keyed by lowercased email. UsersCollection = "_users" )
Location of the user cache in the document store (SPEC §10).
const ( // TouchInterval is the minimum time between two cache writes for the same // user on one instance. TouchInterval = 10 * time.Minute )
Variables ¶
This section is empty.
Functions ¶
func EmailDomain ¶
EmailDomain returns the lowercased domain part of an e-mail address, or "".
func NormalizeEmail ¶
NormalizeEmail trims and lowercases s and returns it when it is a plain e-mail address (local@domain, no display name, no spaces); otherwise it returns "".
func ParseDirectoryCSV ¶
ParseDirectoryCSV parses a CSV people directory (see NewDirectory) into a map keyed by lowercased email. Rows without a valid email are skipped; a later row for the same email replaces an earlier one.
func ParseDirectoryJSON ¶
ParseDirectoryJSON parses a JSON people directory: an array of user objects or an object with a "users" array (see NewDirectory).
Types ¶
type Directory ¶
type Directory interface {
// Lookup returns the profile for an email, or (nil, nil) if unknown.
Lookup(ctx context.Context, email string) (*User, error)
}
Directory looks up people profiles (static file, user cache, …).
func NewDirectory ¶
func NewDirectory(cfg config.DirectoryConfig) (Directory, error)
NewDirectory loads the configured static directory (nil, nil when type is "").
Type "csv" expects a header row containing an "email" column; recognised columns are name, title, team (alias department), slack, avatar_url (aliases avatar, picture) and groups (';'-separated). Any other non-empty column is copied into User.Extra. Type "json" expects an array of user objects (or {"users": [...]}) with the same field names; groups may be an array or a ';'/','-separated string, and an "extra" object as well as any unknown keys end up in User.Extra.
Lookups are case-insensitive on the email. When cfg.Reload is positive the file is re-checked (modification time and size) at most once per interval, on lookup, and re-read in the background when it changed; a failed reload keeps the previous contents.
type Summary ¶
type Summary struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"`
AvatarURL string `json:"avatar_url,omitempty"`
}
Summary is the compact form embedded in realtime presence and messages.
type User ¶
type User struct {
// ID is stable: lowercased email when known, else "sub:<issuer-sub>",
// "token:<name>" for static tokens, or "anon:<16 hex>" when anonymous.
ID string `json:"id"`
Email string `json:"email,omitempty"`
Name string `json:"name"`
Title string `json:"title,omitempty"`
Team string `json:"team,omitempty"`
Slack string `json:"slack,omitempty"`
AvatarURL string `json:"avatar_url,omitempty"`
Groups []string `json:"groups,omitempty"`
Anonymous bool `json:"anonymous"`
Admin bool `json:"admin"`
Extra map[string]any `json:"extra,omitempty"`
}
User is the authenticated (or anonymous) principal of a request. Its JSON shape is part of the public client API (see docs/spec/SPEC.md §4).
func FromContext ¶
FromContext returns the user stored by WithUser, or nil.
func MapClaims ¶
MapClaims builds a User from IdP claims according to mapping. The id is the lowercased email when present, else "sub:"+sub. Admin is NOT set here.
Claim names may address nested objects with dots ("custom.team") when no top-level claim of that exact name exists. The groups claim may be a JSON array or a comma-separated string. The name falls back to given_name + family_name, then to the local part of the email, then to preferred_username and finally to sub. Claims listed in extra are copied verbatim into User.Extra. When neither a valid email nor a sub is present the returned user has an empty ID, which callers must reject.
When the email is read from the standard "email" claim and the claims carry "email_verified": false, the email is ignored (the user is identified by sub): an unverified address must not satisfy allowed_domains or match an admin entry.
func (*User) Clone ¶
Clone returns a deep copy of u (nil for nil). Groups and Extra are copied so the clone can be modified without affecting u.
func (*User) Enrich ¶
Enrich overlays a profile p (typically a directory entry for the same email) onto u: non-empty Name, Title, Team, Slack and AvatarURL of p replace u's values, groups are merged (union, case-insensitive, u's order first) and Extra keys of p override u's. ID, Email, Anonymous and Admin are never changed. A nil u or p is a no-op.
A name of p that is merely the local part of p's email (the fallback used for directory entries without a name) does not replace a name u already has, so a sparse directory never downgrades "Alice Smith" to "alice".
type UserCache ¶
type UserCache struct {
// contains filtered or unexported fields
}
UserCache stores profiles of users who have used Backyard in the docstore (site "_system", collection "_users", doc id = lowercased email) so that GET /users/{email} can resolve colleagues. Writes are throttled per user.
The cache never stores anonymous users, static-token principals or users without an email. Admin flags are not persisted (they are derived from the configuration at request time). A UserCache is safe for concurrent use.
func NewUserCache ¶
NewUserCache returns a cache backed by store. fallback (optional) is consulted first (e.g. the static directory).
func (*UserCache) Lookup ¶
Lookup implements Directory.
The fallback directory is consulted first; the cached profile (if any) is then enriched with the directory entry, so directory data wins over data last seen from the IdP. It returns (nil, nil) for unknown emails.
func (*UserCache) Touch ¶
Touch records u (no-op for anonymous users or users without email; throttled).
The write happens synchronously (bounded by a short timeout and detached from ctx cancellation) at most once per TouchInterval per user and instance; failures are logged and retried after a minute.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package identitytest holds integration tests that run the identity user cache against the real docstore backends (memory, SQLite and, when the BACKYARD_TEST_* services are available, PostgreSQL and DynamoDB Local).
|
Package identitytest holds integration tests that run the identity user cache against the real docstore backends (memory, SQLite and, when the BACKYARD_TEST_* services are available, PostgreSQL and DynamoDB Local). |