mirror of
https://github.com/zitadel/zitadel.git
synced 2025-01-08 18:47:52 +00:00
Merge branch 'main' into next
# Conflicts: # docs/docs/guides/integrate/login-ui/external-login.mdx # internal/command/idp_model.go # proto/zitadel/user/v2alpha/user_service.proto
This commit is contained in:
commit
38f7b1bd06
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node_version: '18'
|
node_version: '18'
|
||||||
buf_version: 'latest'
|
buf_version: 'latest'
|
||||||
go_version: '1.20'
|
go_version: '1.21'
|
||||||
|
|
||||||
console:
|
console:
|
||||||
uses: ./.github/workflows/console.yml
|
uses: ./.github/workflows/console.yml
|
||||||
@ -35,7 +35,7 @@ jobs:
|
|||||||
needs: [core, console, version]
|
needs: [core, console, version]
|
||||||
uses: ./.github/workflows/compile.yml
|
uses: ./.github/workflows/compile.yml
|
||||||
with:
|
with:
|
||||||
go_version: '1.20'
|
go_version: '1.21'
|
||||||
core_cache_key: ${{ needs.core.outputs.cache_key }}
|
core_cache_key: ${{ needs.core.outputs.cache_key }}
|
||||||
console_cache_key: ${{ needs.console.outputs.cache_key }}
|
console_cache_key: ${{ needs.console.outputs.cache_key }}
|
||||||
core_cache_path: ${{ needs.core.outputs.cache_path }}
|
core_cache_path: ${{ needs.core.outputs.cache_path }}
|
||||||
@ -46,7 +46,7 @@ jobs:
|
|||||||
needs: core
|
needs: core
|
||||||
uses: ./.github/workflows/core-unit-test.yml
|
uses: ./.github/workflows/core-unit-test.yml
|
||||||
with:
|
with:
|
||||||
go_version: '1.20'
|
go_version: '1.21'
|
||||||
core_cache_key: ${{ needs.core.outputs.cache_key }}
|
core_cache_key: ${{ needs.core.outputs.cache_key }}
|
||||||
core_cache_path: ${{ needs.core.outputs.cache_path }}
|
core_cache_path: ${{ needs.core.outputs.cache_path }}
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ jobs:
|
|||||||
needs: core
|
needs: core
|
||||||
uses: ./.github/workflows/core-integration-test.yml
|
uses: ./.github/workflows/core-integration-test.yml
|
||||||
with:
|
with:
|
||||||
go_version: '1.20'
|
go_version: '1.21'
|
||||||
core_cache_key: ${{ needs.core.outputs.cache_key }}
|
core_cache_key: ${{ needs.core.outputs.cache_key }}
|
||||||
core_cache_path: ${{ needs.core.outputs.cache_path }}
|
core_cache_path: ${{ needs.core.outputs.cache_path }}
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ jobs:
|
|||||||
needs: [core, console]
|
needs: [core, console]
|
||||||
uses: ./.github/workflows/lint.yml
|
uses: ./.github/workflows/lint.yml
|
||||||
with:
|
with:
|
||||||
go_version: '1.20'
|
go_version: '1.21'
|
||||||
node_version: '18'
|
node_version: '18'
|
||||||
buf_version: 'latest'
|
buf_version: 'latest'
|
||||||
go_lint_version: 'v1.53.2'
|
go_lint_version: 'v1.53.2'
|
||||||
|
@ -8,7 +8,7 @@ issues:
|
|||||||
run:
|
run:
|
||||||
concurrency: 4
|
concurrency: 4
|
||||||
timeout: 10m
|
timeout: 10m
|
||||||
go: '1.19'
|
go: '1.21'
|
||||||
skip-dirs:
|
skip-dirs:
|
||||||
- .artifacts
|
- .artifacts
|
||||||
- .backups
|
- .backups
|
||||||
|
@ -135,7 +135,7 @@ ZITADEL uses [golangci-lint](https://golangci-lint.run) for code quality checks.
|
|||||||
The commands in this section are tested against the following software versions:
|
The commands in this section are tested against the following software versions:
|
||||||
|
|
||||||
- [Docker version 20.10.17](https://docs.docker.com/engine/install/)
|
- [Docker version 20.10.17](https://docs.docker.com/engine/install/)
|
||||||
- [Go version 1.20](https://go.dev/doc/install)
|
- [Go version 1.21](https://go.dev/doc/install)
|
||||||
- [Delve 1.9.1](https://github.com/go-delve/delve/tree/v1.9.1/Documentation/installation)
|
- [Delve 1.9.1](https://github.com/go-delve/delve/tree/v1.9.1/Documentation/installation)
|
||||||
|
|
||||||
Make some changes to the source code, then run the database locally.
|
Make some changes to the source code, then run the database locally.
|
||||||
|
18
README.md
18
README.md
@ -4,21 +4,23 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
<a href="https://github.com/zitadel/zitadel/blob/main/LICENSE" alt="License">
|
||||||
|
<img src="https://badgen.net/github/license/zitadel/zitadel/" /></a>
|
||||||
<a href="https://bestpractices.coreinfrastructure.org/projects/6662"><img src="https://bestpractices.coreinfrastructure.org/projects/6662/badge"></a>
|
<a href="https://bestpractices.coreinfrastructure.org/projects/6662"><img src="https://bestpractices.coreinfrastructure.org/projects/6662/badge"></a>
|
||||||
<a href="https://github.com/zitadel/zitadel/graphs/contributors" alt="Release">
|
|
||||||
<img src="https://badgen.net/github/contributors/zitadel/zitadel" /></a>
|
|
||||||
<a href="https://github.com/semantic-release/semantic-release" alt="semantic-release">
|
<a href="https://github.com/semantic-release/semantic-release" alt="semantic-release">
|
||||||
<img src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg" /></a>
|
<img src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg" /></a>
|
||||||
<a href="https://github.com/zitadel/zitadel/actions" alt="ZITADEL Release">
|
<a href="https://github.com/zitadel/zitadel/actions" alt="ZITADEL Release">
|
||||||
<img src="https://github.com/zitadel/zitadel/actions/workflows/zitadel.yml/badge.svg" /></a>
|
<img alt="GitHub Workflow Status (with event)" src="https://img.shields.io/github/actions/workflow/status/zitadel/zitadel/build.yml?event=pull_request"></a>
|
||||||
<a href="https://github.com/zitadel/zitadel/blob/main/LICENSE" alt="License">
|
|
||||||
<img src="https://badgen.net/github/license/zitadel/zitadel/" /></a>
|
|
||||||
<a href="https://github.com/zitadel/zitadel/releases" alt="Release">
|
<a href="https://github.com/zitadel/zitadel/releases" alt="Release">
|
||||||
<img src="https://badgen.net/github/release/zitadel/zitadel/stable" /></a>
|
<img src="https://badgen.net/github/release/zitadel/zitadel/stable" /></a>
|
||||||
|
<a href="https://github.com/zitadel/zitadel/releases" alt="Release">
|
||||||
|
<img alt="Dynamic YAML Badge" src="https://img.shields.io/badge/dynamic/yaml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fzitadel%2Fzitadel%2Fmain%2Frelease-channels.yaml&query=%24.stable&label=stable"></a>
|
||||||
<a href="https://goreportcard.com/report/github.com/zitadel/zitadel" alt="Go Report Card">
|
<a href="https://goreportcard.com/report/github.com/zitadel/zitadel" alt="Go Report Card">
|
||||||
<img src="https://goreportcard.com/badge/github.com/zitadel/zitadel" /></a>
|
<img src="https://goreportcard.com/badge/github.com/zitadel/zitadel" /></a>
|
||||||
<a href="https://codecov.io/gh/zitadel/zitadel" alt="Code Coverage">
|
<a href="https://codecov.io/gh/zitadel/zitadel" alt="Code Coverage">
|
||||||
<img src="https://codecov.io/gh/zitadel/zitadel/branch/main/graph/badge.svg" /></a>
|
<img src="https://codecov.io/gh/zitadel/zitadel/branch/main/graph/badge.svg" /></a>
|
||||||
|
<a href="https://github.com/zitadel/zitadel/graphs/contributors" alt="Release">
|
||||||
|
<img alt="GitHub contributors" src="https://img.shields.io/github/contributors/zitadel/zitadel"></a>
|
||||||
<a href="https://discord.gg/erh5Brh7jE" alt="Discord Chat">
|
<a href="https://discord.gg/erh5Brh7jE" alt="Discord Chat">
|
||||||
<img src="https://badgen.net/discord/online-members/erh5Brh7jE" /></a>
|
<img src="https://badgen.net/discord/online-members/erh5Brh7jE" /></a>
|
||||||
</p>
|
</p>
|
||||||
@ -94,7 +96,7 @@ Authentication
|
|||||||
- Single Sign On (SSO)
|
- Single Sign On (SSO)
|
||||||
- Passwordless with FIDO2 support (Including Passkeys)
|
- Passwordless with FIDO2 support (Including Passkeys)
|
||||||
- Username / Password
|
- Username / Password
|
||||||
- Multifactor authentication with OTP, U2F
|
- Multifactor authentication with OTP, U2F, Email OTP, SMS OTP
|
||||||
- LDAP
|
- LDAP
|
||||||
- [OpenID Connect certified](https://openid.net/certification/#OPs) => [OIDC Endpoints](https://zitadel.com/docs/apis/openidoauth/endpoints)
|
- [OpenID Connect certified](https://openid.net/certification/#OPs) => [OIDC Endpoints](https://zitadel.com/docs/apis/openidoauth/endpoints)
|
||||||
- [SAML 2.0](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) => [SAML Endpoints](https://zitadel.com/docs/apis/saml/endpoints)
|
- [SAML 2.0](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) => [SAML Endpoints](https://zitadel.com/docs/apis/saml/endpoints)
|
||||||
@ -103,12 +105,12 @@ Authentication
|
|||||||
Multi-Tenancy
|
Multi-Tenancy
|
||||||
- [Identity Brokering](https://zitadel.com/docs/guides/integrate/identity-brokering) with templates for popular identity providers
|
- [Identity Brokering](https://zitadel.com/docs/guides/integrate/identity-brokering) with templates for popular identity providers
|
||||||
- [Delegate role management to third-parties](https://zitadel.com/docs/guides/manage/console/projects)
|
- [Delegate role management to third-parties](https://zitadel.com/docs/guides/manage/console/projects)
|
||||||
- Domain discovery
|
- [Domain discovery](https://zitadel.com/docs/guides/solution-scenarios/domain-discovery)
|
||||||
|
|
||||||
Integration
|
Integration
|
||||||
- [GRPC and REST APIs](https://zitadel.com/docs/apis/introduction)
|
- [GRPC and REST APIs](https://zitadel.com/docs/apis/introduction)
|
||||||
- [Actions](https://zitadel.com/docs/apis/actions/introduction) to call any API, send webhooks, adjust workflows, or customize tokens
|
- [Actions](https://zitadel.com/docs/apis/actions/introduction) to call any API, send webhooks, adjust workflows, or customize tokens
|
||||||
- Role Based Access Control (RBAC)
|
- [Role Based Access Control (RBAC)](https://zitadel.com/docs/guides/integrate/retrieve-user-roles)
|
||||||
|
|
||||||
Self-Service
|
Self-Service
|
||||||
- [Self-registration](https://zitadel.com/docs/concepts/features/selfservice#registration) including verification
|
- [Self-registration](https://zitadel.com/docs/concepts/features/selfservice#registration) including verification
|
||||||
|
@ -307,6 +307,7 @@ Login:
|
|||||||
MaxAge: 12h # ZITADEL_LOGIN_CACHE_MAXAGE
|
MaxAge: 12h # ZITADEL_LOGIN_CACHE_MAXAGE
|
||||||
# 168h is 7 days, one week
|
# 168h is 7 days, one week
|
||||||
SharedMaxAge: 168h # ZITADEL_LOGIN_CACHE_SHAREDMAXAGE
|
SharedMaxAge: 168h # ZITADEL_LOGIN_CACHE_SHAREDMAXAGE
|
||||||
|
DefaultOTPEmailURLV2: "/otp/verify?loginName={{.LoginName}}&code={{.Code}}" # ZITADEL_LOGIN_CACHE_DEFAULTOTPEMAILURLV2
|
||||||
|
|
||||||
Console:
|
Console:
|
||||||
ShortCache:
|
ShortCache:
|
||||||
@ -370,10 +371,10 @@ SystemDefaults:
|
|||||||
MachineKeySize: 2048 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_MACHINEKEYSIZE
|
MachineKeySize: 2048 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_MACHINEKEYSIZE
|
||||||
ApplicationKeySize: 2048 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_APPLICATIONKEYSIZE
|
ApplicationKeySize: 2048 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_APPLICATIONKEYSIZE
|
||||||
PasswordHasher:
|
PasswordHasher:
|
||||||
# Set hasher configuration for user passwords.
|
# Set hasher configuration for user passwords.
|
||||||
# Passwords previously hashed with a different algorithm
|
# Passwords previously hashed with a different algorithm
|
||||||
# or cost are automatically re-hashed using this config,
|
# or cost are automatically re-hashed using this config,
|
||||||
# upon password validation or update.
|
# upon password validation or update.
|
||||||
Hasher:
|
Hasher:
|
||||||
Algorithm: "bcrypt" # ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_HASHER_ALGORITHM
|
Algorithm: "bcrypt" # ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_HASHER_ALGORITHM
|
||||||
Cost: 14 # ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_HASHER_COST
|
Cost: 14 # ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_HASHER_COST
|
||||||
@ -687,6 +688,7 @@ DefaultInstance:
|
|||||||
# If the host of the sender is different from ExternalDomain set DefaultInstance.DomainPolicy.SMTPSenderAddressMatchesInstanceDomain to false
|
# If the host of the sender is different from ExternalDomain set DefaultInstance.DomainPolicy.SMTPSenderAddressMatchesInstanceDomain to false
|
||||||
From: # ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_SMTP_FROM
|
From: # ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_SMTP_FROM
|
||||||
FromName: # ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_SMTP_FROMNAME
|
FromName: # ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_SMTP_FROMNAME
|
||||||
|
ReplyToAddress: # ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_SMTP_REPLYTOADDRESS
|
||||||
MessageTexts:
|
MessageTexts:
|
||||||
- MessageTextType: InitCode
|
- MessageTextType: InitCode
|
||||||
Language: de
|
Language: de
|
||||||
|
@ -222,7 +222,25 @@ func startZitadel(config *Config, masterKey string, server chan<- *Server) error
|
|||||||
actionsLogstoreSvc := logstore.New(queries, usageReporter, actionsExecutionDBEmitter, actionsExecutionStdoutEmitter)
|
actionsLogstoreSvc := logstore.New(queries, usageReporter, actionsExecutionDBEmitter, actionsExecutionStdoutEmitter)
|
||||||
actions.SetLogstoreService(actionsLogstoreSvc)
|
actions.SetLogstoreService(actionsLogstoreSvc)
|
||||||
|
|
||||||
notification.Start(ctx, config.Projections.Customizations["notifications"], config.Projections.Customizations["notificationsquotas"], config.Projections.Customizations["telemetry"], *config.Telemetry, config.ExternalDomain, config.ExternalPort, config.ExternalSecure, commands, queries, eventstoreClient, assets.AssetAPIFromDomain(config.ExternalSecure, config.ExternalPort), config.SystemDefaults.Notifications.FileSystemPath, keys.User, keys.SMTP, keys.SMS)
|
notification.Start(
|
||||||
|
ctx,
|
||||||
|
config.Projections.Customizations["notifications"],
|
||||||
|
config.Projections.Customizations["notificationsquotas"],
|
||||||
|
config.Projections.Customizations["telemetry"],
|
||||||
|
*config.Telemetry,
|
||||||
|
config.ExternalDomain,
|
||||||
|
config.ExternalPort,
|
||||||
|
config.ExternalSecure,
|
||||||
|
commands,
|
||||||
|
queries,
|
||||||
|
eventstoreClient,
|
||||||
|
assets.AssetAPIFromDomain(config.ExternalSecure, config.ExternalPort),
|
||||||
|
config.Login.DefaultOTPEmailURLV2,
|
||||||
|
config.SystemDefaults.Notifications.FileSystemPath,
|
||||||
|
keys.User,
|
||||||
|
keys.SMTP,
|
||||||
|
keys.SMS,
|
||||||
|
)
|
||||||
|
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
tlsConfig, err := config.TLS.Config()
|
tlsConfig, err := config.TLS.Config()
|
||||||
@ -362,7 +380,7 @@ func startAPIs(
|
|||||||
|
|
||||||
apis.RegisterHandlerOnPrefix(idp.HandlerPrefix, idp.NewHandler(commands, queries, keys.IDPConfig, config.ExternalSecure, instanceInterceptor.Handler))
|
apis.RegisterHandlerOnPrefix(idp.HandlerPrefix, idp.NewHandler(commands, queries, keys.IDPConfig, config.ExternalSecure, instanceInterceptor.Handler))
|
||||||
|
|
||||||
userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, id.SonyFlakeGenerator(), config.ExternalSecure, login.EndpointResources)
|
userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, id.SonyFlakeGenerator(), config.ExternalSecure, login.EndpointResources, login.EndpointExternalLoginCallbackFormPost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -56,8 +56,8 @@
|
|||||||
"budgets": [
|
"budgets": [
|
||||||
{
|
{
|
||||||
"type": "initial",
|
"type": "initial",
|
||||||
"maximumWarning": "6mb",
|
"maximumWarning": "8mb",
|
||||||
"maximumError": "7mb"
|
"maximumError": "9mb"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "anyComponentStyle",
|
"type": "anyComponentStyle",
|
||||||
|
@ -173,7 +173,43 @@ export class FilterEventsComponent implements OnInit {
|
|||||||
return this.adminService
|
return this.adminService
|
||||||
.listEventTypes(req)
|
.listEventTypes(req)
|
||||||
.then((list) => {
|
.then((list) => {
|
||||||
this.eventTypes = list.eventTypesList ?? [];
|
this.eventTypes =
|
||||||
|
list.eventTypesList.sort((a, b) => {
|
||||||
|
if (b.localized && b.localized.localizedMessage) {
|
||||||
|
if (a.localized && a.localized.localizedMessage) {
|
||||||
|
if (a.localized.localizedMessage < b.localized.localizedMessage) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (a.localized.localizedMessage > b.localized.localizedMessage) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (a.type < b.localized.localizedMessage) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (a.type > b.localized.localizedMessage) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (a.localized && a.localized.localizedMessage) {
|
||||||
|
if (a.localized.localizedMessage < b.type) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (a.localized.localizedMessage > b.type) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (a.type < b.type) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (a.type > b.type) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}) ?? [];
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
|
@ -80,6 +80,11 @@
|
|||||||
<i class="idp-icon las la-building"></i>
|
<i class="idp-icon las la-building"></i>
|
||||||
Active Directory / LDAP
|
Active Directory / LDAP
|
||||||
</div>
|
</div>
|
||||||
|
<div class="idp-table-provider-type" *ngSwitchCase="ProviderType.PROVIDER_TYPE_APPLE">
|
||||||
|
<img class="idp-logo apple dark" src="./assets/images/idp/apple-dark.svg" alt="apple" />
|
||||||
|
<img class="idp-logo apple light" src="./assets/images/idp/apple.svg" alt="apple" />
|
||||||
|
Apple
|
||||||
|
</div>
|
||||||
<div class="idp-table-provider-type" *ngSwitchDefault>coming soon</div>
|
<div class="idp-table-provider-type" *ngSwitchDefault>coming soon</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
@ -21,6 +21,10 @@
|
|||||||
width: 28px;
|
width: 28px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&.apple {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
&.dark {
|
&.dark {
|
||||||
display: if($is-dark-theme, block, none);
|
display: if($is-dark-theme, block, none);
|
||||||
}
|
}
|
||||||
|
@ -261,6 +261,8 @@ export class IdpTableComponent implements OnInit, OnDestroy {
|
|||||||
];
|
];
|
||||||
case ProviderType.PROVIDER_TYPE_GITHUB:
|
case ProviderType.PROVIDER_TYPE_GITHUB:
|
||||||
return [row.owner === IDPOwnerType.IDP_OWNER_TYPE_SYSTEM ? '/instance' : '/org', 'provider', 'github', row.id];
|
return [row.owner === IDPOwnerType.IDP_OWNER_TYPE_SYSTEM ? '/instance' : '/org', 'provider', 'github', row.id];
|
||||||
|
case ProviderType.PROVIDER_TYPE_APPLE:
|
||||||
|
return [row.owner === IDPOwnerType.IDP_OWNER_TYPE_SYSTEM ? '/instance' : '/org', 'provider', 'apple', row.id];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,6 +109,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="item card"
|
||||||
|
[routerLink]="
|
||||||
|
serviceType === PolicyComponentServiceType.ADMIN
|
||||||
|
? ['/instance', 'provider', 'apple', 'create']
|
||||||
|
: serviceType === PolicyComponentServiceType.MGMT
|
||||||
|
? ['/org', 'provider', 'apple', 'create']
|
||||||
|
: []
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<img class="idp-logo apple dark" src="./assets/images/idp/apple-dark.svg" alt="Apple" />
|
||||||
|
<img class="idp-logo apple light" src="./assets/images/idp/apple.svg" alt="Apple" />
|
||||||
|
<div class="text-container">
|
||||||
|
<span class="title">Apple</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
class="item card"
|
class="item card"
|
||||||
[routerLink]="
|
[routerLink]="
|
||||||
|
@ -64,6 +64,10 @@
|
|||||||
height: 36px;
|
height: 36px;
|
||||||
width: 36px;
|
width: 36px;
|
||||||
|
|
||||||
|
&.apple {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
&.dark {
|
&.dark {
|
||||||
display: if($is-dark-theme, block, none);
|
display: if($is-dark-theme, block, none);
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,17 @@
|
|||||||
<form (ngSubmit)="savePolicy()" [formGroup]="form" autocomplete="off">
|
<form (ngSubmit)="savePolicy()" [formGroup]="form" autocomplete="off">
|
||||||
<cnsl-form-field class="smtp-form-field" label="Sender Address" required="true">
|
<cnsl-form-field class="smtp-form-field" label="Sender Address" required="true">
|
||||||
<cnsl-label>{{ 'SETTING.SMTP.SENDERADDRESS' | translate }}</cnsl-label>
|
<cnsl-label>{{ 'SETTING.SMTP.SENDERADDRESS' | translate }}</cnsl-label>
|
||||||
<input cnslInput name="senderAddress" formControlName="senderAddress" />
|
<input cnslInput name="senderAddress" formControlName="senderAddress" required />
|
||||||
</cnsl-form-field>
|
</cnsl-form-field>
|
||||||
|
|
||||||
<cnsl-form-field class="smtp-form-field" label="Sender Name" required="true">
|
<cnsl-form-field class="smtp-form-field" label="Sender Name" required="true">
|
||||||
<cnsl-label>{{ 'SETTING.SMTP.SENDERNAME' | translate }}</cnsl-label>
|
<cnsl-label>{{ 'SETTING.SMTP.SENDERNAME' | translate }}</cnsl-label>
|
||||||
<input cnslInput name="senderName" formControlName="senderName" />
|
<input cnslInput name="senderName" formControlName="senderName" required />
|
||||||
|
</cnsl-form-field>
|
||||||
|
|
||||||
|
<cnsl-form-field class="smtp-form-field" label="Reply-To Address">
|
||||||
|
<cnsl-label>{{ 'SETTING.SMTP.REPLYTOADDRESS' | translate }}</cnsl-label>
|
||||||
|
<input cnslInput name="senderReplyToAddress" formControlName="replyToAddress" />
|
||||||
</cnsl-form-field>
|
</cnsl-form-field>
|
||||||
|
|
||||||
<mat-checkbox class="smtp-checkbox" formControlName="tls">
|
<mat-checkbox class="smtp-checkbox" formControlName="tls">
|
||||||
@ -29,12 +34,12 @@
|
|||||||
|
|
||||||
<cnsl-form-field class="smtp-form-field" label="Host And Port" required="true">
|
<cnsl-form-field class="smtp-form-field" label="Host And Port" required="true">
|
||||||
<cnsl-label>{{ 'SETTING.SMTP.HOSTANDPORT' | translate }}</cnsl-label>
|
<cnsl-label>{{ 'SETTING.SMTP.HOSTANDPORT' | translate }}</cnsl-label>
|
||||||
<input cnslInput name="hostAndPort" formControlName="hostAndPort" placeholder="smtp.mailtrap.io:2525" />
|
<input cnslInput name="hostAndPort" formControlName="hostAndPort" placeholder="smtp.mailtrap.io:2525" required />
|
||||||
</cnsl-form-field>
|
</cnsl-form-field>
|
||||||
|
|
||||||
<cnsl-form-field class="smtp-form-field" label="User" required="true">
|
<cnsl-form-field class="smtp-form-field" label="User" required="true">
|
||||||
<cnsl-label>{{ 'SETTING.SMTP.USER' | translate }}</cnsl-label>
|
<cnsl-label>{{ 'SETTING.SMTP.USER' | translate }}</cnsl-label>
|
||||||
<input id="smtp-user" cnslInput name="smtp-user" autocomplete="smtp-user" formControlName="user" />
|
<input id="smtp-user" cnslInput name="smtp-user" autocomplete="smtp-user" formControlName="user" required />
|
||||||
</cnsl-form-field>
|
</cnsl-form-field>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
@ -58,6 +58,7 @@ export class NotificationSettingsComponent implements OnInit {
|
|||||||
this.form = this.fb.group({
|
this.form = this.fb.group({
|
||||||
senderAddress: [{ disabled: true, value: '' }, [requiredValidator]],
|
senderAddress: [{ disabled: true, value: '' }, [requiredValidator]],
|
||||||
senderName: [{ disabled: true, value: '' }, [requiredValidator]],
|
senderName: [{ disabled: true, value: '' }, [requiredValidator]],
|
||||||
|
replyToAddress: [{ disabled: true, value: '' }],
|
||||||
tls: [{ disabled: true, value: true }, [requiredValidator]],
|
tls: [{ disabled: true, value: true }, [requiredValidator]],
|
||||||
hostAndPort: [{ disabled: true, value: '' }, [requiredValidator]],
|
hostAndPort: [{ disabled: true, value: '' }, [requiredValidator]],
|
||||||
user: [{ disabled: true, value: '' }, [requiredValidator]],
|
user: [{ disabled: true, value: '' }, [requiredValidator]],
|
||||||
@ -143,6 +144,7 @@ export class NotificationSettingsComponent implements OnInit {
|
|||||||
req.setHost(this.hostAndPort?.value ?? '');
|
req.setHost(this.hostAndPort?.value ?? '');
|
||||||
req.setSenderAddress(this.senderAddress?.value ?? '');
|
req.setSenderAddress(this.senderAddress?.value ?? '');
|
||||||
req.setSenderName(this.senderName?.value ?? '');
|
req.setSenderName(this.senderName?.value ?? '');
|
||||||
|
req.setReplyToAddress(this.replyToAddress?.value ?? '');
|
||||||
req.setTls(this.tls?.value ?? false);
|
req.setTls(this.tls?.value ?? false);
|
||||||
req.setUser(this.user?.value ?? '');
|
req.setUser(this.user?.value ?? '');
|
||||||
|
|
||||||
@ -152,6 +154,7 @@ export class NotificationSettingsComponent implements OnInit {
|
|||||||
req.setHost(this.hostAndPort?.value ?? '');
|
req.setHost(this.hostAndPort?.value ?? '');
|
||||||
req.setSenderAddress(this.senderAddress?.value ?? '');
|
req.setSenderAddress(this.senderAddress?.value ?? '');
|
||||||
req.setSenderName(this.senderName?.value ?? '');
|
req.setSenderName(this.senderName?.value ?? '');
|
||||||
|
req.setReplyToAddress(this.replyToAddress?.value ?? '');
|
||||||
req.setTls(this.tls?.value ?? false);
|
req.setTls(this.tls?.value ?? false);
|
||||||
req.setUser(this.user?.value ?? '');
|
req.setUser(this.user?.value ?? '');
|
||||||
|
|
||||||
@ -298,6 +301,10 @@ export class NotificationSettingsComponent implements OnInit {
|
|||||||
return this.form.get('senderName');
|
return this.form.get('senderName');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get replyToAddress(): AbstractControl | null {
|
||||||
|
return this.form.get('replyToAddress');
|
||||||
|
}
|
||||||
|
|
||||||
public get tls(): AbstractControl | null {
|
public get tls(): AbstractControl | null {
|
||||||
return this.form.get('tls');
|
return this.form.get('tls');
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,146 @@
|
|||||||
|
<cnsl-create-layout
|
||||||
|
title="{{ id ? ('IDP.DETAIL.TITLE' | translate) : ('IDP.CREATE.TITLE' | translate) }}"
|
||||||
|
(closed)="close()"
|
||||||
|
>
|
||||||
|
<div class="identity-provider-create-content">
|
||||||
|
<div class="title-row">
|
||||||
|
<img class="idp-logo apple dark" src="./assets/images/idp/apple-dark.svg" alt="apple" />
|
||||||
|
<img class="idp-logo apple light" src="./assets/images/idp/apple.svg" alt="apple" />
|
||||||
|
<h1>{{ 'IDP.CREATE.APPLE.TITLE' | translate }}</h1>
|
||||||
|
<mat-spinner diameter="25" *ngIf="loading" color="primary"></mat-spinner>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="identity-provider-desc cnsl-secondary-text">
|
||||||
|
{{ !provider ? ('IDP.CREATE.APPLE.DESCRIPTION' | translate) : ('IDP.DETAIL.DESCRIPTION' | translate) }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form [formGroup]="form" (ngSubmit)="submitForm()">
|
||||||
|
<div class="identity-provider-content">
|
||||||
|
<cnsl-form-field class="formfield">
|
||||||
|
<cnsl-label>{{ 'IDP.CLIENTID' | translate }}</cnsl-label>
|
||||||
|
<input cnslInput formControlName="clientId" />
|
||||||
|
</cnsl-form-field>
|
||||||
|
|
||||||
|
<cnsl-form-field class="formfield">
|
||||||
|
<cnsl-label>{{ 'IDP.APPLE.TEAMID' | translate }}</cnsl-label>
|
||||||
|
<input cnslInput formControlName="teamId" />
|
||||||
|
</cnsl-form-field>
|
||||||
|
|
||||||
|
<cnsl-form-field class="formfield">
|
||||||
|
<cnsl-label>{{ 'IDP.APPLE.KEYID' | translate }}</cnsl-label>
|
||||||
|
<input cnslInput formControlName="keyId" />
|
||||||
|
</cnsl-form-field>
|
||||||
|
|
||||||
|
<mat-checkbox
|
||||||
|
class="update-secret-checkbox"
|
||||||
|
*ngIf="provider"
|
||||||
|
[(ngModel)]="updatePrivateKey"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
>{{ 'IDP.APPLE.UPDATEPRIVATEKEY' | translate }}</mat-checkbox
|
||||||
|
>
|
||||||
|
|
||||||
|
<ng-container *ngIf="!provider || (provider && updatePrivateKey)">
|
||||||
|
<span class="pk-label cnsl-secondary-text">{{ 'IDP.APPLE.PRIVATEKEY' | translate }}</span>
|
||||||
|
<div class="private-key-wrapper">
|
||||||
|
<ng-container *ngIf="privateKey?.value; else addLogoButton">
|
||||||
|
<i class="las la-file"></i>
|
||||||
|
<button
|
||||||
|
class="dl-btn"
|
||||||
|
mat-icon-button
|
||||||
|
color="warn"
|
||||||
|
(click)="privateKey?.setValue('')"
|
||||||
|
matTooltip="{{ 'ACTIONS.DELETE' | translate }}"
|
||||||
|
>
|
||||||
|
<i class="las la-trash"></i>
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #addLogoButton>
|
||||||
|
<input
|
||||||
|
#keyFileInput
|
||||||
|
style="display: none"
|
||||||
|
class="file-input"
|
||||||
|
type="file"
|
||||||
|
accept=".p8"
|
||||||
|
(change)="onDropKey($any($event.target).files)"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="asset-add-btn"
|
||||||
|
mat-icon-button
|
||||||
|
matTooltip="{{ 'IDP.APPLE.UPLOADPRIVATEKEY' | translate }}"
|
||||||
|
(click)="$event.preventDefault(); keyFileInput.click()"
|
||||||
|
>
|
||||||
|
<mat-icon>add</mat-icon>
|
||||||
|
</button>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div class="identity-provider-optional-h-wrapper">
|
||||||
|
<h2>{{ 'IDP.OPTIONAL' | translate }}</h2>
|
||||||
|
<button (click)="showOptional = !showOptional" type="button" mat-icon-button>
|
||||||
|
<mat-icon *ngIf="showOptional">keyboard_arrow_up</mat-icon
|
||||||
|
><mat-icon *ngIf="!showOptional">keyboard_arrow_down</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="showOptional">
|
||||||
|
<div class="idp-scopes">
|
||||||
|
<div class="flex-line">
|
||||||
|
<cnsl-form-field class="formfield">
|
||||||
|
<cnsl-label>{{ 'IDP.SCOPESLIST' | translate }}</cnsl-label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
cnslInput
|
||||||
|
[matChipInputFor]="chipScopesList"
|
||||||
|
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||||
|
[matChipInputAddOnBlur]="true"
|
||||||
|
(matChipInputTokenEnd)="addScope($event)"
|
||||||
|
/>
|
||||||
|
</cnsl-form-field>
|
||||||
|
<button class="scope-add-button" (click)="addScope($any($event))" mat-icon-button>
|
||||||
|
<mat-icon>add</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<cnsl-form-field class="formfield">
|
||||||
|
<mat-chip-list #chipScopesList aria-label="scope selection">
|
||||||
|
<mat-chip
|
||||||
|
class="chip"
|
||||||
|
*ngFor="let scope of scopesList?.value"
|
||||||
|
selectable="false"
|
||||||
|
removable
|
||||||
|
(removed)="removeScope(scope)"
|
||||||
|
>
|
||||||
|
{{ scope }} <mat-icon matChipRemove>cancel</mat-icon>
|
||||||
|
</mat-chip>
|
||||||
|
</mat-chip-list>
|
||||||
|
</cnsl-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<cnsl-form-field class="formfield">
|
||||||
|
<cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label>
|
||||||
|
<input cnslInput formControlName="name" />
|
||||||
|
<span class="name-hint cnsl-secondary-text" cnslHint>{{ 'IDP.NAMEHINT' | translate }}</span>
|
||||||
|
</cnsl-form-field>
|
||||||
|
|
||||||
|
<cnsl-provider-options
|
||||||
|
[initialOptions]="provider?.config?.options"
|
||||||
|
(optionsChanged)="options = $event"
|
||||||
|
></cnsl-provider-options>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="identity-provider-create-actions">
|
||||||
|
<button
|
||||||
|
color="primary"
|
||||||
|
mat-raised-button
|
||||||
|
class="continue-button"
|
||||||
|
[disabled]="form.invalid || form.disabled"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span *ngIf="id">{{ 'ACTIONS.SAVE' | translate }}</span>
|
||||||
|
<span *ngIf="!id">{{ 'ACTIONS.CREATE' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</cnsl-create-layout>
|
@ -0,0 +1,24 @@
|
|||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ProviderAppleComponent } from './provider-apple.component';
|
||||||
|
|
||||||
|
describe('ProviderGoogleComponent', () => {
|
||||||
|
let component: ProviderAppleComponent;
|
||||||
|
let fixture: ComponentFixture<ProviderAppleComponent>;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ProviderAppleComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ProviderAppleComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,306 @@
|
|||||||
|
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
|
||||||
|
import { Location } from '@angular/common';
|
||||||
|
import { Component, Injector, Type } from '@angular/core';
|
||||||
|
import { AbstractControl, FormControl, FormGroup } from '@angular/forms';
|
||||||
|
import { MatLegacyChipInputEvent as MatChipInputEvent } from '@angular/material/legacy-chips';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { take } from 'rxjs';
|
||||||
|
import {
|
||||||
|
AddAppleProviderRequest as AdminAddAppleProviderRequest,
|
||||||
|
GetProviderByIDRequest as AdminGetProviderByIDRequest,
|
||||||
|
UpdateAppleProviderRequest as AdminUpdateAppleProviderRequest,
|
||||||
|
} from 'src/app/proto/generated/zitadel/admin_pb';
|
||||||
|
import { Options, Provider } from 'src/app/proto/generated/zitadel/idp_pb';
|
||||||
|
import {
|
||||||
|
AddAppleProviderRequest as MgmtAddAppleProviderRequest,
|
||||||
|
GetProviderByIDRequest as MgmtGetProviderByIDRequest,
|
||||||
|
UpdateAppleProviderRequest as MgmtUpdateAppleProviderRequest,
|
||||||
|
} from 'src/app/proto/generated/zitadel/management_pb';
|
||||||
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
|
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||||
|
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||||
|
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
import { requiredValidator } from '../../form-field/validators/validators';
|
||||||
|
|
||||||
|
import { PolicyComponentServiceType } from '../../policies/policy-component-types.enum';
|
||||||
|
|
||||||
|
const MAX_ALLOWED_SIZE = 5 * 1024;
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'cnsl-provider-apple',
|
||||||
|
templateUrl: './provider-apple.component.html',
|
||||||
|
})
|
||||||
|
export class ProviderAppleComponent {
|
||||||
|
public showOptional: boolean = false;
|
||||||
|
public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true);
|
||||||
|
public id: string | null = '';
|
||||||
|
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
|
||||||
|
private service!: ManagementService | AdminService;
|
||||||
|
|
||||||
|
public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE];
|
||||||
|
|
||||||
|
public form!: FormGroup;
|
||||||
|
|
||||||
|
public loading: boolean = false;
|
||||||
|
|
||||||
|
public provider?: Provider.AsObject;
|
||||||
|
public updatePrivateKey: boolean = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authService: GrpcAuthService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private toast: ToastService,
|
||||||
|
private injector: Injector,
|
||||||
|
private _location: Location,
|
||||||
|
private breadcrumbService: BreadcrumbService,
|
||||||
|
) {
|
||||||
|
this.form = new FormGroup({
|
||||||
|
name: new FormControl('', []),
|
||||||
|
clientId: new FormControl('', [requiredValidator]),
|
||||||
|
teamId: new FormControl('', [requiredValidator]),
|
||||||
|
keyId: new FormControl('', [requiredValidator]),
|
||||||
|
privateKey: new FormControl('', [requiredValidator]),
|
||||||
|
scopesList: new FormControl(['name', 'email'], []),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.authService
|
||||||
|
.isAllowed(
|
||||||
|
this.serviceType === PolicyComponentServiceType.ADMIN
|
||||||
|
? ['iam.idp.write']
|
||||||
|
: this.serviceType === PolicyComponentServiceType.MGMT
|
||||||
|
? ['org.idp.write']
|
||||||
|
: [],
|
||||||
|
)
|
||||||
|
.pipe(take(1))
|
||||||
|
.subscribe((allowed) => {
|
||||||
|
if (allowed) {
|
||||||
|
this.form.enable();
|
||||||
|
} else {
|
||||||
|
this.form.disable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.route.data.pipe(take(1)).subscribe((data) => {
|
||||||
|
this.serviceType = data['serviceType'];
|
||||||
|
|
||||||
|
switch (this.serviceType) {
|
||||||
|
case PolicyComponentServiceType.MGMT:
|
||||||
|
this.service = this.injector.get(ManagementService as Type<ManagementService>);
|
||||||
|
|
||||||
|
const bread: Breadcrumb = {
|
||||||
|
type: BreadcrumbType.ORG,
|
||||||
|
routerLink: ['/org'],
|
||||||
|
};
|
||||||
|
|
||||||
|
this.breadcrumbService.setBreadcrumb([bread]);
|
||||||
|
break;
|
||||||
|
case PolicyComponentServiceType.ADMIN:
|
||||||
|
this.service = this.injector.get(AdminService as Type<AdminService>);
|
||||||
|
|
||||||
|
const iamBread = new Breadcrumb({
|
||||||
|
type: BreadcrumbType.ORG,
|
||||||
|
name: 'Instance',
|
||||||
|
routerLink: ['/instance'],
|
||||||
|
});
|
||||||
|
this.breadcrumbService.setBreadcrumb([iamBread]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.id = this.route.snapshot.paramMap.get('id');
|
||||||
|
if (this.id) {
|
||||||
|
this.privateKey?.setValidators([]);
|
||||||
|
this.getData(this.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getData(id: string): void {
|
||||||
|
const req =
|
||||||
|
this.serviceType === PolicyComponentServiceType.ADMIN
|
||||||
|
? new AdminGetProviderByIDRequest()
|
||||||
|
: new MgmtGetProviderByIDRequest();
|
||||||
|
req.setId(id);
|
||||||
|
this.service
|
||||||
|
.getProviderByID(req)
|
||||||
|
.then((resp) => {
|
||||||
|
this.provider = resp.idp;
|
||||||
|
this.loading = false;
|
||||||
|
if (this.provider?.config?.apple) {
|
||||||
|
this.form.patchValue(this.provider.config.apple);
|
||||||
|
this.name?.setValue(this.provider.name);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public submitForm(): void {
|
||||||
|
this.provider ? this.updateAppleProvider() : this.addAppleProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
public addAppleProvider(): void {
|
||||||
|
const req =
|
||||||
|
this.serviceType === PolicyComponentServiceType.MGMT
|
||||||
|
? new MgmtAddAppleProviderRequest()
|
||||||
|
: new AdminAddAppleProviderRequest();
|
||||||
|
|
||||||
|
req.setName(this.name?.value);
|
||||||
|
req.setClientId(this.clientId?.value);
|
||||||
|
req.setTeamId(this.teamId?.value);
|
||||||
|
req.setKeyId(this.keyId?.value);
|
||||||
|
req.setPrivateKey(this.privateKey?.value);
|
||||||
|
req.setScopesList(this.scopesList?.value);
|
||||||
|
req.setProviderOptions(this.options);
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
this.service
|
||||||
|
.addAppleProvider(req)
|
||||||
|
.then((idp) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loading = false;
|
||||||
|
this.close();
|
||||||
|
}, 2000);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateAppleProvider(): void {
|
||||||
|
if (this.provider) {
|
||||||
|
if (this.serviceType === PolicyComponentServiceType.MGMT) {
|
||||||
|
const req = new MgmtUpdateAppleProviderRequest();
|
||||||
|
req.setId(this.provider.id);
|
||||||
|
req.setName(this.name?.value);
|
||||||
|
req.setClientId(this.clientId?.value);
|
||||||
|
req.setTeamId(this.teamId?.value);
|
||||||
|
req.setKeyId(this.keyId?.value);
|
||||||
|
req.setScopesList(this.scopesList?.value);
|
||||||
|
req.setProviderOptions(this.options);
|
||||||
|
|
||||||
|
if (this.updatePrivateKey) {
|
||||||
|
req.setPrivateKey(this.privateKey?.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
(this.service as ManagementService)
|
||||||
|
.updateAppleProvider(req)
|
||||||
|
.then((idp) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loading = false;
|
||||||
|
this.close();
|
||||||
|
}, 2000);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
} else if (PolicyComponentServiceType.ADMIN) {
|
||||||
|
const req = new AdminUpdateAppleProviderRequest();
|
||||||
|
req.setId(this.provider.id);
|
||||||
|
req.setName(this.name?.value);
|
||||||
|
req.setClientId(this.clientId?.value);
|
||||||
|
req.setTeamId(this.teamId?.value);
|
||||||
|
req.setKeyId(this.keyId?.value);
|
||||||
|
req.setScopesList(this.scopesList?.value);
|
||||||
|
req.setProviderOptions(this.options);
|
||||||
|
|
||||||
|
if (this.updatePrivateKey) {
|
||||||
|
req.setPrivateKey(this.privateKey?.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
(this.service as AdminService)
|
||||||
|
.updateAppleProvider(req)
|
||||||
|
.then((idp) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loading = false;
|
||||||
|
this.close();
|
||||||
|
}, 2000);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.loading = false;
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public close(): void {
|
||||||
|
this._location.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public onDropKey(filelist: FileList): void {
|
||||||
|
const file = filelist.item(0);
|
||||||
|
if (file) {
|
||||||
|
if (file.size > MAX_ALLOWED_SIZE) {
|
||||||
|
this.toast.showInfo('IDP.APPLE.KEYMAXSIZEEXCEEDED', true);
|
||||||
|
} else {
|
||||||
|
this.privateKey?.setValue('');
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = ((aXML) => {
|
||||||
|
return (e) => {
|
||||||
|
const keyBase64 = e.target?.result;
|
||||||
|
if (keyBase64 && typeof keyBase64 === 'string') {
|
||||||
|
const cropped = keyBase64.replace('data:application/octet-stream;base64,', '');
|
||||||
|
this.privateKey?.setValue(cropped);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})(file);
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public addScope(event: MatChipInputEvent): void {
|
||||||
|
const input = event.chipInput?.inputElement;
|
||||||
|
const value = event.value.trim();
|
||||||
|
|
||||||
|
if (value !== '') {
|
||||||
|
if (this.scopesList?.value) {
|
||||||
|
this.scopesList.value.push(value);
|
||||||
|
if (input) {
|
||||||
|
input.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeScope(uri: string): void {
|
||||||
|
if (this.scopesList?.value) {
|
||||||
|
const index = this.scopesList.value.indexOf(uri);
|
||||||
|
|
||||||
|
if (index !== undefined && index >= 0) {
|
||||||
|
this.scopesList.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get name(): AbstractControl | null {
|
||||||
|
return this.form.get('name');
|
||||||
|
}
|
||||||
|
|
||||||
|
public get clientId(): AbstractControl | null {
|
||||||
|
return this.form.get('clientId');
|
||||||
|
}
|
||||||
|
|
||||||
|
public get teamId(): AbstractControl | null {
|
||||||
|
return this.form.get('teamId');
|
||||||
|
}
|
||||||
|
|
||||||
|
public get keyId(): AbstractControl | null {
|
||||||
|
return this.form.get('keyId');
|
||||||
|
}
|
||||||
|
|
||||||
|
public get privateKey(): AbstractControl | null {
|
||||||
|
return this.form.get('privateKey');
|
||||||
|
}
|
||||||
|
|
||||||
|
public get scopesList(): AbstractControl | null {
|
||||||
|
return this.form.get('scopesList');
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
|
|||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
import { ProviderType } from 'src/app/proto/generated/zitadel/idp_pb';
|
import { ProviderType } from 'src/app/proto/generated/zitadel/idp_pb';
|
||||||
|
import { ProviderAppleComponent } from './provider-apple/provider-apple.component';
|
||||||
import { ProviderAzureADComponent } from './provider-azure-ad/provider-azure-ad.component';
|
import { ProviderAzureADComponent } from './provider-azure-ad/provider-azure-ad.component';
|
||||||
import { ProviderGithubESComponent } from './provider-github-es/provider-github-es.component';
|
import { ProviderGithubESComponent } from './provider-github-es/provider-github-es.component';
|
||||||
import { ProviderGithubComponent } from './provider-github/provider-github.component';
|
import { ProviderGithubComponent } from './provider-github/provider-github.component';
|
||||||
@ -27,6 +28,7 @@ const typeMap = {
|
|||||||
[ProviderType.PROVIDER_TYPE_OAUTH]: { path: 'oauth', component: ProviderOAuthComponent },
|
[ProviderType.PROVIDER_TYPE_OAUTH]: { path: 'oauth', component: ProviderOAuthComponent },
|
||||||
[ProviderType.PROVIDER_TYPE_OIDC]: { path: 'oidc', component: ProviderOIDCComponent },
|
[ProviderType.PROVIDER_TYPE_OIDC]: { path: 'oidc', component: ProviderOIDCComponent },
|
||||||
[ProviderType.PROVIDER_TYPE_LDAP]: { path: 'ldap', component: ProviderLDAPComponent },
|
[ProviderType.PROVIDER_TYPE_LDAP]: { path: 'ldap', component: ProviderLDAPComponent },
|
||||||
|
[ProviderType.PROVIDER_TYPE_APPLE]: { path: 'apple', component: ProviderAppleComponent },
|
||||||
};
|
};
|
||||||
|
|
||||||
const routes: Routes = Object.entries(typeMap).map(([key, value]) => {
|
const routes: Routes = Object.entries(typeMap).map(([key, value]) => {
|
||||||
|
@ -17,6 +17,7 @@ import { InfoSectionModule } from '../info-section/info-section.module';
|
|||||||
import { ProviderOptionsModule } from '../provider-options/provider-options.module';
|
import { ProviderOptionsModule } from '../provider-options/provider-options.module';
|
||||||
import { StringListModule } from '../string-list/string-list.module';
|
import { StringListModule } from '../string-list/string-list.module';
|
||||||
import { LDAPAttributesComponent } from './ldap-attributes/ldap-attributes.component';
|
import { LDAPAttributesComponent } from './ldap-attributes/ldap-attributes.component';
|
||||||
|
import { ProviderAppleComponent } from './provider-apple/provider-apple.component';
|
||||||
import { ProviderAzureADComponent } from './provider-azure-ad/provider-azure-ad.component';
|
import { ProviderAzureADComponent } from './provider-azure-ad/provider-azure-ad.component';
|
||||||
import { ProviderGithubESComponent } from './provider-github-es/provider-github-es.component';
|
import { ProviderGithubESComponent } from './provider-github-es/provider-github-es.component';
|
||||||
import { ProviderGithubComponent } from './provider-github/provider-github.component';
|
import { ProviderGithubComponent } from './provider-github/provider-github.component';
|
||||||
@ -43,6 +44,7 @@ import { ProvidersRoutingModule } from './providers-routing.module';
|
|||||||
ProviderOIDCComponent,
|
ProviderOIDCComponent,
|
||||||
ProviderOAuthComponent,
|
ProviderOAuthComponent,
|
||||||
ProviderLDAPComponent,
|
ProviderLDAPComponent,
|
||||||
|
ProviderAppleComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
ProvidersRoutingModule,
|
ProvidersRoutingModule,
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
@mixin identity-provider-theme($theme) {
|
@mixin identity-provider-theme($theme) {
|
||||||
$is-dark-theme: map-get($theme, is-dark);
|
$is-dark-theme: map-get($theme, is-dark);
|
||||||
$background: map-get($theme, background);
|
$background: map-get($theme, background);
|
||||||
|
$foreground: map-get($theme, foreground);
|
||||||
|
|
||||||
.identity-provider-desc {
|
.identity-provider-desc {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@ -19,6 +20,10 @@
|
|||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&.apple {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
&.dark {
|
&.dark {
|
||||||
display: if($is-dark-theme, block, none);
|
display: if($is-dark-theme, block, none);
|
||||||
}
|
}
|
||||||
@ -74,6 +79,58 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pk-label {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.private-key-wrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 70px;
|
||||||
|
width: 70px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
border: 1px solid map-get($foreground, divider);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.dl-btn {
|
||||||
|
z-index: 2;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
visibility: hidden;
|
||||||
|
transform: translateX(50%) translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
object-fit: contain;
|
||||||
|
object-position: center;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.icon {
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.dl-btn {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.string-list-component-wrapper {
|
.string-list-component-wrapper {
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
|
@ -248,7 +248,7 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
|
|||||||
this.metadata = resp.resultList.map((md) => {
|
this.metadata = resp.resultList.map((md) => {
|
||||||
return {
|
return {
|
||||||
key: md.key,
|
key: md.key,
|
||||||
value: Buffer.from(md.value as string, 'base64').toString('ascii'),
|
value: Buffer.from(md.value as string, 'base64').toString('utf-8'),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -521,7 +521,7 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
|||||||
get decodedBase64(): string {
|
get decodedBase64(): string {
|
||||||
const samlReq = this.samlAppRequest.toObject();
|
const samlReq = this.samlAppRequest.toObject();
|
||||||
if (samlReq && samlReq.metadataXml && typeof samlReq.metadataXml === 'string') {
|
if (samlReq && samlReq.metadataXml && typeof samlReq.metadataXml === 'string') {
|
||||||
return Buffer.from(samlReq.metadataXml, 'base64').toString('ascii');
|
return Buffer.from(samlReq.metadataXml, 'base64').toString('utf-8');
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@ -529,7 +529,7 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
set decodedBase64(xmlString) {
|
set decodedBase64(xmlString) {
|
||||||
if (this.samlAppRequest) {
|
if (this.samlAppRequest) {
|
||||||
const base64 = Buffer.from(xmlString, 'ascii').toString('base64');
|
const base64 = Buffer.from(xmlString, 'utf-8').toString('base64');
|
||||||
this.samlAppRequest.setMetadataXml(base64);
|
this.samlAppRequest.setMetadataXml(base64);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -786,7 +786,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
this.app.samlConfig.metadataXml &&
|
this.app.samlConfig.metadataXml &&
|
||||||
typeof this.app.samlConfig.metadataXml === 'string'
|
typeof this.app.samlConfig.metadataXml === 'string'
|
||||||
) {
|
) {
|
||||||
return Buffer.from(this.app?.samlConfig.metadataXml, 'base64').toString('ascii');
|
return Buffer.from(this.app?.samlConfig.metadataXml, 'base64').toString('utf-8');
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@ -794,7 +794,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
set decodedBase64(xmlString: string) {
|
set decodedBase64(xmlString: string) {
|
||||||
if (this.app && this.app.samlConfig && this.app.samlConfig.metadataXml) {
|
if (this.app && this.app.samlConfig && this.app.samlConfig.metadataXml) {
|
||||||
const base64 = Buffer.from(xmlString, 'ascii').toString('base64');
|
const base64 = Buffer.from(xmlString, 'utf-8').toString('base64');
|
||||||
|
|
||||||
if (this.app.samlConfig) {
|
if (this.app.samlConfig) {
|
||||||
this.app.samlConfig.metadataXml = base64;
|
this.app.samlConfig.metadataXml = base64;
|
||||||
|
@ -406,7 +406,7 @@ export class AuthUserDetailComponent implements OnDestroy {
|
|||||||
this.metadata = resp.resultList.map((md) => {
|
this.metadata = resp.resultList.map((md) => {
|
||||||
return {
|
return {
|
||||||
key: md.key,
|
key: md.key,
|
||||||
value: Buffer.from(md.value as string, 'base64').toString('ascii'),
|
value: Buffer.from(md.value as string, 'base64').toString('utf8'),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -508,7 +508,7 @@ export class UserDetailComponent implements OnInit {
|
|||||||
this.metadata = resp.resultList.map((md) => {
|
this.metadata = resp.resultList.map((md) => {
|
||||||
return {
|
return {
|
||||||
key: md.key,
|
key: md.key,
|
||||||
value: Buffer.from(md.value as string, 'base64').toString('ascii'),
|
value: Buffer.from(md.value as string, 'base64').toString('utf-8'),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -6,6 +6,8 @@ import {
|
|||||||
ActivateLabelPolicyResponse,
|
ActivateLabelPolicyResponse,
|
||||||
ActivateSMSProviderRequest,
|
ActivateSMSProviderRequest,
|
||||||
ActivateSMSProviderResponse,
|
ActivateSMSProviderResponse,
|
||||||
|
AddAppleProviderRequest,
|
||||||
|
AddAppleProviderResponse,
|
||||||
AddAzureADProviderRequest,
|
AddAzureADProviderRequest,
|
||||||
AddAzureADProviderResponse,
|
AddAzureADProviderResponse,
|
||||||
AddCustomDomainPolicyRequest,
|
AddCustomDomainPolicyRequest,
|
||||||
@ -214,6 +216,8 @@ import {
|
|||||||
SetSecurityPolicyResponse,
|
SetSecurityPolicyResponse,
|
||||||
SetUpOrgRequest,
|
SetUpOrgRequest,
|
||||||
SetUpOrgResponse,
|
SetUpOrgResponse,
|
||||||
|
UpdateAppleProviderRequest,
|
||||||
|
UpdateAppleProviderResponse,
|
||||||
UpdateAzureADProviderRequest,
|
UpdateAzureADProviderRequest,
|
||||||
UpdateAzureADProviderResponse,
|
UpdateAzureADProviderResponse,
|
||||||
UpdateCustomDomainPolicyRequest,
|
UpdateCustomDomainPolicyRequest,
|
||||||
@ -1173,6 +1177,14 @@ export class AdminService {
|
|||||||
return this.grpcService.admin.updateGitHubEnterpriseServerProvider(req, null).then((resp) => resp.toObject());
|
return this.grpcService.admin.updateGitHubEnterpriseServerProvider(req, null).then((resp) => resp.toObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public addAppleProvider(req: AddAppleProviderRequest): Promise<AddAppleProviderResponse.AsObject> {
|
||||||
|
return this.grpcService.admin.addAppleProvider(req, null).then((resp) => resp.toObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateAppleProvider(req: UpdateAppleProviderRequest): Promise<UpdateAppleProviderResponse.AsObject> {
|
||||||
|
return this.grpcService.admin.updateAppleProvider(req, null).then((resp) => resp.toObject());
|
||||||
|
}
|
||||||
|
|
||||||
public deleteProvider(id: string): Promise<DeleteProviderResponse.AsObject> {
|
public deleteProvider(id: string): Promise<DeleteProviderResponse.AsObject> {
|
||||||
const req = new DeleteProviderRequest();
|
const req = new DeleteProviderRequest();
|
||||||
req.setId(id);
|
req.setId(id);
|
||||||
|
@ -15,6 +15,8 @@ import {
|
|||||||
AddAPIAppResponse,
|
AddAPIAppResponse,
|
||||||
AddAppKeyRequest,
|
AddAppKeyRequest,
|
||||||
AddAppKeyResponse,
|
AddAppKeyResponse,
|
||||||
|
AddAppleProviderRequest,
|
||||||
|
AddAppleProviderResponse,
|
||||||
AddAzureADProviderRequest,
|
AddAzureADProviderRequest,
|
||||||
AddAzureADProviderResponse,
|
AddAzureADProviderResponse,
|
||||||
AddCustomLabelPolicyRequest,
|
AddCustomLabelPolicyRequest,
|
||||||
@ -441,6 +443,8 @@ import {
|
|||||||
UpdateActionResponse,
|
UpdateActionResponse,
|
||||||
UpdateAPIAppConfigRequest,
|
UpdateAPIAppConfigRequest,
|
||||||
UpdateAPIAppConfigResponse,
|
UpdateAPIAppConfigResponse,
|
||||||
|
UpdateAppleProviderRequest,
|
||||||
|
UpdateAppleProviderResponse,
|
||||||
UpdateAppRequest,
|
UpdateAppRequest,
|
||||||
UpdateAppResponse,
|
UpdateAppResponse,
|
||||||
UpdateAzureADProviderRequest,
|
UpdateAzureADProviderRequest,
|
||||||
@ -1041,6 +1045,14 @@ export class ManagementService {
|
|||||||
return this.grpcService.mgmt.updateGitHubEnterpriseServerProvider(req, null).then((resp) => resp.toObject());
|
return this.grpcService.mgmt.updateGitHubEnterpriseServerProvider(req, null).then((resp) => resp.toObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public addAppleProvider(req: AddAppleProviderRequest): Promise<AddAppleProviderResponse.AsObject> {
|
||||||
|
return this.grpcService.mgmt.addAppleProvider(req, null).then((resp) => resp.toObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateAppleProvider(req: UpdateAppleProviderRequest): Promise<UpdateAppleProviderResponse.AsObject> {
|
||||||
|
return this.grpcService.mgmt.updateAppleProvider(req, null).then((resp) => resp.toObject());
|
||||||
|
}
|
||||||
|
|
||||||
public deleteProvider(id: string): Promise<DeleteProviderResponse.AsObject> {
|
public deleteProvider(id: string): Promise<DeleteProviderResponse.AsObject> {
|
||||||
const req = new DeleteProviderRequest();
|
const req = new DeleteProviderRequest();
|
||||||
req.setId(id);
|
req.setId(id);
|
||||||
|
@ -1049,6 +1049,7 @@
|
|||||||
"TITLE": "SMTP настройки",
|
"TITLE": "SMTP настройки",
|
||||||
"SENDERADDRESS": "Имейл адрес на изпращача",
|
"SENDERADDRESS": "Имейл адрес на изпращача",
|
||||||
"SENDERNAME": "Име на изпращача",
|
"SENDERNAME": "Име на изпращача",
|
||||||
|
"REPLYTOADDRESS": "Reply-to адрес",
|
||||||
"HOSTANDPORT": "Хост и порт",
|
"HOSTANDPORT": "Хост и порт",
|
||||||
"USER": "Потребител",
|
"USER": "Потребител",
|
||||||
"PASSWORD": "Парола",
|
"PASSWORD": "Парола",
|
||||||
|
@ -1055,6 +1055,7 @@
|
|||||||
"TITLE": "SMTP Einstellungen",
|
"TITLE": "SMTP Einstellungen",
|
||||||
"SENDERADDRESS": "Sender Email-Adresse",
|
"SENDERADDRESS": "Sender Email-Adresse",
|
||||||
"SENDERNAME": "Sender Name",
|
"SENDERNAME": "Sender Name",
|
||||||
|
"REPLYTOADDRESS": "Reply-to-Adresse",
|
||||||
"HOSTANDPORT": "Host und Port",
|
"HOSTANDPORT": "Host und Port",
|
||||||
"USER": "Benutzer",
|
"USER": "Benutzer",
|
||||||
"PASSWORD": "Passwort",
|
"PASSWORD": "Passwort",
|
||||||
|
@ -1056,6 +1056,7 @@
|
|||||||
"TITLE": "SMTP Settings",
|
"TITLE": "SMTP Settings",
|
||||||
"SENDERADDRESS": "Sender Email Address",
|
"SENDERADDRESS": "Sender Email Address",
|
||||||
"SENDERNAME": "Sender Name",
|
"SENDERNAME": "Sender Name",
|
||||||
|
"REPLYTOADDRESS": "Reply-to Address",
|
||||||
"HOSTANDPORT": "Host And Port",
|
"HOSTANDPORT": "Host And Port",
|
||||||
"USER": "User",
|
"USER": "User",
|
||||||
"PASSWORD": "Password",
|
"PASSWORD": "Password",
|
||||||
@ -1705,6 +1706,10 @@
|
|||||||
"LDAP": {
|
"LDAP": {
|
||||||
"TITLE": "Active Directory / LDAP",
|
"TITLE": "Active Directory / LDAP",
|
||||||
"DESCRIPTION": "Enter the credentials for your LDAP Provider"
|
"DESCRIPTION": "Enter the credentials for your LDAP Provider"
|
||||||
|
},
|
||||||
|
"APPLE": {
|
||||||
|
"TITLE": "Sign in with Apple",
|
||||||
|
"DESCRIPTION": "Enter the credentials for your Apple Provider"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"DETAIL": {
|
"DETAIL": {
|
||||||
@ -1818,6 +1823,14 @@
|
|||||||
"JWTENDPOINT": "JWT Endpoint",
|
"JWTENDPOINT": "JWT Endpoint",
|
||||||
"JWTKEYSENDPOINT": "JWT Keys Endpoint"
|
"JWTKEYSENDPOINT": "JWT Keys Endpoint"
|
||||||
},
|
},
|
||||||
|
"APPLE": {
|
||||||
|
"TEAMID": "Team ID",
|
||||||
|
"KEYID": "Key ID",
|
||||||
|
"PRIVATEKEY": "Private Key",
|
||||||
|
"UPDATEPRIVATEKEY": "Update Private Key",
|
||||||
|
"UPLOADPRIVATEKEY": "Upload Private Key",
|
||||||
|
"KEYMAXSIZEEXCEEDED": "Maximum size of 5kB exceeded."
|
||||||
|
},
|
||||||
"TOAST": {
|
"TOAST": {
|
||||||
"SAVED": "Successfully saved.",
|
"SAVED": "Successfully saved.",
|
||||||
"REACTIVATED": "Idp reactivated.",
|
"REACTIVATED": "Idp reactivated.",
|
||||||
|
@ -1056,6 +1056,7 @@
|
|||||||
"TITLE": "Ajustes SMTP",
|
"TITLE": "Ajustes SMTP",
|
||||||
"SENDERADDRESS": "Dirección email del emisor",
|
"SENDERADDRESS": "Dirección email del emisor",
|
||||||
"SENDERNAME": "Nombre del emisor",
|
"SENDERNAME": "Nombre del emisor",
|
||||||
|
"REPLYTOADDRESS": "Dirección Reply-To",
|
||||||
"HOSTANDPORT": "Servidor y puerto",
|
"HOSTANDPORT": "Servidor y puerto",
|
||||||
"USER": "Usuario",
|
"USER": "Usuario",
|
||||||
"PASSWORD": "Contraseña",
|
"PASSWORD": "Contraseña",
|
||||||
|
@ -1055,6 +1055,7 @@
|
|||||||
"TITLE": "Paramètres SMTP",
|
"TITLE": "Paramètres SMTP",
|
||||||
"SENDERADDRESS": "Adresse e-mail de l'expéditeur",
|
"SENDERADDRESS": "Adresse e-mail de l'expéditeur",
|
||||||
"SENDERNAME": "Nom de l'expéditeur",
|
"SENDERNAME": "Nom de l'expéditeur",
|
||||||
|
"REPLYTOADDRESS": "Adresse Reply-to",
|
||||||
"HOSTANDPORT": "Hôte et port",
|
"HOSTANDPORT": "Hôte et port",
|
||||||
"USER": "Utilisateur",
|
"USER": "Utilisateur",
|
||||||
"PASSWORD": "Mot de passe",
|
"PASSWORD": "Mot de passe",
|
||||||
|
@ -1055,6 +1055,7 @@
|
|||||||
"TITLE": "Impostazioni SMTP",
|
"TITLE": "Impostazioni SMTP",
|
||||||
"SENDERADDRESS": "Indirizzo email del mittente",
|
"SENDERADDRESS": "Indirizzo email del mittente",
|
||||||
"SENDERNAME": "Nome del mittente",
|
"SENDERNAME": "Nome del mittente",
|
||||||
|
"REPLYTOADDRESS": "Indirizzo Reply-to",
|
||||||
"HOSTANDPORT": "Host e porta",
|
"HOSTANDPORT": "Host e porta",
|
||||||
"USER": "Utente",
|
"USER": "Utente",
|
||||||
"PASSWORD": "Password",
|
"PASSWORD": "Password",
|
||||||
|
@ -1056,6 +1056,7 @@
|
|||||||
"TITLE": "SMTP設定",
|
"TITLE": "SMTP設定",
|
||||||
"SENDERADDRESS": "送信者のメールアドレス",
|
"SENDERADDRESS": "送信者のメールアドレス",
|
||||||
"SENDERNAME": "送信者名",
|
"SENDERNAME": "送信者名",
|
||||||
|
"REPLYTOADDRESS": "返信先アドレス",
|
||||||
"HOSTANDPORT": "ホストとポート",
|
"HOSTANDPORT": "ホストとポート",
|
||||||
"USER": "ユーザー",
|
"USER": "ユーザー",
|
||||||
"PASSWORD": "パスワード",
|
"PASSWORD": "パスワード",
|
||||||
|
@ -1056,6 +1056,7 @@
|
|||||||
"TITLE": "SMTP подесувања",
|
"TITLE": "SMTP подесувања",
|
||||||
"SENDERADDRESS": "Адреса на испраќачот",
|
"SENDERADDRESS": "Адреса на испраќачот",
|
||||||
"SENDERNAME": "Име на испраќачот",
|
"SENDERNAME": "Име на испраќачот",
|
||||||
|
"REPLYTOADDRESS": "Reply-to адреса",
|
||||||
"HOSTANDPORT": "Host и Port",
|
"HOSTANDPORT": "Host и Port",
|
||||||
"USER": "Корисник",
|
"USER": "Корисник",
|
||||||
"PASSWORD": "Лозинка",
|
"PASSWORD": "Лозинка",
|
||||||
|
@ -1055,6 +1055,7 @@
|
|||||||
"TITLE": "Ustawienia SMTP",
|
"TITLE": "Ustawienia SMTP",
|
||||||
"SENDERADDRESS": "Adres e-mail nadawcy",
|
"SENDERADDRESS": "Adres e-mail nadawcy",
|
||||||
"SENDERNAME": "Nazwa nadawcy",
|
"SENDERNAME": "Nazwa nadawcy",
|
||||||
|
"REPLYTOADDRESS": "Adres Reply-to",
|
||||||
"HOSTANDPORT": "Host i port",
|
"HOSTANDPORT": "Host i port",
|
||||||
"USER": "Użytkownik",
|
"USER": "Użytkownik",
|
||||||
"PASSWORD": "Hasło",
|
"PASSWORD": "Hasło",
|
||||||
|
@ -1056,6 +1056,7 @@
|
|||||||
"TITLE": "Configurações SMTP",
|
"TITLE": "Configurações SMTP",
|
||||||
"SENDERADDRESS": "Endereço de e-mail do remetente",
|
"SENDERADDRESS": "Endereço de e-mail do remetente",
|
||||||
"SENDERNAME": "Nome do remetente",
|
"SENDERNAME": "Nome do remetente",
|
||||||
|
"REPLYTOADDRESS": "Endereço Reply-To",
|
||||||
"HOSTANDPORT": "Host e porta",
|
"HOSTANDPORT": "Host e porta",
|
||||||
"USER": "Usuário",
|
"USER": "Usuário",
|
||||||
"PASSWORD": "Senha",
|
"PASSWORD": "Senha",
|
||||||
|
@ -1055,6 +1055,7 @@
|
|||||||
"TITLE": "SMTP 设置",
|
"TITLE": "SMTP 设置",
|
||||||
"SENDERADDRESS": "发件人地址",
|
"SENDERADDRESS": "发件人地址",
|
||||||
"SENDERNAME": "发件人名称",
|
"SENDERNAME": "发件人名称",
|
||||||
|
"REPLYTOADDRESS": "Reply-to 地址",
|
||||||
"HOSTANDPORT": "主机和端口",
|
"HOSTANDPORT": "主机和端口",
|
||||||
"USER": "用户名",
|
"USER": "用户名",
|
||||||
"PASSWORD": "密码",
|
"PASSWORD": "密码",
|
||||||
|
10
console/src/assets/images/idp/apple-dark.svg
Executable file
10
console/src/assets/images/idp/apple-dark.svg
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="56px" height="56px" viewBox="18 15.5 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 61 (89581) - https://sketch.com -->
|
||||||
|
<title>White Logo Square </title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<g id="White-Logo-Square-" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<rect id="Rectangle" fill="" x="6" y="6" width="44" height="44"></rect>
|
||||||
|
<path d="M28.2226562,20.3846154 C29.0546875,20.3846154 30.0976562,19.8048315 30.71875,19.0317864 C31.28125,18.3312142 31.6914062,17.352829 31.6914062,16.3744437 C31.6914062,16.2415766 31.6796875,16.1087095 31.65625,16 C30.7304687,16.0362365 29.6171875,16.640178 28.9492187,17.4494596 C28.421875,18.06548 27.9414062,19.0317864 27.9414062,20.0222505 C27.9414062,20.1671964 27.9648438,20.3121424 27.9765625,20.3604577 C28.0351562,20.3725366 28.1289062,20.3846154 28.2226562,20.3846154 Z M25.2929688,35 C26.4296875,35 26.9335938,34.214876 28.3515625,34.214876 C29.7929688,34.214876 30.109375,34.9758423 31.375,34.9758423 C32.6171875,34.9758423 33.4492188,33.792117 34.234375,32.6325493 C35.1132812,31.3038779 35.4765625,29.9993643 35.5,29.9389701 C35.4179688,29.9148125 33.0390625,28.9122695 33.0390625,26.0979021 C33.0390625,23.6579784 34.9140625,22.5588048 35.0195312,22.474253 C33.7773438,20.6382708 31.890625,20.5899555 31.375,20.5899555 C29.9804688,20.5899555 28.84375,21.4596313 28.1289062,21.4596313 C27.3554688,21.4596313 26.3359375,20.6382708 25.1289062,20.6382708 C22.8320312,20.6382708 20.5,22.5950413 20.5,26.2911634 C20.5,28.5861411 21.3671875,31.013986 22.4335938,32.5842339 C23.3476562,33.9129053 24.1445312,35 25.2929688,35 Z" id="" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
10
console/src/assets/images/idp/apple.svg
Executable file
10
console/src/assets/images/idp/apple.svg
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="56px" height="56px" viewBox="18 15.5 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 61 (89581) - https://sketch.com -->
|
||||||
|
<title>Black Logo Square</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<g id="Black-Logo-Square" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<rect id="Rectangle" fill="" x="6" y="6" width="44" height="44"></rect>
|
||||||
|
<path d="M28.2226562,20.3846154 C29.0546875,20.3846154 30.0976562,19.8048315 30.71875,19.0317864 C31.28125,18.3312142 31.6914062,17.352829 31.6914062,16.3744437 C31.6914062,16.2415766 31.6796875,16.1087095 31.65625,16 C30.7304687,16.0362365 29.6171875,16.640178 28.9492187,17.4494596 C28.421875,18.06548 27.9414062,19.0317864 27.9414062,20.0222505 C27.9414062,20.1671964 27.9648438,20.3121424 27.9765625,20.3604577 C28.0351562,20.3725366 28.1289062,20.3846154 28.2226562,20.3846154 Z M25.2929688,35 C26.4296875,35 26.9335938,34.214876 28.3515625,34.214876 C29.7929688,34.214876 30.109375,34.9758423 31.375,34.9758423 C32.6171875,34.9758423 33.4492188,33.792117 34.234375,32.6325493 C35.1132812,31.3038779 35.4765625,29.9993643 35.5,29.9389701 C35.4179688,29.9148125 33.0390625,28.9122695 33.0390625,26.0979021 C33.0390625,23.6579784 34.9140625,22.5588048 35.0195312,22.474253 C33.7773438,20.6382708 31.890625,20.5899555 31.375,20.5899555 C29.9804688,20.5899555 28.84375,21.4596313 28.1289062,21.4596313 C27.3554688,21.4596313 26.3359375,20.6382708 25.1289062,20.6382708 C22.8320312,20.6382708 20.5,22.5950413 20.5,26.2911634 C20.5,28.5861411 21.3671875,31.013986 22.4335938,32.5842339 C23.3476562,33.9129053 24.1445312,35 25.2929688,35 Z" id="" fill="#000000" fill-rule="nonzero"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -429,6 +429,7 @@ $custom-typography: mat.define-legacy-typography-config(
|
|||||||
background-color: map-get($background, cards);
|
background-color: map-get($background, cards);
|
||||||
transition: background-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
transition: background-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
min-height: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-option {
|
.mat-option {
|
||||||
@ -500,6 +501,7 @@ $custom-typography: mat.define-legacy-typography-config(
|
|||||||
background-color: map-get($background, cards);
|
background-color: map-get($background, cards);
|
||||||
transition: background-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
transition: background-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
min-height: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-option {
|
.mat-option {
|
||||||
|
@ -26,7 +26,7 @@ In the response, you will get an authentication URL of the provider you like.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl --request POST \
|
curl --request POST \
|
||||||
--url https://$ZITADEL_DOMAIN/v2alpha/idp_intents/start \
|
--url https://$ZITADEL_DOMAIN/v2alpha/idp_intents \
|
||||||
--header 'Accept: application/json' \
|
--header 'Accept: application/json' \
|
||||||
--header 'Authorization: Bearer '"$TOKEN"''\
|
--header 'Authorization: Bearer '"$TOKEN"''\
|
||||||
--header 'Content-Type: application/json' \
|
--header 'Content-Type: application/json' \
|
||||||
|
@ -212,7 +212,7 @@ Next step is to authenticate the user with the new registered passkey.
|
|||||||
### Create Session
|
### Create Session
|
||||||
|
|
||||||
First step is to ask the user for his username and create a new session with the ZITADEL API.
|
First step is to ask the user for his username and create a new session with the ZITADEL API.
|
||||||
When creating the new session make sure to include the challenge for passkey.
|
When creating the new session make sure to include the challenge for passkey, resp. webAuthN with a required user verification and the domain of your login UI.
|
||||||
The response will include the public key credential request options for the passkey in the challenges.
|
The response will include the public key credential request options for the passkey in the challenges.
|
||||||
|
|
||||||
More detailed information about the API: [Create Session Documentation](/apis/resources/session_service/session-service-create-session)
|
More detailed information about the API: [Create Session Documentation](/apis/resources/session_service/session-service-create-session)
|
||||||
@ -231,9 +231,12 @@ curl --request POST \
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"challenges": [
|
"challenges": {
|
||||||
"CHALLENGE_KIND_PASSKEY"
|
"webAuthN": {
|
||||||
]
|
"domain": "example.domain.com",
|
||||||
|
"userVerificationRequirement": "USER_VERIFICATION_REQUIREMENT_REQUIRED"
|
||||||
|
}
|
||||||
|
}
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -248,7 +251,7 @@ Example Response:
|
|||||||
"sessionId": "d654e6ba-70a3-48ef-a95d-37c8d8a7901a",
|
"sessionId": "d654e6ba-70a3-48ef-a95d-37c8d8a7901a",
|
||||||
"sessionToken": "string",
|
"sessionToken": "string",
|
||||||
"challenges": {
|
"challenges": {
|
||||||
"passkey": {
|
"webAuthN": {
|
||||||
"publicKeyCredentialRequestOptions": {
|
"publicKeyCredentialRequestOptions": {
|
||||||
"publicKey": {
|
"publicKey": {
|
||||||
"allowCredentials": [
|
"allowCredentials": [
|
||||||
@ -274,7 +277,7 @@ After starting the passkey authentication on the side of ZITADEL you have to cha
|
|||||||
To do this you need to call the browser API to get the credentials.
|
To do this you need to call the browser API to get the credentials.
|
||||||
Make sure to send the public key credential request options you got from ZITADEL.
|
Make sure to send the public key credential request options you got from ZITADEL.
|
||||||
|
|
||||||
```bash
|
```javascript
|
||||||
const credential = await navigator.credentials.get({
|
const credential = await navigator.credentials.get({
|
||||||
publicKey: publicKeyCredentialRequestOptions
|
publicKey: publicKeyCredentialRequestOptions
|
||||||
});
|
});
|
||||||
@ -300,7 +303,7 @@ curl --request PATCH \
|
|||||||
--data '{
|
--data '{
|
||||||
"sessionToken": "yMDi6uVPJAcphbbz0LaxC07ihWkNTe7m0Xqch8SzfM5Cz3HSIQIDZ65x1f5Qal0jxz0MEyo-_zYcUg",
|
"sessionToken": "yMDi6uVPJAcphbbz0LaxC07ihWkNTe7m0Xqch8SzfM5Cz3HSIQIDZ65x1f5Qal0jxz0MEyo-_zYcUg",
|
||||||
"checks": {
|
"checks": {
|
||||||
"passkey": {
|
"webAuthN": {
|
||||||
"credentialAssertionData": {}
|
"credentialAssertionData": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ Authorization: "Basic " + base64( formUrlEncode(client_id) + ":" + formUrlEncode
|
|||||||
|
|
||||||
The request from the API to the introspection endpoint should be in the following format:
|
The request from the API to the introspection endpoint should be in the following format:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
curl --request POST \
|
curl --request POST \
|
||||||
--url {your_domain}/oauth/v2/introspect \
|
--url {your_domain}/oauth/v2/introspect \
|
||||||
--header 'Content-Type: application/x-www-form-urlencoded' \
|
--header 'Content-Type: application/x-www-form-urlencoded' \
|
||||||
@ -85,7 +85,7 @@ curl --request POST \
|
|||||||
|
|
||||||
Here's an example of how this is done in Python code:
|
Here's an example of how this is done in Python code:
|
||||||
|
|
||||||
```
|
```python
|
||||||
def introspect_token(self, token_string):
|
def introspect_token(self, token_string):
|
||||||
url = ZITADEL_INTROSPECTION_URL
|
url = ZITADEL_INTROSPECTION_URL
|
||||||
data = {'token': token_string, 'token_type_hint': 'access_token', 'scope': 'openid'}
|
data = {'token': token_string, 'token_type_hint': 'access_token', 'scope': 'openid'}
|
||||||
|
@ -31,7 +31,7 @@ you can setup ZITADEL and either
|
|||||||
# Install CockroachDB
|
# Install CockroachDB
|
||||||
helm install crdb cockroachdb/cockroachdb \
|
helm install crdb cockroachdb/cockroachdb \
|
||||||
--set fullnameOverride=crdb \
|
--set fullnameOverride=crdb \
|
||||||
--set single-node=true \
|
--set conf.single-node=true \
|
||||||
--set statefulset.replicas=1
|
--set statefulset.replicas=1
|
||||||
|
|
||||||
# Install ZITADEL
|
# Install ZITADEL
|
||||||
@ -65,7 +65,7 @@ With this setup you only get a key for a service account. Logging in at ZITADEL
|
|||||||
# Install CockroachDB
|
# Install CockroachDB
|
||||||
helm install crdb cockroachdb/cockroachdb \
|
helm install crdb cockroachdb/cockroachdb \
|
||||||
--set fullnameOverride=crdb \
|
--set fullnameOverride=crdb \
|
||||||
--set single-node=true \
|
--set conf.single-node=true \
|
||||||
--set statefulset.replicas=1
|
--set statefulset.replicas=1
|
||||||
|
|
||||||
# Install ZITADEL
|
# Install ZITADEL
|
||||||
|
@ -23,16 +23,16 @@ By executing the commands below, you will download the following files:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Download the docker compose example configuration for a secure CockroachDB.
|
# Download the docker compose example configuration for a secure CockroachDB.
|
||||||
wget https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/manage/production/docker-compose.yaml
|
wget https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/manage/configure/docker-compose.yaml
|
||||||
|
|
||||||
# Download and adjust the example configuration file containing standard configuration.
|
# Download and adjust the example configuration file containing standard configuration.
|
||||||
wget https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/manage/production/example-zitadel-config.yaml
|
wget https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/manage/configure/example-zitadel-config.yaml
|
||||||
|
|
||||||
# Download and adjust the example configuration file containing secret configuration.
|
# Download and adjust the example configuration file containing secret configuration.
|
||||||
wget https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/manage/production/example-zitadel-secrets.yaml
|
wget https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/manage/configure/example-zitadel-secrets.yaml
|
||||||
|
|
||||||
# Download and adjust the example configuration file containing database initialization configuration.
|
# Download and adjust the example configuration file containing database initialization configuration.
|
||||||
wget https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/manage/production/example-zitadel-init-steps.yaml
|
wget https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/manage/configure/example-zitadel-init-steps.yaml
|
||||||
|
|
||||||
# A single ZITADEL instance always needs the same 32 characters long masterkey
|
# A single ZITADEL instance always needs the same 32 characters long masterkey
|
||||||
# If you haven't done so already, you can generate a new one
|
# If you haven't done so already, you can generate a new one
|
||||||
|
@ -200,6 +200,7 @@ DefaultInstance:
|
|||||||
# if the host of the sender is different from ExternalDomain set DefaultInstance.DomainPolicy.SMTPSenderAddressMatchesInstanceDomain to false
|
# if the host of the sender is different from ExternalDomain set DefaultInstance.DomainPolicy.SMTPSenderAddressMatchesInstanceDomain to false
|
||||||
From:
|
From:
|
||||||
FromName:
|
FromName:
|
||||||
|
ReplyToAddress:
|
||||||
```
|
```
|
||||||
|
|
||||||
- If you don't want to use the DefaultInstance configuration for the first instance that ZITADEL automatically creates for you during the [setup phase](/self-hosting/manage/configure#database-initialization), you can provide a FirstInstance YAML section using the --steps argument.
|
- If you don't want to use the DefaultInstance configuration for the first instance that ZITADEL automatically creates for you during the [setup phase](/self-hosting/manage/configure#database-initialization), you can provide a FirstInstance YAML section using the --steps argument.
|
||||||
|
@ -13,8 +13,8 @@ To address this, we are going to change this behavior so that users will be auto
|
|||||||
|
|
||||||
## Statement
|
## Statement
|
||||||
|
|
||||||
This behaviour change is tracked in the following issue: [Reuse current session if no prompt is selected ](https://github.com/zitadel/zitadel/issues/4841)
|
This behaviour change was tracked in the following issue: [Reuse current session if no prompt is selected](https://github.com/zitadel/zitadel/issues/4841)
|
||||||
As soon as the release version is published, we will include the version here.
|
and released in Version [v2.32.0](https://github.com/zitadel/zitadel/releases/tag/v2.32.0)
|
||||||
|
|
||||||
## Mitigation
|
## Mitigation
|
||||||
|
|
||||||
|
26
docs/docs/support/advisory/a10001.md
Normal file
26
docs/docs/support/advisory/a10001.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
title: Technical Advisory 10001
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Currently, disabling the `Allow Register` setting in the Login Policy, will disable any registration - local and through External Identity Providers (IDP).
|
||||||
|
This might be a good solution, if you manage all users yourself and do not want them to create any new account.
|
||||||
|
If you on the other hand want users to be able to federate their accounts from another IDP and only want to disable local registration, there's currently no option to do so.
|
||||||
|
|
||||||
|
Further ZITADEL provided the possibility to disable registration on each IDP with the introduction of IDP Templates.
|
||||||
|
|
||||||
|
To address this, we are going to change the behavior of the setting mentioned above, so that if disable, it will only prevent local registration. Registration of a federated user will still be possible - if not disabled by the corresponding IDP Template.
|
||||||
|
|
||||||
|
## Statement
|
||||||
|
|
||||||
|
This behaviour change is tracked in the following PR: [Restrict AllowRegistration check to local registration](https://github.com/zitadel/zitadel/pull/5939).
|
||||||
|
As soon as the release version is published, we will include the version here.
|
||||||
|
|
||||||
|
## Mitigation
|
||||||
|
|
||||||
|
If you want to prevent user creation / registration through an IDP, be sure to disable the `isCreationAllowed` option on the desired IDP Templates.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
Once this update has been released and deployed, the `Allow Register` setting in the Login Policy will only affect local registrations and users might be able to create a ZITADEL account through an IDP, depending on your IDP provider options.
|
@ -26,6 +26,14 @@ We understand that these advisories may include breaking changes, and we aim to
|
|||||||
<td>2.32.0</td>
|
<td>2.32.0</td>
|
||||||
<td>Calendar week 32</td>
|
<td>Calendar week 32</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="./advisory/a10001">A-10001</a></td>
|
||||||
|
<td>Login Policy - Allow Register</td>
|
||||||
|
<td>Breaking Behaviour Change</td>
|
||||||
|
<td>When disabling the option, users are currently not able to register locally and also not through an external IDP. With the upcoming change, the setting will only prevent local registration. Restriction to Identity Providers can be managed through the corresponding IDP Template. No action is required on your side if this is the intended behaviour or if you already disabled registration on your IDP.</td>
|
||||||
|
<td>TBD</td>
|
||||||
|
<td>Calendar week 34/35</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
## Subscribe to our Mailing List
|
## Subscribe to our Mailing List
|
||||||
|
@ -20,7 +20,7 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
customFields: {
|
customFields: {
|
||||||
description:
|
description:
|
||||||
"Documentation for ZITADEL - The best of Auth0 and Keycloak combined. Built for the serverless era.",
|
"Documentation for ZITADEL - Identity infrastructure, simplified for you.",
|
||||||
},
|
},
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
metadata: [
|
metadata: [
|
||||||
|
@ -284,6 +284,31 @@ module.exports = {
|
|||||||
"guides/integrate/services/auth0-oidc",
|
"guides/integrate/services/auth0-oidc",
|
||||||
"guides/integrate/services/auth0-saml",
|
"guides/integrate/services/auth0-saml",
|
||||||
"guides/integrate/services/pingidentity-saml",
|
"guides/integrate/services/pingidentity-saml",
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
label: 'Nextcloud',
|
||||||
|
href: 'https://zitadel.com/blog/zitadel-as-sso-provider-for-selfhosting',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
label: 'Cloudflare workers',
|
||||||
|
href: 'https://zitadel.com/blog/increase-spa-security-with-cloudflare-workers',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
label: 'Firezone (firezone.dev)',
|
||||||
|
href: 'https://www.firezone.dev/docs/authenticate/oidc/zitadel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
label: 'Psono (psono.com)',
|
||||||
|
href: 'https://doc.psono.com/admin/configuration/oidc-zitadel.html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
label: 'Netbird (netbird.io)',
|
||||||
|
href: 'https://docs.netbird.io/selfhosted/identity-providers',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
4717
docs/yarn.lock
4717
docs/yarn.lock
File diff suppressed because it is too large
Load Diff
2
go.mod
2
go.mod
@ -1,6 +1,6 @@
|
|||||||
module github.com/zitadel/zitadel
|
module github.com/zitadel/zitadel
|
||||||
|
|
||||||
go 1.20
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/storage v1.32.0
|
cloud.google.com/go/storage v1.32.0
|
||||||
|
@ -42,7 +42,7 @@ func (h *Handler) Commands() *command.Commands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) ErrorHandler() ErrorHandler {
|
func (h *Handler) ErrorHandler() ErrorHandler {
|
||||||
return DefaultErrorHandler
|
return h.errorHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) Storage() static.Storage {
|
func (h *Handler) Storage() static.Storage {
|
||||||
@ -75,10 +75,14 @@ type Downloader interface {
|
|||||||
ResourceOwner(ctx context.Context, ownerPath string) string
|
ResourceOwner(ctx context.Context, ownerPath string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrorHandler func(http.ResponseWriter, *http.Request, error, int)
|
type ErrorHandler func(w http.ResponseWriter, r *http.Request, err error, defaultCode int)
|
||||||
|
|
||||||
func DefaultErrorHandler(w http.ResponseWriter, r *http.Request, err error, code int) {
|
func DefaultErrorHandler(w http.ResponseWriter, r *http.Request, err error, defaultCode int) {
|
||||||
logging.WithFields("uri", r.RequestURI).WithError(err).Warn("error occurred on asset api")
|
logging.WithFields("uri", r.RequestURI).WithError(err).Warn("error occurred on asset api")
|
||||||
|
code, ok := http_util.ZitadelErrorToHTTPStatusCode(err)
|
||||||
|
if !ok {
|
||||||
|
code = defaultCode
|
||||||
|
}
|
||||||
http.Error(w, err.Error(), code)
|
http.Error(w, err.Error(), code)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +166,7 @@ func UploadHandleFunc(s AssetsService, uploader Uploader) func(http.ResponseWrit
|
|||||||
}
|
}
|
||||||
err = uploader.UploadAsset(ctx, ctxData.OrgID, uploadInfo, s.Commands())
|
err = uploader.UploadAsset(ctx, ctxData.OrgID, uploadInfo, s.Commands())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.ErrorHandler()(w, r, fmt.Errorf("upload failed: %v", err), http.StatusInternalServerError)
|
s.ErrorHandler()(w, r, fmt.Errorf("upload failed: %w", err), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,10 +194,6 @@ func DownloadHandleFunc(s AssetsService, downloader Downloader) func(http.Respon
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = GetAsset(w, r, resourceOwner, objectName, s.Storage()); err != nil {
|
if err = GetAsset(w, r, resourceOwner, objectName, s.Storage()); err != nil {
|
||||||
if strings.Contains(err.Error(), "DATAB-pCP8P") {
|
|
||||||
s.ErrorHandler()(w, r, err, http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.ErrorHandler()(w, r, err, http.StatusInternalServerError)
|
s.ErrorHandler()(w, r, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,11 +206,11 @@ func GetAsset(w http.ResponseWriter, r *http.Request, resourceOwner, objectName
|
|||||||
}
|
}
|
||||||
data, getInfo, err := storage.GetObject(r.Context(), authz.GetInstance(r.Context()).InstanceID(), resourceOwner, objectName)
|
data, getInfo, err := storage.GetObject(r.Context(), authz.GetInstance(r.Context()).InstanceID(), resourceOwner, objectName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("download failed: %v", err)
|
return fmt.Errorf("download failed: %w", err)
|
||||||
}
|
}
|
||||||
info, err := getInfo()
|
info, err := getInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("download failed: %v", err)
|
return fmt.Errorf("download failed: %w", err)
|
||||||
}
|
}
|
||||||
if info.Hash == strings.Trim(r.Header.Get(http_util.IfNoneMatch), "\"") {
|
if info.Hash == strings.Trim(r.Header.Get(http_util.IfNoneMatch), "\"") {
|
||||||
w.Header().Set(http_util.LastModified, info.LastModified.Format(time.RFC1123))
|
w.Header().Set(http_util.LastModified, info.LastModified.Format(time.RFC1123))
|
||||||
|
@ -132,9 +132,10 @@ func SecretGeneratorTypeToDomain(generatorType settings_pb.SecretGeneratorType)
|
|||||||
|
|
||||||
func AddSMTPToConfig(req *admin_pb.AddSMTPConfigRequest) *smtp.Config {
|
func AddSMTPToConfig(req *admin_pb.AddSMTPConfigRequest) *smtp.Config {
|
||||||
return &smtp.Config{
|
return &smtp.Config{
|
||||||
Tls: req.Tls,
|
Tls: req.Tls,
|
||||||
From: req.SenderAddress,
|
From: req.SenderAddress,
|
||||||
FromName: req.SenderName,
|
FromName: req.SenderName,
|
||||||
|
ReplyToAddress: req.ReplyToAddress,
|
||||||
SMTP: smtp.SMTP{
|
SMTP: smtp.SMTP{
|
||||||
Host: req.Host,
|
Host: req.Host,
|
||||||
User: req.User,
|
User: req.User,
|
||||||
@ -145,9 +146,10 @@ func AddSMTPToConfig(req *admin_pb.AddSMTPConfigRequest) *smtp.Config {
|
|||||||
|
|
||||||
func UpdateSMTPToConfig(req *admin_pb.UpdateSMTPConfigRequest) *smtp.Config {
|
func UpdateSMTPToConfig(req *admin_pb.UpdateSMTPConfigRequest) *smtp.Config {
|
||||||
return &smtp.Config{
|
return &smtp.Config{
|
||||||
Tls: req.Tls,
|
Tls: req.Tls,
|
||||||
From: req.SenderAddress,
|
From: req.SenderAddress,
|
||||||
FromName: req.SenderName,
|
FromName: req.SenderName,
|
||||||
|
ReplyToAddress: req.ReplyToAddress,
|
||||||
SMTP: smtp.SMTP{
|
SMTP: smtp.SMTP{
|
||||||
Host: req.Host,
|
Host: req.Host,
|
||||||
User: req.User,
|
User: req.User,
|
||||||
@ -157,12 +159,13 @@ func UpdateSMTPToConfig(req *admin_pb.UpdateSMTPConfigRequest) *smtp.Config {
|
|||||||
|
|
||||||
func SMTPConfigToPb(smtp *query.SMTPConfig) *settings_pb.SMTPConfig {
|
func SMTPConfigToPb(smtp *query.SMTPConfig) *settings_pb.SMTPConfig {
|
||||||
mapped := &settings_pb.SMTPConfig{
|
mapped := &settings_pb.SMTPConfig{
|
||||||
Tls: smtp.TLS,
|
Tls: smtp.TLS,
|
||||||
SenderAddress: smtp.SenderAddress,
|
SenderAddress: smtp.SenderAddress,
|
||||||
SenderName: smtp.SenderName,
|
SenderName: smtp.SenderName,
|
||||||
Host: smtp.Host,
|
ReplyToAddress: smtp.ReplyToAddress,
|
||||||
User: smtp.User,
|
Host: smtp.Host,
|
||||||
Details: obj_grpc.ToViewDetailsPb(smtp.Sequence, smtp.CreationDate, smtp.ChangeDate, smtp.AggregateID),
|
User: smtp.User,
|
||||||
|
Details: obj_grpc.ToViewDetailsPb(smtp.Sequence, smtp.CreationDate, smtp.ChangeDate, smtp.AggregateID),
|
||||||
}
|
}
|
||||||
return mapped
|
return mapped
|
||||||
}
|
}
|
||||||
|
@ -405,6 +405,27 @@ func (s *Server) UpdateLDAPProvider(ctx context.Context, req *admin_pb.UpdateLDA
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) AddAppleProvider(ctx context.Context, req *admin_pb.AddAppleProviderRequest) (*admin_pb.AddAppleProviderResponse, error) {
|
||||||
|
id, details, err := s.command.AddInstanceAppleProvider(ctx, addAppleProviderToCommand(req))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &admin_pb.AddAppleProviderResponse{
|
||||||
|
Id: id,
|
||||||
|
Details: object_pb.DomainToAddDetailsPb(details),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) UpdateAppleProvider(ctx context.Context, req *admin_pb.UpdateAppleProviderRequest) (*admin_pb.UpdateAppleProviderResponse, error) {
|
||||||
|
details, err := s.command.UpdateInstanceAppleProvider(ctx, req.Id, updateAppleProviderToCommand(req))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &admin_pb.UpdateAppleProviderResponse{
|
||||||
|
Details: object_pb.DomainToChangeDetailsPb(details),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) DeleteProvider(ctx context.Context, req *admin_pb.DeleteProviderRequest) (*admin_pb.DeleteProviderResponse, error) {
|
func (s *Server) DeleteProvider(ctx context.Context, req *admin_pb.DeleteProviderRequest) (*admin_pb.DeleteProviderResponse, error) {
|
||||||
details, err := s.command.DeleteInstanceProvider(ctx, req.Id)
|
details, err := s.command.DeleteInstanceProvider(ctx, req.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -440,3 +440,27 @@ func updateLDAPProviderToCommand(req *admin_pb.UpdateLDAPProviderRequest) comman
|
|||||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addAppleProviderToCommand(req *admin_pb.AddAppleProviderRequest) command.AppleProvider {
|
||||||
|
return command.AppleProvider{
|
||||||
|
Name: req.Name,
|
||||||
|
ClientID: req.ClientId,
|
||||||
|
TeamID: req.TeamId,
|
||||||
|
KeyID: req.KeyId,
|
||||||
|
PrivateKey: req.PrivateKey,
|
||||||
|
Scopes: req.Scopes,
|
||||||
|
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateAppleProviderToCommand(req *admin_pb.UpdateAppleProviderRequest) command.AppleProvider {
|
||||||
|
return command.AppleProvider{
|
||||||
|
Name: req.Name,
|
||||||
|
ClientID: req.ClientId,
|
||||||
|
TeamID: req.TeamId,
|
||||||
|
KeyID: req.KeyId,
|
||||||
|
PrivateKey: req.PrivateKey,
|
||||||
|
Scopes: req.Scopes,
|
||||||
|
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -414,6 +414,8 @@ func providerTypeToPb(idpType domain.IDPType) idp_pb.ProviderType {
|
|||||||
return idp_pb.ProviderType_PROVIDER_TYPE_GITLAB_SELF_HOSTED
|
return idp_pb.ProviderType_PROVIDER_TYPE_GITLAB_SELF_HOSTED
|
||||||
case domain.IDPTypeGoogle:
|
case domain.IDPTypeGoogle:
|
||||||
return idp_pb.ProviderType_PROVIDER_TYPE_GOOGLE
|
return idp_pb.ProviderType_PROVIDER_TYPE_GOOGLE
|
||||||
|
case domain.IDPTypeApple:
|
||||||
|
return idp_pb.ProviderType_PROVIDER_TYPE_APPLE
|
||||||
case domain.IDPTypeUnspecified:
|
case domain.IDPTypeUnspecified:
|
||||||
return idp_pb.ProviderType_PROVIDER_TYPE_UNSPECIFIED
|
return idp_pb.ProviderType_PROVIDER_TYPE_UNSPECIFIED
|
||||||
default:
|
default:
|
||||||
@ -470,6 +472,10 @@ func configToPb(config *query.IDPTemplate) *idp_pb.ProviderConfig {
|
|||||||
ldapConfigToPb(providerConfig, config.LDAPIDPTemplate)
|
ldapConfigToPb(providerConfig, config.LDAPIDPTemplate)
|
||||||
return providerConfig
|
return providerConfig
|
||||||
}
|
}
|
||||||
|
if config.AppleIDPTemplate != nil {
|
||||||
|
appleConfigToPb(providerConfig, config.AppleIDPTemplate)
|
||||||
|
return providerConfig
|
||||||
|
}
|
||||||
return providerConfig
|
return providerConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -620,3 +626,14 @@ func ldapAttributesToPb(attributes idp.LDAPAttributes) *idp_pb.LDAPAttributes {
|
|||||||
ProfileAttribute: attributes.ProfileAttribute,
|
ProfileAttribute: attributes.ProfileAttribute,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func appleConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.AppleIDPTemplate) {
|
||||||
|
providerConfig.Config = &idp_pb.ProviderConfig_Apple{
|
||||||
|
Apple: &idp_pb.AppleConfig{
|
||||||
|
ClientId: template.ClientID,
|
||||||
|
TeamId: template.TeamID,
|
||||||
|
KeyId: template.KeyID,
|
||||||
|
Scopes: template.Scopes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -397,6 +397,27 @@ func (s *Server) UpdateLDAPProvider(ctx context.Context, req *mgmt_pb.UpdateLDAP
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) AddAppleProvider(ctx context.Context, req *mgmt_pb.AddAppleProviderRequest) (*mgmt_pb.AddAppleProviderResponse, error) {
|
||||||
|
id, details, err := s.command.AddOrgAppleProvider(ctx, authz.GetCtxData(ctx).OrgID, addAppleProviderToCommand(req))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &mgmt_pb.AddAppleProviderResponse{
|
||||||
|
Id: id,
|
||||||
|
Details: object_pb.DomainToAddDetailsPb(details),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) UpdateAppleProvider(ctx context.Context, req *mgmt_pb.UpdateAppleProviderRequest) (*mgmt_pb.UpdateAppleProviderResponse, error) {
|
||||||
|
details, err := s.command.UpdateOrgAppleProvider(ctx, authz.GetCtxData(ctx).OrgID, req.Id, updateAppleProviderToCommand(req))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &mgmt_pb.UpdateAppleProviderResponse{
|
||||||
|
Details: object_pb.DomainToChangeDetailsPb(details),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) DeleteProvider(ctx context.Context, req *mgmt_pb.DeleteProviderRequest) (*mgmt_pb.DeleteProviderResponse, error) {
|
func (s *Server) DeleteProvider(ctx context.Context, req *mgmt_pb.DeleteProviderRequest) (*mgmt_pb.DeleteProviderResponse, error) {
|
||||||
details, err := s.command.DeleteOrgProvider(ctx, authz.GetCtxData(ctx).OrgID, req.Id)
|
details, err := s.command.DeleteOrgProvider(ctx, authz.GetCtxData(ctx).OrgID, req.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -457,3 +457,27 @@ func updateLDAPProviderToCommand(req *mgmt_pb.UpdateLDAPProviderRequest) command
|
|||||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addAppleProviderToCommand(req *mgmt_pb.AddAppleProviderRequest) command.AppleProvider {
|
||||||
|
return command.AppleProvider{
|
||||||
|
Name: req.Name,
|
||||||
|
ClientID: req.ClientId,
|
||||||
|
TeamID: req.TeamId,
|
||||||
|
KeyID: req.KeyId,
|
||||||
|
PrivateKey: req.PrivateKey,
|
||||||
|
Scopes: req.Scopes,
|
||||||
|
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateAppleProviderToCommand(req *mgmt_pb.UpdateAppleProviderRequest) command.AppleProvider {
|
||||||
|
return command.AppleProvider{
|
||||||
|
Name: req.Name,
|
||||||
|
ClientID: req.ClientId,
|
||||||
|
TeamID: req.TeamId,
|
||||||
|
KeyID: req.KeyId,
|
||||||
|
PrivateKey: req.PrivateKey,
|
||||||
|
Scopes: req.Scopes,
|
||||||
|
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -45,7 +45,10 @@ func (s *Server) CreateSession(ctx context.Context, req *session.CreateSessionRe
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
challengeResponse, cmds := s.challengesToCommand(req.GetChallenges(), checks)
|
challengeResponse, cmds, err := s.challengesToCommand(req.GetChallenges(), checks)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
set, err := s.command.CreateSession(ctx, cmds, metadata)
|
set, err := s.command.CreateSession(ctx, cmds, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -64,7 +67,10 @@ func (s *Server) SetSession(ctx context.Context, req *session.SetSessionRequest)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
challengeResponse, cmds := s.challengesToCommand(req.GetChallenges(), checks)
|
challengeResponse, cmds, err := s.challengesToCommand(req.GetChallenges(), checks)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
set, err := s.command.UpdateSession(ctx, req.GetSessionId(), req.GetSessionToken(), cmds, req.GetMetadata())
|
set, err := s.command.UpdateSession(ctx, req.GetSessionId(), req.GetSessionToken(), cmds, req.GetMetadata())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -121,6 +127,8 @@ func factorsToPb(s *query.Session) *session.Factors {
|
|||||||
WebAuthN: webAuthNFactorToPb(s.WebAuthNFactor),
|
WebAuthN: webAuthNFactorToPb(s.WebAuthNFactor),
|
||||||
Intent: intentFactorToPb(s.IntentFactor),
|
Intent: intentFactorToPb(s.IntentFactor),
|
||||||
Totp: totpFactorToPb(s.TOTPFactor),
|
Totp: totpFactorToPb(s.TOTPFactor),
|
||||||
|
OtpSms: otpFactorToPb(s.OTPSMSFactor),
|
||||||
|
OtpEmail: otpFactorToPb(s.OTPEmailFactor),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,6 +169,15 @@ func totpFactorToPb(factor query.SessionTOTPFactor) *session.TOTPFactor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func otpFactorToPb(factor query.SessionOTPFactor) *session.OTPFactor {
|
||||||
|
if factor.OTPCheckedAt.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &session.OTPFactor{
|
||||||
|
VerifiedAt: timestamppb.New(factor.OTPCheckedAt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func userFactorToPb(factor query.SessionUserFactor) *session.UserFactor {
|
func userFactorToPb(factor query.SessionUserFactor) *session.UserFactor {
|
||||||
if factor.UserID == "" || factor.UserCheckedAt.IsZero() {
|
if factor.UserID == "" || factor.UserCheckedAt.IsZero() {
|
||||||
return nil
|
return nil
|
||||||
@ -240,7 +257,7 @@ func (s *Server) checksToCommand(ctx context.Context, checks *session.Checks) ([
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sessionChecks := make([]command.SessionCommand, 0, 3)
|
sessionChecks := make([]command.SessionCommand, 0, 7)
|
||||||
if checkUser != nil {
|
if checkUser != nil {
|
||||||
user, err := checkUser.search(ctx, s.query)
|
user, err := checkUser.search(ctx, s.query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -258,14 +275,20 @@ func (s *Server) checksToCommand(ctx context.Context, checks *session.Checks) ([
|
|||||||
sessionChecks = append(sessionChecks, s.command.CheckWebAuthN(passkey.GetCredentialAssertionData()))
|
sessionChecks = append(sessionChecks, s.command.CheckWebAuthN(passkey.GetCredentialAssertionData()))
|
||||||
}
|
}
|
||||||
if totp := checks.GetTotp(); totp != nil {
|
if totp := checks.GetTotp(); totp != nil {
|
||||||
sessionChecks = append(sessionChecks, command.CheckTOTP(totp.GetTotp()))
|
sessionChecks = append(sessionChecks, command.CheckTOTP(totp.GetCode()))
|
||||||
|
}
|
||||||
|
if otp := checks.GetOtpSms(); otp != nil {
|
||||||
|
sessionChecks = append(sessionChecks, command.CheckOTPSMS(otp.GetCode()))
|
||||||
|
}
|
||||||
|
if otp := checks.GetOtpEmail(); otp != nil {
|
||||||
|
sessionChecks = append(sessionChecks, command.CheckOTPEmail(otp.GetCode()))
|
||||||
}
|
}
|
||||||
return sessionChecks, nil
|
return sessionChecks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) challengesToCommand(challenges *session.RequestChallenges, cmds []command.SessionCommand) (*session.Challenges, []command.SessionCommand) {
|
func (s *Server) challengesToCommand(challenges *session.RequestChallenges, cmds []command.SessionCommand) (*session.Challenges, []command.SessionCommand, error) {
|
||||||
if challenges == nil {
|
if challenges == nil {
|
||||||
return nil, cmds
|
return nil, cmds, nil
|
||||||
}
|
}
|
||||||
resp := new(session.Challenges)
|
resp := new(session.Challenges)
|
||||||
if req := challenges.GetWebAuthN(); req != nil {
|
if req := challenges.GetWebAuthN(); req != nil {
|
||||||
@ -273,7 +296,20 @@ func (s *Server) challengesToCommand(challenges *session.RequestChallenges, cmds
|
|||||||
resp.WebAuthN = challenge
|
resp.WebAuthN = challenge
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
}
|
}
|
||||||
return resp, cmds
|
if req := challenges.GetOtpSms(); req != nil {
|
||||||
|
challenge, cmd := s.createOTPSMSChallengeCommand(req)
|
||||||
|
resp.OtpSms = challenge
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
}
|
||||||
|
if req := challenges.GetOtpEmail(); req != nil {
|
||||||
|
challenge, cmd, err := s.createOTPEmailChallengeCommand(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
resp.OtpEmail = challenge
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
}
|
||||||
|
return resp, cmds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) createWebAuthNChallengeCommand(req *session.RequestChallenges_WebAuthN) (*session.Challenges_WebAuthN, command.SessionCommand) {
|
func (s *Server) createWebAuthNChallengeCommand(req *session.RequestChallenges_WebAuthN) (*session.Challenges_WebAuthN, command.SessionCommand) {
|
||||||
@ -299,6 +335,34 @@ func userVerificationRequirementToDomain(req session.UserVerificationRequirement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) createOTPSMSChallengeCommand(req *session.RequestChallenges_OTPSMS) (*string, command.SessionCommand) {
|
||||||
|
if req.GetReturnCode() {
|
||||||
|
challenge := new(string)
|
||||||
|
return challenge, s.command.CreateOTPSMSChallengeReturnCode(challenge)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, s.command.CreateOTPSMSChallenge()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) createOTPEmailChallengeCommand(req *session.RequestChallenges_OTPEmail) (*string, command.SessionCommand, error) {
|
||||||
|
switch t := req.GetDeliveryType().(type) {
|
||||||
|
case *session.RequestChallenges_OTPEmail_SendCode_:
|
||||||
|
cmd, err := s.command.CreateOTPEmailChallengeURLTemplate(t.SendCode.GetUrlTemplate())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return nil, cmd, nil
|
||||||
|
case *session.RequestChallenges_OTPEmail_ReturnCode_:
|
||||||
|
challenge := new(string)
|
||||||
|
return challenge, s.command.CreateOTPEmailChallengeReturnCode(challenge), nil
|
||||||
|
case nil:
|
||||||
|
return nil, s.command.CreateOTPEmailChallenge(), nil
|
||||||
|
default:
|
||||||
|
return nil, nil, caos_errs.ThrowUnimplementedf(nil, "SESSION-k3ng0", "delivery_type oneOf %T in OTPEmailChallenge not implemented", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func userCheck(user *session.CheckUser) (userSearch, error) {
|
func userCheck(user *session.CheckUser) (userSearch, error) {
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -39,6 +39,14 @@ func TestMain(m *testing.M) {
|
|||||||
|
|
||||||
CTX, _ = Tester.WithAuthorization(ctx, integration.OrgOwner), errCtx
|
CTX, _ = Tester.WithAuthorization(ctx, integration.OrgOwner), errCtx
|
||||||
User = Tester.CreateHumanUser(CTX)
|
User = Tester.CreateHumanUser(CTX)
|
||||||
|
Tester.Client.UserV2.VerifyEmail(CTX, &user.VerifyEmailRequest{
|
||||||
|
UserId: User.GetUserId(),
|
||||||
|
VerificationCode: User.GetEmailCode(),
|
||||||
|
})
|
||||||
|
Tester.Client.UserV2.VerifyPhone(CTX, &user.VerifyPhoneRequest{
|
||||||
|
UserId: User.GetUserId(),
|
||||||
|
VerificationCode: User.GetPhoneCode(),
|
||||||
|
})
|
||||||
Tester.SetUserPassword(CTX, User.GetUserId(), integration.UserPassword)
|
Tester.SetUserPassword(CTX, User.GetUserId(), integration.UserPassword)
|
||||||
Tester.RegisterUserPasskey(CTX, User.GetUserId())
|
Tester.RegisterUserPasskey(CTX, User.GetUserId())
|
||||||
return m.Run()
|
return m.Run()
|
||||||
@ -75,6 +83,8 @@ const (
|
|||||||
wantWebAuthNFactorUserVerified
|
wantWebAuthNFactorUserVerified
|
||||||
wantTOTPFactor
|
wantTOTPFactor
|
||||||
wantIntentFactor
|
wantIntentFactor
|
||||||
|
wantOTPSMSFactor
|
||||||
|
wantOTPEmailFactor
|
||||||
)
|
)
|
||||||
|
|
||||||
func verifyFactors(t testing.TB, factors *session.Factors, window time.Duration, want []wantFactor) {
|
func verifyFactors(t testing.TB, factors *session.Factors, window time.Duration, want []wantFactor) {
|
||||||
@ -107,6 +117,14 @@ func verifyFactors(t testing.TB, factors *session.Factors, window time.Duration,
|
|||||||
pf := factors.GetIntent()
|
pf := factors.GetIntent()
|
||||||
assert.NotNil(t, pf)
|
assert.NotNil(t, pf)
|
||||||
assert.WithinRange(t, pf.GetVerifiedAt().AsTime(), time.Now().Add(-window), time.Now().Add(window))
|
assert.WithinRange(t, pf.GetVerifiedAt().AsTime(), time.Now().Add(-window), time.Now().Add(window))
|
||||||
|
case wantOTPSMSFactor:
|
||||||
|
pf := factors.GetOtpSms()
|
||||||
|
assert.NotNil(t, pf)
|
||||||
|
assert.WithinRange(t, pf.GetVerifiedAt().AsTime(), time.Now().Add(-window), time.Now().Add(window))
|
||||||
|
case wantOTPEmailFactor:
|
||||||
|
pf := factors.GetOtpEmail()
|
||||||
|
assert.NotNil(t, pf)
|
||||||
|
assert.WithinRange(t, pf.GetVerifiedAt().AsTime(), time.Now().Add(-window), time.Now().Add(window))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -362,6 +380,20 @@ func registerTOTP(ctx context.Context, t *testing.T, userID string) (secret stri
|
|||||||
return secret
|
return secret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func registerOTPSMS(ctx context.Context, t *testing.T, userID string) {
|
||||||
|
_, err := Tester.Client.UserV2.AddOTPSMS(ctx, &user.AddOTPSMSRequest{
|
||||||
|
UserId: userID,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerOTPEmail(ctx context.Context, t *testing.T, userID string) {
|
||||||
|
_, err := Tester.Client.UserV2.AddOTPEmail(ctx, &user.AddOTPEmailRequest{
|
||||||
|
UserId: userID,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestServer_SetSession_flow(t *testing.T) {
|
func TestServer_SetSession_flow(t *testing.T) {
|
||||||
// create new, empty session
|
// create new, empty session
|
||||||
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{})
|
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{})
|
||||||
@ -421,6 +453,8 @@ func TestServer_SetSession_flow(t *testing.T) {
|
|||||||
userAuthCtx := Tester.WithAuthorizationToken(CTX, sessionToken)
|
userAuthCtx := Tester.WithAuthorizationToken(CTX, sessionToken)
|
||||||
Tester.RegisterUserU2F(userAuthCtx, User.GetUserId())
|
Tester.RegisterUserU2F(userAuthCtx, User.GetUserId())
|
||||||
totpSecret := registerTOTP(userAuthCtx, t, User.GetUserId())
|
totpSecret := registerTOTP(userAuthCtx, t, User.GetUserId())
|
||||||
|
registerOTPSMS(userAuthCtx, t, User.GetUserId())
|
||||||
|
registerOTPEmail(userAuthCtx, t, User.GetUserId())
|
||||||
|
|
||||||
t.Run("check webauthn, user not verified (U2F)", func(t *testing.T) {
|
t.Run("check webauthn, user not verified (U2F)", func(t *testing.T) {
|
||||||
|
|
||||||
@ -470,7 +504,7 @@ func TestServer_SetSession_flow(t *testing.T) {
|
|||||||
SessionToken: sessionToken,
|
SessionToken: sessionToken,
|
||||||
Checks: &session.Checks{
|
Checks: &session.Checks{
|
||||||
Totp: &session.CheckTOTP{
|
Totp: &session.CheckTOTP{
|
||||||
Totp: code,
|
Code: code,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -478,6 +512,66 @@ func TestServer_SetSession_flow(t *testing.T) {
|
|||||||
sessionToken = resp.GetSessionToken()
|
sessionToken = resp.GetSessionToken()
|
||||||
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, wantUserFactor, wantWebAuthNFactor, wantTOTPFactor)
|
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, wantUserFactor, wantWebAuthNFactor, wantTOTPFactor)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("check OTP SMS", func(t *testing.T) {
|
||||||
|
resp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||||
|
SessionId: createResp.GetSessionId(),
|
||||||
|
SessionToken: sessionToken,
|
||||||
|
Challenges: &session.RequestChallenges{
|
||||||
|
OtpSms: &session.RequestChallenges_OTPSMS{ReturnCode: true},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil)
|
||||||
|
sessionToken = resp.GetSessionToken()
|
||||||
|
|
||||||
|
otp := resp.GetChallenges().GetOtpSms()
|
||||||
|
require.NotEmpty(t, otp)
|
||||||
|
|
||||||
|
resp, err = Client.SetSession(CTX, &session.SetSessionRequest{
|
||||||
|
SessionId: createResp.GetSessionId(),
|
||||||
|
SessionToken: sessionToken,
|
||||||
|
Checks: &session.Checks{
|
||||||
|
OtpSms: &session.CheckOTP{
|
||||||
|
Code: otp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
sessionToken = resp.GetSessionToken()
|
||||||
|
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, wantUserFactor, wantWebAuthNFactor, wantOTPSMSFactor)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("check OTP Email", func(t *testing.T) {
|
||||||
|
resp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||||
|
SessionId: createResp.GetSessionId(),
|
||||||
|
SessionToken: sessionToken,
|
||||||
|
Challenges: &session.RequestChallenges{
|
||||||
|
OtpEmail: &session.RequestChallenges_OTPEmail{
|
||||||
|
DeliveryType: &session.RequestChallenges_OTPEmail_ReturnCode_{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil)
|
||||||
|
sessionToken = resp.GetSessionToken()
|
||||||
|
|
||||||
|
otp := resp.GetChallenges().GetOtpEmail()
|
||||||
|
require.NotEmpty(t, otp)
|
||||||
|
|
||||||
|
resp, err = Client.SetSession(CTX, &session.SetSessionRequest{
|
||||||
|
SessionId: createResp.GetSessionId(),
|
||||||
|
SessionToken: sessionToken,
|
||||||
|
Checks: &session.Checks{
|
||||||
|
OtpEmail: &session.CheckOTP{
|
||||||
|
Code: otp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
sessionToken = resp.GetSessionToken()
|
||||||
|
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, wantUserFactor, wantWebAuthNFactor, wantOTPEmailFactor)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_ZITADEL_API_missing_authentication(t *testing.T) {
|
func Test_ZITADEL_API_missing_authentication(t *testing.T) {
|
||||||
|
47
internal/api/http/error.go
Normal file
47
internal/api/http/error.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ZitadelErrorToHTTPStatusCode(err error) (statusCode int, ok bool) {
|
||||||
|
if err == nil {
|
||||||
|
return http.StatusOK, true
|
||||||
|
}
|
||||||
|
//nolint:errorlint
|
||||||
|
switch err.(type) {
|
||||||
|
case *caos_errs.AlreadyExistsError:
|
||||||
|
return http.StatusConflict, true
|
||||||
|
case *caos_errs.DeadlineExceededError:
|
||||||
|
return http.StatusGatewayTimeout, true
|
||||||
|
case *caos_errs.InternalError:
|
||||||
|
return http.StatusInternalServerError, true
|
||||||
|
case *caos_errs.InvalidArgumentError:
|
||||||
|
return http.StatusBadRequest, true
|
||||||
|
case *caos_errs.NotFoundError:
|
||||||
|
return http.StatusNotFound, true
|
||||||
|
case *caos_errs.PermissionDeniedError:
|
||||||
|
return http.StatusForbidden, true
|
||||||
|
case *caos_errs.PreconditionFailedError:
|
||||||
|
// use the same code as grpc-gateway:
|
||||||
|
// https://github.com/grpc-ecosystem/grpc-gateway/blob/9e33e38f15cb7d2f11096366e62ea391a3459ba9/runtime/errors.go#L59
|
||||||
|
return http.StatusBadRequest, true
|
||||||
|
case *caos_errs.UnauthenticatedError:
|
||||||
|
return http.StatusUnauthorized, true
|
||||||
|
case *caos_errs.UnavailableError:
|
||||||
|
return http.StatusServiceUnavailable, true
|
||||||
|
case *caos_errs.UnimplementedError:
|
||||||
|
return http.StatusNotImplemented, true
|
||||||
|
case *caos_errs.ResourceExhaustedError:
|
||||||
|
return http.StatusTooManyRequests, true
|
||||||
|
default:
|
||||||
|
c := new(caos_errs.CaosError)
|
||||||
|
if errors.As(err, &c) {
|
||||||
|
return ZitadelErrorToHTTPStatusCode(errors.Unwrap(err))
|
||||||
|
}
|
||||||
|
return http.StatusInternalServerError, false
|
||||||
|
}
|
||||||
|
}
|
138
internal/api/http/error_test.go
Normal file
138
internal/api/http/error_test.go
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
caos_errors "github.com/zitadel/zitadel/internal/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestZitadelErrorToHTTPStatusCode(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantStatusCode int
|
||||||
|
wantOk bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no error",
|
||||||
|
args: args{
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
wantStatusCode: http.StatusOK,
|
||||||
|
wantOk: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped already exists",
|
||||||
|
args: args{
|
||||||
|
err: fmt.Errorf("wrapped %w", caos_errors.ThrowAlreadyExists(nil, "id", "message")),
|
||||||
|
},
|
||||||
|
wantStatusCode: http.StatusConflict,
|
||||||
|
wantOk: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped deadline exceeded",
|
||||||
|
args: args{
|
||||||
|
err: fmt.Errorf("wrapped %w", caos_errors.ThrowDeadlineExceeded(nil, "id", "message")),
|
||||||
|
},
|
||||||
|
wantStatusCode: http.StatusGatewayTimeout,
|
||||||
|
wantOk: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped internal",
|
||||||
|
args: args{
|
||||||
|
err: fmt.Errorf("wrapped %w", caos_errors.ThrowInternal(nil, "id", "message")),
|
||||||
|
},
|
||||||
|
wantStatusCode: http.StatusInternalServerError,
|
||||||
|
wantOk: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped invalid argument",
|
||||||
|
args: args{
|
||||||
|
err: fmt.Errorf("wrapped %w", caos_errors.ThrowInvalidArgument(nil, "id", "message")),
|
||||||
|
},
|
||||||
|
wantStatusCode: http.StatusBadRequest,
|
||||||
|
wantOk: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped not found",
|
||||||
|
args: args{
|
||||||
|
err: fmt.Errorf("wrapped %w", caos_errors.ThrowNotFound(nil, "id", "message")),
|
||||||
|
},
|
||||||
|
wantStatusCode: http.StatusNotFound,
|
||||||
|
wantOk: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped permission denied",
|
||||||
|
args: args{
|
||||||
|
err: fmt.Errorf("wrapped %w", caos_errors.ThrowPermissionDenied(nil, "id", "message")),
|
||||||
|
},
|
||||||
|
wantStatusCode: http.StatusForbidden,
|
||||||
|
wantOk: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped precondition failed",
|
||||||
|
args: args{
|
||||||
|
err: fmt.Errorf("wrapped %w", caos_errors.ThrowPreconditionFailed(nil, "id", "message")),
|
||||||
|
},
|
||||||
|
wantStatusCode: http.StatusBadRequest,
|
||||||
|
wantOk: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped unauthenticated",
|
||||||
|
args: args{
|
||||||
|
err: fmt.Errorf("wrapped %w", caos_errors.ThrowUnauthenticated(nil, "id", "message")),
|
||||||
|
},
|
||||||
|
wantStatusCode: http.StatusUnauthorized,
|
||||||
|
wantOk: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped unavailable",
|
||||||
|
args: args{
|
||||||
|
err: fmt.Errorf("wrapped %w", caos_errors.ThrowUnavailable(nil, "id", "message")),
|
||||||
|
},
|
||||||
|
wantStatusCode: http.StatusServiceUnavailable,
|
||||||
|
wantOk: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped unimplemented",
|
||||||
|
args: args{
|
||||||
|
err: fmt.Errorf("wrapped %w", caos_errors.ThrowUnimplemented(nil, "id", "message")),
|
||||||
|
},
|
||||||
|
wantStatusCode: http.StatusNotImplemented,
|
||||||
|
wantOk: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped resource exhausted",
|
||||||
|
args: args{
|
||||||
|
err: fmt.Errorf("wrapped %w", caos_errors.ThrowResourceExhausted(nil, "id", "message")),
|
||||||
|
},
|
||||||
|
wantStatusCode: http.StatusTooManyRequests,
|
||||||
|
wantOk: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no caos/zitadel error",
|
||||||
|
args: args{
|
||||||
|
err: errors.New("error"),
|
||||||
|
},
|
||||||
|
wantStatusCode: http.StatusInternalServerError,
|
||||||
|
wantOk: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
gotStatusCode, gotOk := ZitadelErrorToHTTPStatusCode(tt.args.err)
|
||||||
|
if gotStatusCode != tt.wantStatusCode {
|
||||||
|
t.Errorf("ZitadelErrorToHTTPStatusCode() gotStatusCode = %v, want %v", gotStatusCode, tt.wantStatusCode)
|
||||||
|
}
|
||||||
|
if gotOk != tt.wantOk {
|
||||||
|
t.Errorf("ZitadelErrorToHTTPStatusCode() gotOk = %v, want %v", gotOk, tt.wantOk)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,7 @@ import (
|
|||||||
z_errs "github.com/zitadel/zitadel/internal/errors"
|
z_errs "github.com/zitadel/zitadel/internal/errors"
|
||||||
"github.com/zitadel/zitadel/internal/form"
|
"github.com/zitadel/zitadel/internal/form"
|
||||||
"github.com/zitadel/zitadel/internal/idp"
|
"github.com/zitadel/zitadel/internal/idp"
|
||||||
|
"github.com/zitadel/zitadel/internal/idp/providers/apple"
|
||||||
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
||||||
"github.com/zitadel/zitadel/internal/idp/providers/github"
|
"github.com/zitadel/zitadel/internal/idp/providers/github"
|
||||||
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
|
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
|
||||||
@ -52,6 +53,9 @@ type externalIDPCallbackData struct {
|
|||||||
Code string `schema:"code"`
|
Code string `schema:"code"`
|
||||||
Error string `schema:"error"`
|
Error string `schema:"error"`
|
||||||
ErrorDescription string `schema:"error_description"`
|
ErrorDescription string `schema:"error_description"`
|
||||||
|
|
||||||
|
// Apple returns a user on first registration
|
||||||
|
User string `schema:"user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallbackURL generates the instance specific URL to the IDP callback handler
|
// CallbackURL generates the instance specific URL to the IDP callback handler
|
||||||
@ -115,7 +119,7 @@ func (h *Handler) handleCallback(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
idpUser, idpSession, err := h.fetchIDPUser(ctx, provider, data.Code)
|
idpUser, idpSession, err := h.fetchIDPUser(ctx, provider, data.Code, data.User)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmdErr := h.commands.FailIDPIntent(ctx, intent, err.Error())
|
cmdErr := h.commands.FailIDPIntent(ctx, intent, err.Error())
|
||||||
logging.WithFields("intent", intent.AggregateID).OnError(cmdErr).Error("failed to push failed event on idp intent")
|
logging.WithFields("intent", intent.AggregateID).OnError(cmdErr).Error("failed to push failed event on idp intent")
|
||||||
@ -214,7 +218,7 @@ func redirectToFailureURL(w http.ResponseWriter, r *http.Request, i *command.IDP
|
|||||||
http.Redirect(w, r, i.FailureURL.String(), http.StatusFound)
|
http.Redirect(w, r, i.FailureURL.String(), http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) fetchIDPUser(ctx context.Context, identityProvider idp.Provider, code string) (user idp.User, idpTokens idp.Session, err error) {
|
func (h *Handler) fetchIDPUser(ctx context.Context, identityProvider idp.Provider, code string, appleUser string) (user idp.User, idpTokens idp.Session, err error) {
|
||||||
var session idp.Session
|
var session idp.Session
|
||||||
switch provider := identityProvider.(type) {
|
switch provider := identityProvider.(type) {
|
||||||
case *oauth.Provider:
|
case *oauth.Provider:
|
||||||
@ -229,6 +233,8 @@ func (h *Handler) fetchIDPUser(ctx context.Context, identityProvider idp.Provide
|
|||||||
session = &openid.Session{Provider: provider.Provider, Code: code}
|
session = &openid.Session{Provider: provider.Provider, Code: code}
|
||||||
case *google.Provider:
|
case *google.Provider:
|
||||||
session = &openid.Session{Provider: provider.Provider, Code: code}
|
session = &openid.Session{Provider: provider.Provider, Code: code}
|
||||||
|
case *apple.Provider:
|
||||||
|
session = &apple.Session{Session: &openid.Session{Provider: provider.Provider, Code: code}, UserFormValue: appleUser}
|
||||||
case *jwt.Provider, *ldap.Provider:
|
case *jwt.Provider, *ldap.Provider:
|
||||||
return nil, nil, z_errs.ThrowInvalidArgument(nil, "IDP-52jmn", "Errors.ExternalIDP.IDPTypeNotImplemented")
|
return nil, nil, z_errs.ThrowInvalidArgument(nil, "IDP-52jmn", "Errors.ExternalIDP.IDPTypeNotImplemented")
|
||||||
default:
|
default:
|
||||||
|
@ -3,9 +3,8 @@ package login
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
|
||||||
|
|
||||||
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
|
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/errors"
|
"github.com/zitadel/zitadel/internal/errors"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
"github.com/zitadel/zitadel/internal/idp"
|
"github.com/zitadel/zitadel/internal/idp"
|
||||||
|
"github.com/zitadel/zitadel/internal/idp/providers/apple"
|
||||||
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
||||||
"github.com/zitadel/zitadel/internal/idp/providers/github"
|
"github.com/zitadel/zitadel/internal/idp/providers/github"
|
||||||
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
|
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
|
||||||
@ -41,6 +42,9 @@ type externalIDPData struct {
|
|||||||
type externalIDPCallbackData struct {
|
type externalIDPCallbackData struct {
|
||||||
State string `schema:"state"`
|
State string `schema:"state"`
|
||||||
Code string `schema:"code"`
|
Code string `schema:"code"`
|
||||||
|
|
||||||
|
// Apple returns a user on first registration
|
||||||
|
User string `schema:"user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type externalNotFoundOptionFormData struct {
|
type externalNotFoundOptionFormData struct {
|
||||||
@ -159,6 +163,8 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai
|
|||||||
provider, err = l.gitlabSelfHostedProvider(r.Context(), identityProvider)
|
provider, err = l.gitlabSelfHostedProvider(r.Context(), identityProvider)
|
||||||
case domain.IDPTypeGoogle:
|
case domain.IDPTypeGoogle:
|
||||||
provider, err = l.googleProvider(r.Context(), identityProvider)
|
provider, err = l.googleProvider(r.Context(), identityProvider)
|
||||||
|
case domain.IDPTypeApple:
|
||||||
|
provider, err = l.appleProvider(r.Context(), identityProvider)
|
||||||
case domain.IDPTypeLDAP:
|
case domain.IDPTypeLDAP:
|
||||||
provider, err = l.ldapProvider(r.Context(), identityProvider)
|
provider, err = l.ldapProvider(r.Context(), identityProvider)
|
||||||
case domain.IDPTypeUnspecified:
|
case domain.IDPTypeUnspecified:
|
||||||
@ -180,6 +186,18 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai
|
|||||||
http.Redirect(w, r, session.GetAuthURL(), http.StatusFound)
|
http.Redirect(w, r, session.GetAuthURL(), http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleExternalLoginCallbackForm handles the callback from a IDP with form_post.
|
||||||
|
// It will redirect to the "normal" callback endpoint with the form data as query parameter.
|
||||||
|
// This way cookies will be handled correctly (same site = lax).
|
||||||
|
func (l *Login) handleExternalLoginCallbackForm(w http.ResponseWriter, r *http.Request) {
|
||||||
|
err := r.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
l.renderLogin(w, r, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Redirect(w, r, HandlerPrefix+EndpointExternalLoginCallback+"?"+r.Form.Encode(), 302)
|
||||||
|
}
|
||||||
|
|
||||||
// handleExternalLoginCallback handles the callback from a IDP
|
// handleExternalLoginCallback handles the callback from a IDP
|
||||||
// and tries to extract the user with the provided data
|
// and tries to extract the user with the provided data
|
||||||
func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Request) {
|
func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -259,6 +277,13 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
session = &openid.Session{Provider: provider.(*google.Provider).Provider, Code: data.Code}
|
session = &openid.Session{Provider: provider.(*google.Provider).Provider, Code: data.Code}
|
||||||
|
case domain.IDPTypeApple:
|
||||||
|
provider, err = l.appleProvider(r.Context(), identityProvider)
|
||||||
|
if err != nil {
|
||||||
|
l.externalAuthFailed(w, r, authReq, nil, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
session = &apple.Session{Session: &openid.Session{Provider: provider.(*apple.Provider).Provider, Code: data.Code}, UserFormValue: data.User}
|
||||||
case domain.IDPTypeJWT,
|
case domain.IDPTypeJWT,
|
||||||
domain.IDPTypeLDAP,
|
domain.IDPTypeLDAP,
|
||||||
domain.IDPTypeUnspecified:
|
domain.IDPTypeUnspecified:
|
||||||
@ -936,6 +961,21 @@ func (l *Login) gitlabSelfHostedProvider(ctx context.Context, identityProvider *
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Login) appleProvider(ctx context.Context, identityProvider *query.IDPTemplate) (*apple.Provider, error) {
|
||||||
|
privateKey, err := crypto.Decrypt(identityProvider.AppleIDPTemplate.PrivateKey, l.idpConfigAlg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return apple.New(
|
||||||
|
identityProvider.AppleIDPTemplate.ClientID,
|
||||||
|
identityProvider.AppleIDPTemplate.TeamID,
|
||||||
|
identityProvider.AppleIDPTemplate.KeyID,
|
||||||
|
l.baseURL(ctx)+EndpointExternalLoginCallbackFormPost,
|
||||||
|
privateKey,
|
||||||
|
identityProvider.AppleIDPTemplate.Scopes,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func (l *Login) appendUserGrants(ctx context.Context, userGrants []*domain.UserGrant, resourceOwner string) error {
|
func (l *Login) appendUserGrants(ctx context.Context, userGrants []*domain.UserGrant, resourceOwner string) error {
|
||||||
if len(userGrants) == 0 {
|
if len(userGrants) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@ -971,6 +1011,8 @@ func tokens(session idp.Session) *oidc.Tokens[*oidc.IDTokenClaims] {
|
|||||||
return s.Tokens
|
return s.Tokens
|
||||||
case *azuread.Session:
|
case *azuread.Session:
|
||||||
return s.Tokens
|
return s.Tokens
|
||||||
|
case *apple.Session:
|
||||||
|
return s.Tokens
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,9 @@ type Config struct {
|
|||||||
CSRFCookieName string
|
CSRFCookieName string
|
||||||
Cache middleware.CacheConfig
|
Cache middleware.CacheConfig
|
||||||
AssetCache middleware.CacheConfig
|
AssetCache middleware.CacheConfig
|
||||||
|
|
||||||
|
// LoginV2
|
||||||
|
DefaultOTPEmailURLV2 string
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -117,6 +120,12 @@ func createCSRFInterceptor(cookieName string, csrfCookieKey []byte, externalSecu
|
|||||||
handler.ServeHTTP(w, r)
|
handler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// ignore form post callback
|
||||||
|
// it will redirect to the "normal" callback, where the cookie is set again
|
||||||
|
if r.URL.Path == EndpointExternalLoginCallbackFormPost && r.Method == http.MethodPost {
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
csrf.Protect(csrfCookieKey,
|
csrf.Protect(csrfCookieKey,
|
||||||
csrf.Secure(externalSecure),
|
csrf.Secure(externalSecure),
|
||||||
csrf.CookieName(http_utils.SetCookiePrefix(cookieName, "", path, externalSecure)),
|
csrf.CookieName(http_utils.SetCookiePrefix(cookieName, "", path, externalSecure)),
|
||||||
|
@ -7,44 +7,45 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EndpointRoot = "/"
|
EndpointRoot = "/"
|
||||||
EndpointHealthz = "/healthz"
|
EndpointHealthz = "/healthz"
|
||||||
EndpointReadiness = "/ready"
|
EndpointReadiness = "/ready"
|
||||||
EndpointLogin = "/login"
|
EndpointLogin = "/login"
|
||||||
EndpointExternalLogin = "/login/externalidp"
|
EndpointExternalLogin = "/login/externalidp"
|
||||||
EndpointExternalLoginCallback = "/login/externalidp/callback"
|
EndpointExternalLoginCallback = "/login/externalidp/callback"
|
||||||
EndpointJWTAuthorize = "/login/jwt/authorize"
|
EndpointExternalLoginCallbackFormPost = "/login/externalidp/callback/form"
|
||||||
EndpointJWTCallback = "/login/jwt/callback"
|
EndpointJWTAuthorize = "/login/jwt/authorize"
|
||||||
EndpointLDAPLogin = "/login/ldap"
|
EndpointJWTCallback = "/login/jwt/callback"
|
||||||
EndpointLDAPCallback = "/login/ldap/callback"
|
EndpointLDAPLogin = "/login/ldap"
|
||||||
EndpointPasswordlessLogin = "/login/passwordless"
|
EndpointLDAPCallback = "/login/ldap/callback"
|
||||||
EndpointPasswordlessRegistration = "/login/passwordless/init"
|
EndpointPasswordlessLogin = "/login/passwordless"
|
||||||
EndpointPasswordlessPrompt = "/login/passwordless/prompt"
|
EndpointPasswordlessRegistration = "/login/passwordless/init"
|
||||||
EndpointLoginName = "/loginname"
|
EndpointPasswordlessPrompt = "/login/passwordless/prompt"
|
||||||
EndpointUserSelection = "/userselection"
|
EndpointLoginName = "/loginname"
|
||||||
EndpointChangeUsername = "/username/change"
|
EndpointUserSelection = "/userselection"
|
||||||
EndpointPassword = "/password"
|
EndpointChangeUsername = "/username/change"
|
||||||
EndpointInitPassword = "/password/init"
|
EndpointPassword = "/password"
|
||||||
EndpointChangePassword = "/password/change"
|
EndpointInitPassword = "/password/init"
|
||||||
EndpointPasswordReset = "/password/reset"
|
EndpointChangePassword = "/password/change"
|
||||||
EndpointInitUser = "/user/init"
|
EndpointPasswordReset = "/password/reset"
|
||||||
EndpointMFAVerify = "/mfa/verify"
|
EndpointInitUser = "/user/init"
|
||||||
EndpointMFAPrompt = "/mfa/prompt"
|
EndpointMFAVerify = "/mfa/verify"
|
||||||
EndpointMFAInitVerify = "/mfa/init/verify"
|
EndpointMFAPrompt = "/mfa/prompt"
|
||||||
EndpointMFASMSInitVerify = "/mfa/init/sms/verify"
|
EndpointMFAInitVerify = "/mfa/init/verify"
|
||||||
EndpointMFAOTPVerify = "/mfa/otp/verify"
|
EndpointMFASMSInitVerify = "/mfa/init/sms/verify"
|
||||||
EndpointMFAInitU2FVerify = "/mfa/init/u2f/verify"
|
EndpointMFAOTPVerify = "/mfa/otp/verify"
|
||||||
EndpointU2FVerification = "/mfa/u2f/verify"
|
EndpointMFAInitU2FVerify = "/mfa/init/u2f/verify"
|
||||||
EndpointMailVerification = "/mail/verification"
|
EndpointU2FVerification = "/mfa/u2f/verify"
|
||||||
EndpointMailVerified = "/mail/verified"
|
EndpointMailVerification = "/mail/verification"
|
||||||
EndpointRegisterOption = "/register/option"
|
EndpointMailVerified = "/mail/verified"
|
||||||
EndpointRegister = "/register"
|
EndpointRegisterOption = "/register/option"
|
||||||
EndpointExternalRegister = "/register/externalidp"
|
EndpointRegister = "/register"
|
||||||
EndpointExternalRegisterCallback = "/register/externalidp/callback"
|
EndpointExternalRegister = "/register/externalidp"
|
||||||
EndpointRegisterOrg = "/register/org"
|
EndpointExternalRegisterCallback = "/register/externalidp/callback"
|
||||||
EndpointLogoutDone = "/logout/done"
|
EndpointRegisterOrg = "/register/org"
|
||||||
EndpointLoginSuccess = "/login/success"
|
EndpointLogoutDone = "/logout/done"
|
||||||
EndpointExternalNotFoundOption = "/externaluser/option"
|
EndpointLoginSuccess = "/login/success"
|
||||||
|
EndpointExternalNotFoundOption = "/externaluser/option"
|
||||||
|
|
||||||
EndpointResources = "/resources"
|
EndpointResources = "/resources"
|
||||||
EndpointDynamicResources = "/resources/dynamic"
|
EndpointDynamicResources = "/resources/dynamic"
|
||||||
@ -71,6 +72,7 @@ func CreateRouter(login *Login, staticDir http.FileSystem, interceptors ...mux.M
|
|||||||
router.HandleFunc(EndpointLogin, login.handleLogin).Methods(http.MethodGet, http.MethodPost)
|
router.HandleFunc(EndpointLogin, login.handleLogin).Methods(http.MethodGet, http.MethodPost)
|
||||||
router.HandleFunc(EndpointExternalLogin, login.handleExternalLogin).Methods(http.MethodGet)
|
router.HandleFunc(EndpointExternalLogin, login.handleExternalLogin).Methods(http.MethodGet)
|
||||||
router.HandleFunc(EndpointExternalLoginCallback, login.handleExternalLoginCallback).Methods(http.MethodGet)
|
router.HandleFunc(EndpointExternalLoginCallback, login.handleExternalLoginCallback).Methods(http.MethodGet)
|
||||||
|
router.HandleFunc(EndpointExternalLoginCallbackFormPost, login.handleExternalLoginCallbackForm).Methods(http.MethodPost)
|
||||||
router.HandleFunc(EndpointJWTAuthorize, login.handleJWTRequest).Methods(http.MethodGet)
|
router.HandleFunc(EndpointJWTAuthorize, login.handleJWTRequest).Methods(http.MethodGet)
|
||||||
router.HandleFunc(EndpointJWTCallback, login.handleJWTCallback).Methods(http.MethodGet)
|
router.HandleFunc(EndpointJWTCallback, login.handleJWTCallback).Methods(http.MethodGet)
|
||||||
router.HandleFunc(EndpointPasswordlessLogin, login.handlePasswordlessVerification).Methods(http.MethodPost)
|
router.HandleFunc(EndpointPasswordlessLogin, login.handlePasswordlessVerification).Methods(http.MethodPost)
|
||||||
|
@ -360,6 +360,7 @@ Footer:
|
|||||||
PrivacyPolicy: Политика за поверителност
|
PrivacyPolicy: Политика за поверителност
|
||||||
Help: Помогне
|
Help: Помогне
|
||||||
SupportEmail: Поддръжка на имейл
|
SupportEmail: Поддръжка на имейл
|
||||||
|
SignIn: Влезте с {{.Provider}}
|
||||||
Errors:
|
Errors:
|
||||||
Internal: Възникна вътрешна грешка
|
Internal: Възникна вътрешна грешка
|
||||||
AuthRequest:
|
AuthRequest:
|
||||||
|
@ -372,6 +372,8 @@ Footer:
|
|||||||
Help: Hilfe
|
Help: Hilfe
|
||||||
SupportEmail: Support E-Mail
|
SupportEmail: Support E-Mail
|
||||||
|
|
||||||
|
SignIn: Mit {{.Provider}} anmelden
|
||||||
|
|
||||||
Errors:
|
Errors:
|
||||||
Internal: Es ist ein interner Fehler aufgetreten
|
Internal: Es ist ein interner Fehler aufgetreten
|
||||||
AuthRequest:
|
AuthRequest:
|
||||||
|
@ -372,6 +372,8 @@ Footer:
|
|||||||
Help: Help
|
Help: Help
|
||||||
SupportEmail: Support E-mail
|
SupportEmail: Support E-mail
|
||||||
|
|
||||||
|
SignIn: Sign in with {{.Provider}}
|
||||||
|
|
||||||
Errors:
|
Errors:
|
||||||
Internal: An internal error occurred
|
Internal: An internal error occurred
|
||||||
AuthRequest:
|
AuthRequest:
|
||||||
|
@ -354,6 +354,8 @@ Footer:
|
|||||||
Help: Ayuda
|
Help: Ayuda
|
||||||
SupportEmail: Email de soporte
|
SupportEmail: Email de soporte
|
||||||
|
|
||||||
|
SignIn: Iniciar sesión con {{.Provider}}
|
||||||
|
|
||||||
Errors:
|
Errors:
|
||||||
Internal: Se produjo un error interno
|
Internal: Se produjo un error interno
|
||||||
AuthRequest:
|
AuthRequest:
|
||||||
|
@ -372,6 +372,8 @@ Footer:
|
|||||||
Help: Aide
|
Help: Aide
|
||||||
SupportEmail: E-mail d'assistance
|
SupportEmail: E-mail d'assistance
|
||||||
|
|
||||||
|
SignIn: Connexion avec {{.Provider}}
|
||||||
|
|
||||||
Errors:
|
Errors:
|
||||||
Internal: Une erreur interne s'est produite
|
Internal: Une erreur interne s'est produite
|
||||||
AuthRequest:
|
AuthRequest:
|
||||||
|
@ -372,6 +372,8 @@ Footer:
|
|||||||
Help: Aiuto
|
Help: Aiuto
|
||||||
SupportEmail: E-mail di supporto
|
SupportEmail: E-mail di supporto
|
||||||
|
|
||||||
|
SignIn: Accedi con {{.Provider}}
|
||||||
|
|
||||||
Errors:
|
Errors:
|
||||||
Internal: Si è verificato un errore interno
|
Internal: Si è verificato un errore interno
|
||||||
AuthRequest:
|
AuthRequest:
|
||||||
|
@ -363,6 +363,8 @@ Footer:
|
|||||||
PrivacyPolicy: プライバシーポリシー
|
PrivacyPolicy: プライバシーポリシー
|
||||||
Help: ヘルプ
|
Help: ヘルプ
|
||||||
|
|
||||||
|
SignIn: '{{.Provider}} でサインイン'
|
||||||
|
|
||||||
Errors:
|
Errors:
|
||||||
Internal: 内部でエラーが発生しました
|
Internal: 内部でエラーが発生しました
|
||||||
AuthRequest:
|
AuthRequest:
|
||||||
|
@ -372,6 +372,8 @@ Footer:
|
|||||||
Help: Помош
|
Help: Помош
|
||||||
SupportEmail: Е-пошта за поддршка
|
SupportEmail: Е-пошта за поддршка
|
||||||
|
|
||||||
|
SignIn: Пријавете се со {{.Provider}}
|
||||||
|
|
||||||
Errors:
|
Errors:
|
||||||
Internal: Се појави внатрешна грешка
|
Internal: Се појави внатрешна грешка
|
||||||
AuthRequest:
|
AuthRequest:
|
||||||
|
@ -372,6 +372,8 @@ Footer:
|
|||||||
Help: Pomoc
|
Help: Pomoc
|
||||||
SupportEmail: E-mail wsparcia
|
SupportEmail: E-mail wsparcia
|
||||||
|
|
||||||
|
SignIn: Zaloguj się, używając konta {{.Provider}}
|
||||||
|
|
||||||
Errors:
|
Errors:
|
||||||
Internal: Wewnętrzny błąd
|
Internal: Wewnętrzny błąd
|
||||||
AuthRequest:
|
AuthRequest:
|
||||||
|
@ -366,6 +366,8 @@ Footer:
|
|||||||
Help: Ajuda
|
Help: Ajuda
|
||||||
SupportEmail: E-mail de suporte
|
SupportEmail: E-mail de suporte
|
||||||
|
|
||||||
|
SignIn: Iniciar sessão com a {{.Provider}}
|
||||||
|
|
||||||
Errors:
|
Errors:
|
||||||
Internal: Ocorreu um erro interno
|
Internal: Ocorreu um erro interno
|
||||||
AuthRequest:
|
AuthRequest:
|
||||||
|
@ -372,6 +372,8 @@ Footer:
|
|||||||
Help: 帮助
|
Help: 帮助
|
||||||
SupportEmail: 支持邮箱
|
SupportEmail: 支持邮箱
|
||||||
|
|
||||||
|
SignIn: 通过 {{.Provider}} 登录
|
||||||
|
|
||||||
Errors:
|
Errors:
|
||||||
Internal: 发生了内部错误
|
Internal: 发生了内部错误
|
||||||
AuthRequest:
|
AuthRequest:
|
||||||
|
10
internal/api/ui/login/static/resources/images/idp/apple-dark.svg
Executable file
10
internal/api/ui/login/static/resources/images/idp/apple-dark.svg
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="56px" height="56px" viewBox="18 15.5 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 61 (89581) - https://sketch.com -->
|
||||||
|
<title>White Logo Square </title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<g id="White-Logo-Square-" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<rect id="Rectangle" fill="" x="6" y="6" width="44" height="44"></rect>
|
||||||
|
<path d="M28.2226562,20.3846154 C29.0546875,20.3846154 30.0976562,19.8048315 30.71875,19.0317864 C31.28125,18.3312142 31.6914062,17.352829 31.6914062,16.3744437 C31.6914062,16.2415766 31.6796875,16.1087095 31.65625,16 C30.7304687,16.0362365 29.6171875,16.640178 28.9492187,17.4494596 C28.421875,18.06548 27.9414062,19.0317864 27.9414062,20.0222505 C27.9414062,20.1671964 27.9648438,20.3121424 27.9765625,20.3604577 C28.0351562,20.3725366 28.1289062,20.3846154 28.2226562,20.3846154 Z M25.2929688,35 C26.4296875,35 26.9335938,34.214876 28.3515625,34.214876 C29.7929688,34.214876 30.109375,34.9758423 31.375,34.9758423 C32.6171875,34.9758423 33.4492188,33.792117 34.234375,32.6325493 C35.1132812,31.3038779 35.4765625,29.9993643 35.5,29.9389701 C35.4179688,29.9148125 33.0390625,28.9122695 33.0390625,26.0979021 C33.0390625,23.6579784 34.9140625,22.5588048 35.0195312,22.474253 C33.7773438,20.6382708 31.890625,20.5899555 31.375,20.5899555 C29.9804688,20.5899555 28.84375,21.4596313 28.1289062,21.4596313 C27.3554688,21.4596313 26.3359375,20.6382708 25.1289062,20.6382708 C22.8320312,20.6382708 20.5,22.5950413 20.5,26.2911634 C20.5,28.5861411 21.3671875,31.013986 22.4335938,32.5842339 C23.3476562,33.9129053 24.1445312,35 25.2929688,35 Z" id="" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
10
internal/api/ui/login/static/resources/images/idp/apple.svg
Executable file
10
internal/api/ui/login/static/resources/images/idp/apple.svg
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="56px" height="56px" viewBox="18 15.5 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 61 (89581) - https://sketch.com -->
|
||||||
|
<title>Black Logo Square</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<g id="Black-Logo-Square" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<rect id="Rectangle" fill="" x="6" y="6" width="44" height="44"></rect>
|
||||||
|
<path d="M28.2226562,20.3846154 C29.0546875,20.3846154 30.0976562,19.8048315 30.71875,19.0317864 C31.28125,18.3312142 31.6914062,17.352829 31.6914062,16.3744437 C31.6914062,16.2415766 31.6796875,16.1087095 31.65625,16 C30.7304687,16.0362365 29.6171875,16.640178 28.9492187,17.4494596 C28.421875,18.06548 27.9414062,19.0317864 27.9414062,20.0222505 C27.9414062,20.1671964 27.9648438,20.3121424 27.9765625,20.3604577 C28.0351562,20.3725366 28.1289062,20.3846154 28.2226562,20.3846154 Z M25.2929688,35 C26.4296875,35 26.9335938,34.214876 28.3515625,34.214876 C29.7929688,34.214876 30.109375,34.9758423 31.375,34.9758423 C32.6171875,34.9758423 33.4492188,33.792117 34.234375,32.6325493 C35.1132812,31.3038779 35.4765625,29.9993643 35.5,29.9389701 C35.4179688,29.9148125 33.0390625,28.9122695 33.0390625,26.0979021 C33.0390625,23.6579784 34.9140625,22.5588048 35.0195312,22.474253 C33.7773438,20.6382708 31.890625,20.5899555 31.375,20.5899555 C29.9804688,20.5899555 28.84375,21.4596313 28.1289062,21.4596313 C27.3554688,21.4596313 26.3359375,20.6382708 25.1289062,20.6382708 C22.8320312,20.6382708 20.5,22.5950413 20.5,26.2911634 C20.5,28.5861411 21.3671875,31.013986 22.4335938,32.5842339 C23.3476562,33.9129053 24.1445312,35 25.2929688,35 Z" id="" fill="#000000" fill-rule="nonzero"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -2,6 +2,7 @@ $lgn-idp-margin: 0.5rem 0;
|
|||||||
$lgn-idp-padding: 0 1px;
|
$lgn-idp-padding: 0 1px;
|
||||||
$lgn-idp-provider-name-line-height: 36px;
|
$lgn-idp-provider-name-line-height: 36px;
|
||||||
$lgn-idp-border-radius: 0.5rem;
|
$lgn-idp-border-radius: 0.5rem;
|
||||||
|
$lgn-idp-logo-size: 46px;
|
||||||
|
|
||||||
@mixin lgn-idp-base {
|
@mixin lgn-idp-base {
|
||||||
display: block;
|
display: block;
|
||||||
@ -17,14 +18,14 @@ $lgn-idp-border-radius: 0.5rem;
|
|||||||
transition: border-color 0.2s ease-in-out;
|
transition: border-color 0.2s ease-in-out;
|
||||||
|
|
||||||
span.logo {
|
span.logo {
|
||||||
height: 46px;
|
height: $lgn-idp-logo-size;
|
||||||
width: 46px;
|
width: $lgn-idp-logo-size;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.provider-name {
|
span.provider-name {
|
||||||
line-height: $lgn-idp-provider-name-line-height;
|
line-height: $lgn-idp-provider-name-line-height;
|
||||||
position: absolute;
|
position: relative;
|
||||||
left: 50%;
|
left: calc(50% - $lgn-idp-logo-size);
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,4 +76,17 @@ $lgn-idp-border-radius: 0.5rem;
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.apple {
|
||||||
|
span.logo {
|
||||||
|
height: 46px;
|
||||||
|
width: 46px;
|
||||||
|
background-image: var(--apple-image-src);
|
||||||
|
background-size: 25px;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
border-radius: 5px;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,6 +117,8 @@
|
|||||||
--zitadel-color-github-background: #ffffff;
|
--zitadel-color-github-background: #ffffff;
|
||||||
--zitadel-color-gitlab-text: #8b8d8d;
|
--zitadel-color-gitlab-text: #8b8d8d;
|
||||||
--zitadel-color-gitlab-background: #ffffff;
|
--zitadel-color-gitlab-background: #ffffff;
|
||||||
|
--zitadel-color-apple-text: #8b8d8d;
|
||||||
|
--zitadel-color-apple-background: #ffffff;
|
||||||
|
|
||||||
--zitadel-color-qr: var(--zitadel-color-black);
|
--zitadel-color-qr: var(--zitadel-color-black);
|
||||||
--zitadel-color-qr-background: var(--zitadel-color-white);
|
--zitadel-color-qr-background: var(--zitadel-color-white);
|
||||||
@ -125,6 +127,7 @@
|
|||||||
--github-image-src: url(../../../images/idp/github.png);
|
--github-image-src: url(../../../images/idp/github.png);
|
||||||
--gitlab-image-src: url(../../../images/idp/gitlab.png);
|
--gitlab-image-src: url(../../../images/idp/gitlab.png);
|
||||||
--azure-image-src: url(../../../images/idp/ms.svg);
|
--azure-image-src: url(../../../images/idp/ms.svg);
|
||||||
|
--apple-image-src: url(../../../images/idp/apple.svg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.lgn-dark-theme {
|
.lgn-dark-theme {
|
||||||
@ -227,9 +230,12 @@
|
|||||||
--zitadel-color-github-background: #ffffff;
|
--zitadel-color-github-background: #ffffff;
|
||||||
--zitadel-color-gitlab-text: #8b8d8d;
|
--zitadel-color-gitlab-text: #8b8d8d;
|
||||||
--zitadel-color-gitlab-background: #ffffff;
|
--zitadel-color-gitlab-background: #ffffff;
|
||||||
|
--zitadel-color-apple-text: #8b8d8d;
|
||||||
|
--zitadel-color-apple-background: #ffffff;
|
||||||
|
|
||||||
--google-image-src: url(../../../images/idp/google.png);
|
--google-image-src: url(../../../images/idp/google.png);
|
||||||
--github-image-src: url(../../../images/idp/github-white.png);
|
--github-image-src: url(../../../images/idp/github-white.png);
|
||||||
--gitlab-image-src: url(../../../images/idp/gitlab.png);
|
--gitlab-image-src: url(../../../images/idp/gitlab.png);
|
||||||
--azure-image-src: url(../../../images/idp/ms.svg);
|
--azure-image-src: url(../../../images/idp/ms.svg);
|
||||||
|
--apple-image-src: url(../../../images/idp/apple-dark.svg);
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,11 @@
|
|||||||
<a href="{{ externalIDPAuthURL $reqid $provider.IDPConfigID}}"
|
<a href="{{ externalIDPAuthURL $reqid $provider.IDPConfigID}}"
|
||||||
class="lgn-idp {{idpProviderClass $provider.IDPType}}">
|
class="lgn-idp {{idpProviderClass $provider.IDPType}}">
|
||||||
<span class="logo"></span>
|
<span class="logo"></span>
|
||||||
|
{{if $provider.IDPType.IsSignInButton}}
|
||||||
|
<span class="provider-name">{{t "SignIn" "Provider" $provider.DisplayName}}</span>
|
||||||
|
{{else}}
|
||||||
<span class="provider-name">{{$provider.DisplayName}}</span>
|
<span class="provider-name">{{$provider.DisplayName}}</span>
|
||||||
|
{{end}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,7 +29,11 @@
|
|||||||
<a href="{{ externalIDPRegisterURL $reqid $provider.IDPConfigID}}"
|
<a href="{{ externalIDPRegisterURL $reqid $provider.IDPConfigID}}"
|
||||||
class="lgn-idp {{idpProviderClass $provider.IDPType}}">
|
class="lgn-idp {{idpProviderClass $provider.IDPType}}">
|
||||||
<span class="logo"></span>
|
<span class="logo"></span>
|
||||||
|
{{if $provider.IDPType.IsSignInButton}}
|
||||||
|
<span class="provider-name">{{t "SignIn" "Provider" $provider.DisplayName}}</span>
|
||||||
|
{{else}}
|
||||||
<span class="provider-name">{{$provider.DisplayName}}</span>
|
<span class="provider-name">{{$provider.DisplayName}}</span>
|
||||||
|
{{end}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -200,21 +200,15 @@ func authMethodsFromSession(session *query.Session) []domain.UserAuthMethodType
|
|||||||
if !session.IntentFactor.IntentCheckedAt.IsZero() {
|
if !session.IntentFactor.IntentCheckedAt.IsZero() {
|
||||||
types = append(types, domain.UserAuthMethodTypeIDP)
|
types = append(types, domain.UserAuthMethodTypeIDP)
|
||||||
}
|
}
|
||||||
// TODO: add checks with https://github.com/zitadel/zitadel/issues/5477
|
if !session.TOTPFactor.TOTPCheckedAt.IsZero() {
|
||||||
/*
|
types = append(types, domain.UserAuthMethodTypeTOTP)
|
||||||
if !session.TOTPFactor.TOTPCheckedAt.IsZero() {
|
}
|
||||||
types = append(types, domain.UserAuthMethodTypeTOTP)
|
if !session.OTPSMSFactor.OTPCheckedAt.IsZero() {
|
||||||
}
|
types = append(types, domain.UserAuthMethodTypeOTPSMS)
|
||||||
*/
|
}
|
||||||
// TODO: add checks with https://github.com/zitadel/zitadel/issues/6224
|
if !session.OTPEmailFactor.OTPCheckedAt.IsZero() {
|
||||||
/*
|
types = append(types, domain.UserAuthMethodTypeOTPEmail)
|
||||||
if !session.TOTPFactor.OTPSMSCheckedAt.IsZero() {
|
}
|
||||||
types = append(types, domain.UserAuthMethodTypeOTPSMS)
|
|
||||||
}
|
|
||||||
if !session.TOTPFactor.OTPEmailCheckedAt.IsZero() {
|
|
||||||
types = append(types, domain.UserAuthMethodTypeOTPEmail)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return types
|
return types
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,8 +34,9 @@ import (
|
|||||||
type Commands struct {
|
type Commands struct {
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
|
|
||||||
checkPermission domain.PermissionCheck
|
checkPermission domain.PermissionCheck
|
||||||
newCode cryptoCodeFunc
|
newCode cryptoCodeFunc
|
||||||
|
newCodeWithDefault cryptoCodeWithDefaultFunc
|
||||||
|
|
||||||
eventstore *eventstore.Eventstore
|
eventstore *eventstore.Eventstore
|
||||||
static static.Storage
|
static static.Storage
|
||||||
@ -122,6 +123,7 @@ func StartCommands(
|
|||||||
httpClient: httpClient,
|
httpClient: httpClient,
|
||||||
checkPermission: permissionCheck,
|
checkPermission: permissionCheck,
|
||||||
newCode: newCryptoCode,
|
newCode: newCryptoCode,
|
||||||
|
newCodeWithDefault: newCryptoCodeWithDefaultConfig,
|
||||||
sessionTokenCreator: sessionTokenCreator(idGenerator, sessionAlg),
|
sessionTokenCreator: sessionTokenCreator(idGenerator, sessionAlg),
|
||||||
sessionTokenVerifier: sessionTokenVerifier,
|
sessionTokenVerifier: sessionTokenVerifier,
|
||||||
defaultAccessTokenLifetime: defaultAccessTokenLifetime,
|
defaultAccessTokenLifetime: defaultAccessTokenLifetime,
|
||||||
|
@ -12,6 +12,10 @@ import (
|
|||||||
|
|
||||||
type cryptoCodeFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (*CryptoCode, error)
|
type cryptoCodeFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (*CryptoCode, error)
|
||||||
|
|
||||||
|
type cryptoCodeWithDefaultFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, defaultConfig *crypto.GeneratorConfig) (*CryptoCode, error)
|
||||||
|
|
||||||
|
var emptyConfig = &crypto.GeneratorConfig{}
|
||||||
|
|
||||||
type CryptoCode struct {
|
type CryptoCode struct {
|
||||||
Crypted *crypto.CryptoValue
|
Crypted *crypto.CryptoValue
|
||||||
Plain string
|
Plain string
|
||||||
@ -19,7 +23,11 @@ type CryptoCode struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newCryptoCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (*CryptoCode, error) {
|
func newCryptoCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (*CryptoCode, error) {
|
||||||
gen, config, err := secretGenerator(ctx, filter, typ, alg)
|
return newCryptoCodeWithDefaultConfig(ctx, filter, typ, alg, emptyConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCryptoCodeWithDefaultConfig(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, defaultConfig *crypto.GeneratorConfig) (*CryptoCode, error) {
|
||||||
|
gen, config, err := secretGenerator(ctx, filter, typ, alg, defaultConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -35,15 +43,15 @@ func newCryptoCode(ctx context.Context, filter preparation.FilterToQueryReducer,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func verifyCryptoCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, creation time.Time, expiry time.Duration, crypted *crypto.CryptoValue, plain string) error {
|
func verifyCryptoCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, creation time.Time, expiry time.Duration, crypted *crypto.CryptoValue, plain string) error {
|
||||||
gen, _, err := secretGenerator(ctx, filter, typ, alg)
|
gen, _, err := secretGenerator(ctx, filter, typ, alg, emptyConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return crypto.VerifyCode(creation, expiry, crypted, plain, gen)
|
return crypto.VerifyCode(creation, expiry, crypted, plain, gen)
|
||||||
}
|
}
|
||||||
|
|
||||||
func secretGenerator(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (crypto.Generator, *crypto.GeneratorConfig, error) {
|
func secretGenerator(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, defaultConfig *crypto.GeneratorConfig) (crypto.Generator, *crypto.GeneratorConfig, error) {
|
||||||
config, err := secretGeneratorConfig(ctx, filter, typ)
|
config, err := secretGeneratorConfigWithDefault(ctx, filter, typ, defaultConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -58,6 +66,10 @@ func secretGenerator(ctx context.Context, filter preparation.FilterToQueryReduce
|
|||||||
}
|
}
|
||||||
|
|
||||||
func secretGeneratorConfig(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType) (*crypto.GeneratorConfig, error) {
|
func secretGeneratorConfig(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType) (*crypto.GeneratorConfig, error) {
|
||||||
|
return secretGeneratorConfigWithDefault(ctx, filter, typ, emptyConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func secretGeneratorConfigWithDefault(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, defaultConfig *crypto.GeneratorConfig) (*crypto.GeneratorConfig, error) {
|
||||||
wm := NewInstanceSecretGeneratorConfigWriteModel(ctx, typ)
|
wm := NewInstanceSecretGeneratorConfigWriteModel(ctx, typ)
|
||||||
events, err := filter(ctx, wm.Query())
|
events, err := filter(ctx, wm.Query())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -67,6 +79,9 @@ func secretGeneratorConfig(ctx context.Context, filter preparation.FilterToQuery
|
|||||||
if err := wm.Reduce(); err != nil {
|
if err := wm.Reduce(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if wm.State != domain.SecretGeneratorStateActive {
|
||||||
|
return defaultConfig, nil
|
||||||
|
}
|
||||||
return &crypto.GeneratorConfig{
|
return &crypto.GeneratorConfig{
|
||||||
Length: wm.Length,
|
Length: wm.Length,
|
||||||
Expiry: wm.Expiry,
|
Expiry: wm.Expiry,
|
||||||
|
@ -33,6 +33,21 @@ func mockCode(code string, exp time.Duration) cryptoCodeFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mockCodeWithDefault(code string, exp time.Duration) cryptoCodeWithDefaultFunc {
|
||||||
|
return func(ctx context.Context, filter preparation.FilterToQueryReducer, _ domain.SecretGeneratorType, alg crypto.Crypto, _ *crypto.GeneratorConfig) (*CryptoCode, error) {
|
||||||
|
return &CryptoCode{
|
||||||
|
Crypted: &crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte(code),
|
||||||
|
},
|
||||||
|
Plain: code,
|
||||||
|
Expiry: exp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
testGeneratorConfig = crypto.GeneratorConfig{
|
testGeneratorConfig = crypto.GeneratorConfig{
|
||||||
Length: 12,
|
Length: 12,
|
||||||
@ -175,8 +190,9 @@ func Test_verifyCryptoCode(t *testing.T) {
|
|||||||
|
|
||||||
func Test_secretGenerator(t *testing.T) {
|
func Test_secretGenerator(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
typ domain.SecretGeneratorType
|
typ domain.SecretGeneratorType
|
||||||
alg crypto.Crypto
|
alg crypto.Crypto
|
||||||
|
defaultConfig *crypto.GeneratorConfig
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -190,8 +206,9 @@ func Test_secretGenerator(t *testing.T) {
|
|||||||
name: "filter config error",
|
name: "filter config error",
|
||||||
eventsore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)),
|
eventsore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)),
|
||||||
args: args{
|
args: args{
|
||||||
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
||||||
alg: crypto.CreateMockHashAlg(gomock.NewController(t)),
|
alg: crypto.CreateMockHashAlg(gomock.NewController(t)),
|
||||||
|
defaultConfig: emptyConfig,
|
||||||
},
|
},
|
||||||
wantErr: io.ErrClosedPipe,
|
wantErr: io.ErrClosedPipe,
|
||||||
},
|
},
|
||||||
@ -201,8 +218,9 @@ func Test_secretGenerator(t *testing.T) {
|
|||||||
eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)),
|
eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)),
|
||||||
)),
|
)),
|
||||||
args: args{
|
args: args{
|
||||||
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
||||||
alg: crypto.CreateMockHashAlg(gomock.NewController(t)),
|
alg: crypto.CreateMockHashAlg(gomock.NewController(t)),
|
||||||
|
defaultConfig: emptyConfig,
|
||||||
},
|
},
|
||||||
want: crypto.NewHashGenerator(testGeneratorConfig, crypto.CreateMockHashAlg(gomock.NewController(t))),
|
want: crypto.NewHashGenerator(testGeneratorConfig, crypto.CreateMockHashAlg(gomock.NewController(t))),
|
||||||
wantConf: &testGeneratorConfig,
|
wantConf: &testGeneratorConfig,
|
||||||
@ -213,8 +231,31 @@ func Test_secretGenerator(t *testing.T) {
|
|||||||
eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)),
|
eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)),
|
||||||
)),
|
)),
|
||||||
args: args{
|
args: args{
|
||||||
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
||||||
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
defaultConfig: emptyConfig,
|
||||||
|
},
|
||||||
|
want: crypto.NewEncryptionGenerator(testGeneratorConfig, crypto.CreateMockEncryptionAlg(gomock.NewController(t))),
|
||||||
|
wantConf: &testGeneratorConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hash generator with default config",
|
||||||
|
eventsore: eventstoreExpect(t, expectFilter()),
|
||||||
|
args: args{
|
||||||
|
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
||||||
|
alg: crypto.CreateMockHashAlg(gomock.NewController(t)),
|
||||||
|
defaultConfig: &testGeneratorConfig,
|
||||||
|
},
|
||||||
|
want: crypto.NewHashGenerator(testGeneratorConfig, crypto.CreateMockHashAlg(gomock.NewController(t))),
|
||||||
|
wantConf: &testGeneratorConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "encryption generator with default config",
|
||||||
|
eventsore: eventstoreExpect(t, expectFilter()),
|
||||||
|
args: args{
|
||||||
|
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
||||||
|
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
defaultConfig: &testGeneratorConfig,
|
||||||
},
|
},
|
||||||
want: crypto.NewEncryptionGenerator(testGeneratorConfig, crypto.CreateMockEncryptionAlg(gomock.NewController(t))),
|
want: crypto.NewEncryptionGenerator(testGeneratorConfig, crypto.CreateMockEncryptionAlg(gomock.NewController(t))),
|
||||||
wantConf: &testGeneratorConfig,
|
wantConf: &testGeneratorConfig,
|
||||||
@ -225,15 +266,16 @@ func Test_secretGenerator(t *testing.T) {
|
|||||||
eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)),
|
eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)),
|
||||||
)),
|
)),
|
||||||
args: args{
|
args: args{
|
||||||
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
||||||
alg: nil,
|
alg: nil,
|
||||||
|
defaultConfig: emptyConfig,
|
||||||
},
|
},
|
||||||
wantErr: errors.ThrowInternalf(nil, "COMMA-RreV6", "Errors.Internal unsupported crypto algorithm type %T", nil),
|
wantErr: errors.ThrowInternalf(nil, "COMMA-RreV6", "Errors.Internal unsupported crypto algorithm type %T", nil),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, gotConf, err := secretGenerator(context.Background(), tt.eventsore.Filter, tt.args.typ, tt.args.alg)
|
got, gotConf, err := secretGenerator(context.Background(), tt.eventsore.Filter, tt.args.typ, tt.args.alg, tt.args.defaultConfig)
|
||||||
require.ErrorIs(t, err, tt.wantErr)
|
require.ErrorIs(t, err, tt.wantErr)
|
||||||
assert.IsType(t, tt.want, got)
|
assert.IsType(t, tt.want, got)
|
||||||
assert.Equal(t, tt.wantConf, gotConf)
|
assert.Equal(t, tt.wantConf, gotConf)
|
||||||
|
@ -110,6 +110,16 @@ type LDAPProvider struct {
|
|||||||
IDPOptions idp.Options
|
IDPOptions idp.Options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AppleProvider struct {
|
||||||
|
Name string
|
||||||
|
ClientID string
|
||||||
|
TeamID string
|
||||||
|
KeyID string
|
||||||
|
PrivateKey []byte
|
||||||
|
Scopes []string
|
||||||
|
IDPOptions idp.Options
|
||||||
|
}
|
||||||
|
|
||||||
func ExistsIDP(ctx context.Context, filter preparation.FilterToQueryReducer, id, orgID string) (exists bool, err error) {
|
func ExistsIDP(ctx context.Context, filter preparation.FilterToQueryReducer, id, orgID string) (exists bool, err error) {
|
||||||
writeModel := NewOrgIDPRemoveWriteModel(orgID, id)
|
writeModel := NewOrgIDPRemoveWriteModel(orgID, id)
|
||||||
events, err := filter(ctx, writeModel.Query())
|
events, err := filter(ctx, writeModel.Query())
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/errors"
|
"github.com/zitadel/zitadel/internal/errors"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
"github.com/zitadel/zitadel/internal/idp"
|
"github.com/zitadel/zitadel/internal/idp"
|
||||||
|
"github.com/zitadel/zitadel/internal/idp/providers/apple"
|
||||||
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
||||||
"github.com/zitadel/zitadel/internal/idp/providers/jwt"
|
"github.com/zitadel/zitadel/internal/idp/providers/jwt"
|
||||||
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
|
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
|
||||||
@ -215,6 +216,8 @@ func tokensForSucceededIDPIntent(session idp.Session, encryptionAlg crypto.Encry
|
|||||||
tokens = s.Tokens
|
tokens = s.Tokens
|
||||||
case *azuread.Session:
|
case *azuread.Session:
|
||||||
tokens = s.Tokens
|
tokens = s.Tokens
|
||||||
|
case *apple.Session:
|
||||||
|
tokens = s.Tokens
|
||||||
default:
|
default:
|
||||||
return nil, "", nil
|
return nil, "", nil
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package command
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zitadel/logging"
|
"github.com/zitadel/logging"
|
||||||
@ -14,6 +15,7 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/errors"
|
"github.com/zitadel/zitadel/internal/errors"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
providers "github.com/zitadel/zitadel/internal/idp"
|
providers "github.com/zitadel/zitadel/internal/idp"
|
||||||
|
"github.com/zitadel/zitadel/internal/idp/providers/apple"
|
||||||
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
||||||
"github.com/zitadel/zitadel/internal/idp/providers/github"
|
"github.com/zitadel/zitadel/internal/idp/providers/github"
|
||||||
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
|
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
|
||||||
@ -1587,6 +1589,138 @@ func (wm *LDAPIDPWriteModel) GetProviderOptions() idp.Options {
|
|||||||
return wm.Options
|
return wm.Options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AppleIDPWriteModel struct {
|
||||||
|
eventstore.WriteModel
|
||||||
|
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
ClientID string
|
||||||
|
TeamID string
|
||||||
|
KeyID string
|
||||||
|
PrivateKey *crypto.CryptoValue
|
||||||
|
Scopes []string
|
||||||
|
idp.Options
|
||||||
|
|
||||||
|
State domain.IDPState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *AppleIDPWriteModel) Reduce() error {
|
||||||
|
for _, event := range wm.Events {
|
||||||
|
switch e := event.(type) {
|
||||||
|
case *idp.AppleIDPAddedEvent:
|
||||||
|
wm.reduceAddedEvent(e)
|
||||||
|
case *idp.AppleIDPChangedEvent:
|
||||||
|
wm.reduceChangedEvent(e)
|
||||||
|
case *idp.RemovedEvent:
|
||||||
|
wm.State = domain.IDPStateRemoved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wm.WriteModel.Reduce()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *AppleIDPWriteModel) reduceAddedEvent(e *idp.AppleIDPAddedEvent) {
|
||||||
|
wm.Name = e.Name
|
||||||
|
wm.ClientID = e.ClientID
|
||||||
|
wm.TeamID = e.TeamID
|
||||||
|
wm.KeyID = e.KeyID
|
||||||
|
wm.PrivateKey = e.PrivateKey
|
||||||
|
wm.Scopes = e.Scopes
|
||||||
|
wm.Options = e.Options
|
||||||
|
wm.State = domain.IDPStateActive
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *AppleIDPWriteModel) reduceChangedEvent(e *idp.AppleIDPChangedEvent) {
|
||||||
|
if e.Name != nil {
|
||||||
|
wm.Name = *e.Name
|
||||||
|
}
|
||||||
|
if e.ClientID != nil {
|
||||||
|
wm.ClientID = *e.ClientID
|
||||||
|
}
|
||||||
|
if e.PrivateKey != nil {
|
||||||
|
wm.PrivateKey = e.PrivateKey
|
||||||
|
}
|
||||||
|
if e.Scopes != nil {
|
||||||
|
wm.Scopes = e.Scopes
|
||||||
|
}
|
||||||
|
wm.Options.ReduceChanges(e.OptionChanges)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *AppleIDPWriteModel) NewChanges(
|
||||||
|
name string,
|
||||||
|
clientID string,
|
||||||
|
teamID string,
|
||||||
|
keyID string,
|
||||||
|
privateKey []byte,
|
||||||
|
secretCrypto crypto.Crypto,
|
||||||
|
scopes []string,
|
||||||
|
options idp.Options,
|
||||||
|
) ([]idp.AppleIDPChanges, error) {
|
||||||
|
changes := make([]idp.AppleIDPChanges, 0)
|
||||||
|
var encryptedKey *crypto.CryptoValue
|
||||||
|
var err error
|
||||||
|
if len(privateKey) != 0 {
|
||||||
|
encryptedKey, err = crypto.Crypt(privateKey, secretCrypto)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
changes = append(changes, idp.ChangeApplePrivateKey(encryptedKey))
|
||||||
|
}
|
||||||
|
if wm.Name != name {
|
||||||
|
changes = append(changes, idp.ChangeAppleName(name))
|
||||||
|
}
|
||||||
|
if wm.ClientID != clientID {
|
||||||
|
changes = append(changes, idp.ChangeAppleClientID(clientID))
|
||||||
|
}
|
||||||
|
if wm.TeamID != teamID {
|
||||||
|
changes = append(changes, idp.ChangeAppleTeamID(teamID))
|
||||||
|
}
|
||||||
|
if wm.KeyID != keyID {
|
||||||
|
changes = append(changes, idp.ChangeAppleKeyID(keyID))
|
||||||
|
}
|
||||||
|
if slices.Compare(wm.Scopes, scopes) != 0 {
|
||||||
|
changes = append(changes, idp.ChangeAppleScopes(scopes))
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := wm.Options.Changes(options)
|
||||||
|
if !opts.IsZero() {
|
||||||
|
changes = append(changes, idp.ChangeAppleOptions(opts))
|
||||||
|
}
|
||||||
|
return changes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *AppleIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.EncryptionAlgorithm) (providers.Provider, error) {
|
||||||
|
privateKey, err := crypto.Decrypt(wm.PrivateKey, idpAlg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
opts := make([]oidc.ProviderOpts, 0, 4)
|
||||||
|
if wm.IsCreationAllowed {
|
||||||
|
opts = append(opts, oidc.WithCreationAllowed())
|
||||||
|
}
|
||||||
|
if wm.IsLinkingAllowed {
|
||||||
|
opts = append(opts, oidc.WithLinkingAllowed())
|
||||||
|
}
|
||||||
|
if wm.IsAutoCreation {
|
||||||
|
opts = append(opts, oidc.WithAutoCreation())
|
||||||
|
}
|
||||||
|
if wm.IsAutoUpdate {
|
||||||
|
opts = append(opts, oidc.WithAutoUpdate())
|
||||||
|
}
|
||||||
|
return apple.New(
|
||||||
|
wm.ClientID,
|
||||||
|
wm.TeamID,
|
||||||
|
wm.KeyID,
|
||||||
|
callbackURL,
|
||||||
|
privateKey,
|
||||||
|
wm.Scopes,
|
||||||
|
opts...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *AppleIDPWriteModel) GetProviderOptions() idp.Options {
|
||||||
|
return wm.Options
|
||||||
|
}
|
||||||
|
|
||||||
type IDPRemoveWriteModel struct {
|
type IDPRemoveWriteModel struct {
|
||||||
eventstore.WriteModel
|
eventstore.WriteModel
|
||||||
|
|
||||||
@ -1617,6 +1751,8 @@ func (wm *IDPRemoveWriteModel) Reduce() error {
|
|||||||
wm.reduceAdded(e.ID)
|
wm.reduceAdded(e.ID)
|
||||||
case *idp.LDAPIDPAddedEvent:
|
case *idp.LDAPIDPAddedEvent:
|
||||||
wm.reduceAdded(e.ID)
|
wm.reduceAdded(e.ID)
|
||||||
|
case *idp.AppleIDPAddedEvent:
|
||||||
|
wm.reduceAdded(e.ID)
|
||||||
case *idp.RemovedEvent:
|
case *idp.RemovedEvent:
|
||||||
wm.reduceRemoved(e.ID)
|
wm.reduceRemoved(e.ID)
|
||||||
case *idpconfig.IDPConfigAddedEvent:
|
case *idpconfig.IDPConfigAddedEvent:
|
||||||
@ -1699,6 +1835,10 @@ func (wm *IDPTypeWriteModel) Reduce() error {
|
|||||||
wm.reduceAdded(e.ID, domain.IDPTypeLDAP, e.Aggregate())
|
wm.reduceAdded(e.ID, domain.IDPTypeLDAP, e.Aggregate())
|
||||||
case *org.LDAPIDPAddedEvent:
|
case *org.LDAPIDPAddedEvent:
|
||||||
wm.reduceAdded(e.ID, domain.IDPTypeLDAP, e.Aggregate())
|
wm.reduceAdded(e.ID, domain.IDPTypeLDAP, e.Aggregate())
|
||||||
|
case *instance.AppleIDPAddedEvent:
|
||||||
|
wm.reduceAdded(e.ID, domain.IDPTypeApple, e.Aggregate())
|
||||||
|
case *org.AppleIDPAddedEvent:
|
||||||
|
wm.reduceAdded(e.ID, domain.IDPTypeApple, e.Aggregate())
|
||||||
case *instance.OIDCIDPMigratedAzureADEvent:
|
case *instance.OIDCIDPMigratedAzureADEvent:
|
||||||
wm.reduceChanged(e.ID, domain.IDPTypeAzureAD)
|
wm.reduceChanged(e.ID, domain.IDPTypeAzureAD)
|
||||||
case *org.OIDCIDPMigratedAzureADEvent:
|
case *org.OIDCIDPMigratedAzureADEvent:
|
||||||
@ -1774,6 +1914,7 @@ func (wm *IDPTypeWriteModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
instance.GitLabSelfHostedIDPAddedEventType,
|
instance.GitLabSelfHostedIDPAddedEventType,
|
||||||
instance.GoogleIDPAddedEventType,
|
instance.GoogleIDPAddedEventType,
|
||||||
instance.LDAPIDPAddedEventType,
|
instance.LDAPIDPAddedEventType,
|
||||||
|
instance.AppleIDPAddedEventType,
|
||||||
instance.OIDCIDPMigratedAzureADEventType,
|
instance.OIDCIDPMigratedAzureADEventType,
|
||||||
instance.OIDCIDPMigratedGoogleEventType,
|
instance.OIDCIDPMigratedGoogleEventType,
|
||||||
instance.IDPRemovedEventType,
|
instance.IDPRemovedEventType,
|
||||||
@ -1792,6 +1933,7 @@ func (wm *IDPTypeWriteModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
org.GitLabSelfHostedIDPAddedEventType,
|
org.GitLabSelfHostedIDPAddedEventType,
|
||||||
org.GoogleIDPAddedEventType,
|
org.GoogleIDPAddedEventType,
|
||||||
org.LDAPIDPAddedEventType,
|
org.LDAPIDPAddedEventType,
|
||||||
|
org.AppleIDPAddedEventType,
|
||||||
org.OIDCIDPMigratedAzureADEventType,
|
org.OIDCIDPMigratedAzureADEventType,
|
||||||
org.OIDCIDPMigratedGoogleEventType,
|
org.OIDCIDPMigratedGoogleEventType,
|
||||||
org.IDPRemovedEventType,
|
org.IDPRemovedEventType,
|
||||||
@ -1859,6 +2001,8 @@ func NewAllIDPWriteModel(resourceOwner string, instanceBool bool, id string, idp
|
|||||||
writeModel.model = NewGitLabSelfHostedInstanceIDPWriteModel(resourceOwner, id)
|
writeModel.model = NewGitLabSelfHostedInstanceIDPWriteModel(resourceOwner, id)
|
||||||
case domain.IDPTypeGoogle:
|
case domain.IDPTypeGoogle:
|
||||||
writeModel.model = NewGoogleInstanceIDPWriteModel(resourceOwner, id)
|
writeModel.model = NewGoogleInstanceIDPWriteModel(resourceOwner, id)
|
||||||
|
case domain.IDPTypeApple:
|
||||||
|
writeModel.model = NewAppleInstanceIDPWriteModel(resourceOwner, id)
|
||||||
case domain.IDPTypeUnspecified:
|
case domain.IDPTypeUnspecified:
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
@ -1886,6 +2030,8 @@ func NewAllIDPWriteModel(resourceOwner string, instanceBool bool, id string, idp
|
|||||||
writeModel.model = NewGitLabSelfHostedOrgIDPWriteModel(resourceOwner, id)
|
writeModel.model = NewGitLabSelfHostedOrgIDPWriteModel(resourceOwner, id)
|
||||||
case domain.IDPTypeGoogle:
|
case domain.IDPTypeGoogle:
|
||||||
writeModel.model = NewGoogleOrgIDPWriteModel(resourceOwner, id)
|
writeModel.model = NewGoogleOrgIDPWriteModel(resourceOwner, id)
|
||||||
|
case domain.IDPTypeApple:
|
||||||
|
writeModel.model = NewAppleOrgIDPWriteModel(resourceOwner, id)
|
||||||
case domain.IDPTypeUnspecified:
|
case domain.IDPTypeUnspecified:
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
|
@ -414,6 +414,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
|
|||||||
instanceAgg,
|
instanceAgg,
|
||||||
setup.SMTPConfiguration.From,
|
setup.SMTPConfiguration.From,
|
||||||
setup.SMTPConfiguration.FromName,
|
setup.SMTPConfiguration.FromName,
|
||||||
|
setup.SMTPConfiguration.ReplyToAddress,
|
||||||
setup.SMTPConfiguration.SMTP.Host,
|
setup.SMTPConfiguration.SMTP.Host,
|
||||||
setup.SMTPConfiguration.SMTP.User,
|
setup.SMTPConfiguration.SMTP.User,
|
||||||
[]byte(setup.SMTPConfiguration.SMTP.Password),
|
[]byte(setup.SMTPConfiguration.SMTP.Password),
|
||||||
|
@ -467,6 +467,48 @@ func (c *Commands) UpdateInstanceLDAPProvider(ctx context.Context, id string, pr
|
|||||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Commands) AddInstanceAppleProvider(ctx context.Context, provider AppleProvider) (string, *domain.ObjectDetails, error) {
|
||||||
|
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||||
|
instanceAgg := instance.NewAggregate(instanceID)
|
||||||
|
id, err := c.idGenerator.Next()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
writeModel := NewAppleInstanceIDPWriteModel(instanceID, id)
|
||||||
|
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddInstanceAppleProvider(instanceAgg, writeModel, provider))
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
return id, pushedEventsToObjectDetails(pushedEvents), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) UpdateInstanceAppleProvider(ctx context.Context, id string, provider AppleProvider) (*domain.ObjectDetails, error) {
|
||||||
|
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||||
|
instanceAgg := instance.NewAggregate(instanceID)
|
||||||
|
writeModel := NewAppleInstanceIDPWriteModel(instanceID, id)
|
||||||
|
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateInstanceAppleProvider(instanceAgg, writeModel, provider))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(cmds) == 0 {
|
||||||
|
// no change, so return directly
|
||||||
|
return &domain.ObjectDetails{
|
||||||
|
Sequence: writeModel.ProcessedSequence,
|
||||||
|
EventDate: writeModel.ChangeDate,
|
||||||
|
ResourceOwner: writeModel.ResourceOwner,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Commands) DeleteInstanceProvider(ctx context.Context, id string) (*domain.ObjectDetails, error) {
|
func (c *Commands) DeleteInstanceProvider(ctx context.Context, id string) (*domain.ObjectDetails, error) {
|
||||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareDeleteInstanceProvider(instanceAgg, id))
|
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareDeleteInstanceProvider(instanceAgg, id))
|
||||||
@ -1518,6 +1560,98 @@ func (c *Commands) prepareUpdateInstanceLDAPProvider(a *instance.Aggregate, writ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Commands) prepareAddInstanceAppleProvider(a *instance.Aggregate, writeModel *InstanceAppleIDPWriteModel, provider AppleProvider) preparation.Validation {
|
||||||
|
return func() (preparation.CreateCommands, error) {
|
||||||
|
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
|
||||||
|
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-jkn3w", "Errors.IDP.ClientIDMissing")
|
||||||
|
}
|
||||||
|
if provider.TeamID = strings.TrimSpace(provider.TeamID); provider.TeamID == "" {
|
||||||
|
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Ffg32", "Errors.IDP.TeamIDMissing")
|
||||||
|
}
|
||||||
|
if provider.KeyID = strings.TrimSpace(provider.KeyID); provider.KeyID == "" {
|
||||||
|
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-GDjm5", "Errors.IDP.KeyIDMissing")
|
||||||
|
}
|
||||||
|
if len(provider.PrivateKey) == 0 {
|
||||||
|
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-GVD4n", "Errors.IDP.PrivateKeyMissing")
|
||||||
|
}
|
||||||
|
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||||
|
events, err := filter(ctx, writeModel.Query())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
writeModel.AppendEvents(events...)
|
||||||
|
if err = writeModel.Reduce(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
privateKey, err := crypto.Encrypt(provider.PrivateKey, c.idpConfigEncryption)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return []eventstore.Command{
|
||||||
|
instance.NewAppleIDPAddedEvent(
|
||||||
|
ctx,
|
||||||
|
&a.Aggregate,
|
||||||
|
writeModel.ID,
|
||||||
|
provider.Name,
|
||||||
|
provider.ClientID,
|
||||||
|
provider.TeamID,
|
||||||
|
provider.KeyID,
|
||||||
|
privateKey,
|
||||||
|
provider.Scopes,
|
||||||
|
provider.IDPOptions,
|
||||||
|
),
|
||||||
|
}, nil
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) prepareUpdateInstanceAppleProvider(a *instance.Aggregate, writeModel *InstanceAppleIDPWriteModel, provider AppleProvider) preparation.Validation {
|
||||||
|
return func() (preparation.CreateCommands, error) {
|
||||||
|
if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" {
|
||||||
|
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-FRHBH", "Errors.IDMissing")
|
||||||
|
}
|
||||||
|
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
|
||||||
|
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-SFm4l", "Errors.IDP.ClientIDMissing")
|
||||||
|
}
|
||||||
|
if provider.TeamID = strings.TrimSpace(provider.TeamID); provider.TeamID == "" {
|
||||||
|
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-SG34t", "Errors.IDP.TeamIDMissing")
|
||||||
|
}
|
||||||
|
if provider.KeyID = strings.TrimSpace(provider.KeyID); provider.KeyID == "" {
|
||||||
|
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Gh4z2", "Errors.IDP.KeyIDMissing")
|
||||||
|
}
|
||||||
|
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||||
|
events, err := filter(ctx, writeModel.Query())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
writeModel.AppendEvents(events...)
|
||||||
|
if err = writeModel.Reduce(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !writeModel.State.Exists() {
|
||||||
|
return nil, caos_errs.ThrowNotFound(nil, "INST-SG3bh", "Errors.IDPConfig.NotExisting")
|
||||||
|
}
|
||||||
|
event, err := writeModel.NewChangedEvent(
|
||||||
|
ctx,
|
||||||
|
&a.Aggregate,
|
||||||
|
writeModel.ID,
|
||||||
|
provider.Name,
|
||||||
|
provider.ClientID,
|
||||||
|
provider.TeamID,
|
||||||
|
provider.KeyID,
|
||||||
|
provider.PrivateKey,
|
||||||
|
c.idpConfigEncryption,
|
||||||
|
provider.Scopes,
|
||||||
|
provider.IDPOptions,
|
||||||
|
)
|
||||||
|
if err != nil || event == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return []eventstore.Command{event}, nil
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Commands) prepareDeleteInstanceProvider(a *instance.Aggregate, id string) preparation.Validation {
|
func (c *Commands) prepareDeleteInstanceProvider(a *instance.Aggregate, id string) preparation.Validation {
|
||||||
return func() (preparation.CreateCommands, error) {
|
return func() (preparation.CreateCommands, error) {
|
||||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||||
|
@ -793,6 +793,73 @@ func (wm *InstanceLDAPIDPWriteModel) NewChangedEvent(
|
|||||||
return instance.NewLDAPIDPChangedEvent(ctx, aggregate, id, changes)
|
return instance.NewLDAPIDPChangedEvent(ctx, aggregate, id, changes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InstanceAppleIDPWriteModel struct {
|
||||||
|
AppleIDPWriteModel
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAppleInstanceIDPWriteModel(instanceID, id string) *InstanceAppleIDPWriteModel {
|
||||||
|
return &InstanceAppleIDPWriteModel{
|
||||||
|
AppleIDPWriteModel{
|
||||||
|
WriteModel: eventstore.WriteModel{
|
||||||
|
AggregateID: instanceID,
|
||||||
|
ResourceOwner: instanceID,
|
||||||
|
},
|
||||||
|
ID: id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *InstanceAppleIDPWriteModel) AppendEvents(events ...eventstore.Event) {
|
||||||
|
for _, event := range events {
|
||||||
|
switch e := event.(type) {
|
||||||
|
case *instance.AppleIDPAddedEvent:
|
||||||
|
wm.AppleIDPWriteModel.AppendEvents(&e.AppleIDPAddedEvent)
|
||||||
|
case *instance.AppleIDPChangedEvent:
|
||||||
|
wm.AppleIDPWriteModel.AppendEvents(&e.AppleIDPChangedEvent)
|
||||||
|
case *instance.IDPRemovedEvent:
|
||||||
|
wm.AppleIDPWriteModel.AppendEvents(&e.RemovedEvent)
|
||||||
|
default:
|
||||||
|
wm.AppleIDPWriteModel.AppendEvents(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *InstanceAppleIDPWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||||
|
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||||
|
ResourceOwner(wm.ResourceOwner).
|
||||||
|
AddQuery().
|
||||||
|
AggregateTypes(instance.AggregateType).
|
||||||
|
AggregateIDs(wm.AggregateID).
|
||||||
|
EventTypes(
|
||||||
|
instance.AppleIDPAddedEventType,
|
||||||
|
instance.AppleIDPChangedEventType,
|
||||||
|
instance.IDPRemovedEventType,
|
||||||
|
).
|
||||||
|
EventData(map[string]interface{}{"id": wm.ID}).
|
||||||
|
Builder()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *InstanceAppleIDPWriteModel) NewChangedEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
aggregate *eventstore.Aggregate,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
clientID,
|
||||||
|
teamID,
|
||||||
|
keyID string,
|
||||||
|
privateKey []byte,
|
||||||
|
secretCrypto crypto.Crypto,
|
||||||
|
scopes []string,
|
||||||
|
options idp.Options,
|
||||||
|
) (*instance.AppleIDPChangedEvent, error) {
|
||||||
|
|
||||||
|
changes, err := wm.AppleIDPWriteModel.NewChanges(name, clientID, teamID, keyID, privateKey, secretCrypto, scopes, options)
|
||||||
|
if err != nil || len(changes) == 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return instance.NewAppleIDPChangedEvent(ctx, aggregate, id, changes)
|
||||||
|
}
|
||||||
|
|
||||||
type InstanceIDPRemoveWriteModel struct {
|
type InstanceIDPRemoveWriteModel struct {
|
||||||
IDPRemoveWriteModel
|
IDPRemoveWriteModel
|
||||||
}
|
}
|
||||||
@ -832,6 +899,8 @@ func (wm *InstanceIDPRemoveWriteModel) AppendEvents(events ...eventstore.Event)
|
|||||||
wm.IDPRemoveWriteModel.AppendEvents(&e.GoogleIDPAddedEvent)
|
wm.IDPRemoveWriteModel.AppendEvents(&e.GoogleIDPAddedEvent)
|
||||||
case *instance.LDAPIDPAddedEvent:
|
case *instance.LDAPIDPAddedEvent:
|
||||||
wm.IDPRemoveWriteModel.AppendEvents(&e.LDAPIDPAddedEvent)
|
wm.IDPRemoveWriteModel.AppendEvents(&e.LDAPIDPAddedEvent)
|
||||||
|
case *instance.AppleIDPAddedEvent:
|
||||||
|
wm.IDPRemoveWriteModel.AppendEvents(&e.AppleIDPAddedEvent)
|
||||||
case *instance.IDPRemovedEvent:
|
case *instance.IDPRemovedEvent:
|
||||||
wm.IDPRemoveWriteModel.AppendEvents(&e.RemovedEvent)
|
wm.IDPRemoveWriteModel.AppendEvents(&e.RemovedEvent)
|
||||||
case *instance.IDPConfigAddedEvent:
|
case *instance.IDPConfigAddedEvent:
|
||||||
@ -861,6 +930,7 @@ func (wm *InstanceIDPRemoveWriteModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
instance.GitLabSelfHostedIDPAddedEventType,
|
instance.GitLabSelfHostedIDPAddedEventType,
|
||||||
instance.GoogleIDPAddedEventType,
|
instance.GoogleIDPAddedEventType,
|
||||||
instance.LDAPIDPAddedEventType,
|
instance.LDAPIDPAddedEventType,
|
||||||
|
instance.AppleIDPAddedEventType,
|
||||||
instance.IDPRemovedEventType,
|
instance.IDPRemovedEventType,
|
||||||
).
|
).
|
||||||
EventData(map[string]interface{}{"id": wm.ID}).
|
EventData(map[string]interface{}{"id": wm.ID}).
|
||||||
|
@ -4857,3 +4857,464 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommandSide_AddInstanceAppleIDP(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
eventstore *eventstore.Eventstore
|
||||||
|
idGenerator id.Generator
|
||||||
|
secretCrypto crypto.EncryptionAlgorithm
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
provider AppleProvider
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
id string
|
||||||
|
want *domain.ObjectDetails
|
||||||
|
err func(error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"invalid clientID",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||||
|
provider: AppleProvider{},
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: func(err error) bool {
|
||||||
|
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-jkn3w", "Errors.IDP.ClientIDMissing"))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid teamID",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||||
|
provider: AppleProvider{
|
||||||
|
ClientID: "clientID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: func(err error) bool {
|
||||||
|
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-Ffg32", "Errors.IDP.TeamIDMissing"))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid keyID",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||||
|
provider: AppleProvider{
|
||||||
|
ClientID: "clientID",
|
||||||
|
TeamID: "teamID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: func(err error) bool {
|
||||||
|
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-GDjm5", "Errors.IDP.KeyIDMissing"))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid privateKey",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||||
|
provider: AppleProvider{
|
||||||
|
ClientID: "clientID",
|
||||||
|
TeamID: "teamID",
|
||||||
|
KeyID: "keyID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: func(err error) bool {
|
||||||
|
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-GVD4n", "Errors.IDP.PrivateKeyMissing"))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ok",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(),
|
||||||
|
expectPush(
|
||||||
|
[]*repository.Event{
|
||||||
|
eventFromEventPusherWithInstanceID(
|
||||||
|
"instance1",
|
||||||
|
instance.NewAppleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||||
|
"id1",
|
||||||
|
"",
|
||||||
|
"clientID",
|
||||||
|
"teamID",
|
||||||
|
"keyID",
|
||||||
|
&crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("privateKey"),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
idp.Options{},
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||||
|
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||||
|
provider: AppleProvider{
|
||||||
|
ClientID: "clientID",
|
||||||
|
TeamID: "teamID",
|
||||||
|
KeyID: "keyID",
|
||||||
|
PrivateKey: []byte("privateKey"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
id: "id1",
|
||||||
|
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ok all set",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(),
|
||||||
|
expectPush(
|
||||||
|
[]*repository.Event{
|
||||||
|
eventFromEventPusherWithInstanceID(
|
||||||
|
"instance1",
|
||||||
|
instance.NewAppleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||||
|
"id1",
|
||||||
|
"",
|
||||||
|
"clientID",
|
||||||
|
"teamID",
|
||||||
|
"keyID",
|
||||||
|
&crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("privateKey"),
|
||||||
|
},
|
||||||
|
[]string{"name", "email"},
|
||||||
|
idp.Options{
|
||||||
|
IsCreationAllowed: true,
|
||||||
|
IsLinkingAllowed: true,
|
||||||
|
IsAutoCreation: true,
|
||||||
|
IsAutoUpdate: true,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||||
|
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||||
|
provider: AppleProvider{
|
||||||
|
ClientID: "clientID",
|
||||||
|
TeamID: "teamID",
|
||||||
|
KeyID: "keyID",
|
||||||
|
PrivateKey: []byte("privateKey"),
|
||||||
|
Scopes: []string{"name", "email"},
|
||||||
|
IDPOptions: idp.Options{
|
||||||
|
IsCreationAllowed: true,
|
||||||
|
IsLinkingAllowed: true,
|
||||||
|
IsAutoCreation: true,
|
||||||
|
IsAutoUpdate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
id: "id1",
|
||||||
|
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Commands{
|
||||||
|
eventstore: tt.fields.eventstore,
|
||||||
|
idGenerator: tt.fields.idGenerator,
|
||||||
|
idpConfigEncryption: tt.fields.secretCrypto,
|
||||||
|
}
|
||||||
|
id, got, err := c.AddInstanceAppleProvider(tt.args.ctx, tt.args.provider)
|
||||||
|
if tt.res.err == nil {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
if tt.res.err != nil && !tt.res.err(err) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
|
if tt.res.err == nil {
|
||||||
|
assert.Equal(t, tt.res.id, id)
|
||||||
|
assert.Equal(t, tt.res.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandSide_UpdateInstanceAppleIDP(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
eventstore *eventstore.Eventstore
|
||||||
|
secretCrypto crypto.EncryptionAlgorithm
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
id string
|
||||||
|
provider AppleProvider
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
want *domain.ObjectDetails
|
||||||
|
err func(error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"invalid id",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||||
|
provider: AppleProvider{},
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: func(err error) bool {
|
||||||
|
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-FRHBH", "Errors.IDMissing"))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid clientID",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||||
|
id: "id1",
|
||||||
|
provider: AppleProvider{},
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: func(err error) bool {
|
||||||
|
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-SFm4l", "Errors.IDP.ClientIDMissing"))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid teamID",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||||
|
id: "id1",
|
||||||
|
provider: AppleProvider{
|
||||||
|
ClientID: "clientID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: func(err error) bool {
|
||||||
|
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-SG34t", "Errors.IDP.TeamIDMissing"))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid keyID",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||||
|
id: "id1",
|
||||||
|
provider: AppleProvider{
|
||||||
|
ClientID: "clientID",
|
||||||
|
TeamID: "teamID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: func(err error) bool {
|
||||||
|
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-Gh4z2", "Errors.IDP.KeyIDMissing"))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not found",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||||
|
id: "id1",
|
||||||
|
provider: AppleProvider{
|
||||||
|
ClientID: "clientID",
|
||||||
|
TeamID: "teamID",
|
||||||
|
KeyID: "keyID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
err: caos_errors.IsNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no changes",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
instance.NewAppleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||||
|
"id1",
|
||||||
|
"",
|
||||||
|
"clientID",
|
||||||
|
"teamID",
|
||||||
|
"keyID",
|
||||||
|
&crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("privateKey"),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
idp.Options{},
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||||
|
id: "id1",
|
||||||
|
provider: AppleProvider{
|
||||||
|
ClientID: "clientID",
|
||||||
|
TeamID: "teamID",
|
||||||
|
KeyID: "keyID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change ok",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
instance.NewAppleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||||
|
"id1",
|
||||||
|
"",
|
||||||
|
"clientID",
|
||||||
|
"teamID",
|
||||||
|
"keyID",
|
||||||
|
&crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("privateKey"),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
idp.Options{},
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
[]*repository.Event{
|
||||||
|
eventFromEventPusherWithInstanceID(
|
||||||
|
"instance1",
|
||||||
|
func() eventstore.Command {
|
||||||
|
t := true
|
||||||
|
event, _ := instance.NewAppleIDPChangedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||||
|
"id1",
|
||||||
|
[]idp.AppleIDPChanges{
|
||||||
|
idp.ChangeAppleClientID("clientID2"),
|
||||||
|
idp.ChangeAppleTeamID("teamID2"),
|
||||||
|
idp.ChangeAppleKeyID("keyID2"),
|
||||||
|
idp.ChangeApplePrivateKey(&crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("newPrivateKey"),
|
||||||
|
}),
|
||||||
|
idp.ChangeAppleScopes([]string{"name", "email"}),
|
||||||
|
idp.ChangeAppleOptions(idp.OptionChanges{
|
||||||
|
IsCreationAllowed: &t,
|
||||||
|
IsLinkingAllowed: &t,
|
||||||
|
IsAutoCreation: &t,
|
||||||
|
IsAutoUpdate: &t,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return event
|
||||||
|
}(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||||
|
id: "id1",
|
||||||
|
provider: AppleProvider{
|
||||||
|
ClientID: "clientID2",
|
||||||
|
TeamID: "teamID2",
|
||||||
|
KeyID: "keyID2",
|
||||||
|
PrivateKey: []byte("newPrivateKey"),
|
||||||
|
Scopes: []string{"name", "email"},
|
||||||
|
IDPOptions: idp.Options{
|
||||||
|
IsCreationAllowed: true,
|
||||||
|
IsLinkingAllowed: true,
|
||||||
|
IsAutoCreation: true,
|
||||||
|
IsAutoUpdate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Commands{
|
||||||
|
eventstore: tt.fields.eventstore,
|
||||||
|
idpConfigEncryption: tt.fields.secretCrypto,
|
||||||
|
}
|
||||||
|
got, err := c.UpdateInstanceAppleProvider(tt.args.ctx, tt.args.id, tt.args.provider)
|
||||||
|
if tt.res.err == nil {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
if tt.res.err != nil && !tt.res.err(err) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
|
if tt.res.err == nil {
|
||||||
|
assert.Equal(t, tt.res.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -12,13 +12,14 @@ import (
|
|||||||
type InstanceSMTPConfigWriteModel struct {
|
type InstanceSMTPConfigWriteModel struct {
|
||||||
eventstore.WriteModel
|
eventstore.WriteModel
|
||||||
|
|
||||||
SenderAddress string
|
SenderAddress string
|
||||||
SenderName string
|
SenderName string
|
||||||
TLS bool
|
ReplyToAddress string
|
||||||
Host string
|
TLS bool
|
||||||
User string
|
Host string
|
||||||
Password *crypto.CryptoValue
|
User string
|
||||||
State domain.SMTPConfigState
|
Password *crypto.CryptoValue
|
||||||
|
State domain.SMTPConfigState
|
||||||
|
|
||||||
domain string
|
domain string
|
||||||
domainState domain.InstanceDomainState
|
domainState domain.InstanceDomainState
|
||||||
@ -62,6 +63,7 @@ func (wm *InstanceSMTPConfigWriteModel) Reduce() error {
|
|||||||
wm.TLS = e.TLS
|
wm.TLS = e.TLS
|
||||||
wm.SenderAddress = e.SenderAddress
|
wm.SenderAddress = e.SenderAddress
|
||||||
wm.SenderName = e.SenderName
|
wm.SenderName = e.SenderName
|
||||||
|
wm.ReplyToAddress = e.ReplyToAddress
|
||||||
wm.Host = e.Host
|
wm.Host = e.Host
|
||||||
wm.User = e.User
|
wm.User = e.User
|
||||||
wm.Password = e.Password
|
wm.Password = e.Password
|
||||||
@ -76,6 +78,9 @@ func (wm *InstanceSMTPConfigWriteModel) Reduce() error {
|
|||||||
if e.FromName != nil {
|
if e.FromName != nil {
|
||||||
wm.SenderName = *e.FromName
|
wm.SenderName = *e.FromName
|
||||||
}
|
}
|
||||||
|
if e.ReplyToAddress != nil {
|
||||||
|
wm.ReplyToAddress = *e.ReplyToAddress
|
||||||
|
}
|
||||||
if e.Host != nil {
|
if e.Host != nil {
|
||||||
wm.Host = *e.Host
|
wm.Host = *e.Host
|
||||||
}
|
}
|
||||||
@ -87,6 +92,7 @@ func (wm *InstanceSMTPConfigWriteModel) Reduce() error {
|
|||||||
wm.TLS = false
|
wm.TLS = false
|
||||||
wm.SenderName = ""
|
wm.SenderName = ""
|
||||||
wm.SenderAddress = ""
|
wm.SenderAddress = ""
|
||||||
|
wm.ReplyToAddress = ""
|
||||||
wm.Host = ""
|
wm.Host = ""
|
||||||
wm.User = ""
|
wm.User = ""
|
||||||
wm.Password = nil
|
wm.Password = nil
|
||||||
@ -122,7 +128,7 @@ func (wm *InstanceSMTPConfigWriteModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
Builder()
|
Builder()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wm *InstanceSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, tls bool, fromAddress, fromName, smtpHost, smtpUser string) (*instance.SMTPConfigChangedEvent, bool, error) {
|
func (wm *InstanceSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, tls bool, fromAddress, fromName, replyToAddress, smtpHost, smtpUser string) (*instance.SMTPConfigChangedEvent, bool, error) {
|
||||||
changes := make([]instance.SMTPConfigChanges, 0)
|
changes := make([]instance.SMTPConfigChanges, 0)
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@ -135,6 +141,9 @@ func (wm *InstanceSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, agg
|
|||||||
if wm.SenderName != fromName {
|
if wm.SenderName != fromName {
|
||||||
changes = append(changes, instance.ChangeSMTPConfigFromName(fromName))
|
changes = append(changes, instance.ChangeSMTPConfigFromName(fromName))
|
||||||
}
|
}
|
||||||
|
if wm.ReplyToAddress != replyToAddress {
|
||||||
|
changes = append(changes, instance.ChangeSMTPConfigReplyToAddress(replyToAddress))
|
||||||
|
}
|
||||||
if wm.Host != smtpHost {
|
if wm.Host != smtpHost {
|
||||||
changes = append(changes, instance.ChangeSMTPConfigSMTPHost(smtpHost))
|
changes = append(changes, instance.ChangeSMTPConfigSMTPHost(smtpHost))
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user