mirror of
https://github.com/zitadel/zitadel.git
synced 2025-03-01 11:57:23 +00:00

# Which Problems Are Solved The current login will always prefer external authentication (through an IdP) over local authentication. So as soon as either the user had connected to an IdP or even when the login policy was just set up to have an IdP allowed, users would be redirected to that IdP for (re)authentication. This could lead to problems, where the IdP was not available or any other error occurred in the process (such as secret expired for EntraID). Even when local authentication (passkeys or password) was allowed for the corresponding user, they would always be redirected to the IdP again, preventing any authentication. If admins were affected, they might not even be able to update the client secret of the IdP. # How the Problems Are Solved Errors during the external IdP flow are handled in an `externalAuthFailed` function, which will check if the organisation allows local authentication and if the user has set up such. If either password or passkeys is set up, the corresponding login page will be presented to the user. As already with local auth passkeys is preferred over password authentication. The user is informed that the external login failed and fail back to local auth as an error on the corresponding page in a focused mode. Any interaction or after 5 second the focus mode is disabled. # Additional Changes None. # Additional Context closes #6466
285 lines
6.8 KiB
Go
285 lines
6.8 KiB
Go
package domain
|
|
|
|
import (
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/text/language"
|
|
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
type AuthRequest struct {
|
|
ID string
|
|
AgentID string
|
|
CreationDate time.Time
|
|
ChangeDate time.Time
|
|
BrowserInfo *BrowserInfo
|
|
ApplicationID string
|
|
CallbackURI string
|
|
TransferState string
|
|
Prompt []Prompt
|
|
PossibleLOAs []LevelOfAssurance
|
|
UiLocales []string
|
|
LoginHint string
|
|
MaxAuthAge *time.Duration
|
|
InstanceID string
|
|
Request Request
|
|
|
|
levelOfAssurance LevelOfAssurance
|
|
UserID string
|
|
UserName string
|
|
LoginName string
|
|
DisplayName string
|
|
AvatarKey string
|
|
PresignedAvatar string
|
|
UserOrgID string
|
|
PreferredLanguage *language.Tag
|
|
RequestedOrgID string
|
|
RequestedOrgName string
|
|
RequestedPrimaryDomain string
|
|
RequestedOrgDomain bool
|
|
ApplicationResourceOwner string
|
|
PrivateLabelingSetting PrivateLabelingSetting
|
|
SelectedIDPConfigID string
|
|
LinkingUsers []*ExternalUser
|
|
PossibleSteps []NextStep `json:"-"`
|
|
PasswordVerified bool
|
|
IDPLoginChecked bool
|
|
MFAsVerified []MFAType
|
|
Audience []string
|
|
AuthTime time.Time
|
|
Code string
|
|
LoginPolicy *LoginPolicy
|
|
AllowedExternalIDPs []*IDPProvider
|
|
LabelPolicy *LabelPolicy
|
|
PrivacyPolicy *PrivacyPolicy
|
|
LockoutPolicy *LockoutPolicy
|
|
PasswordAgePolicy *PasswordAgePolicy
|
|
DefaultTranslations []*CustomText
|
|
OrgTranslations []*CustomText
|
|
SAMLRequestID string
|
|
RequestLocalAuth bool
|
|
// orgID the policies were last loaded with
|
|
policyOrgID string
|
|
// SessionID is set to the computed sessionID of the login session table
|
|
SessionID string
|
|
}
|
|
|
|
func (a *AuthRequest) SetPolicyOrgID(id string) {
|
|
a.policyOrgID = id
|
|
}
|
|
|
|
func (a *AuthRequest) PolicyOrgID() string {
|
|
return a.policyOrgID
|
|
}
|
|
|
|
func (a *AuthRequest) AuthMethods() []UserAuthMethodType {
|
|
list := make([]UserAuthMethodType, 0, len(a.MFAsVerified)+2)
|
|
if a.PasswordVerified {
|
|
list = append(list, UserAuthMethodTypePassword)
|
|
}
|
|
if a.IDPLoginChecked {
|
|
list = append(list, UserAuthMethodTypeIDP)
|
|
}
|
|
for _, mfa := range a.MFAsVerified {
|
|
list = append(list, mfa.UserAuthMethodType())
|
|
}
|
|
return slices.Compact(list)
|
|
}
|
|
|
|
type ExternalUser struct {
|
|
IDPConfigID string
|
|
ExternalUserID string
|
|
DisplayName string
|
|
PreferredUsername string
|
|
FirstName string
|
|
LastName string
|
|
NickName string
|
|
Email EmailAddress
|
|
IsEmailVerified bool
|
|
PreferredLanguage language.Tag
|
|
Phone PhoneNumber
|
|
IsPhoneVerified bool
|
|
Metadatas []*Metadata
|
|
}
|
|
|
|
type Prompt int32
|
|
|
|
const (
|
|
PromptUnspecified Prompt = iota
|
|
PromptNone
|
|
PromptLogin
|
|
PromptConsent
|
|
PromptSelectAccount
|
|
PromptCreate
|
|
)
|
|
|
|
func IsPrompt(prompt []Prompt, requestedPrompt Prompt) bool {
|
|
for _, p := range prompt {
|
|
if p == requestedPrompt {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
type LevelOfAssurance int
|
|
|
|
const (
|
|
LevelOfAssuranceNone LevelOfAssurance = iota
|
|
)
|
|
|
|
type MFAType int
|
|
|
|
const (
|
|
MFATypeTOTP MFAType = iota
|
|
MFATypeU2F
|
|
MFATypeU2FUserVerification
|
|
MFATypeOTPSMS
|
|
MFATypeOTPEmail
|
|
)
|
|
|
|
func (m MFAType) UserAuthMethodType() UserAuthMethodType {
|
|
switch m {
|
|
case MFATypeTOTP:
|
|
return UserAuthMethodTypeTOTP
|
|
case MFATypeU2F:
|
|
return UserAuthMethodTypeU2F
|
|
case MFATypeU2FUserVerification:
|
|
return UserAuthMethodTypePasswordless
|
|
case MFATypeOTPSMS:
|
|
return UserAuthMethodTypeOTPSMS
|
|
case MFATypeOTPEmail:
|
|
return UserAuthMethodTypeOTPEmail
|
|
default:
|
|
return UserAuthMethodTypeUnspecified
|
|
}
|
|
}
|
|
|
|
type MFALevel int
|
|
|
|
const (
|
|
MFALevelNotSetUp MFALevel = iota
|
|
MFALevelSecondFactor
|
|
MFALevelMultiFactor
|
|
MFALevelMultiFactorCertified
|
|
)
|
|
|
|
type AuthRequestState int
|
|
|
|
const (
|
|
AuthRequestStateUnspecified AuthRequestState = iota
|
|
AuthRequestStateAdded
|
|
AuthRequestStateCodeAdded
|
|
AuthRequestStateCodeExchanged
|
|
AuthRequestStateFailed
|
|
AuthRequestStateSucceeded
|
|
)
|
|
|
|
func NewAuthRequestFromType(requestType AuthRequestType) (*AuthRequest, error) {
|
|
switch requestType {
|
|
case AuthRequestTypeOIDC:
|
|
return &AuthRequest{Request: &AuthRequestOIDC{}}, nil
|
|
case AuthRequestTypeSAML:
|
|
return &AuthRequest{Request: &AuthRequestSAML{}}, nil
|
|
case AuthRequestTypeDevice:
|
|
return &AuthRequest{Request: &AuthRequestDevice{}}, nil
|
|
}
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "DOMAIN-ds2kl", "invalid request type")
|
|
}
|
|
|
|
func (a *AuthRequest) WithCurrentInfo(info *BrowserInfo) *AuthRequest {
|
|
a.BrowserInfo = info
|
|
return a
|
|
}
|
|
|
|
func (a *AuthRequest) SetUserInfo(userID, userName, loginName, displayName, avatar, userOrgID string) {
|
|
a.UserID = userID
|
|
a.UserName = userName
|
|
a.LoginName = loginName
|
|
a.DisplayName = displayName
|
|
a.AvatarKey = avatar
|
|
a.UserOrgID = userOrgID
|
|
}
|
|
|
|
func (a *AuthRequest) SetOrgInformation(id, name, primaryDomain string, requestedByDomain bool) {
|
|
a.RequestedOrgID = id
|
|
a.RequestedOrgName = name
|
|
a.RequestedPrimaryDomain = primaryDomain
|
|
a.RequestedOrgDomain = requestedByDomain
|
|
}
|
|
|
|
func (a *AuthRequest) MFALevel() MFALevel {
|
|
return -1
|
|
//PLANNED: check a.PossibleLOAs (and Prompt Login?)
|
|
}
|
|
|
|
func (a *AuthRequest) AppendAudIfNotExisting(aud string) {
|
|
for _, a := range a.Audience {
|
|
if a == aud {
|
|
return
|
|
}
|
|
}
|
|
a.Audience = append(a.Audience, aud)
|
|
}
|
|
|
|
func (a *AuthRequest) GetScopeOrgPrimaryDomain() string {
|
|
switch request := a.Request.(type) {
|
|
case *AuthRequestOIDC:
|
|
for _, scope := range request.Scopes {
|
|
if strings.HasPrefix(scope, OrgDomainPrimaryScope) {
|
|
return strings.TrimPrefix(scope, OrgDomainPrimaryScope)
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (a *AuthRequest) GetScopeOrgID() string {
|
|
switch request := a.Request.(type) {
|
|
case *AuthRequestOIDC:
|
|
for _, scope := range request.Scopes {
|
|
if strings.HasPrefix(scope, OrgIDScope) {
|
|
return strings.TrimPrefix(scope, OrgIDScope)
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (a *AuthRequest) Done() bool {
|
|
for _, step := range a.PossibleSteps {
|
|
if step.Type() == NextStepRedirectToCallback {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (a *AuthRequest) PrivateLabelingOrgID(defaultID string) string {
|
|
if a.RequestedOrgID != "" {
|
|
return a.RequestedOrgID
|
|
}
|
|
if (a.PrivateLabelingSetting == PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy || a.PrivateLabelingSetting == PrivateLabelingSettingUnspecified) &&
|
|
a.UserOrgID != "" {
|
|
return a.UserOrgID
|
|
}
|
|
if a.PrivateLabelingSetting != PrivateLabelingSettingUnspecified {
|
|
return a.ApplicationResourceOwner
|
|
}
|
|
return defaultID
|
|
}
|
|
|
|
func (a *AuthRequest) UserAuthMethodTypes() []UserAuthMethodType {
|
|
list := make([]UserAuthMethodType, 0, len(a.MFAsVerified)+1)
|
|
if a.PasswordVerified {
|
|
list = append(list, UserAuthMethodTypePassword)
|
|
}
|
|
for _, mfa := range a.MFAsVerified {
|
|
list = append(list, mfa.UserAuthMethodType())
|
|
}
|
|
return list
|
|
}
|