mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 19:07:30 +00:00
feat: User login commands (#1228)
* feat: change login to command side * feat: change login to command side * fix: fix push on user * feat: user command side * feat: sign out * feat: command side login * feat: command side login * feat: fix register user * feat: fix register user * feat: fix web auth n events * feat: add machine keys * feat: send codes * feat: move authrequest to domain * feat: move authrequest to domain * feat: webauthn working * feat: external users * feat: external users login * feat: notify users * fix: tests * feat: cascade remove user grants on project remove * fix: webauthn * fix: pr requests * fix: register human with member * fix: fix bugs * fix: fix bugs
This commit is contained in:
150
internal/v2/domain/auth_request.go
Normal file
150
internal/v2/domain/auth_request.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"golang.org/x/text/language"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
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 uint32
|
||||
Request Request
|
||||
|
||||
levelOfAssurance LevelOfAssurance
|
||||
UserID string
|
||||
LoginName string
|
||||
DisplayName string
|
||||
UserOrgID string
|
||||
RequestedOrgID string
|
||||
RequestedOrgName string
|
||||
SelectedIDPConfigID string
|
||||
LinkingUsers []*ExternalUser
|
||||
PossibleSteps []NextStep
|
||||
PasswordVerified bool
|
||||
MFAsVerified []MFAType
|
||||
Audience []string
|
||||
AuthTime time.Time
|
||||
Code string
|
||||
LoginPolicy *LoginPolicy
|
||||
AllowedExternalIDPs []*IDPProvider
|
||||
}
|
||||
|
||||
type ExternalUser struct {
|
||||
IDPConfigID string
|
||||
ExternalUserID string
|
||||
DisplayName string
|
||||
PreferredUsername string
|
||||
FirstName string
|
||||
LastName string
|
||||
NickName string
|
||||
Email string
|
||||
IsEmailVerified bool
|
||||
PreferredLanguage language.Tag
|
||||
Phone string
|
||||
IsPhoneVerified bool
|
||||
}
|
||||
|
||||
type Prompt int32
|
||||
|
||||
const (
|
||||
PromptUnspecified Prompt = iota
|
||||
PromptNone
|
||||
PromptLogin
|
||||
PromptConsent
|
||||
PromptSelectAccount
|
||||
)
|
||||
|
||||
type LevelOfAssurance int
|
||||
|
||||
const (
|
||||
LevelOfAssuranceNone LevelOfAssurance = iota
|
||||
)
|
||||
|
||||
type MFAType int
|
||||
|
||||
const (
|
||||
MFATypeOTP MFAType = iota
|
||||
MFATypeU2F
|
||||
MFATypeU2FUserVerification
|
||||
)
|
||||
|
||||
type MFALevel int
|
||||
|
||||
const (
|
||||
MFALevelNotSetUp MFALevel = iota
|
||||
MFALevelSecondFactor
|
||||
MFALevelMultiFactor
|
||||
MFALevelMultiFactorCertified
|
||||
)
|
||||
|
||||
func NewAuthRequestFromType(requestType AuthRequestType) (*AuthRequest, error) {
|
||||
request, ok := authRequestTypeMapping[requestType]
|
||||
if !ok {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "DOMAIN-ds2kl", "invalid request type")
|
||||
}
|
||||
return &AuthRequest{Request: request}, nil
|
||||
}
|
||||
|
||||
func (a *AuthRequest) WithCurrentInfo(info *BrowserInfo) *AuthRequest {
|
||||
a.BrowserInfo = info
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *AuthRequest) SetUserInfo(userID, loginName, displayName, userOrgID string) {
|
||||
a.UserID = userID
|
||||
a.LoginName = loginName
|
||||
a.DisplayName = displayName
|
||||
a.UserOrgID = userOrgID
|
||||
}
|
||||
|
||||
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) GetScopeProjectIDsForAud() []string {
|
||||
projectIDs := make([]string, 0)
|
||||
switch request := a.Request.(type) {
|
||||
case *AuthRequestOIDC:
|
||||
for _, scope := range request.Scopes {
|
||||
if strings.HasPrefix(scope, ProjectIDScope) && strings.HasSuffix(scope, AudSuffix) {
|
||||
projectIDs = append(projectIDs, strings.TrimSuffix(strings.TrimPrefix(scope, ProjectIDScope), AudSuffix))
|
||||
}
|
||||
}
|
||||
}
|
||||
return projectIDs
|
||||
}
|
||||
|
||||
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 ""
|
||||
}
|
22
internal/v2/domain/browser_info.go
Normal file
22
internal/v2/domain/browser_info.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"net"
|
||||
net_http "net/http"
|
||||
|
||||
http_util "github.com/caos/zitadel/internal/api/http"
|
||||
)
|
||||
|
||||
type BrowserInfo struct {
|
||||
UserAgent string
|
||||
AcceptLanguage string
|
||||
RemoteIP net.IP
|
||||
}
|
||||
|
||||
func BrowserInfoFromRequest(r *net_http.Request) *BrowserInfo {
|
||||
return &BrowserInfo{
|
||||
UserAgent: r.Header.Get(http_util.UserAgentHeader),
|
||||
AcceptLanguage: r.Header.Get(http_util.AcceptLanguage),
|
||||
RemoteIP: http_util.RemoteIPFromRequest(r),
|
||||
}
|
||||
}
|
@@ -60,15 +60,15 @@ func (u *Human) IsValid() bool {
|
||||
return u.Profile != nil && u.FirstName != "" && u.LastName != "" && u.Email != nil && u.Email.IsValid() && u.Phone == nil || (u.Phone != nil && u.Phone.PhoneNumber != "" && u.Phone.IsValid())
|
||||
}
|
||||
|
||||
func (u *Human) CheckOrgIAMPolicy(userName string, policy *OrgIAMPolicy) error {
|
||||
func (u *Human) CheckOrgIAMPolicy(policy *OrgIAMPolicy) error {
|
||||
if policy == nil {
|
||||
return caos_errors.ThrowPreconditionFailed(nil, "DOMAIN-zSH7j", "Errors.Users.OrgIamPolicyNil")
|
||||
}
|
||||
if policy.UserLoginMustBeDomain && strings.Contains(userName, "@") {
|
||||
if policy.UserLoginMustBeDomain && strings.Contains(u.Username, "@") {
|
||||
return caos_errors.ThrowPreconditionFailed(nil, "DOMAIN-se4sJ", "Errors.User.EmailAsUsernameNotAllowed")
|
||||
}
|
||||
if !policy.UserLoginMustBeDomain && u.Profile != nil && userName == "" && u.Email != nil {
|
||||
userName = u.EmailAddress
|
||||
if !policy.UserLoginMustBeDomain && u.Profile != nil && u.Username == "" && u.Email != nil {
|
||||
u.Username = u.EmailAddress
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -1,6 +1,9 @@
|
||||
package domain
|
||||
|
||||
import es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
import (
|
||||
"bytes"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
)
|
||||
|
||||
type WebAuthNToken struct {
|
||||
es_models.ObjectRoot
|
||||
@@ -55,3 +58,12 @@ func GetTokenToVerify(tokens []*WebAuthNToken) (int, *WebAuthNToken) {
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
func GetTokenByKeyID(tokens []*WebAuthNToken, keyID []byte) (int, *WebAuthNToken) {
|
||||
for i, token := range tokens {
|
||||
if bytes.Compare(token.KeyID, keyID) == 0 {
|
||||
return i, token
|
||||
}
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
@@ -89,3 +89,12 @@ const (
|
||||
func (f IDPConfigStylingType) Valid() bool {
|
||||
return f >= 0 && f < idpConfigStylingTypeCount
|
||||
}
|
||||
|
||||
func (st IDPConfigStylingType) GetCSSClass() string {
|
||||
switch st {
|
||||
case IDPConfigStylingTypeGoogle:
|
||||
return "google"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,21 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MachineKey struct {
|
||||
models.ObjectRoot
|
||||
|
||||
KeyID string
|
||||
Type MachineKeyType
|
||||
ExpirationDate time.Time
|
||||
PrivateKey []byte
|
||||
PublicKey []byte
|
||||
}
|
||||
|
||||
type MachineKeyType int32
|
||||
|
||||
const (
|
||||
@@ -9,6 +25,33 @@ const (
|
||||
keyCount
|
||||
)
|
||||
|
||||
type MachineKeyState int32
|
||||
|
||||
const (
|
||||
MachineKeyStateUnspecified MachineKeyState = iota
|
||||
MachineKeyStateActive
|
||||
MachineKeyStateRemoved
|
||||
|
||||
machineKeyStateCount
|
||||
)
|
||||
|
||||
func (f MachineKeyState) Valid() bool {
|
||||
return f >= 0 && f < machineKeyStateCount
|
||||
}
|
||||
|
||||
func (f MachineKeyType) Valid() bool {
|
||||
return f >= 0 && f < keyCount
|
||||
}
|
||||
|
||||
func (key *MachineKey) GenerateNewMachineKeyPair(keySize int) error {
|
||||
privateKey, publicKey, err := crypto.GenerateKeyPair(keySize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.PublicKey, err = crypto.PublicKeyToBytes(publicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.PrivateKey = crypto.PrivateKeyToBytes(privateKey)
|
||||
return nil
|
||||
}
|
||||
|
149
internal/v2/domain/next_step.go
Normal file
149
internal/v2/domain/next_step.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package domain
|
||||
|
||||
type NextStep interface {
|
||||
Type() NextStepType
|
||||
}
|
||||
|
||||
type NextStepType int32
|
||||
|
||||
const (
|
||||
NextStepUnspecified NextStepType = iota
|
||||
NextStepLogin
|
||||
NextStepUserSelection
|
||||
NextStepInitUser
|
||||
NextStepPassword
|
||||
NextStepChangePassword
|
||||
NextStepInitPassword
|
||||
NextStepVerifyEmail
|
||||
NextStepMFAPrompt
|
||||
NextStepMFAVerify
|
||||
NextStepRedirectToCallback
|
||||
NextStepChangeUsername
|
||||
NextStepLinkUsers
|
||||
NextStepExternalNotFoundOption
|
||||
NextStepExternalLogin
|
||||
NextStepGrantRequired
|
||||
NextStepPasswordless
|
||||
)
|
||||
|
||||
type LoginStep struct{}
|
||||
|
||||
func (s *LoginStep) Type() NextStepType {
|
||||
return NextStepLogin
|
||||
}
|
||||
|
||||
type SelectUserStep struct {
|
||||
Users []UserSelection
|
||||
}
|
||||
|
||||
func (s *SelectUserStep) Type() NextStepType {
|
||||
return NextStepUserSelection
|
||||
}
|
||||
|
||||
type UserSelection struct {
|
||||
UserID string
|
||||
DisplayName string
|
||||
LoginName string
|
||||
UserSessionState UserSessionState
|
||||
SelectionPossible bool
|
||||
}
|
||||
|
||||
type UserSessionState int32
|
||||
|
||||
const (
|
||||
UserSessionStateActive UserSessionState = iota
|
||||
UserSessionStateTerminated
|
||||
)
|
||||
|
||||
type InitUserStep struct {
|
||||
PasswordSet bool
|
||||
}
|
||||
|
||||
func (s *InitUserStep) Type() NextStepType {
|
||||
return NextStepInitUser
|
||||
}
|
||||
|
||||
type ExternalNotFoundOptionStep struct{}
|
||||
|
||||
func (s *ExternalNotFoundOptionStep) Type() NextStepType {
|
||||
return NextStepExternalNotFoundOption
|
||||
}
|
||||
|
||||
type PasswordStep struct{}
|
||||
|
||||
func (s *PasswordStep) Type() NextStepType {
|
||||
return NextStepPassword
|
||||
}
|
||||
|
||||
type ExternalLoginStep struct {
|
||||
SelectedIDPConfigID string
|
||||
}
|
||||
|
||||
func (s *ExternalLoginStep) Type() NextStepType {
|
||||
return NextStepExternalLogin
|
||||
}
|
||||
|
||||
type PasswordlessStep struct{}
|
||||
|
||||
func (s *PasswordlessStep) Type() NextStepType {
|
||||
return NextStepPasswordless
|
||||
}
|
||||
|
||||
type ChangePasswordStep struct{}
|
||||
|
||||
func (s *ChangePasswordStep) Type() NextStepType {
|
||||
return NextStepChangePassword
|
||||
}
|
||||
|
||||
type InitPasswordStep struct{}
|
||||
|
||||
func (s *InitPasswordStep) Type() NextStepType {
|
||||
return NextStepInitPassword
|
||||
}
|
||||
|
||||
type ChangeUsernameStep struct{}
|
||||
|
||||
func (s *ChangeUsernameStep) Type() NextStepType {
|
||||
return NextStepChangeUsername
|
||||
}
|
||||
|
||||
type VerifyEMailStep struct{}
|
||||
|
||||
func (s *VerifyEMailStep) Type() NextStepType {
|
||||
return NextStepVerifyEmail
|
||||
}
|
||||
|
||||
type MFAPromptStep struct {
|
||||
Required bool
|
||||
MFAProviders []MFAType
|
||||
}
|
||||
|
||||
func (s *MFAPromptStep) Type() NextStepType {
|
||||
return NextStepMFAPrompt
|
||||
}
|
||||
|
||||
type MFAVerificationStep struct {
|
||||
MFAProviders []MFAType
|
||||
}
|
||||
|
||||
func (s *MFAVerificationStep) Type() NextStepType {
|
||||
return NextStepMFAVerify
|
||||
}
|
||||
|
||||
type LinkUsersStep struct{}
|
||||
|
||||
func (s *LinkUsersStep) Type() NextStepType {
|
||||
return NextStepLinkUsers
|
||||
}
|
||||
|
||||
type GrantRequiredStep struct{}
|
||||
|
||||
func (s *GrantRequiredStep) Type() NextStepType {
|
||||
return NextStepGrantRequired
|
||||
}
|
||||
|
||||
type RedirectToCallbackStep struct{}
|
||||
|
||||
func (s *RedirectToCallbackStep) Type() NextStepType {
|
||||
return NextStepRedirectToCallback
|
||||
}
|
17
internal/v2/domain/oidc_code_challenge.go
Normal file
17
internal/v2/domain/oidc_code_challenge.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package domain
|
||||
|
||||
type OIDCCodeChallenge struct {
|
||||
Challenge string
|
||||
Method OIDCCodeChallengeMethod
|
||||
}
|
||||
|
||||
func (c *OIDCCodeChallenge) IsValid() bool {
|
||||
return c.Challenge != ""
|
||||
}
|
||||
|
||||
type OIDCCodeChallengeMethod int32
|
||||
|
||||
const (
|
||||
CodeChallengeMethodPlain OIDCCodeChallengeMethod = iota
|
||||
CodeChallengeMethodS256
|
||||
)
|
@@ -8,7 +8,7 @@ type LoginPolicy struct {
|
||||
Default bool
|
||||
AllowUsernamePassword bool
|
||||
AllowRegister bool
|
||||
AllowExternalIdp bool
|
||||
AllowExternalIDP bool
|
||||
IDPProviders []*IDPProvider
|
||||
ForceMFA bool
|
||||
SecondFactors []SecondFactorType
|
||||
@@ -20,6 +20,11 @@ type IDPProvider struct {
|
||||
models.ObjectRoot
|
||||
Type IdentityProviderType
|
||||
IDPConfigID string
|
||||
|
||||
Name string
|
||||
StylingType IDPConfigStylingType
|
||||
IDPConfigType IDPConfigType
|
||||
IDPState IDPConfigState
|
||||
}
|
||||
|
||||
type PasswordlessType int32
|
||||
@@ -34,3 +39,11 @@ const (
|
||||
func (f PasswordlessType) Valid() bool {
|
||||
return f >= 0 && f < passwordlessCount
|
||||
}
|
||||
|
||||
func (p *LoginPolicy) HasSecondFactors() bool {
|
||||
return len(p.SecondFactors) > 0
|
||||
}
|
||||
|
||||
func (p *LoginPolicy) HasMultiFactors() bool {
|
||||
return len(p.MultiFactors) > 0
|
||||
}
|
||||
|
54
internal/v2/domain/request.go
Normal file
54
internal/v2/domain/request.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package domain
|
||||
|
||||
const (
|
||||
OrgDomainPrimaryScope = "urn:zitadel:iam:org:domain:primary:"
|
||||
OrgDomainPrimaryClaim = "urn:zitadel:iam:org:domain:primary"
|
||||
ProjectIDScope = "urn:zitadel:iam:org:project:id:"
|
||||
AudSuffix = ":aud"
|
||||
)
|
||||
|
||||
//TODO: Change AuthRequest to interface and let oidcauthreqesut implement it
|
||||
type Request interface {
|
||||
Type() AuthRequestType
|
||||
IsValid() bool
|
||||
}
|
||||
|
||||
type AuthRequestType int32
|
||||
|
||||
var (
|
||||
authRequestTypeMapping = map[AuthRequestType]Request{
|
||||
AuthRequestTypeOIDC: &AuthRequestOIDC{},
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
AuthRequestTypeOIDC AuthRequestType = iota
|
||||
AuthRequestTypeSAML
|
||||
)
|
||||
|
||||
type AuthRequestOIDC struct {
|
||||
Scopes []string
|
||||
ResponseType OIDCResponseType
|
||||
Nonce string
|
||||
CodeChallenge *OIDCCodeChallenge
|
||||
}
|
||||
|
||||
func (a *AuthRequestOIDC) Type() AuthRequestType {
|
||||
return AuthRequestTypeOIDC
|
||||
}
|
||||
|
||||
func (a *AuthRequestOIDC) IsValid() bool {
|
||||
return len(a.Scopes) > 0 &&
|
||||
a.CodeChallenge == nil || a.CodeChallenge != nil && a.CodeChallenge.IsValid()
|
||||
}
|
||||
|
||||
type AuthRequestSAML struct {
|
||||
}
|
||||
|
||||
func (a *AuthRequestSAML) Type() AuthRequestType {
|
||||
return AuthRequestTypeSAML
|
||||
}
|
||||
|
||||
func (a *AuthRequestSAML) IsValid() bool {
|
||||
return true
|
||||
}
|
@@ -2,6 +2,7 @@ package domain
|
||||
|
||||
const (
|
||||
RoleOrgOwner = "ORG_OWNER"
|
||||
RoleOrgProjectCreator = "ORG_PROJECT_CREATOR"
|
||||
RoleIAMOwner = "IAM_OWNER"
|
||||
RoleProjectOwner = "PROJECT_OWNER"
|
||||
RoleProjectOwnerGlobal = "PROJECT_OWNER_GLOBAL"
|
||||
|
18
internal/v2/domain/token.go
Normal file
18
internal/v2/domain/token.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
TokenID string
|
||||
ApplicationID string
|
||||
UserAgentID string
|
||||
Audience []string
|
||||
Expiration time.Time
|
||||
Scopes []string
|
||||
PreferredLanguage string
|
||||
}
|
Reference in New Issue
Block a user