chore: move the go code into a subfolder

This commit is contained in:
Florian Forster
2025-08-05 15:20:32 -07:00
parent 4ad22ba456
commit cd2921de26
2978 changed files with 373 additions and 300 deletions

View File

@@ -0,0 +1,96 @@
package domain
import (
"slices"
"time"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type Action struct {
models.ObjectRoot
Name string
Script string
Timeout time.Duration
AllowedToFail bool
State ActionState
}
func (a *Action) IsValid() bool {
return a.Name != ""
}
type ActionState int32
const (
ActionStateUnspecified ActionState = iota
ActionStateActive
ActionStateInactive
ActionStateRemoved
actionStateCount
)
func (s ActionState) Valid() bool {
return s >= 0 && s < actionStateCount
}
func (s ActionState) Exists() bool {
return s != ActionStateUnspecified && s != ActionStateRemoved
}
type ActionsAllowed int32
const (
ActionsNotAllowed ActionsAllowed = iota
ActionsMaxAllowed
ActionsAllowedUnlimited
)
type ActionFunction int32
const (
ActionFunctionUnspecified ActionFunction = iota
ActionFunctionPreUserinfo
ActionFunctionPreAccessToken
ActionFunctionPreSAMLResponse
actionFunctionCount
)
func (s ActionFunction) Valid() bool {
return s >= 0 && s < actionFunctionCount
}
func (s ActionFunction) LocalizationKey() string {
if !s.Valid() {
return ActionFunctionUnspecified.LocalizationKey()
}
switch s {
case ActionFunctionPreUserinfo:
return "preuserinfo"
case ActionFunctionPreAccessToken:
return "preaccesstoken"
case ActionFunctionPreSAMLResponse:
return "presamlresponse"
case ActionFunctionUnspecified, actionFunctionCount:
fallthrough
default:
return "unspecified"
}
}
func AllActionFunctions() []string {
return []string{
ActionFunctionPreUserinfo.LocalizationKey(),
ActionFunctionPreAccessToken.LocalizationKey(),
ActionFunctionPreSAMLResponse.LocalizationKey(),
}
}
func ActionFunctionExists() func(string) bool {
functions := AllActionFunctions()
return func(s string) bool {
return slices.Contains(functions, s)
}
}

View File

@@ -0,0 +1,49 @@
package domain
type Application interface {
GetAppID() string
GetApplicationName() string
GetState() AppState
//GetSequence() uint64
//GetChangeDate() time.Time
//GetResourceOwner() string
}
type AppState int32
const (
AppStateUnspecified AppState = iota
AppStateActive
AppStateInactive
AppStateRemoved
)
func (a AppState) Exists() bool {
return !(a == AppStateUnspecified || a == AppStateRemoved)
}
type ChangeApp struct {
AppID string
AppName string
State AppState
}
func (a *ChangeApp) GetAppID() string {
return a.AppID
}
func (a *ChangeApp) GetApplicationName() string {
return a.AppName
}
func (a *ChangeApp) GetState() AppState {
return a.State
}
type LoginVersion int32
const (
LoginVersionUnspecified LoginVersion = iota
LoginVersion1
LoginVersion2
)

View File

@@ -0,0 +1,61 @@
package domain
import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type APIApp struct {
models.ObjectRoot
AppID string
AppName string
ClientID string
EncodedHash string
ClientSecretString string
AuthMethodType APIAuthMethodType
State AppState
}
func (a *APIApp) GetApplicationName() string {
return a.AppName
}
func (a *APIApp) GetState() AppState {
return a.State
}
type APIAuthMethodType int32
const (
APIAuthMethodTypeBasic APIAuthMethodType = iota
APIAuthMethodTypePrivateKeyJWT
)
func (a *APIApp) IsValid() bool {
return a.AppName != ""
}
func (a *APIApp) setClientID(clientID string) {
a.ClientID = clientID
}
func (a *APIApp) setClientSecret(encodedHash string) {
a.EncodedHash = encodedHash
}
func (a *APIApp) requiresClientSecret() bool {
return a.AuthMethodType == APIAuthMethodTypeBasic
}
func (a *APIApp) GenerateClientSecretIfNeeded(generator *crypto.HashGenerator) (plain string, err error) {
if a.AuthMethodType == APIAuthMethodTypePrivateKeyJWT {
return "", nil
}
a.EncodedHash, plain, err = generator.NewCode()
if err != nil {
return "", err
}
return plain, nil
}

View File

@@ -0,0 +1,60 @@
package domain
import (
"encoding/json"
"time"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/zerrors"
)
type ApplicationKey struct {
models.ObjectRoot
ApplicationID string
ClientID string
KeyID string
Type AuthNKeyType
ExpirationDate time.Time
PrivateKey []byte
PublicKey []byte
}
func (k *ApplicationKey) SetPublicKey(publicKey []byte) {
k.PublicKey = publicKey
}
func (k *ApplicationKey) SetPrivateKey(privateKey []byte) {
k.PrivateKey = privateKey
}
func (k *ApplicationKey) GetExpirationDate() time.Time {
return k.ExpirationDate
}
func (k *ApplicationKey) SetExpirationDate(expiration time.Time) {
k.ExpirationDate = expiration
}
func (k *ApplicationKey) Detail() ([]byte, error) {
if k.Type == AuthNKeyTypeJSON {
return k.MarshalJSON()
}
return nil, zerrors.ThrowPreconditionFailed(nil, "KEY-dsg52", "Errors.Internal")
}
func (k *ApplicationKey) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Type string `json:"type"`
KeyID string `json:"keyId"`
Key string `json:"key"`
AppID string `json:"appId"`
ClientID string `json:"clientId"`
}{
Type: "application",
KeyID: k.KeyID,
Key: string(k.PrivateKey),
AppID: k.ApplicationID,
ClientID: k.ClientID,
})
}

View File

@@ -0,0 +1,34 @@
package domain
import (
"github.com/zitadel/zitadel/internal/id"
)
type oAuthApplication interface {
setClientID(clientID string)
setClientSecret(encodedHash string)
requiresClientSecret() bool
}
// ClientID random_number (eg. 495894098234)
func SetNewClientID(a oAuthApplication, idGenerator id.Generator) error {
clientID, err := idGenerator.Next()
if err != nil {
return err
}
a.setClientID(clientID)
return nil
}
func SetNewClientSecretIfNeeded(a oAuthApplication, generate func() (encodedHash, plain string, err error)) (string, error) {
if !a.requiresClientSecret() {
return "", nil
}
encodedHash, plain, err := generate()
if err != nil {
return "", err
}
a.setClientSecret(encodedHash)
return plain, nil
}

View File

@@ -0,0 +1,424 @@
package domain
import (
"slices"
"strings"
"time"
http_util "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
const (
http = "http://"
httpLocalhostWithPort = "http://localhost:"
httpLocalhostWithoutPort = "http://localhost/"
httpLoopbackV4WithPort = "http://127.0.0.1:"
httpLoopbackV4WithoutPort = "http://127.0.0.1/"
httpLoopbackV6WithPort = "http://[::1]:"
httpLoopbackV6WithoutPort = "http://[::1]/"
httpLoopbackV6LongWithPort = "http://[0:0:0:0:0:0:0:1]:"
httpLoopbackV6LongWithoutPort = "http://[0:0:0:0:0:0:0:1]/"
https = "https://"
)
type OIDCApp struct {
models.ObjectRoot
AppID string
AppName string
ClientID string
EncodedHash string
ClientSecretString string
RedirectUris []string
ResponseTypes []OIDCResponseType
GrantTypes []OIDCGrantType
ApplicationType *OIDCApplicationType
AuthMethodType *OIDCAuthMethodType
PostLogoutRedirectUris []string
OIDCVersion *OIDCVersion
Compliance *Compliance
DevMode *bool
AccessTokenType *OIDCTokenType
AccessTokenRoleAssertion *bool
IDTokenRoleAssertion *bool
IDTokenUserinfoAssertion *bool
ClockSkew *time.Duration
AdditionalOrigins []string
SkipNativeAppSuccessPage *bool
BackChannelLogoutURI *string
LoginVersion *LoginVersion
LoginBaseURI *string
State AppState
}
func (a *OIDCApp) GetApplicationName() string {
return a.AppName
}
func (a *OIDCApp) GetState() AppState {
return a.State
}
func (a *OIDCApp) setClientID(clientID string) {
a.ClientID = clientID
}
func (a *OIDCApp) setClientSecret(encodedHash string) {
a.EncodedHash = encodedHash
}
func (a *OIDCApp) requiresClientSecret() bool {
return a.AuthMethodType != nil && (*a.AuthMethodType == OIDCAuthMethodTypeBasic || *a.AuthMethodType == OIDCAuthMethodTypePost)
}
type OIDCVersion int32
const (
OIDCVersionV1 OIDCVersion = iota
)
type OIDCResponseType int32
const (
OIDCResponseTypeUnspecified OIDCResponseType = iota - 1 // Negative offset not to break existing configs.
OIDCResponseTypeCode
OIDCResponseTypeIDToken
OIDCResponseTypeIDTokenToken
)
//go:generate enumer -type OIDCResponseMode -transform snake -trimprefix OIDCResponseMode
type OIDCResponseMode int
const (
OIDCResponseModeUnspecified OIDCResponseMode = iota
OIDCResponseModeQuery
OIDCResponseModeFragment
OIDCResponseModeFormPost
)
type OIDCGrantType int32
const (
OIDCGrantTypeAuthorizationCode OIDCGrantType = iota
OIDCGrantTypeImplicit
OIDCGrantTypeRefreshToken
OIDCGrantTypeDeviceCode
OIDCGrantTypeTokenExchange
)
type OIDCApplicationType int32
const (
OIDCApplicationTypeWeb OIDCApplicationType = iota
OIDCApplicationTypeUserAgent
OIDCApplicationTypeNative
)
type OIDCAuthMethodType int32
const (
OIDCAuthMethodTypeBasic OIDCAuthMethodType = iota
OIDCAuthMethodTypePost
OIDCAuthMethodTypeNone
OIDCAuthMethodTypePrivateKeyJWT
)
type Compliance struct {
NoneCompliant bool
Problems []string
}
type OIDCTokenType int32
const (
OIDCTokenTypeBearer OIDCTokenType = iota
OIDCTokenTypeJWT
)
func (a *OIDCApp) IsValid() bool {
if (a.ClockSkew != nil && (*a.ClockSkew > time.Second*5 || *a.ClockSkew < time.Second*0)) || !a.OriginsValid() {
return false
}
grantTypes := a.getRequiredGrantTypes()
if len(grantTypes) == 0 {
return false
}
for _, grantType := range grantTypes {
ok := containsOIDCGrantType(a.GrantTypes, grantType)
if !ok {
return false
}
}
return true
}
func (a *OIDCApp) OriginsValid() bool {
for _, origin := range a.AdditionalOrigins {
if !http_util.IsOrigin(strings.TrimSpace(origin)) {
return false
}
}
return true
}
func ContainsRequiredGrantTypes(responseTypes []OIDCResponseType, grantTypes []OIDCGrantType) bool {
required := RequiredOIDCGrantTypes(responseTypes, grantTypes)
return ContainsOIDCGrantTypes(required, grantTypes)
}
func RequiredOIDCGrantTypes(responseTypes []OIDCResponseType, grantTypesSet []OIDCGrantType) (grantTypes []OIDCGrantType) {
var implicit bool
for _, r := range responseTypes {
switch r {
case OIDCResponseTypeCode:
// #5684 when "Device Code" is selected, "Authorization Code" is no longer a hard requirement
if !containsOIDCGrantType(grantTypesSet, OIDCGrantTypeDeviceCode) {
grantTypes = append(grantTypes, OIDCGrantTypeAuthorizationCode)
} else {
grantTypes = append(grantTypes, OIDCGrantTypeDeviceCode)
}
case OIDCResponseTypeIDToken, OIDCResponseTypeIDTokenToken:
if !implicit {
implicit = true
grantTypes = append(grantTypes, OIDCGrantTypeImplicit)
}
}
}
return grantTypes
}
func (a *OIDCApp) getRequiredGrantTypes() []OIDCGrantType {
return RequiredOIDCGrantTypes(a.ResponseTypes, a.GrantTypes)
}
func ContainsOIDCGrantTypes(shouldContain, list []OIDCGrantType) bool {
for _, should := range shouldContain {
if !containsOIDCGrantType(list, should) {
return false
}
}
return true
}
func containsOIDCGrantType(grantTypes []OIDCGrantType, grantType OIDCGrantType) bool {
return slices.Contains(grantTypes, grantType)
}
func (a *OIDCApp) FillCompliance() {
a.Compliance = GetOIDCCompliance(a.OIDCVersion, a.ApplicationType, a.GrantTypes, a.ResponseTypes, a.AuthMethodType, a.RedirectUris)
}
func GetOIDCCompliance(version *OIDCVersion, appType *OIDCApplicationType, grantTypes []OIDCGrantType, responseTypes []OIDCResponseType, authMethod *OIDCAuthMethodType, redirectUris []string) *Compliance {
if version != nil && *version == OIDCVersionV1 {
return GetOIDCV1Compliance(appType, grantTypes, authMethod, redirectUris)
}
return &Compliance{
NoneCompliant: true,
Problems: []string{"Application.OIDC.UnsupportedVersion"},
}
}
func GetOIDCV1Compliance(appType *OIDCApplicationType, grantTypes []OIDCGrantType, authMethod *OIDCAuthMethodType, redirectUris []string) *Compliance {
compliance := &Compliance{NoneCompliant: false}
checkGrantTypesCombination(compliance, grantTypes)
checkRedirectURIs(compliance, grantTypes, appType, redirectUris)
checkApplicationType(compliance, appType, authMethod)
if compliance.NoneCompliant {
compliance.Problems = append([]string{"Application.OIDC.V1.NotCompliant"}, compliance.Problems...)
}
return compliance
}
func checkGrantTypesCombination(compliance *Compliance, grantTypes []OIDCGrantType) {
if !containsOIDCGrantType(grantTypes, OIDCGrantTypeDeviceCode) && containsOIDCGrantType(grantTypes, OIDCGrantTypeRefreshToken) && !containsOIDCGrantType(grantTypes, OIDCGrantTypeAuthorizationCode) {
compliance.NoneCompliant = true
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.GrantType.Refresh.NoAuthCode")
}
}
func checkRedirectURIs(compliance *Compliance, grantTypes []OIDCGrantType, appType *OIDCApplicationType, redirectUris []string) {
// See #5684 for OIDCGrantTypeDeviceCode and redirectUris further explanation
if len(redirectUris) == 0 && (!containsOIDCGrantType(grantTypes, OIDCGrantTypeDeviceCode) || (containsOIDCGrantType(grantTypes, OIDCGrantTypeDeviceCode) && containsOIDCGrantType(grantTypes, OIDCGrantTypeAuthorizationCode))) {
compliance.NoneCompliant = true
compliance.Problems = append([]string{"Application.OIDC.V1.NoRedirectUris"}, compliance.Problems...)
}
if containsOIDCGrantType(grantTypes, OIDCGrantTypeImplicit) && containsOIDCGrantType(grantTypes, OIDCGrantTypeAuthorizationCode) {
CheckRedirectUrisImplicitAndCode(compliance, appType, redirectUris)
} else {
if containsOIDCGrantType(grantTypes, OIDCGrantTypeImplicit) {
CheckRedirectUrisImplicit(compliance, appType, redirectUris)
}
if containsOIDCGrantType(grantTypes, OIDCGrantTypeAuthorizationCode) {
CheckRedirectUrisCode(compliance, appType, redirectUris)
}
}
}
func checkApplicationType(compliance *Compliance, appType *OIDCApplicationType, authMethod *OIDCAuthMethodType) {
if appType != nil {
switch *appType {
case OIDCApplicationTypeNative:
GetOIDCV1NativeApplicationCompliance(compliance, authMethod)
case OIDCApplicationTypeUserAgent:
GetOIDCV1UserAgentApplicationCompliance(compliance, authMethod)
case OIDCApplicationTypeWeb:
return
}
}
if compliance.NoneCompliant {
compliance.Problems = append([]string{"Application.OIDC.V1.NotCompliant"}, compliance.Problems...)
}
}
func GetOIDCV1NativeApplicationCompliance(compliance *Compliance, authMethod *OIDCAuthMethodType) {
if authMethod != nil && *authMethod != OIDCAuthMethodTypeNone {
compliance.NoneCompliant = true
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Native.AuthMethodType.NotNone")
}
}
func GetOIDCV1UserAgentApplicationCompliance(compliance *Compliance, authMethod *OIDCAuthMethodType) {
if authMethod != nil && *authMethod != OIDCAuthMethodTypeNone {
compliance.NoneCompliant = true
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.UserAgent.AuthMethodType.NotNone")
}
}
func CheckRedirectUrisCode(compliance *Compliance, appType *OIDCApplicationType, redirectUris []string) {
if urlsAreHttps(redirectUris) {
return
}
if urlContainsPrefix(redirectUris, http) {
if appType != nil && *appType == OIDCApplicationTypeUserAgent {
compliance.NoneCompliant = true
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Code.RedirectUris.HttpOnlyForWeb")
}
if appType != nil && *appType == OIDCApplicationTypeNative && !onlyLocalhostIsHttp(redirectUris) {
compliance.NoneCompliant = true
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Native.RedirectUris.MustBeHttpLocalhost")
}
}
if containsCustom(redirectUris) && appType != nil && *appType != OIDCApplicationTypeNative {
compliance.NoneCompliant = true
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Code.RedirectUris.CustomOnlyForNative")
}
}
func CheckRedirectUrisImplicit(compliance *Compliance, appType *OIDCApplicationType, redirectUris []string) {
if urlsAreHttps(redirectUris) {
return
}
if containsCustom(redirectUris) {
compliance.NoneCompliant = true
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Implicit.RedirectUris.CustomNotAllowed")
}
if urlContainsPrefix(redirectUris, http) {
if appType != nil && *appType == OIDCApplicationTypeNative {
if !onlyLocalhostIsHttp(redirectUris) {
compliance.NoneCompliant = true
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Native.RedirectUris.MustBeHttpLocalhost")
}
return
}
compliance.NoneCompliant = true
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Implicit.RedirectUris.HttpNotAllowed")
}
}
func CheckRedirectUrisImplicitAndCode(compliance *Compliance, appType *OIDCApplicationType, redirectUris []string) {
if urlsAreHttps(redirectUris) {
return
}
if containsCustom(redirectUris) && appType != nil && *appType != OIDCApplicationTypeNative {
compliance.NoneCompliant = true
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Implicit.RedirectUris.CustomNotAllowed")
}
if urlContainsPrefix(redirectUris, http) {
if appType != nil && *appType == OIDCApplicationTypeUserAgent {
compliance.NoneCompliant = true
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Code.RedirectUris.HttpOnlyForWeb")
}
if !onlyLocalhostIsHttp(redirectUris) && appType != nil && *appType == OIDCApplicationTypeNative {
compliance.NoneCompliant = true
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Native.RedirectUris.MustBeHttpLocalhost")
}
}
if !compliance.NoneCompliant {
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.NotAllCombinationsAreAllowed")
}
}
func urlsAreHttps(uris []string) bool {
for _, uri := range uris {
if !strings.HasPrefix(uri, https) {
return false
}
}
return true
}
func urlContainsPrefix(uris []string, prefix string) bool {
for _, uri := range uris {
if strings.HasPrefix(uri, prefix) {
return true
}
}
return false
}
func containsCustom(uris []string) bool {
for _, uri := range uris {
if !strings.HasPrefix(uri, http) && !strings.HasPrefix(uri, https) {
return true
}
}
return false
}
func onlyLocalhostIsHttp(uris []string) bool {
for _, uri := range uris {
if strings.HasPrefix(uri, http) && !isHTTPLoopbackLocalhost(uri) {
return false
}
}
return true
}
func isHTTPLoopbackLocalhost(uri string) bool {
return strings.HasPrefix(uri, httpLocalhostWithoutPort) ||
strings.HasPrefix(uri, httpLocalhostWithPort) ||
strings.HasPrefix(uri, httpLoopbackV4WithoutPort) ||
strings.HasPrefix(uri, httpLoopbackV4WithPort) ||
strings.HasPrefix(uri, httpLoopbackV6WithoutPort) ||
strings.HasPrefix(uri, httpLoopbackV6WithPort) ||
strings.HasPrefix(uri, httpLoopbackV6LongWithoutPort) ||
strings.HasPrefix(uri, httpLoopbackV6LongWithPort)
}
func OIDCOriginAllowList(redirectURIs, additionalOrigins []string) ([]string, error) {
allowList := make([]string, 0)
for _, redirect := range redirectURIs {
origin, err := http_util.GetOriginFromURLString(redirect)
if err != nil {
return nil, err
}
if !http_util.IsOriginAllowed(allowList, origin) {
allowList = append(allowList, origin)
}
}
for _, origin := range additionalOrigins {
if !http_util.IsOriginAllowed(allowList, origin) {
allowList = append(allowList, origin)
}
}
return allowList, nil
}

View File

@@ -0,0 +1,671 @@
package domain
import (
"reflect"
"strings"
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
func TestApplicationValid(t *testing.T) {
type args struct {
app *OIDCApp
}
tests := []struct {
name string
args args
result bool
}{
{
name: "invalid clock skew",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "AppName",
ClockSkew: gu.Ptr(time.Minute * 1),
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode},
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
},
},
result: false,
},
{
name: "invalid clock skew minus",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "AppName",
ClockSkew: gu.Ptr(time.Minute * -1),
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode},
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
},
},
result: false,
},
{
name: "valid oidc application: responsetype code",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode},
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
},
},
result: true,
},
{
name: "invalid oidc application: responsetype code",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode},
GrantTypes: []OIDCGrantType{OIDCGrantTypeImplicit},
},
},
result: false,
},
{
name: "valid oidc application: responsetype id_token",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeIDToken},
GrantTypes: []OIDCGrantType{OIDCGrantTypeImplicit},
},
},
result: true,
},
{
name: "invalid oidc application: responsetype id_token",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeIDToken},
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
},
},
result: false,
},
{
name: "valid oidc application: responsetype token_id_token",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeIDTokenToken},
GrantTypes: []OIDCGrantType{OIDCGrantTypeImplicit},
},
},
result: true,
},
{
name: "invalid oidc application: responsetype token_id_token",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeIDTokenToken},
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
},
},
result: false,
},
{
name: "valid oidc application: responsetype code & id_token",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode, OIDCResponseTypeIDToken},
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode, OIDCGrantTypeImplicit},
},
},
result: true,
},
{
name: "valid oidc application: responsetype code & token_id_token",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode, OIDCResponseTypeIDTokenToken},
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode, OIDCGrantTypeImplicit},
},
},
result: true,
},
{
name: "valid oidc application: responsetype code & id_token & token_id_token",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode, OIDCResponseTypeIDToken, OIDCResponseTypeIDTokenToken},
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode, OIDCGrantTypeImplicit},
},
},
result: true,
},
{
name: "invalid oidc application: invalid origin",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode, OIDCResponseTypeIDToken, OIDCResponseTypeIDTokenToken},
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode, OIDCGrantTypeImplicit},
AdditionalOrigins: []string{"https://test.com/test"},
},
},
result: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.args.app.IsValid()
if result != tt.result {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, result)
}
})
}
}
func TestGetOIDCV1Compliance(t *testing.T) {
type args struct {
appType *OIDCApplicationType
grantTypes []OIDCGrantType
authMethod *OIDCAuthMethodType
redirectUris []string
}
tests := []struct {
name string
args args
}{
{
name: "none compliant",
args: args{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GetOIDCV1Compliance(tt.args.appType, tt.args.grantTypes, tt.args.authMethod, tt.args.redirectUris)
if !got.NoneCompliant {
t.Error("compliance should be none compliant")
}
if len(got.Problems) == 0 || got.Problems[0] != "Application.OIDC.V1.NotCompliant" {
t.Errorf("first entry of problems should be \"Application.OIDC.V1.NotCompliant\" but got %v", got.Problems)
}
})
}
}
func Test_checkGrantTypesCombination(t *testing.T) {
tests := []struct {
name string
want *Compliance
grantTypes []OIDCGrantType
}{
{
name: "implicit",
want: new(Compliance),
grantTypes: []OIDCGrantType{OIDCGrantTypeImplicit},
},
{
name: "refresh token and implicit",
want: &Compliance{
NoneCompliant: true,
Problems: []string{"Application.OIDC.V1.GrantType.Refresh.NoAuthCode"},
},
grantTypes: []OIDCGrantType{OIDCGrantTypeImplicit, OIDCGrantTypeRefreshToken},
},
{
name: "device code flow and refresh token doesnt require OIDCGrantTypeImplicit",
want: &Compliance{},
grantTypes: []OIDCGrantType{OIDCGrantTypeDeviceCode, OIDCGrantTypeRefreshToken},
},
{
name: "refresh token and authorization code",
want: &Compliance{},
grantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode, OIDCGrantTypeRefreshToken},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
compliance := new(Compliance)
checkGrantTypesCombination(compliance, tt.grantTypes)
if tt.want.NoneCompliant != compliance.NoneCompliant {
t.Errorf("NoneCompliant: expected: %v, got %v", tt.want.NoneCompliant, compliance.NoneCompliant)
}
if !reflect.DeepEqual(tt.want.Problems, compliance.Problems) {
t.Errorf("Problems: expected: %v, got %v", tt.want.Problems, compliance.Problems)
}
})
}
}
func Test_checkRedirectURIs(t *testing.T) {
type args struct {
grantTypes []OIDCGrantType
appType *OIDCApplicationType
redirectUris []string
}
tests := []struct {
name string
want *Compliance
args args
}{
{
name: "no redirect uris",
want: &Compliance{
NoneCompliant: true,
Problems: []string{
"Application.OIDC.V1.NoRedirectUris",
},
},
args: args{},
},
{
name: "implicit and authorization code",
want: &Compliance{
NoneCompliant: false,
Problems: []string{"Application.OIDC.V1.NotAllCombinationsAreAllowed"},
},
args: args{
redirectUris: []string{"http://redirect.to/me"},
grantTypes: []OIDCGrantType{OIDCGrantTypeImplicit, OIDCGrantTypeAuthorizationCode},
},
},
{
name: "only implicit",
want: &Compliance{
NoneCompliant: true,
Problems: []string{"Application.OIDC.V1.Implicit.RedirectUris.HttpNotAllowed"},
},
args: args{
redirectUris: []string{"http://redirect.to/me"},
grantTypes: []OIDCGrantType{OIDCGrantTypeImplicit},
appType: gu.Ptr(OIDCApplicationTypeUserAgent),
},
},
{
name: "only authorization code",
want: &Compliance{
NoneCompliant: true,
Problems: []string{"Application.OIDC.V1.Code.RedirectUris.HttpOnlyForWeb"},
},
args: args{
redirectUris: []string{"http://redirect.to/me"},
grantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
appType: gu.Ptr(OIDCApplicationTypeUserAgent),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
compliance := new(Compliance)
checkRedirectURIs(compliance, tt.args.grantTypes, tt.args.appType, tt.args.redirectUris)
if tt.want.NoneCompliant != compliance.NoneCompliant {
t.Errorf("NoneCompliant: expected: %v, got %v", tt.want.NoneCompliant, compliance.NoneCompliant)
}
if !reflect.DeepEqual(tt.want.Problems, compliance.Problems) {
t.Errorf("Problems: expected: %v, got %v", tt.want.Problems, compliance.Problems)
}
})
}
}
func Test_CheckRedirectUrisImplicitAndCode(t *testing.T) {
type args struct {
appType *OIDCApplicationType
redirectUris []string
}
tests := []struct {
name string
want *Compliance
args args
}{
{
name: "implicit and code https",
want: &Compliance{
NoneCompliant: false,
Problems: nil,
},
args: args{
redirectUris: []string{"https://redirect.to/me"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
compliance := new(Compliance)
CheckRedirectUrisImplicitAndCode(compliance, tt.args.appType, tt.args.redirectUris)
if tt.want.NoneCompliant != compliance.NoneCompliant {
t.Errorf("NoneCompliant: expected: %v, got %v", tt.want.NoneCompliant, compliance.NoneCompliant)
}
if !reflect.DeepEqual(tt.want.Problems, compliance.Problems) {
t.Errorf("Problems: expected: %v, got %v", tt.want.Problems, compliance.Problems)
}
})
}
}
func TestCheckRedirectUrisImplicitAndCode(t *testing.T) {
type args struct {
appType *OIDCApplicationType
redirectUris []string
}
tests := []struct {
name string
args args
want *Compliance
}{
{
name: "only https",
args: args{},
want: &Compliance{},
},
{
name: "custom protocol not native app",
args: args{
appType: gu.Ptr(OIDCApplicationTypeWeb),
redirectUris: []string{"custom://nirvana.com"},
},
want: &Compliance{
NoneCompliant: true,
Problems: []string{"Application.OIDC.V1.Implicit.RedirectUris.CustomNotAllowed"},
},
},
{
name: "http localhost user agent app",
args: args{
appType: gu.Ptr(OIDCApplicationTypeUserAgent),
redirectUris: []string{"http://localhost:9009"},
},
want: &Compliance{
NoneCompliant: true,
Problems: []string{"Application.OIDC.V1.Code.RedirectUris.HttpOnlyForWeb"},
},
},
{
name: "http, not only localhost native app",
args: args{
appType: gu.Ptr(OIDCApplicationTypeNative),
redirectUris: []string{"http://nirvana.com", "http://localhost:9009"},
},
want: &Compliance{
NoneCompliant: true,
Problems: []string{"Application.OIDC.V1.Native.RedirectUris.MustBeHttpLocalhost"},
},
},
{
name: "not allowed combination",
args: args{
appType: gu.Ptr(OIDCApplicationTypeNative),
redirectUris: []string{"https://nirvana.com", "cutom://nirvana.com"},
},
want: &Compliance{
NoneCompliant: false,
Problems: []string{"Application.OIDC.V1.NotAllCombinationsAreAllowed"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := new(Compliance)
CheckRedirectUrisImplicitAndCode(got, tt.args.appType, tt.args.redirectUris)
if tt.want.NoneCompliant != got.NoneCompliant {
t.Errorf("NoneCompliant: expected: %v, got %v", tt.want.NoneCompliant, got.NoneCompliant)
}
if !reflect.DeepEqual(tt.want.Problems, got.Problems) {
t.Errorf("Problems: expected: %v, got %v", tt.want.Problems, got.Problems)
}
})
}
}
func TestCheckRedirectUrisImplicit(t *testing.T) {
type args struct {
appType *OIDCApplicationType
redirectUris []string
}
tests := []struct {
name string
args args
want *Compliance
}{
{
name: "only https",
args: args{},
want: &Compliance{},
},
{
name: "custom protocol",
args: args{
redirectUris: []string{"custom://nirvana.com"},
},
want: &Compliance{
NoneCompliant: true,
Problems: []string{"Application.OIDC.V1.Implicit.RedirectUris.CustomNotAllowed"},
},
},
{
name: "only http protocol, app type native, not only localhost",
args: args{
redirectUris: []string{"http://nirvana.com"},
appType: gu.Ptr(OIDCApplicationTypeNative),
},
want: &Compliance{
NoneCompliant: true,
Problems: []string{"Application.OIDC.V1.Native.RedirectUris.MustBeHttpLocalhost"},
},
},
{
name: "only http protocol, app type native, only localhost",
args: args{
redirectUris: []string{"http://localhost:8080"},
appType: gu.Ptr(OIDCApplicationTypeNative),
},
want: &Compliance{
NoneCompliant: false,
Problems: nil,
},
},
{
name: "only http protocol, app type web",
args: args{
redirectUris: []string{"http://nirvana.com"},
appType: gu.Ptr(OIDCApplicationTypeWeb),
},
want: &Compliance{
NoneCompliant: true,
Problems: []string{"Application.OIDC.V1.Implicit.RedirectUris.HttpNotAllowed"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := new(Compliance)
CheckRedirectUrisImplicit(got, tt.args.appType, tt.args.redirectUris)
if tt.want.NoneCompliant != got.NoneCompliant {
t.Errorf("NoneCompliant: expected: %v, got %v", tt.want.NoneCompliant, got.NoneCompliant)
}
if !reflect.DeepEqual(tt.want.Problems, got.Problems) {
t.Errorf("Problems: expected: %v, got %v", tt.want.Problems, got.Problems)
}
})
}
}
func TestCheckRedirectUrisCode(t *testing.T) {
type args struct {
appType *OIDCApplicationType
redirectUris []string
}
tests := []struct {
name string
args args
want *Compliance
}{
{
name: "only https",
args: args{},
want: &Compliance{},
},
{
name: "custom prefix, app type web",
args: args{
redirectUris: []string{"custom://nirvana.com"},
appType: gu.Ptr(OIDCApplicationTypeWeb),
},
want: &Compliance{
NoneCompliant: true,
Problems: []string{"Application.OIDC.V1.Code.RedirectUris.CustomOnlyForNative"},
},
},
{
name: "only http protocol, app type user agent",
args: args{
redirectUris: []string{"http://nirvana.com"},
appType: gu.Ptr(OIDCApplicationTypeUserAgent),
},
want: &Compliance{
NoneCompliant: true,
Problems: []string{"Application.OIDC.V1.Code.RedirectUris.HttpOnlyForWeb"},
},
},
{
name: "only http protocol, app type native, only localhost",
args: args{
redirectUris: []string{"http://localhost:8080", "http://nirvana.com:8080"},
appType: gu.Ptr(OIDCApplicationTypeNative),
},
want: &Compliance{
NoneCompliant: true,
Problems: []string{"Application.OIDC.V1.Native.RedirectUris.MustBeHttpLocalhost"},
},
},
{
name: "custom protocol, not native",
args: args{
redirectUris: []string{"custom://nirvana.com"},
appType: gu.Ptr(OIDCApplicationTypeWeb),
},
want: &Compliance{
NoneCompliant: true,
Problems: []string{"Application.OIDC.V1.Code.RedirectUris.CustomOnlyForNative"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := new(Compliance)
CheckRedirectUrisCode(got, tt.args.appType, tt.args.redirectUris)
if tt.want.NoneCompliant != got.NoneCompliant {
t.Errorf("NoneCompliant: expected: %v, got %v", tt.want.NoneCompliant, got.NoneCompliant)
}
if !reflect.DeepEqual(tt.want.Problems, got.Problems) {
t.Errorf("Problems: expected: %v, got %v", tt.want.Problems, got.Problems)
}
})
}
}
func TestOIDCOriginAllowList(t *testing.T) {
type args struct {
redirectUris []string
additionalOrigins []string
}
type want struct {
allowed []string
err func(error) bool
}
tests := []struct {
name string
args args
want want
}{
{
name: "no uris, no origins",
args: args{},
want: want{
allowed: []string{},
},
},
{
name: "redirects invalid schema",
args: args{
redirectUris: []string{"https:// localhost:8080"},
},
want: want{
allowed: nil,
err: func(e error) bool {
return strings.HasPrefix(e.Error(), "invalid chavalid character")
},
},
},
{
name: "redirects additional",
args: args{
redirectUris: []string{"https://localhost:8080"},
},
want: want{
allowed: []string{"https://localhost:8080"},
},
},
{
name: "additional origin",
args: args{
additionalOrigins: []string{"https://localhost:8080"},
},
want: want{
allowed: []string{"https://localhost:8080"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
allowed, err := OIDCOriginAllowList(tt.args.redirectUris, tt.args.additionalOrigins)
if tt.want.err == nil && err != nil {
t.Errorf("unexpected error: %v", err)
} else if tt.want.err == nil && err == nil {
//ok
} else if tt.want.err(err) {
t.Errorf("unexpected err got %v", err)
}
if !reflect.DeepEqual(allowed, tt.want.allowed) {
t.Errorf("expected list: %v, got: %v", tt.want.allowed, allowed)
}
})
}
}

View File

@@ -0,0 +1,45 @@
package domain
import (
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type SAMLApp struct {
models.ObjectRoot
AppID string
AppName string
EntityID string
Metadata []byte
MetadataURL *string
LoginVersion *LoginVersion
LoginBaseURI *string
State AppState
}
func (a *SAMLApp) GetApplicationName() string {
return a.AppName
}
func (a *SAMLApp) GetState() AppState {
return a.State
}
func (a *SAMLApp) GetMetadata() []byte {
return a.Metadata
}
func (a *SAMLApp) GetMetadataURL() string {
if a.MetadataURL != nil {
return *a.MetadataURL
}
return ""
}
func (a *SAMLApp) IsValid() bool {
if (a.MetadataURL == nil || *a.MetadataURL == "") && a.Metadata == nil {
return false
}
return true
}

View File

@@ -0,0 +1,46 @@
package domain
import "time"
const (
UsersAssetPath = "users"
AvatarAssetPath = "/avatar"
policyPrefix = "policy"
LabelPolicyPrefix = policyPrefix + "/label"
labelPolicyLogoPrefix = LabelPolicyPrefix + "/logo"
labelPolicyIconPrefix = LabelPolicyPrefix + "/icon"
labelPolicyFontPrefix = LabelPolicyPrefix + "/font"
Dark = "dark"
CssPath = LabelPolicyPrefix + "/css"
CssVariablesFileName = "variables.css"
LabelPolicyLogoPath = labelPolicyLogoPrefix
LabelPolicyIconPath = labelPolicyIconPrefix
LabelPolicyFontPath = labelPolicyFontPrefix
)
type AssetInfo struct {
Bucket string
Key string
ETag string
Size int64
LastModified time.Time
Location string
VersionID string
Expiration time.Time
AutheticatedURL string
ContentType string
}
func GetHumanAvatarAssetPath(userID string) string {
return UsersAssetPath + "/" + userID + AvatarAssetPath
}
func AssetURL(prefix, resourceOwner, key string) string {
if prefix == "" || resourceOwner == "" || key == "" {
return ""
}
return prefix + "/" + resourceOwner + "/" + key
}

View File

@@ -0,0 +1,285 @@
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
SelectedIDPConfigArgs map[string]any
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
}

View File

@@ -0,0 +1,108 @@
package domain
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestMFAType_UserAuthMethodType(t *testing.T) {
tests := []struct {
name string
m MFAType
want UserAuthMethodType
}{
{
name: "totp",
m: MFATypeTOTP,
want: UserAuthMethodTypeTOTP,
},
{
name: "u2f",
m: MFATypeU2F,
want: UserAuthMethodTypeU2F,
},
{
name: "passwordless",
m: MFATypeU2FUserVerification,
want: UserAuthMethodTypePasswordless,
},
{
name: "otp sms",
m: MFATypeOTPSMS,
want: UserAuthMethodTypeOTPSMS,
},
{
name: "otp email",
m: MFATypeOTPEmail,
want: UserAuthMethodTypeOTPEmail,
},
{
name: "unspecified",
m: 99,
want: UserAuthMethodTypeUnspecified,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.m.UserAuthMethodType()
assert.Equal(t, tt.want, got)
})
}
}
func TestAuthRequest_UserAuthMethodTypes(t *testing.T) {
type fields struct {
PasswordVerified bool
MFAsVerified []MFAType
}
tests := []struct {
name string
fields fields
want []UserAuthMethodType
}{
{
name: "no auth methods",
fields: fields{
PasswordVerified: false,
MFAsVerified: nil,
},
want: []UserAuthMethodType{},
},
{
name: "only password",
fields: fields{
PasswordVerified: true,
MFAsVerified: nil,
},
want: []UserAuthMethodType{
UserAuthMethodTypePassword,
},
},
{
name: "password, with mfa",
fields: fields{
PasswordVerified: true,
MFAsVerified: []MFAType{
MFATypeTOTP,
MFATypeU2F,
},
},
want: []UserAuthMethodType{
UserAuthMethodTypePassword,
UserAuthMethodTypeTOTP,
UserAuthMethodTypeU2F,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &AuthRequest{
PasswordVerified: tt.fields.PasswordVerified,
MFAsVerified: tt.fields.MFAsVerified,
}
got := a.UserAuthMethodTypes()
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,65 @@
package domain
import (
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/zerrors"
)
type authNKey interface {
SetPublicKey([]byte)
SetPrivateKey([]byte)
expiration
}
type AuthNKeyType int32
const (
AuthNKeyTypeNONE AuthNKeyType = iota
AuthNKeyTypeJSON
keyCount
)
func (k AuthNKeyType) Valid() bool {
return k >= 0 && k < 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
}
func SetNewAuthNKeyPair(key authNKey, keySize int) error {
privateKey, publicKey, err := NewAuthNKeyPair(keySize)
if err != nil {
return err
}
key.SetPrivateKey(privateKey)
key.SetPublicKey(publicKey)
return nil
}
func NewAuthNKeyPair(keySize int) (privateKey, publicKey []byte, err error) {
private, public, err := crypto.GenerateKeyPair(keySize)
if err != nil {
logging.Log("AUTHN-Ud51I").WithError(err).Error("unable to create authn key pair")
return nil, nil, zerrors.ThrowInternal(err, "AUTHN-gdg2l", "Errors.Project.CouldNotGenerateClientSecret")
}
publicKey, err = crypto.PublicKeyToBytes(public)
if err != nil {
logging.Log("AUTHN-Dbb35").WithError(err).Error("unable to convert public key")
return nil, nil, zerrors.ThrowInternal(err, "AUTHN-Bne3f", "Errors.Project.CouldNotGenerateClientSecret")
}
privateKey = crypto.PrivateKeyToBytes(private)
return privateKey, publicKey, nil
}

View File

@@ -0,0 +1,37 @@
package domain
import (
"net"
net_http "net/http"
http_util "github.com/zitadel/zitadel/internal/api/http"
)
type BrowserInfo struct {
UserAgent string
AcceptLanguage string
RemoteIP net.IP
Header net_http.Header
}
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),
Header: r.Header,
}
}
func (a *AuthRequest) ToUserAgent() *UserAgent {
agent := &UserAgent{
FingerprintID: &a.AgentID,
}
if a.BrowserInfo == nil {
return agent
}
agent.IP = a.BrowserInfo.RemoteIP
agent.Description = &a.BrowserInfo.UserAgent
agent.Header = a.BrowserInfo.Header
return agent
}

View File

@@ -0,0 +1,8 @@
package domain
import "time"
type BucketInfo struct {
Name string
CreationDate time.Time
}

View File

@@ -0,0 +1,9 @@
package domain
//go:generate enumer -type CountParentType -transform lower -trimprefix CountParentType -sql
type CountParentType int
const (
CountParentTypeInstance CountParentType = iota
CountParentTypeOrganization
)

View File

@@ -0,0 +1,109 @@
// Code generated by "enumer -type CountParentType -transform lower -trimprefix CountParentType -sql"; DO NOT EDIT.
package domain
import (
"database/sql/driver"
"fmt"
"strings"
)
const _CountParentTypeName = "instanceorganization"
var _CountParentTypeIndex = [...]uint8{0, 8, 20}
const _CountParentTypeLowerName = "instanceorganization"
func (i CountParentType) String() string {
if i < 0 || i >= CountParentType(len(_CountParentTypeIndex)-1) {
return fmt.Sprintf("CountParentType(%d)", i)
}
return _CountParentTypeName[_CountParentTypeIndex[i]:_CountParentTypeIndex[i+1]]
}
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
func _CountParentTypeNoOp() {
var x [1]struct{}
_ = x[CountParentTypeInstance-(0)]
_ = x[CountParentTypeOrganization-(1)]
}
var _CountParentTypeValues = []CountParentType{CountParentTypeInstance, CountParentTypeOrganization}
var _CountParentTypeNameToValueMap = map[string]CountParentType{
_CountParentTypeName[0:8]: CountParentTypeInstance,
_CountParentTypeLowerName[0:8]: CountParentTypeInstance,
_CountParentTypeName[8:20]: CountParentTypeOrganization,
_CountParentTypeLowerName[8:20]: CountParentTypeOrganization,
}
var _CountParentTypeNames = []string{
_CountParentTypeName[0:8],
_CountParentTypeName[8:20],
}
// CountParentTypeString retrieves an enum value from the enum constants string name.
// Throws an error if the param is not part of the enum.
func CountParentTypeString(s string) (CountParentType, error) {
if val, ok := _CountParentTypeNameToValueMap[s]; ok {
return val, nil
}
if val, ok := _CountParentTypeNameToValueMap[strings.ToLower(s)]; ok {
return val, nil
}
return 0, fmt.Errorf("%s does not belong to CountParentType values", s)
}
// CountParentTypeValues returns all values of the enum
func CountParentTypeValues() []CountParentType {
return _CountParentTypeValues
}
// CountParentTypeStrings returns a slice of all String values of the enum
func CountParentTypeStrings() []string {
strs := make([]string, len(_CountParentTypeNames))
copy(strs, _CountParentTypeNames)
return strs
}
// IsACountParentType returns "true" if the value is listed in the enum definition. "false" otherwise
func (i CountParentType) IsACountParentType() bool {
for _, v := range _CountParentTypeValues {
if i == v {
return true
}
}
return false
}
func (i CountParentType) Value() (driver.Value, error) {
return i.String(), nil
}
func (i *CountParentType) Scan(value interface{}) error {
if value == nil {
return nil
}
var str string
switch v := value.(type) {
case []byte:
str = string(v)
case string:
str = v
case fmt.Stringer:
str = v.String()
default:
return fmt.Errorf("invalid value of CountParentType: %[1]T(%[1]v)", value)
}
val, err := CountParentTypeString(str)
if err != nil {
return err
}
*i = val
return nil
}

View File

@@ -0,0 +1,675 @@
package domain
import (
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
const (
LoginCustomText = "Login"
LoginKeyLogin = "Login."
LoginKeyLoginTitle = LoginKeyLogin + "Title"
LoginKeyLoginDescription = LoginKeyLogin + "Description"
LoginKeyLoginTitleLinkingProcess = LoginKeyLogin + "TitleLinking"
LoginKeyLoginDescriptionLinkingProcess = LoginKeyLogin + "DescriptionLinking"
LoginKeyLoginNameLabel = LoginKeyLogin + "LoginNameLabel"
LoginKeyLoginUsernamePlaceHolder = LoginKeyLogin + "UsernamePlaceHolder"
LoginKeyLoginLoginnamePlaceHolder = LoginKeyLogin + "LoginnamePlaceHolder"
LoginKeyLoginRegisterButtonText = LoginKeyLogin + "RegisterButtonText"
LoginKeyLoginNextButtonText = LoginKeyLogin + "NextButtonText"
LoginKeyLoginExternalUserDescription = LoginKeyLogin + "ExternalUserDescription"
LoginKeyLoginUserMustBeMemberOfOrg = LoginKeyLogin + "MustBeMemberOfOrg"
LoginKeySelectAccount = "SelectAccount."
LoginKeySelectAccountTitle = LoginKeySelectAccount + "Title"
LoginKeySelectAccountDescription = LoginKeySelectAccount + "Description"
LoginKeySelectAccountTitleLinkingProcess = LoginKeySelectAccount + "TitleLinking"
LoginKeySelectAccountDescriptionLinkingProcess = LoginKeySelectAccount + "DescriptionLinking"
LoginKeySelectAccountOtherUser = LoginKeySelectAccount + "OtherUser"
LoginKeySelectAccountSessionStateActive = LoginKeySelectAccount + "SessionState0"
LoginKeySelectAccountSessionStateInactive = LoginKeySelectAccount + "SessionState1"
LoginKeySelectAccountUserMustBeMemberOfOrg = LoginKeySelectAccount + "MustBeMemberOfOrg"
LoginKeyPassword = "Password."
LoginKeyPasswordTitle = LoginKeyPassword + "Title"
LoginKeyPasswordDescription = LoginKeyPassword + "Description"
LoginKeyPasswordLabel = LoginKeyPassword + "PasswordLabel"
LoginKeyPasswordMinLength = LoginKeyPassword + "MinLength"
LoginKeyPasswordHasUppercase = LoginKeyPassword + "HasUppercase"
LoginKeyPasswordHasLowercase = LoginKeyPassword + "HasLowercase"
LoginKeyPasswordHasNumber = LoginKeyPassword + "HasNumber"
LoginKeyPasswordHasSymbol = LoginKeyPassword + "HasSymbol"
LoginKeyPasswordConfirmation = LoginKeyPassword + "Confirmation"
LoginKeyPasswordResetLinkText = LoginKeyPassword + "ResetLinkText"
LoginKeyPasswordBackButtonText = LoginKeyPassword + "BackButtonText"
LoginKeyPasswordNextButtonText = LoginKeyPassword + "NextButtonText"
LoginKeyUsernameChange = "UsernameChange."
LoginKeyUsernameChangeTitle = LoginKeyUsernameChange + "Title"
LoginKeyUsernameChangeDescription = LoginKeyUsernameChange + "Description"
LoginKeyUsernameChangeUsernameLabel = LoginKeyUsernameChange + "UsernameLabel"
LoginKeyUsernameChangeCancelButtonText = LoginKeyUsernameChange + "CancelButtonText"
LoginKeyUsernameChangeNextButtonText = LoginKeyUsernameChange + "NextButtonText"
LoginKeyUsernameChangeDone = "UsernameChangeDone."
LoginKeyUsernameChangeDoneTitle = LoginKeyUsernameChangeDone + "Title"
LoginKeyUsernameChangeDoneDescription = LoginKeyUsernameChangeDone + "Description"
LoginKeyUsernameChangeDoneNextButtonText = LoginKeyUsernameChangeDone + "NextButtonText"
LoginKeyInitPassword = "InitPassword."
LoginKeyInitPasswordTitle = LoginKeyInitPassword + "Title"
LoginKeyInitPasswordDescription = LoginKeyInitPassword + "Description"
LoginKeyInitPasswordCodeLabel = LoginKeyInitPassword + "CodeLabel"
LoginKeyInitPasswordNewPasswordLabel = LoginKeyInitPassword + "NewPasswordLabel"
LoginKeyInitPasswordNewPasswordConfirmLabel = LoginKeyInitPassword + "NewPasswordConfirmLabel"
LoginKeyInitPasswordNextButtonText = LoginKeyInitPassword + "NextButtonText"
LoginKeyInitPasswordResendButtonText = LoginKeyInitPassword + "ResendButtonText"
LoginKeyInitPasswordDone = "InitPasswordDone."
LoginKeyInitPasswordDoneTitle = LoginKeyInitPasswordDone + "Title"
LoginKeyInitPasswordDoneDescription = LoginKeyInitPasswordDone + "Description"
LoginKeyInitPasswordDoneNextButtonText = LoginKeyInitPasswordDone + "NextButtonText"
LoginKeyInitPasswordDoneCancelButtonText = LoginKeyInitPasswordDone + "CancelButtonText"
LoginKeyEmailVerification = "EmailVerification."
LoginKeyEmailVerificationTitle = LoginKeyEmailVerification + "Title"
LoginKeyEmailVerificationDescription = LoginKeyEmailVerification + "Description"
LoginKeyEmailVerificationCodeLabel = LoginKeyEmailVerification + "CodeLabel"
LoginKeyEmailVerificationNextButtonText = LoginKeyEmailVerification + "NextButtonText"
LoginKeyEmailVerificationResendButtonText = LoginKeyEmailVerification + "ResendButtonText"
LoginKeyEmailVerificationDone = "EmailVerificationDone."
LoginKeyEmailVerificationDoneTitle = LoginKeyEmailVerificationDone + "Title"
LoginKeyEmailVerificationDoneDescription = LoginKeyEmailVerificationDone + "Description"
LoginKeyEmailVerificationDoneNextButtonText = LoginKeyEmailVerificationDone + "NextButtonText"
LoginKeyEmailVerificationDoneCancelButtonText = LoginKeyEmailVerificationDone + "CancelButtonText"
LoginKeyEmailVerificationDoneLoginButtonText = LoginKeyEmailVerificationDone + "LoginButtonText"
LoginKeyInitializeUser = "InitUser."
LoginKeyInitializeUserTitle = LoginKeyInitializeUser + "Title"
LoginKeyInitializeUserDescription = LoginKeyInitializeUser + "Description"
LoginKeyInitializeUserCodeLabel = LoginKeyInitializeUser + "CodeLabel"
LoginKeyInitializeUserNewPasswordLabel = LoginKeyInitializeUser + "NewPasswordLabel"
LoginKeyInitializeUserNewPasswordConfirmLabel = LoginKeyInitializeUser + "NewPasswordConfirm"
LoginKeyInitializeUserResendButtonText = LoginKeyInitializeUser + "ResendButtonText"
LoginKeyInitializeUserNextButtonText = LoginKeyInitializeUser + "NextButtonText"
LoginKeyInitUserDone = "InitUserDone."
LoginKeyInitUserDoneTitle = LoginKeyInitUserDone + "Title"
LoginKeyInitUserDoneDescription = LoginKeyInitUserDone + "Description"
LoginKeyInitUserDoneCancelButtonText = LoginKeyInitUserDone + "CancelButtonText"
LoginKeyInitUserDoneNextButtonText = LoginKeyInitUserDone + "NextButtonText"
LoginKeyInitMFAPrompt = "InitMFAPrompt."
LoginKeyInitMFAPromptTitle = LoginKeyInitMFAPrompt + "Title"
LoginKeyInitMFAPromptDescription = LoginKeyInitMFAPrompt + "Description"
LoginKeyInitMFAPromptOTPOption = LoginKeyInitMFAPrompt + "Provider0"
LoginKeyInitMFAPromptU2FOption = LoginKeyInitMFAPrompt + "Provider1"
LoginKeyInitMFAPromptSkipButtonText = LoginKeyInitMFAPrompt + "SkipButtonText"
LoginKeyInitMFAPromptNextButtonText = LoginKeyInitMFAPrompt + "NextButtonText"
LoginKeyInitMFAOTP = "InitMFAOTP."
LoginKeyInitMFAOTPTitle = LoginKeyInitMFAOTP + "Title"
LoginKeyInitMFAOTPDescription = LoginKeyInitMFAOTP + "Description"
LoginKeyInitMFAOTPDescriptionOTP = LoginKeyInitMFAOTP + "OTPDescription"
LoginKeyInitMFAOTPSecretLabel = LoginKeyInitMFAOTP + "SecretLabel"
LoginKeyInitMFAOTPCodeLabel = LoginKeyInitMFAOTP + "CodeLabel"
LoginKeyInitMFAOTPNextButtonText = LoginKeyInitMFAOTP + "NextButtonText"
LoginKeyInitMFAOTPCancelButtonText = LoginKeyInitMFAOTP + "CancelButtonText"
LoginKeyInitMFAU2F = "InitMFAU2F."
LoginKeyInitMFAU2FTitle = LoginKeyInitMFAU2F + "Title"
LoginKeyInitMFAU2FDescription = LoginKeyInitMFAU2F + "Description"
LoginKeyInitMFAU2FTokenNameLabel = LoginKeyInitMFAU2F + "TokenNameLabel"
LoginKeyInitMFAU2FNotSupported = LoginKeyInitMFAU2F + "NotSupported"
LoginKeyInitMFAU2FRegisterTokenButtonText = LoginKeyInitMFAU2F + "RegisterTokenButtonText"
LoginKeyInitMFAU2FErrorRetry = LoginKeyInitMFAU2F + "ErrorRetry"
LoginKeyInitMFADone = "InitMFADone."
LoginKeyInitMFADoneTitle = LoginKeyInitMFADone + "Title"
LoginKeyInitMFADoneDescription = LoginKeyInitMFADone + "Description"
LoginKeyInitMFADoneCancelButtonText = LoginKeyInitMFADone + "CancelButtonText"
LoginKeyInitMFADoneNextButtonText = LoginKeyInitMFADone + "NextButtonText"
LoginKeyMFAProviders = "MFAProvider."
LoginKeyMFAProvidersChooseOther = LoginKeyMFAProviders + "ChooseOther"
LoginKeyMFAProvidersOTP = LoginKeyMFAProviders + "Provider0"
LoginKeyMFAProvidersU2F = LoginKeyMFAProviders + "Provider1"
LoginKeyVerifyMFAOTP = "VerifyMFAOTP."
LoginKeyVerifyMFAOTPTitle = LoginKeyVerifyMFAOTP + "Title"
LoginKeyVerifyMFAOTPDescription = LoginKeyVerifyMFAOTP + "Description"
LoginKeyVerifyMFAOTPCodeLabel = LoginKeyVerifyMFAOTP + "CodeLabel"
LoginKeyVerifyMFAOTPNextButtonText = LoginKeyVerifyMFAOTP + "NextButtonText"
LoginKeyVerifyMFAU2F = "VerifyMFAU2F."
LoginKeyVerifyMFAU2FTitle = LoginKeyVerifyMFAU2F + "Title"
LoginKeyVerifyMFAU2FDescription = LoginKeyVerifyMFAU2F + "Description"
LoginKeyVerifyMFAU2FNotSupported = LoginKeyVerifyMFAU2F + "NotSupported"
LoginKeyVerifyMFAU2FValidateTokenText = LoginKeyVerifyMFAU2F + "ValidateTokenButtonText"
LoginKeyVerifyMFAU2FErrorRetry = LoginKeyVerifyMFAU2F + "ErrorRetry"
LoginKeyPasswordless = "Passwordless."
LoginKeyPasswordlessTitle = LoginKeyPasswordless + "Title"
LoginKeyPasswordlessDescription = LoginKeyPasswordless + "Description"
LoginKeyPasswordlessLoginWithPwButtonText = LoginKeyPasswordless + "LoginWithPwButtonText"
LoginKeyPasswordlessValidateTokenButtonText = LoginKeyPasswordless + "ValidateTokenButtonText"
LoginKeyPasswordlessNotSupported = LoginKeyPasswordless + "NotSupported"
LoginKeyPasswordlessErrorRetry = LoginKeyPasswordless + "ErrorRetry"
LoginKeyPasswordlessPrompt = "PasswordlessPrompt."
LoginKeyPasswordlessPromptTitle = LoginKeyPasswordlessPrompt + "Title"
LoginKeyPasswordlessPromptDescription = LoginKeyPasswordlessPrompt + "Description"
LoginKeyPasswordlessPromptDescriptionInit = LoginKeyPasswordlessPrompt + "DescriptionInit"
LoginKeyPasswordlessPromptPasswordlessButtonText = LoginKeyPasswordlessPrompt + "PasswordlessButtonText"
LoginKeyPasswordlessPromptNextButtonText = LoginKeyPasswordlessPrompt + "NextButtonText"
LoginKeyPasswordlessPromptSkipButtonText = LoginKeyPasswordlessPrompt + "SkipButtonText"
LoginKeyPasswordlessRegistration = "PasswordlessRegistration."
LoginKeyPasswordlessRegistrationTitle = LoginKeyPasswordlessRegistration + "Title"
LoginKeyPasswordlessRegistrationDescription = LoginKeyPasswordlessRegistration + "Description"
LoginKeyPasswordlessRegistrationRegisterTokenButtonText = LoginKeyPasswordlessRegistration + "RegisterTokenButtonText"
LoginKeyPasswordlessRegistrationTokenNameLabel = LoginKeyPasswordlessRegistration + "TokenNameLabel"
LoginKeyPasswordlessRegistrationNotSupported = LoginKeyPasswordlessRegistration + "NotSupported"
LoginKeyPasswordlessRegistrationErrorRetry = LoginKeyPasswordlessRegistration + "ErrorRetry"
LoginKeyPasswordlessRegistrationDone = "PasswordlessRegistrationDone."
LoginKeyPasswordlessRegistrationDoneTitle = LoginKeyPasswordlessRegistrationDone + "Title"
LoginKeyPasswordlessRegistrationDoneDescription = LoginKeyPasswordlessRegistrationDone + "Description"
LoginKeyPasswordlessRegistrationDoneDescriptionClose = LoginKeyPasswordlessRegistrationDone + "DescriptionClose"
LoginKeyPasswordlessRegistrationDoneNextButtonText = LoginKeyPasswordlessRegistrationDone + "NextButtonText"
LoginKeyPasswordlessRegistrationDoneCancelButtonText = LoginKeyPasswordlessRegistrationDone + "CancelButtonText"
LoginKeyPasswordChange = "PasswordChange."
LoginKeyPasswordChangeTitle = LoginKeyPasswordChange + "Title"
LoginKeyPasswordChangeDescription = LoginKeyPasswordChange + "Description"
LoginKeyPasswordChangeExpiredDescription = LoginKeyPasswordChange + "ExpiredDescription"
LoginKeyPasswordChangeOldPasswordLabel = LoginKeyPasswordChange + "OldPasswordLabel"
LoginKeyPasswordChangeNewPasswordLabel = LoginKeyPasswordChange + "NewPasswordLabel"
LoginKeyPasswordChangeNewPasswordConfirmLabel = LoginKeyPasswordChange + "NewPasswordConfirmLabel"
LoginKeyPasswordChangeCancelButtonText = LoginKeyPasswordChange + "CancelButtonText"
LoginKeyPasswordChangeNextButtonText = LoginKeyPasswordChange + "NextButtonText"
LoginKeyPasswordChangeDone = "PasswordChangeDone."
LoginKeyPasswordChangeDoneTitle = LoginKeyPasswordChangeDone + "Title"
LoginKeyPasswordChangeDoneDescription = LoginKeyPasswordChangeDone + "Description"
LoginKeyPasswordChangeDoneNextButtonText = LoginKeyPasswordChangeDone + "NextButtonText"
LoginKeyPasswordResetDone = "PasswordResetDone."
LoginKeyPasswordResetDoneTitle = LoginKeyPasswordResetDone + "Title"
LoginKeyPasswordResetDoneDescription = LoginKeyPasswordResetDone + "Description"
LoginKeyPasswordResetDoneNextButtonText = LoginKeyPasswordResetDone + "NextButtonText"
LoginKeyRegistrationOption = "RegisterOption."
LoginKeyRegistrationOptionTitle = LoginKeyRegistrationOption + "Title"
LoginKeyRegistrationOptionDescription = LoginKeyRegistrationOption + "Description"
LoginKeyRegistrationOptionUserNameButtonText = LoginKeyRegistrationOption + "RegisterUsernamePasswordButtonText"
LoginKeyRegistrationOptionExternalLoginDescription = LoginKeyRegistrationOption + "ExternalLoginDescription"
LoginKeyRegistrationOptionLoginButtonText = LoginKeyRegistrationOption + "LoginButtonText"
LoginKeyRegistrationUser = "RegistrationUser."
LoginKeyRegistrationUserTitle = LoginKeyRegistrationUser + "Title"
LoginKeyRegistrationUserDescription = LoginKeyRegistrationUser + "Description"
LoginKeyRegistrationUserDescriptionOrgRegister = LoginKeyRegistrationUser + "DescriptionOrgRegister"
LoginKeyRegistrationUserFirstnameLabel = LoginKeyRegistrationUser + "FirstnameLabel"
LoginKeyRegistrationUserLastnameLabel = LoginKeyRegistrationUser + "LastnameLabel"
LoginKeyRegistrationUserEmailLabel = LoginKeyRegistrationUser + "EmailLabel"
LoginKeyRegistrationUserUsernameLabel = LoginKeyRegistrationUser + "UsernameLabel"
LoginKeyRegistrationUserLanguageLabel = LoginKeyRegistrationUser + "LanguageLabel"
LoginKeyRegistrationUserGenderLabel = LoginKeyRegistrationUser + "GenderLabel"
LoginKeyRegistrationUserPasswordLabel = LoginKeyRegistrationUser + "PasswordLabel"
LoginKeyRegistrationUserPasswordConfirmLabel = LoginKeyRegistrationUser + "PasswordConfirmLabel"
LoginKeyRegistrationUserTOSAndPrivacyLabel = LoginKeyRegistrationUser + "TosAndPrivacyLabel"
LoginKeyRegistrationUserTOSConfirm = LoginKeyRegistrationUser + "TosConfirm"
LoginKeyRegistrationUserTOSLinkText = LoginKeyRegistrationUser + "TosLinkText"
LoginKeyRegistrationUserPrivacyConfirm = LoginKeyRegistrationUser + "PrivacyConfirm"
LoginKeyRegistrationUserPrivacyLinkText = LoginKeyRegistrationUser + "PrivacyLinkText"
LoginKeyRegistrationUserNextButtonText = LoginKeyRegistrationUser + "NextButtonText"
LoginKeyRegistrationUserBackButtonText = LoginKeyRegistrationUser + "BackButtonText"
LoginKeyExternalRegistrationUserOverview = "ExternalRegistrationUserOverview."
LoginKeyExternalRegistrationUserOverviewTitle = LoginKeyExternalRegistrationUserOverview + "Title"
LoginKeyExternalRegistrationUserOverviewDescription = LoginKeyExternalRegistrationUserOverview + "Description"
LoginKeyExternalRegistrationUserOverviewEmailLabel = LoginKeyExternalRegistrationUserOverview + "EmailLabel"
LoginKeyExternalRegistrationUserOverviewUsernameLabel = LoginKeyExternalRegistrationUserOverview + "UsernameLabel"
LoginKeyExternalRegistrationUserOverviewFirstnameLabel = LoginKeyExternalRegistrationUserOverview + "FirstnameLabel"
LoginKeyExternalRegistrationUserOverviewLastnameLabel = LoginKeyExternalRegistrationUserOverview + "LastnameLabel"
LoginKeyExternalRegistrationUserOverviewNicknameLabel = LoginKeyExternalRegistrationUserOverview + "NicknameLabel"
LoginKeyExternalRegistrationUserOverviewPhoneLabel = LoginKeyExternalRegistrationUserOverview + "PhoneLabel"
LoginKeyExternalRegistrationUserOverviewLanguageLabel = LoginKeyExternalRegistrationUserOverview + "LanguageLabel"
LoginKeyExternalRegistrationUserOverviewTOSAndPrivacyLabel = LoginKeyExternalRegistrationUserOverview + "TosAndPrivacyLabel"
LoginKeyExternalRegistrationUserOverviewTOSConfirm = LoginKeyExternalRegistrationUserOverview + "TosConfirm"
LoginKeyExternalRegistrationUserOverviewTOSLinkText = LoginKeyExternalRegistrationUserOverview + "TosLinkText"
LoginKeyExternalRegistrationUserOverviewPrivacyConfirm = LoginKeyExternalRegistrationUserOverview + "PrivacyConfirm"
LoginKeyExternalRegistrationUserOverviewPrivacyLinkText = LoginKeyExternalRegistrationUserOverview + "PrivacyLinkText"
LoginKeyExternalRegistrationUserOverviewBackButtonText = LoginKeyExternalRegistrationUserOverview + "BackButtonText"
LoginKeyExternalRegistrationUserOverviewNextButtonText = LoginKeyExternalRegistrationUserOverview + "NextButtonText"
LoginKeyRegistrationOrg = "RegistrationOrg."
LoginKeyRegisterOrgTitle = LoginKeyRegistrationOrg + "Title"
LoginKeyRegisterOrgDescription = LoginKeyRegistrationOrg + "Description"
LoginKeyRegisterOrgOrgNameLabel = LoginKeyRegistrationOrg + "OrgNameLabel"
LoginKeyRegisterOrgFirstnameLabel = LoginKeyRegistrationOrg + "FirstnameLabel"
LoginKeyRegisterOrgLastnameLabel = LoginKeyRegistrationOrg + "LastnameLabel"
LoginKeyRegisterOrgUsernameLabel = LoginKeyRegistrationOrg + "UsernameLabel"
LoginKeyRegisterOrgEmailLabel = LoginKeyRegistrationOrg + "EmailLabel"
LoginKeyRegisterOrgPasswordLabel = LoginKeyRegistrationOrg + "PasswordLabel"
LoginKeyRegisterOrgPasswordConfirmLabel = LoginKeyRegistrationOrg + "PasswordConfirmLabel"
LoginKeyRegisterOrgTOSAndPrivacyLabel = LoginKeyRegistrationOrg + "TosAndPrivacyLabel"
LoginKeyRegisterOrgTOSConfirm = LoginKeyRegistrationOrg + "TosConfirm"
LoginKeyRegisterOrgTOSLinkText = LoginKeyRegistrationOrg + "TosLinkText"
LoginKeyRegisterOrgPrivacyConfirm = LoginKeyRegistrationOrg + "PrivacyConfirm"
LoginKeyRegisterOrgPrivacyLinkText = LoginKeyRegistrationOrg + "PrivacyLinkText"
LoginKeyRegisterOrgSaveButtonText = LoginKeyRegistrationOrg + "SaveButtonText"
LoginKeyRegisterOrgBackButtonText = LoginKeyRegistrationOrg + "BackButtonText"
LoginKeyLinkingUserDone = "LinkingUsersDone."
LoginKeyLinkingUserDoneTitle = LoginKeyLinkingUserDone + "Title"
LoginKeyLinkingUserDoneDescription = LoginKeyLinkingUserDone + "Description"
LoginKeyLinkingUserDoneCancelButtonText = LoginKeyLinkingUserDone + "CancelButtonText"
LoginKeyLinkingUserDoneNextButtonText = LoginKeyLinkingUserDone + "NextButtonText"
LoginKeyExternalNotFound = "ExternalNotFound."
LoginKeyExternalNotFoundTitle = LoginKeyExternalNotFound + "Title"
LoginKeyExternalNotFoundDescription = LoginKeyExternalNotFound + "Description"
LoginKeyExternalNotFoundLinkButtonText = LoginKeyExternalNotFound + "LinkButtonText"
LoginKeyExternalNotFoundAutoRegisterButtonText = LoginKeyExternalNotFound + "AutoRegisterButtonText"
LoginKeyExternalNotFoundTOSAndPrivacyLabel = LoginKeyExternalNotFound + "TosAndPrivacyLabel"
LoginKeyExternalNotFoundTOSConfirm = LoginKeyExternalNotFound + "TosConfirm"
LoginKeyExternalNotFoundTOSLinkText = LoginKeyExternalNotFound + "TosLinkText"
LoginKeyExternalNotFoundPrivacyConfirm = LoginKeyExternalNotFound + "PrivacyConfirm"
LoginKeyExternalNotFoundPrivacyLinkText = LoginKeyExternalNotFound + "PrivacyLinkText"
LoginKeySuccessLogin = "LoginSuccess."
LoginKeySuccessLoginTitle = LoginKeySuccessLogin + "Title"
LoginKeySuccessLoginAutoRedirectDescription = LoginKeySuccessLogin + "AutoRedirectDescription"
LoginKeySuccessLoginRedirectedDescription = LoginKeySuccessLogin + "RedirectedDescription"
LoginKeySuccessLoginNextButtonText = LoginKeySuccessLogin + "NextButtonText"
LoginKeyLogoutDone = "LogoutDone."
LoginKeyLogoutDoneTitle = LoginKeyLogoutDone + "Title"
LoginKeyLogoutDoneDescription = LoginKeyLogoutDone + "Description"
LoginKeyLogoutDoneLoginButtonText = LoginKeyLogoutDone + "LoginButtonText"
LoginKeyFooter = "Footer."
LoginKeyFooterTOS = LoginKeyFooter + "Tos"
LoginKeyFooterPrivacyPolicy = LoginKeyFooter + "PrivacyPolicy"
LoginKeyFooterHelp = LoginKeyFooter + "Help"
LoginKeyFooterSupportEmail = LoginKeyFooter + "SupportEmail"
)
type CustomLoginText struct {
models.ObjectRoot
State PolicyState
Default bool
Language language.Tag
IsDefault bool
SelectAccount SelectAccountScreenText
Login LoginScreenText
Password PasswordScreenText
UsernameChange UsernameChangeScreenText
UsernameChangeDone UsernameChangeDoneScreenText
InitPassword InitPasswordScreenText
InitPasswordDone InitPasswordDoneScreenText
EmailVerification EmailVerificationScreenText
EmailVerificationDone EmailVerificationDoneScreenText
InitUser InitializeUserScreenText
InitUserDone InitializeUserDoneScreenText
InitMFAPrompt InitMFAPromptScreenText
InitMFAOTP InitMFAOTPScreenText
InitMFAU2F InitMFAU2FScreenText
InitMFADone InitMFADoneScreenText
MFAProvider MFAProvidersText
VerifyMFAOTP VerifyMFAOTPScreenText
VerifyMFAU2F VerifyMFAU2FScreenText
Passwordless PasswordlessScreenText
PasswordlessPrompt PasswordlessPromptScreenText
PasswordlessRegistration PasswordlessRegistrationScreenText
PasswordlessRegistrationDone PasswordlessRegistrationDoneScreenText
PasswordChange PasswordChangeScreenText
PasswordChangeDone PasswordChangeDoneScreenText
PasswordResetDone PasswordResetDoneScreenText
RegisterOption RegistrationOptionScreenText
RegistrationUser RegistrationUserScreenText
ExternalRegistrationUserOverview ExternalRegistrationUserOverviewScreenText
RegistrationOrg RegistrationOrgScreenText
LinkingUsersDone LinkingUserDoneScreenText
ExternalNotFound ExternalUserNotFoundScreenText
LoginSuccess SuccessLoginScreenText
LogoutDone LogoutDoneScreenText
Footer FooterText
}
func (m *CustomLoginText) IsValid(supportedLanguages []language.Tag) error {
if err := LanguageIsDefined(m.Language); err != nil {
return err
}
return LanguagesAreSupported(supportedLanguages, m.Language)
}
type SelectAccountScreenText struct {
Title string
Description string
TitleLinking string
DescriptionLinking string
OtherUser string
SessionState0 string //active
SessionState1 string //inactive
MustBeMemberOfOrg string
}
type LoginScreenText struct {
Title string
Description string
TitleLinking string
DescriptionLinking string
LoginNameLabel string
UsernamePlaceholder string
LoginnamePlaceholder string
RegisterButtonText string
NextButtonText string
ExternalUserDescription string
MustBeMemberOfOrg string
}
type PasswordScreenText struct {
Title string
Description string
PasswordLabel string
ResetLinkText string
BackButtonText string
NextButtonText string
MinLength string
HasUppercase string
HasLowercase string
HasNumber string
HasSymbol string
Confirmation string
}
type UsernameChangeScreenText struct {
Title string
Description string
UsernameLabel string
CancelButtonText string
NextButtonText string
}
type UsernameChangeDoneScreenText struct {
Title string
Description string
NextButtonText string
}
type InitPasswordScreenText struct {
Title string
Description string
CodeLabel string
NewPasswordLabel string
NewPasswordConfirmLabel string
NextButtonText string
ResendButtonText string
}
type InitPasswordDoneScreenText struct {
Title string
Description string
NextButtonText string
CancelButtonText string
}
type EmailVerificationScreenText struct {
Title string
Description string
CodeLabel string
NextButtonText string
ResendButtonText string
}
type EmailVerificationDoneScreenText struct {
Title string
Description string
NextButtonText string
CancelButtonText string
LoginButtonText string
}
type InitializeUserScreenText struct {
Title string
Description string
CodeLabel string
NewPasswordLabel string
NewPasswordConfirmLabel string
ResendButtonText string
NextButtonText string
}
type InitializeUserDoneScreenText struct {
Title string
Description string
CancelButtonText string
NextButtonText string
}
type InitMFAPromptScreenText struct {
Title string
Description string
Provider0 string //OTP
Provider1 string //U2F
SkipButtonText string
NextButtonText string
}
type InitMFAOTPScreenText struct {
Title string
Description string
OTPDescription string
SecretLabel string
CodeLabel string
NextButtonText string
CancelButtonText string
}
type InitMFAU2FScreenText struct {
Title string
Description string
TokenNameLabel string
RegisterTokenButtonText string
NotSupported string
ErrorRetry string
}
type InitMFADoneScreenText struct {
Title string
Description string
CancelButtonText string
NextButtonText string
}
type MFAProvidersText struct {
ChooseOther string
Provider0 string //OTP
Provider1 string //U2F
}
type VerifyMFAOTPScreenText struct {
Title string
Description string
CodeLabel string
NextButtonText string
}
type VerifyMFAU2FScreenText struct {
Title string
Description string
ValidateTokenButtonText string
NotSupported string
ErrorRetry string
}
type PasswordlessScreenText struct {
Title string
Description string
LoginWithPwButtonText string
ValidateTokenButtonText string
NotSupported string
ErrorRetry string
}
type PasswordChangeScreenText struct {
Title string
Description string
ExpiredDescription string
OldPasswordLabel string
NewPasswordLabel string
NewPasswordConfirmLabel string
CancelButtonText string
NextButtonText string
}
type PasswordChangeDoneScreenText struct {
Title string
Description string
NextButtonText string
}
type PasswordResetDoneScreenText struct {
Title string
Description string
NextButtonText string
}
type RegistrationOptionScreenText struct {
Title string
Description string
RegisterUsernamePasswordButtonText string
ExternalLoginDescription string
LoginButtonText string
}
type RegistrationUserScreenText struct {
Title string
Description string
DescriptionOrgRegister string
FirstnameLabel string
LastnameLabel string
EmailLabel string
UsernameLabel string
LanguageLabel string
GenderLabel string
PasswordLabel string
PasswordConfirmLabel string
TOSAndPrivacyLabel string
TOSConfirm string
TOSLinkText string
PrivacyConfirm string
PrivacyLinkText string
NextButtonText string
BackButtonText string
}
type ExternalRegistrationUserOverviewScreenText struct {
Title string
Description string
EmailLabel string
UsernameLabel string
FirstnameLabel string
LastnameLabel string
NicknameLabel string
LanguageLabel string
PhoneLabel string
TOSAndPrivacyLabel string
TOSConfirm string
TOSLinkText string
PrivacyConfirm string
PrivacyLinkText string
BackButtonText string
NextButtonText string
}
type RegistrationOrgScreenText struct {
Title string
Description string
OrgNameLabel string
FirstnameLabel string
LastnameLabel string
UsernameLabel string
EmailLabel string
PasswordLabel string
PasswordConfirmLabel string
TOSAndPrivacyLabel string
TOSConfirm string
TOSLinkText string
PrivacyConfirm string
PrivacyLinkText string
SaveButtonText string
}
type LinkingUserDoneScreenText struct {
Title string
Description string
CancelButtonText string
NextButtonText string
}
type ExternalUserNotFoundScreenText struct {
Title string
Description string
LinkButtonText string
AutoRegisterButtonText string
TOSAndPrivacyLabel string
TOSConfirm string
TOSLinkText string
PrivacyConfirm string
PrivacyLinkText string
}
type SuccessLoginScreenText struct {
Title string
AutoRedirectDescription string
RedirectedDescription string
NextButtonText string
}
type LogoutDoneScreenText struct {
Title string
Description string
LoginButtonText string
}
type FooterText struct {
TOS string
PrivacyPolicy string
Help string
SupportEmail string
}
type PasswordlessPromptScreenText struct {
Title string
Description string
DescriptionInit string
PasswordlessButtonText string
NextButtonText string
SkipButtonText string
}
type PasswordlessRegistrationScreenText struct {
Title string
Description string
RegisterTokenButtonText string
TokenNameLabel string
NotSupported string
ErrorRetry string
}
type PasswordlessRegistrationDoneScreenText struct {
Title string
Description string
DescriptionClose string
NextButtonText string
CancelButtonText string
}

View File

@@ -0,0 +1,67 @@
package domain
import (
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/zerrors"
)
const (
InitCodeMessageType = "InitCode"
PasswordResetMessageType = "PasswordReset"
VerifyEmailMessageType = "VerifyEmail"
VerifyPhoneMessageType = "VerifyPhone"
VerifySMSOTPMessageType = "VerifySMSOTP"
VerifyEmailOTPMessageType = "VerifyEmailOTP"
DomainClaimedMessageType = "DomainClaimed"
PasswordlessRegistrationMessageType = "PasswordlessRegistration"
PasswordChangeMessageType = "PasswordChange"
InviteUserMessageType = "InviteUser"
MessageTitle = "Title"
MessagePreHeader = "PreHeader"
MessageSubject = "Subject"
MessageGreeting = "Greeting"
MessageText = "Text"
MessageButtonText = "ButtonText"
MessageFooterText = "Footer"
)
type CustomMessageText struct {
models.ObjectRoot
State PolicyState
Default bool
MessageTextType string
Language language.Tag
Title string
PreHeader string
Subject string
Greeting string
Text string
ButtonText string
FooterText string
}
func (m *CustomMessageText) IsValid(supportedLanguages []language.Tag) error {
if m.MessageTextType == "" {
return zerrors.ThrowInvalidArgument(nil, "INSTANCE-kd9fs", "Errors.CustomMessageText.Invalid")
}
if err := LanguageIsDefined(m.Language); err != nil {
return err
}
return LanguagesAreSupported(supportedLanguages, m.Language)
}
func IsMessageTextType(textType string) bool {
return textType == InitCodeMessageType ||
textType == PasswordResetMessageType ||
textType == VerifyEmailMessageType ||
textType == VerifyPhoneMessageType ||
textType == VerifySMSOTPMessageType ||
textType == VerifyEmailOTPMessageType ||
textType == DomainClaimedMessageType ||
textType == PasswordlessRegistrationMessageType ||
textType == PasswordChangeMessageType ||
textType == InviteUserMessageType
}

View File

@@ -0,0 +1,32 @@
package domain
import (
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type CustomText struct {
models.ObjectRoot
State CustomTextState
Default bool
Template string
Key string
Language language.Tag
Text string
}
type CustomTextState int32
const (
CustomTextStateUnspecified CustomTextState = iota
CustomTextStateActive
CustomTextStateRemoved
customTextStateCount
)
func (m *CustomText) IsValid() bool {
return m.Key != "" && m.Language != language.Und && m.Text != ""
}

View File

@@ -0,0 +1,14 @@
package domain
type DebugEventsState int
const (
DebugEventsStateUnspecified DebugEventsState = iota
DebugEventsStateInitial
DebugEventsStateChanged
DebugEventsStateRemoved
)
func (state DebugEventsState) Exists() bool {
return state == DebugEventsStateInitial || state == DebugEventsStateChanged
}

View File

@@ -0,0 +1,68 @@
package domain
import (
"strconv"
)
// DeviceAuthState describes the step the
// the device authorization process is in.
// We generate the Stringer implementation for prettier
// log output.
//
//go:generate stringer -type=DeviceAuthState -linecomment
type DeviceAuthState uint
const (
DeviceAuthStateUndefined DeviceAuthState = iota // undefined
DeviceAuthStateInitiated // initiated
DeviceAuthStateApproved // approved
DeviceAuthStateDenied // denied
DeviceAuthStateExpired // expired
DeviceAuthStateDone // done
deviceAuthStateCount // invalid
)
// Exists returns true when not Undefined and
// any status lower than deviceAuthStateCount.
func (s DeviceAuthState) Exists() bool {
return s > DeviceAuthStateUndefined && s < deviceAuthStateCount
}
// Done returns true when DeviceAuthState is Approved.
// This implements the OIDC interface requirement of "Done"
func (s DeviceAuthState) Done() bool {
return s == DeviceAuthStateApproved
}
// Denied returns true when DeviceAuthState is Denied, Expired or Removed.
// This implements the OIDC interface requirement of "Denied".
func (s DeviceAuthState) Denied() bool {
return s >= DeviceAuthStateDenied
}
func (s DeviceAuthState) GoString() string {
return strconv.Itoa(int(s))
}
// DeviceAuthCanceled is a subset of DeviceAuthState, allowed to
// be used in the deviceauth.CanceledEvent.
// The string type is used to make the eventstore more readable
// on the reason of cancelation.
type DeviceAuthCanceled string
const (
DeviceAuthCanceledDenied = "denied"
DeviceAuthCanceledExpired = "expired"
)
func (c DeviceAuthCanceled) State() DeviceAuthState {
switch c {
case DeviceAuthCanceledDenied:
return DeviceAuthStateDenied
case DeviceAuthCanceledExpired:
return DeviceAuthStateExpired
default:
return DeviceAuthStateUndefined
}
}

View File

@@ -0,0 +1,150 @@
package domain
import (
"testing"
)
func TestDeviceAuthState_Exists(t *testing.T) {
tests := []struct {
s DeviceAuthState
want bool
}{
{
s: DeviceAuthStateUndefined,
want: false,
},
{
s: DeviceAuthStateInitiated,
want: true,
},
{
s: DeviceAuthStateApproved,
want: true,
},
{
s: DeviceAuthStateDenied,
want: true,
},
{
s: DeviceAuthStateExpired,
want: true,
},
{
s: deviceAuthStateCount,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.s.String(), func(t *testing.T) {
if got := tt.s.Exists(); got != tt.want {
t.Errorf("DeviceAuthState.Exists() = %v, want %v", got, tt.want)
}
})
}
}
func TestDeviceAuthState_Done(t *testing.T) {
tests := []struct {
s DeviceAuthState
want bool
}{
{
s: DeviceAuthStateUndefined,
want: false,
},
{
s: DeviceAuthStateInitiated,
want: false,
},
{
s: DeviceAuthStateApproved,
want: true,
},
{
s: DeviceAuthStateDenied,
want: false,
},
{
s: DeviceAuthStateExpired,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.s.String(), func(t *testing.T) {
if got := tt.s.Done(); got != tt.want {
t.Errorf("DeviceAuthState.Done() = %v, want %v", got, tt.want)
}
})
}
}
func TestDeviceAuthState_Denied(t *testing.T) {
tests := []struct {
name string
s DeviceAuthState
want bool
}{
{
s: DeviceAuthStateUndefined,
want: false,
},
{
s: DeviceAuthStateInitiated,
want: false,
},
{
s: DeviceAuthStateApproved,
want: false,
},
{
s: DeviceAuthStateDenied,
want: true,
},
{
s: DeviceAuthStateExpired,
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.s.Denied(); got != tt.want {
t.Errorf("DeviceAuthState.Denied() = %v, want %v", got, tt.want)
}
})
}
}
func TestDeviceAuthCanceled_State(t *testing.T) {
tests := []struct {
name string
c DeviceAuthCanceled
want DeviceAuthState
}{
{
name: "empty",
want: DeviceAuthStateUndefined,
},
{
name: "invalid",
c: "foo",
want: DeviceAuthStateUndefined,
},
{
name: "denied",
c: DeviceAuthCanceledDenied,
want: DeviceAuthStateDenied,
},
{
name: "expired",
c: DeviceAuthCanceledExpired,
want: DeviceAuthStateExpired,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.c.State(); got != tt.want {
t.Errorf("DeviceAuthCanceled.State() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -0,0 +1,29 @@
// Code generated by "stringer -type=DeviceAuthState -linecomment"; DO NOT EDIT.
package domain
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[DeviceAuthStateUndefined-0]
_ = x[DeviceAuthStateInitiated-1]
_ = x[DeviceAuthStateApproved-2]
_ = x[DeviceAuthStateDenied-3]
_ = x[DeviceAuthStateExpired-4]
_ = x[DeviceAuthStateDone-5]
_ = x[deviceAuthStateCount-6]
}
const _DeviceAuthState_name = "undefinedinitiatedapproveddeniedexpireddoneinvalid"
var _DeviceAuthState_index = [...]uint8{0, 9, 18, 26, 32, 39, 43, 50}
func (i DeviceAuthState) String() string {
if i >= DeviceAuthState(len(_DeviceAuthState_index)-1) {
return "DeviceAuthState(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _DeviceAuthState_name[_DeviceAuthState_index[i]:_DeviceAuthState_index[i+1]]
}

View File

@@ -0,0 +1,47 @@
package domain
type ExecutionType uint
func (s ExecutionType) Valid() bool {
return s < executionTypeStateCount
}
const (
ExecutionTypeUnspecified ExecutionType = iota
ExecutionTypeRequest
ExecutionTypeResponse
ExecutionTypeFunction
ExecutionTypeEvent
executionTypeStateCount
)
func (e ExecutionType) String() string {
switch e {
case ExecutionTypeUnspecified, executionTypeStateCount:
return ""
case ExecutionTypeRequest:
return "request"
case ExecutionTypeResponse:
return "response"
case ExecutionTypeFunction:
return "function"
case ExecutionTypeEvent:
return "event"
}
return ""
}
type ExecutionTargetType uint
func (s ExecutionTargetType) Valid() bool {
return s < executionTargetTypeStateCount
}
const (
ExecutionTargetTypeUnspecified ExecutionTargetType = iota
ExecutionTargetTypeInclude
ExecutionTargetTypeTarget
executionTargetTypeStateCount
)

View File

@@ -0,0 +1,36 @@
package domain
import (
"time"
"github.com/zitadel/zitadel/internal/zerrors"
)
var (
//most of us won't survive until 12-31-9999 23:59:59, maybe ZITADEL does
defaultExpDate = time.Date(9999, time.December, 31, 23, 59, 59, 0, time.UTC)
)
type expiration interface {
GetExpirationDate() time.Time
SetExpirationDate(time.Time)
}
func EnsureValidExpirationDate(key expiration) error {
date, err := ValidateExpirationDate(key.GetExpirationDate())
if err != nil {
return err
}
key.SetExpirationDate(date)
return nil
}
func ValidateExpirationDate(date time.Time) (time.Time, error) {
if date.IsZero() {
return defaultExpDate, nil
}
if date.Before(time.Now()) {
return time.Time{}, zerrors.ThrowInvalidArgument(nil, "DOMAIN-dv3t5", "Errors.AuthNKey.ExpireBeforeNow")
}
return date, nil
}

View File

@@ -0,0 +1,44 @@
package domain
type SecondFactorType int32
const (
SecondFactorTypeUnspecified SecondFactorType = iota
SecondFactorTypeTOTP
SecondFactorTypeU2F
SecondFactorTypeOTPEmail
SecondFactorTypeOTPSMS
secondFactorCount
)
type MultiFactorType int32
const (
MultiFactorTypeUnspecified MultiFactorType = iota
MultiFactorTypeU2FWithPIN
multiFactorCount
)
type FactorState int32
const (
FactorStateUnspecified FactorState = iota
FactorStateActive
FactorStateRemoved
factorStateCount
)
func (f SecondFactorType) Valid() bool {
return f > 0 && f < secondFactorCount
}
func (f MultiFactorType) Valid() bool {
return f > 0 && f < multiFactorCount
}
func (f FactorState) Valid() bool {
return f >= 0 && f < factorStateCount
}

View File

@@ -0,0 +1,26 @@
package domain
type Feature int
func (f Feature) Type() FeatureType {
switch f {
case FeatureUnspecified:
return FeatureTypeUnspecified
case FeatureLoginDefaultOrg:
return FeatureTypeBoolean
default:
return FeatureTypeUnspecified
}
}
const (
FeatureTypeUnspecified FeatureType = iota
FeatureTypeBoolean
)
type FeatureType int
const (
FeatureUnspecified Feature = iota
FeatureLoginDefaultOrg
)

View File

@@ -0,0 +1,37 @@
package federatedlogout
type Index int
const (
IndexUnspecified Index = iota
IndexRequestID
)
type FederatedLogout struct {
InstanceID string
FingerPrintID string
SessionID string
IDPID string
UserID string
PostLogoutRedirectURI string
State State
}
// Keys implements cache.Entry
func (c *FederatedLogout) Keys(i Index) []string {
if i == IndexRequestID {
return []string{Key(c.InstanceID, c.SessionID)}
}
return nil
}
func Key(instanceID, sessionID string) string {
return instanceID + "-" + sessionID
}
type State int
const (
StateCreated State = iota
StateRedirected
)

View File

@@ -0,0 +1,151 @@
package domain
import (
"strconv"
)
type FlowState int32
const (
FlowStateActive FlowState = iota
FlowStateInactive
flowStateCount
)
func (s FlowState) Valid() bool {
return s >= 0 && s < flowStateCount
}
type FlowType int32
const (
FlowTypeUnspecified FlowType = iota
FlowTypeExternalAuthentication
FlowTypeCustomiseToken
FlowTypeInternalAuthentication
FlowTypeCustomizeSAMLResponse
flowTypeCount
)
func AllFlowTypes() []FlowType {
return []FlowType{
FlowTypeExternalAuthentication,
FlowTypeCustomiseToken,
FlowTypeInternalAuthentication,
FlowTypeCustomizeSAMLResponse,
}
}
func (s FlowType) Valid() bool {
return s > 0 && s < flowTypeCount
}
func (s FlowType) HasTrigger(triggerType TriggerType) bool {
for _, trigger := range s.TriggerTypes() {
if trigger == triggerType {
return true
}
}
return false
}
func (s FlowType) TriggerTypes() []TriggerType {
switch s {
case FlowTypeExternalAuthentication:
return []TriggerType{
TriggerTypePostAuthentication,
TriggerTypePreCreation,
TriggerTypePostCreation,
}
case FlowTypeCustomiseToken:
return []TriggerType{
TriggerTypePreUserinfoCreation,
TriggerTypePreAccessTokenCreation,
}
case FlowTypeInternalAuthentication:
return []TriggerType{
TriggerTypePostAuthentication,
TriggerTypePreCreation,
TriggerTypePostCreation,
}
case FlowTypeCustomizeSAMLResponse:
return []TriggerType{
TriggerTypePreSAMLResponseCreation,
}
default:
return nil
}
}
func (s FlowType) ID() string {
if s < 0 && s >= flowTypeCount {
return FlowTypeUnspecified.ID()
}
return strconv.Itoa(int(s))
}
func (s FlowType) LocalizationKey() string {
if s < 0 && s >= flowTypeCount {
return FlowTypeUnspecified.LocalizationKey()
}
switch s {
case FlowTypeExternalAuthentication:
return "Action.Flow.Type.ExternalAuthentication"
case FlowTypeCustomiseToken:
return "Action.Flow.Type.CustomiseToken"
case FlowTypeInternalAuthentication:
return "Action.Flow.Type.InternalAuthentication"
case FlowTypeCustomizeSAMLResponse:
return "Action.Flow.Type.CustomizeSAMLResponse"
default:
return "Action.Flow.Type.Unspecified"
}
}
type TriggerType int32
const (
TriggerTypeUnspecified TriggerType = iota
TriggerTypePostAuthentication
TriggerTypePreCreation
TriggerTypePostCreation
TriggerTypePreUserinfoCreation
TriggerTypePreAccessTokenCreation
TriggerTypePreSAMLResponseCreation
triggerTypeCount
)
func (s TriggerType) Valid() bool {
return s >= 0 && s < triggerTypeCount
}
func (s TriggerType) ID() string {
if !s.Valid() {
return TriggerTypeUnspecified.ID()
}
return strconv.Itoa(int(s))
}
func (s TriggerType) LocalizationKey() string {
if !s.Valid() {
return FlowTypeUnspecified.LocalizationKey()
}
switch s {
case TriggerTypePostAuthentication:
return "Action.TriggerType.PostAuthentication"
case TriggerTypePreCreation:
return "Action.TriggerType.PreCreation"
case TriggerTypePostCreation:
return "Action.TriggerType.PostCreation"
case TriggerTypePreUserinfoCreation:
return "Action.TriggerType.PreUserinfoCreation"
case TriggerTypePreAccessTokenCreation:
return "Action.TriggerType.PreAccessTokenCreation"
case TriggerTypePreSAMLResponseCreation:
return "Action.TriggerType.PreSAMLResponseCreation"
default:
return "Action.TriggerType.Unspecified"
}
}

View File

@@ -0,0 +1,142 @@
package domain
import (
"strings"
"time"
"golang.org/x/net/context"
"github.com/zitadel/zitadel/internal/crypto"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
)
type Human struct {
es_models.ObjectRoot
Username string
State UserState
*Password
HashedPassword string
*Profile
*Email
*Phone
*Address
}
func (h Human) GetUsername() string {
return h.Username
}
func (h Human) GetState() UserState {
return h.State
}
type InitUserCode struct {
es_models.ObjectRoot
Code *crypto.CryptoValue
Expiry time.Duration
}
type Gender int32
const (
GenderUnspecified Gender = iota
GenderFemale
GenderMale
GenderDiverse
genderCount
)
func (f Gender) Valid() bool {
return f >= 0 && f < genderCount
}
func (f Gender) Specified() bool {
return f > GenderUnspecified && f < genderCount
}
func (u *Human) Normalize() error {
if u.Username == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-00p2b", "Errors.User.Username.Empty")
}
if err := u.Profile.Validate(); err != nil {
return err
}
if err := u.Email.Validate(); err != nil {
return err
}
if u.Phone != nil && u.Phone.PhoneNumber != "" {
if err := u.Phone.Normalize(); err != nil {
return err
}
}
return nil
}
func (u *Human) CheckDomainPolicy(policy *DomainPolicy) error {
if policy == nil {
return zerrors.ThrowPreconditionFailed(nil, "DOMAIN-zSH7j", "Errors.Users.DomainPolicyNil")
}
if !policy.UserLoginMustBeDomain && u.Profile != nil && u.Username == "" && u.Email != nil {
u.Username = string(u.EmailAddress)
}
return nil
}
func (u *Human) EnsureDisplayName() {
if u.Profile == nil {
u.Profile = new(Profile)
}
if u.DisplayName != "" {
return
}
if u.FirstName != "" && u.LastName != "" {
u.DisplayName = u.FirstName + " " + u.LastName
return
}
if u.Email != nil && strings.TrimSpace(string(u.Email.EmailAddress)) != "" {
u.DisplayName = string(u.Email.EmailAddress)
return
}
u.DisplayName = u.Username
}
func (u *Human) HashPasswordIfExisting(ctx context.Context, policy *PasswordComplexityPolicy, hasher *crypto.Hasher, onetime bool) (err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
if u.Password != nil {
u.Password.ChangeRequired = onetime
return u.Password.HashPasswordIfExisting(ctx, policy, hasher)
}
return nil
}
func (u *Human) IsInitialState(passwordless, externalIDPs bool) bool {
if externalIDPs {
return false
}
return u.Email == nil || !u.IsEmailVerified || !passwordless && (u.Password == nil || u.Password.SecretString == "") && u.HashedPassword == ""
}
func NewInitUserCode(generator crypto.Generator) (*InitUserCode, error) {
initCodeCrypto, _, err := crypto.NewCode(generator)
if err != nil {
return nil, err
}
return &InitUserCode{
Code: initCodeCrypto,
Expiry: generator.Expiry(),
}, nil
}
func GenerateLoginName(username, domain string, appendDomain bool) string {
if !appendDomain {
return username
}
return username + "@" + domain
}

View File

@@ -0,0 +1,27 @@
package domain
import es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
type Address struct {
es_models.ObjectRoot
Country string
Locality string
PostalCode string
Region string
StreetAddress string
}
type AddressState int32
const (
AddressStateUnspecified AddressState = iota
AddressStateActive
AddressStateRemoved
addressStateCount
)
func (s AddressState) Valid() bool {
return s >= 0 && s < addressStateCount
}

View File

@@ -0,0 +1,78 @@
package domain
import (
"io"
"regexp"
"strings"
"time"
"github.com/zitadel/zitadel/internal/crypto"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/zerrors"
)
var (
emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
)
type EmailAddress string
func (e EmailAddress) Validate() error {
if e == "" {
return zerrors.ThrowInvalidArgument(nil, "EMAIL-spblu", "Errors.User.Email.Empty")
}
if !emailRegex.MatchString(string(e)) {
return zerrors.ThrowInvalidArgument(nil, "EMAIL-599BI", "Errors.User.Email.Invalid")
}
return nil
}
func (e EmailAddress) Normalize() EmailAddress {
return EmailAddress(strings.TrimSpace(string(e)))
}
type Email struct {
es_models.ObjectRoot
EmailAddress EmailAddress
IsEmailVerified bool
// PlainCode is set by the command and can be used to return it to the caller (API)
PlainCode *string
}
type EmailCode struct {
es_models.ObjectRoot
Code *crypto.CryptoValue
Expiry time.Duration
}
func (e *Email) Validate() error {
if e == nil {
return zerrors.ThrowInvalidArgument(nil, "EMAIL-spblu", "Errors.User.Email.Empty")
}
return e.EmailAddress.Validate()
}
func NewEmailCode(emailGenerator crypto.Generator) (*EmailCode, string, error) {
emailCodeCrypto, code, err := crypto.NewCode(emailGenerator)
if err != nil {
return nil, "", err
}
return &EmailCode{
Code: emailCodeCrypto,
Expiry: emailGenerator.Expiry(),
}, code, nil
}
type ConfirmURLData struct {
UserID string
Code string
OrgID string
}
// RenderConfirmURLTemplate parses and renders tmpl.
// userID, code and orgID are passed into the [ConfirmURLData].
func RenderConfirmURLTemplate(w io.Writer, tmpl, userID, code, orgID string) error {
return renderURLTemplate(w, tmpl, &ConfirmURLData{userID, code, orgID})
}

View File

@@ -0,0 +1,134 @@
package domain
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestEmailValid(t *testing.T) {
type args struct {
email *Email
}
tests := []struct {
name string
args args
result bool
}{
{
name: "empty email, invalid",
args: args{
email: &Email{},
},
result: false,
},
{
name: "only letters email, invalid",
args: args{
email: &Email{EmailAddress: "testemail"},
},
result: false,
},
{
name: "nothing after @, invalid",
args: args{
email: &Email{EmailAddress: "testemail@"},
},
result: false,
},
{
name: "email, valid",
args: args{
email: &Email{EmailAddress: "testemail@gmail.com"},
},
result: true,
},
{
name: "email, valid",
args: args{
email: &Email{EmailAddress: "test.email@gmail.com"},
},
result: true,
},
{
name: "email, valid",
args: args{
email: &Email{EmailAddress: "test/email@gmail.com"},
},
result: true,
},
{
name: "email, valid",
args: args{
email: &Email{EmailAddress: "test/email@gmail.com"},
},
result: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.args.email.Validate() == nil
if result != tt.result {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, result)
}
})
}
}
func TestRenderConfirmURLTemplate(t *testing.T) {
type args struct {
tmpl string
userID string
code string
orgID string
}
tests := []struct {
name string
args args
want string
wantErr error
}{
{
name: "invalid template",
args: args{
tmpl: "{{",
userID: "user1",
code: "123",
orgID: "org1",
},
wantErr: zerrors.ThrowInvalidArgument(nil, "DOMAIN-oGh5e", "Errors.User.InvalidURLTemplate"),
},
{
name: "execution error",
args: args{
tmpl: "{{.Foo}}",
userID: "user1",
code: "123",
orgID: "org1",
},
wantErr: zerrors.ThrowInvalidArgument(nil, "DOMAIN-ieYa7", "Errors.User.InvalidURLTemplate"),
},
{
name: "success",
args: args{
tmpl: "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}",
userID: "user1",
code: "123",
orgID: "org1",
},
want: "https://example.com/email/verify?userID=user1&code=123&orgID=org1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var w strings.Builder
err := RenderConfirmURLTemplate(&w, tt.args.tmpl, tt.args.userID, tt.args.code, tt.args.orgID)
require.ErrorIs(t, err, tt.wantErr)
assert.Equal(t, tt.want, w.String())
})
}
}

View File

@@ -0,0 +1,37 @@
package domain
import (
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/zerrors"
)
type TOTP struct {
*ObjectDetails
Secret string
URI string
}
func NewTOTPKey(issuer, accountName string) (*otp.Key, error) {
key, err := totp.Generate(totp.GenerateOpts{Issuer: issuer, AccountName: accountName})
if err != nil {
return nil, zerrors.ThrowInternal(err, "TOTP-ieY3o", "Errors.Internal")
}
return key, nil
}
func VerifyTOTP(code string, secret *crypto.CryptoValue, cryptoAlg crypto.EncryptionAlgorithm) error {
decrypt, err := crypto.DecryptString(secret, cryptoAlg)
if err != nil {
return err
}
valid := totp.Validate(code, decrypt)
if !valid {
return zerrors.ThrowInvalidArgument(nil, "EVENT-8isk2", "Errors.User.MFA.OTP.InvalidCode")
}
return nil
}

View File

@@ -0,0 +1,64 @@
package domain
import (
"context"
"time"
"github.com/zitadel/zitadel/internal/crypto"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
)
type Password struct {
es_models.ObjectRoot
SecretString string
EncodedSecret string
ChangeRequired bool
}
func NewPassword(password string) *Password {
return &Password{
SecretString: password,
}
}
type PasswordCode struct {
es_models.ObjectRoot
Code *crypto.CryptoValue
Expiry time.Duration
NotificationType NotificationType
}
func (p *Password) HashPasswordIfExisting(ctx context.Context, policy *PasswordComplexityPolicy, hasher *crypto.Hasher) error {
if p.SecretString == "" {
return nil
}
if policy == nil {
return zerrors.ThrowPreconditionFailed(nil, "DOMAIN-s8ifS", "Errors.User.PasswordComplexityPolicy.NotFound")
}
if err := policy.Check(p.SecretString); err != nil {
return err
}
_, spanHash := tracing.NewNamedSpan(ctx, "passwap.Hash")
encoded, err := hasher.Hash(p.SecretString)
spanHash.EndWithError(err)
if err != nil {
return err
}
p.EncodedSecret = encoded
return nil
}
func NewPasswordCode(passwordGenerator crypto.Generator) (*PasswordCode, error) {
passwordCodeCrypto, _, err := crypto.NewCode(passwordGenerator)
if err != nil {
return nil, err
}
return &PasswordCode{
Code: passwordCodeCrypto,
Expiry: passwordGenerator.Expiry(),
}, nil
}

View File

@@ -0,0 +1,73 @@
package domain
import (
"time"
"github.com/ttacon/libphonenumber"
"github.com/zitadel/zitadel/internal/crypto"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/zerrors"
)
const defaultRegion = "CH"
type PhoneNumber string
func (p PhoneNumber) Normalize() (PhoneNumber, error) {
if p == "" {
return p, zerrors.ThrowInvalidArgument(nil, "PHONE-Zt0NV", "Errors.User.Phone.Empty")
}
phoneNr, err := libphonenumber.Parse(string(p), defaultRegion)
if err != nil {
return p, zerrors.ThrowInvalidArgument(err, "PHONE-so0wa", "Errors.User.Phone.Invalid")
}
return PhoneNumber(libphonenumber.Format(phoneNr, libphonenumber.E164)), nil
}
type Phone struct {
es_models.ObjectRoot
PhoneNumber PhoneNumber
IsPhoneVerified bool
// PlainCode is set by the command and can be used to return it to the caller (API)
PlainCode *string
}
type PhoneCode struct {
es_models.ObjectRoot
Code *crypto.CryptoValue
Expiry time.Duration
}
func (p *Phone) Normalize() error {
if p == nil {
return zerrors.ThrowInvalidArgument(nil, "PHONE-YlbwO", "Errors.User.Phone.Empty")
}
normalizedNumber, err := p.PhoneNumber.Normalize()
if err != nil {
return err
}
// Issue for avoiding mutating state: https://github.com/zitadel/zitadel/issues/5412
p.PhoneNumber = normalizedNumber
return nil
}
type PhoneState int32
const (
PhoneStateUnspecified PhoneState = iota
PhoneStateActive
PhoneStateRemoved
phoneStateCount
)
func (s PhoneState) Valid() bool {
return s >= 0 && s < phoneStateCount
}
func (s PhoneState) Exists() bool {
return s == PhoneStateActive
}

View File

@@ -0,0 +1,106 @@
package domain
import (
"testing"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestFormatPhoneNumber(t *testing.T) {
type args struct {
phone *Phone
}
tests := []struct {
name string
args args
result *Phone
errFunc func(err error) bool
}{
{
name: "invalid phone number",
args: args{
phone: &Phone{
PhoneNumber: "PhoneNumber",
},
},
errFunc: zerrors.IsErrorInvalidArgument,
},
{
name: "format phone 071...",
args: args{
phone: &Phone{
PhoneNumber: "0711234567",
},
},
result: &Phone{
PhoneNumber: "+41711234567",
},
},
{
name: "format phone 0041...",
args: args{
phone: &Phone{
PhoneNumber: "0041711234567",
},
},
result: &Phone{
PhoneNumber: "+41711234567",
},
},
{
name: "format phone 071 xxx xx xx",
args: args{
phone: &Phone{
PhoneNumber: "071 123 45 67",
},
},
result: &Phone{
PhoneNumber: "+41711234567",
},
},
{
name: "format phone +4171 xxx xx xx",
args: args{
phone: &Phone{
PhoneNumber: "+4171 123 45 67",
},
},
result: &Phone{
PhoneNumber: "+41711234567",
},
},
{
name: "format phone 004171 xxx xx xx",
args: args{
phone: &Phone{
PhoneNumber: "004171 123 45 67",
},
},
result: &Phone{
PhoneNumber: "+41711234567",
},
},
{
name: "format non swiss phone 004371 xxx xx xx",
args: args{
phone: &Phone{
PhoneNumber: "004371 123 45 67",
},
},
result: &Phone{
PhoneNumber: "+43711234567",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
normalized, err := tt.args.phone.PhoneNumber.Normalize()
if tt.errFunc == nil && tt.result.PhoneNumber != normalized {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.PhoneNumber, normalized)
}
if tt.errFunc != nil && !tt.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}

View File

@@ -0,0 +1,41 @@
package domain
import (
"golang.org/x/text/language"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/zerrors"
)
type Profile struct {
es_models.ObjectRoot
FirstName string
LastName string
NickName string
DisplayName string
PreferredLanguage language.Tag
Gender Gender
PreferredLoginName string
LoginNames []string
}
func (p *Profile) Validate() error {
if p == nil {
return zerrors.ThrowInvalidArgument(nil, "PROFILE-GPY3p", "Errors.User.Profile.Empty")
}
if p.FirstName == "" {
return zerrors.ThrowInvalidArgument(nil, "PROFILE-RF5z2", "Errors.User.Profile.FirstNameEmpty")
}
if p.LastName == "" {
return zerrors.ThrowInvalidArgument(nil, "PROFILE-DSUkN", "Errors.User.Profile.LastNameEmpty")
}
return nil
}
func AvatarURL(prefix, resourceOwner, key string) string {
if prefix == "" || resourceOwner == "" || key == "" {
return ""
}
return prefix + "/" + resourceOwner + "/" + key
}

View File

@@ -0,0 +1,78 @@
package domain
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestHuman_EnsureDisplayName(t *testing.T) {
type fields struct {
Username string
Profile *Profile
Email *Email
}
tests := []struct {
name string
fields fields
want string
}{
{
"display name set",
fields{
Username: "username",
Profile: &Profile{
FirstName: "firstName",
LastName: "lastName",
DisplayName: "displayName",
},
Email: &Email{
EmailAddress: "email",
},
},
"displayName",
},
{
"first and lastname set",
fields{
Username: "username",
Profile: &Profile{
FirstName: "firstName",
LastName: "lastName",
},
Email: &Email{
EmailAddress: "email",
},
},
"firstName lastName",
},
{
"profile nil, email set",
fields{
Username: "username",
Email: &Email{
EmailAddress: "email",
},
},
"email",
},
{
"email nil, username set",
fields{
Username: "username",
},
"username",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
u := &Human{
Username: tt.fields.Username,
Profile: tt.fields.Profile,
Email: tt.fields.Email,
}
u.EnsureDisplayName()
assert.Equal(t, tt.want, u.DisplayName)
})
}
}

View File

@@ -0,0 +1,102 @@
package domain
import (
"bytes"
"fmt"
"time"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type WebAuthNToken struct {
es_models.ObjectRoot
WebAuthNTokenID string
CredentialCreationData []byte
State MFAState
Challenge string
AllowedCredentialIDs [][]byte
UserVerification UserVerificationRequirement
KeyID []byte
PublicKey []byte
AttestationType string
AAGUID []byte
SignCount uint32
WebAuthNTokenName string
RPID string
}
type WebAuthNLogin struct {
es_models.ObjectRoot
CredentialAssertionData []byte
Challenge string
AllowedCredentialIDs [][]byte
UserVerification UserVerificationRequirement
RPID string
}
type UserVerificationRequirement int32
const (
UserVerificationRequirementUnspecified UserVerificationRequirement = iota
UserVerificationRequirementRequired
UserVerificationRequirementPreferred
UserVerificationRequirementDiscouraged
)
type AuthenticatorAttachment int32
const (
AuthenticatorAttachmentUnspecified AuthenticatorAttachment = iota
AuthenticatorAttachmentPlattform
AuthenticatorAttachmentCrossPlattform
)
func GetTokenToVerify(tokens []*WebAuthNToken) (int, *WebAuthNToken) {
for i, u2f := range tokens {
if u2f.State == MFAStateNotReady {
return i, u2f
}
}
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
}
type PasswordlessInitCodeState int32
const (
PasswordlessInitCodeStateUnspecified PasswordlessInitCodeState = iota
PasswordlessInitCodeStateRequested
PasswordlessInitCodeStateActive
PasswordlessInitCodeStateRemoved
)
type PasswordlessInitCode struct {
es_models.ObjectRoot
CodeID string
Code string
Expiration time.Duration
State PasswordlessInitCodeState
}
func (p *PasswordlessInitCode) Link(baseURL string) string {
return PasswordlessInitCodeLink(baseURL, p.AggregateID, p.ResourceOwner, p.CodeID, p.Code)
}
func PasswordlessInitCodeLink(baseURL, userID, resourceOwner, codeID, code string) string {
return fmt.Sprintf("%s?userID=%s&orgID=%s&codeID=%s&code=%s", baseURL, userID, resourceOwner, codeID, code)
}
func PasswordlessInitCodeLinkTemplate(baseURL, userID, resourceOwner, codeID string) string {
return PasswordlessInitCodeLink(baseURL, userID, resourceOwner, codeID, "{{.Code}}")
}

View File

@@ -0,0 +1,146 @@
package domain
import "github.com/zitadel/logging"
type IDPState int32
const (
IDPStateUnspecified IDPState = iota
IDPStateActive
IDPStateInactive
IDPStateRemoved
IDPStateMigrated
idpStateCount
)
func (s IDPState) Valid() bool {
return s >= 0 && s < idpStateCount
}
func (s IDPState) Exists() bool {
return s != IDPStateUnspecified && s != IDPStateRemoved && s != IDPStateMigrated
}
type IDPType int32
const (
IDPTypeUnspecified IDPType = iota
IDPTypeOIDC
IDPTypeJWT
IDPTypeOAuth
IDPTypeLDAP
IDPTypeAzureAD
IDPTypeGitHub
IDPTypeGitHubEnterprise
IDPTypeGitLab
IDPTypeGitLabSelfHosted
IDPTypeGoogle
IDPTypeApple
IDPTypeSAML
)
func (t IDPType) GetCSSClass() string {
switch t {
case IDPTypeGoogle:
return "google"
case IDPTypeGitHub,
IDPTypeGitHubEnterprise:
return "github"
case IDPTypeGitLab,
IDPTypeGitLabSelfHosted:
return "gitlab"
case IDPTypeAzureAD:
return "azure"
case IDPTypeApple:
return "apple"
case IDPTypeUnspecified,
IDPTypeOIDC,
IDPTypeJWT,
IDPTypeOAuth,
IDPTypeLDAP,
IDPTypeSAML:
fallthrough
default:
return ""
}
}
func IDPName(name string, idpType IDPType) string {
if name != "" {
return name
}
return idpType.DisplayName()
}
// DisplayName returns the name or a default
// to be used when always a name must be displayed (e.g. login)
func (t IDPType) DisplayName() string {
switch t {
case IDPTypeGitHub:
return "GitHub"
case IDPTypeGitLab:
return "GitLab"
case IDPTypeGoogle:
return "Google"
case IDPTypeApple:
return "Apple"
case IDPTypeUnspecified,
IDPTypeOIDC,
IDPTypeJWT,
IDPTypeOAuth,
IDPTypeLDAP,
IDPTypeAzureAD,
IDPTypeGitHubEnterprise,
IDPTypeGitLabSelfHosted,
IDPTypeSAML:
fallthrough
default:
// we should never get here, so log it
logging.Errorf("name of provider (type %d) is empty", t)
return ""
}
}
// IsSignInButton returns if the button should be displayed with a translated
// "Sign in with {{.DisplayName}}", e.g. "Sign in with Apple"
func (t IDPType) IsSignInButton() bool {
return t == IDPTypeApple
}
type IDPIntentState int32
const (
IDPIntentStateUnspecified IDPIntentState = iota
IDPIntentStateStarted
IDPIntentStateSucceeded
IDPIntentStateFailed
IDPIntentStateConsumed
idpIntentStateCount
)
func (s IDPIntentState) Valid() bool {
return s >= 0 && s < idpIntentStateCount
}
func (s IDPIntentState) Exists() bool {
return s != IDPIntentStateUnspecified && s != IDPIntentStateFailed //TODO: ?
}
type AutoLinkingOption uint8
const (
AutoLinkingOptionUnspecified AutoLinkingOption = iota
AutoLinkingOptionUsername
AutoLinkingOptionEmail
)
type SAMLNameIDFormat uint8
const (
SAMLNameIDFormatUnspecified SAMLNameIDFormat = iota
SAMLNameIDFormatEmailAddress
SAMLNameIDFormatPersistent
SAMLNameIDFormatTransient
)

View File

@@ -0,0 +1,133 @@
package domain
import (
"time"
"github.com/zitadel/zitadel/internal/crypto"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type IDPConfig struct {
es_models.ObjectRoot
IDPConfigID string
Type IDPConfigType
Name string
StylingType IDPConfigStylingType
State IDPConfigState
OIDCConfig *OIDCIDPConfig
JWTConfig *JWTIDPConfig
AutoRegister bool
}
type IDPConfigView struct {
AggregateID string
IDPConfigID string
Name string
StylingType IDPConfigStylingType
State IDPConfigState
CreationDate time.Time
ChangeDate time.Time
Sequence uint64
IDPProviderType IdentityProviderType
AutoRegister bool
IsOIDC bool
OIDCClientID string
OIDCClientSecret *crypto.CryptoValue
OIDCIssuer string
OIDCScopes []string
OIDCIDPDisplayNameMapping OIDCMappingField
OIDCUsernameMapping OIDCMappingField
OAuthAuthorizationEndpoint string
OAuthTokenEndpoint string
JWTEndpoint string
JWTIssuer string
JWTKeysEndpoint string
}
type OIDCIDPConfig struct {
es_models.ObjectRoot
IDPConfigID string
ClientID string
ClientSecret *crypto.CryptoValue
ClientSecretString string
Issuer string
AuthorizationEndpoint string
TokenEndpoint string
Scopes []string
IDPDisplayNameMapping OIDCMappingField
UsernameMapping OIDCMappingField
}
type JWTIDPConfig struct {
es_models.ObjectRoot
IDPConfigID string
JWTEndpoint string
Issuer string
KeysEndpoint string
HeaderName string
}
// IDPConfigType
// Deprecated: use [IDPType]
type IDPConfigType int32
const (
IDPConfigTypeOIDC IDPConfigType = iota
IDPConfigTypeSAML
IDPConfigTypeJWT
//count is for validation
idpConfigTypeCount
IDPConfigTypeUnspecified IDPConfigType = -1
)
func (f IDPConfigType) Valid() bool {
return f >= 0 && f < idpConfigTypeCount
}
// IDPConfigState
// Deprecated: use [IDPStateType]
type IDPConfigState int32
const (
IDPConfigStateUnspecified IDPConfigState = iota
IDPConfigStateActive
IDPConfigStateInactive
IDPConfigStateRemoved
idpConfigStateCount
)
func (s IDPConfigState) Valid() bool {
return s >= 0 && s < idpConfigStateCount
}
func (s IDPConfigState) Exists() bool {
return s != IDPConfigStateUnspecified && s != IDPConfigStateRemoved
}
// IDPConfigStylingType
// Deprecated: use a concrete provider
type IDPConfigStylingType int32
const (
IDPConfigStylingTypeUnspecified IDPConfigStylingType = iota
IDPConfigStylingTypeGoogle
idpConfigStylingTypeCount
)
func (f IDPConfigStylingType) Valid() bool {
return f >= 0 && f < idpConfigStylingTypeCount
}
func (st IDPConfigStylingType) GetCSSClass() string {
switch st {
case IDPConfigStylingTypeGoogle:
return "google"
default:
return ""
}
}

View File

@@ -0,0 +1,23 @@
package domain
const (
IAMID = "IAM"
)
type InstanceState int32
const (
InstanceStateUnspecified InstanceState = iota
InstanceStateActive
InstanceStateRemoved
instanceStateCount
)
func (s InstanceState) Valid() bool {
return s >= 0 && s < instanceStateCount
}
func (s InstanceState) Exists() bool {
return s != InstanceStateUnspecified && s != InstanceStateRemoved
}

View File

@@ -0,0 +1,38 @@
package domain
import (
"strings"
"github.com/zitadel/zitadel/internal/crypto"
)
var (
domainRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
)
type InstanceDomainState int32
const (
InstanceDomainStateUnspecified InstanceDomainState = iota
InstanceDomainStateActive
InstanceDomainStateRemoved
instanceDomainStateCount
)
func (f InstanceDomainState) Valid() bool {
return f >= 0 && f < instanceDomainStateCount
}
func (f InstanceDomainState) Exists() bool {
return f == InstanceDomainStateActive
}
func NewGeneratedInstanceDomain(instanceName, iamDomain string) (string, error) {
randomString, err := crypto.GenerateRandomString(6, domainRunes)
if err != nil {
return "", err
}
instanceName = strings.TrimSpace(instanceName)
return strings.ToLower(strings.ReplaceAll(instanceName, " ", "-") + "-" + randomString + "." + iamDomain), nil
}

View File

@@ -0,0 +1,34 @@
package domain
import (
"time"
"github.com/zitadel/zitadel/internal/crypto"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type KeyPair struct {
es_models.ObjectRoot
Usage crypto.KeyUsage
Algorithm string
PrivateKey *Key
PublicKey *Key
Certificate *Key
}
type Key struct {
Key *crypto.CryptoValue
Expiry time.Time
}
func (k *KeyPair) IsValid() bool {
return k.Algorithm != "" &&
k.PrivateKey != nil && k.PrivateKey.IsValid() &&
k.PublicKey != nil && k.PublicKey.IsValid() &&
k.Certificate != nil && k.Certificate.IsValid()
}
func (k *Key) IsValid() bool {
return k.Key != nil
}

View File

@@ -0,0 +1,130 @@
package domain
import (
"errors"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/zerrors"
)
func StringsToLanguages(langs []string) []language.Tag {
return GenericMapSlice(langs, language.Make)
}
func LanguagesToStrings(langs []language.Tag) []string {
return GenericMapSlice(langs, func(lang language.Tag) string { return lang.String() })
}
func GenericMapSlice[T any, U any](from []T, mapTo func(T) U) []U {
if from == nil {
return nil
}
result := make([]U, len(from))
for i, lang := range from {
result[i] = mapTo(lang)
}
return result
}
// LanguagesDiffer returns true if the languages differ.
func LanguagesDiffer(left, right []language.Tag) bool {
if left == nil && right == nil {
return false
}
if left == nil || right == nil || len(left) != len(right) {
return true
}
return !languagesAreContained(left, right)
}
func LanguageIsAllowed(allowUndefined bool, allowedLanguages []language.Tag, lang language.Tag) error {
err := LanguageIsDefined(lang)
if err != nil && allowUndefined {
return nil
}
if err != nil {
return err
}
if len(allowedLanguages) > 0 && !languageIsContained(allowedLanguages, lang) {
return zerrors.ThrowPreconditionFailed(nil, "LANG-2M9fs", "Errors.Language.NotAllowed")
}
return nil
}
func LanguagesAreSupported(supportedLanguages []language.Tag, lang ...language.Tag) error {
unsupported := make([]language.Tag, 0)
for _, l := range lang {
if l.IsRoot() {
continue
}
if !languageIsContained(supportedLanguages, l) {
unsupported = append(unsupported, l)
}
}
if len(unsupported) == 0 {
return nil
}
if len(unsupported) == 1 {
return zerrors.ThrowInvalidArgument(nil, "LANG-lg4DP", "Errors.Language.NotSupported")
}
return zerrors.ThrowInvalidArgumentf(nil, "LANG-XHiK5", "Errors.Languages.NotSupported: %s", LanguagesToStrings(unsupported))
}
func LanguageIsDefined(lang language.Tag) error {
if lang.IsRoot() {
return zerrors.ThrowInvalidArgument(nil, "LANG-3M9f2", "Errors.Language.Undefined")
}
return nil
}
// LanguagesHaveDuplicates returns an error if the passed slices contains duplicates.
// The error lists the duplicates.
func LanguagesHaveDuplicates(langs []language.Tag) error {
unique := make(map[language.Tag]struct{})
duplicates := make([]language.Tag, 0)
for _, lang := range langs {
if _, ok := unique[lang]; ok {
duplicates = append(duplicates, lang)
}
unique[lang] = struct{}{}
}
if len(duplicates) == 0 {
return nil
}
if len(duplicates) > 1 {
return zerrors.ThrowInvalidArgument(nil, "LANG-3M9f2", "Errors.Language.Duplicate")
}
return zerrors.ThrowInvalidArgumentf(nil, "LANG-XHiK5", "Errors.Languages.Duplicate: %s", LanguagesToStrings(duplicates))
}
func ParseLanguage(lang ...string) (tags []language.Tag, err error) {
tags = make([]language.Tag, len(lang))
for i := range lang {
var parseErr error
tags[i], parseErr = language.Parse(lang[i])
err = errors.Join(err, parseErr)
}
if err != nil {
err = zerrors.ThrowInvalidArgument(err, "LANG-jc8Sq", "Errors.Language.NotParsed")
}
return tags, err
}
func languagesAreContained(languages, search []language.Tag) bool {
for _, s := range search {
if !languageIsContained(languages, s) {
return false
}
}
return true
}
func languageIsContained(languages []language.Tag, search language.Tag) bool {
for _, lang := range languages {
if lang == search {
return true
}
}
return false
}

View File

@@ -0,0 +1,24 @@
package domain
import "github.com/zitadel/zitadel/internal/eventstore/v1/models"
type Machine struct {
models.ObjectRoot
Username string
State UserState
Name string
Description string
}
func (m Machine) GetUsername() string {
return m.Username
}
func (m Machine) GetState() UserState {
return m.State
}
func (sa *Machine) IsValid() bool {
return sa.Name != "" && sa.Username != ""
}

View File

@@ -0,0 +1,76 @@
package domain
import (
"encoding/json"
"time"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/zerrors"
)
type MachineKey struct {
models.ObjectRoot
KeyID string
Type AuthNKeyType
ExpirationDate time.Time
PrivateKey []byte
PublicKey []byte
}
func (key *MachineKey) setPublicKey(publicKey []byte) {
key.PublicKey = publicKey
}
func (key *MachineKey) setPrivateKey(privateKey []byte) {
key.PrivateKey = privateKey
}
func (key *MachineKey) expirationDate() time.Time {
return key.ExpirationDate
}
func (key *MachineKey) setExpirationDate(expiration time.Time) {
key.ExpirationDate = expiration
}
func (key *MachineKey) Detail() ([]byte, error) {
if key.Type == AuthNKeyTypeJSON {
return key.MarshalJSON()
}
return nil, zerrors.ThrowPreconditionFailed(nil, "KEY-dsg52", "Errors.Internal")
}
func (key *MachineKey) MarshalJSON() ([]byte, error) {
return MachineKeyMarshalJSON(key.KeyID, key.PrivateKey, key.ExpirationDate, key.AggregateID)
}
type MachineKeyState int32
const (
MachineKeyStateUnspecified MachineKeyState = iota
MachineKeyStateActive
MachineKeyStateRemoved
machineKeyStateCount
)
func (f MachineKeyState) Valid() bool {
return f >= 0 && f < machineKeyStateCount
}
func MachineKeyMarshalJSON(keyID string, privateKey []byte, expirationDate time.Time, userID string) ([]byte, error) {
return json.Marshal(struct {
Type string `json:"type"`
KeyID string `json:"keyId"`
Key string `json:"key"`
ExpirationDate time.Time `json:"expirationDate"`
UserID string `json:"userId"`
}{
Type: "serviceaccount",
KeyID: keyID,
Key: string(privateKey),
ExpirationDate: expirationDate,
UserID: userID,
})
}

View File

@@ -0,0 +1,44 @@
package domain
import (
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type Member struct {
es_models.ObjectRoot
UserID string
Roles []string
}
func NewMember(aggregateID, userID string, roles ...string) *Member {
return &Member{
ObjectRoot: es_models.ObjectRoot{
AggregateID: aggregateID,
},
UserID: userID,
Roles: roles,
}
}
func (i *Member) IsValid() bool {
return i.AggregateID != "" && i.UserID != "" && len(i.Roles) != 0
}
type MemberState int32
const (
MemberStateUnspecified MemberState = iota
MemberStateActive
MemberStateRemoved
memberStateCount
)
func (f MemberState) Valid() bool {
return f >= 0 && f < memberStateCount
}
func (f MemberState) Exists() bool {
return f != MemberStateRemoved && f != MemberStateUnspecified
}

View File

@@ -0,0 +1,83 @@
package domain
import (
"time"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/zerrors"
)
type Metadata struct {
es_models.ObjectRoot
State MetadataState
Key string
Value []byte
}
type MetadataState int32
const (
MetadataStateUnspecified MetadataState = iota
MetadataStateActive
MetadataStateRemoved
)
func (m *Metadata) IsValid() bool {
return m.Key != "" && len(m.Value) > 0
}
func (s MetadataState) Exists() bool {
return s != MetadataStateUnspecified && s != MetadataStateRemoved
}
type MetadataSearchRequest struct {
Offset uint64
Limit uint64
SortingColumn MetadataSearchKey
Asc bool
Queries []*MetadataSearchQuery
}
type MetadataSearchKey int32
const (
MetadataSearchKeyUnspecified MetadataSearchKey = iota
MetadataSearchKeyAggregateID
MetadataSearchKeyResourceOwner
MetadataSearchKeyKey
MetadataSearchKeyValue
)
type MetadataSearchQuery struct {
Key MetadataSearchKey
Method SearchMethod
Value interface{}
}
type MetadataSearchResponse struct {
Offset uint64
Limit uint64
TotalResult uint64
Result []*Metadata
Sequence uint64
Timestamp time.Time
}
func (r *MetadataSearchRequest) EnsureLimit(limit uint64) error {
if r.Limit > limit {
return zerrors.ThrowInvalidArgument(nil, "SEARCH-0ds32", "Errors.Limit.ExceedsDefault")
}
if r.Limit == 0 {
r.Limit = limit
}
return nil
}
func (r *MetadataSearchRequest) AppendAggregateIDQuery(aggregateID string) {
r.Queries = append(r.Queries, &MetadataSearchQuery{Key: MetadataSearchKeyAggregateID, Method: SearchMethodEquals, Value: aggregateID})
}
func (r *MetadataSearchRequest) AppendResourceOwnerQuery(resourceOwner string) {
r.Queries = append(r.Queries, &MetadataSearchQuery{Key: MetadataSearchKeyResourceOwner, Method: SearchMethodEquals, Value: resourceOwner})
}

View File

@@ -0,0 +1,23 @@
package domain
import "github.com/zitadel/zitadel/internal/crypto"
type MFAState int32
const (
MFAStateUnspecified MFAState = iota
MFAStateNotReady
MFAStateReady
MFAStateRemoved
stateCount
)
type MultifactorConfigs struct {
OTP OTPConfig
}
type OTPConfig struct {
Issuer string
CryptoMFA crypto.EncryptionAlgorithm
}

View File

@@ -0,0 +1,22 @@
package permissionmock
import (
"golang.org/x/net/context"
"github.com/zitadel/zitadel/internal/domain"
)
// MockPermissionCheckErr returns a permission check function that will fail
// and return the input error
func MockPermissionCheckErr(err error) domain.PermissionCheck {
return func(_ context.Context, _, _, _ string) error {
return err
}
}
// MockPermissionCheckOK returns a permission check function that will succeed
func MockPermissionCheckOK() domain.PermissionCheck {
return func(_ context.Context, _, _, _ string) (err error) {
return nil
}
}

View File

@@ -0,0 +1,200 @@
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
NextStepPasswordlessRegistrationPrompt
NextStepRegistration
NextStepProjectRequired
NextStepRedirectToExternalIDP
NextStepLoginSucceeded
NextStepVerifyInvite
)
type LoginStep struct{}
func (s *LoginStep) Type() NextStepType {
return NextStepLogin
}
type RegistrationStep struct{}
func (s *RegistrationStep) Type() NextStepType {
return NextStepRegistration
}
type SelectUserStep struct {
Users []UserSelection
}
func (s *SelectUserStep) Type() NextStepType {
return NextStepUserSelection
}
type UserSelection struct {
UserID string
UserName string
DisplayName string
LoginName string
UserSessionState UserSessionState
SelectionPossible bool
AvatarKey string
ResourceOwner string
}
type UserSessionState int32
const (
UserSessionStateActive UserSessionState = iota
UserSessionStateTerminated
)
type RedirectToExternalIDPStep struct{}
func (s *RedirectToExternalIDPStep) Type() NextStepType {
return NextStepRedirectToExternalIDP
}
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 {
PasswordSet bool
}
func (s *PasswordlessStep) Type() NextStepType {
return NextStepPasswordless
}
type PasswordlessRegistrationPromptStep struct{}
func (s *PasswordlessRegistrationPromptStep) Type() NextStepType {
return NextStepPasswordlessRegistrationPrompt
}
type ChangePasswordStep struct {
Expired bool
}
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 {
InitPassword bool
}
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 ProjectRequiredStep struct{}
func (s *ProjectRequiredStep) Type() NextStepType {
return NextStepProjectRequired
}
type RedirectToCallbackStep struct{}
func (s *RedirectToCallbackStep) Type() NextStepType {
return NextStepRedirectToCallback
}
type LoginSucceededStep struct{}
func (s *LoginSucceededStep) Type() NextStepType {
return NextStepLoginSucceeded
}
type VerifyInviteStep struct{}
func (s *VerifyInviteStep) Type() NextStepType {
return NextStepVerifyInvite
}

View File

@@ -0,0 +1,66 @@
package domain
import (
"time"
)
type NotificationType int32
const (
NotificationTypeEmail NotificationType = iota
NotificationTypeSms
notificationCount
)
type NotificationProviderState int32
const (
NotificationProviderStateUnspecified NotificationProviderState = iota
NotificationProviderStateActive
NotificationProviderStateRemoved
notificationProviderCount
)
func (s NotificationProviderState) Exists() bool {
return s == NotificationProviderStateActive
}
type NotificationProviderType int32
const (
NotificationProviderTypeFile NotificationProviderType = iota
NotificationProviderTypeLog
notificationProviderTypeCount
)
type NotificationArguments struct {
Origin string `json:"origin,omitempty"`
Domain string `json:"domain,omitempty"`
Expiry time.Duration `json:"expiry,omitempty"`
TempUsername string `json:"tempUsername,omitempty"`
ApplicationName string `json:"applicationName,omitempty"`
CodeID string `json:"codeID,omitempty"`
SessionID string `json:"sessionID,omitempty"`
AuthRequestID string `json:"authRequestID,omitempty"`
}
// ToMap creates a type safe map of the notification arguments.
// Since these arguments are used in text template, all keys must be PascalCase and types must remain the same (e.g. Duration).
func (n *NotificationArguments) ToMap() map[string]interface{} {
m := make(map[string]interface{})
if n == nil {
return m
}
m["Origin"] = n.Origin
m["Domain"] = n.Domain
m["Expiry"] = n.Expiry
m["TempUsername"] = n.TempUsername
m["ApplicationName"] = n.ApplicationName
m["CodeID"] = n.CodeID
m["SessionID"] = n.SessionID
m["AuthRequestID"] = n.AuthRequestID
return m
}

View File

@@ -0,0 +1,16 @@
package domain
import (
"time"
)
type ObjectDetails struct {
Sequence uint64
// EventDate is the date of the last event that changed the object
EventDate time.Time
// CreationDate is the date of the first event that created the object
CreationDate time.Time
ResourceOwner string
// ID is the Aggregate ID of the resource
ID string
}

View 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
)

View File

@@ -0,0 +1,48 @@
package domain
import (
"errors"
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/zitadel/internal/zerrors"
)
type OIDCErrorReason int32
const (
OIDCErrorReasonUnspecified OIDCErrorReason = iota
OIDCErrorReasonInvalidRequest
OIDCErrorReasonUnauthorizedClient
OIDCErrorReasonAccessDenied
OIDCErrorReasonUnsupportedResponseType
OIDCErrorReasonInvalidScope
OIDCErrorReasonServerError
OIDCErrorReasonTemporaryUnavailable
OIDCErrorReasonInteractionRequired
OIDCErrorReasonLoginRequired
OIDCErrorReasonAccountSelectionRequired
OIDCErrorReasonConsentRequired
OIDCErrorReasonInvalidRequestURI
OIDCErrorReasonInvalidRequestObject
OIDCErrorReasonRequestNotSupported
OIDCErrorReasonRequestURINotSupported
OIDCErrorReasonRegistrationNotSupported
OIDCErrorReasonInvalidGrant
)
func OIDCErrorReasonFromError(err error) OIDCErrorReason {
if errors.Is(err, oidc.ErrInvalidRequest()) {
return OIDCErrorReasonInvalidRequest
}
if errors.Is(err, oidc.ErrInvalidGrant()) {
return OIDCErrorReasonInvalidGrant
}
if zerrors.IsPreconditionFailed(err) {
return OIDCErrorReasonAccessDenied
}
if zerrors.IsInternal(err) {
return OIDCErrorReasonServerError
}
return OIDCErrorReasonUnspecified
}

View File

@@ -0,0 +1,51 @@
package domain
import (
"io"
"testing"
"github.com/stretchr/testify/assert"
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestOIDCErrorReasonFromError(t *testing.T) {
tests := []struct {
name string
err error
want OIDCErrorReason
}{
{
name: "invalid request",
err: oidc.ErrInvalidRequest().WithDescription("foo"),
want: OIDCErrorReasonInvalidRequest,
},
{
name: "invalid grant",
err: oidc.ErrInvalidGrant().WithDescription("foo"),
want: OIDCErrorReasonInvalidGrant,
},
{
name: "precondition failed",
err: zerrors.ThrowPreconditionFailed(nil, "123", "bar"),
want: OIDCErrorReasonAccessDenied,
},
{
name: "internal",
err: zerrors.ThrowInternal(nil, "123", "bar"),
want: OIDCErrorReasonServerError,
},
{
name: "unspecified",
err: io.ErrClosedPipe,
want: OIDCErrorReasonUnspecified,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := OIDCErrorReasonFromError(tt.err)
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,15 @@
package domain
type OIDCMappingField int32
const (
OIDCMappingFieldUnspecified OIDCMappingField = iota
OIDCMappingFieldPreferredLoginName
OIDCMappingFieldEmail
// count is for validation purposes
oidcMappingFieldCount
)
func (f OIDCMappingField) Valid() bool {
return f > 0 && f < oidcMappingFieldCount
}

View File

@@ -0,0 +1,9 @@
package domain
type OIDCSessionState int32
const (
OIDCSessionStateUnspecified OIDCSessionState = iota
OIDCSessionStateActive
OIDCSessionStateTerminated
)

View File

@@ -0,0 +1,37 @@
package domain
import (
"time"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type OIDCSettings struct {
models.ObjectRoot
State OIDCSettingsState
Default bool
AccessTokenLifetime time.Duration
IdTokenLifetime time.Duration
RefreshTokenIdleExpiration time.Duration
RefreshTokenExpiration time.Duration
}
type OIDCSettingsState int32
const (
OIDCSettingsStateUnspecified OIDCSettingsState = iota
OIDCSettingsStateActive
OIDCSettingsStateRemoved
oidcSettingsStateCount
)
func (c OIDCSettingsState) Valid() bool {
return c >= 0 && c < oidcSettingsStateCount
}
func (s OIDCSettingsState) Exists() bool {
return s != OIDCSettingsStateUnspecified && s != OIDCSettingsStateRemoved
}

View File

@@ -0,0 +1,86 @@
// Code generated by "enumer -type OIDCResponseMode -transform snake -trimprefix OIDCResponseMode"; DO NOT EDIT.
package domain
import (
"fmt"
"strings"
)
const _OIDCResponseModeName = "unspecifiedqueryfragmentform_post"
var _OIDCResponseModeIndex = [...]uint8{0, 11, 16, 24, 33}
const _OIDCResponseModeLowerName = "unspecifiedqueryfragmentform_post"
func (i OIDCResponseMode) String() string {
if i < 0 || i >= OIDCResponseMode(len(_OIDCResponseModeIndex)-1) {
return fmt.Sprintf("OIDCResponseMode(%d)", i)
}
return _OIDCResponseModeName[_OIDCResponseModeIndex[i]:_OIDCResponseModeIndex[i+1]]
}
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
func _OIDCResponseModeNoOp() {
var x [1]struct{}
_ = x[OIDCResponseModeUnspecified-(0)]
_ = x[OIDCResponseModeQuery-(1)]
_ = x[OIDCResponseModeFragment-(2)]
_ = x[OIDCResponseModeFormPost-(3)]
}
var _OIDCResponseModeValues = []OIDCResponseMode{OIDCResponseModeUnspecified, OIDCResponseModeQuery, OIDCResponseModeFragment, OIDCResponseModeFormPost}
var _OIDCResponseModeNameToValueMap = map[string]OIDCResponseMode{
_OIDCResponseModeName[0:11]: OIDCResponseModeUnspecified,
_OIDCResponseModeLowerName[0:11]: OIDCResponseModeUnspecified,
_OIDCResponseModeName[11:16]: OIDCResponseModeQuery,
_OIDCResponseModeLowerName[11:16]: OIDCResponseModeQuery,
_OIDCResponseModeName[16:24]: OIDCResponseModeFragment,
_OIDCResponseModeLowerName[16:24]: OIDCResponseModeFragment,
_OIDCResponseModeName[24:33]: OIDCResponseModeFormPost,
_OIDCResponseModeLowerName[24:33]: OIDCResponseModeFormPost,
}
var _OIDCResponseModeNames = []string{
_OIDCResponseModeName[0:11],
_OIDCResponseModeName[11:16],
_OIDCResponseModeName[16:24],
_OIDCResponseModeName[24:33],
}
// OIDCResponseModeString retrieves an enum value from the enum constants string name.
// Throws an error if the param is not part of the enum.
func OIDCResponseModeString(s string) (OIDCResponseMode, error) {
if val, ok := _OIDCResponseModeNameToValueMap[s]; ok {
return val, nil
}
if val, ok := _OIDCResponseModeNameToValueMap[strings.ToLower(s)]; ok {
return val, nil
}
return 0, fmt.Errorf("%s does not belong to OIDCResponseMode values", s)
}
// OIDCResponseModeValues returns all values of the enum
func OIDCResponseModeValues() []OIDCResponseMode {
return _OIDCResponseModeValues
}
// OIDCResponseModeStrings returns a slice of all String values of the enum
func OIDCResponseModeStrings() []string {
strs := make([]string, len(_OIDCResponseModeNames))
copy(strs, _OIDCResponseModeNames)
return strs
}
// IsAOIDCResponseMode returns "true" if the value is listed in the enum definition. "false" otherwise
func (i OIDCResponseMode) IsAOIDCResponseMode() bool {
for _, v := range _OIDCResponseModeValues {
if i == v {
return true
}
}
return false
}

View File

@@ -0,0 +1,49 @@
package domain
import (
"strings"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type Org struct {
models.ObjectRoot
State OrgState
Name string
PrimaryDomain string
Domains []*OrgDomain
}
func (o *Org) IsValid() bool {
if o == nil {
return false
}
o.Name = strings.TrimSpace(o.Name)
return o.Name != ""
}
func (o *Org) AddIAMDomain(iamDomain string) {
orgDomain, _ := NewIAMDomainName(o.Name, iamDomain)
o.Domains = append(o.Domains, &OrgDomain{Domain: orgDomain, Verified: true, Primary: true})
}
type OrgState int32
const (
OrgStateUnspecified OrgState = iota
OrgStateActive
OrgStateInactive
OrgStateRemoved
orgStateMax
)
func (s OrgState) Valid() bool {
return s > OrgStateUnspecified && s < orgStateMax
}
func (s OrgState) Exists() bool {
return s != OrgStateRemoved && s != OrgStateUnspecified
}

View File

@@ -0,0 +1,110 @@
package domain
import (
"regexp"
"strings"
http_util "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/zerrors"
)
type OrgDomain struct {
models.ObjectRoot
Domain string
Primary bool
Verified bool
ValidationType OrgDomainValidationType
ValidationCode *crypto.CryptoValue
}
func (domain *OrgDomain) IsValid() bool {
return domain.Domain != ""
}
func (domain *OrgDomain) GenerateVerificationCode(codeGenerator crypto.Generator) (string, error) {
validationCodeCrypto, validationCode, err := crypto.NewCode(codeGenerator)
if err != nil {
return "", err
}
domain.ValidationCode = validationCodeCrypto
return validationCode, nil
}
func NewIAMDomainName(orgName, iamDomain string) (string, error) {
// Reference: label domain requirements https://www.nic.ad.jp/timeline/en/20th/appendix1.html
// Replaces spaces in org name with hyphens
label := strings.ReplaceAll(orgName, " ", "-")
// The label must only contains alphanumeric characters and hyphens
// Invalid characters are replaced with and empty space but as #6471,
// as these domains are not used to host ZITADEL, but only for user names,
// the characters shouldn't matter that much so we'll accept unicode
// characters, accented characters (\p{L}\p{M}), numbers and hyphens.
label = string(regexp.MustCompile(`[^\p{L}\p{M}0-9-]`).ReplaceAll([]byte(label), []byte("")))
// The label cannot exceed 63 characters
if len(label) > 63 {
label = label[:63]
}
// The total length of the resulting domain can't exceed 253 characters
domain := label + "." + iamDomain
if len(domain) > 253 {
truncateNChars := len(domain) - 253
label = label[:len(label)-truncateNChars]
}
// Label (maybe truncated) can't start with a hyphen
if len(label) > 0 && label[0:1] == "-" {
label = label[1:]
}
// Label (maybe truncated) can't end with a hyphen
if len(label) > 0 && label[len(label)-1:] == "-" {
label = label[:len(label)-1]
}
// Empty string should be invalid
if len(label) > 0 {
return strings.ToLower(label + "." + iamDomain), nil
}
return "", zerrors.ThrowInvalidArgument(nil, "ORG-RrfXY", "Errors.Org.Domain.EmptyString")
}
type OrgDomainValidationType int32
const (
OrgDomainValidationTypeUnspecified OrgDomainValidationType = iota
OrgDomainValidationTypeHTTP
OrgDomainValidationTypeDNS
)
func (t OrgDomainValidationType) CheckType() (http_util.CheckType, bool) {
switch t {
case OrgDomainValidationTypeHTTP:
return http_util.CheckTypeHTTP, true
case OrgDomainValidationTypeDNS:
return http_util.CheckTypeDNS, true
default:
return -1, false
}
}
type OrgDomainState int32
const (
OrgDomainStateUnspecified OrgDomainState = iota
OrgDomainStateActive
OrgDomainStateRemoved
orgDomainStateCount
)
func (f OrgDomainState) Valid() bool {
return f >= 0 && f < orgDomainStateCount
}

View File

@@ -0,0 +1,98 @@
package domain
import (
"testing"
)
func TestNewIAMDomainName(t *testing.T) {
type args struct {
orgName string
iamDomain string
}
tests := []struct {
name string
args args
result string
}{
{
name: "Single word domain is already valid",
args: args{
orgName: "single-word-domain",
iamDomain: "localhost",
},
result: "single-word-domain.localhost",
},
{
name: "resulting domain should be in lowercase",
args: args{
orgName: "Uppercase org Name",
iamDomain: "localhost",
},
result: "uppercase-org-name.localhost",
},
{
name: "replace spaces with hyphens",
args: args{
orgName: "my org name",
iamDomain: "localhost",
},
result: "my-org-name.localhost",
},
{
name: "replace invalid characters [^a-zA-Z0-9-] with empty spaces",
args: args{
orgName: "mí >**name?",
iamDomain: "localhost",
},
result: "mí-name.localhost",
},
{
name: "label created from org name size is not greater than 63 chars",
args: args{
orgName: "my organization name must not exceed sixty-three characters 1234",
iamDomain: "localhost",
},
result: "my-organization-name-must-not-exceed-sixty-three-characters-123.localhost",
},
{
name: "resulting domain cannot exceed 253 chars",
args: args{
orgName: "Lorem ipsum dolor sit amet",
iamDomain: "llgwyngyllgogerychwyrndrobwllllantysiliogogogoch.llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch.llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch.llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch.co.uk",
},
result: "lorem-ipsum-dolor-sit.llgwyngyllgogerychwyrndrobwllllantysiliogogogoch.llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch.llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch.llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch.co.uk",
},
{
name: "label based on org name should not end with a hyphen",
args: args{
orgName: "my super long organization name with many many many characters ",
iamDomain: "localhost",
},
result: "my-super-long-organization-name-with-many-many-many-characters.localhost",
},
{
name: "label based on org name should not start with a hyphen",
args: args{
orgName: " my super long organization name with many many many characters",
iamDomain: "localhost",
},
result: "my-super-long-organization-name-with-many-many-many-characters.localhost",
},
{
name: "string full with invalid characters returns empty",
args: args{
orgName: "*¿=@^[])",
iamDomain: "localhost",
},
result: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
domain, _ := NewIAMDomainName(tt.args.orgName, tt.args.iamDomain)
if tt.result != domain {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, domain)
}
})
}
}

View File

@@ -0,0 +1,19 @@
package domain
type OrganizationSettingsState int32
const (
OrganizationSettingsStateUnspecified OrganizationSettingsState = iota
OrganizationSettingsStateActive
OrganizationSettingsStateRemoved
organizationSettingsStateCount
)
func (c OrganizationSettingsState) Valid() bool {
return c >= 0 && c < organizationSettingsStateCount
}
func (s OrganizationSettingsState) Exists() bool {
return s.Valid() && s != OrganizationSettingsStateUnspecified && s != OrganizationSettingsStateRemoved
}

View File

@@ -0,0 +1,75 @@
package domain
import "context"
type Permissions struct {
Permissions []string
}
func (p *Permissions) AppendPermissions(ctxID string, permissions ...string) {
for _, permission := range permissions {
p.appendPermission(ctxID, permission)
}
}
func (p *Permissions) appendPermission(ctxID, permission string) {
if ctxID != "" {
permission = permission + ":" + ctxID
}
for _, existingPermission := range p.Permissions {
if existingPermission == permission {
return
}
}
p.Permissions = append(p.Permissions, permission)
}
type PermissionCheck func(ctx context.Context, permission, resourceOwnerID, aggregateID string) (err error)
const (
PermissionUserWrite = "user.write"
PermissionUserRead = "user.read"
PermissionUserDelete = "user.delete"
PermissionUserCredentialWrite = "user.credential.write"
PermissionSessionWrite = "session.write"
PermissionSessionRead = "session.read"
PermissionSessionLink = "session.link"
PermissionSessionDelete = "session.delete"
PermissionOrgRead = "org.read"
PermissionIDPRead = "iam.idp.read"
PermissionOrgIDPRead = "org.idp.read"
PermissionProjectWrite = "project.write"
PermissionProjectRead = "project.read"
PermissionProjectDelete = "project.delete"
PermissionProjectGrantWrite = "project.grant.write"
PermissionProjectGrantRead = "project.grant.read"
PermissionProjectGrantDelete = "project.grant.delete"
PermissionProjectRoleWrite = "project.role.write"
PermissionProjectRoleRead = "project.role.read"
PermissionProjectRoleDelete = "project.role.delete"
PermissionProjectAppWrite = "project.app.write"
PermissionProjectAppDelete = "project.app.delete"
PermissionProjectAppRead = "project.app.read"
PermissionInstanceMemberWrite = "iam.member.write"
PermissionInstanceMemberDelete = "iam.member.delete"
PermissionInstanceMemberRead = "iam.member.read"
PermissionOrgMemberWrite = "org.member.write"
PermissionOrgMemberDelete = "org.member.delete"
PermissionOrgMemberRead = "org.member.read"
PermissionProjectMemberWrite = "project.member.write"
PermissionProjectMemberDelete = "project.member.delete"
PermissionProjectMemberRead = "project.member.read"
PermissionProjectGrantMemberWrite = "project.grant.member.write"
PermissionProjectGrantMemberDelete = "project.grant.member.delete"
PermissionProjectGrantMemberRead = "project.grant.member.read"
PermissionUserGrantWrite = "user.grant.write"
PermissionUserGrantRead = "user.grant.read"
PermissionUserGrantDelete = "user.grant.delete"
PermissionIAMPolicyWrite = "iam.policy.write"
PermissionIAMPolicyDelete = "iam.policy.delete"
PermissionPolicyRead = "policy.read"
)
// ProjectPermissionCheck is used as a check for preconditions dependent on application, project, user resourceowner and usergrants.
// Configurable on the project the application belongs to through the flags related to authentication.
type ProjectPermissionCheck func(ctx context.Context, clientID, userID string) (err error)

View File

@@ -0,0 +1,15 @@
package domain
type PolicyState int32
const (
PolicyStateUnspecified PolicyState = iota
PolicyStateActive
PolicyStateRemoved
policyStateCount
)
func (s PolicyState) Exists() bool {
return s != PolicyStateUnspecified && s != PolicyStateRemoved
}

View File

@@ -0,0 +1,14 @@
package domain
import (
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type DomainPolicy struct {
models.ObjectRoot
UserLoginMustBeDomain bool
ValidateOrgDomains bool
SMTPSenderAddressMatchesInstanceDomain bool
Default bool
}

View File

@@ -0,0 +1,85 @@
package domain
import (
"regexp"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/zerrors"
)
var colorRegex = regexp.MustCompile("^$|^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$")
type LabelPolicy struct {
models.ObjectRoot
State LabelPolicyState
Default bool
PrimaryColor string
BackgroundColor string
WarnColor string
FontColor string
LogoURL string
IconURL string
PrimaryColorDark string
BackgroundColorDark string
WarnColorDark string
FontColorDark string
LogoDarkURL string
IconDarkURL string
Font string
HideLoginNameSuffix bool
ErrorMsgPopup bool
DisableWatermark bool
ThemeMode LabelPolicyThemeMode
}
type LabelPolicyState int32
const (
LabelPolicyStateUnspecified LabelPolicyState = iota
LabelPolicyStateActive
LabelPolicyStateRemoved
LabelPolicyStatePreview
labelPolicyStateCount
)
type LabelPolicyThemeMode int32
const (
LabelPolicyThemeAuto LabelPolicyThemeMode = iota
LabelPolicyThemeLight
LabelPolicyThemeDark
)
func (f LabelPolicy) IsValid() error {
if !colorRegex.MatchString(f.PrimaryColor) {
return zerrors.ThrowInvalidArgument(nil, "POLICY-391dG", "Errors.Policy.Label.Invalid.PrimaryColor")
}
if !colorRegex.MatchString(f.BackgroundColor) {
return zerrors.ThrowInvalidArgument(nil, "POLICY-502F1", "Errors.Policy.Label.Invalid.BackgroundColor")
}
if !colorRegex.MatchString(f.WarnColor) {
return zerrors.ThrowInvalidArgument(nil, "POLICY-nvw33", "Errors.Policy.Label.Invalid.WarnColor")
}
if !colorRegex.MatchString(f.FontColor) {
return zerrors.ThrowInvalidArgument(nil, "POLICY-93mSf", "Errors.Policy.Label.Invalid.FontColor")
}
if !colorRegex.MatchString(f.PrimaryColorDark) {
return zerrors.ThrowInvalidArgument(nil, "POLICY-391dG", "Errors.Policy.Label.Invalid.PrimaryColorDark")
}
if !colorRegex.MatchString(f.BackgroundColorDark) {
return zerrors.ThrowInvalidArgument(nil, "POLICY-llsp2", "Errors.Policy.Label.Invalid.BackgroundColorDark")
}
if !colorRegex.MatchString(f.WarnColorDark) {
return zerrors.ThrowInvalidArgument(nil, "POLICY-2b6sf", "Errors.Policy.Label.Invalid.WarnColorDark")
}
if !colorRegex.MatchString(f.FontColorDark) {
return zerrors.ThrowInvalidArgument(nil, "POLICY-3M0fs", "Errors.Policy.Label.Invalid.FontColorDark")
}
return nil
}

View File

@@ -0,0 +1,505 @@
package domain
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestLabelPolicyPrimaryColorValid(t *testing.T) {
type args struct {
policy *LabelPolicy
}
tests := []struct {
name string
args args
err func(error) bool
}{
{
name: "empty primary, valid",
args: args{
policy: &LabelPolicy{PrimaryColor: ""},
},
},
{
name: "color code with 6 characters, valid",
args: args{
policy: &LabelPolicy{PrimaryColor: "#ffffff"},
},
},
{
name: "color code with 3 characters, valid",
args: args{
policy: &LabelPolicy{PrimaryColor: "#000"},
},
},
{
name: "color code with wrong characters, invalid",
args: args{
policy: &LabelPolicy{PrimaryColor: "#0f9wfm"},
},
err: zerrors.IsErrorInvalidArgument,
},
{
name: "color code with wrong count of characters, invalid",
args: args{
policy: &LabelPolicy{PrimaryColor: "#00"},
},
err: zerrors.IsErrorInvalidArgument,
},
{
name: "color code with no #, invalid",
args: args{
policy: &LabelPolicy{PrimaryColor: "#00"},
},
err: zerrors.IsErrorInvalidArgument,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.policy.IsValid()
if tt.err == nil {
assert.NoError(t, err)
}
if tt.err != nil && !tt.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestLabelPolicyBackgroundColorValid(t *testing.T) {
type args struct {
policy *LabelPolicy
}
tests := []struct {
name string
args args
err func(error) bool
}{
{
name: "empty background, valid",
args: args{
policy: &LabelPolicy{BackgroundColor: ""},
},
},
{
name: "color code with 6 characters, valid",
args: args{
policy: &LabelPolicy{BackgroundColor: "#ffffff"},
},
},
{
name: "color code with 3 characters, valid",
args: args{
policy: &LabelPolicy{BackgroundColor: "#000"},
},
},
{
name: "color code with wrong characters, invalid",
args: args{
policy: &LabelPolicy{BackgroundColor: "#0f9wfm"},
},
err: zerrors.IsErrorInvalidArgument,
},
{
name: "color code with wrong count of characters, invalid",
args: args{
policy: &LabelPolicy{BackgroundColor: "#00"},
},
err: zerrors.IsErrorInvalidArgument,
},
{
name: "color code with no #, invalid",
args: args{
policy: &LabelPolicy{BackgroundColor: "#00"},
},
err: zerrors.IsErrorInvalidArgument,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.policy.IsValid()
if tt.err == nil {
assert.NoError(t, err)
}
if tt.err != nil && !tt.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestLabelPolicyWarnColorValid(t *testing.T) {
type args struct {
policy *LabelPolicy
}
tests := []struct {
name string
args args
err func(error) bool
}{
{
name: "empty warn, valid",
args: args{
policy: &LabelPolicy{WarnColor: ""},
},
},
{
name: "color code with 6 characters, valid",
args: args{
policy: &LabelPolicy{WarnColor: "#ffffff"},
},
},
{
name: "color code with 3 characters, valid",
args: args{
policy: &LabelPolicy{WarnColor: "#000"},
},
},
{
name: "color code with wrong characters, invalid",
args: args{
policy: &LabelPolicy{WarnColor: "#0f9wfm"},
},
err: zerrors.IsErrorInvalidArgument,
},
{
name: "color code with wrong count of characters, invalid",
args: args{
policy: &LabelPolicy{WarnColor: "#00"},
},
err: zerrors.IsErrorInvalidArgument,
},
{
name: "color code with no #, invalid",
args: args{
policy: &LabelPolicy{WarnColor: "#00"},
},
err: zerrors.IsErrorInvalidArgument,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.policy.IsValid()
if tt.err == nil {
assert.NoError(t, err)
}
if tt.err != nil && !tt.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestLabelPolicyFontColorValid(t *testing.T) {
type args struct {
policy *LabelPolicy
}
tests := []struct {
name string
args args
err func(error) bool
}{
{
name: "empty font, valid",
args: args{
policy: &LabelPolicy{FontColor: ""},
},
},
{
name: "color code with 6 characters, valid",
args: args{
policy: &LabelPolicy{FontColor: "#ffffff"},
},
},
{
name: "color code with 3 characters, valid",
args: args{
policy: &LabelPolicy{FontColor: "#000"},
},
},
{
name: "color code with wrong characters, invalid",
args: args{
policy: &LabelPolicy{FontColor: "#0f9wfm"},
},
err: zerrors.IsErrorInvalidArgument,
},
{
name: "color code with wrong count of characters, invalid",
args: args{
policy: &LabelPolicy{FontColor: "#00"},
},
err: zerrors.IsErrorInvalidArgument,
},
{
name: "color code with no #, invalid",
args: args{
policy: &LabelPolicy{FontColor: "#00"},
},
err: zerrors.IsErrorInvalidArgument,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.policy.IsValid()
if tt.err == nil {
assert.NoError(t, err)
}
if tt.err != nil && !tt.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestLabelPolicyPrimaryColorDarkValid(t *testing.T) {
type args struct {
policy *LabelPolicy
}
tests := []struct {
name string
args args
err func(error) bool
}{
{
name: "empty primary dark, valid",
args: args{
policy: &LabelPolicy{PrimaryColorDark: ""},
},
},
{
name: "color code with 6 characters, valid",
args: args{
policy: &LabelPolicy{PrimaryColorDark: "#ffffff"},
},
},
{
name: "color code with 3 characters, valid",
args: args{
policy: &LabelPolicy{PrimaryColorDark: "#000"},
},
},
{
name: "color code with wrong characters, invalid",
args: args{
policy: &LabelPolicy{PrimaryColorDark: "#0f9wfm"},
},
err: zerrors.IsErrorInvalidArgument,
},
{
name: "color code with wrong count of characters, invalid",
args: args{
policy: &LabelPolicy{PrimaryColorDark: "#00"},
},
err: zerrors.IsErrorInvalidArgument,
},
{
name: "color code with no #, invalid",
args: args{
policy: &LabelPolicy{PrimaryColorDark: "#00"},
},
err: zerrors.IsErrorInvalidArgument,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.policy.IsValid()
if tt.err == nil {
assert.NoError(t, err)
}
if tt.err != nil && !tt.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestLabelPolicyBackgroundColorDarkValid(t *testing.T) {
type args struct {
policy *LabelPolicy
}
tests := []struct {
name string
args args
err func(error) bool
}{
{
name: "empty background dark, valid",
args: args{
policy: &LabelPolicy{BackgroundColorDark: ""},
},
},
{
name: "color code with 6 characters, valid",
args: args{
policy: &LabelPolicy{BackgroundColorDark: "#ffffff"},
},
},
{
name: "color code with 3 characters, valid",
args: args{
policy: &LabelPolicy{BackgroundColorDark: "#000"},
},
},
{
name: "color code with wrong characters, invalid",
args: args{
policy: &LabelPolicy{BackgroundColorDark: "#0f9wfm"},
},
err: zerrors.IsErrorInvalidArgument,
},
{
name: "color code with wrong count of characters, invalid",
args: args{
policy: &LabelPolicy{BackgroundColorDark: "#00"},
},
err: zerrors.IsErrorInvalidArgument,
},
{
name: "color code with no #, invalid",
args: args{
policy: &LabelPolicy{BackgroundColorDark: "#00"},
},
err: zerrors.IsErrorInvalidArgument,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.policy.IsValid()
if tt.err == nil {
assert.NoError(t, err)
}
if tt.err != nil && !tt.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestLabelPolicyWarnColorDarkValid(t *testing.T) {
type args struct {
policy *LabelPolicy
}
tests := []struct {
name string
args args
err func(error) bool
}{
{
name: "empty warn dark, valid",
args: args{
policy: &LabelPolicy{WarnColorDark: ""},
},
},
{
name: "color code with 6 characters, valid",
args: args{
policy: &LabelPolicy{WarnColorDark: "#ffffff"},
},
},
{
name: "color code with 3 characters, valid",
args: args{
policy: &LabelPolicy{WarnColorDark: "#000"},
},
},
{
name: "color code with wrong characters, invalid",
args: args{
policy: &LabelPolicy{WarnColorDark: "#0f9wfm"},
},
err: zerrors.IsErrorInvalidArgument,
},
{
name: "color code with wrong count of characters, invalid",
args: args{
policy: &LabelPolicy{WarnColorDark: "#00"},
},
err: zerrors.IsErrorInvalidArgument,
},
{
name: "color code with no #, invalid",
args: args{
policy: &LabelPolicy{WarnColorDark: "#00"},
},
err: zerrors.IsErrorInvalidArgument,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.policy.IsValid()
if tt.err == nil {
assert.NoError(t, err)
}
if tt.err != nil && !tt.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestLabelPolicyFontColorDarkValid(t *testing.T) {
type args struct {
policy *LabelPolicy
}
tests := []struct {
name string
args args
err func(error) bool
}{
{
name: "empty font dark, valid",
args: args{
policy: &LabelPolicy{FontColorDark: ""},
},
},
{
name: "color code with 6 characters, valid",
args: args{
policy: &LabelPolicy{FontColorDark: "#ffffff"},
},
},
{
name: "color code with 3 characters, valid",
args: args{
policy: &LabelPolicy{FontColorDark: "#000"},
},
},
{
name: "color code with wrong characters, invalid",
args: args{
policy: &LabelPolicy{FontColorDark: "#0f9wfm"},
},
err: zerrors.IsErrorInvalidArgument,
},
{
name: "color code with wrong count of characters, invalid",
args: args{
policy: &LabelPolicy{FontColorDark: "#00"},
},
err: zerrors.IsErrorInvalidArgument,
},
{
name: "color code with no #, invalid",
args: args{
policy: &LabelPolicy{FontColorDark: "#00"},
},
err: zerrors.IsErrorInvalidArgument,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.policy.IsValid()
if tt.err == nil {
assert.NoError(t, err)
}
if tt.err != nil && !tt.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}

View File

@@ -0,0 +1,97 @@
package domain
import (
"net/url"
"time"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type LoginPolicy struct {
models.ObjectRoot
Default bool
AllowUsernamePassword bool
AllowRegister bool
AllowExternalIDP bool
IDPProviders []*IDPProvider
ForceMFA bool
ForceMFALocalOnly bool
SecondFactors []SecondFactorType
MultiFactors []MultiFactorType
PasswordlessType PasswordlessType
HidePasswordReset bool
IgnoreUnknownUsernames bool
AllowDomainDiscovery bool
DefaultRedirectURI string
PasswordCheckLifetime time.Duration
ExternalLoginCheckLifetime time.Duration
MFAInitSkipLifetime time.Duration
SecondFactorCheckLifetime time.Duration
MultiFactorCheckLifetime time.Duration
DisableLoginWithEmail bool
DisableLoginWithPhone bool
}
func ValidateDefaultRedirectURI(rawURL string) bool {
if rawURL == "" {
return true
}
parsedURL, err := url.Parse(rawURL)
if err != nil {
return false
}
switch parsedURL.Scheme {
case "":
return false
case "http", "https":
return parsedURL.Host != ""
default:
return true
}
}
type IDPProvider struct {
models.ObjectRoot
Type IdentityProviderType
IDPConfigID string
Name string
StylingType IDPConfigStylingType // deprecated
IDPType IDPType
IDPState IDPConfigState
}
func (p IDPProvider) IsValid() bool {
return p.IDPConfigID != ""
}
// DisplayName returns the name or a default
// It's used for html rendering
// to be used when always a name must be displayed (e.g. login)
func (p IDPProvider) DisplayName() string {
return IDPName(p.Name, p.IDPType)
}
type PasswordlessType int32
const (
PasswordlessTypeNotAllowed PasswordlessType = iota
PasswordlessTypeAllowed
passwordlessCount
)
func (f PasswordlessType) Valid() bool {
return f >= 0 && f < passwordlessCount
}
// HasSecondFactors is used in html rendering
func (p *LoginPolicy) HasSecondFactors() bool {
return len(p.SecondFactors) > 0
}
// HasMultiFactors is used in html rendering
func (p *LoginPolicy) HasMultiFactors() bool {
return len(p.MultiFactors) > 0
}

View File

@@ -0,0 +1,73 @@
package domain
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestValidateDefaultRedirectURI(t *testing.T) {
type args struct {
rawURL string
}
tests := []struct {
name string
args args
want bool
}{
{
"invalid url, false",
args{
rawURL: string('\n'),
},
false,
},
{
"empty schema, false",
args{
rawURL: "url",
},
false,
},
{
"empty http host, false",
args{
rawURL: "http://",
},
false,
},
{
"empty https host, false",
args{
rawURL: "https://",
},
false,
},
{
"https, ok",
args{
rawURL: "https://test",
},
true,
},
{
"custom schema, ok",
args{
rawURL: "custom://",
},
true,
},
{
"empty url, ok",
args{
rawURL: "",
},
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, ValidateDefaultRedirectURI(tt.args.rawURL), "ValidateDefaultRedirectURI(%v)", tt.args.rawURL)
})
}
}

View File

@@ -0,0 +1,15 @@
package domain
import "github.com/zitadel/zitadel/internal/eventstore/v1/models"
type MailTemplate struct {
models.ObjectRoot
State PolicyState
Default bool
Template []byte
}
func (m *MailTemplate) IsValid() bool {
return m.Template != nil
}

View File

@@ -0,0 +1,12 @@
package domain
import (
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type PasswordAgePolicy struct {
models.ObjectRoot
MaxAgeDays uint64
ExpireWarnDays uint64
}

View File

@@ -0,0 +1,57 @@
package domain
import (
"regexp"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/zerrors"
)
var (
hasStringLowerCase = regexp.MustCompile(`[a-z]`).MatchString
hasStringUpperCase = regexp.MustCompile(`[A-Z]`).MatchString
hasNumber = regexp.MustCompile(`[0-9]`).MatchString
hasSymbol = regexp.MustCompile(`[^A-Za-z0-9]`).MatchString
)
type PasswordComplexityPolicy struct {
models.ObjectRoot
MinLength uint64
HasLowercase bool
HasUppercase bool
HasNumber bool
HasSymbol bool
Default bool
}
func (p *PasswordComplexityPolicy) IsValid() error {
if p.MinLength == 0 || p.MinLength > 72 {
return zerrors.ThrowInvalidArgument(nil, "MODEL-Lsp0e", "Errors.User.PasswordComplexityPolicy.MinLengthNotAllowed")
}
return nil
}
func (p *PasswordComplexityPolicy) Check(password string) error {
if p.MinLength != 0 && uint64(len(password)) < p.MinLength {
return zerrors.ThrowInvalidArgument(nil, "DOMAIN-HuJf6", "Errors.User.PasswordComplexityPolicy.MinLength")
}
if p.HasLowercase && !hasStringLowerCase(password) {
return zerrors.ThrowInvalidArgument(nil, "DOMAIN-co3Xw", "Errors.User.PasswordComplexityPolicy.HasLower")
}
if p.HasUppercase && !hasStringUpperCase(password) {
return zerrors.ThrowInvalidArgument(nil, "DOMAIN-VoaRj", "Errors.User.PasswordComplexityPolicy.HasUpper")
}
if p.HasNumber && !hasNumber(password) {
return zerrors.ThrowInvalidArgument(nil, "DOMAIN-ZBv4H", "Errors.User.PasswordComplexityPolicy.HasNumber")
}
if p.HasSymbol && !hasSymbol(password) {
return zerrors.ThrowInvalidArgument(nil, "DOMAIN-ZDLwA", "Errors.User.PasswordComplexityPolicy.HasSymbol")
}
return nil
}

View File

@@ -0,0 +1,14 @@
package domain
import (
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type LockoutPolicy struct {
models.ObjectRoot
Default bool
MaxPasswordAttempts uint64
MaxOTPAttempts uint64
ShowLockOutFailures bool
}

View File

@@ -0,0 +1,20 @@
package domain
import (
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type PrivacyPolicy struct {
models.ObjectRoot
State PolicyState
Default bool
TOSLink string
PrivacyLink string
HelpLink string
SupportEmail EmailAddress
DocsLink string
CustomLink string
CustomLinkText string
}

View File

@@ -0,0 +1,49 @@
package domain
import (
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type Project struct {
models.ObjectRoot
State ProjectState
Name string
ProjectRoleAssertion bool
ProjectRoleCheck bool
HasProjectCheck bool
PrivateLabelingSetting PrivateLabelingSetting
}
type ProjectState int32
const (
ProjectStateUnspecified ProjectState = iota
ProjectStateActive
ProjectStateInactive
ProjectStateRemoved
projectStateMax
)
func (s ProjectState) Valid() bool {
return s > ProjectStateUnspecified && s < projectStateMax
}
type PrivateLabelingSetting int32
const (
PrivateLabelingSettingUnspecified PrivateLabelingSetting = iota
PrivateLabelingSettingEnforceProjectResourceOwnerPolicy
PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy
privateLabelingSettingMax
)
func (s PrivateLabelingSetting) Valid() bool {
return s >= PrivateLabelingSettingUnspecified && s < privateLabelingSettingMax
}
func (o *Project) IsValid() bool {
return o.Name != ""
}

View File

@@ -0,0 +1,45 @@
package domain
import es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
type ProjectGrant struct {
es_models.ObjectRoot
GrantID string
GrantedOrgID string
State ProjectGrantState
RoleKeys []string
}
type ProjectGrantState int32
const (
ProjectGrantStateUnspecified ProjectGrantState = iota
ProjectGrantStateActive
ProjectGrantStateInactive
ProjectGrantStateRemoved
projectGrantStateMax
)
func (s ProjectGrantState) Valid() bool {
return s > ProjectGrantStateUnspecified && s < projectGrantStateMax
}
func (s ProjectGrantState) Exists() bool {
return s != ProjectGrantStateUnspecified && s != ProjectGrantStateRemoved
}
func (p *ProjectGrant) IsValid() bool {
return p.GrantedOrgID != ""
}
func GetRemovedRoles(existingRoles, newRoles []string) []string {
removed := make([]string, 0)
for _, role := range existingRoles {
if !containsRoleKey(role, newRoles) {
removed = append(removed, role)
}
}
return removed
}

View File

@@ -0,0 +1,17 @@
package domain
import (
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type ProjectGrantMember struct {
es_models.ObjectRoot
GrantID string
UserID string
Roles []string
}
func (i *ProjectGrantMember) IsValid() bool {
return i.AggregateID != "" && i.GrantID != "" && i.UserID != "" && len(i.Roles) != 0
}

View File

@@ -0,0 +1,47 @@
package domain
import (
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
type ProjectRole struct {
models.ObjectRoot
Key string
DisplayName string
Group string
}
type ProjectRoleState int32
const (
ProjectRoleStateUnspecified ProjectRoleState = iota
ProjectRoleStateActive
ProjectRoleStateRemoved
)
func (s ProjectRoleState) Exists() bool {
return s != ProjectRoleStateUnspecified && s != ProjectRoleStateRemoved
}
func (p *ProjectRole) IsValid() bool {
return p.AggregateID != "" && p.Key != ""
}
func HasInvalidRoles(validRoles, roles []string) bool {
for _, roleKey := range roles {
if !containsRoleKey(roleKey, validRoles) {
return true
}
}
return false
}
func containsRoleKey(roleKey string, validRoles []string) bool {
for _, validRole := range validRoles {
if roleKey == validRole {
return true
}
}
return false
}

View File

@@ -0,0 +1,20 @@
package domain
type IdentityProviderType int8
const (
IdentityProviderTypeSystem IdentityProviderType = iota
IdentityProviderTypeOrg
identityProviderCount
)
type IdentityProviderState int32
const (
IdentityProviderStateUnspecified IdentityProviderState = iota
IdentityProviderStateActive
IdentityProviderStateRemoved
idpProviderState
)

View File

@@ -0,0 +1,37 @@
package domain
import (
"encoding/base64"
"strings"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/zerrors"
)
func NewRefreshToken(userID, tokenID string, algorithm crypto.EncryptionAlgorithm) (string, error) {
return RefreshToken(userID, tokenID, tokenID, algorithm)
}
func RefreshToken(userID, tokenID, token string, algorithm crypto.EncryptionAlgorithm) (string, error) {
encrypted, err := algorithm.Encrypt([]byte(userID + ":" + tokenID + ":" + token))
if err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(encrypted), nil
}
func FromRefreshToken(refreshToken string, algorithm crypto.EncryptionAlgorithm) (userID, tokenID, token string, err error) {
decoded, err := base64.RawURLEncoding.DecodeString(refreshToken)
if err != nil {
return "", "", "", zerrors.ThrowInvalidArgument(err, "DOMAIN-BGDhn", "Errors.User.RefreshToken.Invalid")
}
decrypted, err := algorithm.DecryptString(decoded, algorithm.EncryptionKeyID())
if err != nil {
return "", "", "", zerrors.ThrowInvalidArgument(err, "DOMAIN-rie9A", "Errors.User.RefreshToken.Invalid")
}
split := strings.Split(decrypted, ":")
if len(split) != 3 {
return "", "", "", zerrors.ThrowInvalidArgument(nil, "DOMAIN-Se8oh", "Errors.User.RefreshToken.Invalid")
}
return split[0], split[1], split[2], nil
}

View File

@@ -0,0 +1,129 @@
package domain
import (
"encoding/base64"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/zerrors"
)
type mockKeyStorage struct {
keys crypto.Keys
}
func (s *mockKeyStorage) ReadKeys() (crypto.Keys, error) {
return s.keys, nil
}
func (s *mockKeyStorage) ReadKey(id string) (*crypto.Key, error) {
return &crypto.Key{
ID: id,
Value: s.keys[id],
}, nil
}
func (*mockKeyStorage) CreateKeys(context.Context, ...*crypto.Key) error {
return errors.New("mockKeyStorage.CreateKeys not implemented")
}
func TestFromRefreshToken(t *testing.T) {
const (
userID = "userID"
tokenID = "tokenID"
)
keyConfig := &crypto.KeyConfig{
EncryptionKeyID: "keyID",
DecryptionKeyIDs: []string{"keyID"},
}
keys := crypto.Keys{"keyID": "ThisKeyNeedsToHave32Characters!!"}
algorithm, err := crypto.NewAESCrypto(keyConfig, &mockKeyStorage{keys: keys})
require.NoError(t, err)
refreshToken, err := NewRefreshToken(userID, tokenID, algorithm)
require.NoError(t, err)
invalidRefreshToken, err := algorithm.Encrypt([]byte(userID + ":" + tokenID))
require.NoError(t, err)
type args struct {
refreshToken string
algorithm crypto.EncryptionAlgorithm
}
tests := []struct {
name string
args args
wantUserID string
wantTokenID string
wantToken string
wantErr error
}{
{
name: "invalid base64",
args: args{"~~~", algorithm},
wantErr: zerrors.ThrowInvalidArgument(nil, "DOMAIN-BGDhn", "Errors.User.RefreshToken.Invalid"),
},
{
name: "short cipher text",
args: args{"DEADBEEF", algorithm},
wantErr: zerrors.ThrowInvalidArgument(err, "DOMAIN-rie9A", "Errors.User.RefreshToken.Invalid"),
},
{
name: "incorrect amount of segments",
args: args{base64.RawURLEncoding.EncodeToString(invalidRefreshToken), algorithm},
wantErr: zerrors.ThrowInvalidArgument(nil, "DOMAIN-Se8oh", "Errors.User.RefreshToken.Invalid"),
},
{
name: "success",
args: args{refreshToken, algorithm},
wantUserID: userID,
wantTokenID: tokenID,
wantToken: tokenID,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotUserID, gotTokenID, gotToken, err := FromRefreshToken(tt.args.refreshToken, tt.args.algorithm)
require.ErrorIs(t, err, tt.wantErr)
assert.Equal(t, tt.wantUserID, gotUserID)
assert.Equal(t, tt.wantTokenID, gotTokenID)
assert.Equal(t, tt.wantToken, gotToken)
})
}
}
// Fuzz test invalid inputs. None of the inputs should result in a success.
func FuzzFromRefreshToken(f *testing.F) {
keyConfig := &crypto.KeyConfig{
EncryptionKeyID: "keyID",
DecryptionKeyIDs: []string{"keyID"},
}
keys := crypto.Keys{"keyID": "ThisKeyNeedsToHave32Characters!!"}
algorithm, err := crypto.NewAESCrypto(keyConfig, &mockKeyStorage{keys: keys})
require.NoError(f, err)
invalidRefreshToken, err := algorithm.Encrypt([]byte("userID:tokenID"))
require.NoError(f, err)
tests := []string{
"~~~", // invalid base64
"DEADBEEF", // short cipher text
base64.RawURLEncoding.EncodeToString(invalidRefreshToken), // incorrect amount of segments
}
for _, tc := range tests {
f.Add(tc)
}
f.Fuzz(func(t *testing.T, refreshToken string) {
gotUserID, gotTokenID, gotToken, err := FromRefreshToken(refreshToken, algorithm)
target := zerrors.InvalidArgumentError{ZitadelError: new(zerrors.ZitadelError)}
t.Log(gotUserID, gotTokenID, gotToken)
require.ErrorAs(t, err, &target)
})
}

View File

@@ -0,0 +1,80 @@
package domain
const (
OrgDomainPrimaryScope = "urn:zitadel:iam:org:domain:primary:"
OrgIDScope = "urn:zitadel:iam:org:id:"
OrgRoleIDScope = "urn:zitadel:iam:org:roles:id:"
OrgDomainPrimaryClaim = "urn:zitadel:iam:org:domain:primary"
OrgIDClaim = "urn:zitadel:iam:org:id"
ProjectIDScope = "urn:zitadel:iam:org:project:id:"
ProjectIDScopeZITADEL = "zitadel"
AudSuffix = ":aud"
ProjectScopeZITADEL = ProjectIDScope + ProjectIDScopeZITADEL + AudSuffix
SelectIDPScope = "urn:zitadel:iam:org:idp:id:"
)
// TODO: Change AuthRequest to interface and let oidcauthreqesut implement it
type Request interface {
Type() AuthRequestType
IsValid() bool
}
type AuthRequestType int32
const (
AuthRequestTypeOIDC AuthRequestType = iota
AuthRequestTypeSAML
AuthRequestTypeDevice
)
type AuthRequestOIDC struct {
Scopes []string
ResponseType OIDCResponseType
ResponseMode OIDCResponseMode
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 {
ID string
BindingType string
Code string
Issuer string
IssuerName string
Destination string
}
func (a *AuthRequestSAML) Type() AuthRequestType {
return AuthRequestTypeSAML
}
func (a *AuthRequestSAML) IsValid() bool {
return true
}
type AuthRequestDevice struct {
ClientID string
DeviceCode string
UserCode string
Scopes []string
Audience []string
AppName string
ProjectName string
}
func (*AuthRequestDevice) Type() AuthRequestType {
return AuthRequestTypeDevice
}
func (a *AuthRequestDevice) IsValid() bool {
return a.DeviceCode != "" && a.UserCode != ""
}

View File

@@ -0,0 +1,41 @@
package domain
import (
"strings"
"github.com/zitadel/zitadel/internal/api/authz"
)
const (
IAMRolePrefix = "IAM"
OrgRolePrefix = "ORG"
ProjectRolePrefix = "PROJECT"
ProjectGrantRolePrefix = "PROJECT_GRANT"
RoleOrgOwner = "ORG_OWNER"
RoleOrgProjectCreator = "ORG_PROJECT_CREATOR"
RoleIAMOwner = "IAM_OWNER"
RoleIAMLoginClient = "IAM_LOGIN_CLIENT"
RoleProjectOwner = "PROJECT_OWNER"
RoleProjectOwnerGlobal = "PROJECT_OWNER_GLOBAL"
RoleProjectGrantOwner = "PROJECT_GRANT_OWNER"
RoleSelfManagementGlobal = "SELF_MANAGEMENT_GLOBAL"
)
func CheckForInvalidRoles(roles []string, rolePrefix string, validRoles []authz.RoleMapping) []string {
invalidRoles := make([]string, 0)
for _, role := range roles {
if !containsRole(role, rolePrefix, validRoles) {
invalidRoles = append(invalidRoles, role)
}
}
return invalidRoles
}
func containsRole(role, rolePrefix string, validRoles []authz.RoleMapping) bool {
for _, validRole := range validRoles {
if role == validRole.Role && strings.HasPrefix(role, rolePrefix) {
return true
}
}
return false
}

View File

@@ -0,0 +1,41 @@
package domain
import (
"github.com/zitadel/saml/pkg/provider"
)
type SAMLErrorReason int32
const (
SAMLErrorReasonUnspecified SAMLErrorReason = iota
SAMLErrorReasonVersionMissmatch
SAMLErrorReasonAuthNFailed
SAMLErrorReasonInvalidAttrNameOrValue
SAMLErrorReasonInvalidNameIDPolicy
SAMLErrorReasonRequestDenied
SAMLErrorReasonRequestUnsupported
SAMLErrorReasonUnsupportedBinding
)
func SAMLErrorReasonToString(reason SAMLErrorReason) string {
switch reason {
case SAMLErrorReasonUnspecified:
return "unspecified error"
case SAMLErrorReasonVersionMissmatch:
return provider.StatusCodeVersionMissmatch
case SAMLErrorReasonAuthNFailed:
return provider.StatusCodeAuthNFailed
case SAMLErrorReasonInvalidAttrNameOrValue:
return provider.StatusCodeInvalidAttrNameOrValue
case SAMLErrorReasonInvalidNameIDPolicy:
return provider.StatusCodeInvalidNameIDPolicy
case SAMLErrorReasonRequestDenied:
return provider.StatusCodeRequestDenied
case SAMLErrorReasonRequestUnsupported:
return provider.StatusCodeRequestUnsupported
case SAMLErrorReasonUnsupportedBinding:
return provider.StatusCodeUnsupportedBinding
default:
return "unspecified error"
}
}

View File

@@ -0,0 +1,10 @@
package domain
type SAMLRequestState int
const (
SAMLRequestStateUnspecified SAMLRequestState = iota
SAMLRequestStateAdded
SAMLRequestStateFailed
SAMLRequestStateSucceeded
)

View File

@@ -0,0 +1,9 @@
package domain
type SAMLSessionState int32
const (
SAMLSessionStateUnspecified SAMLSessionState = iota
SAMLSessionStateActive
SAMLSessionStateTerminated
)

View File

@@ -0,0 +1,120 @@
package schema
import (
_ "embed"
"github.com/santhosh-tekuri/jsonschema/v5"
"github.com/zitadel/zitadel/internal/zerrors"
)
var (
//go:embed permission.schema.v1.json
permissionJSON string
permissionSchema = jsonschema.MustCompileString(PermissionSchemaID, permissionJSON)
)
const (
PermissionSchemaID = "urn:zitadel:schema:permission-schema:v1"
PermissionProperty = "urn:zitadel:schema:permission"
)
type Role int32
const (
RoleUnspecified Role = iota
RoleSelf
RoleOwner
)
type permissionExtension struct {
role Role
}
// Compile implements the [jsonschema.ExtCompiler] interface.
// It parses the permission schema extension / annotation of the passed field.
func (c permissionExtension) Compile(ctx jsonschema.CompilerContext, m map[string]interface{}) (_ jsonschema.ExtSchema, err error) {
perm, ok := m[PermissionProperty]
if !ok {
return nil, nil
}
p, ok := perm.(map[string]interface{})
if !ok {
return nil, zerrors.ThrowInvalidArgument(nil, "SCHEMA-WR5gs", "invalid permission")
}
perms := new(permissions)
for key, value := range p {
switch key {
case "self":
perms.self, err = mapPermission(value)
if err != nil {
return
}
case "owner":
perms.owner, err = mapPermission(value)
if err != nil {
return
}
default:
return nil, zerrors.ThrowInvalidArgument(nil, "SCHEMA-GFjio", "invalid permission Role")
}
}
return permissionExtensionConfig{c.role, perms}, nil
}
type permissionExtensionConfig struct {
role Role
permissions *permissions
}
// Validate implements the [jsonschema.ExtSchema] interface.
// It validates the fields of the json instance according to the permission schema.
func (s permissionExtensionConfig) Validate(ctx jsonschema.ValidationContext, v interface{}) error {
switch s.role {
case RoleSelf:
if s.permissions.self == nil || !s.permissions.self.write {
return ctx.Error("permission", "missing required permission")
}
return nil
case RoleOwner:
if s.permissions.owner == nil || !s.permissions.owner.write {
return ctx.Error("permission", "missing required permission")
}
return nil
case RoleUnspecified:
fallthrough
default:
return ctx.Error("permission", "missing required permission")
}
}
func mapPermission(value any) (*permission, error) {
p := new(permission)
switch v := value.(type) {
case string:
for _, s := range v {
switch s {
case 'r':
p.read = true
case 'w':
p.write = true
default:
return nil, zerrors.ThrowInvalidArgumentf(nil, "SCHEMA-EZ5zjh", "invalid permission pattern: `%s` in (%s)", string(s), v)
}
}
return p, nil
default:
return nil, zerrors.ThrowInvalidArgumentf(nil, "SCHEMA-E5h31", "invalid permission type %T (%v)", v, v)
}
}
type permissions struct {
self *permission
owner *permission
}
type permission struct {
read bool
write bool
}

View File

@@ -0,0 +1,28 @@
{
"$id": "urn:zitadel:schema:permission-schema:v1",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"urn:zitadel:schema:property-permission": {
"oneOf": [
{
"type": "string",
"pattern": "^[rw]$"
}
]
}
},
"properties": {
"urn:zitadel:schema:permission": {
"type": "object",
"additionalProperties": false,
"properties": {
"owner": {
"$ref": "#/$defs/urn:zitadel:schema:property-permission"
},
"self": {
"$ref": "#/$defs/urn:zitadel:schema:property-permission"
}
}
}
}
}

View File

@@ -0,0 +1,253 @@
package schema
import (
_ "embed"
"encoding/json"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestPermissionExtension(t *testing.T) {
type args struct {
role Role
schema string
instance string
}
type want struct {
compilationErr error
validationErr bool
}
tests := []struct {
name string
args args
want want
}{
{
"invalid permission, compilation err",
args{
schema: `{
"type": "object",
"properties": {
"name": {
"type": "string",
"urn:zitadel:schema:permission": "read"
}
}
}`,
},
want{
compilationErr: zerrors.ThrowInvalidArgument(nil, "SCHEMA-WR5gs", "invalid permission"),
},
},
{
"invalid permission string, compilation err",
args{
schema: `{
"type": "object",
"properties": {
"name": {
"type": "string",
"urn:zitadel:schema:permission": {
"self": "read"
}
}
}
}`,
},
want{
compilationErr: zerrors.ThrowInvalidArgument(nil, "SCHEMA-EZ5zjh", "invalid permission pattern: `e` in (read)"),
},
},
{
"invalid permission type, compilation err",
args{
schema: `{
"type": "object",
"properties": {
"name": {
"type": "string",
"urn:zitadel:schema:permission": {
"owner": true
}
}
}
}`,
},
want{
compilationErr: zerrors.ThrowInvalidArgument(nil, "SCHEMA-E5h31", "invalid permission type bool (true)"),
},
},
{
"invalid Role, compilation err",
args{
schema: `{
"type": "object",
"properties": {
"name": {
"type": "string",
"urn:zitadel:schema:permission": {
"IAM_OWNER": "rw"
}
}
}
}`,
},
want{
compilationErr: zerrors.ThrowInvalidArgument(nil, "SCHEMA-GFjio", "invalid permission Role"),
},
},
{
"invalid permission self, validation err",
args{
role: RoleSelf,
schema: `{
"type": "object",
"properties": {
"name": {
"type": "string",
"urn:zitadel:schema:permission": {
"owner": "rw",
"self": "r"
}
}
}
}`,
instance: `{ "name": "test"}`,
},
want{
validationErr: true,
},
},
{
"invalid permission owner, validation err",
args{
role: RoleOwner,
schema: `{
"type": "object",
"properties": {
"name": {
"type": "string",
"urn:zitadel:schema:permission": {
"owner": "r",
"self": "r"
}
}
}
}`,
instance: `{ "name": "test"}`,
},
want{
validationErr: true,
},
},
{
"valid permission self, ok",
args{
role: RoleSelf,
schema: `{
"type": "object",
"properties": {
"name": {
"type": "string",
"urn:zitadel:schema:permission": {
"owner": "r",
"self": "rw"
}
}
}
}`,
instance: `{ "name": "test"}`,
},
want{
validationErr: false,
},
},
{
"valid permission owner, ok",
args{
role: RoleOwner,
schema: `{
"type": "object",
"properties": {
"name": {
"type": "string",
"urn:zitadel:schema:permission": {
"owner": "rw",
"self": "r"
}
}
}
}`,
instance: `{ "name": "test"}`,
},
want{
validationErr: false,
},
},
{
"no Role, validation err",
args{
role: RoleUnspecified,
schema: `{
"type": "object",
"properties": {
"name": {
"type": "string",
"urn:zitadel:schema:permission": {
"owner": "rw",
"self": "rw"
}
}
}
}`,
instance: `{ "name": "test"}`,
},
want{
validationErr: true,
},
},
{
"no permission required, ok",
args{
role: RoleSelf,
schema: `{
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}`,
instance: `{ "name": "test"}`,
},
want{
validationErr: false,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
schema, err := NewSchema(tt.args.role, strings.NewReader(tt.args.schema))
require.ErrorIs(t, err, tt.want.compilationErr)
if tt.want.compilationErr != nil {
return
}
var v interface{}
err = json.Unmarshal([]byte(tt.args.instance), &v)
require.NoError(t, err)
err = schema.Validate(v)
if tt.want.validationErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
})
}
}

View File

@@ -0,0 +1,41 @@
package schema
import (
_ "embed"
"io"
"strings"
"github.com/santhosh-tekuri/jsonschema/v5"
"github.com/zitadel/zitadel/internal/zerrors"
)
var (
//go:embed zitadel.schema.v1.json
zitadelJSON string
)
const (
MetaSchemaID = "urn:zitadel:schema:v1"
)
func NewSchema(role Role, r io.Reader) (*jsonschema.Schema, error) {
c := jsonschema.NewCompiler()
if err := c.AddResource(PermissionSchemaID, strings.NewReader(permissionJSON)); err != nil {
return nil, err
}
if err := c.AddResource(MetaSchemaID, strings.NewReader(zitadelJSON)); err != nil {
return nil, err
}
c.RegisterExtension(PermissionSchemaID, permissionSchema, permissionExtension{
role,
})
if err := c.AddResource("schema.json", r); err != nil {
return nil, zerrors.ThrowInvalidArgument(err, "COMMA-Frh42", "Errors.UserSchema.Invalid")
}
schema, err := c.Compile("schema.json")
if err != nil {
return nil, zerrors.ThrowInvalidArgument(err, "COMMA-W21tg", "Errors.UserSchema.Invalid")
}
return schema, nil
}

View File

@@ -0,0 +1,13 @@
{
"$id": "urn:zitadel:schema:v1",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"allOf": [
{
"$ref": "https://json-schema.org/draft/2020-12/schema"
},
{
"$ref": "urn:zitadel:schema:permission-schema:v1"
}
]
}

View File

@@ -0,0 +1,19 @@
package domain
type SearchMethod int32
const (
SearchMethodEquals SearchMethod = iota
SearchMethodStartsWith
SearchMethodContains
SearchMethodEqualsIgnoreCase
SearchMethodStartsWithIgnoreCase
SearchMethodContainsIgnoreCase
SearchMethodNotEquals
SearchMethodGreaterThan
SearchMethodLessThan
SearchMethodIsOneOf
SearchMethodListContains
SearchMethodEndsWith
SearchMethodEndsWithIgnoreCase
)

View File

@@ -0,0 +1,33 @@
package domain
//go:generate enumer -type SecretGeneratorType -transform snake -trimprefix SecretGeneratorType
type SecretGeneratorType int32
const (
SecretGeneratorTypeUnspecified SecretGeneratorType = iota
SecretGeneratorTypeInitCode
SecretGeneratorTypeVerifyEmailCode
SecretGeneratorTypeVerifyPhoneCode
SecretGeneratorTypeVerifyDomain
SecretGeneratorTypePasswordResetCode
SecretGeneratorTypePasswordlessInitCode
SecretGeneratorTypeAppSecret
SecretGeneratorTypeOTPSMS
SecretGeneratorTypeOTPEmail
SecretGeneratorTypeInviteCode
SecretGeneratorTypeSigningKey
secretGeneratorTypeCount
)
func (t SecretGeneratorType) Valid() bool {
return t > SecretGeneratorTypeUnspecified && t < secretGeneratorTypeCount
}
type SecretGeneratorState int32
const (
SecretGeneratorStateUnspecified SecretGeneratorState = iota
SecretGeneratorStateActive
SecretGeneratorStateRemoved
)

View File

@@ -0,0 +1,122 @@
// Code generated by "enumer -type SecretGeneratorType -transform snake -trimprefix SecretGeneratorType"; DO NOT EDIT.
package domain
import (
"fmt"
"strings"
)
const _SecretGeneratorTypeName = "unspecifiedinit_codeverify_email_codeverify_phone_codeverify_domainpassword_reset_codepasswordless_init_codeapp_secretotpsmsotp_emailinvite_codesigning_keysecret_generator_type_count"
var _SecretGeneratorTypeIndex = [...]uint8{0, 11, 20, 37, 54, 67, 86, 108, 118, 124, 133, 144, 155, 182}
const _SecretGeneratorTypeLowerName = "unspecifiedinit_codeverify_email_codeverify_phone_codeverify_domainpassword_reset_codepasswordless_init_codeapp_secretotpsmsotp_emailinvite_codesigning_keysecret_generator_type_count"
func (i SecretGeneratorType) String() string {
if i < 0 || i >= SecretGeneratorType(len(_SecretGeneratorTypeIndex)-1) {
return fmt.Sprintf("SecretGeneratorType(%d)", i)
}
return _SecretGeneratorTypeName[_SecretGeneratorTypeIndex[i]:_SecretGeneratorTypeIndex[i+1]]
}
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
func _SecretGeneratorTypeNoOp() {
var x [1]struct{}
_ = x[SecretGeneratorTypeUnspecified-(0)]
_ = x[SecretGeneratorTypeInitCode-(1)]
_ = x[SecretGeneratorTypeVerifyEmailCode-(2)]
_ = x[SecretGeneratorTypeVerifyPhoneCode-(3)]
_ = x[SecretGeneratorTypeVerifyDomain-(4)]
_ = x[SecretGeneratorTypePasswordResetCode-(5)]
_ = x[SecretGeneratorTypePasswordlessInitCode-(6)]
_ = x[SecretGeneratorTypeAppSecret-(7)]
_ = x[SecretGeneratorTypeOTPSMS-(8)]
_ = x[SecretGeneratorTypeOTPEmail-(9)]
_ = x[SecretGeneratorTypeInviteCode-(10)]
_ = x[SecretGeneratorTypeSigningKey-(11)]
_ = x[secretGeneratorTypeCount-(12)]
}
var _SecretGeneratorTypeValues = []SecretGeneratorType{SecretGeneratorTypeUnspecified, SecretGeneratorTypeInitCode, SecretGeneratorTypeVerifyEmailCode, SecretGeneratorTypeVerifyPhoneCode, SecretGeneratorTypeVerifyDomain, SecretGeneratorTypePasswordResetCode, SecretGeneratorTypePasswordlessInitCode, SecretGeneratorTypeAppSecret, SecretGeneratorTypeOTPSMS, SecretGeneratorTypeOTPEmail, SecretGeneratorTypeInviteCode, SecretGeneratorTypeSigningKey, secretGeneratorTypeCount}
var _SecretGeneratorTypeNameToValueMap = map[string]SecretGeneratorType{
_SecretGeneratorTypeName[0:11]: SecretGeneratorTypeUnspecified,
_SecretGeneratorTypeLowerName[0:11]: SecretGeneratorTypeUnspecified,
_SecretGeneratorTypeName[11:20]: SecretGeneratorTypeInitCode,
_SecretGeneratorTypeLowerName[11:20]: SecretGeneratorTypeInitCode,
_SecretGeneratorTypeName[20:37]: SecretGeneratorTypeVerifyEmailCode,
_SecretGeneratorTypeLowerName[20:37]: SecretGeneratorTypeVerifyEmailCode,
_SecretGeneratorTypeName[37:54]: SecretGeneratorTypeVerifyPhoneCode,
_SecretGeneratorTypeLowerName[37:54]: SecretGeneratorTypeVerifyPhoneCode,
_SecretGeneratorTypeName[54:67]: SecretGeneratorTypeVerifyDomain,
_SecretGeneratorTypeLowerName[54:67]: SecretGeneratorTypeVerifyDomain,
_SecretGeneratorTypeName[67:86]: SecretGeneratorTypePasswordResetCode,
_SecretGeneratorTypeLowerName[67:86]: SecretGeneratorTypePasswordResetCode,
_SecretGeneratorTypeName[86:108]: SecretGeneratorTypePasswordlessInitCode,
_SecretGeneratorTypeLowerName[86:108]: SecretGeneratorTypePasswordlessInitCode,
_SecretGeneratorTypeName[108:118]: SecretGeneratorTypeAppSecret,
_SecretGeneratorTypeLowerName[108:118]: SecretGeneratorTypeAppSecret,
_SecretGeneratorTypeName[118:124]: SecretGeneratorTypeOTPSMS,
_SecretGeneratorTypeLowerName[118:124]: SecretGeneratorTypeOTPSMS,
_SecretGeneratorTypeName[124:133]: SecretGeneratorTypeOTPEmail,
_SecretGeneratorTypeLowerName[124:133]: SecretGeneratorTypeOTPEmail,
_SecretGeneratorTypeName[133:144]: SecretGeneratorTypeInviteCode,
_SecretGeneratorTypeLowerName[133:144]: SecretGeneratorTypeInviteCode,
_SecretGeneratorTypeName[144:155]: SecretGeneratorTypeSigningKey,
_SecretGeneratorTypeLowerName[144:155]: SecretGeneratorTypeSigningKey,
_SecretGeneratorTypeName[155:182]: secretGeneratorTypeCount,
_SecretGeneratorTypeLowerName[155:182]: secretGeneratorTypeCount,
}
var _SecretGeneratorTypeNames = []string{
_SecretGeneratorTypeName[0:11],
_SecretGeneratorTypeName[11:20],
_SecretGeneratorTypeName[20:37],
_SecretGeneratorTypeName[37:54],
_SecretGeneratorTypeName[54:67],
_SecretGeneratorTypeName[67:86],
_SecretGeneratorTypeName[86:108],
_SecretGeneratorTypeName[108:118],
_SecretGeneratorTypeName[118:124],
_SecretGeneratorTypeName[124:133],
_SecretGeneratorTypeName[133:144],
_SecretGeneratorTypeName[144:155],
_SecretGeneratorTypeName[155:182],
}
// SecretGeneratorTypeString retrieves an enum value from the enum constants string name.
// Throws an error if the param is not part of the enum.
func SecretGeneratorTypeString(s string) (SecretGeneratorType, error) {
if val, ok := _SecretGeneratorTypeNameToValueMap[s]; ok {
return val, nil
}
if val, ok := _SecretGeneratorTypeNameToValueMap[strings.ToLower(s)]; ok {
return val, nil
}
return 0, fmt.Errorf("%s does not belong to SecretGeneratorType values", s)
}
// SecretGeneratorTypeValues returns all values of the enum
func SecretGeneratorTypeValues() []SecretGeneratorType {
return _SecretGeneratorTypeValues
}
// SecretGeneratorTypeStrings returns a slice of all String values of the enum
func SecretGeneratorTypeStrings() []string {
strs := make([]string, len(_SecretGeneratorTypeNames))
copy(strs, _SecretGeneratorTypeNames)
return strs
}
// IsASecretGeneratorType returns "true" if the value is listed in the enum definition. "false" otherwise
func (i SecretGeneratorType) IsASecretGeneratorType() bool {
for _, v := range _SecretGeneratorTypeValues {
if i == v {
return true
}
}
return false
}

View File

@@ -0,0 +1,37 @@
package domain
import (
"io"
"golang.org/x/text/language"
)
type SessionState int32
const (
SessionStateUnspecified SessionState = iota
SessionStateActive
SessionStateTerminated
)
type OTPEmailURLData struct {
Code string
UserID string
LoginName string
DisplayName string
PreferredLanguage language.Tag
SessionID string
}
// RenderOTPEmailURLTemplate parses and renders tmpl.
// code, userID, (preferred) loginName, displayName and preferredLanguage are passed into the [OTPEmailURLData].
func RenderOTPEmailURLTemplate(w io.Writer, tmpl, code, userID, loginName, displayName, sessionID string, preferredLanguage language.Tag) error {
return renderURLTemplate(w, tmpl, &OTPEmailURLData{
Code: code,
UserID: userID,
LoginName: loginName,
DisplayName: displayName,
PreferredLanguage: preferredLanguage,
SessionID: sessionID,
})
}

View File

@@ -0,0 +1,14 @@
package domain
type SMSConfigState int32
const (
SMSConfigStateUnspecified SMSConfigState = iota
SMSConfigStateActive
SMSConfigStateInactive
SMSConfigStateRemoved
)
func (s SMSConfigState) Exists() bool {
return s != SMSConfigStateUnspecified && s != SMSConfigStateRemoved
}

Some files were not shown because too many files have changed in this diff Show More