2023-07-10 13:27:00 +00:00
package command
import (
"context"
"encoding/base64"
2023-07-17 12:33:37 +00:00
"fmt"
2023-07-10 13:27:00 +00:00
"strings"
"time"
2023-07-17 12:33:37 +00:00
"github.com/zitadel/logging"
2024-05-16 05:07:56 +00:00
"golang.org/x/text/language"
2023-07-17 12:33:37 +00:00
2024-05-16 05:07:56 +00:00
"github.com/zitadel/zitadel/internal/activity"
2023-07-10 13:27:00 +00:00
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
2024-03-20 10:18:46 +00:00
"github.com/zitadel/zitadel/internal/domain"
2023-07-10 13:27:00 +00:00
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/repository/authrequest"
"github.com/zitadel/zitadel/internal/repository/oidcsession"
2024-10-31 14:57:17 +00:00
"github.com/zitadel/zitadel/internal/repository/sessionlogout"
2023-07-14 11:16:16 +00:00
"github.com/zitadel/zitadel/internal/repository/user"
2024-05-16 05:07:56 +00:00
"github.com/zitadel/zitadel/internal/telemetry/tracing"
2023-12-08 14:30:55 +00:00
"github.com/zitadel/zitadel/internal/zerrors"
2023-07-10 13:27:00 +00:00
)
2023-07-17 12:33:37 +00:00
const (
TokenDelimiter = "-"
AccessTokenPrefix = "at_"
RefreshTokenPrefix = "rt_"
oidcTokenSubjectDelimiter = ":"
oidcTokenFormat = "%s" + oidcTokenSubjectDelimiter + "%s"
)
2024-05-16 05:07:56 +00:00
type OIDCSession struct {
SessionID string
TokenID string
ClientID string
UserID string
Audience [ ] string
Expiration time . Time
Scope [ ] string
AuthMethods [ ] domain . UserAuthMethodType
AuthTime time . Time
Nonce string
PreferredLanguage * language . Tag
UserAgent * domain . UserAgent
Reason domain . TokenReason
Actor * domain . TokenActor
RefreshToken string
}
type AuthRequestComplianceChecker func ( context . Context , * AuthRequestWriteModel ) error
// CreateOIDCSessionFromAuthRequest creates a new OIDC Session, creates an access token and refresh token.
// It returns the access token id, expiration and the refresh token.
2023-07-10 13:27:00 +00:00
// If the underlying [AuthRequest] is a OIDC Auth Code Flow, it will set the code as exchanged.
2024-05-16 05:07:56 +00:00
func ( c * Commands ) CreateOIDCSessionFromAuthRequest ( ctx context . Context , authReqId string , complianceCheck AuthRequestComplianceChecker , needRefreshToken bool ) ( session * OIDCSession , state string , err error ) {
ctx , span := tracing . NewSpan ( ctx )
defer func ( ) { span . EndWithError ( err ) } ( )
if authReqId == "" {
return nil , "" , zerrors . ThrowPreconditionFailed ( nil , "COMMAND-Sf3g2" , "Errors.AuthRequest.InvalidCode" )
}
authReqModel , err := c . getAuthRequestWriteModel ( ctx , authReqId )
2023-07-10 13:27:00 +00:00
if err != nil {
2024-05-16 05:07:56 +00:00
return nil , "" , err
2023-07-10 13:27:00 +00:00
}
2024-05-16 05:07:56 +00:00
if authReqModel . ResponseType == domain . OIDCResponseTypeCode && authReqModel . AuthRequestState != domain . AuthRequestStateCodeAdded {
return nil , "" , zerrors . ThrowPreconditionFailed ( nil , "COMMAND-Iung5" , "Errors.AuthRequest.NoCode" )
2023-07-10 13:27:00 +00:00
}
2024-05-16 05:07:56 +00:00
2024-10-28 08:29:34 +00:00
instanceID := authz . GetInstance ( ctx ) . InstanceID ( )
sessionModel := NewSessionWriteModel ( authReqModel . SessionID , instanceID )
2024-05-16 05:07:56 +00:00
err = c . eventstore . FilterToQueryReducer ( ctx , sessionModel )
if err != nil {
return nil , "" , err
}
if err = sessionModel . CheckIsActive ( ) ; err != nil {
return nil , "" , err
}
2024-09-17 13:21:49 +00:00
cmd , err := c . newOIDCSessionAddEvents ( ctx , sessionModel . UserID , sessionModel . UserResourceOwner )
2024-05-16 05:07:56 +00:00
if err != nil {
return nil , "" , err
}
if authReqModel . ResponseType == domain . OIDCResponseTypeCode {
if err = cmd . SetAuthRequestCodeExchanged ( ctx , authReqModel ) ; err != nil {
return nil , "" , err
}
}
if err = complianceCheck ( ctx , authReqModel ) ; err != nil {
return nil , "" , err
}
cmd . AddSession ( ctx ,
sessionModel . UserID ,
sessionModel . UserResourceOwner ,
sessionModel . AggregateID ,
authReqModel . ClientID ,
authReqModel . Audience ,
authReqModel . Scope ,
authReqModel . AuthMethods ,
authReqModel . AuthTime ,
authReqModel . Nonce ,
sessionModel . PreferredLanguage ,
sessionModel . UserAgent ,
)
if authReqModel . ResponseType != domain . OIDCResponseTypeIDToken {
if err = cmd . AddAccessToken ( ctx , authReqModel . Scope , sessionModel . UserID , sessionModel . UserResourceOwner , domain . TokenReasonAuthRequest , nil ) ; err != nil {
return nil , "" , err
}
}
if authReqModel . NeedRefreshToken && needRefreshToken {
if err = cmd . AddRefreshToken ( ctx , sessionModel . UserID ) ; err != nil {
return nil , "" , err
}
}
cmd . SetAuthRequestSuccessful ( ctx , authReqModel . aggregate )
2024-10-28 08:29:34 +00:00
postCommit , err := cmd . SetMilestones ( ctx , authReqModel . ClientID , true )
if err != nil {
return nil , "" , err
}
if session , err = cmd . PushEvents ( ctx ) ; err != nil {
return nil , "" , err
}
postCommit ( ctx )
return session , authReqModel . State , nil
2023-07-10 13:27:00 +00:00
}
2024-05-16 05:07:56 +00:00
func ( c * Commands ) CreateOIDCSession ( ctx context . Context ,
userID ,
resourceOwner ,
2024-10-31 14:57:17 +00:00
clientID ,
backChannelLogoutURI string ,
2024-05-16 05:07:56 +00:00
scope ,
audience [ ] string ,
authMethods [ ] domain . UserAuthMethodType ,
authTime time . Time ,
nonce string ,
preferredLanguage * language . Tag ,
userAgent * domain . UserAgent ,
reason domain . TokenReason ,
actor * domain . TokenActor ,
needRefreshToken bool ,
2024-09-03 13:19:00 +00:00
sessionID string ,
2024-11-12 15:20:48 +00:00
responseType domain . OIDCResponseType ,
2024-05-16 05:07:56 +00:00
) ( session * OIDCSession , err error ) {
ctx , span := tracing . NewSpan ( ctx )
defer func ( ) { span . EndWithError ( err ) } ( )
2024-09-17 13:21:49 +00:00
cmd , err := c . newOIDCSessionAddEvents ( ctx , userID , resourceOwner )
2023-07-10 13:27:00 +00:00
if err != nil {
2024-05-16 05:07:56 +00:00
return nil , err
2023-07-10 13:27:00 +00:00
}
2024-05-16 05:07:56 +00:00
if reason == domain . TokenReasonImpersonation {
if err := c . checkPermission ( ctx , "impersonation" , resourceOwner , userID ) ; err != nil {
return nil , err
}
cmd . UserImpersonated ( ctx , userID , resourceOwner , clientID , actor )
2023-07-10 13:27:00 +00:00
}
2024-05-16 05:07:56 +00:00
2024-09-03 13:19:00 +00:00
cmd . AddSession ( ctx , userID , resourceOwner , sessionID , clientID , audience , scope , authMethods , authTime , nonce , preferredLanguage , userAgent )
2024-10-31 14:57:17 +00:00
cmd . RegisterLogout ( ctx , sessionID , userID , clientID , backChannelLogoutURI )
2024-11-12 15:20:48 +00:00
if responseType != domain . OIDCResponseTypeIDToken {
if err = cmd . AddAccessToken ( ctx , scope , userID , resourceOwner , reason , actor ) ; err != nil {
return nil , err
}
2024-05-16 05:07:56 +00:00
}
if needRefreshToken {
if err = cmd . AddRefreshToken ( ctx , userID ) ; err != nil {
return nil , err
}
2023-07-10 13:27:00 +00:00
}
2024-10-28 08:29:34 +00:00
postCommit , err := cmd . SetMilestones ( ctx , clientID , sessionID != "" )
if err != nil {
return nil , err
}
if session , err = cmd . PushEvents ( ctx ) ; err != nil {
return nil , err
}
postCommit ( ctx )
return session , nil
2023-07-10 13:27:00 +00:00
}
2024-05-16 05:07:56 +00:00
type RefreshTokenComplianceChecker func ( ctx context . Context , wm * OIDCSessionWriteModel , requestedScope [ ] string ) ( scope [ ] string , err error )
2023-07-10 13:27:00 +00:00
// ExchangeOIDCSessionRefreshAndAccessToken updates an existing OIDC Session, creates a new access and refresh token.
// It returns the access token id and expiration and the new refresh token.
2024-05-16 05:07:56 +00:00
func ( c * Commands ) ExchangeOIDCSessionRefreshAndAccessToken ( ctx context . Context , refreshToken string , scope [ ] string , complianceCheck RefreshTokenComplianceChecker ) ( _ * OIDCSession , err error ) {
ctx , span := tracing . NewSpan ( ctx )
defer func ( ) { span . EndWithError ( err ) } ( )
cmd , err := c . newOIDCSessionUpdateEvents ( ctx , refreshToken )
2023-07-10 13:27:00 +00:00
if err != nil {
2024-05-16 05:07:56 +00:00
return nil , err
2023-07-10 13:27:00 +00:00
}
2024-05-16 05:07:56 +00:00
scope , err = complianceCheck ( ctx , cmd . oidcSessionWriteModel , scope )
if err != nil {
return nil , err
}
err = cmd . AddAccessToken ( ctx , scope ,
cmd . oidcSessionWriteModel . UserID ,
cmd . oidcSessionWriteModel . UserResourceOwner ,
domain . TokenReasonRefresh ,
cmd . oidcSessionWriteModel . AccessTokenActor ,
)
if err != nil {
return nil , err
2023-07-10 13:27:00 +00:00
}
if err = cmd . RenewRefreshToken ( ctx ) ; err != nil {
2024-05-16 05:07:56 +00:00
return nil , err
2023-07-10 13:27:00 +00:00
}
return cmd . PushEvents ( ctx )
}
// OIDCSessionByRefreshToken computes the current state of an existing OIDCSession by a refresh_token (to start a Refresh Token Grant).
// If either the session is not active, the token is invalid or expired (incl. idle expiration) an invalid refresh token error will be returned.
2024-05-16 05:07:56 +00:00
func ( c * Commands ) OIDCSessionByRefreshToken ( ctx context . Context , refreshToken string ) ( _ * OIDCSessionWriteModel , err error ) {
ctx , span := tracing . NewSpan ( ctx )
defer func ( ) { span . EndWithError ( err ) } ( )
2023-07-17 12:33:37 +00:00
oidcSessionID , refreshTokenID , err := parseRefreshToken ( refreshToken )
if err != nil {
return nil , err
2023-07-10 13:27:00 +00:00
}
2023-07-17 12:33:37 +00:00
writeModel := NewOIDCSessionWriteModel ( oidcSessionID , "" )
err = c . eventstore . FilterToQueryReducer ( ctx , writeModel )
2023-07-10 13:27:00 +00:00
if err != nil {
2023-12-08 14:30:55 +00:00
return nil , zerrors . ThrowPreconditionFailed ( err , "OIDCS-SAF31" , "Errors.OIDCSession.RefreshTokenInvalid" )
2023-07-10 13:27:00 +00:00
}
2023-07-17 12:33:37 +00:00
if err = writeModel . CheckRefreshToken ( refreshTokenID ) ; err != nil {
2023-07-10 13:27:00 +00:00
return nil , err
}
return writeModel , nil
}
2023-07-17 12:33:37 +00:00
func oidcSessionTokenIDsFromToken ( token string ) ( oidcSessionID , refreshTokenID , accessTokenID string , err error ) {
split := strings . Split ( token , TokenDelimiter )
if len ( split ) != 2 {
2023-12-08 14:30:55 +00:00
return "" , "" , "" , zerrors . ThrowPreconditionFailed ( nil , "OIDCS-S87kl" , "Errors.OIDCSession.Token.Invalid" )
2023-07-17 12:33:37 +00:00
}
if strings . HasPrefix ( split [ 1 ] , RefreshTokenPrefix ) {
return split [ 0 ] , split [ 1 ] , "" , nil
}
if strings . HasPrefix ( split [ 1 ] , AccessTokenPrefix ) {
return split [ 0 ] , "" , split [ 1 ] , nil
}
2023-12-08 14:30:55 +00:00
return "" , "" , "" , zerrors . ThrowPreconditionFailed ( nil , "OIDCS-S87kl" , "Errors.OIDCSession.Token.Invalid" )
2023-07-17 12:33:37 +00:00
}
// RevokeOIDCSessionToken revokes an access_token or refresh_token
// if the OIDCSession cannot be retrieved by the provided token, is not active or if the token is already revoked,
// then no error will be returned.
// The only possible error (except db connection or other internal errors) occurs if a client tries to revoke a token,
// which was not part of the audience.
func ( c * Commands ) RevokeOIDCSessionToken ( ctx context . Context , token , clientID string ) ( err error ) {
oidcSessionID , refreshTokenID , accessTokenID , err := oidcSessionTokenIDsFromToken ( token )
if err != nil {
logging . WithError ( err ) . Info ( "token revocation with invalid token format" )
return nil
}
writeModel := NewOIDCSessionWriteModel ( oidcSessionID , "" )
err = c . eventstore . FilterToQueryReducer ( ctx , writeModel )
if err != nil {
2023-12-08 14:30:55 +00:00
return zerrors . ThrowInternal ( err , "OIDCS-NB3t2" , "Errors.Internal" )
2023-07-17 12:33:37 +00:00
}
if err = writeModel . CheckClient ( clientID ) ; err != nil {
return err
}
if refreshTokenID != "" {
if err = writeModel . CheckRefreshToken ( refreshTokenID ) ; err != nil {
logging . WithFields ( "oidcSessionID" , oidcSessionID , "refreshTokenID" , refreshTokenID ) . WithError ( err ) .
Info ( "refresh token revocation with invalid token" )
return nil
}
return c . pushAppendAndReduce ( ctx , writeModel , oidcsession . NewRefreshTokenRevokedEvent ( ctx , writeModel . aggregate ) )
}
if err = writeModel . CheckAccessToken ( accessTokenID ) ; err != nil {
logging . WithFields ( "oidcSessionID" , oidcSessionID , "accessTokenID" , accessTokenID ) . WithError ( err ) .
Info ( "access token revocation with invalid token" )
return nil
}
return c . pushAppendAndReduce ( ctx , writeModel , oidcsession . NewAccessTokenRevokedEvent ( ctx , writeModel . aggregate ) )
}
2024-09-17 13:21:49 +00:00
func ( c * Commands ) newOIDCSessionAddEvents ( ctx context . Context , userID , resourceOwner string , pending ... eventstore . Command ) ( * OIDCSessionEvents , error ) {
userStateModel , err := c . userStateWriteModel ( ctx , userID )
if err != nil {
return nil , err
}
if ! userStateModel . UserState . IsEnabled ( ) {
return nil , zerrors . ThrowPreconditionFailed ( nil , "OIDCS-kj3g2" , "Errors.User.NotActive" )
}
2023-07-10 13:27:00 +00:00
accessTokenLifetime , refreshTokenLifeTime , refreshTokenIdleLifetime , err := c . tokenTokenLifetimes ( ctx )
if err != nil {
return nil , err
}
sessionID , err := c . idGenerator . Next ( )
if err != nil {
return nil , err
}
sessionID = IDPrefixV2 + sessionID
return & OIDCSessionEvents {
2024-10-28 08:29:34 +00:00
commands : c ,
2023-07-10 13:27:00 +00:00
idGenerator : c . idGenerator ,
encryptionAlg : c . keyAlgorithm ,
2024-05-16 05:07:56 +00:00
events : pending ,
2023-07-14 11:16:16 +00:00
oidcSessionWriteModel : NewOIDCSessionWriteModel ( sessionID , resourceOwner ) ,
2024-09-17 13:21:49 +00:00
userStateModel : userStateModel ,
2023-07-10 13:27:00 +00:00
accessTokenLifetime : accessTokenLifetime ,
refreshTokenLifeTime : refreshTokenLifeTime ,
refreshTokenIdleLifetime : refreshTokenIdleLifetime ,
} , nil
}
2024-05-16 05:07:56 +00:00
func ( c * Commands ) decryptRefreshToken ( refreshToken string ) ( sessionID , refreshTokenID string , err error ) {
2023-07-10 13:27:00 +00:00
decoded , err := base64 . RawURLEncoding . DecodeString ( refreshToken )
if err != nil {
2024-05-16 05:07:56 +00:00
return "" , "" , zerrors . ThrowInvalidArgument ( err , "OIDCS-Cux9a" , "Errors.User.RefreshToken.Invalid" )
2023-07-10 13:27:00 +00:00
}
decrypted , err := c . keyAlgorithm . DecryptString ( decoded , c . keyAlgorithm . EncryptionKeyID ( ) )
if err != nil {
2024-08-02 08:38:37 +00:00
return "" , "" , zerrors . ThrowInvalidArgument ( err , "OIDCS-Jei0i" , "Errors.User.RefreshToken.Invalid" )
2023-07-10 13:27:00 +00:00
}
2024-05-16 05:07:56 +00:00
return parseRefreshToken ( decrypted )
2023-07-17 12:33:37 +00:00
}
func parseRefreshToken ( refreshToken string ) ( oidcSessionID , refreshTokenID string , err error ) {
split := strings . Split ( refreshToken , TokenDelimiter )
if len ( split ) < 2 || ! strings . HasPrefix ( split [ 1 ] , RefreshTokenPrefix ) {
2023-12-08 14:30:55 +00:00
return "" , "" , zerrors . ThrowPreconditionFailed ( nil , "OIDCS-JOI23" , "Errors.OIDCSession.RefreshTokenInvalid" )
2023-07-17 12:33:37 +00:00
}
// the oidc library requires that every token has the format of <tokenID>:<userID>
// the V2 tokens don't use the userID anymore, so let's just remove it
return split [ 0 ] , strings . Split ( split [ 1 ] , oidcTokenSubjectDelimiter ) [ 0 ] , nil
2023-07-10 13:27:00 +00:00
}
2024-05-16 05:07:56 +00:00
func ( c * Commands ) newOIDCSessionUpdateEvents ( ctx context . Context , refreshToken string ) ( * OIDCSessionEvents , error ) {
oidcSessionID , refreshTokenID , err := c . decryptRefreshToken ( refreshToken )
2023-07-10 13:27:00 +00:00
if err != nil {
return nil , err
}
2023-07-14 11:16:16 +00:00
sessionWriteModel := NewOIDCSessionWriteModel ( oidcSessionID , "" )
2023-07-10 13:27:00 +00:00
if err = c . eventstore . FilterToQueryReducer ( ctx , sessionWriteModel ) ; err != nil {
return nil , err
}
if err = sessionWriteModel . CheckRefreshToken ( refreshTokenID ) ; err != nil {
return nil , err
}
2024-09-17 13:21:49 +00:00
userStateWriteModel , err := c . userStateWriteModel ( ctx , sessionWriteModel . UserID )
if err != nil {
return nil , err
}
if ! userStateWriteModel . UserState . IsEnabled ( ) {
return nil , zerrors . ThrowPreconditionFailed ( nil , "OIDCS-J39h2" , "Errors.User.NotActive" )
}
2023-07-10 13:27:00 +00:00
accessTokenLifetime , refreshTokenLifeTime , refreshTokenIdleLifetime , err := c . tokenTokenLifetimes ( ctx )
if err != nil {
return nil , err
}
return & OIDCSessionEvents {
2024-10-28 08:29:34 +00:00
commands : c ,
2023-07-10 13:27:00 +00:00
idGenerator : c . idGenerator ,
encryptionAlg : c . keyAlgorithm ,
oidcSessionWriteModel : sessionWriteModel ,
accessTokenLifetime : accessTokenLifetime ,
refreshTokenLifeTime : refreshTokenLifeTime ,
refreshTokenIdleLifetime : refreshTokenIdleLifetime ,
} , nil
}
type OIDCSessionEvents struct {
2024-10-28 08:29:34 +00:00
commands * Commands
2024-05-16 05:07:56 +00:00
idGenerator id . Generator
encryptionAlg crypto . EncryptionAlgorithm
events [ ] eventstore . Command
oidcSessionWriteModel * OIDCSessionWriteModel
2024-09-17 13:21:49 +00:00
userStateModel * UserV2WriteModel
2024-05-16 05:07:56 +00:00
2023-07-10 13:27:00 +00:00
accessTokenLifetime time . Duration
refreshTokenLifeTime time . Duration
refreshTokenIdleLifetime time . Duration
// accessTokenID is set by the command
accessTokenID string
// refreshToken is set by the command
2024-05-16 05:07:56 +00:00
refreshTokenID string
refreshToken string
2023-07-10 13:27:00 +00:00
}
2024-05-16 05:07:56 +00:00
func ( c * OIDCSessionEvents ) AddSession (
ctx context . Context ,
userID ,
userResourceOwner ,
sessionID ,
clientID string ,
audience ,
scope [ ] string ,
authMethods [ ] domain . UserAuthMethodType ,
authTime time . Time ,
nonce string ,
preferredLanguage * language . Tag ,
userAgent * domain . UserAgent ,
) {
2023-07-10 13:27:00 +00:00
c . events = append ( c . events , oidcsession . NewAddedEvent (
ctx ,
c . oidcSessionWriteModel . aggregate ,
2024-05-16 05:07:56 +00:00
userID ,
userResourceOwner ,
sessionID ,
clientID ,
audience ,
scope ,
authMethods ,
authTime ,
nonce ,
preferredLanguage ,
userAgent ,
2023-07-10 13:27:00 +00:00
) )
}
2024-05-16 05:07:56 +00:00
func ( c * OIDCSessionEvents ) SetAuthRequestCodeExchanged ( ctx context . Context , model * AuthRequestWriteModel ) error {
event := authrequest . NewCodeExchangedEvent ( ctx , model . aggregate )
model . AppendEvents ( event )
c . events = append ( c . events , event )
return model . Reduce ( )
}
func ( c * OIDCSessionEvents ) SetAuthRequestSuccessful ( ctx context . Context , authRequestAggregate * eventstore . Aggregate ) {
c . events = append ( c . events , authrequest . NewSucceededEvent ( ctx , authRequestAggregate ) )
}
func ( c * OIDCSessionEvents ) SetAuthRequestFailed ( ctx context . Context , authRequestAggregate * eventstore . Aggregate , err error ) {
c . events = append ( c . events , authrequest . NewFailedEvent ( ctx , authRequestAggregate , domain . OIDCErrorReasonFromError ( err ) ) )
2023-07-10 13:27:00 +00:00
}
2024-10-31 14:57:17 +00:00
func ( c * OIDCSessionEvents ) RegisterLogout ( ctx context . Context , sessionID , userID , clientID , backChannelLogoutURI string ) {
// If there's no SSO session (e.g. service accounts) we do not need to register a logout handler.
// Also, if the client did not register a backchannel_logout_uri it will not support it (https://openid.net/specs/openid-connect-backchannel-1_0.html#BCRegistration)
if sessionID == "" || backChannelLogoutURI == "" {
return
}
if ! authz . GetFeatures ( ctx ) . EnableBackChannelLogout {
return
}
c . events = append ( c . events , sessionlogout . NewBackChannelLogoutRegisteredEvent (
ctx ,
& sessionlogout . NewAggregate ( sessionID , authz . GetInstance ( ctx ) . InstanceID ( ) ) . Aggregate ,
c . oidcSessionWriteModel . AggregateID ,
userID ,
clientID ,
backChannelLogoutURI ,
) )
}
2024-05-16 05:07:56 +00:00
func ( c * OIDCSessionEvents ) AddAccessToken ( ctx context . Context , scope [ ] string , userID , resourceOwner string , reason domain . TokenReason , actor * domain . TokenActor ) error {
2023-07-17 12:33:37 +00:00
accessTokenID , err := c . idGenerator . Next ( )
2023-07-10 13:27:00 +00:00
if err != nil {
return err
}
2023-07-17 12:33:37 +00:00
c . accessTokenID = AccessTokenPrefix + accessTokenID
2024-09-26 13:55:41 +00:00
c . events = append ( c . events , oidcsession . NewAccessTokenAddedEvent ( ctx , c . oidcSessionWriteModel . aggregate , c . accessTokenID , scope , c . accessTokenLifetime , reason , actor ) )
if ! authz . GetFeatures ( ctx ) . DisableUserTokenEvent {
c . events = append ( c . events , user . NewUserTokenV2AddedEvent ( ctx , & user . NewAggregate ( userID , resourceOwner ) . Aggregate , c . accessTokenID ) )
}
2023-07-10 13:27:00 +00:00
return nil
}
2024-05-16 05:07:56 +00:00
func ( c * OIDCSessionEvents ) AddRefreshToken ( ctx context . Context , userID string ) ( err error ) {
c . refreshTokenID , c . refreshToken , err = c . generateRefreshToken ( userID )
2023-07-10 13:27:00 +00:00
if err != nil {
return err
}
2024-05-16 05:07:56 +00:00
c . events = append ( c . events , oidcsession . NewRefreshTokenAddedEvent ( ctx , c . oidcSessionWriteModel . aggregate , c . refreshTokenID , c . refreshTokenLifeTime , c . refreshTokenIdleLifetime ) )
2023-07-10 13:27:00 +00:00
return nil
}
func ( c * OIDCSessionEvents ) RenewRefreshToken ( ctx context . Context ) ( err error ) {
var refreshTokenID string
2023-07-17 12:33:37 +00:00
refreshTokenID , c . refreshToken , err = c . generateRefreshToken ( c . oidcSessionWriteModel . UserID )
2023-07-10 13:27:00 +00:00
if err != nil {
return err
}
c . events = append ( c . events , oidcsession . NewRefreshTokenRenewedEvent ( ctx , c . oidcSessionWriteModel . aggregate , refreshTokenID , c . refreshTokenIdleLifetime ) )
return nil
}
2024-05-16 05:07:56 +00:00
func ( c * OIDCSessionEvents ) UserImpersonated ( ctx context . Context , userID , resourceOwner , clientID string , actor * domain . TokenActor ) {
c . events = append ( c . events , user . NewUserImpersonatedEvent ( ctx , & user . NewAggregate ( userID , resourceOwner ) . Aggregate , clientID , actor ) )
}
2023-07-17 12:33:37 +00:00
func ( c * OIDCSessionEvents ) generateRefreshToken ( userID string ) ( refreshTokenID , refreshToken string , err error ) {
2023-07-10 13:27:00 +00:00
refreshTokenID , err = c . idGenerator . Next ( )
if err != nil {
return "" , "" , err
}
2023-07-17 12:33:37 +00:00
refreshTokenID = RefreshTokenPrefix + refreshTokenID
token , err := c . encryptionAlg . Encrypt ( [ ] byte ( fmt . Sprintf ( oidcTokenFormat , c . oidcSessionWriteModel . OIDCRefreshTokenID ( refreshTokenID ) , userID ) ) )
2023-07-10 13:27:00 +00:00
if err != nil {
return "" , "" , err
}
return refreshTokenID , base64 . RawURLEncoding . EncodeToString ( token ) , nil
}
2024-05-16 05:07:56 +00:00
func ( c * OIDCSessionEvents ) PushEvents ( ctx context . Context ) ( * OIDCSession , error ) {
2024-10-28 08:29:34 +00:00
pushedEvents , err := c . commands . eventstore . Push ( ctx , c . events ... )
2023-07-10 13:27:00 +00:00
if err != nil {
2024-05-16 05:07:56 +00:00
return nil , err
2023-07-10 13:27:00 +00:00
}
err = AppendAndReduce ( c . oidcSessionWriteModel , pushedEvents ... )
if err != nil {
2024-05-16 05:07:56 +00:00
return nil , err
2023-07-10 13:27:00 +00:00
}
2024-05-16 05:07:56 +00:00
session := & OIDCSession {
SessionID : c . oidcSessionWriteModel . SessionID ,
ClientID : c . oidcSessionWriteModel . ClientID ,
UserID : c . oidcSessionWriteModel . UserID ,
Audience : c . oidcSessionWriteModel . Audience ,
Expiration : c . oidcSessionWriteModel . AccessTokenExpiration ,
Scope : c . oidcSessionWriteModel . Scope ,
AuthMethods : c . oidcSessionWriteModel . AuthMethods ,
AuthTime : c . oidcSessionWriteModel . AuthTime ,
Nonce : c . oidcSessionWriteModel . Nonce ,
PreferredLanguage : c . oidcSessionWriteModel . PreferredLanguage ,
UserAgent : c . oidcSessionWriteModel . UserAgent ,
Reason : c . oidcSessionWriteModel . AccessTokenReason ,
Actor : c . oidcSessionWriteModel . AccessTokenActor ,
RefreshToken : c . refreshToken ,
}
if c . accessTokenID != "" {
// prefix the returned id with the oidcSessionID so that we can retrieve it later on
// we need to use `-` as a delimiter because the OIDC library uses `:` and will check for a length of 2 parts
session . TokenID = c . oidcSessionWriteModel . AggregateID + TokenDelimiter + c . accessTokenID
}
2024-10-28 08:29:34 +00:00
activity . Trigger ( ctx , c . oidcSessionWriteModel . UserResourceOwner , c . oidcSessionWriteModel . UserID , tokenReasonToActivityMethodType ( c . oidcSessionWriteModel . AccessTokenReason ) , c . commands . eventstore . FilterToQueryReducer )
2024-05-16 05:07:56 +00:00
return session , nil
2023-07-10 13:27:00 +00:00
}
func ( c * Commands ) tokenTokenLifetimes ( ctx context . Context ) ( accessTokenLifetime time . Duration , refreshTokenLifetime time . Duration , refreshTokenIdleLifetime time . Duration , err error ) {
oidcSettings := NewInstanceOIDCSettingsWriteModel ( ctx )
err = c . eventstore . FilterToQueryReducer ( ctx , oidcSettings )
if err != nil {
return 0 , 0 , 0 , err
}
accessTokenLifetime = c . defaultAccessTokenLifetime
refreshTokenLifetime = c . defaultRefreshTokenLifetime
refreshTokenIdleLifetime = c . defaultRefreshTokenIdleLifetime
if oidcSettings . AccessTokenLifetime > 0 {
accessTokenLifetime = oidcSettings . AccessTokenLifetime
}
if oidcSettings . RefreshTokenExpiration > 0 {
refreshTokenLifetime = oidcSettings . RefreshTokenExpiration
}
if oidcSettings . RefreshTokenIdleExpiration > 0 {
refreshTokenIdleLifetime = oidcSettings . RefreshTokenIdleExpiration
}
return accessTokenLifetime , refreshTokenLifetime , refreshTokenIdleLifetime , nil
}
2024-05-16 05:07:56 +00:00
func tokenReasonToActivityMethodType ( r domain . TokenReason ) activity . TriggerMethod {
if r == domain . TokenReasonUnspecified {
return activity . Unspecified
}
if r == domain . TokenReasonRefresh {
return activity . OIDCRefreshToken
}
// all other reasons result in an access token
return activity . OIDCAccessToken
}