mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
feat: native app links for passkeys (#12580)
# Which Problems Are Solved - Native iOS/Android passkeys need OS trust files on the ZITADEL domain; without them, Associated Domains / App Links verification fails. - Operators could not configure iOS Team ID + Bundle ID or Android package name + SHA-256 fingerprints on OIDC apps. - `/.well-known/apple-app-site-association` and `/.well-known/assetlinks.json` were not served from application config. # How the Problems Are Solved - Add iOS/Android app-link fields on OIDC app create/update (Application API v2 + Management), with validation. - Persist and project those fields; query active app-link configs instance-wide. - Serve AASA (`webcredentials`) and Digital Asset Links (`get_login_creds`) from well-known paths, with configurable `Cache-Control` and fingerprint normalization at serve time. - Console UI to edit the fields, with links to the well-known endpoints. - Operator docs for configuration, endpoints, caching, and verification. # Additional Changes - Document on API fields that well-known responses may be HTTP-cached and platform verifiers may delay propagation. - Runtime config: `WellKnown.AppLinksCacheControlMaxAge` (default `5m`; `0` → `no-store`). # Additional Context - Closes #12497 - Implemented and reviewed as stack: - #12531 API contract - #12532 storage wiring - #12536 well-known endpoints - #12537 console - #12547 docs --------- Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -15,6 +15,13 @@ This would give us `${CUSTOM_DOMAIN}/.well-known/openid-configuration`.
|
|||||||
|
|
||||||
**Link to spec.** [OpenID Connect Discovery 1.0 incorporating errata set 1](https://openid.net/specs/openid-connect-discovery-1_0.html)
|
**Link to spec.** [OpenID Connect Discovery 1.0 incorporating errata set 1](https://openid.net/specs/openid-connect-discovery-1_0.html)
|
||||||
|
|
||||||
|
ZITADEL also serves instance-scoped association files for native passkeys (not part of OIDC discovery):
|
||||||
|
|
||||||
|
- `${CUSTOM_DOMAIN}/.well-known/apple-app-site-association`
|
||||||
|
- `${CUSTOM_DOMAIN}/.well-known/assetlinks.json`
|
||||||
|
|
||||||
|
See [Native app links for passkeys](/guides/integrate/login/oidc/native-app-links).
|
||||||
|
|
||||||
## authorization_endpoint
|
## authorization_endpoint
|
||||||
|
|
||||||
`${CUSTOM_DOMAIN}/oauth/v2/authorize`
|
`${CUSTOM_DOMAIN}/oauth/v2/authorize`
|
||||||
|
|||||||
@@ -35,5 +35,6 @@ Imagine signing in without passwords! Passkeys, replacing traditional passwords,
|
|||||||
* Passkey support is still evolving in browsers and platforms. Check compatibility for your target audience.
|
* Passkey support is still evolving in browsers and platforms. Check compatibility for your target audience.
|
||||||
* ZITADEL actively develops its passkey features. Stay updated with documentation and releases.
|
* ZITADEL actively develops its passkey features. Stay updated with documentation and releases.
|
||||||
* Passkeys are bound to your domain, thus we recommend configuring a [Custom Domain](/concepts/features/custom-domain) before setting up passkeys.
|
* Passkeys are bound to your domain, thus we recommend configuring a [Custom Domain](/concepts/features/custom-domain) before setting up passkeys.
|
||||||
|
* Native iOS and Android apps that use platform passkey APIs also need domain association files. Configure them on the OIDC app and let ZITADEL serve the well-known documents — see [Native app links for passkeys](/guides/integrate/login/oidc/native-app-links).
|
||||||
|
|
||||||
Don't hesitate to ask if you have further questions about integrating passkeys in your ZITADEL application!
|
Don't hesitate to ask if you have further questions about integrating passkeys in your ZITADEL application!
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ Passkeys are a replacement for passwords that provide faster, easier, and more s
|
|||||||
Unlike passwords, passkeys are phishing-resistant and can improve the user experience and security at the same time.
|
Unlike passwords, passkeys are phishing-resistant and can improve the user experience and security at the same time.
|
||||||
Passkeys and there underlying protocols are a standard defined by the [FIDO Standard](https://fidoalliance.org/) .
|
Passkeys and there underlying protocols are a standard defined by the [FIDO Standard](https://fidoalliance.org/) .
|
||||||
|
|
||||||
|
<Callout type="info">
|
||||||
|
This guide covers passkeys in a **custom browser login UI**.
|
||||||
|
Native iOS/Android apps that use platform passkey APIs also need domain association files served from your issuer — see [Native app links for passkeys](/guides/integrate/login/oidc/native-app-links).
|
||||||
|
</Callout>
|
||||||
|
|
||||||
## Register Passkey
|
## Register Passkey
|
||||||
|
|
||||||
### Flow
|
### Flow
|
||||||
|
|||||||
@@ -21,5 +21,6 @@ import { FileText, Folder, Link as LinkIcon } from 'lucide-react';
|
|||||||
<Card title="Grant Types" href="/apis/openidoauth/grant-types" icon={<FileText />} />
|
<Card title="Grant Types" href="/apis/openidoauth/grant-types" icon={<FileText />} />
|
||||||
<Card title="Authrequest" href="/apis/openidoauth/authrequest" icon={<FileText />} />
|
<Card title="Authrequest" href="/apis/openidoauth/authrequest" icon={<FileText />} />
|
||||||
<Card title="Webkeys" href="/guides/integrate/login/oidc/webkeys" icon={<FileText />} />
|
<Card title="Webkeys" href="/guides/integrate/login/oidc/webkeys" icon={<FileText />} />
|
||||||
|
<Card title="Native App Links" href="/guides/integrate/login/oidc/native-app-links" icon={<FileText />} />
|
||||||
<Card title="Token Exchange" href="/guides/integrate/token-exchange" icon={<FileText />} />
|
<Card title="Token Exchange" href="/guides/integrate/token-exchange" icon={<FileText />} />
|
||||||
</Cards>
|
</Cards>
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
---
|
||||||
|
title: Native app links for passkeys
|
||||||
|
description: "Configure iOS and Android association on OIDC apps so ZITADEL can serve apple-app-site-association and assetlinks.json for native passkeys"
|
||||||
|
sidebar_label: Native App Links
|
||||||
|
---
|
||||||
|
|
||||||
|
Native mobile apps that create or use passkeys with platform WebAuthn APIs need the issuer domain to publish OS trust association files.
|
||||||
|
ZITADEL serves those files from your instance based on iOS and Android settings on OIDC applications.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- An OIDC **Native** application in your project
|
||||||
|
- Passkeys enabled in your login policy
|
||||||
|
- A stable issuer / [custom domain](/concepts/features/custom-domain) (passkeys are domain-bound)
|
||||||
|
- Platform association configured in the mobile app project (Associated Domains on iOS; Digital Asset Links / App Links on Android)
|
||||||
|
|
||||||
|
## Configure the OIDC application
|
||||||
|
|
||||||
|
Set association fields on each Native OIDC app that should participate in passkey trust.
|
||||||
|
|
||||||
|
You can do this in the [Console](/guides/manage/console/applications-overview#native-app-links) under the app's **Native App Links** section, or via the Application API / Management API OIDC app create and update endpoints (`ios` / `android`).
|
||||||
|
|
||||||
|
### iOS
|
||||||
|
|
||||||
|
| Field | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| **Team ID** | Apple Developer Team ID (exactly 10 alphanumeric characters), for example `ABCDE12345` |
|
||||||
|
| **Bundle ID** | App bundle identifier (`CFBundleIdentifier`), for example `com.example.app` — do not include the Team ID prefix |
|
||||||
|
|
||||||
|
ZITADEL publishes these as a single App ID in the association file: `{team_id}.{bundle_id}`.
|
||||||
|
|
||||||
|
### Android
|
||||||
|
|
||||||
|
| Field | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| **Package name** | Android `applicationId` / package name from the app manifest |
|
||||||
|
| **SHA-256 certificate fingerprints** | Signing certificate fingerprints (64 hex characters, optionally colon-separated). Include debug and release fingerprints as needed. |
|
||||||
|
|
||||||
|
### Update semantics
|
||||||
|
|
||||||
|
On update:
|
||||||
|
|
||||||
|
- Omit a field group to leave it unchanged
|
||||||
|
- Send an empty message / empty values to clear
|
||||||
|
- Send populated values to replace
|
||||||
|
|
||||||
|
## Well-known endpoints
|
||||||
|
|
||||||
|
ZITADEL aggregates association data from **active** OIDC apps on the instance and serves:
|
||||||
|
|
||||||
|
| Path | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `${CUSTOM_DOMAIN}/.well-known/apple-app-site-association` | iOS Associated Domains / `webcredentials` |
|
||||||
|
| `${CUSTOM_DOMAIN}/.well-known/assetlinks.json` | Android Digital Asset Links for `delegate_permission/common.get_login_creds` |
|
||||||
|
|
||||||
|
If no apps are configured, both endpoints still return HTTP `200` with empty collections.
|
||||||
|
|
||||||
|
### Example: apple-app-site-association
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"webcredentials": {
|
||||||
|
"apps": [
|
||||||
|
"ABCDE12345.com.example.one",
|
||||||
|
"FGHIJ67890.com.example.two"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Duplicate iOS App IDs across apps are deduplicated.
|
||||||
|
|
||||||
|
### Example: assetlinks.json
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"relation": ["delegate_permission/common.get_login_creds"],
|
||||||
|
"target": {
|
||||||
|
"namespace": "android_app",
|
||||||
|
"package_name": "com.example.one",
|
||||||
|
"sha256_cert_fingerprints": [
|
||||||
|
"AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Fingerprints are normalized to colon-separated uppercase hex when served.
|
||||||
|
|
||||||
|
## Caching
|
||||||
|
|
||||||
|
Successful responses include a `Cache-Control` header so intermediate caches may reuse the documents for a short time.
|
||||||
|
|
||||||
|
By default (and in ZITADEL Cloud) caching is allowed for 5 minutes:
|
||||||
|
|
||||||
|
```
|
||||||
|
Cache-Control: public, max-age=300
|
||||||
|
```
|
||||||
|
|
||||||
|
Self-hosters can change this with the `ZITADEL_WELLKNOWN_APPLINKSCACHECONTROLMAXAGE` environment variable or in the settings YAML:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
WellKnown:
|
||||||
|
AppLinksCacheControlMaxAge: 5m
|
||||||
|
```
|
||||||
|
|
||||||
|
Setting the value to `0` results in `Cache-Control: no-store`.
|
||||||
|
|
||||||
|
<Callout type="info">
|
||||||
|
Platform verifiers may cache association files on their side as well.
|
||||||
|
After you change app link settings, allow time for HTTP caches and OS verification to refresh before expecting passkeys to work with the new values.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
## Verify
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -i "${CUSTOM_DOMAIN}/.well-known/apple-app-site-association"
|
||||||
|
curl -i "${CUSTOM_DOMAIN}/.well-known/assetlinks.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
Confirm:
|
||||||
|
|
||||||
|
- HTTP status `200`
|
||||||
|
- `Content-Type: application/json`
|
||||||
|
- Expected `Cache-Control` header
|
||||||
|
- JSON contents match your configured apps
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [Passkeys concept](/concepts/features/passkeys)
|
||||||
|
- [Console applications](/guides/manage/console/applications-overview)
|
||||||
|
- [Custom domain](/concepts/features/custom-domain)
|
||||||
|
- [Passkeys in a custom login UI](/guides/integrate/login-ui/passkey) (browser / WebAuthn APIs)
|
||||||
@@ -63,6 +63,15 @@ Native Applications are installed on a thin client, such as a smartphone or desk
|
|||||||
|
|
||||||
<AuthType components={props.components} appType="native" />
|
<AuthType components={props.components} appType="native" />
|
||||||
|
|
||||||
|
#### Native app links
|
||||||
|
|
||||||
|
For native passkeys, configure OS association on the OIDC app under **Native App Links**:
|
||||||
|
|
||||||
|
* **iOS:** Team ID and Bundle ID (published in `/.well-known/apple-app-site-association`)
|
||||||
|
* **Android:** Package name and SHA-256 certificate fingerprints (published in `/.well-known/assetlinks.json`)
|
||||||
|
|
||||||
|
See [Native app links for passkeys](/guides/integrate/login/oidc/native-app-links) for endpoint details, caching, and verification.
|
||||||
|
|
||||||
### User Agent
|
### User Agent
|
||||||
|
|
||||||
User Agent Applications are executed entirely in the web browser (Client-Side).
|
User Agent Applications are executed entirely in the web browser (Client-Side).
|
||||||
@@ -219,3 +228,4 @@ Ensure the management of application settings is limited to authorized users onl
|
|||||||
## References
|
## References
|
||||||
|
|
||||||
- [Recommended OIDC Flows](../../integrate/login/oidc/oauth-recommended-flows)
|
- [Recommended OIDC Flows](../../integrate/login/oidc/oauth-recommended-flows)
|
||||||
|
- [Native app links for passkeys](../../integrate/login/oidc/native-app-links)
|
||||||
|
|||||||
@@ -269,6 +269,7 @@ export const guidesSidebar: readonly SidebarItem[] = [
|
|||||||
"apis/openidoauth/grant-types",
|
"apis/openidoauth/grant-types",
|
||||||
"apis/openidoauth/authrequest",
|
"apis/openidoauth/authrequest",
|
||||||
"guides/integrate/login/oidc/webkeys",
|
"guides/integrate/login/oidc/webkeys",
|
||||||
|
"guides/integrate/login/oidc/native-app-links",
|
||||||
"guides/integrate/token-exchange",
|
"guides/integrate/token-exchange",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -701,6 +701,11 @@ OIDC:
|
|||||||
# Lifetime of the token used to notify clients through OIDC back-channel logout.
|
# Lifetime of the token used to notify clients through OIDC back-channel logout.
|
||||||
TokenLifetime: 15m # ZITADEL_OIDC_BACKCHANNELLOGOUT_TOKENLIFETIME
|
TokenLifetime: 15m # ZITADEL_OIDC_BACKCHANNELLOGOUT_TOKENLIFETIME
|
||||||
|
|
||||||
|
WellKnown:
|
||||||
|
# HTTP Cache-Control max-age for /.well-known/apple-app-site-association and
|
||||||
|
# /.well-known/assetlinks.json. 0 sets Cache-Control: no-store.
|
||||||
|
AppLinksCacheControlMaxAge: 5m # ZITADEL_WELLKNOWN_APPLINKSCACHECONTROLMAXAGE
|
||||||
|
|
||||||
SAML:
|
SAML:
|
||||||
DefaultLoginURLV2: "/ui/v2/login/login?samlRequest=" # ZITADEL_SAML_DEFAULTLOGINURLV2
|
DefaultLoginURLV2: "/ui/v2/login/login?samlRequest=" # ZITADEL_SAML_DEFAULTLOGINURLV2
|
||||||
ProviderConfig:
|
ProviderConfig:
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/database"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed 75.sql
|
||||||
|
addOIDCAppLinkConfig string
|
||||||
|
)
|
||||||
|
|
||||||
|
type Apps7OIDCConfigsAddAppLinkConfig struct {
|
||||||
|
dbClient *database.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mig *Apps7OIDCConfigsAddAppLinkConfig) Execute(ctx context.Context, _ eventstore.Event) error {
|
||||||
|
_, err := mig.dbClient.ExecContext(ctx, addOIDCAppLinkConfig)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mig *Apps7OIDCConfigsAddAppLinkConfig) String() string {
|
||||||
|
return "75_apps7_oidc_configs_add_app_link_config"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE IF EXISTS projections.apps7_oidc_configs ADD COLUMN IF NOT EXISTS ios_team_id TEXT;
|
||||||
|
ALTER TABLE IF EXISTS projections.apps7_oidc_configs ADD COLUMN IF NOT EXISTS ios_bundle_id TEXT;
|
||||||
|
ALTER TABLE IF EXISTS projections.apps7_oidc_configs ADD COLUMN IF NOT EXISTS android_package_name TEXT;
|
||||||
|
ALTER TABLE IF EXISTS projections.apps7_oidc_configs ADD COLUMN IF NOT EXISTS android_sha256_cert_fingerprints TEXT[];
|
||||||
@@ -194,6 +194,7 @@ type Steps struct {
|
|||||||
s72AddColumnsToLoginNamesView *AddColumnsToLoginNamesView
|
s72AddColumnsToLoginNamesView *AddColumnsToLoginNamesView
|
||||||
s73FixUserGrantRoles *FixUserGrantRoles
|
s73FixUserGrantRoles *FixUserGrantRoles
|
||||||
s74Apps7OIDCConfigsAddRegistrationToken *Apps7OIDCConfigsAddRegistrationToken
|
s74Apps7OIDCConfigsAddRegistrationToken *Apps7OIDCConfigsAddRegistrationToken
|
||||||
|
s75Apps7OIDCConfigsAddAppLinkConfig *Apps7OIDCConfigsAddAppLinkConfig
|
||||||
RelationalTables *TransactionalTables
|
RelationalTables *TransactionalTables
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -257,6 +257,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
|
|||||||
steps.s72AddColumnsToLoginNamesView = &AddColumnsToLoginNamesView{dbClient: dbClient}
|
steps.s72AddColumnsToLoginNamesView = &AddColumnsToLoginNamesView{dbClient: dbClient}
|
||||||
steps.s73FixUserGrantRoles = &FixUserGrantRoles{eventstore: eventstoreClient}
|
steps.s73FixUserGrantRoles = &FixUserGrantRoles{eventstore: eventstoreClient}
|
||||||
steps.s74Apps7OIDCConfigsAddRegistrationToken = &Apps7OIDCConfigsAddRegistrationToken{dbClient: dbClient}
|
steps.s74Apps7OIDCConfigsAddRegistrationToken = &Apps7OIDCConfigsAddRegistrationToken{dbClient: dbClient}
|
||||||
|
steps.s75Apps7OIDCConfigsAddAppLinkConfig = &Apps7OIDCConfigsAddAppLinkConfig{dbClient: dbClient}
|
||||||
|
|
||||||
err = projection.Create(ctx, dbClient, eventstoreClient, config.Projections, nil, nil, nil)
|
err = projection.Create(ctx, dbClient, eventstoreClient, config.Projections, nil, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -390,6 +391,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
|
|||||||
steps.s72AddColumnsToLoginNamesView,
|
steps.s72AddColumnsToLoginNamesView,
|
||||||
steps.s73FixUserGrantRoles,
|
steps.s73FixUserGrantRoles,
|
||||||
steps.s74Apps7OIDCConfigsAddRegistrationToken,
|
steps.s74Apps7OIDCConfigsAddRegistrationToken,
|
||||||
|
steps.s75Apps7OIDCConfigsAddAppLinkConfig,
|
||||||
} {
|
} {
|
||||||
setupErr = executeMigration(ctx, eventstoreClient, step, "migration failed")
|
setupErr = executeMigration(ctx, eventstoreClient, step, "migration failed")
|
||||||
if setupErr != nil {
|
if setupErr != nil {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
scim_config "github.com/zitadel/zitadel/internal/api/scim/config"
|
scim_config "github.com/zitadel/zitadel/internal/api/scim/config"
|
||||||
"github.com/zitadel/zitadel/internal/api/ui/console"
|
"github.com/zitadel/zitadel/internal/api/ui/console"
|
||||||
"github.com/zitadel/zitadel/internal/api/ui/login"
|
"github.com/zitadel/zitadel/internal/api/ui/login"
|
||||||
|
"github.com/zitadel/zitadel/internal/api/well_known"
|
||||||
auth_es "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing"
|
auth_es "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing"
|
||||||
"github.com/zitadel/zitadel/internal/cache/connector"
|
"github.com/zitadel/zitadel/internal/cache/connector"
|
||||||
"github.com/zitadel/zitadel/internal/command"
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
@@ -72,6 +73,7 @@ type Config struct {
|
|||||||
SCIM scim_config.Config
|
SCIM scim_config.Config
|
||||||
Login login.Config
|
Login login.Config
|
||||||
Console console.Config
|
Console console.Config
|
||||||
|
WellKnown well_known.Config
|
||||||
AssetStorage static_config.AssetStorageConfig
|
AssetStorage static_config.AssetStorageConfig
|
||||||
InternalAuthZ authz.Config
|
InternalAuthZ authz.Config
|
||||||
SystemAuthZ authz.Config
|
SystemAuthZ authz.Config
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/api/ui/console"
|
"github.com/zitadel/zitadel/internal/api/ui/console"
|
||||||
"github.com/zitadel/zitadel/internal/api/ui/console/path"
|
"github.com/zitadel/zitadel/internal/api/ui/console/path"
|
||||||
"github.com/zitadel/zitadel/internal/api/ui/login"
|
"github.com/zitadel/zitadel/internal/api/ui/login"
|
||||||
|
"github.com/zitadel/zitadel/internal/api/well_known"
|
||||||
auth_es "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing"
|
auth_es "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing"
|
||||||
"github.com/zitadel/zitadel/internal/authz"
|
"github.com/zitadel/zitadel/internal/authz"
|
||||||
authz_repo "github.com/zitadel/zitadel/internal/authz/repository"
|
authz_repo "github.com/zitadel/zitadel/internal/authz/repository"
|
||||||
@@ -657,6 +658,9 @@ func startAPIs(
|
|||||||
}
|
}
|
||||||
apis.RegisterHandlerOnPrefix(robots_txt.HandlerPrefix, robotsTxtHandler)
|
apis.RegisterHandlerOnPrefix(robots_txt.HandlerPrefix, robotsTxtHandler)
|
||||||
|
|
||||||
|
// native app link well-known files (instance-scoped)
|
||||||
|
apis.RegisterHandlerPrefixes(instanceInterceptor.Handler(well_known.NewHandler(queries, config.WellKnown)), well_known.HandlerPrefixes...)
|
||||||
|
|
||||||
// TODO: Record openapi access logs?
|
// TODO: Record openapi access logs?
|
||||||
openAPIHandler, err := openapi.Start()
|
openAPIHandler, err := openapi.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -462,6 +462,102 @@
|
|||||||
</div>
|
</div>
|
||||||
</cnsl-card>
|
</cnsl-card>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (currentSetting.id === 'native-app-links' && oidcForm && app?.oidcConfig) {
|
||||||
|
<cnsl-card title="{{ 'APP.OIDC.APPLINKSSECTION' | translate }}">
|
||||||
|
<p class="app-desc cnsl-secondary-text">{{ 'APP.OIDC.APPLINKSDESC' | translate }}</p>
|
||||||
|
<form [formGroup]="oidcForm" (ngSubmit)="saveOIDCApp()">
|
||||||
|
<h3 class="title">{{ 'APP.OIDC.IOSTITLE' | translate }}</h3>
|
||||||
|
<cnsl-info-section class="full-width app-desc">
|
||||||
|
<span>{{ 'APP.OIDC.IOSDESC' | translate }}</span>
|
||||||
|
@if (issuer$ | async; as issuer) {
|
||||||
|
<div class="link app-link-endpoint">
|
||||||
|
<a [href]="issuer + '/.well-known/apple-app-site-association'" rel="noreferrer" target="_blank"
|
||||||
|
>{{ issuer }}/.well-known/apple-app-site-association</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</cnsl-info-section>
|
||||||
|
<div class="content">
|
||||||
|
<cnsl-form-field class="app-formfield">
|
||||||
|
<cnsl-label>{{ 'APP.OIDC.TEAMID' | translate }}</cnsl-label>
|
||||||
|
<input
|
||||||
|
cnslInput
|
||||||
|
formControlName="iosTeamId"
|
||||||
|
maxlength="10"
|
||||||
|
autocomplete="off"
|
||||||
|
[placeholder]="'APP.OIDC.TEAMIDPLACEHOLDER' | translate"
|
||||||
|
/>
|
||||||
|
</cnsl-form-field>
|
||||||
|
<cnsl-form-field class="app-formfield">
|
||||||
|
<cnsl-label>{{ 'APP.OIDC.BUNDLEID' | translate }}</cnsl-label>
|
||||||
|
<input
|
||||||
|
cnslInput
|
||||||
|
formControlName="iosBundleId"
|
||||||
|
autocomplete="off"
|
||||||
|
[placeholder]="'APP.OIDC.BUNDLEIDPLACEHOLDER' | translate"
|
||||||
|
/>
|
||||||
|
</cnsl-form-field>
|
||||||
|
</div>
|
||||||
|
<cnsl-info-section class="full-width app-desc">
|
||||||
|
<span>{{ 'APP.OIDC.TEAMIDDESC' | translate }} {{ 'APP.OIDC.BUNDLEIDDESC' | translate }}</span>
|
||||||
|
</cnsl-info-section>
|
||||||
|
@if (showIosAppLinkIncompleteWarning()) {
|
||||||
|
<cnsl-info-section class="full-width app-desc" [type]="InfoSectionType.WARN">
|
||||||
|
<span>{{ 'APP.OIDC.IOSAPPLINKINCOMPLETE' | translate }}</span>
|
||||||
|
</cnsl-info-section>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h3 class="title" style="margin-top: 1.5rem">{{ 'APP.OIDC.ANDROIDTITLE' | translate }}</h3>
|
||||||
|
<cnsl-info-section class="full-width app-desc">
|
||||||
|
<span>{{ 'APP.OIDC.ANDROIDDESC' | translate }}</span>
|
||||||
|
@if (issuer$ | async; as issuer) {
|
||||||
|
<div class="link app-link-endpoint">
|
||||||
|
<a [href]="issuer + '/.well-known/assetlinks.json'" rel="noreferrer" target="_blank"
|
||||||
|
>{{ issuer }}/.well-known/assetlinks.json</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</cnsl-info-section>
|
||||||
|
<div class="content">
|
||||||
|
<cnsl-form-field class="app-formfield full-width">
|
||||||
|
<cnsl-label>{{ 'APP.OIDC.PACKAGENAME' | translate }}</cnsl-label>
|
||||||
|
<input
|
||||||
|
cnslInput
|
||||||
|
formControlName="androidPackageName"
|
||||||
|
autocomplete="off"
|
||||||
|
[placeholder]="'APP.OIDC.PACKAGENAMEPLACEHOLDER' | translate"
|
||||||
|
/>
|
||||||
|
</cnsl-form-field>
|
||||||
|
<cnsl-form-field class="app-formfield full-width fingerprints-field">
|
||||||
|
<cnsl-label>{{ 'APP.OIDC.FINGERPRINTS' | translate }}</cnsl-label>
|
||||||
|
<textarea
|
||||||
|
cnslInput
|
||||||
|
class="fingerprints-input"
|
||||||
|
formControlName="androidSha256CertFingerprints"
|
||||||
|
rows="10"
|
||||||
|
[placeholder]="'APP.OIDC.FINGERPRINTSPLACEHOLDER' | translate"
|
||||||
|
></textarea>
|
||||||
|
</cnsl-form-field>
|
||||||
|
</div>
|
||||||
|
<cnsl-info-section class="full-width app-desc">
|
||||||
|
<span>{{ 'APP.OIDC.FINGERPRINTSDESC' | translate }}</span>
|
||||||
|
</cnsl-info-section>
|
||||||
|
@if (showAndroidPackageRequiredWarning()) {
|
||||||
|
<cnsl-info-section class="full-width app-desc" [type]="InfoSectionType.WARN">
|
||||||
|
<span>{{ 'APP.OIDC.ANDROIDPACKAGEREQUIRED' | translate }}</span>
|
||||||
|
</cnsl-info-section>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="btn-container">
|
||||||
|
<button class="submit-button" type="submit" color="primary" [disabled]="!canWrite" mat-raised-button>
|
||||||
|
{{ 'ACTIONS.SAVE' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</cnsl-card>
|
||||||
|
}
|
||||||
|
|
||||||
@if (currentSetting.id === 'urls') {
|
@if (currentSetting.id === 'urls') {
|
||||||
<cnsl-card title=" {{ 'APP.URLS' | translate }}">
|
<cnsl-card title=" {{ 'APP.URLS' | translate }}">
|
||||||
@if (issuer$ | async; as issuer) {
|
@if (issuer$ | async; as issuer) {
|
||||||
|
|||||||
@@ -53,6 +53,11 @@
|
|||||||
.app-desc {
|
.app-desc {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
|
.app-link-endpoint {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-input {
|
.app-input {
|
||||||
@@ -281,6 +286,17 @@
|
|||||||
input {
|
input {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea.fingerprints-input,
|
||||||
|
.fingerprints-input {
|
||||||
|
max-width: none;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 12rem;
|
||||||
|
resize: vertical;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
|
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { Component, OnDestroy, OnInit, signal } from '@angular/core';
|
import { Component, OnDestroy, OnInit, signal } from '@angular/core';
|
||||||
import { AbstractControl, FormControl, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
|
import {
|
||||||
|
AbstractControl,
|
||||||
|
FormControl,
|
||||||
|
UntypedFormBuilder,
|
||||||
|
UntypedFormControl,
|
||||||
|
UntypedFormGroup,
|
||||||
|
ValidationErrors,
|
||||||
|
ValidatorFn,
|
||||||
|
Validators,
|
||||||
|
} from '@angular/forms';
|
||||||
import { MatCheckboxChange } from '@angular/material/checkbox';
|
import { MatCheckboxChange } from '@angular/material/checkbox';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
@@ -206,18 +215,25 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
private breadcrumbService: BreadcrumbService,
|
private breadcrumbService: BreadcrumbService,
|
||||||
) {
|
) {
|
||||||
this.oidcForm = this.fb.group({
|
this.oidcForm = this.fb.group(
|
||||||
devMode: [{ value: false, disabled: true }],
|
{
|
||||||
skipNativeAppSuccessPage: [{ value: false, disabled: true }],
|
devMode: [{ value: false, disabled: true }],
|
||||||
clientId: [{ value: '', disabled: true }],
|
skipNativeAppSuccessPage: [{ value: false, disabled: true }],
|
||||||
responseTypesList: [{ value: [], disabled: true }],
|
clientId: [{ value: '', disabled: true }],
|
||||||
grantTypesList: [{ value: [], disabled: true }],
|
responseTypesList: [{ value: [], disabled: true }],
|
||||||
appType: [{ value: '', disabled: true }],
|
grantTypesList: [{ value: [], disabled: true }],
|
||||||
authMethodType: [{ value: '', disabled: true }],
|
appType: [{ value: '', disabled: true }],
|
||||||
loginV2: [{ value: false, disabled: true }],
|
authMethodType: [{ value: '', disabled: true }],
|
||||||
loginV2BaseURL: [{ value: '', disabled: true }],
|
loginV2: [{ value: false, disabled: true }],
|
||||||
backChannelLogoutURI: [{ value: '', disabled: true }],
|
loginV2BaseURL: [{ value: '', disabled: true }],
|
||||||
});
|
backChannelLogoutURI: [{ value: '', disabled: true }],
|
||||||
|
iosTeamId: [{ value: '', disabled: true }, [iosTeamIdValidator]],
|
||||||
|
iosBundleId: [{ value: '', disabled: true }, [Validators.maxLength(200)]],
|
||||||
|
androidPackageName: [{ value: '', disabled: true }, [Validators.maxLength(200)]],
|
||||||
|
androidSha256CertFingerprints: [{ value: '', disabled: true }],
|
||||||
|
},
|
||||||
|
{ validators: [appLinkConfigValidator()] },
|
||||||
|
);
|
||||||
|
|
||||||
this.oidcTokenForm = this.fb.group({
|
this.oidcTokenForm = this.fb.group({
|
||||||
accessTokenType: [{ value: '', disabled: true }],
|
accessTokenType: [{ value: '', disabled: true }],
|
||||||
@@ -375,6 +391,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
this.settingsList = [
|
this.settingsList = [
|
||||||
{ id: 'configuration', i18nKey: 'APP.CONFIGURATION' },
|
{ id: 'configuration', i18nKey: 'APP.CONFIGURATION' },
|
||||||
{ id: 'token', i18nKey: 'APP.TOKEN' },
|
{ id: 'token', i18nKey: 'APP.TOKEN' },
|
||||||
|
{ id: 'native-app-links', i18nKey: 'APP.OIDC.APPLINKSSECTION' },
|
||||||
{ id: 'urls', i18nKey: 'APP.URLS' },
|
{ id: 'urls', i18nKey: 'APP.URLS' },
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
@@ -383,6 +400,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
{ id: 'token', i18nKey: 'APP.TOKEN' },
|
{ id: 'token', i18nKey: 'APP.TOKEN' },
|
||||||
{ id: 'redirect-uris', i18nKey: 'APP.OIDC.REDIRECTSECTIONTITLE' },
|
{ id: 'redirect-uris', i18nKey: 'APP.OIDC.REDIRECTSECTIONTITLE' },
|
||||||
{ id: 'additional-origins', i18nKey: 'APP.ADDITIONALORIGINS' },
|
{ id: 'additional-origins', i18nKey: 'APP.ADDITIONALORIGINS' },
|
||||||
|
{ id: 'native-app-links', i18nKey: 'APP.OIDC.APPLINKSSECTION' },
|
||||||
{ id: 'urls', i18nKey: 'APP.URLS' },
|
{ id: 'urls', i18nKey: 'APP.URLS' },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -465,6 +483,12 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
if (this.app.oidcConfig) {
|
if (this.app.oidcConfig) {
|
||||||
this.oidcForm.patchValue(this.app.oidcConfig);
|
this.oidcForm.patchValue(this.app.oidcConfig);
|
||||||
this.oidcTokenForm.patchValue(this.app.oidcConfig);
|
this.oidcTokenForm.patchValue(this.app.oidcConfig);
|
||||||
|
this.oidcForm.controls['iosTeamId'].setValue(this.app.oidcConfig.ios?.teamId ?? '');
|
||||||
|
this.oidcForm.controls['iosBundleId'].setValue(this.app.oidcConfig.ios?.bundleId ?? '');
|
||||||
|
this.oidcForm.controls['androidPackageName'].setValue(this.app.oidcConfig.android?.packageName ?? '');
|
||||||
|
this.oidcForm.controls['androidSha256CertFingerprints'].setValue(
|
||||||
|
(this.app.oidcConfig.android?.sha256CertFingerprintsList ?? []).join('\n'),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (this.app.apiConfig) {
|
if (this.app.apiConfig) {
|
||||||
this.apiForm.patchValue(this.app.apiConfig);
|
this.apiForm.patchValue(this.app.apiConfig);
|
||||||
@@ -657,96 +681,106 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
public saveOIDCApp(): void {
|
public saveOIDCApp(): void {
|
||||||
this.requestRedirectValuesSubject$.next();
|
this.requestRedirectValuesSubject$.next();
|
||||||
if (this.oidcForm.valid) {
|
if (!this.oidcForm.valid) {
|
||||||
if (this.app?.oidcConfig) {
|
this.markNativeAppLinkFieldsTouched();
|
||||||
// configuration
|
return;
|
||||||
this.app.oidcConfig.responseTypesList = this.responseTypesList?.value;
|
}
|
||||||
this.app.oidcConfig.grantTypesList = this.grantTypesList?.value;
|
if (this.app?.oidcConfig) {
|
||||||
this.app.oidcConfig.appType = this.appType?.value;
|
// configuration
|
||||||
this.app.oidcConfig.authMethodType = this.authMethodType?.value;
|
this.app.oidcConfig.responseTypesList = this.responseTypesList?.value;
|
||||||
this.app.oidcConfig.backChannelLogoutUri = this.backChannelLogoutURI?.value;
|
this.app.oidcConfig.grantTypesList = this.grantTypesList?.value;
|
||||||
|
this.app.oidcConfig.appType = this.appType?.value;
|
||||||
|
this.app.oidcConfig.authMethodType = this.authMethodType?.value;
|
||||||
|
this.app.oidcConfig.backChannelLogoutUri = this.backChannelLogoutURI?.value;
|
||||||
|
|
||||||
// token
|
// token
|
||||||
this.app.oidcConfig.accessTokenType = this.accessTokenType?.value;
|
this.app.oidcConfig.accessTokenType = this.accessTokenType?.value;
|
||||||
this.app.oidcConfig.accessTokenRoleAssertion = this.accessTokenRoleAssertion?.value;
|
this.app.oidcConfig.accessTokenRoleAssertion = this.accessTokenRoleAssertion?.value;
|
||||||
this.app.oidcConfig.idTokenRoleAssertion = this.idTokenRoleAssertion?.value;
|
this.app.oidcConfig.idTokenRoleAssertion = this.idTokenRoleAssertion?.value;
|
||||||
this.app.oidcConfig.idTokenUserinfoAssertion = this.idTokenUserinfoAssertion?.value;
|
this.app.oidcConfig.idTokenUserinfoAssertion = this.idTokenUserinfoAssertion?.value;
|
||||||
|
|
||||||
// redirects
|
// redirects
|
||||||
this.app.oidcConfig.redirectUrisList = this.redirectUrisList;
|
this.app.oidcConfig.redirectUrisList = this.redirectUrisList;
|
||||||
this.app.oidcConfig.postLogoutRedirectUrisList = this.postLogoutRedirectUrisList;
|
this.app.oidcConfig.postLogoutRedirectUrisList = this.postLogoutRedirectUrisList;
|
||||||
this.app.oidcConfig.additionalOriginsList = this.additionalOriginsList;
|
this.app.oidcConfig.additionalOriginsList = this.additionalOriginsList;
|
||||||
this.app.oidcConfig.devMode = !!this.devMode?.value;
|
this.app.oidcConfig.devMode = !!this.devMode?.value;
|
||||||
this.app.oidcConfig.skipNativeAppSuccessPage = !!this.skipNativeAppSuccessPage?.value;
|
this.app.oidcConfig.skipNativeAppSuccessPage = !!this.skipNativeAppSuccessPage?.value;
|
||||||
|
|
||||||
const loginVersion: MessageInitShape<typeof LoginVersionSchema> = this.oidcLoginV2?.value
|
const loginVersion: MessageInitShape<typeof LoginVersionSchema> = this.oidcLoginV2?.value
|
||||||
? {
|
? {
|
||||||
version: {
|
version: {
|
||||||
case: 'loginV2',
|
case: 'loginV2',
|
||||||
value: { baseUri: this.oidcLoginV2BaseURL?.value },
|
value: { baseUri: this.oidcLoginV2BaseURL?.value },
|
||||||
},
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
version: {
|
|
||||||
case: 'loginV1',
|
|
||||||
value: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const req: MessageInitShape<typeof UpdateApplicationRequestSchema> = {
|
|
||||||
projectId: this.projectId,
|
|
||||||
applicationId: this.app.id,
|
|
||||||
applicationType: {
|
|
||||||
case: 'oidcConfiguration',
|
|
||||||
value: {
|
|
||||||
// configuration
|
|
||||||
responseTypes: this.app.oidcConfig.responseTypesList.map((type) => this.toOIDCV2ResponseType(type)),
|
|
||||||
authMethodType: this.app.oidcConfig.authMethodType as unknown as OIDCV2AuthMethodType,
|
|
||||||
grantTypes: this.app.oidcConfig.grantTypesList as unknown as OIDCV2GrantType[],
|
|
||||||
applicationType: this.app.oidcConfig.appType as unknown as OIDCV2ApplicationType,
|
|
||||||
backChannelLogoutUri: this.app.oidcConfig.backChannelLogoutUri,
|
|
||||||
loginVersion,
|
|
||||||
|
|
||||||
// token
|
|
||||||
accessTokenType: this.app.oidcConfig.accessTokenType as unknown as OIDCV2TokenType,
|
|
||||||
accessTokenRoleAssertion: this.app.oidcConfig.accessTokenRoleAssertion,
|
|
||||||
idTokenRoleAssertion: this.app.oidcConfig.idTokenRoleAssertion,
|
|
||||||
idTokenUserinfoAssertion: this.app.oidcConfig.idTokenUserinfoAssertion,
|
|
||||||
|
|
||||||
// redirects
|
|
||||||
redirectUris: this.normalizeOIDCListForUpdate(this.app.oidcConfig.redirectUrisList),
|
|
||||||
additionalOrigins: this.app.oidcConfig.additionalOriginsList,
|
|
||||||
postLogoutRedirectUris: this.normalizeOIDCListForUpdate(this.app.oidcConfig.postLogoutRedirectUrisList),
|
|
||||||
developmentMode: this.app.oidcConfig.devMode,
|
|
||||||
skipNativeAppSuccessPage: this.app.oidcConfig.skipNativeAppSuccessPage,
|
|
||||||
...(this.clockSkewSeconds?.value
|
|
||||||
? {
|
|
||||||
clockSkew: {
|
|
||||||
seconds: BigInt(Math.floor(this.clockSkewSeconds?.value)),
|
|
||||||
nanos: Math.floor(this.clockSkewSeconds?.value % 1) * 10000,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
};
|
: {
|
||||||
|
version: {
|
||||||
|
case: 'loginV1',
|
||||||
|
value: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
this.applicationService
|
const req: MessageInitShape<typeof UpdateApplicationRequestSchema> = {
|
||||||
.updateApplication(req)
|
projectId: this.projectId,
|
||||||
.then(() => {
|
applicationId: this.app.id,
|
||||||
if (this.app?.oidcConfig) {
|
applicationType: {
|
||||||
const config = { oidc: this.app.oidcConfig };
|
case: 'oidcConfiguration',
|
||||||
this.currentAuthMethod = this.authMethodFromPartialConfig(config);
|
value: {
|
||||||
}
|
// configuration
|
||||||
this.toast.showInfo('APP.TOAST.OIDCUPDATED', true);
|
responseTypes: this.app.oidcConfig.responseTypesList.map((type) => this.toOIDCV2ResponseType(type)),
|
||||||
setTimeout(() => {
|
authMethodType: this.app.oidcConfig.authMethodType as unknown as OIDCV2AuthMethodType,
|
||||||
this.getData(this.projectId, this.appId);
|
grantTypes: this.app.oidcConfig.grantTypesList as unknown as OIDCV2GrantType[],
|
||||||
}, 1000);
|
applicationType: this.app.oidcConfig.appType as unknown as OIDCV2ApplicationType,
|
||||||
})
|
backChannelLogoutUri: this.app.oidcConfig.backChannelLogoutUri,
|
||||||
.catch((error) => {
|
loginVersion,
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
// token
|
||||||
}
|
accessTokenType: this.app.oidcConfig.accessTokenType as unknown as OIDCV2TokenType,
|
||||||
|
accessTokenRoleAssertion: this.app.oidcConfig.accessTokenRoleAssertion,
|
||||||
|
idTokenRoleAssertion: this.app.oidcConfig.idTokenRoleAssertion,
|
||||||
|
idTokenUserinfoAssertion: this.app.oidcConfig.idTokenUserinfoAssertion,
|
||||||
|
|
||||||
|
// redirects
|
||||||
|
redirectUris: this.normalizeOIDCListForUpdate(this.app.oidcConfig.redirectUrisList),
|
||||||
|
additionalOrigins: this.app.oidcConfig.additionalOriginsList,
|
||||||
|
postLogoutRedirectUris: this.normalizeOIDCListForUpdate(this.app.oidcConfig.postLogoutRedirectUrisList),
|
||||||
|
developmentMode: this.app.oidcConfig.devMode,
|
||||||
|
skipNativeAppSuccessPage: this.app.oidcConfig.skipNativeAppSuccessPage,
|
||||||
|
ios: {
|
||||||
|
teamId: (this.iosTeamId?.value ?? '').trim(),
|
||||||
|
bundleId: (this.iosBundleId?.value ?? '').trim(),
|
||||||
|
},
|
||||||
|
android: {
|
||||||
|
packageName: (this.androidPackageName?.value ?? '').trim(),
|
||||||
|
sha256CertFingerprints: this.parseAndroidFingerprints(this.androidSha256CertFingerprints?.value),
|
||||||
|
},
|
||||||
|
...(this.clockSkewSeconds?.value
|
||||||
|
? {
|
||||||
|
clockSkew: {
|
||||||
|
seconds: BigInt(Math.floor(this.clockSkewSeconds?.value)),
|
||||||
|
nanos: Math.floor(this.clockSkewSeconds?.value % 1) * 10000,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.applicationService
|
||||||
|
.updateApplication(req)
|
||||||
|
.then(() => {
|
||||||
|
if (this.app?.oidcConfig) {
|
||||||
|
const config = { oidc: this.app.oidcConfig };
|
||||||
|
this.currentAuthMethod = this.authMethodFromPartialConfig(config);
|
||||||
|
}
|
||||||
|
this.toast.showInfo('APP.TOAST.OIDCUPDATED', true);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.getData(this.projectId, this.appId);
|
||||||
|
}, 1000);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -754,6 +788,16 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
return values.length === 0 ? [''] : values;
|
return values.length === 0 ? [''] : values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private parseAndroidFingerprints(value: unknown): string[] {
|
||||||
|
if (typeof value !== 'string' || !value.trim()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
.split(/[\n,]+/)
|
||||||
|
.map((fp) => fp.trim())
|
||||||
|
.filter((fp) => fp.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
private toOIDCV2ResponseType(type: OIDCResponseType): OIDCV2ResponseType {
|
private toOIDCV2ResponseType(type: OIDCResponseType): OIDCV2ResponseType {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case OIDCResponseType.OIDC_RESPONSE_TYPE_CODE:
|
case OIDCResponseType.OIDC_RESPONSE_TYPE_CODE:
|
||||||
@@ -943,6 +987,49 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
return this.oidcForm.get('skipNativeAppSuccessPage') as FormControl<boolean>;
|
return this.oidcForm.get('skipNativeAppSuccessPage') as FormControl<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get iosTeamId(): AbstractControl | null {
|
||||||
|
return this.oidcForm.get('iosTeamId');
|
||||||
|
}
|
||||||
|
|
||||||
|
public get iosBundleId(): AbstractControl | null {
|
||||||
|
return this.oidcForm.get('iosBundleId');
|
||||||
|
}
|
||||||
|
|
||||||
|
public get androidPackageName(): AbstractControl | null {
|
||||||
|
return this.oidcForm.get('androidPackageName');
|
||||||
|
}
|
||||||
|
|
||||||
|
public get androidSha256CertFingerprints(): AbstractControl | null {
|
||||||
|
return this.oidcForm.get('androidSha256CertFingerprints');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Defer cross-field warnings until the user has visited the paired empty field. */
|
||||||
|
public showIosAppLinkIncompleteWarning(): boolean {
|
||||||
|
if (!this.oidcForm.hasError('iosAppLinkIncomplete')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const teamId = String(this.iosTeamId?.value ?? '').trim();
|
||||||
|
const bundleId = String(this.iosBundleId?.value ?? '').trim();
|
||||||
|
if (teamId !== '' && bundleId === '') {
|
||||||
|
return !!this.iosBundleId?.touched;
|
||||||
|
}
|
||||||
|
if (bundleId !== '' && teamId === '') {
|
||||||
|
return !!this.iosTeamId?.touched;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public showAndroidPackageRequiredWarning(): boolean {
|
||||||
|
return this.oidcForm.hasError('androidAppLinkPackageRequired') && !!this.androidPackageName?.touched;
|
||||||
|
}
|
||||||
|
|
||||||
|
private markNativeAppLinkFieldsTouched(): void {
|
||||||
|
this.iosTeamId?.markAsTouched();
|
||||||
|
this.iosBundleId?.markAsTouched();
|
||||||
|
this.androidPackageName?.markAsTouched();
|
||||||
|
this.androidSha256CertFingerprints?.markAsTouched();
|
||||||
|
}
|
||||||
|
|
||||||
public get accessTokenType(): AbstractControl | null {
|
public get accessTokenType(): AbstractControl | null {
|
||||||
return this.oidcTokenForm.get('accessTokenType');
|
return this.oidcTokenForm.get('accessTokenType');
|
||||||
}
|
}
|
||||||
@@ -1006,3 +1093,49 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function iosTeamIdValidator(c: AbstractControl): ValidationErrors | null {
|
||||||
|
const value = String(c.value ?? '').trim();
|
||||||
|
if (value === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!/^[A-Z0-9]+$/.test(value)) {
|
||||||
|
return {
|
||||||
|
invalid: true,
|
||||||
|
errorsinvalidformat: { valid: false, i18nKey: 'ERRORS.INVALID_FORMAT' },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (value.length !== 10) {
|
||||||
|
return {
|
||||||
|
invalid: true,
|
||||||
|
errorsapoidcteamidlength: { valid: false, i18nKey: 'APP.OIDC.TEAMIDLENGTH' },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Mirrors backend AppLinkConfigValid: iOS fields together; package required when fingerprints are set. */
|
||||||
|
function appLinkConfigValidator(): ValidatorFn {
|
||||||
|
return (group: AbstractControl): ValidationErrors | null => {
|
||||||
|
const teamId = String(group.get('iosTeamId')?.value ?? '').trim();
|
||||||
|
const bundleId = String(group.get('iosBundleId')?.value ?? '').trim();
|
||||||
|
const packageName = String(group.get('androidPackageName')?.value ?? '').trim();
|
||||||
|
const fingerprintsRaw = group.get('androidSha256CertFingerprints')?.value;
|
||||||
|
const fingerprints =
|
||||||
|
typeof fingerprintsRaw === 'string'
|
||||||
|
? fingerprintsRaw
|
||||||
|
.split(/[\n,]+/)
|
||||||
|
.map((fp) => fp.trim())
|
||||||
|
.filter((fp) => fp.length > 0)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const errors: ValidationErrors = {};
|
||||||
|
if ((teamId === '') !== (bundleId === '')) {
|
||||||
|
errors['iosAppLinkIncomplete'] = true;
|
||||||
|
}
|
||||||
|
if (fingerprints.length > 0 && packageName === '') {
|
||||||
|
errors['androidAppLinkPackageRequired'] = true;
|
||||||
|
}
|
||||||
|
return Object.keys(errors).length ? errors : null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -2610,6 +2610,25 @@
|
|||||||
"DEVMODEDESC": "احذر: مع تمكين وضع التطوير، لن يتم التحقق من عناوين URI لإعادة التوجيه.",
|
"DEVMODEDESC": "احذر: مع تمكين وضع التطوير، لن يتم التحقق من عناوين URI لإعادة التوجيه.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "تخطي صفحة نجاح تسجيل الدخول",
|
"SKIPNATIVEAPPSUCCESSPAGE": "تخطي صفحة نجاح تسجيل الدخول",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "تخطي صفحة النجاح بعد تسجيل الدخول لهذا التطبيق الأصلي.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "تخطي صفحة النجاح بعد تسجيل الدخول لهذا التطبيق الأصلي.",
|
||||||
|
"APPLINKSSECTION": "روابط التطبيقات الأصلية",
|
||||||
|
"APPLINKSDESC": "قم بتهيئة ارتباط iOS وAndroid حتى تتمكن هذه المثيلة من تقديم ملفات الثقة بـ Passkey لتطبيقاتك الأصلية.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "تُقدَّم في apple-app-site-association كمدخل webcredentials بالصيغة TEAMID.BUNDLEID.",
|
||||||
|
"TEAMID": "معرّف الفريق",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "يجب أن يكون Apple Team ID مكونًا من 10 أحرف أبجدية رقمية بالضبط، مثل ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "يجب أن يكون Team ID مكونًا من 10 أحرف أبجدية رقمية بالضبط.",
|
||||||
|
"BUNDLEID": "معرّف الحزمة",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "معرّف حزمة iOS (CFBundleIdentifier)، مثل com.example.app. لا تُدرج بادئة Team ID.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "تُقدَّم في assetlinks.json لبيانات اعتماد Passkey عبر Digital Asset Links.",
|
||||||
|
"PACKAGENAME": "اسم الحزمة",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "معرّف تطبيق Android / اسم الحزمة (applicationId)، مثل com.example.app.",
|
||||||
|
"FINGERPRINTS": "بصمات شهادة SHA-256",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "بصمة واحدة لكل سطر (64 حرفًا سادس عشريًا، النقطتان اختياريتان). أدرج إصداري التصحيح والإصدار حسب الحاجة.",
|
||||||
"REDIRECT": "عناوين URI لإعادة التوجيه",
|
"REDIRECT": "عناوين URI لإعادة التوجيه",
|
||||||
"REDIRECTSECTION": "عناوين URI لإعادة التوجيه",
|
"REDIRECTSECTION": "عناوين URI لإعادة التوجيه",
|
||||||
"POSTLOGOUTREDIRECT": "عناوين URI بعد تسجيل الخروج",
|
"POSTLOGOUTREDIRECT": "عناوين URI بعد تسجيل الخروج",
|
||||||
@@ -2673,7 +2692,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "عنوان URI لتسجيل الخروج من القناة الخلفية"
|
"BACKCHANNELLOOGOUTURI": "عنوان URI لتسجيل الخروج من القناة الخلفية",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "يجب تعيين معرّف فريق iOS ومعرّف الحزمة معًا، أو تركهما فارغين معًا.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "اسم حزمة Android مطلوب عند تعيين بصمات شهادة."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2618,6 +2618,25 @@
|
|||||||
"DEVMODEDESC": "Внимание: При активиран режим на разработка URI адресите за пренасочване няма да бъдат валидирани.",
|
"DEVMODEDESC": "Внимание: При активиран режим на разработка URI адресите за пренасочване няма да бъдат валидирани.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Пропуснете страницата за успешно влизане",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Пропуснете страницата за успешно влизане",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Пропуснете страницата за успех след влизане в това родно приложение.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Пропуснете страницата за успех след влизане в това родно приложение.",
|
||||||
|
"APPLINKSSECTION": "Връзки за нативни приложения",
|
||||||
|
"APPLINKSDESC": "Конфигурирайте iOS и Android асоциации, за да може тази инстанция да предоставя trust файлове за passkeys на вашите нативни приложения.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "Предоставя се в apple-app-site-association като webcredentials запис TEAMID.BUNDLEID.",
|
||||||
|
"TEAMID": "Team ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "Apple Team ID трябва да е точно 10 буквено-цифрови знака, напр. ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "Team ID трябва да е точно 10 буквено-цифрови знака.",
|
||||||
|
"BUNDLEID": "Bundle ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "iOS Bundle Identifier (CFBundleIdentifier), напр. com.example.app. Не включвайте префикса Team ID.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Предоставя се в assetlinks.json за Digital Asset Links / passkey credentials.",
|
||||||
|
"PACKAGENAME": "Име на пакета",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "Android applicationId / име на пакет, напр. com.example.app.",
|
||||||
|
"FINGERPRINTS": "SHA-256 сертификатни отпечатъци",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "Един отпечатък на ред (64 шестнадесетични знака, двоеточията са по избор). Добавете debug и release при нужда.",
|
||||||
"REDIRECT": "Пренасочване на URI",
|
"REDIRECT": "Пренасочване на URI",
|
||||||
"REDIRECTSECTION": "Пренасочване на URI",
|
"REDIRECTSECTION": "Пренасочване на URI",
|
||||||
"POSTLOGOUTREDIRECT": "Публикувайте URI адреси за излизане",
|
"POSTLOGOUTREDIRECT": "Публикувайте URI адреси за излизане",
|
||||||
@@ -2681,7 +2700,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI"
|
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "iOS Team ID и Bundle ID трябва да бъдат зададени заедно или и двете да останат празни.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "Името на Android пакета е задължително, когато са зададени сертификатни пръстови отпечатъци."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2600,6 +2600,25 @@
|
|||||||
"DEVMODEDESC": "Pozor: S povoleným vývojovým režimem nebudou URI pro přesměrování ověřovány.",
|
"DEVMODEDESC": "Pozor: S povoleným vývojovým režimem nebudou URI pro přesměrování ověřovány.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Přeskočit stránku úspěchu přihlášení",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Přeskočit stránku úspěchu přihlášení",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Přeskočit stránku úspěchu po přihlášení pro tuto nativní aplikaci.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Přeskočit stránku úspěchu po přihlášení pro tuto nativní aplikaci.",
|
||||||
|
"APPLINKSSECTION": "Odkazy pro nativní aplikace",
|
||||||
|
"APPLINKSDESC": "Nakonfigurujte asociaci iOS a Android, aby tato instance mohla poskytovat trust soubory pro passkeys vašich nativních aplikací.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "Poskytováno v apple-app-site-association jako webcredentials položka TEAMID.BUNDLEID.",
|
||||||
|
"TEAMID": "Team ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "Apple Team ID musí mít přesně 10 alfanumerických znaků, např. ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "Team ID musí mít přesně 10 alfanumerických znaků.",
|
||||||
|
"BUNDLEID": "Bundle ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "iOS Bundle Identifier (CFBundleIdentifier), např. com.example.app. Nezahrnujte prefix Team ID.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Poskytováno v assetlinks.json pro Digital Asset Links / passkey credentials.",
|
||||||
|
"PACKAGENAME": "Název balíčku",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "Android applicationId / název balíčku, např. com.example.app.",
|
||||||
|
"FINGERPRINTS": "SHA-256 otisky certifikátů",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "Jeden otisk na řádek (64 hexadecimálních znaků, dvojtečky volitelně). Podle potřeby uveďte debug i release.",
|
||||||
"REDIRECT": "URI pro přesměrování",
|
"REDIRECT": "URI pro přesměrování",
|
||||||
"REDIRECTSECTION": "URI pro přesměrování",
|
"REDIRECTSECTION": "URI pro přesměrování",
|
||||||
"POSTLOGOUTREDIRECT": "URI po odhlášení",
|
"POSTLOGOUTREDIRECT": "URI po odhlášení",
|
||||||
@@ -2663,7 +2682,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI"
|
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "iOS Team ID a Bundle ID musí být nastaveny společně, nebo obě ponechány prázdné.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "Název balíčku Android je povinný, pokud jsou nastaveny otisky certifikátu."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2591,6 +2591,25 @@
|
|||||||
"DEVMODEDESC": "Bei eingeschaltetem Entwicklermodus werden die Weiterleitungs-URIs im OIDC-Flow nicht validiert.",
|
"DEVMODEDESC": "Bei eingeschaltetem Entwicklermodus werden die Weiterleitungs-URIs im OIDC-Flow nicht validiert.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Login Erfolgseite überspringen",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Login Erfolgseite überspringen",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Erfolgseite nach dem Login für diese Native Applikation überspringen.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Erfolgseite nach dem Login für diese Native Applikation überspringen.",
|
||||||
|
"APPLINKSSECTION": "Native App Links",
|
||||||
|
"APPLINKSDESC": "Konfigurieren Sie iOS- und Android-Zuordnungen, damit diese Instanz Passkey-Trust-Dateien für Ihre nativen Apps bereitstellen kann.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "Wird in apple-app-site-association als webcredentials-Eintrag TEAMID.BUNDLEID ausgeliefert.",
|
||||||
|
"TEAMID": "Team-ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "Apple Team-ID muss genau 10 alphanumerische Zeichen haben, z. B. ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "Team-ID muss genau 10 alphanumerische Zeichen haben.",
|
||||||
|
"BUNDLEID": "Bundle-ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "iOS Bundle Identifier (CFBundleIdentifier), z. B. com.example.app. Ohne Team-ID-Präfix.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Wird in assetlinks.json für Digital Asset Links / Passkey-Credentials ausgeliefert.",
|
||||||
|
"PACKAGENAME": "Package Name",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "Android applicationId / Package Name, z. B. com.example.app.",
|
||||||
|
"FINGERPRINTS": "SHA-256 Zertifikatsfingerabdrücke",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "Ein Fingerabdruck pro Zeile (64 Hex-Zeichen, Doppelpunkte optional). Debug und Release bei Bedarf angeben.",
|
||||||
"REDIRECT": "Weiterleitungs-URIs",
|
"REDIRECT": "Weiterleitungs-URIs",
|
||||||
"REDIRECTSECTION": "Weiterleitungs-URIs",
|
"REDIRECTSECTION": "Weiterleitungs-URIs",
|
||||||
"POSTLOGOUTREDIRECT": "URIs für Post-Log-out",
|
"POSTLOGOUTREDIRECT": "URIs für Post-Log-out",
|
||||||
@@ -2654,7 +2673,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI"
|
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "iOS Team ID und Bundle ID müssen beide gesetzt oder beide leer sein.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "Android-Paketname ist erforderlich, wenn Zertifikat-Fingerprints gesetzt sind."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2622,6 +2622,27 @@
|
|||||||
"DEVMODEDESC": "Beware: With development mode enabled redirect URIs will not be validated.",
|
"DEVMODEDESC": "Beware: With development mode enabled redirect URIs will not be validated.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Skip Login Success Page",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Skip Login Success Page",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Skip the success page after a login for this native app.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Skip the success page after a login for this native app.",
|
||||||
|
"APPLINKSSECTION": "Native App Links",
|
||||||
|
"APPLINKSDESC": "Configure iOS and Android association so this instance can serve passkey trust files for your native apps.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "Served in apple-app-site-association as a webcredentials entry TEAMID.BUNDLEID.",
|
||||||
|
"TEAMID": "Team ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "Apple Team ID must be exactly 10 alphanumeric characters, e.g. ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "Team ID must be exactly 10 alphanumeric characters.",
|
||||||
|
"BUNDLEID": "Bundle ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "iOS Bundle Identifier (CFBundleIdentifier), e.g. com.example.app. Do not include the Team ID prefix.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Served in assetlinks.json for Digital Asset Links / passkey credentials.",
|
||||||
|
"PACKAGENAME": "Package Name",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "Android applicationId / package name, e.g. com.example.app.",
|
||||||
|
"FINGERPRINTS": "SHA-256 Certificate Fingerprints",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "One fingerprint per line (64 hex characters, optional colons). Include debug and release as needed.",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "iOS Team ID and Bundle ID must both be set, or both left empty.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "Android package name is required when certificate fingerprints are set.",
|
||||||
"REDIRECT": "Redirect URIs",
|
"REDIRECT": "Redirect URIs",
|
||||||
"REDIRECTSECTION": "Redirect URIs",
|
"REDIRECTSECTION": "Redirect URIs",
|
||||||
"POSTLOGOUTREDIRECT": "Post Logout URIs",
|
"POSTLOGOUTREDIRECT": "Post Logout URIs",
|
||||||
|
|||||||
@@ -2588,6 +2588,25 @@
|
|||||||
"DEVMODEDESC": "Cuidado: Si el modo de desarrollo está activado las URIs de redirección no serán validadas.",
|
"DEVMODEDESC": "Cuidado: Si el modo de desarrollo está activado las URIs de redirección no serán validadas.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Saltar página de inicio de sesión con éxito",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Saltar página de inicio de sesión con éxito",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Sáltate la página de éxito después de iniciar sesión en esta app nativa.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Sáltate la página de éxito después de iniciar sesión en esta app nativa.",
|
||||||
|
"APPLINKSSECTION": "Enlaces de aplicaciones nativas",
|
||||||
|
"APPLINKSDESC": "Configure la asociación de iOS y Android para que esta instancia pueda servir archivos de confianza de passkeys para sus aplicaciones nativas.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "Se sirve en apple-app-site-association como entrada webcredentials TEAMID.BUNDLEID.",
|
||||||
|
"TEAMID": "Team ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "El Apple Team ID debe tener exactamente 10 caracteres alfanuméricos, p. ej. ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "El Team ID debe tener exactamente 10 caracteres alfanuméricos.",
|
||||||
|
"BUNDLEID": "Bundle ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "Identificador de paquete de iOS (CFBundleIdentifier), p. ej. com.example.app. No incluya el prefijo del Team ID.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Se sirve en assetlinks.json para Digital Asset Links / credenciales de passkey.",
|
||||||
|
"PACKAGENAME": "Nombre del paquete",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "applicationId / nombre del paquete de Android, p. ej. com.example.app.",
|
||||||
|
"FINGERPRINTS": "Huellas SHA-256 del certificado",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "Una huella por línea (64 caracteres hexadecimales, los dos puntos son opcionales). Incluya debug y release según sea necesario.",
|
||||||
"REDIRECT": "URIs de redirección",
|
"REDIRECT": "URIs de redirección",
|
||||||
"REDIRECTSECTION": "URIs de redirección",
|
"REDIRECTSECTION": "URIs de redirección",
|
||||||
"POSTLOGOUTREDIRECT": "URIs a usar tras cerrar sesión",
|
"POSTLOGOUTREDIRECT": "URIs a usar tras cerrar sesión",
|
||||||
@@ -2651,7 +2670,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI"
|
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "El Team ID y el Bundle ID de iOS deben estar ambos configurados, o ambos vacíos.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "El nombre del paquete de Android es obligatorio cuando se definen huellas de certificado."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2592,6 +2592,25 @@
|
|||||||
"DEVMODEDESC": "Attention",
|
"DEVMODEDESC": "Attention",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Sauter la page de succès de connexion",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Sauter la page de succès de connexion",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Sauter la page de succès après la connexion pour cette application native",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Sauter la page de succès après la connexion pour cette application native",
|
||||||
|
"APPLINKSSECTION": "Liens d'applications natives",
|
||||||
|
"APPLINKSDESC": "Configurez l'association iOS et Android afin que cette instance puisse servir les fichiers de confiance passkey pour vos applications natives.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "Servi dans apple-app-site-association comme entrée webcredentials TEAMID.BUNDLEID.",
|
||||||
|
"TEAMID": "Team ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "L'Apple Team ID doit comporter exactement 10 caractères alphanumériques, p. ex. ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "L'ID d'équipe doit comporter exactement 10 caractères alphanumériques.",
|
||||||
|
"BUNDLEID": "Bundle ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "Identifiant de bundle iOS (CFBundleIdentifier), p. ex. com.example.app. N'incluez pas le préfixe Team ID.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Servi dans assetlinks.json pour Digital Asset Links / identifiants passkey.",
|
||||||
|
"PACKAGENAME": "Nom du package",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "applicationId / nom de package Android, p. ex. com.example.app.",
|
||||||
|
"FINGERPRINTS": "Empreintes de certificat SHA-256",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "Une empreinte par ligne (64 caractères hexadécimaux, deux-points optionnels). Incluez debug et release si nécessaire.",
|
||||||
"REDIRECT": "Rediriger les URIs",
|
"REDIRECT": "Rediriger les URIs",
|
||||||
"REDIRECTSECTION": "URIs de redirection",
|
"REDIRECTSECTION": "URIs de redirection",
|
||||||
"POSTLOGOUTREDIRECT": "URIs de post-déconnexion",
|
"POSTLOGOUTREDIRECT": "URIs de post-déconnexion",
|
||||||
@@ -2655,7 +2674,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI"
|
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "L'ID d'équipe iOS et le Bundle ID doivent tous deux être renseignés, ou tous deux laissés vides.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "Le nom de package Android est requis lorsque des empreintes de certificat sont définies."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2620,6 +2620,25 @@
|
|||||||
"DEVMODEDESC": "Figyelem: Ha a fejlesztői mód be van kapcsolva, a redirect URI-k nem lesznek érvényesítve.",
|
"DEVMODEDESC": "Figyelem: Ha a fejlesztői mód be van kapcsolva, a redirect URI-k nem lesznek érvényesítve.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Login sikeroldal kihagyása",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Login sikeroldal kihagyása",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Hagyd ki a sikeroldalt a bejelentkezés után ennél a helyi alkalmazásnál.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Hagyd ki a sikeroldalt a bejelentkezés után ennél a helyi alkalmazásnál.",
|
||||||
|
"APPLINKSSECTION": "Natív alkalmazáshivatkozások",
|
||||||
|
"APPLINKSDESC": "Állítsa be az iOS és Android társítást, hogy ez a példány passkey trust fájlokat szolgálhasson ki a natív alkalmazásaihoz.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "Az apple-app-site-association fájlban webcredentials bejegyzésként jelenik meg: TEAMID.BUNDLEID.",
|
||||||
|
"TEAMID": "Team ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "Az Apple Team ID pontosan 10 alfanumerikus karakterből álljon, pl. ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "A Team ID pontosan 10 alfanumerikus karakterből álljon.",
|
||||||
|
"BUNDLEID": "Bundle ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "iOS Bundle Identifier (CFBundleIdentifier), pl. com.example.app. Ne tartalmazza a Team ID előtagot.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Az assetlinks.json fájlban Digital Asset Links / passkey hitelesítő adatokhoz szolgál.",
|
||||||
|
"PACKAGENAME": "Csomagnév",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "Android applicationId / csomagnév, pl. com.example.app.",
|
||||||
|
"FINGERPRINTS": "SHA-256 tanúsítvány ujjlenyomatok",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "Soronként egy ujjlenyomat (64 hexadecimális karakter, a kettőspontok opcionálisak). Szükség szerint adja meg a debug és release értékeket.",
|
||||||
"REDIRECT": "Redirect URIs",
|
"REDIRECT": "Redirect URIs",
|
||||||
"REDIRECTSECTION": "Redirect URIs",
|
"REDIRECTSECTION": "Redirect URIs",
|
||||||
"POSTLOGOUTREDIRECT": "Kijelentkezés utáni URIs",
|
"POSTLOGOUTREDIRECT": "Kijelentkezés utáni URIs",
|
||||||
@@ -2683,7 +2702,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI"
|
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "Az iOS Team ID-t és Bundle ID-t együtt kell megadni, vagy mindkettőt üresen hagyni.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "Android csomagnév kötelező, ha tanúsítvány ujjlenyomatok vannak megadva."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2614,6 +2614,25 @@
|
|||||||
"DEVMODEDESC": "Hati-hati: Dengan mengaktifkan mode pengembangan, URI pengalihan tidak akan divalidasi.",
|
"DEVMODEDESC": "Hati-hati: Dengan mengaktifkan mode pengembangan, URI pengalihan tidak akan divalidasi.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Lewati Halaman Sukses Login",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Lewati Halaman Sukses Login",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Lewati halaman sukses setelah login untuk aplikasi asli ini.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Lewati halaman sukses setelah login untuk aplikasi asli ini.",
|
||||||
|
"APPLINKSSECTION": "Tautan Aplikasi Native",
|
||||||
|
"APPLINKSDESC": "Konfigurasikan asosiasi iOS dan Android agar instance ini dapat menyajikan file kepercayaan passkey untuk aplikasi native Anda.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "Disajikan di apple-app-site-association sebagai entri webcredentials TEAMID.BUNDLEID.",
|
||||||
|
"TEAMID": "Team ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "Apple Team ID harus tepat 10 karakter alfanumerik, mis. ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "Team ID harus tepat 10 karakter alfanumerik.",
|
||||||
|
"BUNDLEID": "Bundle ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "iOS Bundle Identifier (CFBundleIdentifier), mis. com.example.app. Jangan sertakan awalan Team ID.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Disajikan di assetlinks.json untuk Digital Asset Links / kredensial passkey.",
|
||||||
|
"PACKAGENAME": "Nama Paket",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "Android applicationId / nama paket, mis. com.example.app.",
|
||||||
|
"FINGERPRINTS": "Sidik Jari Sertifikat SHA-256",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "Satu sidik jari per baris (64 karakter heksadesimal, titik dua opsional). Sertakan debug dan release sesuai kebutuhan.",
|
||||||
"REDIRECT": "Arahkan ulang URI",
|
"REDIRECT": "Arahkan ulang URI",
|
||||||
"REDIRECTSECTION": "Arahkan ulang URI",
|
"REDIRECTSECTION": "Arahkan ulang URI",
|
||||||
"POSTLOGOUTREDIRECT": "URI Posting Logout",
|
"POSTLOGOUTREDIRECT": "URI Posting Logout",
|
||||||
@@ -2677,7 +2696,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI"
|
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "Team ID dan Bundle ID iOS harus keduanya diisi, atau keduanya dikosongkan.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "Nama paket Android wajib diisi bila sidik jari sertifikat diatur."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2592,6 +2592,25 @@
|
|||||||
"DEVMODEDESC": "Attenzione: Con la modalità di sviluppo abilitata, gli URI di reindirizzamento non saranno convalidati.",
|
"DEVMODEDESC": "Attenzione: Con la modalità di sviluppo abilitata, gli URI di reindirizzamento non saranno convalidati.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Salta la pagina di successo dopo il login",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Salta la pagina di successo dopo il login",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Salta la pagina di successo dopo il login per questa applicazione nativa",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Salta la pagina di successo dopo il login per questa applicazione nativa",
|
||||||
|
"APPLINKSSECTION": "Collegamenti app native",
|
||||||
|
"APPLINKSDESC": "Configura l'associazione iOS e Android in modo che questa istanza possa fornire i file di trust passkey per le tue app native.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "Fornito in apple-app-site-association come voce webcredentials TEAMID.BUNDLEID.",
|
||||||
|
"TEAMID": "Team ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "L'Apple Team ID deve contenere esattamente 10 caratteri alfanumerici, ad es. ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "Il Team ID deve contenere esattamente 10 caratteri alfanumerici.",
|
||||||
|
"BUNDLEID": "Bundle ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "Bundle Identifier iOS (CFBundleIdentifier), ad es. com.example.app. Non includere il prefisso Team ID.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Fornito in assetlinks.json per Digital Asset Links / credenziali passkey.",
|
||||||
|
"PACKAGENAME": "Nome del package",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "applicationId / nome del package Android, ad es. com.example.app.",
|
||||||
|
"FINGERPRINTS": "Impronte del certificato SHA-256",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "Un'impronta per riga (64 caratteri esadecimali, i due punti sono opzionali). Includere debug e release se necessario.",
|
||||||
"REDIRECT": "URI per il reindrizzamento",
|
"REDIRECT": "URI per il reindrizzamento",
|
||||||
"REDIRECTSECTION": "Reindirizzamento",
|
"REDIRECTSECTION": "Reindirizzamento",
|
||||||
"POSTLOGOUTREDIRECT": "URI post logout",
|
"POSTLOGOUTREDIRECT": "URI post logout",
|
||||||
@@ -2655,7 +2674,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI"
|
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "Team ID e Bundle ID iOS devono essere entrambi impostati oppure entrambi vuoti.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "Il nome del package Android è obbligatorio quando sono impostate le impronte del certificato."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2617,6 +2617,25 @@
|
|||||||
"DEVMODEDESC": "注意:開発モードを有効にすると、URIが認証されません。",
|
"DEVMODEDESC": "注意:開発モードを有効にすると、URIが認証されません。",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "ログイン後に成功ページをスキップする",
|
"SKIPNATIVEAPPSUCCESSPAGE": "ログイン後に成功ページをスキップする",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "このネイティブアプリのログイン後に成功ページをスキップする",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "このネイティブアプリのログイン後に成功ページをスキップする",
|
||||||
|
"APPLINKSSECTION": "ネイティブアプリリンク",
|
||||||
|
"APPLINKSDESC": "このインスタンスがネイティブアプリ向けのパスキー信頼ファイルを提供できるよう、iOS / Android の関連付けを設定します。",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "apple-app-site-association の webcredentials エントリ TEAMID.BUNDLEID として提供されます。",
|
||||||
|
"TEAMID": "チーム ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "Apple Team ID は英数字ちょうど 10 文字である必要があります(例: ABCDE12345)。",
|
||||||
|
"TEAMIDLENGTH": "Team ID は英数字ちょうど 10 文字である必要があります。",
|
||||||
|
"BUNDLEID": "バンドル ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "iOS の Bundle Identifier(CFBundleIdentifier)、例: com.example.app。Team ID プレフィックスは含めないでください。",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Digital Asset Links / パスキー認証情報用に assetlinks.json で提供されます。",
|
||||||
|
"PACKAGENAME": "パッケージ名",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "Android の applicationId / パッケージ名、例: com.example.app。",
|
||||||
|
"FINGERPRINTS": "SHA-256 証明書フィンガープリント",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "1 行に 1 つ(16 進 64 文字、コロンは任意)。必要に応じてデバッグ用とリリース用を含めてください。",
|
||||||
"REDIRECT": "リダイレクトURI",
|
"REDIRECT": "リダイレクトURI",
|
||||||
"REDIRECTSECTION": "リダイレクトURI",
|
"REDIRECTSECTION": "リダイレクトURI",
|
||||||
"POSTLOGOUTREDIRECT": "ログアウトURI",
|
"POSTLOGOUTREDIRECT": "ログアウトURI",
|
||||||
@@ -2680,7 +2699,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "バックチャネルログアウトURI"
|
"BACKCHANNELLOOGOUTURI": "バックチャネルログアウトURI",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "iOS の Team ID と Bundle ID は両方設定するか、両方とも空にしてください。",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "証明書フィンガープリントを設定する場合、Android パッケージ名は必須です。"
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2616,6 +2616,25 @@
|
|||||||
"DEVMODEDESC": "주의: 개발 모드가 활성화된 경우 리디렉션 URI가 검증되지 않습니다.",
|
"DEVMODEDESC": "주의: 개발 모드가 활성화된 경우 리디렉션 URI가 검증되지 않습니다.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "로그인 성공 페이지 건너뛰기",
|
"SKIPNATIVEAPPSUCCESSPAGE": "로그인 성공 페이지 건너뛰기",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "네이티브 앱 로그인 후 성공 페이지를 건너뜁니다.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "네이티브 앱 로그인 후 성공 페이지를 건너뜁니다.",
|
||||||
|
"APPLINKSSECTION": "네이티브 앱 링크",
|
||||||
|
"APPLINKSDESC": "이 인스턴스가 네이티브 앱용 패스키 신뢰 파일을 제공할 수 있도록 iOS 및 Android 연결을 구성합니다.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "apple-app-site-association의 webcredentials 항목 TEAMID.BUNDLEID로 제공됩니다.",
|
||||||
|
"TEAMID": "팀 ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "Apple Team ID는 영숫자 정확히 10자여야 합니다(예: ABCDE12345).",
|
||||||
|
"TEAMIDLENGTH": "Team ID는 영숫자 정확히 10자여야 합니다.",
|
||||||
|
"BUNDLEID": "번들 ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "iOS Bundle Identifier(CFBundleIdentifier), 예: com.example.app. Team ID 접두사는 포함하지 마세요.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Digital Asset Links / 패스키 자격 증명을 위해 assetlinks.json에서 제공됩니다.",
|
||||||
|
"PACKAGENAME": "패키지 이름",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "Android applicationId / 패키지 이름, 예: com.example.app.",
|
||||||
|
"FINGERPRINTS": "SHA-256 인증서 지문",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "줄당 지문 하나(16진수 64자, 콜론 선택 사항). 필요에 따라 디버그 및 릴리스를 포함하세요.",
|
||||||
"REDIRECT": "리디렉션 URI",
|
"REDIRECT": "리디렉션 URI",
|
||||||
"REDIRECTSECTION": "리디렉션 URI",
|
"REDIRECTSECTION": "리디렉션 URI",
|
||||||
"POSTLOGOUTREDIRECT": "로그아웃 후 리디렉션 URI",
|
"POSTLOGOUTREDIRECT": "로그아웃 후 리디렉션 URI",
|
||||||
@@ -2679,7 +2698,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "백채널 로그아웃 URI"
|
"BACKCHANNELLOOGOUTURI": "백채널 로그아웃 URI",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "iOS Team ID와 Bundle ID는 둘 다 설정하거나 둘 다 비워 두어야 합니다.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "인증서 지문이 설정된 경우 Android 패키지 이름이 필요합니다."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2588,6 +2588,25 @@
|
|||||||
"DEVMODEDESC": "Внимавајте: Со овозможен Development mode, URIs за пренасочување нема да бидат валидирани.",
|
"DEVMODEDESC": "Внимавајте: Со овозможен Development mode, URIs за пренасочување нема да бидат валидирани.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Прескокни страница за успешна најава",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Прескокни страница за успешна најава",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Прескокнете ја страницата за успешна најава за оваа нативна апликација.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Прескокнете ја страницата за успешна најава за оваа нативна апликација.",
|
||||||
|
"APPLINKSSECTION": "Врски за нативни апликации",
|
||||||
|
"APPLINKSDESC": "Конфигурирајте асоцијација за iOS и Android за оваа инстанца да може да доставува trust датотеки за passkeys на вашите нативни апликации.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "Се доставува во apple-app-site-association како webcredentials запис TEAMID.BUNDLEID.",
|
||||||
|
"TEAMID": "Team ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "Apple Team ID мора да има точно 10 алфанумерички знаци, нпр. ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "Team ID мора да има точно 10 алфанумерички знаци.",
|
||||||
|
"BUNDLEID": "Bundle ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "iOS Bundle Identifier (CFBundleIdentifier), нпр. com.example.app. Не го вклучувајте префиксот Team ID.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Се доставува во assetlinks.json за Digital Asset Links / passkey credentials.",
|
||||||
|
"PACKAGENAME": "Име на пакет",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "Android applicationId / име на пакет, нпр. com.example.app.",
|
||||||
|
"FINGERPRINTS": "SHA-256 отпечатоци на сертификат",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "Еден отпечаток по ред (64 хексадецимални знаци, двоточките се опционални). Вклучете debug и release по потреба.",
|
||||||
"REDIRECT": "Redirect URIs",
|
"REDIRECT": "Redirect URIs",
|
||||||
"REDIRECTSECTION": "Redirect URIs",
|
"REDIRECTSECTION": "Redirect URIs",
|
||||||
"POSTLOGOUTREDIRECT": "URIs по одјавувањето",
|
"POSTLOGOUTREDIRECT": "URIs по одјавувањето",
|
||||||
@@ -2651,7 +2670,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI"
|
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "iOS Team ID и Bundle ID мора да се постават заедно, или обете да останат празни.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "Името на Android пакетот е задолжително кога се поставени отпечатоци од сертификат."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2610,6 +2610,25 @@
|
|||||||
"DEVMODEDESC": "Let op: Met ingeschakelde ontwikkelingsmodus worden redirect URI's niet gevalideerd.",
|
"DEVMODEDESC": "Let op: Met ingeschakelde ontwikkelingsmodus worden redirect URI's niet gevalideerd.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Sla Login Succes Pagina over",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Sla Login Succes Pagina over",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Sla de succespagina na een login voor deze native app over.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Sla de succespagina na een login voor deze native app over.",
|
||||||
|
"APPLINKSSECTION": "Native app-koppelingen",
|
||||||
|
"APPLINKSDESC": "Configureer iOS- en Android-associatie zodat deze instance passkey-trustbestanden voor uw native apps kan serveren.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "Wordt geserveerd in apple-app-site-association als webcredentials-vermelding TEAMID.BUNDLEID.",
|
||||||
|
"TEAMID": "Team-ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "Apple Team ID moet precies 10 alfanumerieke tekens zijn, bijv. ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "Team ID moet precies 10 alfanumerieke tekens zijn.",
|
||||||
|
"BUNDLEID": "Bundle-ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "iOS Bundle Identifier (CFBundleIdentifier), bijv. com.example.app. Neem het Team ID-voorvoegsel niet op.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Wordt geserveerd in assetlinks.json voor Digital Asset Links / passkey-credentials.",
|
||||||
|
"PACKAGENAME": "Pakketnaam",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "Android applicationId / pakketnaam, bijv. com.example.app.",
|
||||||
|
"FINGERPRINTS": "SHA-256-certificaatvingerafdrukken",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "Eén vingerafdruk per regel (64 hexadecimale tekens, dubbele punten optioneel). Voeg desgewenst debug en release toe.",
|
||||||
"REDIRECT": "Redirect URI's",
|
"REDIRECT": "Redirect URI's",
|
||||||
"REDIRECTSECTION": "Redirect URI's",
|
"REDIRECTSECTION": "Redirect URI's",
|
||||||
"POSTLOGOUTREDIRECT": "Post Logout URI's",
|
"POSTLOGOUTREDIRECT": "Post Logout URI's",
|
||||||
@@ -2673,7 +2692,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI"
|
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "iOS Team ID en Bundle ID moeten beide zijn ingesteld, of beide leeg blijven.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "Android-pakketnaam is verplicht wanneer certificaatfingerprints zijn ingesteld."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2591,6 +2591,25 @@
|
|||||||
"DEVMODEDESC": "Uwaga: przy włączonym trybie rozwoju adresy URI przekierowania nie będą sprawdzane.",
|
"DEVMODEDESC": "Uwaga: przy włączonym trybie rozwoju adresy URI przekierowania nie będą sprawdzane.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Pomiń stronę sukcesu po zalogowaniu",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Pomiń stronę sukcesu po zalogowaniu",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Pomiń stronę sukcesu po zalogowaniu dla tej Natywny aplikację",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Pomiń stronę sukcesu po zalogowaniu dla tej Natywny aplikację",
|
||||||
|
"APPLINKSSECTION": "Łącza aplikacji natywnych",
|
||||||
|
"APPLINKSDESC": "Skonfiguruj powiązanie iOS i Android, aby ta instancja mogła serwować pliki zaufania passkey dla Twoich aplikacji natywnych.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "Serwowane w apple-app-site-association jako wpis webcredentials TEAMID.BUNDLEID.",
|
||||||
|
"TEAMID": "Team ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "Apple Team ID musi mieć dokładnie 10 znaków alfanumerycznych, np. ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "Team ID musi mieć dokładnie 10 znaków alfanumerycznych.",
|
||||||
|
"BUNDLEID": "Bundle ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "iOS Bundle Identifier (CFBundleIdentifier), np. com.example.app. Nie dołączaj prefiksu Team ID.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Serwowane w assetlinks.json dla Digital Asset Links / poświadczeń passkey.",
|
||||||
|
"PACKAGENAME": "Nazwa pakietu",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "Android applicationId / nazwa pakietu, np. com.example.app.",
|
||||||
|
"FINGERPRINTS": "Odciski certyfikatu SHA-256",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "Jeden odcisk w wierszu (64 znaki szesnastkowe, dwukropki opcjonalne). W razie potrzeby dodaj debug i release.",
|
||||||
"REDIRECT": "Adresy URI przekierowania",
|
"REDIRECT": "Adresy URI przekierowania",
|
||||||
"REDIRECTSECTION": "Adresy URI przekierowania",
|
"REDIRECTSECTION": "Adresy URI przekierowania",
|
||||||
"POSTLOGOUTREDIRECT": "Adresy URI po wylogowaniu",
|
"POSTLOGOUTREDIRECT": "Adresy URI po wylogowaniu",
|
||||||
@@ -2654,7 +2673,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI"
|
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "Team ID i Bundle ID iOS muszą być ustawione razem albo oba pozostawione puste.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "Nazwa pakietu Android jest wymagana, gdy ustawiono odciski certyfikatu."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2587,6 +2587,25 @@
|
|||||||
"DEVMODEDESC": "Atenção: Com o modo de desenvolvimento habilitado, as URIs de redirecionamento não serão validadas.",
|
"DEVMODEDESC": "Atenção: Com o modo de desenvolvimento habilitado, as URIs de redirecionamento não serão validadas.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Pular Página de Sucesso de Login",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Pular Página de Sucesso de Login",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Pule a página de sucesso após o login para este aplicativo nativo.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Pule a página de sucesso após o login para este aplicativo nativo.",
|
||||||
|
"APPLINKSSECTION": "Links de aplicativos nativos",
|
||||||
|
"APPLINKSDESC": "Configure a associação de iOS e Android para que esta instância possa servir arquivos de confiança de passkeys para seus aplicativos nativos.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "Servido em apple-app-site-association como entrada webcredentials TEAMID.BUNDLEID.",
|
||||||
|
"TEAMID": "Team ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "O Apple Team ID deve ter exatamente 10 caracteres alfanuméricos, p. ex. ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "O Team ID deve ter exatamente 10 caracteres alfanuméricos.",
|
||||||
|
"BUNDLEID": "Bundle ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "Identificador de pacote iOS (CFBundleIdentifier), p. ex. com.example.app. Não inclua o prefixo do Team ID.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Servido em assetlinks.json para Digital Asset Links / credenciais de passkey.",
|
||||||
|
"PACKAGENAME": "Nome do pacote",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "applicationId / nome do pacote Android, p. ex. com.example.app.",
|
||||||
|
"FINGERPRINTS": "Impressões digitais do certificado SHA-256",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "Uma impressão por linha (64 caracteres hexadecimais, dois-pontos opcionais). Inclua debug e release conforme necessário.",
|
||||||
"REDIRECT": "URIs de Redirecionamento",
|
"REDIRECT": "URIs de Redirecionamento",
|
||||||
"REDIRECTSECTION": "URIs de Redirecionamento",
|
"REDIRECTSECTION": "URIs de Redirecionamento",
|
||||||
"POSTLOGOUTREDIRECT": "URIs de Pós-Logout",
|
"POSTLOGOUTREDIRECT": "URIs de Pós-Logout",
|
||||||
@@ -2650,7 +2669,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI"
|
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "O Team ID e o Bundle ID do iOS devem estar ambos definidos, ou ambos vazios.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "O nome do pacote Android é obrigatório quando as impressões digitais do certificado estão definidas."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2612,6 +2612,25 @@
|
|||||||
"DEVMODEDESC": "Atenție: Cu modul de dezvoltare activat, URI-urile de redirecționare nu vor fi validate.",
|
"DEVMODEDESC": "Atenție: Cu modul de dezvoltare activat, URI-urile de redirecționare nu vor fi validate.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Omiteți pagina de succes la conectare",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Omiteți pagina de succes la conectare",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Omiteți pagina de succes după o conectare pentru această aplicație nativă.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Omiteți pagina de succes după o conectare pentru această aplicație nativă.",
|
||||||
|
"APPLINKSSECTION": "Linkuri pentru aplicații native",
|
||||||
|
"APPLINKSDESC": "Configurați asocierea iOS și Android pentru ca această instanță să poată servi fișierele de încredere passkey pentru aplicațiile dvs. native.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "Servit în apple-app-site-association ca intrare webcredentials TEAMID.BUNDLEID.",
|
||||||
|
"TEAMID": "Team ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "Apple Team ID trebuie să aibă exact 10 caractere alfanumerice, de ex. ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "Team ID trebuie să aibă exact 10 caractere alfanumerice.",
|
||||||
|
"BUNDLEID": "Bundle ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "Identificatorul de bundle iOS (CFBundleIdentifier), de ex. com.example.app. Nu includeți prefixul Team ID.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Servit în assetlinks.json pentru Digital Asset Links / acreditări passkey.",
|
||||||
|
"PACKAGENAME": "Numele pachetului",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "Android applicationId / numele pachetului, de ex. com.example.app.",
|
||||||
|
"FINGERPRINTS": "Amprente de certificat SHA-256",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "O amprentă pe linie (64 caractere hexazecimale, două puncte opționale). Includeți debug și release după nevoie.",
|
||||||
"REDIRECT": "URI-uri de redirecționare",
|
"REDIRECT": "URI-uri de redirecționare",
|
||||||
"REDIRECTSECTION": "URI-uri de redirecționare",
|
"REDIRECTSECTION": "URI-uri de redirecționare",
|
||||||
"POSTLOGOUTREDIRECT": "URI-uri de redirecționare după deconectare",
|
"POSTLOGOUTREDIRECT": "URI-uri de redirecționare după deconectare",
|
||||||
@@ -2675,7 +2694,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI"
|
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "Team ID și Bundle ID iOS trebuie setate împreună sau ambele lăsate goale.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "Numele pachetului Android este obligatoriu când sunt setate amprentele certificatului."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2704,6 +2704,25 @@
|
|||||||
"DEVMODEDESC": "Осторожно: При включённом режиме разработки URI перенаправления не будут проверяться.",
|
"DEVMODEDESC": "Осторожно: При включённом режиме разработки URI перенаправления не будут проверяться.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Пропустить страницу успешного входа в систему",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Пропустить страницу успешного входа в систему",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Пропустите страницу успеха после входа в систему для этого собственного приложения.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Пропустите страницу успеха после входа в систему для этого собственного приложения.",
|
||||||
|
"APPLINKSSECTION": "Ссылки нативных приложений",
|
||||||
|
"APPLINKSDESC": "Настройте ассоциацию iOS и Android, чтобы этот инстанс мог отдавать trust-файлы passkey для ваших нативных приложений.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "Отдаётся в apple-app-site-association как запись webcredentials TEAMID.BUNDLEID.",
|
||||||
|
"TEAMID": "Team ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "Apple Team ID должен состоять ровно из 10 буквенно-цифровых символов, напр. ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "Team ID должен состоять ровно из 10 буквенно-цифровых символов.",
|
||||||
|
"BUNDLEID": "Bundle ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "iOS Bundle Identifier (CFBundleIdentifier), напр. com.example.app. Не включайте префикс Team ID.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Отдаётся в assetlinks.json для Digital Asset Links / учётных данных passkey.",
|
||||||
|
"PACKAGENAME": "Имя пакета",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "Android applicationId / имя пакета, напр. com.example.app.",
|
||||||
|
"FINGERPRINTS": "Отпечатки сертификата SHA-256",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "Один отпечаток в строке (64 шестнадцатеричных символа, двоеточия необязательны). При необходимости укажите debug и release.",
|
||||||
"REDIRECT": "Перенаправление URI",
|
"REDIRECT": "Перенаправление URI",
|
||||||
"REDIRECTSECTION": "Перенаправление URI",
|
"REDIRECTSECTION": "Перенаправление URI",
|
||||||
"POSTLOGOUTREDIRECT": "URI после выхода из системы",
|
"POSTLOGOUTREDIRECT": "URI после выхода из системы",
|
||||||
@@ -2767,7 +2786,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "URI выхода из системы через резервный канал"
|
"BACKCHANNELLOOGOUTURI": "URI выхода из системы через резервный канал",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "iOS Team ID и Bundle ID должны быть заданы вместе или оба оставлены пустыми.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "Имя пакета Android обязательно, если заданы отпечатки сертификата."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2619,6 +2619,25 @@
|
|||||||
"DEVMODEDESC": "Observera: Med utvecklingsläge aktiverat kommer omdirigerings-URI:er inte att valideras.",
|
"DEVMODEDESC": "Observera: Med utvecklingsläge aktiverat kommer omdirigerings-URI:er inte att valideras.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Hoppa över inloggningsframgångssida",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Hoppa över inloggningsframgångssida",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Hoppa över framgångssidan efter en inloggning för denna inhemska app.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Hoppa över framgångssidan efter en inloggning för denna inhemska app.",
|
||||||
|
"APPLINKSSECTION": "Länkar för inbyggda appar",
|
||||||
|
"APPLINKSDESC": "Konfigurera iOS- och Android-association så att den här instansen kan servera passkey-trustfiler för dina inbyggda appar.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "Serveras i apple-app-site-association som webcredentials-posten TEAMID.BUNDLEID.",
|
||||||
|
"TEAMID": "Team-ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "Apple Team ID måste vara exakt 10 alfanumeriska tecken, t.ex. ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "Team ID måste vara exakt 10 alfanumeriska tecken.",
|
||||||
|
"BUNDLEID": "Bundle-ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "iOS Bundle Identifier (CFBundleIdentifier), t.ex. com.example.app. Inkludera inte Team ID-prefixet.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Serveras i assetlinks.json för Digital Asset Links / passkey-uppgifter.",
|
||||||
|
"PACKAGENAME": "Paketnamn",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "Android applicationId / paketnamn, t.ex. com.example.app.",
|
||||||
|
"FINGERPRINTS": "SHA-256-certifikatfingeravtryck",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "Ett fingeravtryck per rad (64 hexadecimala tecken, kolon valfritt). Inkludera debug och release vid behov.",
|
||||||
"REDIRECT": "Omdirigerings-URI:er",
|
"REDIRECT": "Omdirigerings-URI:er",
|
||||||
"REDIRECTSECTION": "Omdirigerings-URI:er",
|
"REDIRECTSECTION": "Omdirigerings-URI:er",
|
||||||
"POSTLOGOUTREDIRECT": "Post Logout-URI:er",
|
"POSTLOGOUTREDIRECT": "Post Logout-URI:er",
|
||||||
@@ -2682,7 +2701,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI"
|
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "iOS Team ID och Bundle ID måste båda anges, eller båda lämnas tomma.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "Android-paketnamn krävs när certifikatfingeravtryck anges."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2620,6 +2620,25 @@
|
|||||||
"DEVMODEDESC": "Dikkat: Geliştirme modu etkinleştirildiğinde yönlendirme URI'leri doğrulanmayacaktır.",
|
"DEVMODEDESC": "Dikkat: Geliştirme modu etkinleştirildiğinde yönlendirme URI'leri doğrulanmayacaktır.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Giriş Başarı Sayfasını Atla",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Giriş Başarı Sayfasını Atla",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Bu yerel uygulama için giriş sonrası başarı sayfasını atla.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Bu yerel uygulama için giriş sonrası başarı sayfasını atla.",
|
||||||
|
"APPLINKSSECTION": "Yerel uygulama bağlantıları",
|
||||||
|
"APPLINKSDESC": "Bu instansın yerel uygulamalarınız için passkey güven dosyalarını sunabilmesi amacıyla iOS ve Android ilişkilendirmesini yapılandırın.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "apple-app-site-association içinde webcredentials girdisi TEAMID.BUNDLEID olarak sunulur.",
|
||||||
|
"TEAMID": "Team ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "Apple Team ID tam olarak 10 alfanümerik karakter olmalıdır, örn. ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "Team ID tam olarak 10 alfanümerik karakter olmalıdır.",
|
||||||
|
"BUNDLEID": "Bundle ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "iOS Bundle Identifier (CFBundleIdentifier), örn. com.example.app. Team ID önekini eklemeyin.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Digital Asset Links / passkey kimlik bilgileri için assetlinks.json içinde sunulur.",
|
||||||
|
"PACKAGENAME": "Paket adı",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "Android applicationId / paket adı, örn. com.example.app.",
|
||||||
|
"FINGERPRINTS": "SHA-256 sertifika parmak izleri",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "Satır başına bir parmak izi (64 onaltılık karakter, iki nokta isteğe bağlı). Gerektiğinde debug ve release ekleyin.",
|
||||||
"REDIRECT": "Yönlendirme URI'leri",
|
"REDIRECT": "Yönlendirme URI'leri",
|
||||||
"REDIRECTSECTION": "Yönlendirme URI'leri",
|
"REDIRECTSECTION": "Yönlendirme URI'leri",
|
||||||
"POSTLOGOUTREDIRECT": "Çıkış Sonrası URI'ler",
|
"POSTLOGOUTREDIRECT": "Çıkış Sonrası URI'ler",
|
||||||
@@ -2683,7 +2702,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI"
|
"BACKCHANNELLOOGOUTURI": "Back-Channel Logout URI",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "iOS Team ID ve Bundle ID ikisi birden ayarlanmalı veya ikisi de boş bırakılmalıdır.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "Sertifika parmak izleri ayarlandığında Android paket adı zorunludur."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2622,6 +2622,25 @@
|
|||||||
"DEVMODEDESC": "Увага: з увімкненим режимом розробки URI перенаправлення не перевірятимуться.",
|
"DEVMODEDESC": "Увага: з увімкненим режимом розробки URI перенаправлення не перевірятимуться.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Пропустити сторінку успішного входу",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Пропустити сторінку успішного входу",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Пропустити сторінку успіху після входу для цього нативного додатку.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Пропустити сторінку успіху після входу для цього нативного додатку.",
|
||||||
|
"APPLINKSSECTION": "Посилання нативних застосунків",
|
||||||
|
"APPLINKSDESC": "Налаштуйте асоціацію iOS та Android, щоб цей інстанс міг віддавати trust-файли passkey для ваших нативних застосунків.",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "Віддається в apple-app-site-association як запис webcredentials TEAMID.BUNDLEID.",
|
||||||
|
"TEAMID": "Team ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "Apple Team ID має складатися рівно з 10 буквено-цифрових символів, напр. ABCDE12345.",
|
||||||
|
"TEAMIDLENGTH": "Team ID має складатися рівно з 10 буквено-цифрових символів.",
|
||||||
|
"BUNDLEID": "Bundle ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "iOS Bundle Identifier (CFBundleIdentifier), напр. com.example.app. Не включайте префікс Team ID.",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "Віддається в assetlinks.json для Digital Asset Links / облікових даних passkey.",
|
||||||
|
"PACKAGENAME": "Назва пакета",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "Android applicationId / назва пакета, напр. com.example.app.",
|
||||||
|
"FINGERPRINTS": "Відбитки сертифіката SHA-256",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "Один відбиток у рядку (64 шістнадцяткові символи, двокрапки необов’язкові). За потреби вкажіть debug і release.",
|
||||||
"REDIRECT": "URI перенаправлення",
|
"REDIRECT": "URI перенаправлення",
|
||||||
"REDIRECTSECTION": "URI перенаправлення",
|
"REDIRECTSECTION": "URI перенаправлення",
|
||||||
"POSTLOGOUTREDIRECT": "URI після виходу",
|
"POSTLOGOUTREDIRECT": "URI після виходу",
|
||||||
@@ -2685,7 +2704,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "URI виходу з зворотного каналу"
|
"BACKCHANNELLOOGOUTURI": "URI виходу з зворотного каналу",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "iOS Team ID та Bundle ID мають бути задані разом або обидва залишені порожніми.",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "Назва пакета Android обов’язкова, якщо задано відбитки сертифіката."
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -2624,6 +2624,25 @@
|
|||||||
"DEVMODEDESC": "注意:启用开发模式的重定向 URI 将不会被验证。",
|
"DEVMODEDESC": "注意:启用开发模式的重定向 URI 将不会被验证。",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "登录后跳过成功页面",
|
"SKIPNATIVEAPPSUCCESSPAGE": "登录后跳过成功页面",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "登录后跳过本机应用的成功页面",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "登录后跳过本机应用的成功页面",
|
||||||
|
"APPLINKSSECTION": "原生应用关联",
|
||||||
|
"APPLINKSDESC": "配置 iOS 和 Android 关联,使此实例能够为您的原生应用提供通行密钥信任文件。",
|
||||||
|
"IOSTITLE": "iOS",
|
||||||
|
"IOSDESC": "在 apple-app-site-association 中作为 webcredentials 条目 TEAMID.BUNDLEID 提供。",
|
||||||
|
"TEAMID": "团队 ID",
|
||||||
|
"TEAMIDPLACEHOLDER": "ABCDE12345",
|
||||||
|
"TEAMIDDESC": "Apple Team ID 必须恰好为 10 个字母或数字,例如 ABCDE12345。",
|
||||||
|
"TEAMIDLENGTH": "Team ID 必须恰好为 10 个字母或数字。",
|
||||||
|
"BUNDLEID": "Bundle ID",
|
||||||
|
"BUNDLEIDPLACEHOLDER": "com.example.app",
|
||||||
|
"BUNDLEIDDESC": "iOS Bundle Identifier(CFBundleIdentifier),例如 com.example.app。请勿包含 Team ID 前缀。",
|
||||||
|
"ANDROIDTITLE": "Android",
|
||||||
|
"ANDROIDDESC": "在 assetlinks.json 中为 Digital Asset Links / 通行密钥凭据提供。",
|
||||||
|
"PACKAGENAME": "软件包名称",
|
||||||
|
"PACKAGENAMEPLACEHOLDER": "com.example.app",
|
||||||
|
"PACKAGENAMEDESC": "Android applicationId / 软件包名称,例如 com.example.app。",
|
||||||
|
"FINGERPRINTS": "SHA-256 证书指纹",
|
||||||
|
"FINGERPRINTSPLACEHOLDER": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
"FINGERPRINTSDESC": "每行一个指纹(64 个十六进制字符,冒号可选)。按需包含调试版和正式版。",
|
||||||
"REDIRECT": "重定向 URLs",
|
"REDIRECT": "重定向 URLs",
|
||||||
"REDIRECTSECTION": "重定向 URLs",
|
"REDIRECTSECTION": "重定向 URLs",
|
||||||
"POSTLOGOUTREDIRECT": "退出登录重定向 URLs",
|
"POSTLOGOUTREDIRECT": "退出登录重定向 URLs",
|
||||||
@@ -2687,7 +2706,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BACKCHANNELLOOGOUTURI": "反向通道注销URI"
|
"BACKCHANNELLOOGOUTURI": "反向通道注销URI",
|
||||||
|
"IOSAPPLINKINCOMPLETE": "iOS Team ID 与 Bundle ID 必须同时填写,或同时留空。",
|
||||||
|
"ANDROIDPACKAGEREQUIRED": "设置证书指纹时必须填写 Android 软件包名称。"
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"INFO": {
|
"INFO": {
|
||||||
|
|||||||
@@ -15,30 +15,36 @@ func CreateOIDCAppRequestToDomain(name, appID, projectID string, req *applicatio
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
iosTeamID, iosBundleID := iosAppLinkConfigToDomain(req.GetIos())
|
||||||
|
androidPackageName, androidFingerprints := androidAppLinkConfigToDomain(req.GetAndroid())
|
||||||
return &domain.OIDCApp{
|
return &domain.OIDCApp{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: projectID,
|
AggregateID: projectID,
|
||||||
},
|
},
|
||||||
AppID: appID,
|
AppID: appID,
|
||||||
AppName: name,
|
AppName: name,
|
||||||
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
|
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
|
||||||
RedirectUris: req.GetRedirectUris(),
|
RedirectUris: req.GetRedirectUris(),
|
||||||
ResponseTypes: oidcResponseTypesToDomain(req.GetResponseTypes()),
|
ResponseTypes: oidcResponseTypesToDomain(req.GetResponseTypes()),
|
||||||
GrantTypes: oidcGrantTypesToDomain(req.GetGrantTypes()),
|
GrantTypes: oidcGrantTypesToDomain(req.GetGrantTypes()),
|
||||||
ApplicationType: gu.Ptr(oidcApplicationTypeToDomain(req.GetApplicationType())),
|
ApplicationType: gu.Ptr(oidcApplicationTypeToDomain(req.GetApplicationType())),
|
||||||
AuthMethodType: gu.Ptr(oidcAuthMethodTypeToDomain(req.GetAuthMethodType())),
|
AuthMethodType: gu.Ptr(oidcAuthMethodTypeToDomain(req.GetAuthMethodType())),
|
||||||
PostLogoutRedirectUris: req.GetPostLogoutRedirectUris(),
|
PostLogoutRedirectUris: req.GetPostLogoutRedirectUris(),
|
||||||
DevMode: &req.DevelopmentMode,
|
DevMode: &req.DevelopmentMode,
|
||||||
AccessTokenType: gu.Ptr(oidcTokenTypeToDomain(req.GetAccessTokenType())),
|
AccessTokenType: gu.Ptr(oidcTokenTypeToDomain(req.GetAccessTokenType())),
|
||||||
AccessTokenRoleAssertion: gu.Ptr(req.GetAccessTokenRoleAssertion()),
|
AccessTokenRoleAssertion: gu.Ptr(req.GetAccessTokenRoleAssertion()),
|
||||||
IDTokenRoleAssertion: gu.Ptr(req.GetIdTokenRoleAssertion()),
|
IDTokenRoleAssertion: gu.Ptr(req.GetIdTokenRoleAssertion()),
|
||||||
IDTokenUserinfoAssertion: gu.Ptr(req.GetIdTokenUserinfoAssertion()),
|
IDTokenUserinfoAssertion: gu.Ptr(req.GetIdTokenUserinfoAssertion()),
|
||||||
ClockSkew: gu.Ptr(req.GetClockSkew().AsDuration()),
|
ClockSkew: gu.Ptr(req.GetClockSkew().AsDuration()),
|
||||||
AdditionalOrigins: req.GetAdditionalOrigins(),
|
AdditionalOrigins: req.GetAdditionalOrigins(),
|
||||||
SkipNativeAppSuccessPage: gu.Ptr(req.GetSkipNativeAppSuccessPage()),
|
SkipNativeAppSuccessPage: gu.Ptr(req.GetSkipNativeAppSuccessPage()),
|
||||||
BackChannelLogoutURI: gu.Ptr(req.GetBackChannelLogoutUri()),
|
BackChannelLogoutURI: gu.Ptr(req.GetBackChannelLogoutUri()),
|
||||||
LoginVersion: loginVersion,
|
LoginVersion: loginVersion,
|
||||||
LoginBaseURI: loginBaseURI,
|
LoginBaseURI: loginBaseURI,
|
||||||
|
IOSTeamID: iosTeamID,
|
||||||
|
IOSBundleID: iosBundleID,
|
||||||
|
AndroidPackageName: androidPackageName,
|
||||||
|
AndroidSHA256CertFingerprints: androidFingerprints,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,28 +53,35 @@ func UpdateOIDCAppConfigRequestToDomain(appID, projectID string, app *applicatio
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// nested messages: omit = no change; present (incl. empty) = set/clear
|
||||||
|
iosTeamID, iosBundleID := iosAppLinkConfigToDomain(app.Ios)
|
||||||
|
androidPackageName, androidFingerprints := androidAppLinkConfigToDomain(app.Android)
|
||||||
return &domain.OIDCApp{
|
return &domain.OIDCApp{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: projectID,
|
AggregateID: projectID,
|
||||||
},
|
},
|
||||||
AppID: appID,
|
AppID: appID,
|
||||||
RedirectUris: app.RedirectUris,
|
RedirectUris: app.RedirectUris,
|
||||||
ResponseTypes: oidcResponseTypesToDomain(app.ResponseTypes),
|
ResponseTypes: oidcResponseTypesToDomain(app.ResponseTypes),
|
||||||
GrantTypes: oidcGrantTypesToDomain(app.GrantTypes),
|
GrantTypes: oidcGrantTypesToDomain(app.GrantTypes),
|
||||||
ApplicationType: oidcApplicationTypeToDomainPtr(app.ApplicationType),
|
ApplicationType: oidcApplicationTypeToDomainPtr(app.ApplicationType),
|
||||||
AuthMethodType: oidcAuthMethodTypeToDomainPtr(app.AuthMethodType),
|
AuthMethodType: oidcAuthMethodTypeToDomainPtr(app.AuthMethodType),
|
||||||
PostLogoutRedirectUris: app.PostLogoutRedirectUris,
|
PostLogoutRedirectUris: app.PostLogoutRedirectUris,
|
||||||
DevMode: app.DevelopmentMode,
|
DevMode: app.DevelopmentMode,
|
||||||
AccessTokenType: oidcTokenTypeToDomainPtr(app.AccessTokenType),
|
AccessTokenType: oidcTokenTypeToDomainPtr(app.AccessTokenType),
|
||||||
AccessTokenRoleAssertion: app.AccessTokenRoleAssertion,
|
AccessTokenRoleAssertion: app.AccessTokenRoleAssertion,
|
||||||
IDTokenRoleAssertion: app.IdTokenRoleAssertion,
|
IDTokenRoleAssertion: app.IdTokenRoleAssertion,
|
||||||
IDTokenUserinfoAssertion: app.IdTokenUserinfoAssertion,
|
IDTokenUserinfoAssertion: app.IdTokenUserinfoAssertion,
|
||||||
ClockSkew: gu.Ptr(app.GetClockSkew().AsDuration()),
|
ClockSkew: gu.Ptr(app.GetClockSkew().AsDuration()),
|
||||||
AdditionalOrigins: app.AdditionalOrigins,
|
AdditionalOrigins: app.AdditionalOrigins,
|
||||||
SkipNativeAppSuccessPage: app.SkipNativeAppSuccessPage,
|
SkipNativeAppSuccessPage: app.SkipNativeAppSuccessPage,
|
||||||
BackChannelLogoutURI: app.BackChannelLogoutUri,
|
BackChannelLogoutURI: app.BackChannelLogoutUri,
|
||||||
LoginVersion: loginVersion,
|
LoginVersion: loginVersion,
|
||||||
LoginBaseURI: loginBaseURI,
|
LoginBaseURI: loginBaseURI,
|
||||||
|
IOSTeamID: iosTeamID,
|
||||||
|
IOSBundleID: iosBundleID,
|
||||||
|
AndroidPackageName: androidPackageName,
|
||||||
|
AndroidSHA256CertFingerprints: androidFingerprints,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,10 +226,50 @@ func appOIDCConfigToPb(oidcApp *query.OIDCApp) *application.Application_OidcConf
|
|||||||
SkipNativeAppSuccessPage: oidcApp.SkipNativeAppSuccessPage,
|
SkipNativeAppSuccessPage: oidcApp.SkipNativeAppSuccessPage,
|
||||||
BackChannelLogoutUri: oidcApp.BackChannelLogoutURI,
|
BackChannelLogoutUri: oidcApp.BackChannelLogoutURI,
|
||||||
LoginVersion: loginVersionToPb(oidcApp.LoginVersion, oidcApp.LoginBaseURI),
|
LoginVersion: loginVersionToPb(oidcApp.LoginVersion, oidcApp.LoginBaseURI),
|
||||||
|
Ios: iosAppLinkConfigToPb(oidcApp.IOSTeamID, oidcApp.IOSBundleID),
|
||||||
|
Android: androidAppLinkConfigToPb(oidcApp.AndroidPackageName, oidcApp.AndroidSHA256CertFingerprints),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func iosAppLinkConfigToDomain(cfg *application.IOSAppLinkConfig) (teamID, bundleID *string) {
|
||||||
|
if cfg == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return gu.Ptr(cfg.GetTeamId()), gu.Ptr(cfg.GetBundleId())
|
||||||
|
}
|
||||||
|
|
||||||
|
func androidAppLinkConfigToDomain(cfg *application.AndroidAppLinkConfig) (packageName *string, fingerprints []string) {
|
||||||
|
if cfg == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
fps := cfg.GetSha256CertFingerprints()
|
||||||
|
if fps == nil {
|
||||||
|
fps = []string{}
|
||||||
|
}
|
||||||
|
return gu.Ptr(cfg.GetPackageName()), fps
|
||||||
|
}
|
||||||
|
|
||||||
|
func iosAppLinkConfigToPb(teamID, bundleID string) *application.IOSAppLinkConfig {
|
||||||
|
if teamID == "" && bundleID == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &application.IOSAppLinkConfig{
|
||||||
|
TeamId: teamID,
|
||||||
|
BundleId: bundleID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func androidAppLinkConfigToPb(packageName string, fingerprints []string) *application.AndroidAppLinkConfig {
|
||||||
|
if packageName == "" && len(fingerprints) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &application.AndroidAppLinkConfig{
|
||||||
|
PackageName: packageName,
|
||||||
|
Sha256CertFingerprints: fingerprints,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func oidcResponseTypesFromModel(responseTypes []domain.OIDCResponseType) []application.OIDCResponseType {
|
func oidcResponseTypesFromModel(responseTypes []domain.OIDCResponseType) []application.OIDCResponseType {
|
||||||
oidcResponseTypes := make([]application.OIDCResponseType, len(responseTypes))
|
oidcResponseTypes := make([]application.OIDCResponseType, len(responseTypes))
|
||||||
for i, responseType := range responseTypes {
|
for i, responseType := range responseTypes {
|
||||||
|
|||||||
@@ -66,29 +66,41 @@ func TestCreateOIDCAppRequestToDomain(t *testing.T) {
|
|||||||
LoginVersion: &application.LoginVersion{Version: &application.LoginVersion_LoginV2{LoginV2: &application.LoginV2{
|
LoginVersion: &application.LoginVersion{Version: &application.LoginVersion_LoginV2{LoginV2: &application.LoginV2{
|
||||||
BaseUri: gu.Ptr("https://login"),
|
BaseUri: gu.Ptr("https://login"),
|
||||||
}}},
|
}}},
|
||||||
|
Ios: &application.IOSAppLinkConfig{
|
||||||
|
TeamId: "TEAMID",
|
||||||
|
BundleId: "com.example.app",
|
||||||
|
},
|
||||||
|
Android: &application.AndroidAppLinkConfig{
|
||||||
|
PackageName: "com.example.app",
|
||||||
|
Sha256CertFingerprints: []string{"AA:BB:CC"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
expectedModel: &domain.OIDCApp{
|
expectedModel: &domain.OIDCApp{
|
||||||
ObjectRoot: models.ObjectRoot{AggregateID: "project1"},
|
ObjectRoot: models.ObjectRoot{AggregateID: "project1"},
|
||||||
AppName: "all fields set",
|
AppName: "all fields set",
|
||||||
AppID: "app1",
|
AppID: "app1",
|
||||||
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
|
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
|
||||||
RedirectUris: []string{"https://redirect"},
|
RedirectUris: []string{"https://redirect"},
|
||||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
|
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
|
||||||
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypeBasic),
|
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypeBasic),
|
||||||
PostLogoutRedirectUris: []string{"https://logout"},
|
PostLogoutRedirectUris: []string{"https://logout"},
|
||||||
DevMode: gu.Ptr(true),
|
DevMode: gu.Ptr(true),
|
||||||
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
|
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
|
||||||
AccessTokenRoleAssertion: gu.Ptr(true),
|
AccessTokenRoleAssertion: gu.Ptr(true),
|
||||||
IDTokenRoleAssertion: gu.Ptr(true),
|
IDTokenRoleAssertion: gu.Ptr(true),
|
||||||
IDTokenUserinfoAssertion: gu.Ptr(true),
|
IDTokenUserinfoAssertion: gu.Ptr(true),
|
||||||
ClockSkew: gu.Ptr(5 * time.Second),
|
ClockSkew: gu.Ptr(5 * time.Second),
|
||||||
AdditionalOrigins: []string{"https://origin"},
|
AdditionalOrigins: []string{"https://origin"},
|
||||||
SkipNativeAppSuccessPage: gu.Ptr(true),
|
SkipNativeAppSuccessPage: gu.Ptr(true),
|
||||||
BackChannelLogoutURI: gu.Ptr("https://backchannel"),
|
BackChannelLogoutURI: gu.Ptr("https://backchannel"),
|
||||||
LoginVersion: gu.Ptr(domain.LoginVersion2),
|
LoginVersion: gu.Ptr(domain.LoginVersion2),
|
||||||
LoginBaseURI: gu.Ptr("https://login"),
|
LoginBaseURI: gu.Ptr("https://login"),
|
||||||
|
IOSTeamID: gu.Ptr("TEAMID"),
|
||||||
|
IOSBundleID: gu.Ptr("com.example.app"),
|
||||||
|
AndroidPackageName: gu.Ptr("com.example.app"),
|
||||||
|
AndroidSHA256CertFingerprints: []string{"AA:BB:CC"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -182,6 +194,71 @@ func TestUpdateOIDCAppConfigRequestToDomain(t *testing.T) {
|
|||||||
LoginBaseURI: gu.Ptr("https://login"),
|
LoginBaseURI: gu.Ptr("https://login"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
testName: "omit app links leaves nil",
|
||||||
|
appID: "app1",
|
||||||
|
projectID: "proj1",
|
||||||
|
req: &application.UpdateOIDCApplicationConfigurationRequest{},
|
||||||
|
expectedModel: &domain.OIDCApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "proj1"},
|
||||||
|
AppID: "app1",
|
||||||
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
|
ClockSkew: gu.Ptr(time.Duration(0)),
|
||||||
|
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
|
||||||
|
LoginBaseURI: gu.Ptr(""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "set app links",
|
||||||
|
appID: "app1",
|
||||||
|
projectID: "proj1",
|
||||||
|
req: &application.UpdateOIDCApplicationConfigurationRequest{
|
||||||
|
Ios: &application.IOSAppLinkConfig{
|
||||||
|
TeamId: "TEAMID",
|
||||||
|
BundleId: "com.example.app",
|
||||||
|
},
|
||||||
|
Android: &application.AndroidAppLinkConfig{
|
||||||
|
PackageName: "com.example.app",
|
||||||
|
Sha256CertFingerprints: []string{"AA:BB:CC"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedModel: &domain.OIDCApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "proj1"},
|
||||||
|
AppID: "app1",
|
||||||
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
|
ClockSkew: gu.Ptr(time.Duration(0)),
|
||||||
|
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
|
||||||
|
LoginBaseURI: gu.Ptr(""),
|
||||||
|
IOSTeamID: gu.Ptr("TEAMID"),
|
||||||
|
IOSBundleID: gu.Ptr("com.example.app"),
|
||||||
|
AndroidPackageName: gu.Ptr("com.example.app"),
|
||||||
|
AndroidSHA256CertFingerprints: []string{"AA:BB:CC"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "clear app links with empty messages",
|
||||||
|
appID: "app1",
|
||||||
|
projectID: "proj1",
|
||||||
|
req: &application.UpdateOIDCApplicationConfigurationRequest{
|
||||||
|
Ios: &application.IOSAppLinkConfig{},
|
||||||
|
Android: &application.AndroidAppLinkConfig{},
|
||||||
|
},
|
||||||
|
expectedModel: &domain.OIDCApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "proj1"},
|
||||||
|
AppID: "app1",
|
||||||
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
|
ClockSkew: gu.Ptr(time.Duration(0)),
|
||||||
|
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
|
||||||
|
LoginBaseURI: gu.Ptr(""),
|
||||||
|
IOSTeamID: gu.Ptr(""),
|
||||||
|
IOSBundleID: gu.Ptr(""),
|
||||||
|
AndroidPackageName: gu.Ptr(""),
|
||||||
|
AndroidSHA256CertFingerprints: []string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tt {
|
for _, tc := range tt {
|
||||||
@@ -453,26 +530,30 @@ func TestAppOIDCConfigToPb(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "full config",
|
name: "full config",
|
||||||
input: &query.OIDCApp{
|
input: &query.OIDCApp{
|
||||||
RedirectURIs: []string{"https://example.com/callback"},
|
RedirectURIs: []string{"https://example.com/callback"},
|
||||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
AppType: domain.OIDCApplicationTypeWeb,
|
AppType: domain.OIDCApplicationTypeWeb,
|
||||||
ClientID: "client123",
|
ClientID: "client123",
|
||||||
AuthMethodType: domain.OIDCAuthMethodTypeBasic,
|
AuthMethodType: domain.OIDCAuthMethodTypeBasic,
|
||||||
PostLogoutRedirectURIs: []string{"https://example.com/logout"},
|
PostLogoutRedirectURIs: []string{"https://example.com/logout"},
|
||||||
ComplianceProblems: []string{"problem1", "problem2"},
|
ComplianceProblems: []string{"problem1", "problem2"},
|
||||||
IsDevMode: true,
|
IsDevMode: true,
|
||||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
AccessTokenType: domain.OIDCTokenTypeBearer,
|
||||||
AssertAccessTokenRole: true,
|
AssertAccessTokenRole: true,
|
||||||
AssertIDTokenRole: true,
|
AssertIDTokenRole: true,
|
||||||
AssertIDTokenUserinfo: true,
|
AssertIDTokenUserinfo: true,
|
||||||
ClockSkew: 5 * time.Second,
|
ClockSkew: 5 * time.Second,
|
||||||
AdditionalOrigins: []string{"https://app.example.com"},
|
AdditionalOrigins: []string{"https://app.example.com"},
|
||||||
AllowedOrigins: []string{"https://allowed.example.com"},
|
AllowedOrigins: []string{"https://allowed.example.com"},
|
||||||
SkipNativeAppSuccessPage: true,
|
SkipNativeAppSuccessPage: true,
|
||||||
BackChannelLogoutURI: "https://example.com/backchannel",
|
BackChannelLogoutURI: "https://example.com/backchannel",
|
||||||
LoginVersion: domain.LoginVersion2,
|
LoginVersion: domain.LoginVersion2,
|
||||||
LoginBaseURI: gu.Ptr("https://login.example.com"),
|
LoginBaseURI: gu.Ptr("https://login.example.com"),
|
||||||
|
IOSTeamID: "TEAMID",
|
||||||
|
IOSBundleID: "com.example.app",
|
||||||
|
AndroidPackageName: "com.example.app",
|
||||||
|
AndroidSHA256CertFingerprints: []string{"AA:BB:CC"},
|
||||||
},
|
},
|
||||||
expected: &application.Application_OidcConfiguration{
|
expected: &application.Application_OidcConfiguration{
|
||||||
OidcConfiguration: &application.OIDCConfiguration{
|
OidcConfiguration: &application.OIDCConfiguration{
|
||||||
@@ -506,6 +587,14 @@ func TestAppOIDCConfigToPb(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Ios: &application.IOSAppLinkConfig{
|
||||||
|
TeamId: "TEAMID",
|
||||||
|
BundleId: "com.example.app",
|
||||||
|
},
|
||||||
|
Android: &application.AndroidAppLinkConfig{
|
||||||
|
PackageName: "com.example.app",
|
||||||
|
Sha256CertFingerprints: []string{"AA:BB:CC"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package app_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
|
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOIDCAppLinkConfig(t *testing.T) {
|
||||||
|
p := instance.CreateProject(IAMOwnerCtx, t, instance.DefaultOrg.GetId(), integration.ProjectName(), false, false)
|
||||||
|
|
||||||
|
ios := &application.IOSAppLinkConfig{
|
||||||
|
TeamId: "ABCDE12345",
|
||||||
|
BundleId: "com.example.app",
|
||||||
|
}
|
||||||
|
android := &application.AndroidAppLinkConfig{
|
||||||
|
PackageName: "com.example.app",
|
||||||
|
Sha256CertFingerprints: []string{"AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("create with incomplete ios config is rejected", func(t *testing.T) {
|
||||||
|
_, err := instance.Client.ApplicationV2.CreateApplication(IAMOwnerCtx, &application.CreateApplicationRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
Name: integration.ApplicationName(),
|
||||||
|
ApplicationType: &application.CreateApplicationRequest_OidcConfiguration{
|
||||||
|
OidcConfiguration: baseOIDCCreateRequest(iosOnlyTeamID()),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.Equal(t, codes.InvalidArgument, status.Code(err))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("create get update omit and clear round trip", func(t *testing.T) {
|
||||||
|
created, err := instance.Client.ApplicationV2.CreateApplication(IAMOwnerCtx, &application.CreateApplicationRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
Name: integration.ApplicationName(),
|
||||||
|
ApplicationType: &application.CreateApplicationRequest_OidcConfiguration{
|
||||||
|
OidcConfiguration: baseOIDCCreateRequestWithAppLinks(ios, android),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, created.GetApplicationId())
|
||||||
|
|
||||||
|
assertOIDCAppLinks(t, created.GetApplicationId(), ios, android)
|
||||||
|
|
||||||
|
updatedIos := &application.IOSAppLinkConfig{
|
||||||
|
TeamId: "NEWTEAM123",
|
||||||
|
BundleId: "com.example.app.updated",
|
||||||
|
}
|
||||||
|
updatedAndroid := &application.AndroidAppLinkConfig{
|
||||||
|
PackageName: "com.example.app.updated",
|
||||||
|
Sha256CertFingerprints: []string{"11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00"},
|
||||||
|
}
|
||||||
|
_, err = instance.Client.ApplicationV2.UpdateApplication(IAMOwnerCtx, &application.UpdateApplicationRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
ApplicationId: created.GetApplicationId(),
|
||||||
|
ApplicationType: &application.UpdateApplicationRequest_OidcConfiguration{
|
||||||
|
OidcConfiguration: &application.UpdateOIDCApplicationConfigurationRequest{
|
||||||
|
Ios: updatedIos,
|
||||||
|
Android: updatedAndroid,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertOIDCAppLinks(t, created.GetApplicationId(), updatedIos, updatedAndroid)
|
||||||
|
|
||||||
|
_, err = instance.Client.ApplicationV2.UpdateApplication(IAMOwnerCtx, &application.UpdateApplicationRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
ApplicationId: created.GetApplicationId(),
|
||||||
|
ApplicationType: &application.UpdateApplicationRequest_OidcConfiguration{
|
||||||
|
OidcConfiguration: &application.UpdateOIDCApplicationConfigurationRequest{
|
||||||
|
PostLogoutRedirectUris: []string{"http://example.com/home-omitted"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertOIDCAppLinks(t, created.GetApplicationId(), updatedIos, updatedAndroid)
|
||||||
|
|
||||||
|
_, err = instance.Client.ApplicationV2.UpdateApplication(IAMOwnerCtx, &application.UpdateApplicationRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
ApplicationId: created.GetApplicationId(),
|
||||||
|
ApplicationType: &application.UpdateApplicationRequest_OidcConfiguration{
|
||||||
|
OidcConfiguration: &application.UpdateOIDCApplicationConfigurationRequest{
|
||||||
|
Ios: &application.IOSAppLinkConfig{},
|
||||||
|
Android: &application.AndroidAppLinkConfig{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertOIDCAppLinks(t, created.GetApplicationId(), nil, nil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func baseOIDCCreateRequest(opts ...func(*application.CreateOIDCApplicationRequest)) *application.CreateOIDCApplicationRequest {
|
||||||
|
req := &application.CreateOIDCApplicationRequest{
|
||||||
|
RedirectUris: []string{"http://example.com"},
|
||||||
|
ResponseTypes: []application.OIDCResponseType{application.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE},
|
||||||
|
GrantTypes: []application.OIDCGrantType{application.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE},
|
||||||
|
ApplicationType: application.OIDCApplicationType_OIDC_APP_TYPE_NATIVE,
|
||||||
|
AuthMethodType: application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE,
|
||||||
|
PostLogoutRedirectUris: []string{"http://example.com/home"},
|
||||||
|
Version: application.OIDCVersion_OIDC_VERSION_1_0,
|
||||||
|
AccessTokenType: application.OIDCTokenType_OIDC_TOKEN_TYPE_BEARER,
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(req)
|
||||||
|
}
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func baseOIDCCreateRequestWithAppLinks(ios *application.IOSAppLinkConfig, android *application.AndroidAppLinkConfig) *application.CreateOIDCApplicationRequest {
|
||||||
|
return baseOIDCCreateRequest(func(req *application.CreateOIDCApplicationRequest) {
|
||||||
|
req.Ios = ios
|
||||||
|
req.Android = android
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func iosOnlyTeamID() func(*application.CreateOIDCApplicationRequest) {
|
||||||
|
return func(req *application.CreateOIDCApplicationRequest) {
|
||||||
|
req.Ios = &application.IOSAppLinkConfig{TeamId: "ABCDE12345"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertOIDCAppLinks(t *testing.T, appID string, wantIOS *application.IOSAppLinkConfig, wantAndroid *application.AndroidAppLinkConfig) {
|
||||||
|
t.Helper()
|
||||||
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(IAMOwnerCtx, time.Minute)
|
||||||
|
require.EventuallyWithT(t, func(ct *assert.CollectT) {
|
||||||
|
res, err := instance.Client.ApplicationV2.GetApplication(IAMOwnerCtx, &application.GetApplicationRequest{
|
||||||
|
ApplicationId: appID,
|
||||||
|
})
|
||||||
|
require.NoError(ct, err)
|
||||||
|
cfg := res.GetApplication().GetOidcConfiguration()
|
||||||
|
require.NotNil(ct, cfg)
|
||||||
|
|
||||||
|
if wantIOS == nil {
|
||||||
|
assert.Nil(ct, cfg.GetIos())
|
||||||
|
} else {
|
||||||
|
require.NotNil(ct, cfg.GetIos())
|
||||||
|
assert.Equal(ct, wantIOS.GetTeamId(), cfg.GetIos().GetTeamId())
|
||||||
|
assert.Equal(ct, wantIOS.GetBundleId(), cfg.GetIos().GetBundleId())
|
||||||
|
}
|
||||||
|
|
||||||
|
if wantAndroid == nil {
|
||||||
|
assert.Nil(ct, cfg.GetAndroid())
|
||||||
|
} else {
|
||||||
|
require.NotNil(ct, cfg.GetAndroid())
|
||||||
|
assert.Equal(ct, wantAndroid.GetPackageName(), cfg.GetAndroid().GetPackageName())
|
||||||
|
assert.Equal(ct, wantAndroid.GetSha256CertFingerprints(), cfg.GetAndroid().GetSha256CertFingerprints())
|
||||||
|
}
|
||||||
|
}, retryDuration, tick)
|
||||||
|
}
|
||||||
@@ -47,29 +47,35 @@ func AddOIDCAppRequestToDomain(req *mgmt_pb.AddOIDCAppRequest) (*domain.OIDCApp,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
iosTeamID, iosBundleID := app_grpc.IOSAppLinkConfigToDomain(req.GetIos())
|
||||||
|
androidPackageName, androidFingerprints := app_grpc.AndroidAppLinkConfigToDomain(req.GetAndroid())
|
||||||
return &domain.OIDCApp{
|
return &domain.OIDCApp{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: req.ProjectId,
|
AggregateID: req.ProjectId,
|
||||||
},
|
},
|
||||||
AppName: req.Name,
|
AppName: req.Name,
|
||||||
OIDCVersion: gu.Ptr(app_grpc.OIDCVersionToDomain(req.Version)),
|
OIDCVersion: gu.Ptr(app_grpc.OIDCVersionToDomain(req.Version)),
|
||||||
RedirectUris: req.RedirectUris,
|
RedirectUris: req.RedirectUris,
|
||||||
ResponseTypes: app_grpc.OIDCResponseTypesToDomain(req.ResponseTypes),
|
ResponseTypes: app_grpc.OIDCResponseTypesToDomain(req.ResponseTypes),
|
||||||
GrantTypes: app_grpc.OIDCGrantTypesToDomain(req.GrantTypes),
|
GrantTypes: app_grpc.OIDCGrantTypesToDomain(req.GrantTypes),
|
||||||
ApplicationType: gu.Ptr(app_grpc.OIDCApplicationTypeToDomain(req.AppType)),
|
ApplicationType: gu.Ptr(app_grpc.OIDCApplicationTypeToDomain(req.AppType)),
|
||||||
AuthMethodType: gu.Ptr(app_grpc.OIDCAuthMethodTypeToDomain(req.AuthMethodType)),
|
AuthMethodType: gu.Ptr(app_grpc.OIDCAuthMethodTypeToDomain(req.AuthMethodType)),
|
||||||
PostLogoutRedirectUris: req.PostLogoutRedirectUris,
|
PostLogoutRedirectUris: req.PostLogoutRedirectUris,
|
||||||
DevMode: gu.Ptr(req.GetDevMode()),
|
DevMode: gu.Ptr(req.GetDevMode()),
|
||||||
AccessTokenType: gu.Ptr(app_grpc.OIDCTokenTypeToDomain(req.AccessTokenType)),
|
AccessTokenType: gu.Ptr(app_grpc.OIDCTokenTypeToDomain(req.AccessTokenType)),
|
||||||
AccessTokenRoleAssertion: gu.Ptr(req.GetAccessTokenRoleAssertion()),
|
AccessTokenRoleAssertion: gu.Ptr(req.GetAccessTokenRoleAssertion()),
|
||||||
IDTokenRoleAssertion: gu.Ptr(req.GetIdTokenRoleAssertion()),
|
IDTokenRoleAssertion: gu.Ptr(req.GetIdTokenRoleAssertion()),
|
||||||
IDTokenUserinfoAssertion: gu.Ptr(req.GetIdTokenUserinfoAssertion()),
|
IDTokenUserinfoAssertion: gu.Ptr(req.GetIdTokenUserinfoAssertion()),
|
||||||
ClockSkew: gu.Ptr(req.GetClockSkew().AsDuration()),
|
ClockSkew: gu.Ptr(req.GetClockSkew().AsDuration()),
|
||||||
AdditionalOrigins: req.AdditionalOrigins,
|
AdditionalOrigins: req.AdditionalOrigins,
|
||||||
SkipNativeAppSuccessPage: gu.Ptr(req.GetSkipNativeAppSuccessPage()),
|
SkipNativeAppSuccessPage: gu.Ptr(req.GetSkipNativeAppSuccessPage()),
|
||||||
BackChannelLogoutURI: gu.Ptr(req.GetBackChannelLogoutUri()),
|
BackChannelLogoutURI: gu.Ptr(req.GetBackChannelLogoutUri()),
|
||||||
LoginVersion: gu.Ptr(loginVersion),
|
LoginVersion: gu.Ptr(loginVersion),
|
||||||
LoginBaseURI: gu.Ptr(loginBaseURI),
|
LoginBaseURI: gu.Ptr(loginBaseURI),
|
||||||
|
IOSTeamID: iosTeamID,
|
||||||
|
IOSBundleID: iosBundleID,
|
||||||
|
AndroidPackageName: androidPackageName,
|
||||||
|
AndroidSHA256CertFingerprints: androidFingerprints,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,28 +118,35 @@ func UpdateOIDCAppConfigRequestToDomain(app *mgmt_pb.UpdateOIDCAppConfigRequest)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// nested messages: omit = no change; present (incl. empty) = set/clear
|
||||||
|
iosTeamID, iosBundleID := app_grpc.IOSAppLinkConfigToDomain(app.Ios)
|
||||||
|
androidPackageName, androidFingerprints := app_grpc.AndroidAppLinkConfigToDomain(app.Android)
|
||||||
return &domain.OIDCApp{
|
return &domain.OIDCApp{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: app.ProjectId,
|
AggregateID: app.ProjectId,
|
||||||
},
|
},
|
||||||
AppID: app.AppId,
|
AppID: app.AppId,
|
||||||
RedirectUris: app.RedirectUris,
|
RedirectUris: app.RedirectUris,
|
||||||
ResponseTypes: app_grpc.OIDCResponseTypesToDomain(app.ResponseTypes),
|
ResponseTypes: app_grpc.OIDCResponseTypesToDomain(app.ResponseTypes),
|
||||||
GrantTypes: app_grpc.OIDCGrantTypesToDomain(app.GrantTypes),
|
GrantTypes: app_grpc.OIDCGrantTypesToDomain(app.GrantTypes),
|
||||||
ApplicationType: gu.Ptr(app_grpc.OIDCApplicationTypeToDomain(app.AppType)),
|
ApplicationType: gu.Ptr(app_grpc.OIDCApplicationTypeToDomain(app.AppType)),
|
||||||
AuthMethodType: gu.Ptr(app_grpc.OIDCAuthMethodTypeToDomain(app.AuthMethodType)),
|
AuthMethodType: gu.Ptr(app_grpc.OIDCAuthMethodTypeToDomain(app.AuthMethodType)),
|
||||||
PostLogoutRedirectUris: app.PostLogoutRedirectUris,
|
PostLogoutRedirectUris: app.PostLogoutRedirectUris,
|
||||||
DevMode: gu.Ptr(app.GetDevMode()),
|
DevMode: gu.Ptr(app.GetDevMode()),
|
||||||
AccessTokenType: gu.Ptr(app_grpc.OIDCTokenTypeToDomain(app.AccessTokenType)),
|
AccessTokenType: gu.Ptr(app_grpc.OIDCTokenTypeToDomain(app.AccessTokenType)),
|
||||||
AccessTokenRoleAssertion: gu.Ptr(app.GetAccessTokenRoleAssertion()),
|
AccessTokenRoleAssertion: gu.Ptr(app.GetAccessTokenRoleAssertion()),
|
||||||
IDTokenRoleAssertion: gu.Ptr(app.GetIdTokenRoleAssertion()),
|
IDTokenRoleAssertion: gu.Ptr(app.GetIdTokenRoleAssertion()),
|
||||||
IDTokenUserinfoAssertion: gu.Ptr(app.GetIdTokenUserinfoAssertion()),
|
IDTokenUserinfoAssertion: gu.Ptr(app.GetIdTokenUserinfoAssertion()),
|
||||||
ClockSkew: gu.Ptr(app.GetClockSkew().AsDuration()),
|
ClockSkew: gu.Ptr(app.GetClockSkew().AsDuration()),
|
||||||
AdditionalOrigins: app.AdditionalOrigins,
|
AdditionalOrigins: app.AdditionalOrigins,
|
||||||
SkipNativeAppSuccessPage: gu.Ptr(app.GetSkipNativeAppSuccessPage()),
|
SkipNativeAppSuccessPage: gu.Ptr(app.GetSkipNativeAppSuccessPage()),
|
||||||
BackChannelLogoutURI: gu.Ptr(app.GetBackChannelLogoutUri()),
|
BackChannelLogoutURI: gu.Ptr(app.GetBackChannelLogoutUri()),
|
||||||
LoginVersion: gu.Ptr(loginVersion),
|
LoginVersion: gu.Ptr(loginVersion),
|
||||||
LoginBaseURI: gu.Ptr(loginBaseURI),
|
LoginBaseURI: gu.Ptr(loginBaseURI),
|
||||||
|
IOSTeamID: iosTeamID,
|
||||||
|
IOSBundleID: iosBundleID,
|
||||||
|
AndroidPackageName: androidPackageName,
|
||||||
|
AndroidSHA256CertFingerprints: androidFingerprints,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package project
|
|||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
"google.golang.org/protobuf/types/known/durationpb"
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
|
||||||
object_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
|
object_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
|
||||||
@@ -68,6 +69,8 @@ func AppOIDCConfigToPb(app *query.OIDCApp) *app_pb.App_OidcConfig {
|
|||||||
SkipNativeAppSuccessPage: app.SkipNativeAppSuccessPage,
|
SkipNativeAppSuccessPage: app.SkipNativeAppSuccessPage,
|
||||||
BackChannelLogoutUri: app.BackChannelLogoutURI,
|
BackChannelLogoutUri: app.BackChannelLogoutURI,
|
||||||
LoginVersion: loginVersionToPb(app.LoginVersion, app.LoginBaseURI),
|
LoginVersion: loginVersionToPb(app.LoginVersion, app.LoginBaseURI),
|
||||||
|
Ios: iosAppLinkConfigToPb(app.IOSTeamID, app.IOSBundleID),
|
||||||
|
Android: androidAppLinkConfigToPb(app.AndroidPackageName, app.AndroidSHA256CertFingerprints),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,3 +348,41 @@ func LoginVersionToDomain(version *app_pb.LoginVersion) (domain.LoginVersion, st
|
|||||||
return domain.LoginVersionUnspecified, "", nil
|
return domain.LoginVersionUnspecified, "", nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IOSAppLinkConfigToDomain(cfg *app_pb.IOSAppLinkConfig) (teamID, bundleID *string) {
|
||||||
|
if cfg == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return gu.Ptr(cfg.GetTeamId()), gu.Ptr(cfg.GetBundleId())
|
||||||
|
}
|
||||||
|
|
||||||
|
func AndroidAppLinkConfigToDomain(cfg *app_pb.AndroidAppLinkConfig) (packageName *string, fingerprints []string) {
|
||||||
|
if cfg == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
fps := cfg.GetSha256CertFingerprints()
|
||||||
|
if fps == nil {
|
||||||
|
fps = []string{}
|
||||||
|
}
|
||||||
|
return gu.Ptr(cfg.GetPackageName()), fps
|
||||||
|
}
|
||||||
|
|
||||||
|
func iosAppLinkConfigToPb(teamID, bundleID string) *app_pb.IOSAppLinkConfig {
|
||||||
|
if teamID == "" && bundleID == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &app_pb.IOSAppLinkConfig{
|
||||||
|
TeamId: teamID,
|
||||||
|
BundleId: bundleID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func androidAppLinkConfigToPb(packageName string, fingerprints []string) *app_pb.AndroidAppLinkConfig {
|
||||||
|
if packageName == "" && len(fingerprints) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &app_pb.AndroidAppLinkConfig{
|
||||||
|
PackageName: packageName,
|
||||||
|
Sha256CertFingerprints: fingerprints,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,187 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package well_known_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/well_known"
|
||||||
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
|
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
CTX context.Context
|
||||||
|
IAMOwnerCtx context.Context
|
||||||
|
Instance *integration.Instance
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
os.Exit(func() int {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
Instance = integration.NewInstance(ctx)
|
||||||
|
CTX = ctx
|
||||||
|
IAMOwnerCtx = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
|
||||||
|
return m.Run()
|
||||||
|
}())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWellKnownAppLinks(t *testing.T) {
|
||||||
|
issuer := Instance.OIDCIssuer()
|
||||||
|
|
||||||
|
t.Run("empty when no apps configured", func(t *testing.T) {
|
||||||
|
assertEventuallyJSON(t, issuer+well_known.AppleAppSiteAssociationPath, map[string]any{
|
||||||
|
"webcredentials": map[string]any{
|
||||||
|
"apps": []any{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assertEventuallyJSON(t, issuer+well_known.AssetLinksPath, []any{})
|
||||||
|
})
|
||||||
|
|
||||||
|
project := Instance.CreateProject(IAMOwnerCtx, t, Instance.DefaultOrg.GetId(), integration.ProjectName(), false, false)
|
||||||
|
|
||||||
|
ios1 := &application.IOSAppLinkConfig{TeamId: "TEAMAAAAAA", BundleId: "com.example.one"}
|
||||||
|
android1 := &application.AndroidAppLinkConfig{
|
||||||
|
PackageName: "com.example.one",
|
||||||
|
Sha256CertFingerprints: []string{"11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11"},
|
||||||
|
}
|
||||||
|
createOIDCAppWithLinks(t, project.GetId(), ios1, android1)
|
||||||
|
|
||||||
|
ios2 := &application.IOSAppLinkConfig{TeamId: "TEAMBBBBBB", BundleId: "com.example.two"}
|
||||||
|
createOIDCAppWithLinks(t, project.GetId(), ios2, nil)
|
||||||
|
|
||||||
|
android2 := &application.AndroidAppLinkConfig{
|
||||||
|
PackageName: "com.example.two",
|
||||||
|
Sha256CertFingerprints: []string{"22:22:22:22:22:22:22:22:22:22:22:22:22:22:22:22:22:22:22:22:22:22:22:22:22:22:22:22:22:22:22:22"},
|
||||||
|
}
|
||||||
|
createOIDCAppWithLinks(t, project.GetId(), nil, android2)
|
||||||
|
|
||||||
|
// duplicate iOS app id should be deduped in AASA
|
||||||
|
createOIDCAppWithLinks(t, project.GetId(), ios1, nil)
|
||||||
|
|
||||||
|
assertEventuallyJSONUnorderedApps(t, issuer,
|
||||||
|
[]string{
|
||||||
|
"TEAMAAAAAA.com.example.one",
|
||||||
|
"TEAMBBBBBB.com.example.two",
|
||||||
|
},
|
||||||
|
[]any{
|
||||||
|
map[string]any{
|
||||||
|
"relation": []any{"delegate_permission/common.get_login_creds"},
|
||||||
|
"target": map[string]any{
|
||||||
|
"namespace": "android_app",
|
||||||
|
"package_name": "com.example.one",
|
||||||
|
"sha256_cert_fingerprints": []any{android1.Sha256CertFingerprints[0]},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map[string]any{
|
||||||
|
"relation": []any{"delegate_permission/common.get_login_creds"},
|
||||||
|
"target": map[string]any{
|
||||||
|
"namespace": "android_app",
|
||||||
|
"package_name": "com.example.two",
|
||||||
|
"sha256_cert_fingerprints": []any{android2.Sha256CertFingerprints[0]},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createOIDCAppWithLinks(t *testing.T, projectID string, ios *application.IOSAppLinkConfig, android *application.AndroidAppLinkConfig) {
|
||||||
|
t.Helper()
|
||||||
|
_, err := Instance.Client.ApplicationV2.CreateApplication(IAMOwnerCtx, &application.CreateApplicationRequest{
|
||||||
|
ProjectId: projectID,
|
||||||
|
Name: integration.ApplicationName(),
|
||||||
|
ApplicationType: &application.CreateApplicationRequest_OidcConfiguration{
|
||||||
|
OidcConfiguration: &application.CreateOIDCApplicationRequest{
|
||||||
|
RedirectUris: []string{"http://example.com"},
|
||||||
|
ResponseTypes: []application.OIDCResponseType{application.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE},
|
||||||
|
GrantTypes: []application.OIDCGrantType{application.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE},
|
||||||
|
ApplicationType: application.OIDCApplicationType_OIDC_APP_TYPE_NATIVE,
|
||||||
|
AuthMethodType: application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE,
|
||||||
|
Version: application.OIDCVersion_OIDC_VERSION_1_0,
|
||||||
|
AccessTokenType: application.OIDCTokenType_OIDC_TOKEN_TYPE_BEARER,
|
||||||
|
Ios: ios,
|
||||||
|
Android: android,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertEventuallyJSON(t *testing.T, url string, want any) {
|
||||||
|
t.Helper()
|
||||||
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
|
||||||
|
require.EventuallyWithT(t, func(ct *assert.CollectT) {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if !assert.NoError(ct, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_, _ = io.Copy(io.Discard, resp.Body)
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
}()
|
||||||
|
if !assert.Equal(ct, http.StatusOK, resp.StatusCode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Contains(ct, resp.Header.Get("Content-Type"), "application/json")
|
||||||
|
assert.Equal(ct, "public, max-age=300", resp.Header.Get("Cache-Control"))
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if !assert.NoError(ct, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var got any
|
||||||
|
if !assert.NoError(ct, json.Unmarshal(body, &got)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equal(ct, want, got)
|
||||||
|
}, retryDuration, tick)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertEventuallyJSONUnorderedApps(t *testing.T, url string, wantApps []string, wantAssetLinks []any) {
|
||||||
|
t.Helper()
|
||||||
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
|
||||||
|
require.EventuallyWithT(t, func(ct *assert.CollectT) {
|
||||||
|
aasaResp, err := http.Get(url + well_known.AppleAppSiteAssociationPath)
|
||||||
|
if !assert.NoError(ct, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer aasaResp.Body.Close()
|
||||||
|
if !assert.Equal(ct, http.StatusOK, aasaResp.StatusCode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var aasa struct {
|
||||||
|
WebCredentials struct {
|
||||||
|
Apps []string `json:"apps"`
|
||||||
|
} `json:"webcredentials"`
|
||||||
|
}
|
||||||
|
if !assert.NoError(ct, json.NewDecoder(aasaResp.Body).Decode(&aasa)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.ElementsMatch(ct, wantApps, aasa.WebCredentials.Apps)
|
||||||
|
|
||||||
|
assetResp, err := http.Get(url + well_known.AssetLinksPath)
|
||||||
|
if !assert.NoError(ct, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer assetResp.Body.Close()
|
||||||
|
if !assert.Equal(ct, http.StatusOK, assetResp.StatusCode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var got []any
|
||||||
|
if !assert.NoError(ct, json.NewDecoder(assetResp.Body).Decode(&got)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.ElementsMatch(ct, wantAssetLinks, got)
|
||||||
|
}, retryDuration, tick)
|
||||||
|
}
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
package well_known
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/backend/v3/instrumentation/logging"
|
||||||
|
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AppleAppSiteAssociationPath = "/.well-known/apple-app-site-association"
|
||||||
|
AssetLinksPath = "/.well-known/assetlinks.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandlerPrefixes are registered with [api.API.RegisterHandlerPrefixes].
|
||||||
|
var HandlerPrefixes = []string{
|
||||||
|
AppleAppSiteAssociationPath,
|
||||||
|
AssetLinksPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config holds runtime options for the well-known handlers.
|
||||||
|
type Config struct {
|
||||||
|
// AppLinksCacheControlMaxAge sets the Cache-Control max-age for
|
||||||
|
// apple-app-site-association and assetlinks.json responses.
|
||||||
|
// 0 sets Cache-Control: no-store.
|
||||||
|
AppLinksCacheControlMaxAge time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type appLinkQuerier interface {
|
||||||
|
SearchOIDCAppLinkConfigs(ctx context.Context) ([]*query.OIDCAppLinkConfig, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
queries appLinkQuerier
|
||||||
|
cacheControlMaxAge time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(queries appLinkQuerier, config Config) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
queries: queries,
|
||||||
|
cacheControlMaxAge: config.AppLinksCacheControlMaxAge,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.URL.Path {
|
||||||
|
case AppleAppSiteAssociationPath:
|
||||||
|
h.serveAppleAppSiteAssociation(w, r)
|
||||||
|
case AssetLinksPath:
|
||||||
|
h.serveAssetLinks(w, r)
|
||||||
|
default:
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type appleAppSiteAssociation struct {
|
||||||
|
WebCredentials appleWebCredentials `json:"webcredentials"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type appleWebCredentials struct {
|
||||||
|
Apps []string `json:"apps"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type assetLink struct {
|
||||||
|
Relation []string `json:"relation"`
|
||||||
|
Target assetLinkTarget `json:"target"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type assetLinkTarget struct {
|
||||||
|
Namespace string `json:"namespace"`
|
||||||
|
PackageName string `json:"package_name"`
|
||||||
|
SHA256CertFingerprints []string `json:"sha256_cert_fingerprints"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) serveAppleAppSiteAssociation(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||||
|
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
configs, err := h.queries.SearchOIDCAppLinkConfigs(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
logging.Error(r.Context(), "unable to query OIDC app link configs for apple-app-site-association", "err", err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.writeJSON(w, r, buildAppleAppSiteAssociation(configs))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) serveAssetLinks(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||||
|
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
configs, err := h.queries.SearchOIDCAppLinkConfigs(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
logging.Error(r.Context(), "unable to query OIDC app link configs for assetlinks.json", "err", err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.writeJSON(w, r, buildAssetLinks(r.Context(), configs))
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildAppleAppSiteAssociation(configs []*query.OIDCAppLinkConfig) appleAppSiteAssociation {
|
||||||
|
apps := make([]string, 0)
|
||||||
|
seen := make(map[string]struct{})
|
||||||
|
for _, cfg := range configs {
|
||||||
|
if cfg.IOSTeamID == "" || cfg.IOSBundleID == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
appID := cfg.IOSTeamID + "." + cfg.IOSBundleID
|
||||||
|
if _, ok := seen[appID]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[appID] = struct{}{}
|
||||||
|
apps = append(apps, appID)
|
||||||
|
}
|
||||||
|
return appleAppSiteAssociation{
|
||||||
|
WebCredentials: appleWebCredentials{Apps: apps},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildAssetLinks(ctx context.Context, configs []*query.OIDCAppLinkConfig) []assetLink {
|
||||||
|
links := make([]assetLink, 0)
|
||||||
|
for _, cfg := range configs {
|
||||||
|
if cfg.AndroidPackageName == "" || len(cfg.AndroidSHA256CertFingerprints) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
links = append(links, assetLink{
|
||||||
|
Relation: []string{"delegate_permission/common.get_login_creds"},
|
||||||
|
Target: assetLinkTarget{
|
||||||
|
Namespace: "android_app",
|
||||||
|
PackageName: cfg.AndroidPackageName,
|
||||||
|
SHA256CertFingerprints: normalizeSHA256Fingerprints(ctx, cfg.AndroidSHA256CertFingerprints),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizeSHA256Fingerprints returns colon-separated uppercase hex fingerprints
|
||||||
|
// for assetlinks.json. Normalization is applied at serve time only so stored /
|
||||||
|
// client state can keep the format clients sent.
|
||||||
|
func normalizeSHA256Fingerprints(ctx context.Context, fps []string) []string {
|
||||||
|
out := make([]string, len(fps))
|
||||||
|
for i, fp := range fps {
|
||||||
|
out[i] = normalizeSHA256Fingerprint(ctx, fp)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeSHA256Fingerprint(ctx context.Context, fp string) string {
|
||||||
|
if strings.Contains(fp, ":") {
|
||||||
|
return strings.ToUpper(fp) // already 32 valid pairs per API validation; only case may vary
|
||||||
|
}
|
||||||
|
raw, err := hex.DecodeString(fp) // guaranteed 64 hex chars
|
||||||
|
if err != nil || len(raw) != sha256.Size {
|
||||||
|
logging.Warn(ctx, "stored android sha256 fingerprint is not canonical hex; serving as-is",
|
||||||
|
"fingerprint", fp, "err", err)
|
||||||
|
return strings.ToUpper(fp)
|
||||||
|
}
|
||||||
|
parts := make([]string, len(raw))
|
||||||
|
for i, b := range raw {
|
||||||
|
parts[i] = fmt.Sprintf("%02X", b)
|
||||||
|
}
|
||||||
|
return strings.Join(parts, ":")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) writeJSON(w http.ResponseWriter, r *http.Request, v any) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
h.setCacheControl(w)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
if r.Method == http.MethodHead {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(w).Encode(v); err != nil {
|
||||||
|
logging.Error(r.Context(), "unable to encode well-known response", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) setCacheControl(w http.ResponseWriter) {
|
||||||
|
if h.cacheControlMaxAge <= 0 {
|
||||||
|
w.Header().Set(http_util.CacheControl, "no-store")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set(http_util.CacheControl,
|
||||||
|
fmt.Sprintf("public, max-age=%d", int(h.cacheControlMaxAge/time.Second)),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
package well_known
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildAppleAppSiteAssociation(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
configs []*query.OIDCAppLinkConfig
|
||||||
|
wantApps []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
configs: nil,
|
||||||
|
wantApps: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "skips incomplete ios",
|
||||||
|
configs: []*query.OIDCAppLinkConfig{
|
||||||
|
{IOSTeamID: "TEAM"},
|
||||||
|
{IOSBundleID: "com.example"},
|
||||||
|
},
|
||||||
|
wantApps: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aggregates and dedupes",
|
||||||
|
configs: []*query.OIDCAppLinkConfig{
|
||||||
|
{IOSTeamID: "TEAM1", IOSBundleID: "com.one"},
|
||||||
|
{IOSTeamID: "TEAM2", IOSBundleID: "com.two"},
|
||||||
|
{IOSTeamID: "TEAM1", IOSBundleID: "com.one"},
|
||||||
|
{AndroidPackageName: "com.android", AndroidSHA256CertFingerprints: []string{"AA"}},
|
||||||
|
},
|
||||||
|
wantApps: []string{"TEAM1.com.one", "TEAM2.com.two"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
got := buildAppleAppSiteAssociation(tc.configs)
|
||||||
|
assert.Equal(t, tc.wantApps, got.WebCredentials.Apps)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildAssetLinks(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
configs []*query.OIDCAppLinkConfig
|
||||||
|
want []assetLink
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
configs: nil,
|
||||||
|
want: []assetLink{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "skips incomplete android",
|
||||||
|
configs: []*query.OIDCAppLinkConfig{
|
||||||
|
{AndroidPackageName: "com.example"},
|
||||||
|
{AndroidSHA256CertFingerprints: []string{"AA"}},
|
||||||
|
},
|
||||||
|
want: []assetLink{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aggregates and normalizes fingerprints",
|
||||||
|
configs: []*query.OIDCAppLinkConfig{
|
||||||
|
{
|
||||||
|
IOSTeamID: "TEAM",
|
||||||
|
IOSBundleID: "com.ios",
|
||||||
|
AndroidPackageName: "com.one",
|
||||||
|
AndroidSHA256CertFingerprints: []string{"aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
AndroidPackageName: "com.two",
|
||||||
|
AndroidSHA256CertFingerprints: []string{
|
||||||
|
"cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb",
|
||||||
|
"eeff00112233445566778899aabbccddeeff00112233445566778899aabbccdd",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []assetLink{
|
||||||
|
{
|
||||||
|
Relation: []string{"delegate_permission/common.get_login_creds"},
|
||||||
|
Target: assetLinkTarget{
|
||||||
|
Namespace: "android_app",
|
||||||
|
PackageName: "com.one",
|
||||||
|
SHA256CertFingerprints: []string{
|
||||||
|
"AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Relation: []string{"delegate_permission/common.get_login_creds"},
|
||||||
|
Target: assetLinkTarget{
|
||||||
|
Namespace: "android_app",
|
||||||
|
PackageName: "com.two",
|
||||||
|
SHA256CertFingerprints: []string{
|
||||||
|
"CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB",
|
||||||
|
"EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
assert.Equal(t, tc.want, buildAssetLinks(t.Context(), tc.configs))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeSHA256Fingerprint(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
in string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "already canonical",
|
||||||
|
in: "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
want: "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "lowercase with colons",
|
||||||
|
in: "aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
want: "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "lowercase without separators",
|
||||||
|
in: "aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899",
|
||||||
|
want: "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid continuous hex uppercased",
|
||||||
|
in: "aabb",
|
||||||
|
want: "AABB",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "colon form uppercased without reparse",
|
||||||
|
in: "aa:bb",
|
||||||
|
want: "AA:BB",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
assert.Equal(t, tc.want, normalizeSHA256Fingerprint(t.Context(), tc.in))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type stubAppLinkQuerier struct {
|
||||||
|
configs []*query.OIDCAppLinkConfig
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s stubAppLinkQuerier) SearchOIDCAppLinkConfigs(_ context.Context) ([]*query.OIDCAppLinkConfig, error) {
|
||||||
|
return s.configs, s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandlerCacheControl(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
maxAge time.Duration
|
||||||
|
wantHeader string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default max-age",
|
||||||
|
maxAge: 5 * time.Minute,
|
||||||
|
wantHeader: "public, max-age=300",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no-store when zero",
|
||||||
|
maxAge: 0,
|
||||||
|
wantHeader: "no-store",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no-store when negative",
|
||||||
|
maxAge: -time.Minute,
|
||||||
|
wantHeader: "no-store",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
h := NewHandler(stubAppLinkQuerier{}, Config{AppLinksCacheControlMaxAge: tc.maxAge})
|
||||||
|
|
||||||
|
for _, path := range []string{AppleAppSiteAssociationPath, AssetLinksPath} {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, path, nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
h.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
assert.Equal(t, tc.wantHeader, rec.Header().Get(http_util.CacheControl), path)
|
||||||
|
assert.NotContains(t, rec.Header().Get(http_util.CacheControl), "stale-while-revalidate")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandlerHeadOmitsBody(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
h := NewHandler(stubAppLinkQuerier{}, Config{AppLinksCacheControlMaxAge: 5 * time.Minute})
|
||||||
|
for _, path := range []string{AppleAppSiteAssociationPath, AssetLinksPath} {
|
||||||
|
t.Run(path, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
req := httptest.NewRequest(http.MethodHead, path, nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
h.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
assert.Equal(t, "application/json", rec.Header().Get("Content-Type"))
|
||||||
|
assert.Equal(t, "public, max-age=300", rec.Header().Get(http_util.CacheControl))
|
||||||
|
assert.Empty(t, rec.Body.Bytes())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -158,7 +158,10 @@ func TestCommandSide_AddInstanceDomain(t *testing.T) {
|
|||||||
"",
|
"",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
"",
|
"",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectPush(
|
expectPush(
|
||||||
|
|||||||
@@ -125,7 +125,10 @@ func oidcAppEvents(ctx context.Context, orgID, projectID, id, name, clientID str
|
|||||||
"",
|
"",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
"",
|
"",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,7 +471,10 @@ func generatedDomainFilters(instanceID, orgID, projectID, appID, generatedDomain
|
|||||||
"",
|
"",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
"",
|
"",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
),
|
),
|
||||||
expectFilter(
|
expectFilter(
|
||||||
func() eventstore.Event {
|
func() eventstore.Event {
|
||||||
|
|||||||
@@ -20,24 +20,28 @@ import (
|
|||||||
|
|
||||||
type addOIDCApp struct {
|
type addOIDCApp struct {
|
||||||
AddApp
|
AddApp
|
||||||
Version domain.OIDCVersion
|
Version domain.OIDCVersion
|
||||||
RedirectUris []string
|
RedirectUris []string
|
||||||
ResponseTypes []domain.OIDCResponseType
|
ResponseTypes []domain.OIDCResponseType
|
||||||
GrantTypes []domain.OIDCGrantType
|
GrantTypes []domain.OIDCGrantType
|
||||||
ApplicationType domain.OIDCApplicationType
|
ApplicationType domain.OIDCApplicationType
|
||||||
AuthMethodType domain.OIDCAuthMethodType
|
AuthMethodType domain.OIDCAuthMethodType
|
||||||
PostLogoutRedirectUris []string
|
PostLogoutRedirectUris []string
|
||||||
DevMode bool
|
DevMode bool
|
||||||
AccessTokenType domain.OIDCTokenType
|
AccessTokenType domain.OIDCTokenType
|
||||||
AccessTokenRoleAssertion bool
|
AccessTokenRoleAssertion bool
|
||||||
IDTokenRoleAssertion bool
|
IDTokenRoleAssertion bool
|
||||||
IDTokenUserinfoAssertion bool
|
IDTokenUserinfoAssertion bool
|
||||||
ClockSkew time.Duration
|
ClockSkew time.Duration
|
||||||
AdditionalOrigins []string
|
AdditionalOrigins []string
|
||||||
SkipSuccessPageForNativeApp bool
|
SkipSuccessPageForNativeApp bool
|
||||||
BackChannelLogoutURI string
|
BackChannelLogoutURI string
|
||||||
LoginVersion domain.LoginVersion
|
LoginVersion domain.LoginVersion
|
||||||
LoginBaseURI string
|
LoginBaseURI string
|
||||||
|
IOSTeamID string
|
||||||
|
IOSBundleID string
|
||||||
|
AndroidPackageName string
|
||||||
|
AndroidSHA256CertFingerprints []string
|
||||||
|
|
||||||
ClientID string
|
ClientID string
|
||||||
ClientSecret string
|
ClientSecret string
|
||||||
@@ -118,6 +122,10 @@ func (c *Commands) AddOIDCAppCommand(app *addOIDCApp) preparation.Validation {
|
|||||||
app.BackChannelLogoutURI,
|
app.BackChannelLogoutURI,
|
||||||
app.LoginVersion,
|
app.LoginVersion,
|
||||||
app.LoginBaseURI,
|
app.LoginBaseURI,
|
||||||
|
app.IOSTeamID,
|
||||||
|
app.IOSBundleID,
|
||||||
|
app.AndroidPackageName,
|
||||||
|
app.AndroidSHA256CertFingerprints,
|
||||||
),
|
),
|
||||||
}, nil
|
}, nil
|
||||||
}, nil
|
}, nil
|
||||||
@@ -251,6 +259,10 @@ func (c *Commands) pushOIDCApplication(ctx context.Context, addedApplication *OI
|
|||||||
backchannelLogoutURI,
|
backchannelLogoutURI,
|
||||||
gu.Value(oidcApp.LoginVersion),
|
gu.Value(oidcApp.LoginVersion),
|
||||||
strings.TrimSpace(gu.Value(oidcApp.LoginBaseURI)),
|
strings.TrimSpace(gu.Value(oidcApp.LoginBaseURI)),
|
||||||
|
strings.TrimSpace(gu.Value(oidcApp.IOSTeamID)),
|
||||||
|
strings.TrimSpace(gu.Value(oidcApp.IOSBundleID)),
|
||||||
|
strings.TrimSpace(gu.Value(oidcApp.AndroidPackageName)),
|
||||||
|
trimStringSliceWhiteSpaces(oidcApp.AndroidSHA256CertFingerprints),
|
||||||
))
|
))
|
||||||
|
|
||||||
events = append(events, extraEvents...)
|
events = append(events, extraEvents...)
|
||||||
@@ -340,7 +352,7 @@ func (c *Commands) UpdateOIDCApplication(ctx context.Context, oidc *domain.OIDCA
|
|||||||
// UpdateDynamicOIDCClient). It reports whether anything actually changed.
|
// UpdateDynamicOIDCClient). It reports whether anything actually changed.
|
||||||
func (c *Commands) oidcApplicationChangeEvent(ctx context.Context, existingOIDC *OIDCApplicationWriteModel, oidc *domain.OIDCApp) (*project_repo.OIDCConfigChangedEvent, bool, error) {
|
func (c *Commands) oidcApplicationChangeEvent(ctx context.Context, existingOIDC *OIDCApplicationWriteModel, oidc *domain.OIDCApp) (*project_repo.OIDCConfigChangedEvent, bool, error) {
|
||||||
projectAgg := ProjectAggregateFromWriteModelWithCTX(ctx, &existingOIDC.WriteModel)
|
projectAgg := ProjectAggregateFromWriteModelWithCTX(ctx, &existingOIDC.WriteModel)
|
||||||
var backChannelLogout, loginBaseURI *string
|
var backChannelLogout, loginBaseURI, iosTeamID, iosBundleID, androidPackageName *string
|
||||||
if oidc.BackChannelLogoutURI != nil {
|
if oidc.BackChannelLogoutURI != nil {
|
||||||
bcl, err := c.validateBackchannelLogoutURI(oidc)
|
bcl, err := c.validateBackchannelLogoutURI(oidc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -352,6 +364,15 @@ func (c *Commands) oidcApplicationChangeEvent(ctx context.Context, existingOIDC
|
|||||||
if oidc.LoginBaseURI != nil {
|
if oidc.LoginBaseURI != nil {
|
||||||
loginBaseURI = gu.Ptr(strings.TrimSpace(*oidc.LoginBaseURI))
|
loginBaseURI = gu.Ptr(strings.TrimSpace(*oidc.LoginBaseURI))
|
||||||
}
|
}
|
||||||
|
if oidc.IOSTeamID != nil {
|
||||||
|
iosTeamID = gu.Ptr(strings.TrimSpace(*oidc.IOSTeamID))
|
||||||
|
}
|
||||||
|
if oidc.IOSBundleID != nil {
|
||||||
|
iosBundleID = gu.Ptr(strings.TrimSpace(*oidc.IOSBundleID))
|
||||||
|
}
|
||||||
|
if oidc.AndroidPackageName != nil {
|
||||||
|
androidPackageName = gu.Ptr(strings.TrimSpace(*oidc.AndroidPackageName))
|
||||||
|
}
|
||||||
|
|
||||||
return existingOIDC.NewChangedEvent(
|
return existingOIDC.NewChangedEvent(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -375,6 +396,10 @@ func (c *Commands) oidcApplicationChangeEvent(ctx context.Context, existingOIDC
|
|||||||
backChannelLogout,
|
backChannelLogout,
|
||||||
oidc.LoginVersion,
|
oidc.LoginVersion,
|
||||||
loginBaseURI,
|
loginBaseURI,
|
||||||
|
iosTeamID,
|
||||||
|
iosBundleID,
|
||||||
|
androidPackageName,
|
||||||
|
trimStringSliceWhiteSpaces(oidc.AndroidSHA256CertFingerprints),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -234,7 +234,10 @@ func TestCommandSide_AddDynamicOIDCClient(t *testing.T) {
|
|||||||
"",
|
"",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
"",
|
"",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
// The registration access token (RFC 7592 §3) is persisted in the same
|
// The registration access token (RFC 7592 §3) is persisted in the same
|
||||||
// push as the application, so a registered client is never left
|
// push as the application, so a registered client is never left
|
||||||
// unmanageable.
|
// unmanageable.
|
||||||
@@ -336,7 +339,10 @@ func TestCommandSide_AddDynamicOIDCClient(t *testing.T) {
|
|||||||
"",
|
"",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
"",
|
"",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
project.NewOIDCConfigRegistrationTokenChangedEvent(context.Background(),
|
project.NewOIDCConfigRegistrationTokenChangedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
"app1",
|
"app1",
|
||||||
@@ -581,7 +587,11 @@ func TestCommandSide_UpdateDynamicOIDCClient(t *testing.T) {
|
|||||||
domain.OIDCApplicationTypeWeb, domain.OIDCAuthMethodTypeNone,
|
domain.OIDCApplicationTypeWeb, domain.OIDCAuthMethodTypeNone,
|
||||||
[]string{}, false, domain.OIDCTokenTypeBearer,
|
[]string{}, false, domain.OIDCTokenTypeBearer,
|
||||||
false, false, false, 0, []string{}, false, "",
|
false, false, false, 0, []string{}, false, "",
|
||||||
domain.LoginVersionUnspecified, "")),
|
domain.LoginVersionUnspecified, "",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sameMetadata := &domain.OIDCApp{
|
sameMetadata := &domain.OIDCApp{
|
||||||
|
|||||||
@@ -14,32 +14,36 @@ import (
|
|||||||
type OIDCApplicationWriteModel struct {
|
type OIDCApplicationWriteModel struct {
|
||||||
eventstore.WriteModel
|
eventstore.WriteModel
|
||||||
|
|
||||||
AppID string
|
AppID string
|
||||||
AppName string
|
AppName string
|
||||||
ClientID string
|
ClientID string
|
||||||
HashedSecret string
|
HashedSecret string
|
||||||
ClientSecretString string
|
ClientSecretString string
|
||||||
RedirectUris []string
|
RedirectUris []string
|
||||||
ResponseTypes []domain.OIDCResponseType
|
ResponseTypes []domain.OIDCResponseType
|
||||||
GrantTypes []domain.OIDCGrantType
|
GrantTypes []domain.OIDCGrantType
|
||||||
ApplicationType domain.OIDCApplicationType
|
ApplicationType domain.OIDCApplicationType
|
||||||
AuthMethodType domain.OIDCAuthMethodType
|
AuthMethodType domain.OIDCAuthMethodType
|
||||||
PostLogoutRedirectUris []string
|
PostLogoutRedirectUris []string
|
||||||
OIDCVersion domain.OIDCVersion
|
OIDCVersion domain.OIDCVersion
|
||||||
Compliance *domain.Compliance
|
Compliance *domain.Compliance
|
||||||
DevMode bool
|
DevMode bool
|
||||||
AccessTokenType domain.OIDCTokenType
|
AccessTokenType domain.OIDCTokenType
|
||||||
AccessTokenRoleAssertion bool
|
AccessTokenRoleAssertion bool
|
||||||
IDTokenRoleAssertion bool
|
IDTokenRoleAssertion bool
|
||||||
IDTokenUserinfoAssertion bool
|
IDTokenUserinfoAssertion bool
|
||||||
ClockSkew time.Duration
|
ClockSkew time.Duration
|
||||||
State domain.AppState
|
State domain.AppState
|
||||||
AdditionalOrigins []string
|
AdditionalOrigins []string
|
||||||
SkipNativeAppSuccessPage bool
|
SkipNativeAppSuccessPage bool
|
||||||
BackChannelLogoutURI string
|
BackChannelLogoutURI string
|
||||||
LoginVersion domain.LoginVersion
|
LoginVersion domain.LoginVersion
|
||||||
LoginBaseURI string
|
LoginBaseURI string
|
||||||
oidc bool
|
IOSTeamID string
|
||||||
|
IOSBundleID string
|
||||||
|
AndroidPackageName string
|
||||||
|
AndroidSHA256CertFingerprints []string
|
||||||
|
oidc bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOIDCApplicationWriteModelWithAppID(projectID, appID, resourceOwner string) *OIDCApplicationWriteModel {
|
func NewOIDCApplicationWriteModelWithAppID(projectID, appID, resourceOwner string) *OIDCApplicationWriteModel {
|
||||||
@@ -140,6 +144,10 @@ func (wm *OIDCApplicationWriteModel) Reduce() error {
|
|||||||
wm.BackChannelLogoutURI = ""
|
wm.BackChannelLogoutURI = ""
|
||||||
wm.LoginVersion = domain.LoginVersionUnspecified
|
wm.LoginVersion = domain.LoginVersionUnspecified
|
||||||
wm.LoginBaseURI = ""
|
wm.LoginBaseURI = ""
|
||||||
|
wm.IOSTeamID = ""
|
||||||
|
wm.IOSBundleID = ""
|
||||||
|
wm.AndroidPackageName = ""
|
||||||
|
wm.AndroidSHA256CertFingerprints = nil
|
||||||
wm.oidc = false
|
wm.oidc = false
|
||||||
wm.AppName = e.Name
|
wm.AppName = e.Name
|
||||||
wm.State = domain.AppStateActive
|
wm.State = domain.AppStateActive
|
||||||
@@ -189,6 +197,10 @@ func (wm *OIDCApplicationWriteModel) Reduce() error {
|
|||||||
wm.BackChannelLogoutURI = ""
|
wm.BackChannelLogoutURI = ""
|
||||||
wm.LoginVersion = domain.LoginVersionUnspecified
|
wm.LoginVersion = domain.LoginVersionUnspecified
|
||||||
wm.LoginBaseURI = ""
|
wm.LoginBaseURI = ""
|
||||||
|
wm.IOSTeamID = ""
|
||||||
|
wm.IOSBundleID = ""
|
||||||
|
wm.AndroidPackageName = ""
|
||||||
|
wm.AndroidSHA256CertFingerprints = nil
|
||||||
wm.oidc = false
|
wm.oidc = false
|
||||||
wm.State = domain.AppStateRemoved
|
wm.State = domain.AppStateRemoved
|
||||||
}
|
}
|
||||||
@@ -218,6 +230,10 @@ func (wm *OIDCApplicationWriteModel) appendAddOIDCEvent(e *project.OIDCConfigAdd
|
|||||||
wm.BackChannelLogoutURI = e.BackChannelLogoutURI
|
wm.BackChannelLogoutURI = e.BackChannelLogoutURI
|
||||||
wm.LoginVersion = e.LoginVersion
|
wm.LoginVersion = e.LoginVersion
|
||||||
wm.LoginBaseURI = e.LoginBaseURI
|
wm.LoginBaseURI = e.LoginBaseURI
|
||||||
|
wm.IOSTeamID = e.IOSTeamID
|
||||||
|
wm.IOSBundleID = e.IOSBundleID
|
||||||
|
wm.AndroidPackageName = e.AndroidPackageName
|
||||||
|
wm.AndroidSHA256CertFingerprints = e.AndroidSHA256CertFingerprints
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wm *OIDCApplicationWriteModel) appendChangeOIDCEvent(e *project.OIDCConfigChangedEvent) {
|
func (wm *OIDCApplicationWriteModel) appendChangeOIDCEvent(e *project.OIDCConfigChangedEvent) {
|
||||||
@@ -275,6 +291,18 @@ func (wm *OIDCApplicationWriteModel) appendChangeOIDCEvent(e *project.OIDCConfig
|
|||||||
if e.LoginBaseURI != nil {
|
if e.LoginBaseURI != nil {
|
||||||
wm.LoginBaseURI = *e.LoginBaseURI
|
wm.LoginBaseURI = *e.LoginBaseURI
|
||||||
}
|
}
|
||||||
|
if e.IOSTeamID != nil {
|
||||||
|
wm.IOSTeamID = *e.IOSTeamID
|
||||||
|
}
|
||||||
|
if e.IOSBundleID != nil {
|
||||||
|
wm.IOSBundleID = *e.IOSBundleID
|
||||||
|
}
|
||||||
|
if e.AndroidPackageName != nil {
|
||||||
|
wm.AndroidPackageName = *e.AndroidPackageName
|
||||||
|
}
|
||||||
|
if e.AndroidSHA256CertFingerprints != nil {
|
||||||
|
wm.AndroidSHA256CertFingerprints = *e.AndroidSHA256CertFingerprints
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wm *OIDCApplicationWriteModel) Query() *eventstore.SearchQueryBuilder {
|
func (wm *OIDCApplicationWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||||
@@ -320,6 +348,10 @@ func (wm *OIDCApplicationWriteModel) NewChangedEvent(
|
|||||||
backChannelLogoutURI *string,
|
backChannelLogoutURI *string,
|
||||||
loginVersion *domain.LoginVersion,
|
loginVersion *domain.LoginVersion,
|
||||||
loginBaseURI *string,
|
loginBaseURI *string,
|
||||||
|
iosTeamID *string,
|
||||||
|
iosBundleID *string,
|
||||||
|
androidPackageName *string,
|
||||||
|
androidSHA256CertFingerprints []string,
|
||||||
) (*project.OIDCConfigChangedEvent, bool, error) {
|
) (*project.OIDCConfigChangedEvent, bool, error) {
|
||||||
changes := make([]project.OIDCConfigChanges, 0)
|
changes := make([]project.OIDCConfigChanges, 0)
|
||||||
var err error
|
var err error
|
||||||
@@ -378,6 +410,18 @@ func (wm *OIDCApplicationWriteModel) NewChangedEvent(
|
|||||||
if loginBaseURI != nil && wm.LoginBaseURI != *loginBaseURI {
|
if loginBaseURI != nil && wm.LoginBaseURI != *loginBaseURI {
|
||||||
changes = append(changes, project.ChangeOIDCLoginBaseURI(*loginBaseURI))
|
changes = append(changes, project.ChangeOIDCLoginBaseURI(*loginBaseURI))
|
||||||
}
|
}
|
||||||
|
if iosTeamID != nil && wm.IOSTeamID != *iosTeamID {
|
||||||
|
changes = append(changes, project.ChangeIOSTeamID(*iosTeamID))
|
||||||
|
}
|
||||||
|
if iosBundleID != nil && wm.IOSBundleID != *iosBundleID {
|
||||||
|
changes = append(changes, project.ChangeIOSBundleID(*iosBundleID))
|
||||||
|
}
|
||||||
|
if androidPackageName != nil && wm.AndroidPackageName != *androidPackageName {
|
||||||
|
changes = append(changes, project.ChangeAndroidPackageName(*androidPackageName))
|
||||||
|
}
|
||||||
|
if androidSHA256CertFingerprints != nil && !slices.Equal(wm.AndroidSHA256CertFingerprints, androidSHA256CertFingerprints) {
|
||||||
|
changes = append(changes, project.ChangeAndroidSHA256CertFingerprints(androidSHA256CertFingerprints))
|
||||||
|
}
|
||||||
|
|
||||||
if len(changes) == 0 {
|
if len(changes) == 0 {
|
||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
|
"github.com/zitadel/zitadel/internal/repository/project"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOIDCApplicationWriteModel_NewChangedEvent_AppLinks(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
agg := &project.NewAggregate("project-id", "org-id").Aggregate
|
||||||
|
base := func() *OIDCApplicationWriteModel {
|
||||||
|
return &OIDCApplicationWriteModel{
|
||||||
|
WriteModel: eventstore.WriteModel{
|
||||||
|
AggregateID: "project-id",
|
||||||
|
ResourceOwner: "org-id",
|
||||||
|
},
|
||||||
|
AppID: "app-id",
|
||||||
|
IOSTeamID: "OLDTEAM",
|
||||||
|
IOSBundleID: "com.old.app",
|
||||||
|
AndroidPackageName: "com.old.app",
|
||||||
|
AndroidSHA256CertFingerprints: []string{"AA:AA"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("omit keeps existing", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
wm := base()
|
||||||
|
event, hasChanged, err := wm.NewChangedEvent(
|
||||||
|
context.Background(), agg, "app-id",
|
||||||
|
nil, nil, nil, nil, nil, nil, nil, nil,
|
||||||
|
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
||||||
|
nil, nil, nil, nil,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, hasChanged)
|
||||||
|
assert.Nil(t, event)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("set changes fields", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
wm := base()
|
||||||
|
event, hasChanged, err := wm.NewChangedEvent(
|
||||||
|
context.Background(), agg, "app-id",
|
||||||
|
nil, nil, nil, nil, nil, nil, nil, nil,
|
||||||
|
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
||||||
|
gu.Ptr("NEWTEAM"),
|
||||||
|
gu.Ptr("com.new.app"),
|
||||||
|
gu.Ptr("com.new.app"),
|
||||||
|
[]string{"BB:BB"},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, hasChanged)
|
||||||
|
require.NotNil(t, event)
|
||||||
|
assert.Equal(t, gu.Ptr("NEWTEAM"), event.IOSTeamID)
|
||||||
|
assert.Equal(t, gu.Ptr("com.new.app"), event.IOSBundleID)
|
||||||
|
assert.Equal(t, gu.Ptr("com.new.app"), event.AndroidPackageName)
|
||||||
|
assert.Equal(t, &[]string{"BB:BB"}, event.AndroidSHA256CertFingerprints)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("clear with empty values", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
wm := base()
|
||||||
|
event, hasChanged, err := wm.NewChangedEvent(
|
||||||
|
context.Background(), agg, "app-id",
|
||||||
|
nil, nil, nil, nil, nil, nil, nil, nil,
|
||||||
|
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
||||||
|
gu.Ptr(""),
|
||||||
|
gu.Ptr(""),
|
||||||
|
gu.Ptr(""),
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, hasChanged)
|
||||||
|
require.NotNil(t, event)
|
||||||
|
assert.Equal(t, gu.Ptr(""), event.IOSTeamID)
|
||||||
|
assert.Equal(t, gu.Ptr(""), event.IOSBundleID)
|
||||||
|
assert.Equal(t, gu.Ptr(""), event.AndroidPackageName)
|
||||||
|
assert.Equal(t, &[]string{}, event.AndroidSHA256CertFingerprints)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -176,7 +176,10 @@ func TestAddOIDCApp(t *testing.T) {
|
|||||||
"",
|
"",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
"",
|
"",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -244,7 +247,10 @@ func TestAddOIDCApp(t *testing.T) {
|
|||||||
"",
|
"",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
"",
|
"",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -312,7 +318,10 @@ func TestAddOIDCApp(t *testing.T) {
|
|||||||
"",
|
"",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
"",
|
"",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -380,7 +389,10 @@ func TestAddOIDCApp(t *testing.T) {
|
|||||||
"",
|
"",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
"",
|
"",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -534,7 +546,10 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
"https://test.ch/backchannel",
|
"https://test.ch/backchannel",
|
||||||
domain.LoginVersion2,
|
domain.LoginVersion2,
|
||||||
"https://login.test.ch",
|
"https://login.test.ch",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "app1", "client1"),
|
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "app1", "client1"),
|
||||||
@@ -643,7 +658,10 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
"https://test.ch/backchannel",
|
"https://test.ch/backchannel",
|
||||||
domain.LoginVersion2,
|
domain.LoginVersion2,
|
||||||
"https://login.test.ch",
|
"https://login.test.ch",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "client1"),
|
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "client1"),
|
||||||
@@ -753,7 +771,10 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
"https://test.ch/backchannel",
|
"https://test.ch/backchannel",
|
||||||
domain.LoginVersion2,
|
domain.LoginVersion2,
|
||||||
"https://login.test.ch",
|
"https://login.test.ch",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "app1", "client1"),
|
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "app1", "client1"),
|
||||||
@@ -1072,7 +1093,10 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
"https://test.ch/backchannel",
|
"https://test.ch/backchannel",
|
||||||
domain.LoginVersion2,
|
domain.LoginVersion2,
|
||||||
"https://login.test.ch",
|
"https://login.test.ch",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
@@ -1147,7 +1171,10 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
"https://test.ch/backchannel",
|
"https://test.ch/backchannel",
|
||||||
domain.LoginVersion2,
|
domain.LoginVersion2,
|
||||||
"https://login.test.ch",
|
"https://login.test.ch",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
@@ -1222,7 +1249,10 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
"https://test.ch/backchannel",
|
"https://test.ch/backchannel",
|
||||||
domain.LoginVersion1,
|
domain.LoginVersion1,
|
||||||
"",
|
"",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
@@ -1316,7 +1346,10 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
"",
|
"",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
"",
|
"",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
@@ -1376,7 +1409,10 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
"",
|
"",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
"",
|
"",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
@@ -1531,7 +1567,10 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
|
|||||||
"",
|
"",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
"",
|
"",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectPush(
|
expectPush(
|
||||||
|
|||||||
@@ -118,7 +118,10 @@ func TestCommandSide_ChangeApplicationSecret(t *testing.T) {
|
|||||||
"",
|
"",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
"",
|
"",
|
||||||
),
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nil),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectPush(
|
expectPush(
|
||||||
|
|||||||
@@ -29,32 +29,45 @@ func projectGrantWriteModelToProjectGrant(writeModel *ProjectGrantWriteModel) *d
|
|||||||
|
|
||||||
func oidcWriteModelToOIDCConfig(writeModel *OIDCApplicationWriteModel) *domain.OIDCApp {
|
func oidcWriteModelToOIDCConfig(writeModel *OIDCApplicationWriteModel) *domain.OIDCApp {
|
||||||
return &domain.OIDCApp{
|
return &domain.OIDCApp{
|
||||||
ObjectRoot: writeModelToObjectRoot(writeModel.WriteModel),
|
ObjectRoot: writeModelToObjectRoot(writeModel.WriteModel),
|
||||||
AppID: writeModel.AppID,
|
AppID: writeModel.AppID,
|
||||||
AppName: writeModel.AppName,
|
AppName: writeModel.AppName,
|
||||||
State: writeModel.State,
|
State: writeModel.State,
|
||||||
ClientID: writeModel.ClientID,
|
ClientID: writeModel.ClientID,
|
||||||
RedirectUris: writeModel.RedirectUris,
|
RedirectUris: writeModel.RedirectUris,
|
||||||
ResponseTypes: writeModel.ResponseTypes,
|
ResponseTypes: writeModel.ResponseTypes,
|
||||||
GrantTypes: writeModel.GrantTypes,
|
GrantTypes: writeModel.GrantTypes,
|
||||||
ApplicationType: gu.Ptr(writeModel.ApplicationType),
|
ApplicationType: gu.Ptr(writeModel.ApplicationType),
|
||||||
AuthMethodType: gu.Ptr(writeModel.AuthMethodType),
|
AuthMethodType: gu.Ptr(writeModel.AuthMethodType),
|
||||||
PostLogoutRedirectUris: writeModel.PostLogoutRedirectUris,
|
PostLogoutRedirectUris: writeModel.PostLogoutRedirectUris,
|
||||||
OIDCVersion: gu.Ptr(writeModel.OIDCVersion),
|
OIDCVersion: gu.Ptr(writeModel.OIDCVersion),
|
||||||
DevMode: gu.Ptr(writeModel.DevMode),
|
DevMode: gu.Ptr(writeModel.DevMode),
|
||||||
AccessTokenType: gu.Ptr(writeModel.AccessTokenType),
|
AccessTokenType: gu.Ptr(writeModel.AccessTokenType),
|
||||||
AccessTokenRoleAssertion: gu.Ptr(writeModel.AccessTokenRoleAssertion),
|
AccessTokenRoleAssertion: gu.Ptr(writeModel.AccessTokenRoleAssertion),
|
||||||
IDTokenRoleAssertion: gu.Ptr(writeModel.IDTokenRoleAssertion),
|
IDTokenRoleAssertion: gu.Ptr(writeModel.IDTokenRoleAssertion),
|
||||||
IDTokenUserinfoAssertion: gu.Ptr(writeModel.IDTokenUserinfoAssertion),
|
IDTokenUserinfoAssertion: gu.Ptr(writeModel.IDTokenUserinfoAssertion),
|
||||||
ClockSkew: gu.Ptr(writeModel.ClockSkew),
|
ClockSkew: gu.Ptr(writeModel.ClockSkew),
|
||||||
AdditionalOrigins: writeModel.AdditionalOrigins,
|
AdditionalOrigins: writeModel.AdditionalOrigins,
|
||||||
SkipNativeAppSuccessPage: gu.Ptr(writeModel.SkipNativeAppSuccessPage),
|
SkipNativeAppSuccessPage: gu.Ptr(writeModel.SkipNativeAppSuccessPage),
|
||||||
BackChannelLogoutURI: gu.Ptr(writeModel.BackChannelLogoutURI),
|
BackChannelLogoutURI: gu.Ptr(writeModel.BackChannelLogoutURI),
|
||||||
LoginVersion: gu.Ptr(writeModel.LoginVersion),
|
LoginVersion: gu.Ptr(writeModel.LoginVersion),
|
||||||
LoginBaseURI: gu.Ptr(writeModel.LoginBaseURI),
|
LoginBaseURI: gu.Ptr(writeModel.LoginBaseURI),
|
||||||
|
IOSTeamID: emptyStringPtr(writeModel.IOSTeamID),
|
||||||
|
IOSBundleID: emptyStringPtr(writeModel.IOSBundleID),
|
||||||
|
AndroidPackageName: emptyStringPtr(writeModel.AndroidPackageName),
|
||||||
|
AndroidSHA256CertFingerprints: writeModel.AndroidSHA256CertFingerprints,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// emptyStringPtr returns nil for empty strings so unset app-link fields stay
|
||||||
|
// distinguishable from explicitly set values in equality checks / APIs.
|
||||||
|
func emptyStringPtr(s string) *string {
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return gu.Ptr(s)
|
||||||
|
}
|
||||||
|
|
||||||
func samlWriteModelToSAMLConfig(writeModel *SAMLApplicationWriteModel) *domain.SAMLApp {
|
func samlWriteModelToSAMLConfig(writeModel *SAMLApplicationWriteModel) *domain.SAMLApp {
|
||||||
return &domain.SAMLApp{
|
return &domain.SAMLApp{
|
||||||
ObjectRoot: writeModelToObjectRoot(writeModel.WriteModel),
|
ObjectRoot: writeModelToObjectRoot(writeModel.WriteModel),
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
|
||||||
http_util "github.com/zitadel/zitadel/internal/api/http"
|
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
)
|
)
|
||||||
@@ -49,6 +51,14 @@ type OIDCApp struct {
|
|||||||
BackChannelLogoutURI *string
|
BackChannelLogoutURI *string
|
||||||
LoginVersion *LoginVersion
|
LoginVersion *LoginVersion
|
||||||
LoginBaseURI *string
|
LoginBaseURI *string
|
||||||
|
// IOSTeamID and IOSBundleID are Associated Domains / passkey trust fields.
|
||||||
|
// Both must be set together, or both empty. Composed as TEAMID.BUNDLEID in AASA.
|
||||||
|
IOSTeamID *string
|
||||||
|
IOSBundleID *string
|
||||||
|
// AndroidPackageName and AndroidSHA256CertFingerprints are Digital Asset Links /
|
||||||
|
// passkey trust fields. Package name is required when fingerprints are non-empty.
|
||||||
|
AndroidPackageName *string
|
||||||
|
AndroidSHA256CertFingerprints []string
|
||||||
|
|
||||||
State AppState
|
State AppState
|
||||||
}
|
}
|
||||||
@@ -138,7 +148,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (a *OIDCApp) IsValid() bool {
|
func (a *OIDCApp) IsValid() bool {
|
||||||
if (a.ClockSkew != nil && (*a.ClockSkew > time.Second*5 || *a.ClockSkew < time.Second*0)) || !a.OriginsValid() {
|
if (a.ClockSkew != nil && (*a.ClockSkew > time.Second*5 || *a.ClockSkew < time.Second*0)) || !a.OriginsValid() || !a.AppLinkConfigValid() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
grantTypes := a.getRequiredGrantTypes()
|
grantTypes := a.getRequiredGrantTypes()
|
||||||
@@ -154,6 +164,19 @@ func (a *OIDCApp) IsValid() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AppLinkConfigValid checks sibling rules for iOS/Android association fields.
|
||||||
|
// Nil and blank (after trim) are treated the same: both iOS fields must be set or
|
||||||
|
// both empty; Android package name is required whenever fingerprints are present.
|
||||||
|
func (a *OIDCApp) AppLinkConfigValid() bool {
|
||||||
|
teamID := strings.TrimSpace(gu.Value(a.IOSTeamID))
|
||||||
|
bundleID := strings.TrimSpace(gu.Value(a.IOSBundleID))
|
||||||
|
if (teamID == "") != (bundleID == "") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
packageName := strings.TrimSpace(gu.Value(a.AndroidPackageName))
|
||||||
|
return len(a.AndroidSHA256CertFingerprints) == 0 || packageName != ""
|
||||||
|
}
|
||||||
|
|
||||||
func (a *OIDCApp) OriginsValid() bool {
|
func (a *OIDCApp) OriginsValid() bool {
|
||||||
for _, origin := range a.AdditionalOrigins {
|
for _, origin := range a.AdditionalOrigins {
|
||||||
if !http_util.IsOrigin(strings.TrimSpace(origin)) {
|
if !http_util.IsOrigin(strings.TrimSpace(origin)) {
|
||||||
|
|||||||
@@ -669,3 +669,84 @@ func TestOIDCOriginAllowList(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOIDCApp_AppLinkConfigValid(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
app *OIDCApp
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty config",
|
||||||
|
app: &OIDCApp{},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ios both set",
|
||||||
|
app: &OIDCApp{
|
||||||
|
IOSTeamID: gu.Ptr("TEAMID"),
|
||||||
|
IOSBundleID: gu.Ptr("com.example.app"),
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ios team only",
|
||||||
|
app: &OIDCApp{
|
||||||
|
IOSTeamID: gu.Ptr("TEAMID"),
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ios bundle only",
|
||||||
|
app: &OIDCApp{
|
||||||
|
IOSBundleID: gu.Ptr("com.example.app"),
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ios both empty strings",
|
||||||
|
app: &OIDCApp{
|
||||||
|
IOSTeamID: gu.Ptr(""),
|
||||||
|
IOSBundleID: gu.Ptr(""),
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "android package only",
|
||||||
|
app: &OIDCApp{
|
||||||
|
AndroidPackageName: gu.Ptr("com.example.app"),
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "android package and fingerprints",
|
||||||
|
app: &OIDCApp{
|
||||||
|
AndroidPackageName: gu.Ptr("com.example.app"),
|
||||||
|
AndroidSHA256CertFingerprints: []string{"AA:BB"},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "android fingerprints without package",
|
||||||
|
app: &OIDCApp{
|
||||||
|
AndroidSHA256CertFingerprints: []string{"AA:BB"},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "android fingerprints with empty package",
|
||||||
|
app: &OIDCApp{
|
||||||
|
AndroidPackageName: gu.Ptr(" "),
|
||||||
|
AndroidSHA256CertFingerprints: []string{"AA:BB"},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := tt.app.AppLinkConfigValid(); got != tt.want {
|
||||||
|
t.Errorf("AppLinkConfigValid() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+111
-59
@@ -43,27 +43,31 @@ type App struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OIDCApp struct {
|
type OIDCApp struct {
|
||||||
RedirectURIs database.TextArray[string]
|
RedirectURIs database.TextArray[string]
|
||||||
ResponseTypes database.NumberArray[domain.OIDCResponseType]
|
ResponseTypes database.NumberArray[domain.OIDCResponseType]
|
||||||
GrantTypes database.NumberArray[domain.OIDCGrantType]
|
GrantTypes database.NumberArray[domain.OIDCGrantType]
|
||||||
AppType domain.OIDCApplicationType
|
AppType domain.OIDCApplicationType
|
||||||
ClientID string
|
ClientID string
|
||||||
AuthMethodType domain.OIDCAuthMethodType
|
AuthMethodType domain.OIDCAuthMethodType
|
||||||
PostLogoutRedirectURIs database.TextArray[string]
|
PostLogoutRedirectURIs database.TextArray[string]
|
||||||
Version domain.OIDCVersion
|
Version domain.OIDCVersion
|
||||||
ComplianceProblems database.TextArray[string]
|
ComplianceProblems database.TextArray[string]
|
||||||
IsDevMode bool
|
IsDevMode bool
|
||||||
AccessTokenType domain.OIDCTokenType
|
AccessTokenType domain.OIDCTokenType
|
||||||
AssertAccessTokenRole bool
|
AssertAccessTokenRole bool
|
||||||
AssertIDTokenRole bool
|
AssertIDTokenRole bool
|
||||||
AssertIDTokenUserinfo bool
|
AssertIDTokenUserinfo bool
|
||||||
ClockSkew time.Duration
|
ClockSkew time.Duration
|
||||||
AdditionalOrigins database.TextArray[string]
|
AdditionalOrigins database.TextArray[string]
|
||||||
AllowedOrigins database.TextArray[string]
|
AllowedOrigins database.TextArray[string]
|
||||||
SkipNativeAppSuccessPage bool
|
SkipNativeAppSuccessPage bool
|
||||||
BackChannelLogoutURI string
|
BackChannelLogoutURI string
|
||||||
LoginVersion domain.LoginVersion
|
LoginVersion domain.LoginVersion
|
||||||
LoginBaseURI *string
|
LoginBaseURI *string
|
||||||
|
IOSTeamID string
|
||||||
|
IOSBundleID string
|
||||||
|
AndroidPackageName string
|
||||||
|
AndroidSHA256CertFingerprints database.TextArray[string]
|
||||||
}
|
}
|
||||||
|
|
||||||
type SAMLApp struct {
|
type SAMLApp struct {
|
||||||
@@ -278,6 +282,22 @@ var (
|
|||||||
name: projection.AppOIDCConfigColumnLoginBaseURI,
|
name: projection.AppOIDCConfigColumnLoginBaseURI,
|
||||||
table: appOIDCConfigsTable,
|
table: appOIDCConfigsTable,
|
||||||
}
|
}
|
||||||
|
AppOIDCConfigColumnIOSTeamID = Column{
|
||||||
|
name: projection.AppOIDCConfigColumnIOSTeamID,
|
||||||
|
table: appOIDCConfigsTable,
|
||||||
|
}
|
||||||
|
AppOIDCConfigColumnIOSBundleID = Column{
|
||||||
|
name: projection.AppOIDCConfigColumnIOSBundleID,
|
||||||
|
table: appOIDCConfigsTable,
|
||||||
|
}
|
||||||
|
AppOIDCConfigColumnAndroidPackageName = Column{
|
||||||
|
name: projection.AppOIDCConfigColumnAndroidPackageName,
|
||||||
|
table: appOIDCConfigsTable,
|
||||||
|
}
|
||||||
|
AppOIDCConfigColumnAndroidSHA256CertFingerprints = Column{
|
||||||
|
name: projection.AppOIDCConfigColumnAndroidSHA256CertFingerprints,
|
||||||
|
table: appOIDCConfigsTable,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bool, projectID, appID string) (app *App, err error) {
|
func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bool, projectID, appID string) (app *App, err error) {
|
||||||
@@ -725,6 +745,10 @@ func prepareAppQuery(activeOnly bool) (sq.SelectBuilder, func(*sql.Row) (*App, e
|
|||||||
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
|
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
|
||||||
AppOIDCConfigColumnLoginVersion.identifier(),
|
AppOIDCConfigColumnLoginVersion.identifier(),
|
||||||
AppOIDCConfigColumnLoginBaseURI.identifier(),
|
AppOIDCConfigColumnLoginBaseURI.identifier(),
|
||||||
|
AppOIDCConfigColumnIOSTeamID.identifier(),
|
||||||
|
AppOIDCConfigColumnIOSBundleID.identifier(),
|
||||||
|
AppOIDCConfigColumnAndroidPackageName.identifier(),
|
||||||
|
AppOIDCConfigColumnAndroidSHA256CertFingerprints.identifier(),
|
||||||
|
|
||||||
AppSAMLConfigColumnAppID.identifier(),
|
AppSAMLConfigColumnAppID.identifier(),
|
||||||
AppSAMLConfigColumnEntityID.identifier(),
|
AppSAMLConfigColumnEntityID.identifier(),
|
||||||
@@ -794,6 +818,10 @@ func scanApp(row *sql.Row) (*App, error) {
|
|||||||
&oidcConfig.backChannelLogoutURI,
|
&oidcConfig.backChannelLogoutURI,
|
||||||
&oidcConfig.loginVersion,
|
&oidcConfig.loginVersion,
|
||||||
&oidcConfig.loginBaseURI,
|
&oidcConfig.loginBaseURI,
|
||||||
|
&oidcConfig.iosTeamID,
|
||||||
|
&oidcConfig.iosBundleID,
|
||||||
|
&oidcConfig.androidPackageName,
|
||||||
|
&oidcConfig.androidSHA256CertFingerprints,
|
||||||
|
|
||||||
&samlConfig.appID,
|
&samlConfig.appID,
|
||||||
&samlConfig.entityID,
|
&samlConfig.entityID,
|
||||||
@@ -848,6 +876,10 @@ func prepareOIDCAppQuery() (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
|||||||
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
|
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
|
||||||
AppOIDCConfigColumnLoginVersion.identifier(),
|
AppOIDCConfigColumnLoginVersion.identifier(),
|
||||||
AppOIDCConfigColumnLoginBaseURI.identifier(),
|
AppOIDCConfigColumnLoginBaseURI.identifier(),
|
||||||
|
AppOIDCConfigColumnIOSTeamID.identifier(),
|
||||||
|
AppOIDCConfigColumnIOSBundleID.identifier(),
|
||||||
|
AppOIDCConfigColumnAndroidPackageName.identifier(),
|
||||||
|
AppOIDCConfigColumnAndroidSHA256CertFingerprints.identifier(),
|
||||||
).From(appsTable.identifier()).
|
).From(appsTable.identifier()).
|
||||||
Join(join(AppOIDCConfigColumnAppID, AppColumnID)).
|
Join(join(AppOIDCConfigColumnAppID, AppColumnID)).
|
||||||
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*App, error) {
|
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*App, error) {
|
||||||
@@ -887,6 +919,10 @@ func prepareOIDCAppQuery() (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
|||||||
&oidcConfig.backChannelLogoutURI,
|
&oidcConfig.backChannelLogoutURI,
|
||||||
&oidcConfig.loginVersion,
|
&oidcConfig.loginVersion,
|
||||||
&oidcConfig.loginBaseURI,
|
&oidcConfig.loginBaseURI,
|
||||||
|
&oidcConfig.iosTeamID,
|
||||||
|
&oidcConfig.iosBundleID,
|
||||||
|
&oidcConfig.androidPackageName,
|
||||||
|
&oidcConfig.androidSHA256CertFingerprints,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1004,6 +1040,10 @@ func prepareAppsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Apps, error)) {
|
|||||||
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
|
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
|
||||||
AppOIDCConfigColumnLoginVersion.identifier(),
|
AppOIDCConfigColumnLoginVersion.identifier(),
|
||||||
AppOIDCConfigColumnLoginBaseURI.identifier(),
|
AppOIDCConfigColumnLoginBaseURI.identifier(),
|
||||||
|
AppOIDCConfigColumnIOSTeamID.identifier(),
|
||||||
|
AppOIDCConfigColumnIOSBundleID.identifier(),
|
||||||
|
AppOIDCConfigColumnAndroidPackageName.identifier(),
|
||||||
|
AppOIDCConfigColumnAndroidSHA256CertFingerprints.identifier(),
|
||||||
|
|
||||||
AppSAMLConfigColumnAppID.identifier(),
|
AppSAMLConfigColumnAppID.identifier(),
|
||||||
AppSAMLConfigColumnEntityID.identifier(),
|
AppSAMLConfigColumnEntityID.identifier(),
|
||||||
@@ -1061,6 +1101,10 @@ func prepareAppsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Apps, error)) {
|
|||||||
&oidcConfig.backChannelLogoutURI,
|
&oidcConfig.backChannelLogoutURI,
|
||||||
&oidcConfig.loginVersion,
|
&oidcConfig.loginVersion,
|
||||||
&oidcConfig.loginBaseURI,
|
&oidcConfig.loginBaseURI,
|
||||||
|
&oidcConfig.iosTeamID,
|
||||||
|
&oidcConfig.iosBundleID,
|
||||||
|
&oidcConfig.androidPackageName,
|
||||||
|
&oidcConfig.androidSHA256CertFingerprints,
|
||||||
|
|
||||||
&samlConfig.appID,
|
&samlConfig.appID,
|
||||||
&samlConfig.entityID,
|
&samlConfig.entityID,
|
||||||
@@ -1148,26 +1192,30 @@ func prepareLoginVersionBySAMLAppID() (sq.SelectBuilder, func(*sql.Row) (domain.
|
|||||||
}
|
}
|
||||||
|
|
||||||
type sqlOIDCConfig struct {
|
type sqlOIDCConfig struct {
|
||||||
appID sql.NullString
|
appID sql.NullString
|
||||||
version sql.NullInt32
|
version sql.NullInt32
|
||||||
clientID sql.NullString
|
clientID sql.NullString
|
||||||
redirectUris database.TextArray[string]
|
redirectUris database.TextArray[string]
|
||||||
applicationType sql.NullInt16
|
applicationType sql.NullInt16
|
||||||
authMethodType sql.NullInt16
|
authMethodType sql.NullInt16
|
||||||
postLogoutRedirectUris database.TextArray[string]
|
postLogoutRedirectUris database.TextArray[string]
|
||||||
devMode sql.NullBool
|
devMode sql.NullBool
|
||||||
accessTokenType sql.NullInt16
|
accessTokenType sql.NullInt16
|
||||||
accessTokenRoleAssertion sql.NullBool
|
accessTokenRoleAssertion sql.NullBool
|
||||||
iDTokenRoleAssertion sql.NullBool
|
iDTokenRoleAssertion sql.NullBool
|
||||||
iDTokenUserinfoAssertion sql.NullBool
|
iDTokenUserinfoAssertion sql.NullBool
|
||||||
clockSkew sql.NullInt64
|
clockSkew sql.NullInt64
|
||||||
additionalOrigins database.TextArray[string]
|
additionalOrigins database.TextArray[string]
|
||||||
responseTypes database.NumberArray[domain.OIDCResponseType]
|
responseTypes database.NumberArray[domain.OIDCResponseType]
|
||||||
grantTypes database.NumberArray[domain.OIDCGrantType]
|
grantTypes database.NumberArray[domain.OIDCGrantType]
|
||||||
skipNativeAppSuccessPage sql.NullBool
|
skipNativeAppSuccessPage sql.NullBool
|
||||||
backChannelLogoutURI sql.NullString
|
backChannelLogoutURI sql.NullString
|
||||||
loginVersion sql.NullInt16
|
loginVersion sql.NullInt16
|
||||||
loginBaseURI sql.NullString
|
loginBaseURI sql.NullString
|
||||||
|
iosTeamID sql.NullString
|
||||||
|
iosBundleID sql.NullString
|
||||||
|
androidPackageName sql.NullString
|
||||||
|
androidSHA256CertFingerprints database.TextArray[string]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c sqlOIDCConfig) set(app *App) {
|
func (c sqlOIDCConfig) set(app *App) {
|
||||||
@@ -1175,24 +1223,28 @@ func (c sqlOIDCConfig) set(app *App) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
app.OIDCConfig = &OIDCApp{
|
app.OIDCConfig = &OIDCApp{
|
||||||
Version: domain.OIDCVersion(c.version.Int32),
|
Version: domain.OIDCVersion(c.version.Int32),
|
||||||
ClientID: c.clientID.String,
|
ClientID: c.clientID.String,
|
||||||
RedirectURIs: c.redirectUris,
|
RedirectURIs: c.redirectUris,
|
||||||
AppType: domain.OIDCApplicationType(c.applicationType.Int16),
|
AppType: domain.OIDCApplicationType(c.applicationType.Int16),
|
||||||
AuthMethodType: domain.OIDCAuthMethodType(c.authMethodType.Int16),
|
AuthMethodType: domain.OIDCAuthMethodType(c.authMethodType.Int16),
|
||||||
PostLogoutRedirectURIs: c.postLogoutRedirectUris,
|
PostLogoutRedirectURIs: c.postLogoutRedirectUris,
|
||||||
IsDevMode: c.devMode.Bool,
|
IsDevMode: c.devMode.Bool,
|
||||||
AccessTokenType: domain.OIDCTokenType(c.accessTokenType.Int16),
|
AccessTokenType: domain.OIDCTokenType(c.accessTokenType.Int16),
|
||||||
AssertAccessTokenRole: c.accessTokenRoleAssertion.Bool,
|
AssertAccessTokenRole: c.accessTokenRoleAssertion.Bool,
|
||||||
AssertIDTokenRole: c.iDTokenRoleAssertion.Bool,
|
AssertIDTokenRole: c.iDTokenRoleAssertion.Bool,
|
||||||
AssertIDTokenUserinfo: c.iDTokenUserinfoAssertion.Bool,
|
AssertIDTokenUserinfo: c.iDTokenUserinfoAssertion.Bool,
|
||||||
ClockSkew: time.Duration(c.clockSkew.Int64),
|
ClockSkew: time.Duration(c.clockSkew.Int64),
|
||||||
AdditionalOrigins: c.additionalOrigins,
|
AdditionalOrigins: c.additionalOrigins,
|
||||||
ResponseTypes: c.responseTypes,
|
ResponseTypes: c.responseTypes,
|
||||||
GrantTypes: c.grantTypes,
|
GrantTypes: c.grantTypes,
|
||||||
SkipNativeAppSuccessPage: c.skipNativeAppSuccessPage.Bool,
|
SkipNativeAppSuccessPage: c.skipNativeAppSuccessPage.Bool,
|
||||||
BackChannelLogoutURI: c.backChannelLogoutURI.String,
|
BackChannelLogoutURI: c.backChannelLogoutURI.String,
|
||||||
LoginVersion: domain.LoginVersion(c.loginVersion.Int16),
|
LoginVersion: domain.LoginVersion(c.loginVersion.Int16),
|
||||||
|
IOSTeamID: c.iosTeamID.String,
|
||||||
|
IOSBundleID: c.iosBundleID.String,
|
||||||
|
AndroidPackageName: c.androidPackageName.String,
|
||||||
|
AndroidSHA256CertFingerprints: c.androidSHA256CertFingerprints,
|
||||||
}
|
}
|
||||||
if c.loginBaseURI.Valid {
|
if c.loginBaseURI.Valid {
|
||||||
app.OIDCConfig.LoginBaseURI = &c.loginBaseURI.String
|
app.OIDCConfig.LoginBaseURI = &c.loginBaseURI.String
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
sq "github.com/Masterminds/squirrel"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
|
"github.com/zitadel/zitadel/internal/database"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||||
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OIDCAppLinkConfig holds iOS/Android association fields for one active OIDC app.
|
||||||
|
type OIDCAppLinkConfig struct {
|
||||||
|
AppID string
|
||||||
|
IOSTeamID string
|
||||||
|
IOSBundleID string
|
||||||
|
AndroidPackageName string
|
||||||
|
AndroidSHA256CertFingerprints []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchOIDCAppLinkConfigs returns association fields for active OIDC apps in the
|
||||||
|
// current instance that have at least one iOS or Android app-link field set.
|
||||||
|
func (q *Queries) SearchOIDCAppLinkConfigs(ctx context.Context) (configs []*OIDCAppLinkConfig, err error) {
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
|
query, scan := prepareOIDCAppLinkConfigsQuery()
|
||||||
|
iosTeamIDCol := AppOIDCConfigColumnIOSTeamID.identifier()
|
||||||
|
iosBundleIDCol := AppOIDCConfigColumnIOSBundleID.identifier()
|
||||||
|
androidPackageCol := AppOIDCConfigColumnAndroidPackageName.identifier()
|
||||||
|
stmt, args, err := query.Where(sq.And{
|
||||||
|
sq.Eq{
|
||||||
|
AppOIDCConfigColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||||
|
AppColumnState.identifier(): domain.AppStateActive,
|
||||||
|
},
|
||||||
|
sq.Or{
|
||||||
|
sq.Expr("COALESCE(" + iosTeamIDCol + ", '') <> ''"),
|
||||||
|
sq.Expr("COALESCE(" + iosBundleIDCol + ", '') <> ''"),
|
||||||
|
sq.Expr("COALESCE(" + androidPackageCol + ", '') <> ''"),
|
||||||
|
},
|
||||||
|
}).OrderBy(AppOIDCConfigColumnAppID.identifier()).ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, zerrors.ThrowInvalidArgument(err, "QUERY-ApLn1", "Errors.Query.InvalidRequest")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
|
||||||
|
configs, err = scan(rows)
|
||||||
|
return err
|
||||||
|
}, stmt, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, zerrors.ThrowInternal(err, "QUERY-ApLn2", "Errors.Internal")
|
||||||
|
}
|
||||||
|
return configs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareOIDCAppLinkConfigsQuery() (sq.SelectBuilder, func(*sql.Rows) ([]*OIDCAppLinkConfig, error)) {
|
||||||
|
return sq.Select(
|
||||||
|
AppOIDCConfigColumnAppID.identifier(),
|
||||||
|
AppOIDCConfigColumnIOSTeamID.identifier(),
|
||||||
|
AppOIDCConfigColumnIOSBundleID.identifier(),
|
||||||
|
AppOIDCConfigColumnAndroidPackageName.identifier(),
|
||||||
|
AppOIDCConfigColumnAndroidSHA256CertFingerprints.identifier(),
|
||||||
|
).From(appOIDCConfigsTable.identifier()).
|
||||||
|
Join(join(AppColumnID, AppOIDCConfigColumnAppID)).
|
||||||
|
PlaceholderFormat(sq.Dollar),
|
||||||
|
func(rows *sql.Rows) ([]*OIDCAppLinkConfig, error) {
|
||||||
|
configs := make([]*OIDCAppLinkConfig, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var (
|
||||||
|
appID sql.NullString
|
||||||
|
iosTeamID sql.NullString
|
||||||
|
iosBundleID sql.NullString
|
||||||
|
androidPackage sql.NullString
|
||||||
|
fingerprints database.TextArray[string]
|
||||||
|
)
|
||||||
|
if err := rows.Scan(
|
||||||
|
&appID,
|
||||||
|
&iosTeamID,
|
||||||
|
&iosBundleID,
|
||||||
|
&androidPackage,
|
||||||
|
&fingerprints,
|
||||||
|
); err != nil {
|
||||||
|
return nil, zerrors.ThrowInternal(err, "QUERY-ApLn3", "Errors.Internal")
|
||||||
|
}
|
||||||
|
configs = append(configs, &OIDCAppLinkConfig{
|
||||||
|
AppID: appID.String,
|
||||||
|
IOSTeamID: strings.TrimSpace(iosTeamID.String),
|
||||||
|
IOSBundleID: strings.TrimSpace(iosBundleID.String),
|
||||||
|
AndroidPackageName: strings.TrimSpace(androidPackage.String),
|
||||||
|
AndroidSHA256CertFingerprints: fingerprints,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return configs, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,6 +51,10 @@ var (
|
|||||||
` projections.apps7_oidc_configs.back_channel_logout_uri,` +
|
` projections.apps7_oidc_configs.back_channel_logout_uri,` +
|
||||||
` projections.apps7_oidc_configs.login_version,` +
|
` projections.apps7_oidc_configs.login_version,` +
|
||||||
` projections.apps7_oidc_configs.login_base_uri,` +
|
` projections.apps7_oidc_configs.login_base_uri,` +
|
||||||
|
` projections.apps7_oidc_configs.ios_team_id,` +
|
||||||
|
` projections.apps7_oidc_configs.ios_bundle_id,` +
|
||||||
|
` projections.apps7_oidc_configs.android_package_name,` +
|
||||||
|
` projections.apps7_oidc_configs.android_sha256_cert_fingerprints,` +
|
||||||
//saml config
|
//saml config
|
||||||
` projections.apps7_saml_configs.app_id,` +
|
` projections.apps7_saml_configs.app_id,` +
|
||||||
` projections.apps7_saml_configs.entity_id,` +
|
` projections.apps7_saml_configs.entity_id,` +
|
||||||
@@ -99,6 +103,10 @@ var (
|
|||||||
` projections.apps7_oidc_configs.back_channel_logout_uri,` +
|
` projections.apps7_oidc_configs.back_channel_logout_uri,` +
|
||||||
` projections.apps7_oidc_configs.login_version,` +
|
` projections.apps7_oidc_configs.login_version,` +
|
||||||
` projections.apps7_oidc_configs.login_base_uri,` +
|
` projections.apps7_oidc_configs.login_base_uri,` +
|
||||||
|
` projections.apps7_oidc_configs.ios_team_id,` +
|
||||||
|
` projections.apps7_oidc_configs.ios_bundle_id,` +
|
||||||
|
` projections.apps7_oidc_configs.android_package_name,` +
|
||||||
|
` projections.apps7_oidc_configs.android_sha256_cert_fingerprints,` +
|
||||||
//saml config
|
//saml config
|
||||||
` projections.apps7_saml_configs.app_id,` +
|
` projections.apps7_saml_configs.app_id,` +
|
||||||
` projections.apps7_saml_configs.entity_id,` +
|
` projections.apps7_saml_configs.entity_id,` +
|
||||||
@@ -172,6 +180,10 @@ var (
|
|||||||
"back_channel_logout_uri",
|
"back_channel_logout_uri",
|
||||||
"login_version",
|
"login_version",
|
||||||
"login_base_uri",
|
"login_base_uri",
|
||||||
|
"ios_team_id",
|
||||||
|
"ios_bundle_id",
|
||||||
|
"android_package_name",
|
||||||
|
"android_sha256_cert_fingerprints",
|
||||||
//saml config
|
//saml config
|
||||||
"app_id",
|
"app_id",
|
||||||
"entity_id",
|
"entity_id",
|
||||||
@@ -248,6 +260,10 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@@ -319,6 +335,10 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@@ -393,6 +413,10 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
"app-id",
|
"app-id",
|
||||||
"https://test.com/saml/metadata",
|
"https://test.com/saml/metadata",
|
||||||
@@ -469,6 +493,10 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@@ -563,6 +591,10 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@@ -657,6 +689,10 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@@ -751,6 +787,10 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@@ -845,6 +885,10 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@@ -939,6 +983,10 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@@ -1033,6 +1081,10 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
domain.LoginVersion2,
|
domain.LoginVersion2,
|
||||||
"https://login.ch/",
|
"https://login.ch/",
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@@ -1075,6 +1127,10 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@@ -1117,6 +1173,10 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
"saml-app-id",
|
"saml-app-id",
|
||||||
"https://test.com/saml/metadata",
|
"https://test.com/saml/metadata",
|
||||||
@@ -1302,6 +1362,10 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@@ -1367,6 +1431,10 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@@ -1437,6 +1505,10 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@@ -1526,6 +1598,10 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@@ -1615,6 +1691,10 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
"app-id",
|
"app-id",
|
||||||
"https://test.com/saml/metadata",
|
"https://test.com/saml/metadata",
|
||||||
@@ -1688,6 +1768,10 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@@ -1777,6 +1861,10 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@@ -1866,6 +1954,10 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@@ -1955,6 +2047,10 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
domain.LoginVersionUnspecified,
|
domain.LoginVersionUnspecified,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
|||||||
@@ -38,30 +38,34 @@ const (
|
|||||||
AppAPIConfigColumnClientSecret = "client_secret"
|
AppAPIConfigColumnClientSecret = "client_secret"
|
||||||
AppAPIConfigColumnAuthMethod = "auth_method"
|
AppAPIConfigColumnAuthMethod = "auth_method"
|
||||||
|
|
||||||
appOIDCTableSuffix = "oidc_configs"
|
appOIDCTableSuffix = "oidc_configs"
|
||||||
AppOIDCConfigColumnAppID = "app_id"
|
AppOIDCConfigColumnAppID = "app_id"
|
||||||
AppOIDCConfigColumnInstanceID = "instance_id"
|
AppOIDCConfigColumnInstanceID = "instance_id"
|
||||||
AppOIDCConfigColumnVersion = "version"
|
AppOIDCConfigColumnVersion = "version"
|
||||||
AppOIDCConfigColumnClientID = "client_id"
|
AppOIDCConfigColumnClientID = "client_id"
|
||||||
AppOIDCConfigColumnClientSecret = "client_secret"
|
AppOIDCConfigColumnClientSecret = "client_secret"
|
||||||
AppOIDCConfigColumnRedirectUris = "redirect_uris"
|
AppOIDCConfigColumnRedirectUris = "redirect_uris"
|
||||||
AppOIDCConfigColumnResponseTypes = "response_types"
|
AppOIDCConfigColumnResponseTypes = "response_types"
|
||||||
AppOIDCConfigColumnGrantTypes = "grant_types"
|
AppOIDCConfigColumnGrantTypes = "grant_types"
|
||||||
AppOIDCConfigColumnApplicationType = "application_type"
|
AppOIDCConfigColumnApplicationType = "application_type"
|
||||||
AppOIDCConfigColumnAuthMethodType = "auth_method_type"
|
AppOIDCConfigColumnAuthMethodType = "auth_method_type"
|
||||||
AppOIDCConfigColumnPostLogoutRedirectUris = "post_logout_redirect_uris"
|
AppOIDCConfigColumnPostLogoutRedirectUris = "post_logout_redirect_uris"
|
||||||
AppOIDCConfigColumnDevMode = "is_dev_mode"
|
AppOIDCConfigColumnDevMode = "is_dev_mode"
|
||||||
AppOIDCConfigColumnAccessTokenType = "access_token_type"
|
AppOIDCConfigColumnAccessTokenType = "access_token_type"
|
||||||
AppOIDCConfigColumnAccessTokenRoleAssertion = "access_token_role_assertion"
|
AppOIDCConfigColumnAccessTokenRoleAssertion = "access_token_role_assertion"
|
||||||
AppOIDCConfigColumnIDTokenRoleAssertion = "id_token_role_assertion"
|
AppOIDCConfigColumnIDTokenRoleAssertion = "id_token_role_assertion"
|
||||||
AppOIDCConfigColumnIDTokenUserinfoAssertion = "id_token_userinfo_assertion"
|
AppOIDCConfigColumnIDTokenUserinfoAssertion = "id_token_userinfo_assertion"
|
||||||
AppOIDCConfigColumnClockSkew = "clock_skew"
|
AppOIDCConfigColumnClockSkew = "clock_skew"
|
||||||
AppOIDCConfigColumnAdditionalOrigins = "additional_origins"
|
AppOIDCConfigColumnAdditionalOrigins = "additional_origins"
|
||||||
AppOIDCConfigColumnSkipNativeAppSuccessPage = "skip_native_app_success_page"
|
AppOIDCConfigColumnSkipNativeAppSuccessPage = "skip_native_app_success_page"
|
||||||
AppOIDCConfigColumnBackChannelLogoutURI = "back_channel_logout_uri"
|
AppOIDCConfigColumnBackChannelLogoutURI = "back_channel_logout_uri"
|
||||||
AppOIDCConfigColumnLoginVersion = "login_version"
|
AppOIDCConfigColumnLoginVersion = "login_version"
|
||||||
AppOIDCConfigColumnLoginBaseURI = "login_base_uri"
|
AppOIDCConfigColumnLoginBaseURI = "login_base_uri"
|
||||||
AppOIDCConfigColumnRegistrationToken = "registration_token"
|
AppOIDCConfigColumnIOSTeamID = "ios_team_id"
|
||||||
|
AppOIDCConfigColumnIOSBundleID = "ios_bundle_id"
|
||||||
|
AppOIDCConfigColumnAndroidPackageName = "android_package_name"
|
||||||
|
AppOIDCConfigColumnAndroidSHA256CertFingerprints = "android_sha256_cert_fingerprints"
|
||||||
|
AppOIDCConfigColumnRegistrationToken = "registration_token"
|
||||||
|
|
||||||
appSAMLTableSuffix = "saml_configs"
|
appSAMLTableSuffix = "saml_configs"
|
||||||
AppSAMLConfigColumnAppID = "app_id"
|
AppSAMLConfigColumnAppID = "app_id"
|
||||||
@@ -134,6 +138,10 @@ func (*appProjection) Init() *old_handler.Check {
|
|||||||
handler.NewColumn(AppOIDCConfigColumnBackChannelLogoutURI, handler.ColumnTypeText, handler.Nullable()),
|
handler.NewColumn(AppOIDCConfigColumnBackChannelLogoutURI, handler.ColumnTypeText, handler.Nullable()),
|
||||||
handler.NewColumn(AppOIDCConfigColumnLoginVersion, handler.ColumnTypeEnum, handler.Nullable()),
|
handler.NewColumn(AppOIDCConfigColumnLoginVersion, handler.ColumnTypeEnum, handler.Nullable()),
|
||||||
handler.NewColumn(AppOIDCConfigColumnLoginBaseURI, handler.ColumnTypeText, handler.Nullable()),
|
handler.NewColumn(AppOIDCConfigColumnLoginBaseURI, handler.ColumnTypeText, handler.Nullable()),
|
||||||
|
handler.NewColumn(AppOIDCConfigColumnIOSTeamID, handler.ColumnTypeText, handler.Nullable()),
|
||||||
|
handler.NewColumn(AppOIDCConfigColumnIOSBundleID, handler.ColumnTypeText, handler.Nullable()),
|
||||||
|
handler.NewColumn(AppOIDCConfigColumnAndroidPackageName, handler.ColumnTypeText, handler.Nullable()),
|
||||||
|
handler.NewColumn(AppOIDCConfigColumnAndroidSHA256CertFingerprints, handler.ColumnTypeTextArray, handler.Nullable()),
|
||||||
handler.NewColumn(AppOIDCConfigColumnRegistrationToken, handler.ColumnTypeText, handler.Nullable()),
|
handler.NewColumn(AppOIDCConfigColumnRegistrationToken, handler.ColumnTypeText, handler.Nullable()),
|
||||||
},
|
},
|
||||||
handler.NewPrimaryKey(AppOIDCConfigColumnInstanceID, AppOIDCConfigColumnAppID),
|
handler.NewPrimaryKey(AppOIDCConfigColumnInstanceID, AppOIDCConfigColumnAppID),
|
||||||
@@ -519,6 +527,10 @@ func (p *appProjection) reduceOIDCConfigAdded(event eventstore.Event) (*handler.
|
|||||||
handler.NewCol(AppOIDCConfigColumnBackChannelLogoutURI, e.BackChannelLogoutURI),
|
handler.NewCol(AppOIDCConfigColumnBackChannelLogoutURI, e.BackChannelLogoutURI),
|
||||||
handler.NewCol(AppOIDCConfigColumnLoginVersion, e.LoginVersion),
|
handler.NewCol(AppOIDCConfigColumnLoginVersion, e.LoginVersion),
|
||||||
handler.NewCol(AppOIDCConfigColumnLoginBaseURI, e.LoginBaseURI),
|
handler.NewCol(AppOIDCConfigColumnLoginBaseURI, e.LoginBaseURI),
|
||||||
|
handler.NewCol(AppOIDCConfigColumnIOSTeamID, e.IOSTeamID),
|
||||||
|
handler.NewCol(AppOIDCConfigColumnIOSBundleID, e.IOSBundleID),
|
||||||
|
handler.NewCol(AppOIDCConfigColumnAndroidPackageName, e.AndroidPackageName),
|
||||||
|
handler.NewCol(AppOIDCConfigColumnAndroidSHA256CertFingerprints, database.TextArray[string](e.AndroidSHA256CertFingerprints)),
|
||||||
},
|
},
|
||||||
handler.WithTableSuffix(appOIDCTableSuffix),
|
handler.WithTableSuffix(appOIDCTableSuffix),
|
||||||
),
|
),
|
||||||
@@ -596,6 +608,18 @@ func (p *appProjection) reduceOIDCConfigChanged(event eventstore.Event) (*handle
|
|||||||
if e.LoginBaseURI != nil {
|
if e.LoginBaseURI != nil {
|
||||||
cols = append(cols, handler.NewCol(AppOIDCConfigColumnLoginBaseURI, *e.LoginBaseURI))
|
cols = append(cols, handler.NewCol(AppOIDCConfigColumnLoginBaseURI, *e.LoginBaseURI))
|
||||||
}
|
}
|
||||||
|
if e.IOSTeamID != nil {
|
||||||
|
cols = append(cols, handler.NewCol(AppOIDCConfigColumnIOSTeamID, *e.IOSTeamID))
|
||||||
|
}
|
||||||
|
if e.IOSBundleID != nil {
|
||||||
|
cols = append(cols, handler.NewCol(AppOIDCConfigColumnIOSBundleID, *e.IOSBundleID))
|
||||||
|
}
|
||||||
|
if e.AndroidPackageName != nil {
|
||||||
|
cols = append(cols, handler.NewCol(AppOIDCConfigColumnAndroidPackageName, *e.AndroidPackageName))
|
||||||
|
}
|
||||||
|
if e.AndroidSHA256CertFingerprints != nil {
|
||||||
|
cols = append(cols, handler.NewCol(AppOIDCConfigColumnAndroidSHA256CertFingerprints, database.TextArray[string](*e.AndroidSHA256CertFingerprints)))
|
||||||
|
}
|
||||||
|
|
||||||
if len(cols) == 0 {
|
if len(cols) == 0 {
|
||||||
return handler.NewNoOpStatement(e), nil
|
return handler.NewNoOpStatement(e), nil
|
||||||
|
|||||||
@@ -572,7 +572,7 @@ func TestAppProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.apps7_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page, back_channel_logout_uri, login_version, login_base_uri) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)",
|
expectedStmt: "INSERT INTO projections.apps7_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page, back_channel_logout_uri, login_version, login_base_uri, ios_team_id, ios_bundle_id, android_package_name, android_sha256_cert_fingerprints) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"app-id",
|
"app-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
@@ -596,6 +596,10 @@ func TestAppProjection_reduces(t *testing.T) {
|
|||||||
"back.channel.one.ch",
|
"back.channel.one.ch",
|
||||||
domain.LoginVersion2,
|
domain.LoginVersion2,
|
||||||
"https://login.ch/",
|
"https://login.ch/",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
database.TextArray[string](nil),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -650,7 +654,7 @@ func TestAppProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.apps7_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page, back_channel_logout_uri, login_version, login_base_uri) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)",
|
expectedStmt: "INSERT INTO projections.apps7_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page, back_channel_logout_uri, login_version, login_base_uri, ios_team_id, ios_bundle_id, android_package_name, android_sha256_cert_fingerprints) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"app-id",
|
"app-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
@@ -674,6 +678,10 @@ func TestAppProjection_reduces(t *testing.T) {
|
|||||||
"back.channel.one.ch",
|
"back.channel.one.ch",
|
||||||
domain.LoginVersion2,
|
domain.LoginVersion2,
|
||||||
"https://login.ch/",
|
"https://login.ch/",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
database.TextArray[string](nil),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -761,6 +769,52 @@ func TestAppProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "project reduceOIDCConfigChanged app links",
|
||||||
|
args: args{
|
||||||
|
event: getEvent(
|
||||||
|
testEvent(
|
||||||
|
project.OIDCConfigChangedType,
|
||||||
|
project.AggregateType,
|
||||||
|
[]byte(`{
|
||||||
|
"appId": "app-id",
|
||||||
|
"iosTeamId": "TEAMID",
|
||||||
|
"iosBundleId": "com.example.app",
|
||||||
|
"androidPackageName": "com.example.app",
|
||||||
|
"androidSha256CertFingerprints": ["AA:BB:CC"]
|
||||||
|
}`),
|
||||||
|
), project.OIDCConfigChangedEventMapper),
|
||||||
|
},
|
||||||
|
reduce: (&appProjection{}).reduceOIDCConfigChanged,
|
||||||
|
want: wantReduce{
|
||||||
|
aggregateType: eventstore.AggregateType("project"),
|
||||||
|
sequence: 15,
|
||||||
|
executer: &testExecuter{
|
||||||
|
executions: []execution{
|
||||||
|
{
|
||||||
|
expectedStmt: "UPDATE projections.apps7_oidc_configs SET (ios_team_id, ios_bundle_id, android_package_name, android_sha256_cert_fingerprints) = ($1, $2, $3, $4) WHERE (app_id = $5) AND (instance_id = $6)",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
"TEAMID",
|
||||||
|
"com.example.app",
|
||||||
|
"com.example.app",
|
||||||
|
database.TextArray[string]{"AA:BB:CC"},
|
||||||
|
"app-id",
|
||||||
|
"instance-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
anyArg{},
|
||||||
|
uint64(15),
|
||||||
|
"app-id",
|
||||||
|
"instance-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "project reduceOIDCConfigChanged noop",
|
name: "project reduceOIDCConfigChanged noop",
|
||||||
args: args{
|
args: args{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package project
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
@@ -31,23 +32,27 @@ type OIDCConfigAddedEvent struct {
|
|||||||
ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"`
|
ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"`
|
||||||
HashedSecret string `json:"hashedSecret,omitempty"`
|
HashedSecret string `json:"hashedSecret,omitempty"`
|
||||||
|
|
||||||
RedirectUris []string `json:"redirectUris,omitempty"`
|
RedirectUris []string `json:"redirectUris,omitempty"`
|
||||||
ResponseTypes []domain.OIDCResponseType `json:"responseTypes,omitempty"`
|
ResponseTypes []domain.OIDCResponseType `json:"responseTypes,omitempty"`
|
||||||
GrantTypes []domain.OIDCGrantType `json:"grantTypes,omitempty"`
|
GrantTypes []domain.OIDCGrantType `json:"grantTypes,omitempty"`
|
||||||
ApplicationType domain.OIDCApplicationType `json:"applicationType,omitempty"`
|
ApplicationType domain.OIDCApplicationType `json:"applicationType,omitempty"`
|
||||||
AuthMethodType domain.OIDCAuthMethodType `json:"authMethodType,omitempty"`
|
AuthMethodType domain.OIDCAuthMethodType `json:"authMethodType,omitempty"`
|
||||||
PostLogoutRedirectUris []string `json:"postLogoutRedirectUris,omitempty"`
|
PostLogoutRedirectUris []string `json:"postLogoutRedirectUris,omitempty"`
|
||||||
DevMode bool `json:"devMode,omitempty"`
|
DevMode bool `json:"devMode,omitempty"`
|
||||||
AccessTokenType domain.OIDCTokenType `json:"accessTokenType,omitempty"`
|
AccessTokenType domain.OIDCTokenType `json:"accessTokenType,omitempty"`
|
||||||
AccessTokenRoleAssertion bool `json:"accessTokenRoleAssertion,omitempty"`
|
AccessTokenRoleAssertion bool `json:"accessTokenRoleAssertion,omitempty"`
|
||||||
IDTokenRoleAssertion bool `json:"idTokenRoleAssertion,omitempty"`
|
IDTokenRoleAssertion bool `json:"idTokenRoleAssertion,omitempty"`
|
||||||
IDTokenUserinfoAssertion bool `json:"idTokenUserinfoAssertion,omitempty"`
|
IDTokenUserinfoAssertion bool `json:"idTokenUserinfoAssertion,omitempty"`
|
||||||
ClockSkew time.Duration `json:"clockSkew,omitempty"`
|
ClockSkew time.Duration `json:"clockSkew,omitempty"`
|
||||||
AdditionalOrigins []string `json:"additionalOrigins,omitempty"`
|
AdditionalOrigins []string `json:"additionalOrigins,omitempty"`
|
||||||
SkipNativeAppSuccessPage bool `json:"skipNativeAppSuccessPage,omitempty"`
|
SkipNativeAppSuccessPage bool `json:"skipNativeAppSuccessPage,omitempty"`
|
||||||
BackChannelLogoutURI string `json:"backChannelLogoutURI,omitempty"`
|
BackChannelLogoutURI string `json:"backChannelLogoutURI,omitempty"`
|
||||||
LoginVersion domain.LoginVersion `json:"loginVersion,omitempty"`
|
LoginVersion domain.LoginVersion `json:"loginVersion,omitempty"`
|
||||||
LoginBaseURI string `json:"loginBaseURI,omitempty"`
|
LoginBaseURI string `json:"loginBaseURI,omitempty"`
|
||||||
|
IOSTeamID string `json:"iosTeamId,omitempty"`
|
||||||
|
IOSBundleID string `json:"iosBundleId,omitempty"`
|
||||||
|
AndroidPackageName string `json:"androidPackageName,omitempty"`
|
||||||
|
AndroidSHA256CertFingerprints []string `json:"androidSha256CertFingerprints,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *OIDCConfigAddedEvent) Payload() interface{} {
|
func (e *OIDCConfigAddedEvent) Payload() interface{} {
|
||||||
@@ -82,6 +87,10 @@ func NewOIDCConfigAddedEvent(
|
|||||||
backChannelLogoutURI string,
|
backChannelLogoutURI string,
|
||||||
loginVersion domain.LoginVersion,
|
loginVersion domain.LoginVersion,
|
||||||
loginBaseURI string,
|
loginBaseURI string,
|
||||||
|
iosTeamID string,
|
||||||
|
iosBundleID string,
|
||||||
|
androidPackageName string,
|
||||||
|
androidSHA256CertFingerprints []string,
|
||||||
) *OIDCConfigAddedEvent {
|
) *OIDCConfigAddedEvent {
|
||||||
return &OIDCConfigAddedEvent{
|
return &OIDCConfigAddedEvent{
|
||||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||||
@@ -89,27 +98,31 @@ func NewOIDCConfigAddedEvent(
|
|||||||
aggregate,
|
aggregate,
|
||||||
OIDCConfigAddedType,
|
OIDCConfigAddedType,
|
||||||
),
|
),
|
||||||
Version: version,
|
Version: version,
|
||||||
AppID: appID,
|
AppID: appID,
|
||||||
ClientID: clientID,
|
ClientID: clientID,
|
||||||
HashedSecret: hashedSecret,
|
HashedSecret: hashedSecret,
|
||||||
RedirectUris: redirectUris,
|
RedirectUris: redirectUris,
|
||||||
ResponseTypes: responseTypes,
|
ResponseTypes: responseTypes,
|
||||||
GrantTypes: grantTypes,
|
GrantTypes: grantTypes,
|
||||||
ApplicationType: applicationType,
|
ApplicationType: applicationType,
|
||||||
AuthMethodType: authMethodType,
|
AuthMethodType: authMethodType,
|
||||||
PostLogoutRedirectUris: postLogoutRedirectUris,
|
PostLogoutRedirectUris: postLogoutRedirectUris,
|
||||||
DevMode: devMode,
|
DevMode: devMode,
|
||||||
AccessTokenType: accessTokenType,
|
AccessTokenType: accessTokenType,
|
||||||
AccessTokenRoleAssertion: accessTokenRoleAssertion,
|
AccessTokenRoleAssertion: accessTokenRoleAssertion,
|
||||||
IDTokenRoleAssertion: idTokenRoleAssertion,
|
IDTokenRoleAssertion: idTokenRoleAssertion,
|
||||||
IDTokenUserinfoAssertion: idTokenUserinfoAssertion,
|
IDTokenUserinfoAssertion: idTokenUserinfoAssertion,
|
||||||
ClockSkew: clockSkew,
|
ClockSkew: clockSkew,
|
||||||
AdditionalOrigins: additionalOrigins,
|
AdditionalOrigins: additionalOrigins,
|
||||||
SkipNativeAppSuccessPage: skipNativeAppSuccessPage,
|
SkipNativeAppSuccessPage: skipNativeAppSuccessPage,
|
||||||
BackChannelLogoutURI: backChannelLogoutURI,
|
BackChannelLogoutURI: backChannelLogoutURI,
|
||||||
LoginVersion: loginVersion,
|
LoginVersion: loginVersion,
|
||||||
LoginBaseURI: loginBaseURI,
|
LoginBaseURI: loginBaseURI,
|
||||||
|
IOSTeamID: iosTeamID,
|
||||||
|
IOSBundleID: iosBundleID,
|
||||||
|
AndroidPackageName: androidPackageName,
|
||||||
|
AndroidSHA256CertFingerprints: androidSHA256CertFingerprints,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +217,19 @@ func (e *OIDCConfigAddedEvent) Validate(cmd eventstore.Command) bool {
|
|||||||
if e.LoginVersion != c.LoginVersion {
|
if e.LoginVersion != c.LoginVersion {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return e.LoginBaseURI == c.LoginBaseURI
|
if e.LoginBaseURI != c.LoginBaseURI {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if e.IOSTeamID != c.IOSTeamID {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if e.IOSBundleID != c.IOSBundleID {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if e.AndroidPackageName != c.AndroidPackageName {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return slices.Equal(e.AndroidSHA256CertFingerprints, c.AndroidSHA256CertFingerprints)
|
||||||
}
|
}
|
||||||
|
|
||||||
func OIDCConfigAddedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
func OIDCConfigAddedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||||
@@ -223,25 +248,29 @@ func OIDCConfigAddedEventMapper(event eventstore.Event) (eventstore.Event, error
|
|||||||
type OIDCConfigChangedEvent struct {
|
type OIDCConfigChangedEvent struct {
|
||||||
eventstore.BaseEvent `json:"-"`
|
eventstore.BaseEvent `json:"-"`
|
||||||
|
|
||||||
Version *domain.OIDCVersion `json:"oidcVersion,omitempty"`
|
Version *domain.OIDCVersion `json:"oidcVersion,omitempty"`
|
||||||
AppID string `json:"appId"`
|
AppID string `json:"appId"`
|
||||||
RedirectUris *[]string `json:"redirectUris,omitempty"`
|
RedirectUris *[]string `json:"redirectUris,omitempty"`
|
||||||
ResponseTypes *[]domain.OIDCResponseType `json:"responseTypes,omitempty"`
|
ResponseTypes *[]domain.OIDCResponseType `json:"responseTypes,omitempty"`
|
||||||
GrantTypes *[]domain.OIDCGrantType `json:"grantTypes,omitempty"`
|
GrantTypes *[]domain.OIDCGrantType `json:"grantTypes,omitempty"`
|
||||||
ApplicationType *domain.OIDCApplicationType `json:"applicationType,omitempty"`
|
ApplicationType *domain.OIDCApplicationType `json:"applicationType,omitempty"`
|
||||||
AuthMethodType *domain.OIDCAuthMethodType `json:"authMethodType,omitempty"`
|
AuthMethodType *domain.OIDCAuthMethodType `json:"authMethodType,omitempty"`
|
||||||
PostLogoutRedirectUris *[]string `json:"postLogoutRedirectUris,omitempty"`
|
PostLogoutRedirectUris *[]string `json:"postLogoutRedirectUris,omitempty"`
|
||||||
DevMode *bool `json:"devMode,omitempty"`
|
DevMode *bool `json:"devMode,omitempty"`
|
||||||
AccessTokenType *domain.OIDCTokenType `json:"accessTokenType,omitempty"`
|
AccessTokenType *domain.OIDCTokenType `json:"accessTokenType,omitempty"`
|
||||||
AccessTokenRoleAssertion *bool `json:"accessTokenRoleAssertion,omitempty"`
|
AccessTokenRoleAssertion *bool `json:"accessTokenRoleAssertion,omitempty"`
|
||||||
IDTokenRoleAssertion *bool `json:"idTokenRoleAssertion,omitempty"`
|
IDTokenRoleAssertion *bool `json:"idTokenRoleAssertion,omitempty"`
|
||||||
IDTokenUserinfoAssertion *bool `json:"idTokenUserinfoAssertion,omitempty"`
|
IDTokenUserinfoAssertion *bool `json:"idTokenUserinfoAssertion,omitempty"`
|
||||||
ClockSkew *time.Duration `json:"clockSkew,omitempty"`
|
ClockSkew *time.Duration `json:"clockSkew,omitempty"`
|
||||||
AdditionalOrigins *[]string `json:"additionalOrigins,omitempty"`
|
AdditionalOrigins *[]string `json:"additionalOrigins,omitempty"`
|
||||||
SkipNativeAppSuccessPage *bool `json:"skipNativeAppSuccessPage,omitempty"`
|
SkipNativeAppSuccessPage *bool `json:"skipNativeAppSuccessPage,omitempty"`
|
||||||
BackChannelLogoutURI *string `json:"backChannelLogoutURI,omitempty"`
|
BackChannelLogoutURI *string `json:"backChannelLogoutURI,omitempty"`
|
||||||
LoginVersion *domain.LoginVersion `json:"loginVersion,omitempty"`
|
LoginVersion *domain.LoginVersion `json:"loginVersion,omitempty"`
|
||||||
LoginBaseURI *string `json:"loginBaseURI,omitempty"`
|
LoginBaseURI *string `json:"loginBaseURI,omitempty"`
|
||||||
|
IOSTeamID *string `json:"iosTeamId,omitempty"`
|
||||||
|
IOSBundleID *string `json:"iosBundleId,omitempty"`
|
||||||
|
AndroidPackageName *string `json:"androidPackageName,omitempty"`
|
||||||
|
AndroidSHA256CertFingerprints *[]string `json:"androidSha256CertFingerprints,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *OIDCConfigChangedEvent) Payload() interface{} {
|
func (e *OIDCConfigChangedEvent) Payload() interface{} {
|
||||||
@@ -398,6 +427,34 @@ func ChangeOIDCLoginBaseURI(loginBaseURI string) func(event *OIDCConfigChangedEv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ChangeIOSTeamID(iosTeamID string) func(event *OIDCConfigChangedEvent) {
|
||||||
|
return func(e *OIDCConfigChangedEvent) {
|
||||||
|
e.IOSTeamID = &iosTeamID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeIOSBundleID(iosBundleID string) func(event *OIDCConfigChangedEvent) {
|
||||||
|
return func(e *OIDCConfigChangedEvent) {
|
||||||
|
e.IOSBundleID = &iosBundleID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeAndroidPackageName(androidPackageName string) func(event *OIDCConfigChangedEvent) {
|
||||||
|
return func(e *OIDCConfigChangedEvent) {
|
||||||
|
e.AndroidPackageName = &androidPackageName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeAndroidSHA256CertFingerprints(fingerprints []string) func(event *OIDCConfigChangedEvent) {
|
||||||
|
return func(e *OIDCConfigChangedEvent) {
|
||||||
|
if fingerprints == nil {
|
||||||
|
// explicitly set them to empty so we can differentiate "not set" in the event in case of no changes
|
||||||
|
fingerprints = make([]string, 0)
|
||||||
|
}
|
||||||
|
e.AndroidSHA256CertFingerprints = &fingerprints
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func OIDCConfigChangedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
func OIDCConfigChangedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||||
e := &OIDCConfigChangedEvent{
|
e := &OIDCConfigChangedEvent{
|
||||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||||
|
|||||||
@@ -179,6 +179,57 @@ message OIDCConfig {
|
|||||||
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
|
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
IOSAppLinkConfig ios = 23 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "iOS Associated Domains / passkey trust config. Served in /.well-known/apple-app-site-association as webcredentials.apps entry \"{team_id}.{bundle_id}\" (Apple's full App ID). That response may be HTTP-cached (Cache-Control), and platform verifiers may cache longer; changes can take time to take effect.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
AndroidAppLinkConfig android = 24 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Android Digital Asset Links / passkey trust config. Served in /.well-known/assetlinks.json for delegate_permission/common.get_login_creds. That response may be HTTP-cached (Cache-Control), and platform verifiers may cache longer; changes can take time to take effect.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message IOSAppLinkConfig {
|
||||||
|
string team_id = 1 [
|
||||||
|
(validate.rules).string = {ignore_empty: true, len: 10, pattern: "^[A-Z0-9]+$"},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"ABCDE12345\"";
|
||||||
|
description: "Apple Team ID (App ID prefix), exactly 10 alphanumeric characters, e.g. ABCDE12345. Combined with bundle_id as TEAMID.BUNDLEID in AASA webcredentials.apps. Not the full Apple App ID by itself. Empty clears the value on update.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string bundle_id = 2 [
|
||||||
|
(validate.rules).string = {ignore_empty: true, min_len: 1, max_len: 200},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"com.example.app\"";
|
||||||
|
description: "iOS Bundle ID (CFBundleIdentifier), e.g. com.example.app. Combined with team_id for AASA webcredentials.apps. Do not paste TEAMID.bundle.id here. Empty clears the value on update.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message AndroidAppLinkConfig {
|
||||||
|
string package_name = 1 [
|
||||||
|
(validate.rules).string = {ignore_empty: true, min_len: 1, max_len: 200},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"com.example.app\"";
|
||||||
|
description: "Android package name (applicationId). Used in assetlinks.json target.package_name. Empty clears the value on update.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
repeated string sha256_cert_fingerprints = 2 [
|
||||||
|
(validate.rules).repeated = {
|
||||||
|
max_items: 20,
|
||||||
|
items: {
|
||||||
|
string: {
|
||||||
|
pattern: "^([0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2}){31}|[0-9A-Fa-f]{64})$";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "[\"AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99\"]";
|
||||||
|
description: "SHA-256 signing certificate fingerprints for Digital Asset Links (passkey / get_login_creds). 64 hex chars, optionally colon-separated pairs. At most 20 entries. Include debug and release as needed.";
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
enum OIDCResponseType {
|
enum OIDCResponseType {
|
||||||
|
|||||||
@@ -341,6 +341,19 @@ message CreateOIDCApplicationRequest {
|
|||||||
// hosted on any other domain.
|
// hosted on any other domain.
|
||||||
// If unset, the login UI is chosen by the instance default.
|
// If unset, the login UI is chosen by the instance default.
|
||||||
LoginVersion login_version = 17;
|
LoginVersion login_version = 17;
|
||||||
|
|
||||||
|
// IOS is iOS Associated Domains / passkey trust config.
|
||||||
|
// Served in /.well-known/apple-app-site-association as webcredentials.apps entry
|
||||||
|
// "{team_id}.{bundle_id}" (Apple's full App ID = Team ID prefix + Bundle ID).
|
||||||
|
// That response may be HTTP-cached (Cache-Control), and platform verifiers may cache longer;
|
||||||
|
// changes can take time to take effect.
|
||||||
|
IOSAppLinkConfig ios = 18;
|
||||||
|
|
||||||
|
// Android is Android Digital Asset Links / passkey trust config.
|
||||||
|
// Served in /.well-known/assetlinks.json for delegate_permission/common.get_login_creds.
|
||||||
|
// That response may be HTTP-cached (Cache-Control), and platform verifiers may cache longer;
|
||||||
|
// changes can take time to take effect.
|
||||||
|
AndroidAppLinkConfig android = 19;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CreateOIDCApplicationResponse {
|
message CreateOIDCApplicationResponse {
|
||||||
@@ -578,6 +591,21 @@ message UpdateOIDCApplicationConfigurationRequest {
|
|||||||
// hosted on any other domain.
|
// hosted on any other domain.
|
||||||
// If unset, the login UI is chosen by the instance default.
|
// If unset, the login UI is chosen by the instance default.
|
||||||
optional LoginVersion login_version = 17;
|
optional LoginVersion login_version = 17;
|
||||||
|
|
||||||
|
// IOS is iOS Associated Domains / passkey trust config.
|
||||||
|
// Served in /.well-known/apple-app-site-association as webcredentials.apps entry
|
||||||
|
// "{team_id}.{bundle_id}" (Apple's full App ID = Team ID prefix + Bundle ID).
|
||||||
|
// That response may be HTTP-cached (Cache-Control), and platform verifiers may cache longer;
|
||||||
|
// changes can take time to take effect.
|
||||||
|
// If not set, the iOS config will not be changed.
|
||||||
|
optional IOSAppLinkConfig ios = 18;
|
||||||
|
|
||||||
|
// Android is Android Digital Asset Links / passkey trust config.
|
||||||
|
// Served in /.well-known/assetlinks.json for delegate_permission/common.get_login_creds.
|
||||||
|
// That response may be HTTP-cached (Cache-Control), and platform verifiers may cache longer;
|
||||||
|
// changes can take time to take effect.
|
||||||
|
// If not set, the Android config will not be changed.
|
||||||
|
optional AndroidAppLinkConfig android = 19;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateAPIApplicationConfigurationRequest {
|
message UpdateAPIApplicationConfigurationRequest {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package zitadel.application.v2;
|
|||||||
|
|
||||||
import "google/protobuf/duration.proto";
|
import "google/protobuf/duration.proto";
|
||||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
|
import "validate/validate.proto";
|
||||||
import "zitadel/application/v2/login.proto";
|
import "zitadel/application/v2/login.proto";
|
||||||
|
|
||||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/application/v2;application";
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/application/v2;application";
|
||||||
@@ -157,4 +158,55 @@ message OIDCConfiguration {
|
|||||||
// hosted on any other domain.
|
// hosted on any other domain.
|
||||||
// If unset, the login UI is chosen by the instance default.
|
// If unset, the login UI is chosen by the instance default.
|
||||||
LoginVersion login_version = 21;
|
LoginVersion login_version = 21;
|
||||||
|
|
||||||
|
// IOS is iOS Associated Domains / passkey trust config.
|
||||||
|
// Served in /.well-known/apple-app-site-association as webcredentials.apps entry
|
||||||
|
// "{team_id}.{bundle_id}" (Apple's full App ID = Team ID prefix + Bundle ID).
|
||||||
|
// That response may be HTTP-cached (Cache-Control), and platform verifiers may cache longer;
|
||||||
|
// changes can take time to take effect.
|
||||||
|
IOSAppLinkConfig ios = 22;
|
||||||
|
|
||||||
|
// Android is Android Digital Asset Links / passkey trust config.
|
||||||
|
// Served in /.well-known/assetlinks.json for delegate_permission/common.get_login_creds.
|
||||||
|
// That response may be HTTP-cached (Cache-Control), and platform verifiers may cache longer;
|
||||||
|
// changes can take time to take effect.
|
||||||
|
AndroidAppLinkConfig android = 23;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IOSAppLinkConfig is iOS Associated Domains / passkey trust config.
|
||||||
|
// Served in /.well-known/apple-app-site-association as webcredentials.apps entry
|
||||||
|
// "{team_id}.{bundle_id}" (Apple's full App ID = Team ID prefix + Bundle ID).
|
||||||
|
// That response may be HTTP-cached (Cache-Control), and platform verifiers may cache longer;
|
||||||
|
// changes can take time to take effect.
|
||||||
|
message IOSAppLinkConfig {
|
||||||
|
// TeamID is the Apple Developer Team ID (exactly 10 alphanumeric characters), e.g. ABCDE12345.
|
||||||
|
// From Apple Developer account membership — not the Bundle ID.
|
||||||
|
// Empty clears the value on update.
|
||||||
|
string team_id = 1 [(validate.rules).string = {ignore_empty: true, len: 10, pattern: "^[A-Z0-9]+$"}];
|
||||||
|
|
||||||
|
// BundleID is the Xcode / CFBundleIdentifier, e.g. com.example.app.
|
||||||
|
// Not the full Apple App ID; do not include the Team ID prefix here.
|
||||||
|
// Empty clears the value on update.
|
||||||
|
string bundle_id = 2 [(validate.rules).string = {ignore_empty: true, min_len: 1, max_len: 200}];
|
||||||
|
}
|
||||||
|
|
||||||
|
// AndroidAppLinkConfig is Android Digital Asset Links / passkey trust config.
|
||||||
|
// Served in /.well-known/assetlinks.json for delegate_permission/common.get_login_creds.
|
||||||
|
// That response may be HTTP-cached (Cache-Control), and platform verifiers may cache longer;
|
||||||
|
// changes can take time to take effect.
|
||||||
|
message AndroidAppLinkConfig {
|
||||||
|
// PackageName is the Android applicationId / package name from the app manifest.
|
||||||
|
// Empty clears the value on update.
|
||||||
|
string package_name = 1 [(validate.rules).string = {ignore_empty: true, min_len: 1, max_len: 200}];
|
||||||
|
|
||||||
|
// SHA256CertFingerprints are signing certificate fingerprints (64 hex chars, optional colons).
|
||||||
|
// Include debug and release fingerprints as needed. At most 20 entries.
|
||||||
|
repeated string sha256_cert_fingerprints = 2 [(validate.rules).repeated = {
|
||||||
|
max_items: 20,
|
||||||
|
items: {
|
||||||
|
string: {
|
||||||
|
pattern: "^([0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2}){31}|[0-9A-Fa-f]{64})$";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10382,6 +10382,16 @@ message AddOIDCAppRequest {
|
|||||||
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
|
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
zitadel.app.v1.IOSAppLinkConfig ios = 20 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "iOS Associated Domains / passkey trust config. Served in /.well-known/apple-app-site-association as webcredentials.apps entry \"{team_id}.{bundle_id}\" (Apple's full App ID). That response may be HTTP-cached (Cache-Control), and platform verifiers may cache longer; changes can take time to take effect.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
zitadel.app.v1.AndroidAppLinkConfig android = 21 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Android Digital Asset Links / passkey trust config. Served in /.well-known/assetlinks.json for delegate_permission/common.get_login_creds. That response may be HTTP-cached (Cache-Control), and platform verifiers may cache longer; changes can take time to take effect.";
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
message AddOIDCAppResponse {
|
message AddOIDCAppResponse {
|
||||||
@@ -10573,6 +10583,16 @@ message UpdateOIDCAppConfigRequest {
|
|||||||
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
|
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
zitadel.app.v1.IOSAppLinkConfig ios = 19 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "iOS Associated Domains / passkey trust config. Served in /.well-known/apple-app-site-association as webcredentials.apps entry \"{team_id}.{bundle_id}\" (Apple's full App ID). That response may be HTTP-cached (Cache-Control), and platform verifiers may cache longer; changes can take time to take effect.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
zitadel.app.v1.AndroidAppLinkConfig android = 20 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Android Digital Asset Links / passkey trust config. Served in /.well-known/assetlinks.json for delegate_permission/common.get_login_creds. That response may be HTTP-cached (Cache-Control), and platform verifiers may cache longer; changes can take time to take effect.";
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateOIDCAppConfigResponse {
|
message UpdateOIDCAppConfigResponse {
|
||||||
|
|||||||
Reference in New Issue
Block a user