jambo

package module
v0.1.5 Latest Latest
Warning

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

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

README

jambo

jambo is a Go package to build an OpenID Connect provider (OIDC server).

"Jambo" is also a Swahili word. It translates to "hello" or "hi". It's a common greeting used in East Africa, particularly in Tanzania and Kenya.

In order to create a OpenID Connect provider, you will have to create a new server, which is in turn a HTTP handler.

This is a complete example:

package main

import (
	"net/http"

	"github.com/cespedes/jambo"
)

func main() {
	issuer := "https://example.com/oidc"
	root := "/oidc"
	s := jambo.NewServer(issuer, root)

	clientID := "test-client"
	clientSecret := "client-secret"
	client := s.NewClient(clientID, clientSecret)
	client.AddAllowedRedirectURIs("https://example.com/callback")

	s.SetAuthenticator(func (req *jambo.Request) jambo.Response {
		if req.Params["login"] == "admin" && req.Params["password"] == "secret" {
			return jambo.Response{
				Type: jambo.ResponseTypeLoginOK,
				Login:  "admin",
				Name:   "Charlie Root",
				Claims: map[string]any{},
			}
		}
		return jambo.Response{
			Type: jambo.ResponseTypeLoginFailed,
			Login: req.Params["login"],
		}
	})

	http.ListenAndServe(":8080", s)
}

It will create a HTTP server, listening to requests under root and providing all the necessary handlers for the OIDC provider.


The OpenID Connect specification is here:

  • https://openid.net/specs/openid-connect-discovery-1_0.html
  • https://openid.net/specs/openid-connect-core-1_0.html

HTTP server endpoints

endpoint description
/.well-known/openid-configuration OpenID Connect configuration
/auth HTML page to ask for credentials
POST /auth/login used by end users to send login information (password, OTP...) to the server
POST /token used by clients to send the code (or a refresh token) and get id token and access token
/keys get the list of keys used to sign the tokens
/userinfo used by clients to get Claims from the access token
/.well-known/ssf-configuration Shared Signals Framework (SSF) transmitter configuration
/ssf/stream SSF stream management: create/read/update/delete (POST/GET/PATCH/PUT/DELETE)
/ssf/status, /ssf/subjects:add, /ssf/subjects:remove, /ssf/verify rest of the SSF stream management API
POST /ssf/poll/{stream_id} poll delivery: receivers pull pending Security Event Tokens from here

Workflow

We will assume Alice (a user) wants to connect to a GitLab instance (a client), which is configured to authenticate using Jambo, our OpenID Connect provider.

  • Alice opens a web browser and goes to GitLab page (https://gitlab.example.com).
  • GitLab redirects to Jambo's authentication page (https://jambo.example.com/auth), with query parameters specifying the client (GitLab) and the list of the required scopes.
  • Jambo reads the query parameters and checks if the client exists and the URL is well-formed.
  • Jambo creates a session for this connection and stores its state.
  • Jambo parses a HTML template and offers it to Alice a login page (typically with a HTML form).
  • Alice fills the user and password and presses "submit".
  • The form is posted to the authentication page (https://jambo.example.com/auth/login).
  • Jambo receives the request, checks if it comes from an active session, and calls the Authenticator function with all the parameters received from the form.
  • The Authentication function checks the parameters and returns a "Login OK".
  • Optionally, the Authenticator can ask for another step (for example a one-time code) by returning ResponseTypeRedirect with the name of an HTML template to show, and the flow goes back to the previous step when that form is submitted.
  • Once the login is OK, Jambo redirects Alice to GitLab's "callback address"
  • GitLab receives the request with a "code"
  • GitLab connects to Jambo in background, sending the "code" and the "client secret".
  • Jambo replies with an access token (also sent as id token): a signed JWT (JWS) whose claims (login, name, e-mail...) depend on the requested scopes.
  • GitLab receives the response and sends Alice the GitLab page, already authenticated.

Login sessions

Each /auth request creates a session (identified by a random value that is also the authorization code), kept in memory only:

  • It can be used for 10 minutes. The code is single-use: once it has been redeemed at /token, it can't be used again.
  • Sessions that have expired or been used are not deleted right away: they are kept for 24 hours after being created, so that an HTML template can still tell which client they belonged to (the client template value is set from Server.GetConnection in /auth and in /auth/login).
  • If a login form is submitted for a session that has been used, has expired or is unknown, /auth/login renders error.html with a message telling which of the three it is (without repeating the session identifier). /token always answers invalid_grant in all those cases, as RFC 6749 requires; with SetDebug(true) it logs the real reason.

Reconfiguring a Server at runtime

Server.NewClient(id, secret) and Server.RemoveClient(id) let a host application apply a changed configuration -- e.g. reloaded from a config file on SIGHUP -- to a Server that's already serving requests, without restarting it:

client := s.NewClient(clientID, newSecret) // drops the old one under this id first, if any
client.AddAllowedRedirectURIs(newRedirectURIs...)
client.AddAllowedScopes(newScopes...)

Calling NewClient again with an id that's already registered atomically replaces that *Client; any refresh tokens or SSF streams already created under that client id are untouched (they're keyed by the id string, not by the *Client value) and remain reachable through the newly configured client -- this is what you want when the config change is still, conceptually, the same client.

Server.RemoveClient(id) is a hard delete instead: besides removing the client itself, it also deletes that client id's refresh tokens and SSF streams from Storage, precisely so that reusing the same id later (e.g. a plain NewClient call for a completely unrelated client) starts clean rather than silently inheriting whatever the previous occupant of that id left behind. Existing tokens and Connections it already handed out keep working until they'd next need the client looked up again, but no new /auth or /token request will find it.

Server.ReplacePresentation(staticFS, templatesFS, templateArgs) does the same for the other reloadable part of a Server's configuration -- static files, HTML templates and template args -- letting a web_static/ web_templates/template-args change from a reloaded config apply without restarting:

s.ReplacePresentation(newStaticFS, newTemplatesFS, newTemplateArgs)

It always builds the new static files/templates/args from scratch (layered on jambo's embedded defaults) before atomically swapping them in -- templateArgs is replaced outright, not merged with whatever was set before -- which is what makes it safe to call on a Server already serving live traffic. staticFS/templatesFS may be nil to mean "just the embedded defaults, no override." Client configuration is untouched -- combine it with NewClient/RemoveClient above for a full reload.

Template values for one client

Besides the values given to ReplacePresentation, which every template receives, each client can have values of its own, available to the templates rendered during its logins:

client.AddTemplateArgs(map[string]string{"url": "https://gitlab.example.com/"})

In the template this is just {{ .url }}. Values from a client replace the Server-wide ones with the same name, and the values Jambo itself passes (client, which is the client's id, and, depending on the page, session, postURL, error...) take precedence over both. Like the rest of a client's configuration, they start empty again when NewClient replaces the client.

Shared Signals Framework (SSF)

Jambo can also act as an SSF transmitter (OpenID Shared Signals Framework 1.0), so a receiver such as Apple Business Manager can subscribe to security events (e.g. "a session was revoked") instead of, or in addition to, doing SSO through it. This is what Apple Business Manager's federated-authentication setup requires when it asks for an "SSF configuration URL" and the ssf.manage/ssf.read scopes.

To wire it up:

client := s.NewClient(clientID, clientSecret)
client.AddAllowedScopes("offline_access", "ssf.manage", "ssf.read")
client.AddSSFEventsSupported(jambo.EventCAEPSessionRevoked, jambo.EventCAEPCredentialChange)
  • offline_access makes /token also return a refresh token, so the receiver can keep calling the SSF management API long after the user's session ends (a standard OAuth2 grant_type=refresh_token flow, not SSF-specific).
  • AddSSFEventsSupported declares which event types this client's streams may ever receive; a receiver requests a subset of these when it creates a stream through the /ssf/stream management API.

The receiver creates and manages its own stream (delivered by push or poll, its choice) using an OAuth access token obtained through the normal authorization code flow. To actually notify it of something, call:

s.EmitSecurityEvent(clientID, jambo.EventCAEPSessionRevoked, jambo.Subject{
	Format: jambo.SubjectFormatEmail,
	Email:  "alice@example.com",
}, nil)

which signs a Security Event Token and delivers it to every enabled stream of that client that requested the event type and has that subject registered.

Every stream also implicitly supports the built-in "verification" event type, regardless of what the receiver asked for, and Jambo pushes one right after creating the stream, without being asked: several real receivers (Apple Business Manager among them) wait to receive this before considering the stream successfully set up, rather than calling /ssf/verify themselves.

Trust model for /ssf/subjects:add: a receiver registers which subjects it wants events about itself, via its own ssf.manage-scoped token -- Jambo does not check that the subject has ever actually authenticated through that specific client, or has any other relationship to it, before accepting the registration. Any client with ssf.manage can therefore ask to be notified about any subject at all. This is fine as long as ssf.manage is only ever granted, in the host application's own client configuration, to receivers that are themselves trusted with that scope of visibility (e.g. as of this writing, the only place this is wired up grants it to a single, IT-managed client) -- but if a host application ever grants ssf.manage to more than one client, each of those clients can silently monitor events for subjects that have nothing to do with it. A host application that needs to prevent that has to enforce it itself (e.g. by recording which subjects have actually authenticated through a given client, and consulting that before calling EmitSecurityEvent or before allowing subjects:add to succeed for a subject outside that set) -- Jambo does not do this on its own.

Refresh tokens and SSF streams (unlike the short-lived login state used during SSO) are meant to outlive a process restart. Jambo keeps them in memory by default (MemoryStorage), which is fine for development but loses everything on restart; a production deployment should implement the small Storage interface against its own datastore and install it with Server.SetStorage before serving traffic.

Other OpenID Connect providers

Documentation

Index

Constants

View Source
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.

View Source
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.

View Source
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

func (c *Client) AddAllowedRedirectURIs(names ...string)

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

func (c *Client) AddAllowedRoles(names ...string)

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

func (c *Client) AddAllowedScopes(names ...string)

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

func (c *Client) AddSSFEventsSupported(events ...string)

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

func (c *Client) AddTemplateArgs(args map[string]string)

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

func (idt IDToken) MarshalJSON() ([]byte, error)

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

func NewServer(issuer, root string) *Server

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

func (s *Server) NewClient(name, secret string) *Client

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

func (s *Server) RemoveClient(id string) bool

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

func (s *Server) SetAuthenticator(f func(req *Request) Response)

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

func (s *Server) SetConnection(r *http.Request, conn *Connection) *http.Request

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

func (s *Server) SetDebug(enabled bool)

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

func (s *Server) SetSSFAllowPrivatePush(allow bool)

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

func (s *Server) SetStorage(storage Storage)

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.

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.

Jump to

Keyboard shortcuts

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