zitadel/internal/query/oidc_client.go
Livio Spring 041af26917
feat(OIDC): add back channel logout (#8837)
# Which Problems Are Solved

Currently ZITADEL supports RP-initiated logout for clients. Back-channel
logout ensures that user sessions are terminated across all connected
applications, even if the user closes their browser or loses
connectivity providing a more secure alternative for certain use cases.

# How the Problems Are Solved

If the feature is activated and the client used for the authentication
has a back_channel_logout_uri configured, a
`session_logout.back_channel` will be registered. Once a user terminates
their session, a (notification) handler will send a SET (form POST) to
the registered uri containing a logout_token (with the user's ID and
session ID).

- A new feature "back_channel_logout" is added on system and instance
level
- A `back_channel_logout_uri` can be managed on OIDC applications
- Added a `session_logout` aggregate to register and inform about sent
`back_channel` notifications
- Added a `SecurityEventToken` channel and `Form`message type in the
notification handlers
- Added `TriggeredAtOrigin` fields to `HumanSignedOut` and
`TerminateSession` events for notification handling
- Exported various functions and types in the `oidc` package to be able
to reuse for token signing in the back_channel notifier.
- To prevent that current existing session termination events will be
handled, a setup step is added to set the `current_states` for the
`projections.notifications_back_channel_logout` to the current position

- [x] requires https://github.com/zitadel/oidc/pull/671

# Additional Changes

- Updated all OTEL dependencies to v1.29.0, since OIDC already updated
some of them to that version.
- Single Session Termination feature is correctly checked (fixed feature
mapping)

# Additional Context

- closes https://github.com/zitadel/zitadel/issues/8467
- TODO:
  - Documentation
  - UI to be done: https://github.com/zitadel/zitadel/issues/8469

---------

Co-authored-by: Hidde Wieringa <hidde@hiddewieringa.nl>
2024-10-31 15:57:17 +01:00

68 lines
3.5 KiB
Go

package query
import (
"context"
"database/sql"
_ "embed"
"errors"
"time"
"github.com/zitadel/zitadel/internal/api/authz"
http_util "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/ui/console/path"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
)
type OIDCClient struct {
InstanceID string `json:"instance_id,omitempty"`
AppID string `json:"app_id,omitempty"`
State domain.AppState `json:"state,omitempty"`
ClientID string `json:"client_id,omitempty"`
BackChannelLogoutURI string `json:"back_channel_logout_uri,omitempty"`
HashedSecret string `json:"client_secret,omitempty"`
RedirectURIs []string `json:"redirect_uris,omitempty"`
ResponseTypes []domain.OIDCResponseType `json:"response_types,omitempty"`
GrantTypes []domain.OIDCGrantType `json:"grant_types,omitempty"`
ApplicationType domain.OIDCApplicationType `json:"application_type,omitempty"`
AuthMethodType domain.OIDCAuthMethodType `json:"auth_method_type,omitempty"`
PostLogoutRedirectURIs []string `json:"post_logout_redirect_uris,omitempty"`
IsDevMode bool `json:"is_dev_mode,omitempty"`
AccessTokenType domain.OIDCTokenType `json:"access_token_type,omitempty"`
AccessTokenRoleAssertion bool `json:"access_token_role_assertion,omitempty"`
IDTokenRoleAssertion bool `json:"id_token_role_assertion,omitempty"`
IDTokenUserinfoAssertion bool `json:"id_token_userinfo_assertion,omitempty"`
ClockSkew time.Duration `json:"clock_skew,omitempty"`
AdditionalOrigins []string `json:"additional_origins,omitempty"`
PublicKeys map[string][]byte `json:"public_keys,omitempty"`
ProjectID string `json:"project_id,omitempty"`
ProjectRoleAssertion bool `json:"project_role_assertion,omitempty"`
ProjectRoleKeys []string `json:"project_role_keys,omitempty"`
Settings *OIDCSettings `json:"settings,omitempty"`
}
//go:embed oidc_client_by_id.sql
var oidcClientQuery string
func (q *Queries) ActiveOIDCClientByID(ctx context.Context, clientID string, getKeys bool) (client *OIDCClient, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
client, err = database.QueryJSONObject[OIDCClient](ctx, q.client, oidcClientQuery,
authz.GetInstance(ctx).InstanceID(), clientID, getKeys,
)
if errors.Is(err, sql.ErrNoRows) {
return nil, zerrors.ThrowNotFound(err, "QUERY-wu6Ee", "Errors.App.NotFound")
}
if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-ieR7R", "Errors.Internal")
}
if authz.GetInstance(ctx).ConsoleClientID() == clientID {
client.RedirectURIs = append(client.RedirectURIs, http_util.DomainContext(ctx).Origin()+path.RedirectPath)
client.PostLogoutRedirectURIs = append(client.PostLogoutRedirectURIs, http_util.DomainContext(ctx).Origin()+path.PostLogoutPath)
}
return client, err
}