Documentation
¶
Index ¶
- Constants
- type Client
- type Connection
- type Delivery
- type IDToken
- type MemoryStorage
- func (m *MemoryStorage) AckEvent(streamID, jti string) error
- func (m *MemoryStorage) DeleteRefreshToken(token string) error
- func (m *MemoryStorage) DeleteRefreshTokensForClient(clientID string) error
- func (m *MemoryStorage) DeleteStream(streamID string) error
- func (m *MemoryStorage) GetRefreshToken(token string) (RefreshToken, bool, error)
- func (m *MemoryStorage) GetStream(streamID string) (Stream, bool, error)
- func (m *MemoryStorage) ListStreams(clientID string) ([]Stream, error)
- func (m *MemoryStorage) PendingEvents(streamID string, max int) ([]PendingEvent, bool, error)
- func (m *MemoryStorage) QueueEvent(streamID string, event PendingEvent) error
- func (m *MemoryStorage) SaveRefreshToken(rt RefreshToken) error
- func (m *MemoryStorage) SaveStream(stream Stream) error
- type PendingEvent
- type RefreshToken
- type Request
- type Response
- type ResponseType
- type Server
- func (s *Server) EmitSecurityEvent(clientID, eventType string, subject Subject, claims map[string]any) error
- func (s *Server) GetConnection(r *http.Request) *Connection
- func (s *Server) NewClient(name, secret string) *Client
- func (s *Server) RemoveClient(id string) bool
- func (s *Server) ReplacePresentation(staticFS, templatesFS fs.FS, templateArgs map[string]string) error
- func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (s *Server) SetAuthenticator(f func(req *Request) Response)
- func (s *Server) SetConnection(r *http.Request, conn *Connection) *http.Request
- func (s *Server) SetDebug(enabled bool)
- func (s *Server) SetSSFAllowPrivatePush(allow bool)
- func (s *Server) SetStorage(storage Storage)
- type Storage
- type Stream
- type Subject
Constants ¶
const ( EventCAEPSessionRevoked = "https://schemas.openid.net/secevent/caep/event-type/session-revoked" EventCAEPCredentialChange = "https://schemas.openid.net/secevent/caep/event-type/credential-change" EventCAEPTokenClaimsChange = "https://schemas.openid.net/secevent/caep/event-type/token-claims-change" )
CAEP (Continuous Access Evaluation Profile) event type URIs. A Client only ever has events delivered on a stream if the event type is both requested by the receiver and declared here via Client.AddSSFEventsSupported.
const ( SubjectFormatEmail = "email" // Subject.Email is set SubjectFormatIssSub = "iss_sub" // Subject.Iss and Subject.Sub are set SubjectFormatOpaque = "opaque" // Subject.ID is set )
Subject identifier formats (RFC 9493) accepted as Subject.Format.
const ( StreamStatusEnabled = "enabled" // events are delivered normally StreamStatusPaused = "paused" // no events are delivered, but the stream still exists StreamStatusDisabled = "disabled" // no events are delivered )
Values a Stream's (internal) Status field may hold; also the values accepted by the /ssf/status management endpoint.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is an OAuth2/OIDC client (a relying party) allowed to authenticate through a Server, created (or reconfigured) with Server.NewClient.
func (*Client) AddAllowedRedirectURIs ¶ added in v0.0.2
AddAllowedRedirectURIs adds one or more URIs c is allowed to redirect back to after a successful login. /auth rejects a request whose redirect_uri isn't one of these.
func (*Client) AddAllowedRoles ¶ added in v0.0.2
AddAllowedRoles adds one or more roles a user must belong to at least one of in order to log in through c. If none are ever added, any authenticated user is allowed.
func (*Client) AddAllowedScopes ¶ added in v0.0.2
AddAllowedScopes adds one or more scopes c is allowed to request beyond the standard "openid"/"profile"/"email" ones (e.g. "offline_access", "ssf.manage", or an application-specific scope). /auth rejects a request for any other scope it doesn't recognize.
func (*Client) AddSSFEventsSupported ¶ added in v0.0.13
AddSSFEventsSupported declares the Shared Signals Framework event type URIs (see the EventCAEP* constants) that this client's streams may receive. A receiver's stream is only ever delivered events that are both requested by the receiver when the stream is created and declared supported here.
func (*Client) AddTemplateArgs ¶ added in v0.1.5
AddTemplateArgs adds values that the HTML templates rendered for c's logins receive in addition to the Server-wide ones (see Server.ReplacePresentation); a key set here replaces the Server-wide value of the same name. The values a handler passes itself (e.g. "session" or "postURL") and "client" (c's id) always take precedence over these. They are only available while a template is rendered with c's Connection at hand (see Server.SetConnection), as /auth and /auth/login do.
type Connection ¶
type Connection struct {
// contains filtered or unexported fields
}
Connection holds the state of one login flow, from the /auth request that started it until it is redeemed at /token, expires, or is purged (see connectionTTL and connectionRetention). It has no exported fields or methods; a host application only ever passes one it got from Server.GetConnection to Server.SetConnection.
type Delivery ¶ added in v0.0.13
type Delivery struct {
Method string `json:"method"` // one of the delivery method URIs isPushDeliveryMethod/isPollDeliveryMethod accept
EndpointURL string `json:"endpoint_url,omitempty"` // where SETs are POSTed to (push) or polled from (poll)
AuthorizationHeader string `json:"authorization_header,omitempty"` // sent as "Authorization" on each push, if set
}
Delivery describes how Security Event Tokens are transported for a stream: either pushed by the transmitter (RFC 8935) to EndpointURL, or polled by the receiver (RFC 8936) from EndpointURL.
type IDToken ¶
type IDToken struct {
// Standard claims:
Issuer string `json:"iss"`
SubjectIdentifier string `json:"sub"`
Audience string `json:"aud"`
Expiration int64 `json:"exp"`
IssuedAt int64 `json:"iat"`
Scope string `json:"scope,omitempty"`
Nonce string `json:"nonce,omitempty"`
PreferredUsername string `json:"preferred_username,omitempty"`
Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
EmailVerified bool `json:"email_verified,omitempty"`
// Other claims:
Claims map[string]any
}
IDToken is the set of claims signed into the JWS jambo issues as both "id_token" and "access_token" (see signToken). See https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.2.
func (IDToken) MarshalJSON ¶ added in v0.0.2
MarshalJSON implements json.Marshaler, emitting the standard claims in a fixed, spec-friendly order followed by whatever is in idt.Claims.
type MemoryStorage ¶ added in v0.0.13
type MemoryStorage struct {
// contains filtered or unexported fields
}
MemoryStorage is the default, in-memory Storage. It is fine for development or a single-process demo, but state is lost on restart — not suitable for a production SSF integration, where streams and refresh tokens must outlive process restarts and be visible to every replica handling requests from the receiver.
func NewMemoryStorage ¶ added in v0.0.13
func NewMemoryStorage() *MemoryStorage
NewMemoryStorage returns an empty MemoryStorage, ready to use.
func (*MemoryStorage) AckEvent ¶ added in v0.0.13
func (m *MemoryStorage) AckEvent(streamID, jti string) error
AckEvent implements Storage.
func (*MemoryStorage) DeleteRefreshToken ¶ added in v0.0.13
func (m *MemoryStorage) DeleteRefreshToken(token string) error
DeleteRefreshToken implements Storage.
func (*MemoryStorage) DeleteRefreshTokensForClient ¶ added in v0.1.0
func (m *MemoryStorage) DeleteRefreshTokensForClient(clientID string) error
DeleteRefreshTokensForClient implements Storage.
func (*MemoryStorage) DeleteStream ¶ added in v0.0.13
func (m *MemoryStorage) DeleteStream(streamID string) error
DeleteStream implements Storage.
func (*MemoryStorage) GetRefreshToken ¶ added in v0.0.13
func (m *MemoryStorage) GetRefreshToken(token string) (RefreshToken, bool, error)
GetRefreshToken implements Storage.
func (*MemoryStorage) GetStream ¶ added in v0.0.13
func (m *MemoryStorage) GetStream(streamID string) (Stream, bool, error)
GetStream implements Storage.
func (*MemoryStorage) ListStreams ¶ added in v0.0.13
func (m *MemoryStorage) ListStreams(clientID string) ([]Stream, error)
ListStreams implements Storage.
func (*MemoryStorage) PendingEvents ¶ added in v0.0.13
func (m *MemoryStorage) PendingEvents(streamID string, max int) ([]PendingEvent, bool, error)
PendingEvents implements Storage.
func (*MemoryStorage) QueueEvent ¶ added in v0.0.13
func (m *MemoryStorage) QueueEvent(streamID string, event PendingEvent) error
QueueEvent implements Storage.
func (*MemoryStorage) SaveRefreshToken ¶ added in v0.0.13
func (m *MemoryStorage) SaveRefreshToken(rt RefreshToken) error
SaveRefreshToken implements Storage.
func (*MemoryStorage) SaveStream ¶ added in v0.0.13
func (m *MemoryStorage) SaveStream(stream Stream) error
SaveStream implements Storage.
type PendingEvent ¶ added in v0.0.13
type PendingEvent struct {
JTI string // the SET's "jti" claim, used to ack/nack it when polling
SET string // compact JWS serialization
}
PendingEvent is a signed Security Event Token queued for a poll-delivery stream.
type RefreshToken ¶ added in v0.0.13
type RefreshToken struct {
Token string // the opaque bearer value the client presents at /token
ClientID string // the client it was issued to
Scopes []string // the scopes it was granted, reused for every token it's redeemed for
Response Response // carries the login/name/mail/claims to reissue tokens from
}
RefreshToken is the state kept for a refresh token issued when a client requests the "offline_access" scope (which it must first be granted via Client.AddAllowedScopes). It is persisted through Server's Storage, since -- unlike the short-lived authorization code -- it is meant to outlive a single login and, often, a process restart.
type Request ¶
type Request struct {
Session string // unique ID for this login attempt.
Client string // id of the OIDC client the user is signing in to.
Scopes []string // scopes the user has requested
Roles []string // roles allowed to sign in through Client; the authenticator should check the user belongs to at least one
Params map[string]string // form parameters submitted by the user (e.g. login, password, OTP)
}
Request is a message sent from the OIDC server to the authenticator, asking whether the given credentials are valid.
type Response ¶
type Response struct {
Type ResponseType
// If Type == ResponseTypeLoginFailed we can send an error to the user:
Error string
// If Type == ResponseTypeRedirect we need the name of the next template
// and the list of params that will be used to call it:
Redirect string
Params map[string]string
// Login is the user's login/username, usually the same value sent in
// the request. Used in claims "sub" and "preferred_username".
Login string
// Name is the user's full name (given name and surname). Used in claim "name".
Name string
// e-mail address. Used in claim "email".
Mail string
// Other claims:
Claims map[string]any
}
Response is sent from the authenticator to the OIDC server, answering a Request.
type ResponseType ¶
type ResponseType int
ResponseType is the kind of answer a Response carries; see the ResponseType* constants.
const ( ResponseTypeInvalid ResponseType = iota // zero value; an authenticator should never return this ResponseTypeLoginOK // login is successful ResponseTypeLoginFailed // login failed ResponseTypeRedirect // login is OK so far, but we are not finished yet )
type Server ¶
type Server struct {
sync.Mutex // to access clients, connections, and the webStatic/webTemplates/templateArgs presentation state
// contains filtered or unexported fields
}
Server is an OpenID Connect provider and an http.Handler. Create one with NewServer, register clients with Server.NewClient, and supply Server.SetAuthenticator before serving requests.
func NewServer ¶
NewServer creates a Server for the OpenID Connect provider identified by issuer, mounted under root. The returned Server has a fresh signing key, jambo's embedded default web assets, and an in-memory Storage; register at least one client with Server.NewClient and supply Server.SetAuthenticator before serving requests.
func (*Server) EmitSecurityEvent ¶ added in v0.0.13
func (s *Server) EmitSecurityEvent(clientID, eventType string, subject Subject, claims map[string]any) error
EmitSecurityEvent notifies every enabled stream belonging to clientID that requested eventType (one of the EventCAEP* constants, or a custom URI declared via Client.AddSSFEventsSupported) and that has subject registered (via the /ssf/subjects:add management call), delivering a signed Security Event Token: immediately, for push streams, or queued for the receiver to retrieve, for poll streams.
claims is merged into the SET's event-specific claims (e.g. {"reason": "password-reset"} for a credential-change event); it may be nil.
func (*Server) GetConnection ¶ added in v0.0.2
func (s *Server) GetConnection(r *http.Request) *Connection
GetConnection returns the Connection previously stored in r by SetConnection, or nil if none was.
func (*Server) NewClient ¶ added in v0.0.2
NewClient registers a client (a relying party) allowed to authenticate through s, identified by the OAuth2 client_id name and authenticated with the client_secret secret. Configure it further with Client.AddAllowedRedirectURIs and the other Client methods.
If a client is already registered under name, it's atomically replaced by this one -- so, e.g., a host can apply a changed configuration (a different secret, redirect URIs, scopes, roles or SSF events) by calling NewClient again with the same name, including on a Server that's already serving live traffic (e.g. on a config reload). None of the old Client's configuration carries over: the caller still needs to call AddAllowedRedirectURIs and any other AddAllowed*/ AddSSFEventsSupported on the returned Client, exactly as for a brand-new one.
That client id's state in Storage (refresh tokens, SSF streams) is untouched either way: it's keyed by the client id string rather than by the *Client value, and remains reachable through the new Client -- e.g. a receiver's existing SSF stream survives its client's redirect_uri or scopes being edited and reloaded. Only rely on this when name is meant to keep identifying the same client; to repurpose an id for an unrelated one, call Server.RemoveClient first (which does delete that state) and then NewClient.
func (*Server) RemoveClient ¶ added in v0.1.0
RemoveClient removes a previously registered client, so it can no longer authenticate, be looked up by /auth or /token, or call the SSF management API, and deletes that client id's refresh tokens and SSF streams from Storage. It returns false if no client with that id was registered (Storage is not touched in that case).
It is safe to call while the Server is serving requests (e.g. to apply a reloaded configuration without restarting), but it does not reach into anything already in flight: an in-progress Connection (a pending /auth/login) keeps its own pointer to the Client it started with and is unaffected, and neither are already-issued access/ID tokens -- they simply stop working the next time something needs to look the client up again (redeeming a code or refresh token, or calling an SSF endpoint). The Storage deletion is best-effort: a failure is logged when SetDebug(true) is in effect but otherwise not surfaced, since RemoveClient's own bool return has no room for a second error.
This is a hard delete precisely so that a client id can be reused later (e.g. a plain NewClient call after RemoveClient, for an unrelated client) without inheriting whatever the previous occupant of that id left behind. A host that instead wants to reconfigure the *same* client -- keeping its refresh tokens and SSF streams -- should just call NewClient again with the same id, not RemoveClient followed by NewClient.
func (*Server) ReplacePresentation ¶ added in v0.1.0
func (s *Server) ReplacePresentation(staticFS, templatesFS fs.FS, templateArgs map[string]string) error
ReplacePresentation resets the Server's static files, HTML templates and template arguments to a fresh copy of jambo's embedded defaults, then layers staticFS and templatesFS on top of them (either may be nil to mean "just the embedded defaults, no override"), and replaces templateArgs outright -- it is not merged with whatever was set before.
It always builds the new state from scratch -- a fresh *template.Template is parsed rather than reusing and mutating the existing one -- before atomically swapping it in, which is what makes it safe to call on a Server that's already serving live traffic (e.g. to apply web_static/web_templates/template args from a reloaded configuration, on SIGHUP): a request being handled concurrently sees either the old presentation or the new one, never a torn mix of both.
Client configuration (Server.clients) is untouched; see Server.NewClient for that.
func (*Server) ServeHTTP ¶
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler, dispatching every OIDC/SSF endpoint and static asset under s's root.
func (*Server) SetAuthenticator ¶ added in v0.0.3
SetAuthenticator installs the callback /auth/login calls to validate the credentials a user submitted, and decide whether to log them in.
func (*Server) SetConnection ¶ added in v0.0.2
SetConnection returns a copy of r whose context carries conn, so a later call to GetConnection on that request (or one derived from it) can retrieve it.
func (*Server) SetDebug ¶ added in v0.0.13
SetDebug enables or disables extra diagnostic logging, such as incoming requests and the reasons behind auth/token errors. It can be called at any time, even while the Server is serving requests, and takes effect on the next log line.
func (*Server) SetSSFAllowPrivatePush ¶ added in v0.0.13
SetSSFAllowPrivatePush disables the SSRF guard that otherwise rejects SSF push delivery endpoint_url values resolving to loopback, private or link-local addresses, and the requirement that they use https. Only meant for local development and tests, where a receiver's push endpoint is legitimately something like http://127.0.0.1:port/.
func (*Server) SetStorage ¶ added in v0.0.13
SetStorage installs the Storage used to persist refresh tokens and SSF streams. It replaces the default MemoryStorage installed by NewServer. Call it before the Server starts handling requests.
type Storage ¶ added in v0.0.13
type Storage interface {
// SaveRefreshToken creates or overwrites a refresh token, keyed by rt.Token.
SaveRefreshToken(rt RefreshToken) error
// GetRefreshToken looks up a previously saved refresh token by its value.
GetRefreshToken(token string) (rt RefreshToken, ok bool, err error)
// DeleteRefreshToken removes one refresh token. Deleting an unknown
// token is a no-op.
DeleteRefreshToken(token string) error
// DeleteRefreshTokensForClient removes every refresh token issued to
// clientID. Server.RemoveClient calls this, so a client id that gets
// reused later (e.g. NewClient after RemoveClient) never silently
// inherits tokens issued to whatever previously had that id.
DeleteRefreshTokensForClient(clientID string) error
// SaveStream creates or overwrites a stream, keyed by stream.StreamID.
SaveStream(stream Stream) error
// GetStream looks up a previously saved stream by its id.
GetStream(streamID string) (stream Stream, ok bool, err error)
// ListStreams returns every stream belonging to clientID.
ListStreams(clientID string) ([]Stream, error)
// DeleteStream removes one stream and any events queued for it.
// Deleting an unknown stream id is a no-op.
DeleteStream(streamID string) error
// QueueEvent appends a signed Security Event Token pending poll delivery.
QueueEvent(streamID string, event PendingEvent) error
// PendingEvents returns up to max not-yet-acknowledged events for
// streamID, oldest first, and whether more remain beyond those
// returned. A max <= 0 means "no limit" (moreAvailable is then always false).
PendingEvents(streamID string, max int) (events []PendingEvent, moreAvailable bool, err error)
// AckEvent removes an event from the poll queue once the receiver has
// acknowledged delivery. Acking an unknown (streamID, jti) is a no-op.
AckEvent(streamID, jti string) error
}
Storage persists the state that must survive process restarts and be reachable from every replica of a Server: OAuth refresh tokens and Shared Signals Framework (SSF) streams, including events queued for poll delivery.
Server ships MemoryStorage as a default. A host integrating with a receiver that expects long-lived streams and refresh tokens (such as Apple Business Manager) should implement Storage itself, backed by whatever datastore it already runs, and install it with Server.SetStorage before serving traffic.
type Stream ¶ added in v0.0.13
type Stream struct {
StreamID string `json:"stream_id"`
Iss string `json:"iss"`
Aud []string `json:"aud"`
EventsSupported []string `json:"events_supported,omitempty"`
EventsRequested []string `json:"events_requested"`
EventsDelivered []string `json:"events_delivered"`
Delivery Delivery `json:"delivery"`
Description string `json:"description,omitempty"`
Format string `json:"format,omitempty"` // default Subject format for this stream (RFC 9493)
MinVerificationInterval int `json:"min_verification_interval,omitempty"`
// Internal bookkeeping: not part of the JSON the receiver sees.
ClientID string `json:"-"`
Status string `json:"-"` // "enabled", "paused" or "disabled"
Subjects []Subject `json:"-"`
}
Stream is a Shared Signals Framework event stream, as created and managed by a receiver (e.g. Apple Business Manager) through the /ssf/stream management API.
type Subject ¶ added in v0.0.13
type Subject struct {
Format string `json:"format"`
Email string `json:"email,omitempty"`
Iss string `json:"iss,omitempty"`
Sub string `json:"sub,omitempty"`
ID string `json:"id,omitempty"` // used with Format == "opaque"
}
Subject identifies the principal a Security Event Token is about, using one of the formats from RFC 9493. Exactly the fields matching Format should be set.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
oidc-server
command
|
|
|
Package mergefs provides support for merging multiple filesystems together.
|
Package mergefs provides support for merging multiple filesystems together. |