2020-06-05 05:50:04 +00:00
package eventstore
import (
"context"
2021-04-06 09:38:39 +00:00
"encoding/base64"
2022-08-23 06:02:36 +00:00
"fmt"
2024-08-26 09:26:13 +00:00
"slices"
2020-11-20 06:57:39 +00:00
"strings"
"time"
2024-04-15 09:17:36 +00:00
"github.com/go-jose/go-jose/v4"
2024-05-16 05:07:56 +00:00
"github.com/muhlemmer/gu"
2022-04-26 23:01:45 +00:00
"github.com/zitadel/logging"
2023-10-19 10:34:00 +00:00
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/oidc/v3/pkg/op"
feat: label policy (#1708)
* feat: label policy proto extension
* feat: label policy and activate event
* feat: label policy asset events
* feat: label policy asset commands
* feat: add storage key
* feat: storage key validation
* feat: label policy asset tests
* feat: label policy query side
* feat: avatar
* feat: avatar event
* feat: human avatar
* feat: avatar read side
* feat: font on iam label policy
* feat: label policy font
* feat: possiblity to create bucket on put file
* uplaoder
* login policy logo
* set bucket prefix
* feat: avatar upload
* feat: avatar upload
* feat: use assets on command side
* feat: fix human avatar removed event
* feat: remove human avatar
* feat: mock asset storage
* feat: remove human avatar
* fix(operator): add configuration of asset storage to zitadel operator
* feat(console): private labeling policy (#1697)
* private labeling component, routing, preview
* font, colors, upload, i18n
* show logo
* fix: uniqueness (#1710)
* fix: uniqueconstraint to lower
* feat: change org
* feat: org change test
* feat: change org
* fix: tests
* fix: handle domain claims correctly
* feat: update org
Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>
* fix: handle domain claimed event correctly for service users (#1711)
* fix: handle domain claimed event correctly on user view
* fix: ignore domain claimed events for email notifications
* fix: change org
* handle org changed in read models correctly
* fix: change org in user grant handler
Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>
* fix: correct value (#1695)
* docs(api): correct link (#1712)
* upload service
Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>
Co-authored-by: Florian Forster <florian@caos.ch>
* feat: fix tests,
* feat: remove assets from label policy
* fix npm, set environment
* lint ts
* remove stylelinting
* fix(operator): add mapping for console with changed unit tests
* fix(operator): add secrets as env variables to pod
* feat: remove human avatar
* fix(operator): add secrets as env variables to pod
* feat: map label policy
* feat: labelpolicy, admin, mgmt, adv settings (#1715)
* fetch label policy, mgmt, admin service
* feat: advanced beh, links, add, update
* lint ts
* feat: watermark
* feat: remove human avatar
* feat: remove human avatar
* feat: remove human avatar
* feat: remove human avatar
* feat: remove human avatar
* feat: remove human avatar
* feat: remove human avatar
* feat: custom css
* css
* css
* css
* css
* css
* getobject
* feat: dynamic handler
* feat: varibale css
* content info
* css overwrite
* feat: variablen css
* feat: generate css file
* feat: dark mode
* feat: dark mode
* fix logo css
* feat: upload logos
* dark mode with cookie
* feat: handle images in login
* avatar css and begin font
* feat: avatar
* feat: user avatar
* caching of static assets in login
* add avatar.js to main.html
* feat: header dont show logo if no url
* feat: label policy colors
* feat: mock asset storage
* feat: mock asset storage
* feat: fix tests
* feat: user avatar
* feat: header logo
* avatar
* avatar
* make it compatible with go 1.15
* feat: remove unused logos
* fix handler
* fix: styling error handling
* fonts
* fix: download func
* switch to mux
* fix: change upload api to assets
* fix build
* fix: download avatar
* fix: download logos
* fix: my avatar
* font
* fix: remove error msg popup possibility
* fix: docs
* fix: svalidate colors
* rem msg popup from frontend
* fix: email with private labeling
* fix: tests
* fix: email templates
* fix: change migration version
* fix: fix duplicate imports
* fix(console): assets, service url, upload, policy current and preview (#1781)
* upload endpoint, layout
* fetch current, preview, fix upload
* cleanup private labeling
* fix linting
* begin generated asset handler
* generate asset api in dockerfile
* features for label policy
* features for label policy
* features
* flag for asset generator
* change asset generator flag
* fix label policy view in grpc
* fix: layout, activate policy (#1786)
* theme switcher up on top
* change layout
* activate policy
* feat(console): label policy back color, layout (#1788)
* theme switcher up on top
* change layout
* activate policy
* fix overwrite value fc
* reset policy, reset service
* autosave policy, preview desc, layout impv
* layout, i18n
* background colors, inject material styles
* load images
* clean, lint
* fix layout
* set custom hex
* fix content size conversion
* remove font format in generated css
* fix features for assets
* fix(console): label policy colors, image downloads, preview (#1804)
* load images
* colors, images binding
* lint
* refresh emitter
* lint
* propagate font colors
* upload error handling
* label policy feature check
* add blob in csp for console
* log
* fix: feature edits for label policy, refresh state on upload (#1807)
* show error on load image, stop spinner
* fix merge
* fix migration versions
* fix assets
* fix csp
* fix background color
* scss
* fix build
* lint scss
* fix statik for console
* fix features check for label policy
* cleanup
* lint
* public links
* fix notifications
* public links
* feat: merge main
* feat: fix translation files
* fix migration
* set api domain
* fix logo in email
* font face in email
* font face in email
* validate assets on upload
* cleanup
* add missing translations
* add missing translations
Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: Stefan Benz <stefan@caos.ch>
Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Florian Forster <florian@caos.ch>
2021-06-04 12:53:51 +00:00
2022-04-26 23:01:45 +00:00
"github.com/zitadel/zitadel/internal/api/authz"
2022-08-23 06:02:36 +00:00
http_util "github.com/zitadel/zitadel/internal/api/http"
2022-04-26 23:01:45 +00:00
"github.com/zitadel/zitadel/internal/authz/repository/eventsourcing/view"
2023-07-14 11:16:16 +00:00
"github.com/zitadel/zitadel/internal/command"
2022-04-26 23:01:45 +00:00
"github.com/zitadel/zitadel/internal/crypto"
2023-07-14 11:16:16 +00:00
"github.com/zitadel/zitadel/internal/domain"
2023-10-19 10:19:10 +00:00
"github.com/zitadel/zitadel/internal/eventstore"
2022-04-26 23:01:45 +00:00
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
usr_model "github.com/zitadel/zitadel/internal/user/model"
usr_view "github.com/zitadel/zitadel/internal/user/repository/view"
"github.com/zitadel/zitadel/internal/user/repository/view/model"
2023-12-08 14:30:55 +00:00
"github.com/zitadel/zitadel/internal/zerrors"
2020-06-05 05:50:04 +00:00
)
type TokenVerifierRepo struct {
2021-04-06 06:31:18 +00:00
TokenVerificationKey crypto . EncryptionAlgorithm
2023-10-19 10:19:10 +00:00
Eventstore * eventstore . Eventstore
2020-06-05 05:50:04 +00:00
View * view . View
2021-11-21 19:22:25 +00:00
Query * query . Queries
2022-08-23 06:02:36 +00:00
ExternalSecure bool
2020-06-05 05:50:04 +00:00
}
2022-05-25 12:07:16 +00:00
func ( repo * TokenVerifierRepo ) Health ( ) error {
return repo . View . Health ( )
}
2022-05-24 09:18:25 +00:00
func ( repo * TokenVerifierRepo ) tokenByID ( ctx context . Context , tokenID , userID string ) ( _ * usr_model . TokenView , err error ) {
ctx , span := tracing . NewSpan ( ctx )
defer func ( ) { span . EndWithError ( err ) } ( )
2022-09-02 14:05:13 +00:00
instanceID := authz . GetInstance ( ctx ) . InstanceID ( )
2023-10-09 07:26:27 +00:00
// always load the latest sequence first, so in case the token was not found by id,
// the sequence will be equal or lower than the actual projection and no events are lost
2023-10-19 10:19:10 +00:00
sequence , err := repo . View . GetLatestState ( ctx )
2023-10-09 07:26:27 +00:00
logging . WithFields ( "instanceID" , instanceID , "userID" , userID , "tokenID" , tokenID ) .
OnError ( err ) .
Errorf ( "could not get current sequence for token check" )
2022-09-15 12:59:40 +00:00
token , viewErr := repo . View . TokenByIDs ( tokenID , userID , instanceID )
2023-12-08 14:30:55 +00:00
if viewErr != nil && ! zerrors . IsNotFound ( viewErr ) {
2020-10-15 11:52:41 +00:00
return nil , viewErr
}
2023-12-08 14:30:55 +00:00
if zerrors . IsNotFound ( viewErr ) {
2020-10-15 11:52:41 +00:00
token = new ( model . TokenView )
token . ID = tokenID
token . UserID = userID
2022-09-02 14:05:13 +00:00
if sequence != nil {
2023-12-14 10:07:47 +00:00
token . ChangeDate = sequence . EventCreatedAt
2022-09-02 14:05:13 +00:00
}
2020-10-15 11:52:41 +00:00
}
2023-12-14 10:07:47 +00:00
events , esErr := repo . getUserEvents ( ctx , userID , instanceID , token . ChangeDate , token . GetRelevantEventTypes ( ) )
2023-12-08 14:30:55 +00:00
if zerrors . IsNotFound ( viewErr ) && len ( events ) == 0 {
return nil , zerrors . ThrowNotFound ( nil , "EVENT-4T90g" , "Errors.Token.NotFound" )
2020-10-15 11:52:41 +00:00
}
if esErr != nil {
2022-08-23 06:02:36 +00:00
logging . WithError ( viewErr ) . WithField ( "traceID" , tracing . TraceIDFromCtx ( ctx ) ) . Debug ( "error retrieving new events" )
2020-10-15 11:52:41 +00:00
return model . TokenViewToModel ( token ) , nil
}
viewToken := * token
for _ , event := range events {
err := token . AppendEventIfMyToken ( event )
if err != nil {
return model . TokenViewToModel ( & viewToken ) , nil
}
}
if ! token . Expiration . After ( time . Now ( ) . UTC ( ) ) || token . Deactivated {
2023-12-08 14:30:55 +00:00
return nil , zerrors . ThrowNotFound ( nil , "EVENT-5Bm9s" , "Errors.Token.NotFound" )
2020-10-15 11:52:41 +00:00
}
return model . TokenViewToModel ( token ) , nil
}
2021-11-26 06:57:05 +00:00
func ( repo * TokenVerifierRepo ) VerifyAccessToken ( ctx context . Context , tokenString , verifierClientID , projectID string ) ( userID string , agentID string , clientID , prefLang , resourceOwner string , err error ) {
2020-10-21 08:18:34 +00:00
ctx , span := tracing . NewSpan ( ctx )
defer func ( ) { span . EndWithError ( err ) } ( )
2020-10-15 11:52:41 +00:00
2022-08-23 06:02:36 +00:00
tokenID , subject , ok := repo . getTokenIDAndSubject ( ctx , tokenString )
if ! ok {
2023-12-08 14:30:55 +00:00
return "" , "" , "" , "" , "" , zerrors . ThrowUnauthenticated ( nil , "APP-Reb32" , "invalid token" )
2020-10-15 11:52:41 +00:00
}
2023-07-14 11:16:16 +00:00
if strings . HasPrefix ( tokenID , command . IDPrefixV2 ) {
2024-05-16 05:07:56 +00:00
return repo . verifyAccessTokenV2 ( ctx , tokenID , verifierClientID , projectID )
2023-07-14 11:16:16 +00:00
}
if sessionID , ok := strings . CutPrefix ( tokenID , authz . SessionTokenPrefix ) ; ok {
userID , clientID , resourceOwner , err = repo . verifySessionToken ( ctx , sessionID , tokenString )
return
}
return repo . verifyAccessTokenV1 ( ctx , tokenID , subject , verifierClientID , projectID )
}
2024-05-16 05:07:56 +00:00
func ( repo * TokenVerifierRepo ) verifyAccessTokenV1 ( ctx context . Context , tokenID , subject , verifierClientID , projectID string ) ( userID , agentID , clientID , prefLang , resourceOwner string , err error ) {
2023-07-14 11:16:16 +00:00
ctx , span := tracing . NewSpan ( ctx )
defer func ( ) { span . EndWithError ( err ) } ( )
2024-03-28 12:19:03 +00:00
_ , tokenSpan := tracing . NewNamedSpan ( ctx , "tokenByID" )
2022-08-23 06:02:36 +00:00
token , err := repo . tokenByID ( ctx , tokenID , subject )
2022-05-24 09:18:25 +00:00
tokenSpan . EndWithError ( err )
2020-06-05 05:50:04 +00:00
if err != nil {
2023-12-08 14:30:55 +00:00
return "" , "" , "" , "" , "" , zerrors . ThrowUnauthenticated ( err , "APP-BxUSiL" , "invalid token" )
2020-06-05 05:50:04 +00:00
}
2024-03-28 12:19:03 +00:00
if token . Actor != nil {
return "" , "" , "" , "" , "" , zerrors . ThrowPermissionDenied ( nil , "APP-wai8O" , "Errors.TokenExchange.Token.NotForAPI" )
}
2020-06-05 05:50:04 +00:00
if ! token . Expiration . After ( time . Now ( ) . UTC ( ) ) {
2023-12-08 14:30:55 +00:00
return "" , "" , "" , "" , "" , zerrors . ThrowUnauthenticated ( err , "APP-k9KS0" , "invalid token" )
2020-06-05 05:50:04 +00:00
}
2022-02-08 08:37:28 +00:00
if token . IsPAT {
return token . UserID , "" , "" , "" , token . ResourceOwner , nil
}
2023-07-14 11:16:16 +00:00
if err = verifyAudience ( token . Audience , verifierClientID , projectID ) ; err != nil {
return "" , "" , "" , "" , "" , err
}
return token . UserID , token . UserAgentID , token . ApplicationID , token . PreferredLanguage , token . ResourceOwner , nil
}
2024-05-16 05:07:56 +00:00
func ( repo * TokenVerifierRepo ) verifyAccessTokenV2 ( ctx context . Context , token , verifierClientID , projectID string ) ( userID , agentID , clientID , prefLang , resourceOwner string , err error ) {
2023-07-14 11:16:16 +00:00
ctx , span := tracing . NewSpan ( ctx )
defer func ( ) { span . EndWithError ( err ) } ( )
activeToken , err := repo . Query . ActiveAccessTokenByToken ( ctx , token )
if err != nil {
2024-05-16 05:07:56 +00:00
return "" , "" , "" , "" , "" , err
2023-07-14 11:16:16 +00:00
}
2024-03-20 10:18:46 +00:00
if activeToken . Actor != nil {
2024-05-16 05:07:56 +00:00
return "" , "" , "" , "" , "" , zerrors . ThrowPermissionDenied ( nil , "APP-Shi0J" , "Errors.TokenExchange.Token.NotForAPI" )
2024-03-20 10:18:46 +00:00
}
2023-07-14 11:16:16 +00:00
if err = verifyAudience ( activeToken . Audience , verifierClientID , projectID ) ; err != nil {
2024-05-16 05:07:56 +00:00
return "" , "" , "" , "" , "" , err
2023-07-14 11:16:16 +00:00
}
if err = repo . checkAuthentication ( ctx , activeToken . AuthMethods , activeToken . UserID ) ; err != nil {
2024-05-16 05:07:56 +00:00
return "" , "" , "" , "" , "" , err
2020-06-05 05:50:04 +00:00
}
2024-05-16 05:07:56 +00:00
prefLang = gu . Value ( activeToken . PreferredLanguage ) . String ( )
agentID = gu . Value ( gu . Value ( activeToken . UserAgent ) . FingerprintID )
return activeToken . UserID , agentID , activeToken . ClientID , prefLang , activeToken . ResourceOwner , nil
2023-07-14 11:16:16 +00:00
}
func ( repo * TokenVerifierRepo ) verifySessionToken ( ctx context . Context , sessionID , token string ) ( userID , clientID , resourceOwner string , err error ) {
ctx , span := tracing . NewSpan ( ctx )
defer func ( ) { span . EndWithError ( err ) } ( )
2023-08-04 15:16:27 +00:00
session , err := repo . Query . SessionByID ( ctx , true , sessionID , token )
2023-07-14 11:16:16 +00:00
if err != nil {
return "" , "" , "" , err
}
2023-11-06 09:48:28 +00:00
if ! session . Expiration . IsZero ( ) && session . Expiration . Before ( time . Now ( ) ) {
2023-12-08 14:30:55 +00:00
return "" , "" , "" , zerrors . ThrowPermissionDenied ( nil , "AUTHZ-EGDo3" , "session expired" )
2023-11-06 09:48:28 +00:00
}
2023-07-14 11:16:16 +00:00
if err = repo . checkAuthentication ( ctx , authMethodsFromSession ( session ) , session . UserFactor . UserID ) ; err != nil {
return "" , "" , "" , err
}
return session . UserFactor . UserID , "" , session . UserFactor . ResourceOwner , nil
}
// checkAuthentication ensures the session or token was authenticated (at least a single [domain.UserAuthMethodType]).
2024-10-22 14:59:16 +00:00
// It will also check if there was a multi-factor authentication, if either MFA is forced by the login policy or if the user has set up any second factor
2023-07-14 11:16:16 +00:00
func ( repo * TokenVerifierRepo ) checkAuthentication ( ctx context . Context , authMethods [ ] domain . UserAuthMethodType , userID string ) error {
if len ( authMethods ) == 0 {
2023-12-08 14:30:55 +00:00
return zerrors . ThrowPermissionDenied ( nil , "AUTHZ-Kl3p0" , "authentication required" )
2023-07-14 11:16:16 +00:00
}
if domain . HasMFA ( authMethods ) {
return nil
}
2024-05-23 05:35:10 +00:00
requirements , err := repo . Query . ListUserAuthMethodTypesRequired ( setCallerCtx ( ctx , userID ) , userID )
2023-07-14 11:16:16 +00:00
if err != nil {
return err
}
2024-05-23 05:35:10 +00:00
if requirements . UserType == domain . UserTypeMachine {
return nil
}
if domain . RequiresMFA (
requirements . ForceMFA ,
requirements . ForceMFALocalOnly ,
2024-06-12 12:24:17 +00:00
! hasIDPAuthentication ( authMethods ) ,
) {
2023-12-08 14:30:55 +00:00
return zerrors . ThrowPermissionDenied ( nil , "AUTHZ-Kl3p0" , "mfa required" )
2023-07-14 11:16:16 +00:00
}
return nil
}
2023-07-20 04:06:16 +00:00
func hasIDPAuthentication ( authMethods [ ] domain . UserAuthMethodType ) bool {
for _ , method := range authMethods {
if method == domain . UserAuthMethodTypeIDP {
return true
}
}
return false
}
2023-07-14 11:16:16 +00:00
func authMethodsFromSession ( session * query . Session ) [ ] domain . UserAuthMethodType {
types := make ( [ ] domain . UserAuthMethodType , 0 , domain . UserAuthMethodTypeIDP )
if ! session . PasswordFactor . PasswordCheckedAt . IsZero ( ) {
types = append ( types , domain . UserAuthMethodTypePassword )
}
2023-08-11 15:36:18 +00:00
if ! session . WebAuthNFactor . WebAuthNCheckedAt . IsZero ( ) {
if session . WebAuthNFactor . UserVerified {
types = append ( types , domain . UserAuthMethodTypePasswordless )
} else {
types = append ( types , domain . UserAuthMethodTypeU2F )
}
2023-07-14 11:16:16 +00:00
}
if ! session . IntentFactor . IntentCheckedAt . IsZero ( ) {
types = append ( types , domain . UserAuthMethodTypeIDP )
}
2023-09-01 12:53:10 +00:00
if ! session . TOTPFactor . TOTPCheckedAt . IsZero ( ) {
types = append ( types , domain . UserAuthMethodTypeTOTP )
}
2023-08-24 09:41:52 +00:00
if ! session . OTPSMSFactor . OTPCheckedAt . IsZero ( ) {
types = append ( types , domain . UserAuthMethodTypeOTPSMS )
}
if ! session . OTPEmailFactor . OTPCheckedAt . IsZero ( ) {
types = append ( types , domain . UserAuthMethodTypeOTPEmail )
}
2023-07-14 11:16:16 +00:00
return types
}
func setCallerCtx ( ctx context . Context , userID string ) context . Context {
ctxData := authz . GetCtxData ( ctx )
ctxData . UserID = userID
return authz . SetCtxData ( ctx , ctxData )
2020-06-05 05:50:04 +00:00
}
2020-08-24 08:06:55 +00:00
func ( repo * TokenVerifierRepo ) ProjectIDAndOriginsByClientID ( ctx context . Context , clientID string ) ( projectID string , origins [ ] string , err error ) {
2022-03-29 09:53:19 +00:00
app , err := repo . View . ApplicationByOIDCClientID ( ctx , clientID )
2020-06-05 05:50:04 +00:00
if err != nil {
2020-08-24 08:06:55 +00:00
return "" , nil , err
2020-06-05 05:50:04 +00:00
}
2021-11-26 06:57:05 +00:00
return app . ProjectID , app . OIDCConfig . AllowedOrigins , nil
2020-06-05 05:50:04 +00:00
}
2021-11-26 06:57:05 +00:00
func ( repo * TokenVerifierRepo ) VerifierClientID ( ctx context . Context , appName string ) ( clientID , projectID string , err error ) {
2020-10-21 08:18:34 +00:00
ctx , span := tracing . NewSpan ( ctx )
defer func ( ) { span . EndWithError ( err ) } ( )
2022-04-29 12:16:23 +00:00
app , err := repo . View . ApplicationByProjecIDAndAppName ( ctx , authz . GetInstance ( ctx ) . ProjectID ( ) , appName )
2020-06-05 05:50:04 +00:00
if err != nil {
2021-11-26 06:57:05 +00:00
return "" , "" , err
}
if app . OIDCConfig != nil {
clientID = app . OIDCConfig . ClientID
} else if app . APIConfig != nil {
clientID = app . APIConfig . ClientID
2020-06-05 05:50:04 +00:00
}
2021-11-26 06:57:05 +00:00
return clientID , app . ProjectID , nil
2020-06-05 05:50:04 +00:00
}
2021-02-22 13:08:47 +00:00
2023-12-14 10:07:47 +00:00
func ( repo * TokenVerifierRepo ) getUserEvents ( ctx context . Context , userID , instanceID string , changeDate time . Time , eventTypes [ ] eventstore . EventType ) ( _ [ ] eventstore . Event , err error ) {
2022-05-24 09:18:25 +00:00
ctx , span := tracing . NewSpan ( ctx )
defer func ( ) { span . EndWithError ( err ) } ( )
2023-12-14 10:07:47 +00:00
query , err := usr_view . UserByIDQuery ( userID , instanceID , changeDate , eventTypes )
2021-02-22 13:08:47 +00:00
if err != nil {
return nil , err
}
2023-10-19 10:19:10 +00:00
return repo . Eventstore . Filter ( ctx , query )
2022-08-23 06:02:36 +00:00
}
2022-09-15 12:59:40 +00:00
// getTokenIDAndSubject returns the TokenID and Subject of both opaque tokens and JWTs
2022-08-23 06:02:36 +00:00
func ( repo * TokenVerifierRepo ) getTokenIDAndSubject ( ctx context . Context , accessToken string ) ( tokenID string , subject string , valid bool ) {
// accessToken can be either opaque or JWT
// let's try opaque first:
tokenIDSubject , err := repo . decryptAccessToken ( accessToken )
if err != nil {
2023-11-27 15:35:08 +00:00
logging . WithError ( err ) . Warn ( "token verifier repo: decrypt access token" )
2022-08-23 06:02:36 +00:00
// if decryption did not work, it might be a JWT
2023-03-28 11:28:56 +00:00
accessTokenClaims , err := op . VerifyAccessToken [ * oidc . AccessTokenClaims ] ( ctx , accessToken , repo . jwtTokenVerifier ( ctx ) )
2022-08-23 06:02:36 +00:00
if err != nil {
2023-11-27 15:35:08 +00:00
logging . WithError ( err ) . Warn ( "token verifier repo: verify JWT access token" )
2022-08-23 06:02:36 +00:00
return "" , "" , false
}
2023-03-28 11:28:56 +00:00
return accessTokenClaims . JWTID , accessTokenClaims . Subject , true
2022-08-23 06:02:36 +00:00
}
splitToken := strings . Split ( tokenIDSubject , ":" )
if len ( splitToken ) != 2 {
return "" , "" , false
}
return splitToken [ 0 ] , splitToken [ 1 ] , true
}
2023-10-19 10:34:00 +00:00
func ( repo * TokenVerifierRepo ) jwtTokenVerifier ( ctx context . Context ) * op . AccessTokenVerifier {
2022-08-23 06:02:36 +00:00
keySet := & openIDKeySet { repo . Query }
feat: trusted (instance) domains (#8369)
# Which Problems Are Solved
ZITADEL currently selects the instance context based on a HTTP header
(see https://github.com/zitadel/zitadel/issues/8279#issue-2399959845 and
checks it against the list of instance domains. Let's call it instance
or API domain.
For any context based URL (e.g. OAuth, OIDC, SAML endpoints, links in
emails, ...) the requested domain (instance domain) will be used. Let's
call it the public domain.
In cases of proxied setups, all exposed domains (public domains) require
the domain to be managed as instance domain.
This can either be done using the "ExternalDomain" in the runtime config
or via system API, which requires a validation through CustomerPortal on
zitadel.cloud.
# How the Problems Are Solved
- Two new headers / header list are added:
- `InstanceHostHeaders`: an ordered list (first sent wins), which will
be used to match the instance.
(For backward compatibility: the `HTTP1HostHeader`, `HTTP2HostHeader`
and `forwarded`, `x-forwarded-for`, `x-forwarded-host` are checked
afterwards as well)
- `PublicHostHeaders`: an ordered list (first sent wins), which will be
used as public host / domain. This will be checked against a list of
trusted domains on the instance.
- The middleware intercepts all requests to the API and passes a
`DomainCtx` object with the hosts and protocol into the context
(previously only a computed `origin` was passed)
- HTTP / GRPC server do not longer try to match the headers to instances
themself, but use the passed `http.DomainContext` in their interceptors.
- The `RequestedHost` and `RequestedDomain` from authz.Instance are
removed in favor of the `http.DomainContext`
- When authenticating to or signing out from Console UI, the current
`http.DomainContext(ctx).Origin` (already checked by instance
interceptor for validity) is used to compute and dynamically add a
`redirect_uri` and `post_logout_redirect_uri`.
- Gateway passes all configured host headers (previously only did
`x-zitadel-*`)
- Admin API allows to manage trusted domain
# Additional Changes
None
# Additional Context
- part of #8279
- open topics:
- "single-instance" mode
- Console UI
2024-07-31 15:00:38 +00:00
return op . NewAccessTokenVerifier ( http_util . DomainContext ( ctx ) . Origin ( ) , keySet )
2022-08-23 06:02:36 +00:00
}
func ( repo * TokenVerifierRepo ) decryptAccessToken ( token string ) ( string , error ) {
tokenData , err := base64 . RawURLEncoding . DecodeString ( token )
if err != nil {
2023-12-08 14:30:55 +00:00
return "" , zerrors . ThrowUnauthenticated ( nil , "APP-ASdgg" , "invalid token" )
2022-08-23 06:02:36 +00:00
}
tokenIDSubject , err := repo . TokenVerificationKey . DecryptString ( tokenData , repo . TokenVerificationKey . EncryptionKeyID ( ) )
if err != nil {
2023-12-08 14:30:55 +00:00
return "" , zerrors . ThrowUnauthenticated ( nil , "APP-8EF0zZ" , "invalid token" )
2022-08-23 06:02:36 +00:00
}
return tokenIDSubject , nil
}
2023-07-14 11:16:16 +00:00
func verifyAudience ( audience [ ] string , verifierClientID , projectID string ) error {
for _ , aud := range audience {
if verifierClientID == aud || projectID == aud {
return nil
}
}
2023-12-08 14:30:55 +00:00
return zerrors . ThrowUnauthenticated ( nil , "APP-Zxfako" , "invalid audience" )
2023-07-14 11:16:16 +00:00
}
2022-08-23 06:02:36 +00:00
type openIDKeySet struct {
* query . Queries
}
2022-09-15 12:59:40 +00:00
// VerifySignature implements the oidc.KeySet interface
// providing an implementation for the keys retrieved directly from Queries
2024-08-26 09:26:13 +00:00
func ( o * openIDKeySet ) VerifySignature ( ctx context . Context , jws * jose . JSONWebSignature ) ( payload [ ] byte , err error ) {
keySet := new ( jose . JSONWebKeySet )
if authz . GetFeatures ( ctx ) . WebKey {
keySet , err = o . Queries . GetWebKeySet ( ctx )
if err != nil {
return nil , err
}
}
legacyKeySet , err := o . Queries . ActivePublicKeys ( ctx , time . Now ( ) )
2022-08-23 06:02:36 +00:00
if err != nil {
return nil , fmt . Errorf ( "error fetching keys: %w" , err )
}
2024-08-26 09:26:13 +00:00
appendPublicKeysToWebKeySet ( keySet , legacyKeySet )
2022-08-23 06:02:36 +00:00
keyID , alg := oidc . GetKeyIDAndAlg ( jws )
2024-08-26 09:26:13 +00:00
key , err := oidc . FindMatchingKey ( keyID , oidc . KeyUseSignature , alg , keySet . Keys ... )
2022-08-23 06:02:36 +00:00
if err != nil {
return nil , fmt . Errorf ( "invalid signature: %w" , err )
}
return jws . Verify ( & key )
}
2024-08-26 09:26:13 +00:00
func appendPublicKeysToWebKeySet ( keyset * jose . JSONWebKeySet , pubkeys * query . PublicKeys ) {
if pubkeys == nil || len ( pubkeys . Keys ) == 0 {
return
}
keyset . Keys = slices . Grow ( keyset . Keys , len ( pubkeys . Keys ) )
for _ , key := range pubkeys . Keys {
keyset . Keys = append ( keyset . Keys , jose . JSONWebKey {
Key : key . Key ( ) ,
2022-08-23 06:02:36 +00:00
KeyID : key . ID ( ) ,
Algorithm : key . Algorithm ( ) ,
Use : key . Use ( ) . String ( ) ,
2024-08-26 09:26:13 +00:00
} )
2022-08-23 06:02:36 +00:00
}
2021-02-22 13:08:47 +00:00
}