mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-07 14:52:15 +00:00
# Which Problems Are Solved #12313 lets clients register themselves as OIDC applications (RFC 7591), but a dynamically registered client has no way to manage itself afterwards: - The registration response returns no `registration_access_token` and no `registration_client_uri`, which RFC 7591 and 7592 clients (including the MCP SDKs) expect in order to manage their own registration. - There is no way to read back, update or delete a registration without an operator acting on the client's behalf through the Console or the Management API. A client that needs to rotate a redirect URI has to ask a human. # How the Problems Are Solved Implement the [OAuth 2.0 Dynamic Client Registration Management Protocol (RFC 7592)](https://datatracker.ietf.org/doc/html/rfc7592): `GET`, `PUT` and `DELETE` on `/oauth/v2/register/{client_id}`, wired through the same `op.WithSetRouter` hook as the registration endpoint. ## Control plane: the same security settings, no new switch The management endpoints are gated on the `DynamicClientRegistrationSettings.enabled` introduced in #12313. No new setting, no feature flag: a client that may register may manage what it registered. When registration is disabled the endpoints answer `404`, exactly as `POST /oauth/v2/register` does. `allow_unauthenticated` selects the registration mode and does not apply here, because the registration access token authorizes these endpoints in both modes. ## Authorization: the registration access token, and nothing else RFC 7592 §3 defines the credential as the registration access token issued at registration and bound to a single client, not a user credential. So a client manages only itself, proving it with the token it was handed: | | Registration (`POST`) | Management (`GET` / `PUT` / `DELETE`) | | --- | --- | --- | | Credential | Access token, or none in open mode | Registration access token | | Permission | `project.app.register_dynamic` in token mode | None | Neither `project.app.register_dynamic`, which governs who may *create* clients, nor `project.app.write` applies to the management endpoints. One authorization path per endpoint is easier to reason about than two, and there is no user identity to check against in the first place. Operators keep managing dynamically registered clients through the Management API and the Console, like any other application; they cannot obtain a client's registration access token, by design. Status codes: a missing, malformed or non matching token is `401`; a client that no longer exists is `404`. The token's `client_id` is checked against the path before any lookup, so a token bound to another client is rejected without touching the database. ## The token itself - **Stored like a client secret.** Only its passwap hash is persisted, through a new `project.application.config.oidc.registration_token.changed` event; the plain token leaves the server exactly once, in the response that issues it. - **Bound to its client and organization** through authenticated encryption with the instance key, the same mechanism as refresh tokens. The opaque token handed to the client encrypts `client_id:org_id:secret`, so the endpoint recovers the binding before checking the secret against the stored hash. - **Verified in O(1)** against the hash on the projection, folded into the client lookup the handler already does, with a strongly consistent fallback to the eventstore so a token that was just rotated is accepted before it is projected. - **Rotated on update** (RFC 7592 §4.4) and invalidated implicitly when the client is deleted. A read does not rotate it. Every update rotates, including one that changes no metadata, so a client always gets a usable token back. ## Reusing the existing persistence A small refactor extracts the shared OIDC application creation and update logic (`pushOIDCApplication` gains a variadic `extraEvents`, and `oidcApplicationChangeEvent` is lifted out of `UpdateOIDCApplication`), so the dynamic commands reuse it without the permission check. The registration token event is pushed atomically with the application, so a registered client is never left unmanageable. # Additional Changes - Setup step **74** adds the `registration_token` column to the `apps7_oidc_configs` projection, with a reducer for the new event modelled on the client secret. Applications are not part of the relational storage projection, so there is no second write path to keep in sync. - The registration response (`POST`) now also carries `registration_access_token` and `registration_client_uri`. - Docs: a "Manage a registration" section in the dynamic client registration guide covering the three operations, the authorization model, token rotation and the new limitations, plus the management endpoints on the OIDC endpoints page. - Unit tests for the command layer (issue, verify, rotate, update, delete), the projection reducer, and the token binding and response mapping. An integration test covers the full lifecycle (register, read, update with rotation, delete, read again with `404`), a token bound to another client, and the endpoints not being served when registration is disabled. None of the integration calls carries a user access token, which pins down that the management endpoints require no permission of their own. # Additional Context - Part of the MCP authorization effort in #9810. Follow-up to #12313 (RFC 7591), which is now merged; this branch has been rebased on `main` and carries the settings and authorization model agreed there. - **Open question for the review**: the authorization model above (registration access token only, no operator path through these endpoints) is the one I proposed when #12313 was reviewed, but it was never explicitly confirmed. If you would rather have `project.app.write` also grant access to a client in the operator's own organization, that is a contained change in `authorizeClientManagement`. - Rotation is eventually consistent: right after an update the previous token may keep working for a brief moment until the rotation is projected, while the new token works immediately through the eventstore fallback. This matches how ZITADEL validates access tokens, where revocation is likewise eventually consistent. It is documented in the guide and asserted as such in the integration test. - A database migration is introduced for the new projection column. No proto change and no new feature flag. - Verified locally: full build, `go vet` including `-tags integration`, `golangci-lint` with 0 issues, and the unit suites for every touched package. As before, I cannot run the integration suite here, so CI remains the check on it.
540 lines
17 KiB
Go
540 lines
17 KiB
Go
package project
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/zitadel/zitadel/internal/crypto"
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
const (
|
|
OIDCConfigAddedType = applicationEventTypePrefix + "config.oidc.added"
|
|
OIDCConfigChangedType = applicationEventTypePrefix + "config.oidc.changed"
|
|
OIDCConfigSecretChangedType = applicationEventTypePrefix + "config.oidc.secret.changed"
|
|
OIDCConfigSecretHashUpdatedType = applicationEventTypePrefix + "config.oidc.secret.updated"
|
|
|
|
OIDCConfigRegistrationTokenChangedType = applicationEventTypePrefix + "config.oidc.registration_token.changed"
|
|
)
|
|
|
|
type OIDCConfigAddedEvent struct {
|
|
eventstore.BaseEvent `json:"-"`
|
|
|
|
Version domain.OIDCVersion `json:"oidcVersion,omitempty"`
|
|
AppID string `json:"appId"`
|
|
ClientID string `json:"clientId,omitempty"`
|
|
|
|
// New events only use EncodedHash. However, the ClientSecret field
|
|
// is preserved to handle events older than the switch to Passwap.
|
|
ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"`
|
|
HashedSecret string `json:"hashedSecret,omitempty"`
|
|
|
|
RedirectUris []string `json:"redirectUris,omitempty"`
|
|
ResponseTypes []domain.OIDCResponseType `json:"responseTypes,omitempty"`
|
|
GrantTypes []domain.OIDCGrantType `json:"grantTypes,omitempty"`
|
|
ApplicationType domain.OIDCApplicationType `json:"applicationType,omitempty"`
|
|
AuthMethodType domain.OIDCAuthMethodType `json:"authMethodType,omitempty"`
|
|
PostLogoutRedirectUris []string `json:"postLogoutRedirectUris,omitempty"`
|
|
DevMode bool `json:"devMode,omitempty"`
|
|
AccessTokenType domain.OIDCTokenType `json:"accessTokenType,omitempty"`
|
|
AccessTokenRoleAssertion bool `json:"accessTokenRoleAssertion,omitempty"`
|
|
IDTokenRoleAssertion bool `json:"idTokenRoleAssertion,omitempty"`
|
|
IDTokenUserinfoAssertion bool `json:"idTokenUserinfoAssertion,omitempty"`
|
|
ClockSkew time.Duration `json:"clockSkew,omitempty"`
|
|
AdditionalOrigins []string `json:"additionalOrigins,omitempty"`
|
|
SkipNativeAppSuccessPage bool `json:"skipNativeAppSuccessPage,omitempty"`
|
|
BackChannelLogoutURI string `json:"backChannelLogoutURI,omitempty"`
|
|
LoginVersion domain.LoginVersion `json:"loginVersion,omitempty"`
|
|
LoginBaseURI string `json:"loginBaseURI,omitempty"`
|
|
}
|
|
|
|
func (e *OIDCConfigAddedEvent) Payload() interface{} {
|
|
return e
|
|
}
|
|
|
|
func (e *OIDCConfigAddedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
|
return nil
|
|
}
|
|
|
|
func NewOIDCConfigAddedEvent(
|
|
ctx context.Context,
|
|
aggregate *eventstore.Aggregate,
|
|
version domain.OIDCVersion,
|
|
appID string,
|
|
clientID string,
|
|
hashedSecret string,
|
|
redirectUris []string,
|
|
responseTypes []domain.OIDCResponseType,
|
|
grantTypes []domain.OIDCGrantType,
|
|
applicationType domain.OIDCApplicationType,
|
|
authMethodType domain.OIDCAuthMethodType,
|
|
postLogoutRedirectUris []string,
|
|
devMode bool,
|
|
accessTokenType domain.OIDCTokenType,
|
|
accessTokenRoleAssertion bool,
|
|
idTokenRoleAssertion bool,
|
|
idTokenUserinfoAssertion bool,
|
|
clockSkew time.Duration,
|
|
additionalOrigins []string,
|
|
skipNativeAppSuccessPage bool,
|
|
backChannelLogoutURI string,
|
|
loginVersion domain.LoginVersion,
|
|
loginBaseURI string,
|
|
) *OIDCConfigAddedEvent {
|
|
return &OIDCConfigAddedEvent{
|
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
|
ctx,
|
|
aggregate,
|
|
OIDCConfigAddedType,
|
|
),
|
|
Version: version,
|
|
AppID: appID,
|
|
ClientID: clientID,
|
|
HashedSecret: hashedSecret,
|
|
RedirectUris: redirectUris,
|
|
ResponseTypes: responseTypes,
|
|
GrantTypes: grantTypes,
|
|
ApplicationType: applicationType,
|
|
AuthMethodType: authMethodType,
|
|
PostLogoutRedirectUris: postLogoutRedirectUris,
|
|
DevMode: devMode,
|
|
AccessTokenType: accessTokenType,
|
|
AccessTokenRoleAssertion: accessTokenRoleAssertion,
|
|
IDTokenRoleAssertion: idTokenRoleAssertion,
|
|
IDTokenUserinfoAssertion: idTokenUserinfoAssertion,
|
|
ClockSkew: clockSkew,
|
|
AdditionalOrigins: additionalOrigins,
|
|
SkipNativeAppSuccessPage: skipNativeAppSuccessPage,
|
|
BackChannelLogoutURI: backChannelLogoutURI,
|
|
LoginVersion: loginVersion,
|
|
LoginBaseURI: loginBaseURI,
|
|
}
|
|
}
|
|
|
|
func (e *OIDCConfigAddedEvent) Validate(cmd eventstore.Command) bool {
|
|
c, ok := cmd.(*OIDCConfigAddedEvent)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
if e.Version != c.Version {
|
|
return false
|
|
}
|
|
if e.AppID != c.AppID {
|
|
return false
|
|
}
|
|
if e.ClientID != c.ClientID {
|
|
return false
|
|
}
|
|
if e.ClientSecret != c.ClientSecret {
|
|
return false
|
|
}
|
|
if len(e.RedirectUris) != len(c.RedirectUris) {
|
|
return false
|
|
}
|
|
for i, uri := range e.RedirectUris {
|
|
if uri != c.RedirectUris[i] {
|
|
return false
|
|
}
|
|
}
|
|
if len(e.ResponseTypes) != len(c.ResponseTypes) {
|
|
return false
|
|
}
|
|
for i, typ := range e.ResponseTypes {
|
|
if typ != c.ResponseTypes[i] {
|
|
return false
|
|
}
|
|
}
|
|
if len(e.GrantTypes) != len(c.GrantTypes) {
|
|
return false
|
|
}
|
|
for i, typ := range e.GrantTypes {
|
|
if typ != c.GrantTypes[i] {
|
|
return false
|
|
}
|
|
}
|
|
if e.ApplicationType != c.ApplicationType {
|
|
return false
|
|
}
|
|
if e.AuthMethodType != c.AuthMethodType {
|
|
return false
|
|
}
|
|
if len(e.PostLogoutRedirectUris) != len(c.PostLogoutRedirectUris) {
|
|
return false
|
|
}
|
|
for i, uri := range e.PostLogoutRedirectUris {
|
|
if uri != c.PostLogoutRedirectUris[i] {
|
|
return false
|
|
}
|
|
}
|
|
if e.DevMode != c.DevMode {
|
|
return false
|
|
}
|
|
if e.AccessTokenType != c.AccessTokenType {
|
|
return false
|
|
}
|
|
if e.AccessTokenRoleAssertion != c.AccessTokenRoleAssertion {
|
|
return false
|
|
}
|
|
if e.IDTokenRoleAssertion != c.IDTokenRoleAssertion {
|
|
return false
|
|
}
|
|
if e.IDTokenUserinfoAssertion != c.IDTokenUserinfoAssertion {
|
|
return false
|
|
}
|
|
if e.ClockSkew != c.ClockSkew {
|
|
return false
|
|
}
|
|
if len(e.AdditionalOrigins) != len(c.AdditionalOrigins) {
|
|
return false
|
|
}
|
|
for i, origin := range e.AdditionalOrigins {
|
|
if origin != c.AdditionalOrigins[i] {
|
|
return false
|
|
}
|
|
}
|
|
if e.SkipNativeAppSuccessPage != c.SkipNativeAppSuccessPage {
|
|
return false
|
|
}
|
|
if e.BackChannelLogoutURI != c.BackChannelLogoutURI {
|
|
return false
|
|
}
|
|
if e.LoginVersion != c.LoginVersion {
|
|
return false
|
|
}
|
|
return e.LoginBaseURI == c.LoginBaseURI
|
|
}
|
|
|
|
func OIDCConfigAddedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
|
e := &OIDCConfigAddedEvent{
|
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
|
}
|
|
|
|
err := event.Unmarshal(e)
|
|
if err != nil {
|
|
return nil, zerrors.ThrowInternal(err, "OIDC-BFd15", "unable to unmarshal oidc config")
|
|
}
|
|
|
|
return e, nil
|
|
}
|
|
|
|
type OIDCConfigChangedEvent struct {
|
|
eventstore.BaseEvent `json:"-"`
|
|
|
|
Version *domain.OIDCVersion `json:"oidcVersion,omitempty"`
|
|
AppID string `json:"appId"`
|
|
RedirectUris *[]string `json:"redirectUris,omitempty"`
|
|
ResponseTypes *[]domain.OIDCResponseType `json:"responseTypes,omitempty"`
|
|
GrantTypes *[]domain.OIDCGrantType `json:"grantTypes,omitempty"`
|
|
ApplicationType *domain.OIDCApplicationType `json:"applicationType,omitempty"`
|
|
AuthMethodType *domain.OIDCAuthMethodType `json:"authMethodType,omitempty"`
|
|
PostLogoutRedirectUris *[]string `json:"postLogoutRedirectUris,omitempty"`
|
|
DevMode *bool `json:"devMode,omitempty"`
|
|
AccessTokenType *domain.OIDCTokenType `json:"accessTokenType,omitempty"`
|
|
AccessTokenRoleAssertion *bool `json:"accessTokenRoleAssertion,omitempty"`
|
|
IDTokenRoleAssertion *bool `json:"idTokenRoleAssertion,omitempty"`
|
|
IDTokenUserinfoAssertion *bool `json:"idTokenUserinfoAssertion,omitempty"`
|
|
ClockSkew *time.Duration `json:"clockSkew,omitempty"`
|
|
AdditionalOrigins *[]string `json:"additionalOrigins,omitempty"`
|
|
SkipNativeAppSuccessPage *bool `json:"skipNativeAppSuccessPage,omitempty"`
|
|
BackChannelLogoutURI *string `json:"backChannelLogoutURI,omitempty"`
|
|
LoginVersion *domain.LoginVersion `json:"loginVersion,omitempty"`
|
|
LoginBaseURI *string `json:"loginBaseURI,omitempty"`
|
|
}
|
|
|
|
func (e *OIDCConfigChangedEvent) Payload() interface{} {
|
|
return e
|
|
}
|
|
|
|
func (e *OIDCConfigChangedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
|
return nil
|
|
}
|
|
|
|
func NewOIDCConfigChangedEvent(
|
|
ctx context.Context,
|
|
aggregate *eventstore.Aggregate,
|
|
appID string,
|
|
changes []OIDCConfigChanges,
|
|
) (*OIDCConfigChangedEvent, error) {
|
|
if len(changes) == 0 {
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "OIDC-i8idç", "Errors.NoChangesFound")
|
|
}
|
|
|
|
changeEvent := &OIDCConfigChangedEvent{
|
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
|
ctx,
|
|
aggregate,
|
|
OIDCConfigChangedType,
|
|
),
|
|
AppID: appID,
|
|
}
|
|
for _, change := range changes {
|
|
change(changeEvent)
|
|
}
|
|
return changeEvent, nil
|
|
}
|
|
|
|
type OIDCConfigChanges func(event *OIDCConfigChangedEvent)
|
|
|
|
func ChangeVersion(version domain.OIDCVersion) func(event *OIDCConfigChangedEvent) {
|
|
return func(e *OIDCConfigChangedEvent) {
|
|
e.Version = &version
|
|
}
|
|
}
|
|
|
|
func ChangeRedirectURIs(uris []string) func(event *OIDCConfigChangedEvent) {
|
|
return func(e *OIDCConfigChangedEvent) {
|
|
if uris == nil {
|
|
// explicitly set them to empty so we can differentiate "not set" in the event in case of no changes
|
|
uris = make([]string, 0)
|
|
}
|
|
e.RedirectUris = &uris
|
|
}
|
|
}
|
|
|
|
func ChangeResponseTypes(responseTypes []domain.OIDCResponseType) func(event *OIDCConfigChangedEvent) {
|
|
return func(e *OIDCConfigChangedEvent) {
|
|
e.ResponseTypes = &responseTypes
|
|
}
|
|
}
|
|
|
|
func ChangeGrantTypes(grantTypes []domain.OIDCGrantType) func(event *OIDCConfigChangedEvent) {
|
|
return func(e *OIDCConfigChangedEvent) {
|
|
e.GrantTypes = &grantTypes
|
|
}
|
|
}
|
|
|
|
func ChangeApplicationType(appType domain.OIDCApplicationType) func(event *OIDCConfigChangedEvent) {
|
|
return func(e *OIDCConfigChangedEvent) {
|
|
e.ApplicationType = &appType
|
|
}
|
|
}
|
|
|
|
func ChangeAuthMethodType(authMethodType domain.OIDCAuthMethodType) func(event *OIDCConfigChangedEvent) {
|
|
return func(e *OIDCConfigChangedEvent) {
|
|
e.AuthMethodType = &authMethodType
|
|
}
|
|
}
|
|
|
|
func ChangePostLogoutRedirectURIs(logoutRedirects []string) func(event *OIDCConfigChangedEvent) {
|
|
return func(e *OIDCConfigChangedEvent) {
|
|
if logoutRedirects == nil {
|
|
// explicitly set them to empty so we can differentiate "not set" in the event in case of no changes
|
|
logoutRedirects = make([]string, 0)
|
|
}
|
|
e.PostLogoutRedirectUris = &logoutRedirects
|
|
}
|
|
}
|
|
|
|
func ChangeDevMode(devMode bool) func(event *OIDCConfigChangedEvent) {
|
|
return func(e *OIDCConfigChangedEvent) {
|
|
e.DevMode = &devMode
|
|
}
|
|
}
|
|
|
|
func ChangeAccessTokenType(accessTokenType domain.OIDCTokenType) func(event *OIDCConfigChangedEvent) {
|
|
return func(e *OIDCConfigChangedEvent) {
|
|
e.AccessTokenType = &accessTokenType
|
|
}
|
|
}
|
|
|
|
func ChangeAccessTokenRoleAssertion(accessTokenRoleAssertion bool) func(event *OIDCConfigChangedEvent) {
|
|
return func(e *OIDCConfigChangedEvent) {
|
|
e.AccessTokenRoleAssertion = &accessTokenRoleAssertion
|
|
}
|
|
}
|
|
|
|
func ChangeIDTokenRoleAssertion(idTokenRoleAssertion bool) func(event *OIDCConfigChangedEvent) {
|
|
return func(e *OIDCConfigChangedEvent) {
|
|
e.IDTokenRoleAssertion = &idTokenRoleAssertion
|
|
}
|
|
}
|
|
|
|
func ChangeIDTokenUserinfoAssertion(idTokenUserinfoAssertion bool) func(event *OIDCConfigChangedEvent) {
|
|
return func(e *OIDCConfigChangedEvent) {
|
|
e.IDTokenUserinfoAssertion = &idTokenUserinfoAssertion
|
|
}
|
|
}
|
|
|
|
func ChangeClockSkew(clockSkew time.Duration) func(event *OIDCConfigChangedEvent) {
|
|
return func(e *OIDCConfigChangedEvent) {
|
|
e.ClockSkew = &clockSkew
|
|
}
|
|
}
|
|
|
|
func ChangeAdditionalOrigins(additionalOrigins []string) func(event *OIDCConfigChangedEvent) {
|
|
return func(e *OIDCConfigChangedEvent) {
|
|
if additionalOrigins == nil {
|
|
// explicitly set them to empty so we can differentiate "not set" in the event in case of no changes
|
|
additionalOrigins = make([]string, 0)
|
|
}
|
|
e.AdditionalOrigins = &additionalOrigins
|
|
}
|
|
}
|
|
|
|
func ChangeSkipNativeAppSuccessPage(skipNativeAppSuccessPage bool) func(event *OIDCConfigChangedEvent) {
|
|
return func(e *OIDCConfigChangedEvent) {
|
|
e.SkipNativeAppSuccessPage = &skipNativeAppSuccessPage
|
|
}
|
|
}
|
|
|
|
func ChangeBackChannelLogoutURI(backChannelLogoutURI string) func(event *OIDCConfigChangedEvent) {
|
|
return func(e *OIDCConfigChangedEvent) {
|
|
e.BackChannelLogoutURI = &backChannelLogoutURI
|
|
}
|
|
}
|
|
|
|
func ChangeOIDCLoginVersion(loginVersion domain.LoginVersion) func(event *OIDCConfigChangedEvent) {
|
|
return func(e *OIDCConfigChangedEvent) {
|
|
e.LoginVersion = &loginVersion
|
|
}
|
|
}
|
|
|
|
func ChangeOIDCLoginBaseURI(loginBaseURI string) func(event *OIDCConfigChangedEvent) {
|
|
return func(e *OIDCConfigChangedEvent) {
|
|
e.LoginBaseURI = &loginBaseURI
|
|
}
|
|
}
|
|
|
|
func OIDCConfigChangedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
|
e := &OIDCConfigChangedEvent{
|
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
|
}
|
|
|
|
err := event.Unmarshal(e)
|
|
if err != nil {
|
|
return nil, zerrors.ThrowInternal(err, "OIDC-BFd15", "unable to unmarshal oidc config")
|
|
}
|
|
|
|
return e, nil
|
|
}
|
|
|
|
type OIDCConfigSecretChangedEvent struct {
|
|
eventstore.BaseEvent `json:"-"`
|
|
|
|
AppID string `json:"appId"`
|
|
|
|
// New events only use EncodedHash. However, the ClientSecret field
|
|
// is preserved to handle events older than the switch to Passwap.
|
|
ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"`
|
|
HashedSecret string `json:"hashedSecret,omitempty"`
|
|
}
|
|
|
|
func (e *OIDCConfigSecretChangedEvent) Payload() interface{} {
|
|
return e
|
|
}
|
|
|
|
func (e *OIDCConfigSecretChangedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
|
return nil
|
|
}
|
|
|
|
func NewOIDCConfigSecretChangedEvent(
|
|
ctx context.Context,
|
|
aggregate *eventstore.Aggregate,
|
|
appID string,
|
|
hashedSecret string,
|
|
) *OIDCConfigSecretChangedEvent {
|
|
return &OIDCConfigSecretChangedEvent{
|
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
|
ctx,
|
|
aggregate,
|
|
OIDCConfigSecretChangedType,
|
|
),
|
|
AppID: appID,
|
|
HashedSecret: hashedSecret,
|
|
}
|
|
}
|
|
|
|
func OIDCConfigSecretChangedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
|
e := &OIDCConfigSecretChangedEvent{
|
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
|
}
|
|
|
|
err := event.Unmarshal(e)
|
|
if err != nil {
|
|
return nil, zerrors.ThrowInternal(err, "OIDC-M893d", "unable to unmarshal oidc config")
|
|
}
|
|
|
|
return e, nil
|
|
}
|
|
|
|
type OIDCConfigSecretHashUpdatedEvent struct {
|
|
*eventstore.BaseEvent `json:"-"`
|
|
|
|
AppID string `json:"appId"`
|
|
HashedSecret string `json:"hashedSecret,omitempty"`
|
|
}
|
|
|
|
func NewOIDCConfigSecretHashUpdatedEvent(
|
|
ctx context.Context,
|
|
aggregate *eventstore.Aggregate,
|
|
appID string,
|
|
hashedSecret string,
|
|
) *OIDCConfigSecretHashUpdatedEvent {
|
|
return &OIDCConfigSecretHashUpdatedEvent{
|
|
BaseEvent: eventstore.NewBaseEventForPush(
|
|
ctx,
|
|
aggregate,
|
|
OIDCConfigSecretHashUpdatedType,
|
|
),
|
|
AppID: appID,
|
|
HashedSecret: hashedSecret,
|
|
}
|
|
}
|
|
|
|
func (e *OIDCConfigSecretHashUpdatedEvent) SetBaseEvent(b *eventstore.BaseEvent) {
|
|
e.BaseEvent = b
|
|
}
|
|
|
|
func (e *OIDCConfigSecretHashUpdatedEvent) Payload() interface{} {
|
|
return e
|
|
}
|
|
|
|
func (e *OIDCConfigSecretHashUpdatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
|
return nil
|
|
}
|
|
|
|
// OIDCConfigRegistrationTokenChangedEvent stores the hash of the registration access token
|
|
// (RFC 7592 §3) of a dynamically registered OIDC client. The token authenticates the read,
|
|
// update and delete operations on the client's registration. As with a client secret only the
|
|
// hash is persisted (and projected); the token itself is returned to the client once and never
|
|
// stored in clear text. The event is written on registration and again whenever the token is
|
|
// rotated (on update).
|
|
type OIDCConfigRegistrationTokenChangedEvent struct {
|
|
*eventstore.BaseEvent `json:"-"`
|
|
|
|
AppID string `json:"appId"`
|
|
HashedToken string `json:"hashedToken,omitempty"`
|
|
}
|
|
|
|
func NewOIDCConfigRegistrationTokenChangedEvent(
|
|
ctx context.Context,
|
|
aggregate *eventstore.Aggregate,
|
|
appID string,
|
|
hashedToken string,
|
|
) *OIDCConfigRegistrationTokenChangedEvent {
|
|
return &OIDCConfigRegistrationTokenChangedEvent{
|
|
BaseEvent: eventstore.NewBaseEventForPush(
|
|
ctx,
|
|
aggregate,
|
|
OIDCConfigRegistrationTokenChangedType,
|
|
),
|
|
AppID: appID,
|
|
HashedToken: hashedToken,
|
|
}
|
|
}
|
|
|
|
func (e *OIDCConfigRegistrationTokenChangedEvent) SetBaseEvent(b *eventstore.BaseEvent) {
|
|
e.BaseEvent = b
|
|
}
|
|
|
|
func (e *OIDCConfigRegistrationTokenChangedEvent) Payload() interface{} {
|
|
return e
|
|
}
|
|
|
|
func (e *OIDCConfigRegistrationTokenChangedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
|
return nil
|
|
}
|