Merge branch 'main' into next

This commit is contained in:
Livio Spring 2024-03-01 07:36:22 +01:00
commit 704197c282
No known key found for this signature in database
GPG Key ID: 26BB1C2FA5952CF0
367 changed files with 16478 additions and 2089 deletions

View File

@ -33,10 +33,10 @@ updates:
commit-message:
prefix: chore
include: scope
- package-ecosystem: "docker"
directory: "/build/operator"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
interval: weekly
time: "02:00"
open-pull-requests-limit: 10
commit-message:

View File

@ -41,18 +41,9 @@ jobs:
console_cache_path: ${{ needs.console.outputs.cache_path }}
version: ${{ needs.version.outputs.version }}
core-unit-test:
core-test:
needs: core
uses: ./.github/workflows/core-unit-test.yml
with:
go_version: "1.21"
core_cache_key: ${{ needs.core.outputs.cache_key }}
core_cache_path: ${{ needs.core.outputs.cache_path }}
crdb_version: "23.1.13"
core-integration-test:
needs: core
uses: ./.github/workflows/core-integration-test.yml
uses: ./.github/workflows/core-test.yml
with:
go_version: "1.21"
core_cache_key: ${{ needs.core.outputs.cache_key }}
@ -91,7 +82,7 @@ jobs:
issues: write
pull-requests: write
needs:
[version, core-unit-test, core-integration-test, lint, container, e2e]
[version, core-test, lint, container, e2e]
if: ${{ github.event_name == 'workflow_dispatch' }}
secrets:
GCR_JSON_KEY_BASE64: ${{ secrets.GCR_JSON_KEY_BASE64 }}

View File

@ -1,72 +0,0 @@
name: Unit test core
on:
workflow_call:
inputs:
go_version:
required: true
type: string
core_cache_key:
required: true
type: string
core_cache_path:
required: true
type: string
crdb_version:
required: false
type: string
jobs:
test:
runs-on:
group: zitadel-public
steps:
-
uses: actions/checkout@v3
-
uses: actions/setup-go@v4
with:
go-version: ${{ inputs.go_version }}
-
uses: actions/cache/restore@v3
timeout-minutes: 1
name: restore core
id: restore-core
with:
path: ${{ inputs.core_cache_path }}
key: ${{ inputs.core_cache_key }}
fail-on-cache-miss: true
-
id: go-cache-path
name: set cache path
run: echo "GO_CACHE_PATH=$(go env GOCACHE)" >> $GITHUB_OUTPUT
-
uses: actions/cache/restore@v3
id: cache
timeout-minutes: 1
continue-on-error: true
name: restore previous results
with:
key: unit-test-${{ inputs.core_cache_key }}
restore-keys: |
unit-test-core-
path: ${{ steps.go-cache-path.outputs.GO_CACHE_PATH }}
-
name: test
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
run: export ZITADEL_CRDB_VERSION="${{ inputs.crdb_version }}" && make core_unit_test
-
name: publish coverage
uses: codecov/codecov-action@v3.1.4
with:
file: profile.cov
name: core-unit-tests
flags: core-unit-tests
-
uses: actions/cache/save@v3
name: cache results
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
with:
key: unit-test-${{ inputs.core_cache_key }}
path: ${{ steps.go-cache-path.outputs.GO_CACHE_PATH }}

View File

@ -108,13 +108,13 @@ Please make sure you cover your changes with tests before marking a Pull Request
The code consists of the following parts:
| name | description | language | where to find |
| --------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------- | -------------------------------------------------- |
| backend | Service that serves the grpc(-web) and RESTful API | [go](https://go.dev) | [API implementation](./internal/api/grpc) |
| console | Frontend the user interacts with after he is logged in | [Angular](https://angular.io), [Typescript](https://www.typescriptlang.org) | [./console](./console) |
| name | description | language | where to find |
| --------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------- | -------------------------------------------------- |
| backend | Service that serves the grpc(-web) and RESTful API | [go](https://go.dev) | [API implementation](./internal/api/grpc) |
| console | Frontend the user interacts with after log in | [Angular](https://angular.io), [Typescript](https://www.typescriptlang.org) | [./console](./console) |
| login | Server side rendered frontend the user interacts with during login | [go](https://go.dev), [go templates](https://pkg.go.dev/html/template) | [./internal/api/ui/login](./internal/api/ui/login) |
| API definitions | Specifications of the API | [Protobuf](https://developers.google.com/protocol-buffers) | [./proto/zitadel](./proto/zitadel) |
| docs | Project documentation made with docusaurus | [Docusaurus](https://docusaurus.io/) | [./docs](./docs) |
| API definitions | Specifications of the API | [Protobuf](https://developers.google.com/protocol-buffers) | [./proto/zitadel](./proto/zitadel) |
| docs | Project documentation made with docusaurus | [Docusaurus](https://docusaurus.io/) | [./docs](./docs) |
Please validate and test the code before you contribute.
@ -123,6 +123,24 @@ We add the label "good first issue" for problems we think are a good starting po
- [Issues for first time contributors](https://github.com/zitadel/zitadel/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
- [All issues](https://github.com/zitadel/zitadel/issues)
### General Guidelines
#### Gender Neutrality and Inclusive Language
We are committed to creating a welcoming and inclusive community for all developers, regardless of their gender identity or expression. To achieve this, we are actively working to ensure that our contribution guidelines are gender-neutral and use inclusive language.
**Use gender-neutral pronouns**:
Don't use gender-specific pronouns unless the person you're referring to is actually that gender.
In particular, don't use he, him, his, she, or her as gender-neutral pronouns, and don't use he/she or (s)he or other such punctuational approaches. Instead, use the singular they.
**Choose gender-neutral alternatives**:
Opt for gender-neutral terms instead of gendered ones whenever possible.
Replace "policeman" with "police officer," "manpower" with "workforce," and "businessman" with "entrepreneur" or "businessperson."
**Avoid ableist language**:
Ableist language includes words or phrases such as crazy, insane, blind to or blind eye to, cripple, dumb, and others.
Choose alternative words depending on the context.
### Backend/login
By executing the commands from this section, you run everything you need to develop the ZITADEL backend locally.
@ -163,25 +181,41 @@ You can now run and debug the binary in .artifacts/zitadel/zitadel using your fa
You can test if ZITADEL does what you expect by using the UI at http://localhost:8080/ui/console.
Also, you can verify the data by running `cockroach sql --database zitadel --insecure` and running SQL queries.
As soon as you are ready to battle test your changes, run the end-to-end tests.
#### Run Local Unit Tests
#### Running the tests
Running the tests with docker doesn't require you to take care of other dependencies than docker and make.
To test the code without dependencies, run the unit tests:
```bash
# Build the production binary (unit tests are executed, too)
make core_build console_build
GOOS=linux make compile_pipeline
make core_unit_test
```
# Pack the binary into a docker image
DOCKER_BUILDKIT=1 docker build --file build/Dockerfile . -t zitadel:local
#### Run Local Integration Tests
To test the database-connected gRPC API, run PostgreSQL and CockroachDB, set up a ZITADEL instance and run the tests including integration tests:
```bash
export INTEGRATION_DB_FLAVOR="postgres" ZITADEL_MASTERKEY="MasterkeyNeedsToHave32Characters"
docker compose -f internal/integration/config/docker-compose.yaml up --pull always --wait ${INTEGRATION_DB_FLAVOR}
make core_integration_test
docker compose -f internal/integration/config/docker-compose.yaml down
```
Repeat the above with `INTEGRATION_DB_FLAVOR="postgres"`.
#### Run Local End-to-End Tests
To test the whole system, including the console UI and the login UI, run the E2E tests.
```bash
# Build the production docker image
export ZITADEL_IMAGE=zitadel:local GOOS=linux
make docker_image
# If you made changes in the e2e directory, make sure you reformat the files
make console_lint
# Run the tests
ZITADEL_IMAGE=zitadel:local docker compose --file ./e2e/config/host.docker.internal/docker-compose.yaml run --service-ports e2e
docker compose --file ./e2e/config/host.docker.internal/docker-compose.yaml run --service-ports e2e
```
When you are happy with your changes, you can cleanup your environment.
@ -191,7 +225,7 @@ When you are happy with your changes, you can cleanup your environment.
docker compose --file ./e2e/config/host.docker.internal/docker-compose.yaml down
```
#### Running the tests without docker
#### Run Local End-to-End Tests Against Your Dev Server Console
If you also make [changes to the console](#console), you can run the test suite against your locally built backend code and frontend server.
But you will have to install the relevant node dependencies.
@ -214,19 +248,6 @@ When you are happy with your changes, you can cleanup your environment.
docker compose --file ./e2e/config/host.docker.internal/docker-compose.yaml down
```
#### Integration tests
In order to run the integrations tests for the gRPC API, PostgreSQL and CockroachDB must be started and initialized:
```bash
export INTEGRATION_DB_FLAVOR="postgres" ZITADEL_MASTERKEY="MasterkeyNeedsToHave32Characters"
docker compose -f internal/integration/config/docker-compose.yaml up --pull always --wait ${INTEGRATION_DB_FLAVOR}
make core_integration_test
docker compose -f internal/integration/config/docker-compose.yaml down
```
The above can be repeated with `INTEGRATION_DB_FLAVOR="postgres"`.
### Console
By executing the commands from this section, you run everything you need to develop the console locally.

View File

@ -5,10 +5,15 @@ gen_zitadel_path := "$(go_bin)/protoc-gen-zitadel"
now := $(shell date --rfc-3339=seconds | sed 's/ /T/')
VERSION ?= development-$(now)
COMMIT_SHA ?= $(shell git rev-parse HEAD)
ZITADEL_IMAGE ?= zitadel:local
.PHONY: compile
compile: core_build console_build compile_pipeline
.PHONY: docker_image
docker_image: compile
DOCKER_BUILDKIT=1 docker build -f build/Dockerfile -t $(ZITADEL_IMAGE) .
.PHONY: compile_pipeline
compile_pipeline: console_move
CGO_ENABLED=0 go build -o zitadel -v -ldflags="-s -w -X 'github.com/zitadel/zitadel/cmd/build.commit=$(COMMIT_SHA)' -X 'github.com/zitadel/zitadel/cmd/build.date=$(now)' -X 'github.com/zitadel/zitadel/cmd/build.version=$(VERSION)' "
@ -108,14 +113,14 @@ core_integration_setup:
.PHONY: core_integration_test
core_integration_test: core_integration_setup
go test -tags=integration -race -p 1 -coverprofile=profile.cov -coverpkg=./internal/...,./cmd/... ./internal/integration ./internal/api/grpc/... ./internal/notification/handlers/... ./internal/api/oidc/...
go test -tags=integration -race -p 1 -coverprofile=profile.cov -coverpkg=./internal/...,./cmd/... ./...
.PHONY: console_lint
console_lint:
cd console && \
yarn lint
.PHONE: core_lint
.PHONY: core_lint
core_lint:
golangci-lint run \
--timeout 10m \

View File

@ -1,7 +1,7 @@
Log:
Level: info # ZITADEL_LOG_LEVEL
Formatter:
Format: text # ZITADEL_LOG_LEVEL
Format: text # ZITADEL_LOG_FORMATTER_FORMAT
# Exposes metrics on /debug/metrics
Metrics:
@ -29,7 +29,7 @@ Telemetry:
# As long as Enabled is true, ZITADEL tries to send usage data to the configured Telemetry.Endpoints.
# Data is projected by ZITADEL even if Enabled is false.
# This means that switching this to true makes ZITADEL try to send past data.
Enabled: false
Enabled: false # ZITADEL_TELEMETRY_ENABLED
# Push telemetry data to all these endpoints at least once using an HTTP POST request.
# If one endpoint returns an unsuccessful response code or times out,
# ZITADEL retries to push the data point to all configured endpoints until it succeeds.
@ -40,7 +40,9 @@ Telemetry:
Endpoints:
- https://httpbin.org/post
# These headers are sent with every request to the configured endpoints.
Headers:
# Configure headers by environment variable using a JSON string with header values as arrays, like this:
# ZITADEL_TELEMETRY_HEADERS='{"header1": ["value1"], "header2": ["value2", "value3"]}'
Headers: # ZITADEL_TELEMETRY_HEADERS
# single-value: "single-value"
# multi-value:
# - "multi-value-1"
@ -85,7 +87,7 @@ HTTP2HostHeader: ":authority" # ZITADEL_HTTP2HOSTHEADER
# Header name of HTTP1 calls from which the instance will be matched
HTTP1HostHeader: "host" # ZITADEL_HTTP1HOSTHEADER
WebAuthNName: ZITADEL # ZITADEL_WEBAUTHN_NAME
WebAuthNName: ZITADEL # ZITADEL_WEBAUTHNNAME
Database:
# ZITADEL manages three database connection pools.
@ -170,7 +172,7 @@ Machine:
Enabled: true # ZITADEL_MACHINE_IDENTIFICATION_WEBHOOK_ENABLED
Url: "http://metadata.google.internal/computeMetadata/v1/instance/id" # ZITADEL_MACHINE_IDENTIFICATION_WEBHOOK_URL
Headers:
"Metadata-Flavor": "Google" # ZITADEL_MACHINE_IDENTIFICATION_WEBHOOK_HEADERS
"Metadata-Flavor": "Google"
#
# AWS EC2 IMDSv1 Configuration: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html
# Webhook:
@ -205,7 +207,7 @@ Projections:
# Time interval between scheduled projections
RequeueEvery: 60s # ZITADEL_PROJECTIONS_REQUEUEEVERY
# Time between retried database statements resulting from projected events
RetryFailedAfter: 1s # ZITADEL_PROJECTIONS_RETRYFAILED
RetryFailedAfter: 1s # ZITADEL_PROJECTIONS_RETRYFAILEDAFTER
# Retried execution number of database statements resulting from projected events
MaxFailureCount: 5 # ZITADEL_PROJECTIONS_MAXFAILURECOUNT
# Limit of returned events per query
@ -332,11 +334,6 @@ OIDC:
Path: /oauth/v2/device_authorization # ZITADEL_OIDC_CUSTOMENDPOINTS_DEVICEAUTH_PATH
DefaultLoginURLV2: "/login?authRequest=" # ZITADEL_OIDC_DEFAULTLOGINURLV2
DefaultLogoutURLV2: "/logout?post_logout_redirect=" # ZITADEL_OIDC_DEFAULTLOGOUTURLV2
Features:
# Wheter projection triggers are used in the new Introspection implementation.
TriggerIntrospectionProjections: false
# Allows fallback to the Legacy Introspection implementation
LegacyIntrospection: false
PublicKeyCacheMaxAge: 24h # ZITADEL_OIDC_PUBLICKEYCACHEMAXAGE
SAML:
@ -378,28 +375,28 @@ Console:
EncryptionKeys:
DomainVerification:
EncryptionKeyID: "domainVerificationKey" # ZITADEL_ENCRYPTIONKEYS_DOMAINVERIFICATION_ENCRYPTIONKEYID
DecryptionKeyIDs:
DecryptionKeyIDs: # ZITADEL_ENCRYPTIONKEYS_DOMAINVERIFICATION_DECRYPTIONKEYIDS (comma separated list)
IDPConfig:
EncryptionKeyID: "idpConfigKey" # ZITADEL_ENCRYPTIONKEYS_IDPCONFIG_ENCRYPTIONKEYID
DecryptionKeyIDs:
DecryptionKeyIDs: # ZITADEL_ENCRYPTIONKEYS_IDPCONFIG_DECRYPTIONKEYIDS (comma separated list)
OIDC:
EncryptionKeyID: "oidcKey" # ZITADEL_ENCRYPTIONKEYS_OIDC_ENCRYPTIONKEYID
DecryptionKeyIDs:
DecryptionKeyIDs: # ZITADEL_ENCRYPTIONKEYS_OIDC_DECRYPTIONKEYIDS (comma separated list)
SAML:
EncryptionKeyID: "samlKey" # ZITADEL_ENCRYPTIONKEYS_SAML_ENCRYPTIONKEYID
DecryptionKeyIDs:
DecryptionKeyIDs: # ZITADEL_ENCRYPTIONKEYS_SAML_DECRYPTIONKEYIDS (comma separated list)
OTP:
EncryptionKeyID: "otpKey" # ZITADEL_ENCRYPTIONKEYS_OTP_ENCRYPTIONKEYID
DecryptionKeyIDs:
DecryptionKeyIDs: # ZITADEL_ENCRYPTIONKEYS_OTP_DECRYPTIONKEYIDS (comma separated list)
SMS:
EncryptionKeyID: "smsKey" # ZITADEL_ENCRYPTIONKEYS_SMS_ENCRYPTIONKEYID
DecryptionKeyIDs:
DecryptionKeyIDs: # ZITADEL_ENCRYPTIONKEYS_SMS_DECRYPTIONKEYIDS (comma separated list)
SMTP:
EncryptionKeyID: "smtpKey" # ZITADEL_ENCRYPTIONKEYS_SMTP_ENCRYPTIONKEYID
DecryptionKeyIDs:
DecryptionKeyIDs: # ZITADEL_ENCRYPTIONKEYS_SMTP_DECRYPTIONKEYIDS (comma separated list)
User:
EncryptionKeyID: "userKey" # ZITADEL_ENCRYPTIONKEYS_USER_ENCRYPTIONKEYID
DecryptionKeyIDs:
DecryptionKeyIDs: # ZITADEL_ENCRYPTIONKEYS_USER_DECRYPTIONKEYIDS (comma separated list)
CSRFCookieKeyID: "csrfCookieKey" # ZITADEL_ENCRYPTIONKEYS_CSRFCOOKIEKEYID
UserAgentCookieKeyID: "userAgentCookieKey" # ZITADEL_ENCRYPTIONKEYS_USERAGENTCOOKIEKEYID
@ -426,8 +423,9 @@ SystemAPIUsers:
# - superuser2:
# # If no memberships are specified, the user has a membership of type System with the role "SYSTEM_OWNER"
# KeyData: <base64 encoded key> # or you can directly embed it as base64 encoded value
# Configure the SystemAPIUsers by environment variable using JSON notation:
# ZITADEL_SYSTEMAPIUSERS='{"systemuser":{"Path":"/path/to/superuser/key.pem"},"systemuser2":{"KeyData":"<base64 encoded key>"}}'
#TODO: remove as soon as possible
SystemDefaults:
SecretGenerators:
PasswordSaltCost: 14 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_PASSWORDSALTCOST
@ -457,13 +455,13 @@ SystemDefaults:
# Threads: 4 # ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_HASHER_THREADS
# Hasher:
# Algorithm: "scrypt"
# Cost: 15
# Algorithm: "scrypt" # ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_HASHER_ALGORITHM
# Cost: 15 # ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_HASHER_COST
# Hasher:
# Algorithm: "pbkdf2"
# Rounds: 290000
# Hash: "sha256" # Can be "sha1", "sha224", "sha256", "sha384" or "sha512"
# Algorithm: "pbkdf2" # ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_HASHER_ALGORITHM
# Rounds: 290000 # ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_HASHER_ROUNDS
# Hash: "sha256" # Can be "sha1", "sha224", "sha256", "sha384" or "sha512" # ZITADEL_SYSTEMDEFAULTS_PASSWORDHASHER_HASHER_HASH
# Verifiers enable the possibility of verifying
# passwords that are previously hashed using another
@ -509,7 +507,7 @@ SystemDefaults:
Actions:
HTTP:
# Wildcard sub domains are currently unsupported
DenyList:
DenyList: # ZITADEL_ACTIONS_HTTP_DENYLIST (comma separated list)
- localhost
- "127.0.0.1"
@ -542,6 +540,8 @@ Quotas:
Eventstore:
# Sets the maximum duration of transactions pushing events
PushTimeout: 15s #ZITADEL_EVENTSTORE_PUSHTIMEOUT
# Maximum amount of push retries in case of primary key violation on the sequence
MaxRetries: 5 #ZITADEL_EVENTSTORE_MAXRETRIES
DefaultInstance:
InstanceName: ZITADEL # ZITADEL_DEFAULTINSTANCE_INSTANCENAME
@ -727,6 +727,9 @@ DefaultInstance:
From: # ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_FROM
FromName: # ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_FROMNAME
ReplyToAddress: # ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_REPLYTOADDRESS
# Configure the MessageTexts by environment variable using JSON notation:
# ZITADEL_DEFAULTINSTANCE_MESSAGETEXTS='[{"messageTextType": "InitCode", "title": "My custom title"},{"messageTextType": "PasswordReset", "greeting": "Hi there!"}]'
# Beware that if you configure the MessageTexts by environment variable, all the default MessageTexts are lost.
MessageTexts:
- MessageTextType: InitCode
Language: de
@ -824,8 +827,13 @@ DefaultInstance:
Greeting: Hello {{.DisplayName}},
Text: The password of your user has changed. If this change was not done by you, please be advised to immediately reset your password.
ButtonText: Login
# Once a feature is set on the instance (true or false), system level feature settings
# will be ignored until instance level features are reset.
Features:
- FeatureLoginDefaultOrg: true
LoginDefaultOrg: true # ZITADEL_DEFAULTINSTANCE_FEATURES_LOGINDEFAULTORG
# TriggerIntrospectionProjections: false # ZITADEL_DEFAULTINSTANCE_FEATURES_TRIGGERINTROSPECTIONPROJECTIONS
# LegacyIntrospection: false # ZITADEL_DEFAULTINSTANCE_FEATURES_LEGACYINTROSPECTION
Limits:
# AuditLogRetention limits the number of events that can be queried via the events API by their age.
# A value of "0s" means that all events are available.
@ -857,7 +865,9 @@ DefaultInstance:
# "actions.all.runs.seconds"
# The sum of all actions run durations in seconds
Items:
# Configure the Items by environment variable using JSON notation:
# ZITADEL_DEFAULTINSTANCE_QUOTAS_ITEMS='[{"unit": "requests.all.authenticated", "notifications": [{"percent": 100}]}]'
Items: # ZITADEL_DEFAULTINSTANCE_QUOTAS_ITEMS
# - Unit: "requests.all.authenticated"
# # From defines the starting time from which the current quota period is calculated.
# # This is relevant for querying the current usage.
@ -884,6 +894,9 @@ DefaultInstance:
AuditLogRetention: 0s # ZITADEL_AUDITLOGRETENTION
InternalAuthZ:
# Configure the RolePermissionMappings by environment variable using JSON notation:
# ZITADEL_INTERNALAUTHZ_ROLEPERMISSIONMAPPINGS='[{"role": "IAM_OWNER", "permissions": ["iam.read", "iam.write"]}]'
# Beware that if you configure the RolePermissionMappings by environment variable, all the default RolePermissionMappings are lost.
RolePermissionMappings:
- Role: "SYSTEM_OWNER"
Permissions:
@ -896,7 +909,9 @@ InternalAuthZ:
- "system.debug.read"
- "system.debug.write"
- "system.debug.delete"
- "system.feature.read"
- "system.feature.write"
- "system.feature.delete"
- "system.limits.write"
- "system.limits.delete"
- "system.quota.write"
@ -907,6 +922,7 @@ InternalAuthZ:
- "system.instance.read"
- "system.domain.read"
- "system.debug.read"
- "system.feature.read"
- "system.iam.member.read"
- Role: "IAM_OWNER"
Permissions:
@ -927,7 +943,9 @@ InternalAuthZ:
- "iam.flow.read"
- "iam.flow.write"
- "iam.flow.delete"
- "iam.feature.read"
- "iam.feature.write"
- "iam.feature.delete"
- "iam.restrictions.read"
- "iam.restrictions.write"
- "org.read"
@ -947,6 +965,9 @@ InternalAuthZ:
- "org.flow.read"
- "org.flow.write"
- "org.flow.delete"
- "org.feature.read"
- "org.feature.write"
- "org.feature.delete"
- "user.read"
- "user.global.read"
- "user.write"
@ -957,6 +978,9 @@ InternalAuthZ:
- "user.membership.read"
- "user.credential.write"
- "user.passkey.write"
- "user.feature.read"
- "user.feature.write"
- "user.feature.delete"
- "policy.read"
- "policy.write"
- "policy.delete"
@ -984,6 +1008,9 @@ InternalAuthZ:
- "session.delete"
- "execution.target.write"
- "execution.target.delete"
- "execution.read"
- "execution.write"
- "execution.delete"
- Role: "IAM_OWNER_VIEWER"
Permissions:
- "iam.read"
@ -993,15 +1020,18 @@ InternalAuthZ:
- "iam.action.read"
- "iam.flow.read"
- "iam.restrictions.read"
- "iam.feature.read"
- "org.read"
- "org.member.read"
- "org.idp.read"
- "org.action.read"
- "org.flow.read"
- "org.feature.read"
- "user.read"
- "user.global.read"
- "user.grant.read"
- "user.membership.read"
- "user.feature.read"
- "policy.read"
- "project.read"
- "project.member.read"
@ -1030,6 +1060,9 @@ InternalAuthZ:
- "org.flow.read"
- "org.flow.write"
- "org.flow.delete"
- "org.feature.read"
- "org.feature.write"
- "org.feature.delete"
- "user.read"
- "user.global.read"
- "user.write"
@ -1040,6 +1073,9 @@ InternalAuthZ:
- "user.membership.read"
- "user.credential.write"
- "user.passkey.write"
- "user.feature.read"
- "user.feature.write"
- "user.feature.delete"
- "policy.read"
- "policy.write"
- "policy.delete"
@ -1078,6 +1114,9 @@ InternalAuthZ:
- "user.grant.delete"
- "user.membership.read"
- "user.passkey.write"
- "user.feature.read"
- "user.feature.write"
- "user.feature.delete"
- "project.read"
- "project.member.read"
- "project.role.read"
@ -1087,6 +1126,13 @@ InternalAuthZ:
- "project.grant.delete"
- "project.grant.member.read"
- "session.delete"
- Role: "IAM_ADMIN_IMPERSONATOR"
Permissions:
- "admin.impersonation"
- "impersonation"
- Role: "IAM_END_USER_IMPERSONATOR"
Permissions:
- "impersonation"
- Role: "ORG_OWNER"
Permissions:
- "org.read"
@ -1105,6 +1151,9 @@ InternalAuthZ:
- "org.flow.read"
- "org.flow.write"
- "org.flow.delete"
- "org.feature.read"
- "org.feature.write"
- "org.feature.delete"
- "user.read"
- "user.global.read"
- "user.write"
@ -1115,6 +1164,9 @@ InternalAuthZ:
- "user.membership.read"
- "user.credential.write"
- "user.passkey.write"
- "user.feature.read"
- "user.feature.write"
- "user.feature.delete"
- "policy.read"
- "policy.write"
- "policy.delete"
@ -1148,6 +1200,9 @@ InternalAuthZ:
- "user.grant.write"
- "user.grant.delete"
- "user.membership.read"
- "user.feature.read"
- "user.feature.write"
- "user.feature.delete"
- "policy.read"
- "project.read"
- "project.role.read"
@ -1159,10 +1214,12 @@ InternalAuthZ:
- "org.idp.read"
- "org.action.read"
- "org.flow.read"
- "org.feature.read"
- "user.read"
- "user.global.read"
- "user.grant.read"
- "user.membership.read"
- "user.feature.read"
- "policy.read"
- "project.read"
- "project.member.read"
@ -1179,6 +1236,9 @@ InternalAuthZ:
- "org.idp.read"
- "org.idp.write"
- "org.idp.delete"
- "org.feature.read"
- "org.feature.write"
- "org.feature.delete"
- "policy.read"
- "policy.write"
- "policy.delete"
@ -1222,6 +1282,13 @@ InternalAuthZ:
- "policy.read"
- "project.read:self"
- "project.create"
- Role: "ORG_ADMIN_IMPERSONATOR"
Permissions:
- "admin.impersonation"
- "impersonation"
- Role: "ORG_END_USER_IMPERSONATOR"
Permissions:
- "impersonation"
- Role: "PROJECT_OWNER"
Permissions:
- "org.global.read"

35
cmd/hooks/complex.go Normal file
View File

@ -0,0 +1,35 @@
package hooks
import (
"encoding/json"
"net/http"
"reflect"
)
func SliceTypeStringDecode[T any](from, to reflect.Value) (any, error) {
into := make([]T, 0)
return complexTypeStringDecodeHook(from, to, into)
}
func MapTypeStringDecode[K ~string | ~int, V any](from, to reflect.Value) (any, error) {
into := make(map[K]V, 0)
return complexTypeStringDecodeHook(from, to, into)
}
func MapHTTPHeaderStringDecode(from, to reflect.Value) (any, error) {
into := http.Header{}
return complexTypeStringDecodeHook(from, to, into)
}
func complexTypeStringDecodeHook(from, to reflect.Value, out any) (any, error) {
fromInterface := from.Interface()
if to.Type() != reflect.TypeOf(out) {
return fromInterface, nil
}
data, ok := fromInterface.(string)
if !ok {
return fromInterface, nil
}
err := json.Unmarshal([]byte(data), &out)
return out, err
}

View File

@ -10,7 +10,6 @@ import (
internal_authz "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/config/hook"
"github.com/zitadel/zitadel/internal/config/network"
"github.com/zitadel/zitadel/internal/domain"
)
type Config struct {
@ -27,7 +26,6 @@ func MustNewConfig(v *viper.Viper) *Config {
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToTimeHookFunc(time.RFC3339),
mapstructure.StringToSliceHookFunc(","),
hook.EnumHookFunc(domain.FeatureString),
hook.EnumHookFunc(internal_authz.MemberTypeString),
)),
)

View File

@ -24,7 +24,7 @@ type FirstInstance struct {
Org command.InstanceOrgSetup
MachineKeyPath string
PatPath string
Features map[domain.Feature]any
Features *command.InstanceFeatures
instanceSetup command.InstanceSetup
userEncryptionKey *crypto.KeyConfig

View File

@ -10,7 +10,7 @@ import (
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/cmd/encryption"
"github.com/zitadel/zitadel/cmd/systemapi"
"github.com/zitadel/zitadel/cmd/hooks"
"github.com/zitadel/zitadel/internal/actions"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/oidc"
@ -19,7 +19,6 @@ import (
"github.com/zitadel/zitadel/internal/config/hook"
"github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/notification/handlers"
@ -47,7 +46,7 @@ type Config struct {
Login login.Config
WebAuthNName string
Telemetry *handlers.TelemetryPusherConfig
SystemAPIUsers systemapi.Users
SystemAPIUsers map[string]*authz.SystemAPIUser
}
type InitProjections struct {
@ -67,9 +66,9 @@ func MustNewConfig(v *viper.Viper) *Config {
mapstructure.StringToTimeHookFunc(time.RFC3339),
mapstructure.StringToSliceHookFunc(","),
database.DecodeHook,
hook.EnumHookFunc(domain.FeatureString),
hook.EnumHookFunc(authz.MemberTypeString),
actions.HTTPConfigDecodeHook,
hooks.MapTypeStringDecode[string, *authz.SystemAPIUser],
)),
)
logging.OnError(err).Fatal("unable to read default config")
@ -126,8 +125,6 @@ func MustNewSteps(v *viper.Viper) *Steps {
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToTimeHookFunc(time.RFC3339),
mapstructure.StringToSliceHookFunc(","),
hook.EnumHookFunc(domain.FeatureString),
systemapi.UsersDecodeHook,
)),
)
logging.OnError(err).Fatal("unable to read steps")

View File

@ -8,7 +8,7 @@ import (
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/cmd/encryption"
"github.com/zitadel/zitadel/cmd/systemapi"
"github.com/zitadel/zitadel/cmd/hooks"
"github.com/zitadel/zitadel/internal/actions"
admin_es "github.com/zitadel/zitadel/internal/admin/repository/eventsourcing"
internal_authz "github.com/zitadel/zitadel/internal/api/authz"
@ -61,7 +61,7 @@ type Config struct {
EncryptionKeys *encryption.EncryptionKeyConfig
DefaultInstance command.InstanceSetup
AuditLogRetention time.Duration
SystemAPIUsers systemapi.Users
SystemAPIUsers map[string]*internal_authz.SystemAPIUser
CustomerPortal string
Machine *id.Config
Actions *actions.Config
@ -84,6 +84,12 @@ func MustNewConfig(v *viper.Viper) *Config {
err := v.Unmarshal(config,
viper.DecodeHook(mapstructure.ComposeDecodeHookFunc(
hooks.SliceTypeStringDecode[*domain.CustomMessageText],
hooks.SliceTypeStringDecode[*command.SetQuota],
hooks.SliceTypeStringDecode[internal_authz.RoleMapping],
hooks.MapTypeStringDecode[string, *internal_authz.SystemAPIUser],
hooks.MapTypeStringDecode[domain.Feature, any],
hooks.MapHTTPHeaderStringDecode,
hook.Base64ToBytesHookFunc(),
hook.TagToLanguageHookFunc(),
mapstructure.StringToTimeDurationHookFunc(),
@ -91,8 +97,6 @@ func MustNewConfig(v *viper.Viper) *Config {
mapstructure.StringToSliceHookFunc(","),
database.DecodeHook,
actions.HTTPConfigDecodeHook,
systemapi.UsersDecodeHook,
hook.EnumHookFunc(domain.FeatureString),
hook.EnumHookFunc(internal_authz.MemberTypeString),
)),
)

View File

@ -1,13 +1,17 @@
package start
import (
"reflect"
"encoding/base64"
"fmt"
"net"
"net/http"
"strings"
"testing"
"github.com/muhlemmer/gu"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/actions"
"github.com/zitadel/zitadel/internal/api/authz"
@ -16,29 +20,76 @@ import (
)
func TestMustNewConfig(t *testing.T) {
encodedKey := "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF6aStGRlNKTDdmNXl3NEtUd3pnTQpQMzRlUEd5Y20vTStrVDBNN1Y0Q2d4NVYzRWFESXZUUUtUTGZCYUVCNDV6YjlMdGpJWHpEdzByWFJvUzJoTzZ0CmgrQ1lRQ3ozS0N2aDA5QzBJenhaaUIySVMzSC9hVCs1Qng5RUZZK3ZuQWtaamNjYnlHNVlOUnZtdE9sbnZJZUkKSDdxWjB0RXdrUGZGNUdFWk5QSlB0bXkzVUdWN2lvZmRWUVMxeFJqNzMrYU13NXJ2SDREOElkeWlBQzNWZWtJYgpwdDBWajBTVVgzRHdLdG9nMzM3QnpUaVBrM2FYUkYwc2JGaFFvcWRKUkk4TnFnWmpDd2pxOXlmSTV0eXhZc3duCitKR3pIR2RIdlczaWRPRGxtd0V0NUsycGFzaVJJV0syT0dmcSt3MEVjbHRRSGFidXFFUGdabG1oQ2tSZE5maXgKQndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=="
decodedKey, err := base64.StdEncoding.DecodeString(encodedKey)
if err != nil {
t.Fatal(err)
}
type args struct {
yaml string
}
tests := []struct {
name string
args args
want *Config
want func(*testing.T, *Config)
}{{
name: "actions deny list ok",
args: args{
yaml: `
Actions:
HTTP:
DenyList:
- localhost
- 127.0.0.1
- foobar
Log:
Level: info
`},
want: func(t *testing.T, config *Config) {
assert.Equal(t, config.Actions.HTTP.DenyList, []actions.AddressChecker{
&actions.DomainChecker{Domain: "localhost"},
&actions.IPChecker{IP: net.ParseIP("127.0.0.1")},
&actions.DomainChecker{Domain: "foobar"}})
},
}, {
name: "actions deny list string ok",
args: args{
yaml: `
Actions:
HTTP:
DenyList: localhost,127.0.0.1,foobar
Log:
Level: info
`},
want: func(t *testing.T, config *Config) {
assert.Equal(t, config.Actions.HTTP.DenyList, []actions.AddressChecker{
&actions.DomainChecker{Domain: "localhost"},
&actions.IPChecker{IP: net.ParseIP("127.0.0.1")},
&actions.DomainChecker{Domain: "foobar"}})
},
}, {
name: "features ok",
args: args{yaml: `
DefaultInstance:
Features:
- FeatureLoginDefaultOrg: true
LoginDefaultOrg: true
LegacyIntrospection: true
TriggerIntrospectionProjections: true
Log:
Level: info
Actions:
HTTP:
DenyList: []
`},
want: &Config{
DefaultInstance: command.InstanceSetup{
Features: map[domain.Feature]any{
domain.FeatureLoginDefaultOrg: true,
},
},
want: func(t *testing.T, config *Config) {
assert.Equal(t, config.DefaultInstance.Features, &command.InstanceFeatures{
LoginDefaultOrg: gu.Ptr(true),
LegacyIntrospection: gu.Ptr(true),
TriggerIntrospectionProjections: gu.Ptr(true),
})
},
}, {
name: "membership types ok",
name: "system api users ok",
args: args{yaml: `
SystemAPIUsers:
- superuser:
@ -46,9 +97,14 @@ SystemAPIUsers:
- MemberType: System
- MemberType: Organization
- MemberType: IAM
Log:
Level: info
Actions:
HTTP:
DenyList: []
`},
want: &Config{
SystemAPIUsers: map[string]*authz.SystemAPIUser{
want: func(t *testing.T, config *Config) {
assert.Equal(t, config.SystemAPIUsers, map[string]*authz.SystemAPIUser{
"superuser": {
Memberships: authz.Memberships{{
MemberType: authz.MemberTypeSystem,
@ -58,27 +114,121 @@ SystemAPIUsers:
MemberType: authz.MemberTypeIAM,
}},
},
},
})
},
}, {
name: "system api users string ok",
args: args{yaml: fmt.Sprintf(`
SystemAPIUsers: >
{"systemuser": {"path": "/path/to/superuser/key.pem"}, "systemuser2": {"keyData": "%s"}}
Log:
Level: info
Actions:
HTTP:
DenyList: []
`, encodedKey)},
want: func(t *testing.T, config *Config) {
assert.Equal(t, config.SystemAPIUsers, map[string]*authz.SystemAPIUser{
"systemuser": {
Path: "/path/to/superuser/key.pem",
},
"systemuser2": {
KeyData: decodedKey,
},
})
},
}, {
name: "headers ok",
args: args{yaml: `
Telemetry:
Headers:
single-value: single-value
multi-value:
- multi-value1
- multi-value2
Log:
Level: info
Actions:
HTTP:
DenyList: []
`},
want: func(t *testing.T, config *Config) {
assert.Equal(t, config.Telemetry.Headers, http.Header{
"single-value": []string{"single-value"},
"multi-value": []string{"multi-value1", "multi-value2"},
})
},
}, {
name: "headers string ok",
args: args{yaml: `
Telemetry:
Headers: >
{"single-value": "single-value", "multi-value": ["multi-value1", "multi-value2"]}
Log:
Level: info
Actions:
HTTP:
DenyList: []
`},
want: func(t *testing.T, config *Config) {
assert.Equal(t, config.Telemetry.Headers, http.Header{
"single-value": []string{"single-value"},
"multi-value": []string{"multi-value1", "multi-value2"},
})
},
}, {
name: "message texts ok",
args: args{yaml: `
DefaultInstance:
MessageTexts:
- MessageTextType: InitCode
Title: foo
- MessageTextType: PasswordReset
Greeting: bar
Log:
Level: info
Actions:
HTTP:
DenyList: []
`},
want: func(t *testing.T, config *Config) {
assert.Equal(t, config.DefaultInstance.MessageTexts, []*domain.CustomMessageText{{
MessageTextType: "InitCode",
Title: "foo",
}, {
MessageTextType: "PasswordReset",
Greeting: "bar",
}})
},
}, {
name: "message texts string ok",
args: args{yaml: `
DefaultInstance:
MessageTexts: >
[{"messageTextType": "InitCode", "title": "foo"}, {"messageTextType": "PasswordReset", "greeting": "bar"}]
Log:
Level: info
Actions:
HTTP:
DenyList: []
`},
want: func(t *testing.T, config *Config) {
assert.Equal(t, config.DefaultInstance.MessageTexts, []*domain.CustomMessageText{{
MessageTextType: "InitCode",
Title: "foo",
}, {
MessageTextType: "PasswordReset",
Greeting: "bar",
}})
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := viper.New()
v.SetConfigType("yaml")
err := v.ReadConfig(strings.NewReader(`Log:
Level: info
Actions:
HTTP:
DenyList: []
` + tt.args.yaml))
require.NoError(t, err)
tt.want.Log = &logging.Config{Level: "info"}
tt.want.Actions = &actions.Config{HTTP: actions.HTTPConfig{DenyList: []actions.AddressChecker{}}}
require.NoError(t, tt.want.Log.SetLogger())
require.NoError(t, v.ReadConfig(strings.NewReader(tt.args.yaml)))
got := MustNewConfig(v)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("MustNewConfig() = %v, want %v", got, tt.want)
}
tt.want(t, got)
})
}
}

View File

@ -2,34 +2,31 @@ package start
import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/cmd/key"
"github.com/zitadel/zitadel/cmd/tls"
)
var tlsMode *string
var (
startFlagSet = &pflag.FlagSet{}
)
func init() {
startFlagSet.Uint16("port", 0, "port to run ZITADEL on")
startFlagSet.String("externalDomain", "", "domain ZITADEL will be exposed on")
startFlagSet.String("externalPort", "", "port ZITADEL will be exposed on")
}
func startFlags(cmd *cobra.Command) {
bindUint16Flag(cmd, "port", "port to run ZITADEL on")
bindStringFlag(cmd, "externalDomain", "domain ZITADEL will be exposed on")
bindStringFlag(cmd, "externalPort", "port ZITADEL will be exposed on")
cmd.Flags().AddFlagSet(startFlagSet)
logging.OnError(
viper.BindPFlags(startFlagSet),
).Fatal("start flags")
tls.AddTLSModeFlag(cmd)
key.AddMasterKeyFlag(cmd)
}
func bindStringFlag(cmd *cobra.Command, name, description string) {
cmd.PersistentFlags().String(name, viper.GetString(name), description)
viper.BindPFlag(name, cmd.PersistentFlags().Lookup(name))
}
func bindUint16Flag(cmd *cobra.Command, name, description string) {
cmd.PersistentFlags().Uint16(name, uint16(viper.GetUint(name)), description)
viper.BindPFlag(name, cmd.PersistentFlags().Lookup(name))
}
func bindBoolFlag(cmd *cobra.Command, name, description string) {
cmd.PersistentFlags().Bool(name, viper.GetBool(name), description)
viper.BindPFlag(name, cmd.PersistentFlags().Lookup(name))
}

View File

@ -9,6 +9,7 @@ import (
"net/http"
"os"
"os/signal"
"slices"
"syscall"
"time"
@ -28,7 +29,6 @@ import (
"github.com/zitadel/zitadel/cmd/encryption"
"github.com/zitadel/zitadel/cmd/key"
cmd_tls "github.com/zitadel/zitadel/cmd/tls"
"github.com/zitadel/zitadel/feature"
"github.com/zitadel/zitadel/internal/actions"
admin_es "github.com/zitadel/zitadel/internal/admin/repository/eventsourcing"
"github.com/zitadel/zitadel/internal/api"
@ -37,6 +37,7 @@ import (
"github.com/zitadel/zitadel/internal/api/grpc/admin"
"github.com/zitadel/zitadel/internal/api/grpc/auth"
execution_v3_alpha "github.com/zitadel/zitadel/internal/api/grpc/execution/v3alpha"
"github.com/zitadel/zitadel/internal/api/grpc/feature/v2"
"github.com/zitadel/zitadel/internal/api/grpc/management"
oidc_v2 "github.com/zitadel/zitadel/internal/api/grpc/oidc/v2"
"github.com/zitadel/zitadel/internal/api/grpc/org/v2"
@ -264,7 +265,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
if err != nil {
return err
}
err = startAPIs(
api, err := startAPIs(
ctx,
clock,
router,
@ -281,6 +282,8 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
if err != nil {
return err
}
commands.GrpcMethodExisting = checkExisting(api.ListGrpcMethods())
commands.GrpcServiceExisting = checkExisting(api.ListGrpcServices())
shutdown := make(chan os.Signal, 1)
signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM)
@ -319,7 +322,7 @@ func startAPIs(
authZRepo authz_repo.Repository,
keys *encryption.EncryptionKeys,
permissionCheck domain.PermissionCheck,
) error {
) (*api.API, error) {
repo := struct {
authz_repo.Repository
*query.Queries
@ -332,22 +335,22 @@ func startAPIs(
router.Use(middleware.WithOrigin(config.ExternalSecure))
systemTokenVerifier, err := internal_authz.StartSystemTokenVerifierFromConfig(http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure), config.SystemAPIUsers)
if err != nil {
return err
return nil, err
}
accessTokenVerifer := internal_authz.StartAccessTokenVerifierFromRepo(repo)
verifier := internal_authz.StartAPITokenVerifier(repo, accessTokenVerifer, systemTokenVerifier)
tlsConfig, err := config.TLS.Config()
if err != nil {
return err
return nil, err
}
accessStdoutEmitter, err := logstore.NewEmitter[*record.AccessLog](ctx, clock, &logstore.EmitterConfig{Enabled: config.LogStore.Access.Stdout.Enabled}, stdout.NewStdoutEmitter[*record.AccessLog]())
if err != nil {
return err
return nil, err
}
accessDBEmitter, err := logstore.NewEmitter[*record.AccessLog](ctx, clock, &config.Quotas.Access.EmitterConfig, access.NewDatabaseLogStorage(dbClient, commands, queries))
if err != nil {
return err
return nil, err
}
accessSvc := logstore.New[*record.AccessLog](queries, accessDBEmitter, accessStdoutEmitter)
@ -357,54 +360,57 @@ func startAPIs(
http_util.WithMaxAge(int(math.Floor(config.Quotas.Access.ExhaustedCookieMaxAge.Seconds()))),
)
limitingAccessInterceptor := middleware.NewAccessInterceptor(accessSvc, exhaustedCookieHandler, &config.Quotas.Access.AccessConfig)
apis, err := api.New(ctx, config.Port, router, queries, verifier, config.InternalAuthZ, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader, limitingAccessInterceptor)
apis, err := api.New(ctx, config.Port, router, queries, verifier, config.InternalAuthZ, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader, config.ExternalDomain, limitingAccessInterceptor)
if err != nil {
return fmt.Errorf("error creating api %w", err)
return nil, fmt.Errorf("error creating api %w", err)
}
config.Auth.Spooler.Client = dbClient
config.Auth.Spooler.Eventstore = eventstore
authRepo, err := auth_es.Start(ctx, config.Auth, config.SystemDefaults, commands, queries, dbClient, eventstore, keys.OIDC, keys.User)
if err != nil {
return fmt.Errorf("error starting auth repo: %w", err)
return nil, fmt.Errorf("error starting auth repo: %w", err)
}
config.Admin.Spooler.Client = dbClient
config.Admin.Spooler.Eventstore = eventstore
err = admin_es.Start(ctx, config.Admin, store, dbClient)
if err != nil {
return fmt.Errorf("error starting admin repo: %w", err)
return nil, fmt.Errorf("error starting admin repo: %w", err)
}
if err := apis.RegisterServer(ctx, system.CreateServer(commands, queries, config.Database.DatabaseName(), config.DefaultInstance, config.ExternalDomain), tlsConfig); err != nil {
return err
return nil, err
}
if err := apis.RegisterServer(ctx, admin.CreateServer(config.Database.DatabaseName(), commands, queries, config.SystemDefaults, config.ExternalSecure, keys.User, config.AuditLogRetention), tlsConfig); err != nil {
return err
return nil, err
}
if err := apis.RegisterServer(ctx, management.CreateServer(commands, queries, config.SystemDefaults, keys.User, config.ExternalSecure), tlsConfig); err != nil {
return err
return nil, err
}
if err := apis.RegisterServer(ctx, auth.CreateServer(commands, queries, authRepo, config.SystemDefaults, keys.User, config.ExternalSecure), tlsConfig); err != nil {
return err
return nil, err
}
if err := apis.RegisterService(ctx, user_v2.CreateServer(commands, queries, keys.User, keys.IDPConfig, idp.CallbackURL(config.ExternalSecure), idp.SAMLRootURL(config.ExternalSecure), assets.AssetAPI(config.ExternalSecure), permissionCheck)); err != nil {
return err
return nil, err
}
if err := apis.RegisterService(ctx, session.CreateServer(commands, queries)); err != nil {
return err
return nil, err
}
if err := apis.RegisterService(ctx, settings.CreateServer(commands, queries, config.ExternalSecure)); err != nil {
return err
return nil, err
}
if err := apis.RegisterService(ctx, org.CreateServer(commands, queries, permissionCheck)); err != nil {
return err
return nil, err
}
if err := apis.RegisterService(ctx, execution_v3_alpha.CreateServer(commands, queries)); err != nil {
return err
if err := apis.RegisterService(ctx, feature.CreateServer(commands, queries)); err != nil {
return nil, err
}
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader, login.IgnoreInstanceEndpoints...)
if err := apis.RegisterService(ctx, execution_v3_alpha.CreateServer(commands, queries, domain.AllFunctions, apis.ListGrpcMethods, apis.ListGrpcServices)); err != nil {
return nil, err
}
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader, config.ExternalDomain, login.IgnoreInstanceEndpoints...)
assetsCache := middleware.AssetsCacheInterceptor(config.AssetStorage.Cache.MaxAge, config.AssetStorage.Cache.SharedMaxAge)
apis.RegisterHandlerOnPrefix(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator(), store, queries, middleware.CallDurationHandler, instanceInterceptor.Handler, assetsCache.Handler, limitingAccessInterceptor.Handle))
@ -412,38 +418,38 @@ func startAPIs(
userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, id.SonyFlakeGenerator(), config.ExternalSecure, login.EndpointResources, login.EndpointExternalLoginCallbackFormPost, login.EndpointSAMLACS)
if err != nil {
return err
return nil, err
}
// robots.txt handler
robotsTxtHandler, err := robots_txt.Start()
if err != nil {
return fmt.Errorf("unable to start robots txt handler: %w", err)
return nil, fmt.Errorf("unable to start robots txt handler: %w", err)
}
apis.RegisterHandlerOnPrefix(robots_txt.HandlerPrefix, robotsTxtHandler)
// TODO: Record openapi access logs?
openAPIHandler, err := openapi.Start()
if err != nil {
return fmt.Errorf("unable to start openapi handler: %w", err)
return nil, fmt.Errorf("unable to start openapi handler: %w", err)
}
apis.RegisterHandlerOnPrefix(openapi.HandlerPrefix, openAPIHandler)
oidcServer, err := oidc.NewServer(config.OIDC, login.DefaultLoggedOutPath, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.OIDCKey, eventstore, dbClient, userAgentInterceptor, instanceInterceptor.Handler, limitingAccessInterceptor, config.Log.Slog())
if err != nil {
return fmt.Errorf("unable to start oidc provider: %w", err)
return nil, fmt.Errorf("unable to start oidc provider: %w", err)
}
apis.RegisterHandlerPrefixes(oidcServer, oidcPrefixes...)
samlProvider, err := saml.NewProvider(config.SAML, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.SAML, eventstore, dbClient, instanceInterceptor.Handler, userAgentInterceptor, limitingAccessInterceptor)
if err != nil {
return fmt.Errorf("unable to start saml provider: %w", err)
return nil, fmt.Errorf("unable to start saml provider: %w", err)
}
apis.RegisterHandlerOnPrefix(saml.HandlerPrefix, samlProvider.HttpHandler())
c, err := console.Start(config.Console, config.ExternalSecure, oidcServer.IssuerFromRequest, middleware.CallDurationHandler, instanceInterceptor.Handler, limitingAccessInterceptor, config.CustomerPortal)
if err != nil {
return fmt.Errorf("unable to start console: %w", err)
return nil, fmt.Errorf("unable to start console: %w", err)
}
apis.RegisterHandlerOnPrefix(console.HandlerPrefix, c)
consolePath := console.HandlerPrefix + "/"
@ -466,21 +472,20 @@ func startAPIs(
keys.User,
keys.IDPConfig,
keys.CSRFCookieKey,
feature.NewCheck(eventstore),
)
if err != nil {
return fmt.Errorf("unable to start login: %w", err)
return nil, fmt.Errorf("unable to start login: %w", err)
}
apis.RegisterHandlerOnPrefix(login.HandlerPrefix, l.Handler())
apis.HandleFunc(login.EndpointDeviceAuth, login.RedirectDeviceAuthToPrefix)
// After OIDC provider so that the callback endpoint can be used
if err := apis.RegisterService(ctx, oidc_v2.CreateServer(commands, queries, oidcServer, config.ExternalSecure)); err != nil {
return err
return nil, err
}
// handle grpc at last to be able to handle the root, because grpc and gateway require a lot of different prefixes
apis.RouteGRPC()
return nil
return apis, nil
}
func listen(ctx context.Context, router *mux.Router, port uint16, tlsConfig *tls.Config, shutdown <-chan os.Signal) error {
@ -551,3 +556,9 @@ func showBasicInformation(startConfig *Config) {
}
fmt.Printf("\n ===============================================================\n\n")
}
func checkExisting(values []string) func(string) bool {
return func(value string) bool {
return slices.Contains(values, value)
}
}

View File

@ -1,27 +0,0 @@
package systemapi
import (
"encoding/json"
"reflect"
"github.com/zitadel/zitadel/internal/api/authz"
)
type Users map[string]*authz.SystemAPIUser
func UsersDecodeHook(from, to reflect.Value) (any, error) {
if to.Type() != reflect.TypeOf(Users{}) {
return from.Interface(), nil
}
data, ok := from.Interface().(string)
if !ok {
return from.Interface(), nil
}
users := make(Users)
err := json.Unmarshal([]byte(data), &users)
if err != nil {
return nil, err
}
return users, nil
}

View File

@ -23,7 +23,12 @@
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets", "src/manifest.webmanifest"],
"assets": [
"src/favicon.ico",
"src/assets",
"src/manifest.webmanifest",
{ "glob": "**/*", "input": "../docs/static/img", "output": "assets/docs/img" }
],
"styles": ["src/styles.scss"],
"scripts": ["./node_modules/tinycolor2/dist/tinycolor-min.js"],
"stylePreprocessorOptions": {
@ -81,6 +86,7 @@
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "console:build:production"

View File

@ -29,6 +29,7 @@
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-brands-svg-icons": "^6.4.2",
"@grpc/grpc-js": "^1.9.3",
"@netlify/framework-info": "^9.8.10",
"@ngx-translate/core": "^15.0.0",
"angular-oauth2-oidc": "^15.0.1",
"angularx-qrcode": "^16.0.0",

View File

@ -0,0 +1,35 @@
<form>
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'QUICKSTART.FRAMEWORK' | translate }}</cnsl-label>
<input cnslInput type="text" placeholder="" #nameInput [formControl]="myControl" [matAutocomplete]="auto" />
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
<mat-option *ngIf="isLoading()" class="is-loading">
<mat-spinner diameter="30"></mat-spinner>
</mat-option>
<mat-option *ngFor="let framework of filteredOptions | async" [value]="framework.id">
<div class="framework-option">
<div class="framework-option-column">
<div class="img-wrapper">
<img class="dark-only" *ngIf="framework.imgSrcDark" [src]="framework.imgSrcDark" />
<img class="light-only" *ngIf="framework.imgSrcLight" [src]="framework.imgSrcLight" />
</div>
<span class="fill-space"></span>
<span>{{ framework.title }}</span>
</div>
</div>
</mat-option>
<mat-option *ngIf="withCustom" [value]="'other'">
<div class="framework-option">
<div class="framework-option-column">
<div class="img-wrapper"></div>
<span class="fill-space"></span>
<span>{{ 'QUICKSTART.FRAMEWORK_OTHER' | translate }}</span>
</div>
</div>
</mat-option>
</mat-autocomplete>
</cnsl-form-field>
</form>

View File

@ -0,0 +1,69 @@
@mixin framework-autocomplete-theme($theme) {
$primary: map-get($theme, primary);
$warn: map-get($theme, warn);
$background: map-get($theme, background);
$accent: map-get($theme, accent);
$primary-color: map-get($primary, 500);
$warn-color: map-get($warn, 500);
$accent-color: map-get($accent, 500);
$foreground: map-get($theme, foreground);
$is-dark-theme: map-get($theme, is-dark);
$back: map-get($background, background);
$list-background-color: map-get($background, 300);
$card-background-color: map-get($background, cards);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2));
$border-selected-color: if($is-dark-theme, #fff, #000);
.full-width {
width: 100%;
}
input {
max-width: 500px;
}
.framework-option {
display: flex;
align-items: center;
.framework-option-column {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
width: 100%;
span {
line-height: normal;
}
.fill-space {
flex: 1;
}
.img-wrapper {
width: 50px;
margin-right: 1rem;
img {
width: 100%;
height: 100%;
max-width: 30px;
max-height: 30px;
object-fit: contain;
object-position: center;
}
}
.dark-only {
display: if($is-dark-theme, block, none);
}
.light-only {
display: if($is-dark-theme, none, block);
}
}
}
}

View File

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FrameworkAutocompleteComponent } from './framework-autocomplete.component';
describe('FrameworkAutocompleteComponent', () => {
let component: FrameworkAutocompleteComponent;
let fixture: ComponentFixture<FrameworkAutocompleteComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [FrameworkAutocompleteComponent],
});
fixture = TestBed.createComponent(FrameworkAutocompleteComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,63 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output, signal } from '@angular/core';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { MatButtonModule } from '@angular/material/button';
import { MatSelectModule } from '@angular/material/select';
import { MatAutocompleteModule, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { InputModule } from 'src/app/modules/input/input.module';
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Observable, map, of, startWith, switchMap, tap } from 'rxjs';
import { Framework } from '../quickstart/quickstart.component';
@Component({
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'cnsl-framework-autocomplete',
templateUrl: './framework-autocomplete.component.html',
styleUrls: ['./framework-autocomplete.component.scss'],
imports: [
TranslateModule,
RouterModule,
MatSelectModule,
MatAutocompleteModule,
ReactiveFormsModule,
MatProgressSpinnerModule,
FormsModule,
CommonModule,
MatButtonModule,
InputModule,
],
})
export class FrameworkAutocompleteComponent implements OnInit {
public isLoading = signal(false);
@Input() public frameworkId?: string;
@Input() public frameworks: Framework[] = [];
@Input() public withCustom: boolean = false;
public myControl: FormControl = new FormControl();
@Output() public selectionChanged: EventEmitter<string> = new EventEmitter();
public filteredOptions: Observable<Framework[]> = of([]);
constructor() {}
public ngOnInit() {
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
map((value) => {
return this._filter(value || '');
}),
);
}
private _filter(value: string): Framework[] {
const filterValue = value.toLowerCase();
return this.frameworks
.filter((option) => option.id)
.filter((option) => option.title.toLowerCase().includes(filterValue));
}
public selected(event: MatAutocompleteSelectedEvent): void {
this.selectionChanged.emit(event.option.value);
}
}

View File

@ -0,0 +1,17 @@
<h2 mat-dialog-title>{{ 'QUICKSTART.DIALOG.CHANGE.TITLE' | translate }}</h2>
<mat-dialog-content>
{{ 'QUICKSTART.DIALOG.CHANGE.DESCRIPTION' | translate }}
<div class="framework-change-block">
<cnsl-framework-autocomplete
[frameworkId]="data.framework.id"
[frameworks]="data.frameworks"
(selectionChanged)="findFramework($event)"
></cnsl-framework-autocomplete>
</div>
</mat-dialog-content>
<div>
<mat-dialog-actions class="actions">
<button mat-stroked-button mat-dialog-close>{{ 'ACTIONS.CANCEL' | translate }}</button>
<button color="primary" mat-raised-button (click)="close()" cdkFocusInitial>{{ 'ACTIONS.CHANGE' | translate }}</button>
</mat-dialog-actions>
</div>

View File

@ -0,0 +1,10 @@
.framework-change-block {
display: flex;
flex-direction: column;
align-items: stretch;
}
.actions {
display: flex;
justify-content: space-between;
}

View File

@ -0,0 +1,41 @@
import { Component, Inject, signal } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import {
MAT_DIALOG_DATA,
MatDialogActions,
MatDialogClose,
MatDialogContent,
MatDialogModule,
MatDialogRef,
MatDialogTitle,
} from '@angular/material/dialog';
import { FrameworkAutocompleteComponent } from '../framework-autocomplete/framework-autocomplete.component';
import { Framework } from '../quickstart/quickstart.component';
import { TranslateModule } from '@ngx-translate/core';
@Component({
selector: 'cnsl-framework-change-dialog',
templateUrl: './framework-change-dialog.component.html',
styleUrls: ['./framework-change-dialog.component.scss'],
standalone: true,
imports: [MatButtonModule, MatDialogModule, TranslateModule, FrameworkAutocompleteComponent],
})
export class FrameworkChangeDialogComponent {
public framework = signal<Framework | undefined>(undefined);
constructor(
public dialogRef: MatDialogRef<FrameworkChangeDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
) {
this.framework.set(data.framework);
}
public findFramework(id: string) {
const temp = this.data.frameworks.find((f: Framework) => f.id === id);
this.framework.set(temp);
}
public close() {
this.dialogRef.close(this.framework());
}
}

View File

@ -0,0 +1,14 @@
<div class="framework-change-wrapper">
<div class="framework-card-wrapper">
<div class="framework-card card" *ngIf="framework | async as framework">
<div [routerLink]="['/app-create']" [queryParams]="{ id: framework.docsLink }" class="">
<img class="dark-only" *ngIf="framework.imgSrcDark" [src]="framework.imgSrcDark" />
<img class="light-only" *ngIf="framework.imgSrcLight" [src]="framework.imgSrcLight" />
</div>
<span>{{ framework.title }}</span>
</div>
<button (click)="openDialog()" mat-stroked-button>
{{ 'ACTIONS.CHANGE' | translate }}
</button>
</div>
</div>

View File

@ -0,0 +1,82 @@
@mixin framework-change-theme($theme) {
$primary: map-get($theme, primary);
$warn: map-get($theme, warn);
$background: map-get($theme, background);
$accent: map-get($theme, accent);
$primary-color: map-get($primary, 500);
$warn-color: map-get($warn, 500);
$accent-color: map-get($accent, 500);
$foreground: map-get($theme, foreground);
$is-dark-theme: map-get($theme, is-dark);
$back: map-get($background, background);
$list-background-color: map-get($background, 300);
$card-background-color: map-get($background, cards);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2));
$border-selected-color: if($is-dark-theme, #fff, #000);
.framework-change-wrapper {
.framework-card-wrapper {
display: flex;
align-items: center;
gap: 1rem;
.framework-card {
position: relative;
flex-shrink: 0;
text-decoration: none;
border-radius: 0.5rem;
box-sizing: border-box;
transition: all 0.1s ease-in;
display: flex;
flex-direction: row;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
width: fit-content;
// background-color: if($is-dark-theme, map-get($background, state), #e4e7e4);
// box-shadow: 0 0 3px #0000001a;
border: 1px solid rgba(#8795a1, 0.2);
padding: 0 0.5rem;
img {
width: 100%;
height: 100%;
max-width: 40px;
max-height: 40px;
object-fit: contain;
object-position: center;
}
.dark-only {
display: if($is-dark-theme, block, none);
}
.light-only {
display: if($is-dark-theme, none, block);
}
span {
margin: 0.5rem;
text-align: center;
color: map-get($foreground, text);
}
.action-row {
display: flex;
align-items: center;
justify-content: flex-end;
font-size: 14px;
margin-bottom: 0.5rem;
color: map-get($primary, 400);
.icon {
margin-left: 0rem;
}
}
}
}
}
}

View File

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FrameworkChangeComponent } from './framework-change.component';
describe('FrameworkChangeComponent', () => {
let component: FrameworkChangeComponent;
let fixture: ComponentFixture<FrameworkChangeComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [FrameworkChangeComponent],
});
fixture = TestBed.createComponent(FrameworkChangeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,88 @@
import { CommonModule } from '@angular/common';
import { Component, EventEmitter, OnDestroy, OnInit, Output, effect, signal } from '@angular/core';
import { ActivatedRoute, Params, RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import frameworkDefinition from '../../../../../docs/frameworks.json';
import { MatButtonModule } from '@angular/material/button';
import { listFrameworks, hasFramework, getFramework } from '@netlify/framework-info';
import { FrameworkName } from '@netlify/framework-info/lib/generated/frameworkNames';
import { FrameworkAutocompleteComponent } from '../framework-autocomplete/framework-autocomplete.component';
import { Framework } from '../quickstart/quickstart.component';
import { BehaviorSubject, Subject, takeUntil } from 'rxjs';
import {
MatDialog,
MatDialogActions,
MatDialogClose,
MatDialogContent,
MatDialogModule,
MatDialogRef,
MatDialogTitle,
} from '@angular/material/dialog';
import { FrameworkChangeDialogComponent } from './framework-change-dialog.component';
@Component({
standalone: true,
selector: 'cnsl-framework-change',
templateUrl: './framework-change.component.html',
styleUrls: ['./framework-change.component.scss'],
imports: [TranslateModule, RouterModule, CommonModule, MatButtonModule, FrameworkAutocompleteComponent],
})
export class FrameworkChangeComponent implements OnInit, OnDestroy {
private destroy$: Subject<void> = new Subject();
public framework: BehaviorSubject<Framework | undefined> = new BehaviorSubject<Framework | undefined>(undefined);
public showFrameworkAutocomplete = signal<boolean>(false);
@Output() public frameworkChanged: EventEmitter<Framework> = new EventEmitter();
public frameworks: Framework[] = frameworkDefinition.map((f) => {
return {
...f,
fragment: '',
imgSrcDark: `assets${f.imgSrcDark}`,
imgSrcLight: `assets${f.imgSrcLight ? f.imgSrcLight : f.imgSrcDark}`,
};
});
constructor(
private activatedRoute: ActivatedRoute,
private dialog: MatDialog,
) {
this.framework.pipe(takeUntil(this.destroy$)).subscribe((value) => {
this.frameworkChanged.emit(value);
});
}
public ngOnInit() {
this.activatedRoute.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params: Params) => {
const { framework } = params;
if (framework) {
this.findFramework(framework);
}
});
}
public ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
public findFramework(id: string) {
const temp = this.frameworks.find((f) => f.id === id);
this.framework.next(temp);
this.frameworkChanged.emit(temp);
}
public openDialog(): void {
const ref = this.dialog.open(FrameworkChangeDialogComponent, {
width: '400px',
data: {
framework: this.framework.value,
frameworks: this.frameworks,
},
});
ref.afterClosed().subscribe((resp) => {
if (resp) {
this.framework.next(resp);
}
});
}
}

View File

@ -0,0 +1,53 @@
<div class="configuration-wrapper">
<div class="row">
<span class="left cnsl-secondary-text">
{{ 'APP.NAME' | translate }}
</span>
<span class="right name">
<span>{{ name }}</span>
<button (click)="changeName.emit()" mat-icon-button><i class="las la-pen"></i></button>
</span>
</div>
<div class="row">
<span class="left cnsl-secondary-text">
{{ 'APP.TYPE' | translate }}
</span>
<span class="right">
{{ 'APP.OIDC.APPTYPE.' + configuration.appType | translate }}
</span>
</div>
<div class="row">
<span class="left cnsl-secondary-text">
{{ 'APP.GRANT' | translate }}
</span>
<span class="right" *ngIf="configuration.grantTypesList && configuration.grantTypesList.length > 0">
[<span *ngFor="let element of configuration.grantTypesList ?? []; index as i">
{{ 'APP.OIDC.GRANT.' + element | translate }}
{{ i < configuration.grantTypesList.length - 1 ? ', ' : '' }} </span
>]
</span>
</div>
<div class="row">
<span class="left cnsl-secondary-text">
{{ 'APP.OIDC.RESPONSETYPE' | translate }}
</span>
<span class="right" *ngIf="configuration.responseTypesList && configuration.responseTypesList.length > 0">
[<span *ngFor="let element of configuration.responseTypesList ?? []; index as i">
{{ 'APP.OIDC.RESPONSE.' + element | translate }}
{{ i < configuration.responseTypesList.length - 1 ? ', ' : '' }} </span
>]
</span>
</div>
<div class="row">
<span class="left cnsl-secondary-text">
{{ 'APP.AUTHMETHOD' | translate }}
</span>
<span class="right">
<span>
{{ 'APP.OIDC.AUTHMETHOD.' + configuration.authMethodType | translate }}
</span>
</span>
</div>
</div>

View File

@ -0,0 +1,23 @@
.configuration-wrapper {
.row {
display: flex;
justify-content: space-between;
align-items: center;
.left,
.right {
margin-bottom: 0.5rem;
font-size: 14px;
}
.name {
display: flex;
align-items: center;
button {
margin-right: -0.5rem;
margin-left: 0.25rem;
}
}
}
}

View File

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { QuickstartComponent } from './quickstart.component';
describe('QuickstartComponent', () => {
let component: QuickstartComponent;
let fixture: ComponentFixture<QuickstartComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [QuickstartComponent],
});
fixture = TestBed.createComponent(QuickstartComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,35 @@
import { CommonModule } from '@angular/common';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import frameworkDefinition from '../../../../../docs/frameworks.json';
import { MatButtonModule } from '@angular/material/button';
import { listFrameworks, hasFramework, getFramework } from '@netlify/framework-info';
import { FrameworkName } from '@netlify/framework-info/lib/generated/frameworkNames';
import { AddOIDCAppRequest } from 'src/app/proto/generated/zitadel/management_pb';
export type FrameworkDefinition = {
id?: FrameworkName | string;
title: string;
imgSrcDark: string;
imgSrcLight?: string;
docsLink: string;
external?: boolean;
};
export type Framework = FrameworkDefinition & {
fragment: string;
};
@Component({
standalone: true,
selector: 'cnsl-oidc-app-configuration',
templateUrl: './oidc-configuration.component.html',
styleUrls: ['./oidc-configuration.component.scss'],
imports: [TranslateModule, RouterModule, CommonModule, MatButtonModule],
})
export class OIDCConfigurationComponent {
@Input() public name?: string;
@Input() public configuration: AddOIDCAppRequest.AsObject = new AddOIDCAppRequest().toObject();
@Output() public changeName: EventEmitter<string> = new EventEmitter();
}

View File

@ -0,0 +1,23 @@
<div class="quickstart-header">
<div class="quickstart-left">
<h2>{{ 'QUICKSTART.TITLE' | translate }}</h2>
<p class="description">{{ 'QUICKSTART.DESCRIPTION' | translate }}</p>
<div class="btn-wrapper">
<a mat-raised-button color="primary" [routerLink]="['/projects', 'app-create']">{{
'QUICKSTART.BTN_START' | translate
}}</a>
<a mat-stroked-button color="primary" href="https://zitadel.com/docs/sdk-examples/introduction" target="_blank">{{
'QUICKSTART.BTN_LEARNMORE' | translate
}}</a>
</div>
</div>
<div class="quickstart-card-wrapper">
<ng-container *ngFor="let framework of frameworks.slice(0, 18)">
<a [routerLink]="['/projects', 'app-create']" [queryParams]="{ framework: framework.id }" class="quickstart-card card">
<img class="dark-only" *ngIf="framework.imgSrcDark" [src]="framework.imgSrcDark" alt="{{ framework.title }}" />
<img class="light-only" *ngIf="framework.imgSrcLight" [src]="framework.imgSrcLight" alt="{{ framework.title }}" />
</a>
</ng-container>
</div>
</div>

View File

@ -0,0 +1,121 @@
@mixin quickstart-theme($theme) {
$primary: map-get($theme, primary);
$warn: map-get($theme, warn);
$background: map-get($theme, background);
$accent: map-get($theme, accent);
$primary-color: map-get($primary, 500);
$warn-color: map-get($warn, 500);
$accent-color: map-get($accent, 500);
$foreground: map-get($theme, foreground);
$is-dark-theme: map-get($theme, is-dark);
$back: map-get($background, background);
$list-background-color: map-get($background, 300);
$card-background-color: map-get($background, cards);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2));
$border-selected-color: if($is-dark-theme, #fff, #000);
.quickstart-header {
display: flex;
flex-direction: row;
margin: 0 -2rem;
padding: 2rem;
margin-bottom: 2rem;
gap: 5rem;
justify-content: space-between;
background-color: map-get($background, metadata-section);
.quickstart-left {
display: flex;
flex-direction: column;
max-width: 400px;
.btn-wrapper {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 1rem;
margin: 1rem 0;
}
}
.quickstart-card-wrapper {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-column-gap: 1rem;
grid-row-gap: 1rem;
grid-auto-columns: 0;
overflow-x: hidden;
box-sizing: border-box;
max-width: 600px;
margin-left: auto;
.quickstart-card {
position: relative;
flex-shrink: 0;
text-decoration: none;
cursor: pointer;
border-radius: 0.5rem;
box-sizing: border-box;
transition: all 0.1s ease-in;
display: flex;
flex-direction: column;
height: 60px;
width: 60px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 3px #0000001a;
border: 1px solid rgba(#8795a1, 0.2);
color: var(--success);
opacity: 0.8;
&:hover {
border: 2px solid var(--success);
opacity: 1;
}
img {
width: 100%;
height: 100%;
max-width: 40px;
max-height: 40px;
object-fit: contain;
object-position: center;
}
.dark-only {
display: if($is-dark-theme, block, none);
}
.light-only {
display: if($is-dark-theme, none, block);
}
span {
margin: 0.5rem;
text-align: center;
color: map-get($foreground, text);
}
.action-row {
display: flex;
align-items: center;
justify-content: flex-end;
font-size: 14px;
margin-bottom: 0.5rem;
color: map-get($primary, 400);
.icon {
margin-left: 0rem;
}
}
&:hover {
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.12);
}
}
}
}
}

View File

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { QuickstartComponent } from './quickstart.component';
describe('QuickstartComponent', () => {
let component: QuickstartComponent;
let fixture: ComponentFixture<QuickstartComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [QuickstartComponent],
});
fixture = TestBed.createComponent(QuickstartComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,42 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import frameworkDefinition from '../../../../../docs/frameworks.json';
import { MatButtonModule } from '@angular/material/button';
import { listFrameworks, hasFramework, getFramework } from '@netlify/framework-info';
import { FrameworkName } from '@netlify/framework-info/lib/generated/frameworkNames';
import { OIDC_CONFIGURATIONS } from 'src/app/utils/framework';
export type FrameworkDefinition = {
id?: FrameworkName | string;
title: string;
description?: string;
imgSrcDark: string;
imgSrcLight?: string;
docsLink: string;
external?: boolean;
};
export type Framework = FrameworkDefinition & {
fragment: string;
};
@Component({
standalone: true,
selector: 'cnsl-quickstart',
templateUrl: './quickstart.component.html',
styleUrls: ['./quickstart.component.scss'],
imports: [TranslateModule, RouterModule, CommonModule, MatButtonModule],
})
export class QuickstartComponent {
public frameworks: FrameworkDefinition[] = frameworkDefinition
.filter((f) => f.id && OIDC_CONFIGURATIONS[f.id])
.map((f) => {
return {
...f,
imgSrcDark: `assets${f.imgSrcDark}`,
imgSrcLight: `assets${f.imgSrcLight ? f.imgSrcLight : f.imgSrcDark}`,
};
});
}

View File

@ -1,10 +1,17 @@
<div
class="info-section-row"
[ngClass]="{ info: type === 'INFO', warn: type === 'WARN', alert: type === 'ALERT', fit: fitWidth }"
[ngClass]="{
info: type === 'INFO',
warn: type === 'WARN',
alert: type === 'ALERT',
success: type === 'SUCCESS',
fit: fitWidth
}"
>
<i *ngIf="type === 'INFO'" class="icon las la-info"></i>
<i *ngIf="type === 'WARN'" class="icon las la-exclamation"></i>
<i *ngIf="type === 'ALERT'" class="icon las la-exclamation"></i>
<i *ngIf="type === 'SUCCESS'" class="icon las la-check-circle"></i>
<div class="info-section-content">
<ng-content></ng-content>

View File

@ -5,19 +5,22 @@
</div>
<div class="security-wrapper">
<cnsl-info-section [type]="InfoSectionType.ALERT">{{ 'SETTING.SECURITY.DESCRIPTION' | translate }}</cnsl-info-section>
<h3>{{ 'SETTING.SECURITY.IFRAMETITLE' | translate }}</h3>
<mat-checkbox
card-actions
class="security-policy-toggle"
color="primary"
ngDefaultControl
(change)="enabledChanged($event)"
[(ngModel)]="enabled"
(change)="iframeEnabledChanged($event)"
[(ngModel)]="iframeEnabled"
[disabled]="(['iam.policy.write'] | hasRole | async) === false"
>
{{ 'SETTING.SECURITY.IFRAMEENABLED' | translate }}
</mat-checkbox>
<cnsl-info-section [type]="InfoSectionType.ALERT">{{
'SETTING.SECURITY.IFRAMEDESCRIPTION' | translate
}}</cnsl-info-section>
<form class="security-allowed-originsform" (ngSubmit)="add(redInput)">
<cnsl-form-field class="formfield">
@ -29,14 +32,14 @@
matTooltip="{{ 'ACTIONS.ADD' | translate }}"
type="submit"
mat-icon-button
[disabled]="!enabled || originsControl.invalid || (['iam.policy.write'] | hasRole | async) === false"
[disabled]="!iframeEnabled || originsControl.invalid || (['iam.policy.write'] | hasRole | async) === false"
>
<mat-icon>add</mat-icon>
</button>
</form>
<div class="security-allowed-uris-list">
<div *ngFor="let uri of originsList" class="uri-line" [ngClass]="{ disabled: !enabled }">
<div *ngFor="let uri of originsList" class="uri-line" [ngClass]="{ disabled: !iframeEnabled }">
<span class="uri">{{ uri }}</span>
<span class="fill-space"></span>
@ -45,6 +48,21 @@
</button>
</div>
</div>
<h3>{{ 'SETTING.SECURITY.IMPERSONATIONTITLE' | translate }}</h3>
<mat-checkbox
card-actions
class="security-policy-toggle"
color="primary"
ngDefaultControl
[(ngModel)]="impersonationEnabled"
[disabled]="(['iam.policy.write'] | hasRole | async) === false"
>
{{ 'SETTING.SECURITY.IMPERSONATIONENABLED' | translate }}
</mat-checkbox>
<cnsl-info-section [type]="InfoSectionType.INFO">{{
'SETTING.SECURITY.IMPERSONATIONDESCRIPTION' | translate
}}</cnsl-info-section>
</div>
<div class="general-btn-container">

View File

@ -13,7 +13,8 @@ import { InfoSectionType } from '../../info-section/info-section.component';
})
export class SecurityPolicyComponent implements OnInit {
public originsList: string[] = [];
public enabled: boolean = false;
public iframeEnabled: boolean = false;
public impersonationEnabled: boolean = false;
public loading: boolean = false;
public InfoSectionType: any = InfoSectionType;
@ -32,7 +33,8 @@ export class SecurityPolicyComponent implements OnInit {
private fetchData(): void {
this.service.getSecurityPolicy().then((securityPolicy) => {
if (securityPolicy.policy) {
this.enabled = securityPolicy.policy?.enableIframeEmbedding;
this.impersonationEnabled = securityPolicy.policy?.enableImpersonation;
this.iframeEnabled = securityPolicy.policy?.enableIframeEmbedding;
this.originsList = securityPolicy.policy?.allowedOriginsList;
if (securityPolicy.policy.enableIframeEmbedding) {
this.originsControl.enable();
@ -46,7 +48,8 @@ export class SecurityPolicyComponent implements OnInit {
private updateData(): Promise<SetDefaultLanguageResponse.AsObject> {
const req = new SetSecurityPolicyRequest();
req.setAllowedOriginsList(this.originsList);
req.setEnableIframeEmbedding(this.enabled);
req.setEnableIframeEmbedding(this.iframeEnabled);
req.setEnableImpersonation(this.impersonationEnabled);
return (this.service as AdminService).setSecurityPolicy(req);
}
@ -88,7 +91,7 @@ export class SecurityPolicyComponent implements OnInit {
}
}
public enabledChanged(event: MatCheckboxChange) {
public iframeEnabledChanged(event: MatCheckboxChange) {
if (event.checked) {
this.originsControl.enable();
} else {

View File

@ -37,13 +37,19 @@ export class SearchProjectAutocompleteComponent implements OnInit, OnDestroy {
@Output() public selectionChanged: EventEmitter<{
project: Project.AsObject | GrantedProject.AsObject;
type: ProjectType;
name: string;
}> = new EventEmitter();
@Output() public valueChanged: EventEmitter<string> = new EventEmitter();
private unsubscribed$: Subject<void> = new Subject();
constructor(private mgmtService: ManagementService) {
this.myControl.valueChanges
.pipe(
takeUntil(this.unsubscribed$),
tap((value) => {
const name = typeof value === 'string' ? value : value.name ? value.name : '';
this.valueChanged.emit(name);
}),
debounceTime(200),
tap(() => (this.isLoading = true)),
switchMap((value) => {
@ -124,44 +130,6 @@ export class SearchProjectAutocompleteComponent implements OnInit, OnDestroy {
return project && project.projectName ? `${project.projectName}` : project && project.name ? `${project.name}` : '';
}
public add(event: MatChipInputEvent): void {
if (!this.matAutocomplete.isOpen) {
const input = event.chipInput?.inputElement;
const value = event.value;
if ((value || '').trim()) {
const index = this.filteredProjects.findIndex((project) => {
if (project?.projectName) {
return project.projectName === value;
} else if (project?.name) {
return project.name === value;
} else {
return false;
}
});
if (index > -1) {
if (this.projects && this.projects.length > 0) {
this.projects.push(this.filteredProjects[index]);
} else {
this.projects = [this.filteredProjects[index]];
}
}
}
if (input) {
input.value = '';
}
}
}
public remove(project: GrantedProject.AsObject): void {
const index = this.projects.indexOf(project);
if (index >= 0) {
this.projects.splice(index, 1);
}
}
public selected(event: MatAutocompleteSelectedEvent): void {
const p: Project.AsObject | GrantedProject.AsObject = event.option.value;
const type = (p as Project.AsObject).id
@ -170,8 +138,17 @@ export class SearchProjectAutocompleteComponent implements OnInit, OnDestroy {
? ProjectType.PROJECTTYPE_GRANTED
: ProjectType.PROJECTTYPE_OWNED;
const name = (p as Project.AsObject).name
? (p as Project.AsObject).name
: (p as GrantedProject.AsObject).projectName
? (p as GrantedProject.AsObject).projectName
: '';
console.log(name);
this.selectionChanged.emit({
project: p,
name,
type: type,
});
}

View File

@ -1,25 +1,53 @@
<cnsl-create-layout title="{{ 'APP.PAGES.CREATE' | translate }}" (closed)="close()">
<div class="app-create-main-content">
<h1>{{ 'APP.PAGES.CREATE_SELECT_PROJECT' | translate }}</h1>
<cnsl-framework-change *ngIf="initialParam()" (frameworkChanged)="framework.set($event)"></cnsl-framework-change>
<div class="content-wrapper" [ngClass]="{ reverse: initialParam() }">
<div>
<h1>{{ 'APP.PAGES.CREATE_SELECT_PROJECT' | translate }}</h1>
<cnsl-search-project-autocomplete
class="block"
[autocompleteType]="ProjectAutocompleteType.PROJECT_OWNED"
(selectionChanged)="selectProject($any($event.project))"
<cnsl-search-project-autocomplete
class="block"
[autocompleteType]="ProjectAutocompleteType.PROJECT_OWNED"
(selectionChanged)="selectProject($any($event))"
(valueChanged)="projectName = $any($event)"
>
</cnsl-search-project-autocomplete>
<p>{{ 'APP.PAGES.CREATE_NEW_PROJECT' | translate }}</p>
</div>
<div *ngIf="!initialParam()">
<h1>{{ 'QUICKSTART.SELECT_FRAMEWORK' | translate }}</h1>
<cnsl-framework-autocomplete
*ngIf="frameworks"
[frameworkId]="framework()?.id"
[frameworks]="frameworks"
[withCustom]="true"
(selectionChanged)="findFramework($event)"
></cnsl-framework-autocomplete>
</div>
</div>
<cnsl-info-section *ngIf="error()" [type]="InfoSectionType.WARN"
><span class="error-msg">{{ error() }}</span></cnsl-info-section
>
</cnsl-search-project-autocomplete>
<div [innerHtml]="'APP.PAGES.CREATE_NEW_PROJECT' | translate: { url: '/projects/create' }"></div>
<div class="app-create-btn-container">
<button
color="primary"
mat-raised-button
class="continue-button"
[disabled]="!projectId"
(click)="goToAppCreatePage()"
[disabled]="(!project && !projectName) || !(framework() || customFramework())"
(click)="project && project.name === projectName ? goToAppIntegratePage() : createProjectAndContinue()"
>
{{ 'ACTIONS.CONTINUE' | translate }}
{{
!project && !projectName
? ('ACTIONS.CONTINUE' | translate)
: project && project.name === projectName
? ('ACTIONS.CONTINUEWITH' | translate: { value: project.name })
: ('QUICKSTART.CREATEPROJECTFORAPP' | translate: { value: projectName })
}}
</button>
</div>
</div>

View File

@ -5,6 +5,20 @@ h1 {
.app-create-main-content {
max-width: 35rem;
.content-wrapper {
display: flex;
flex-direction: column;
&.reverse {
flex-direction: column-reverse;
}
}
.error-msg {
margin-top: 0.25rem;
display: block;
}
.app-create-btn-container {
display: flex;
align-items: center;
@ -17,9 +31,4 @@ h1 {
padding: 0 4rem;
}
}
.complexity-view {
width: 100%;
margin: 0 0.5rem;
}
}

View File

@ -1,19 +1,19 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { OrgCreateComponent } from './org-create.component';
import { AppCreateComponent } from './app-create.component';
describe('OrgCreateComponent', () => {
let component: OrgCreateComponent;
let fixture: ComponentFixture<OrgCreateComponent>;
describe('AppCreateComponent', () => {
let component: AppCreateComponent;
let fixture: ComponentFixture<AppCreateComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [OrgCreateComponent],
declarations: [AppCreateComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(OrgCreateComponent);
fixture = TestBed.createComponent(AppCreateComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@ -1,40 +1,144 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component, OnDestroy, signal } from '@angular/core';
import { ActivatedRoute, Navigation, Params, Router } from '@angular/router';
import { Subject, takeUntil } from 'rxjs';
import { InfoSectionType } from 'src/app/modules/info-section/info-section.component';
import { ProjectType } from 'src/app/modules/project-members/project-members-datasource';
import { ProjectAutocompleteType } from 'src/app/modules/search-project-autocomplete/search-project-autocomplete.component';
import { Project } from 'src/app/proto/generated/zitadel/project_pb';
import { AddProjectRequest, AddProjectResponse } from 'src/app/proto/generated/zitadel/management_pb';
import { GrantedProject, Project } from 'src/app/proto/generated/zitadel/project_pb';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { Framework } from 'src/app/components/quickstart/quickstart.component';
import frameworkDefinition from '../../../../../docs/frameworks.json';
import { NavigationService } from 'src/app/services/navigation.service';
import { Location } from '@angular/common';
@Component({
selector: 'cnsl-app-create',
templateUrl: './app-create.component.html',
styleUrls: ['./app-create.component.scss'],
})
export class AppCreateComponent {
public projectId: string = '';
export class AppCreateComponent implements OnDestroy {
public InfoSectionType: any = InfoSectionType;
public project?: {
project: Project.AsObject | GrantedProject.AsObject;
type: ProjectType;
name: string;
} = undefined;
public ProjectAutocompleteType: any = ProjectAutocompleteType;
public projectName: string = '';
public error = signal('');
public framework = signal<Framework | undefined>(undefined);
public customFramework = signal<boolean>(false);
public initialParam = signal<string>('');
public destroy$: Subject<void> = new Subject();
public frameworks: Framework[] = frameworkDefinition.map((f) => {
return {
...f,
fragment: '',
imgSrcDark: `assets${f.imgSrcDark}`,
imgSrcLight: `assets${f.imgSrcLight ? f.imgSrcLight : f.imgSrcDark}`,
};
});
constructor(
private router: Router,
private mgmtService: ManagementService,
breadcrumbService: BreadcrumbService,
activatedRoute: ActivatedRoute,
private _location: Location,
private navigation: NavigationService,
) {
const bread: Breadcrumb = {
type: BreadcrumbType.ORG,
routerLink: ['/org'],
};
breadcrumbService.setBreadcrumb([bread]);
activatedRoute.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params: Params) => {
const { framework } = params;
if (framework) {
this.initialParam.set(framework);
}
});
}
public goToAppCreatePage(): void {
this.router.navigate(['/projects', this.projectId, 'apps', 'create']);
public findFramework(id: string) {
if (id !== 'other') {
this.customFramework.set(false);
const temp = this.frameworks.find((f) => f.id === id);
this.framework.set(temp);
} else {
this.customFramework.set(true);
}
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
public goToAppIntegratePage(): void {
if (this.project && this.customFramework()) {
const id = (this.project.project as Project.AsObject).id
? (this.project.project as Project.AsObject).id
: (this.project.project as GrantedProject.AsObject).projectId
? (this.project.project as GrantedProject.AsObject).projectId
: '';
this.router.navigate(['/projects', id, 'apps', 'create']);
} else if (this.project && this.framework()) {
const id = (this.project.project as Project.AsObject).id
? (this.project.project as Project.AsObject).id
: (this.project.project as GrantedProject.AsObject).projectId
? (this.project.project as GrantedProject.AsObject).projectId
: '';
this.router.navigate(['/projects', id, 'apps', 'integrate'], { queryParams: { framework: this.framework()?.id } });
}
}
public close(): void {
window.history.back();
}
public selectProject(project: Project.AsObject): void {
if (project.id) {
this.projectId = project.id;
if (this.navigation.isBackPossible) {
this._location.back();
} else {
if (this.project && this.framework()) {
const id = (this.project.project as Project.AsObject).id
? (this.project.project as Project.AsObject).id
: (this.project.project as GrantedProject.AsObject).projectId
? (this.project.project as GrantedProject.AsObject).projectId
: '';
this.router.navigate(['/projects', id]);
} else {
this.router.navigate(['/projects']);
}
}
}
public selectProject(project: {
project: Project.AsObject | GrantedProject.AsObject;
type: ProjectType;
name: string;
}): void {
if (project) {
this.project = project;
}
}
public createProjectAndContinue() {
const project = new AddProjectRequest();
project.setName(this.projectName);
return this.mgmtService
.addProject(project.toObject())
.then((resp: AddProjectResponse.AsObject) => {
this.error.set('');
this.router.navigate(['/projects', resp.id, 'apps', 'integrate'], {
queryParams: { framework: this.framework()?.id },
});
})
.catch((error) => {
const { message } = error;
this.error.set(message);
});
}
}

View File

@ -17,11 +17,15 @@ import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.mod
import { SearchProjectAutocompleteModule } from 'src/app/modules/search-project-autocomplete/search-project-autocomplete.module';
import { AppCreateRoutingModule } from './app-create-routing.module';
import { AppCreateComponent } from './app-create.component';
import { FrameworkAutocompleteComponent } from 'src/app/components/framework-autocomplete/framework-autocomplete.component';
import { FrameworkChangeComponent } from 'src/app/components/framework-change/framework-change.component';
@NgModule({
declarations: [AppCreateComponent],
imports: [
FrameworkChangeComponent,
AppCreateRoutingModule,
FrameworkAutocompleteComponent,
CommonModule,
FormsModule,
ReactiveFormsModule,
@ -35,9 +39,6 @@ import { AppCreateComponent } from './app-create.component';
HasRolePipeModule,
TranslateModule,
HasRoleModule,
MatCheckboxModule,
PasswordComplexityViewModule,
MatSlideToggleModule,
],
})
export default class AppCreateModule {}

View File

@ -2,6 +2,8 @@
<h1 class="home-title" data-e2e="authenticated-welcome">{{ 'HOME.WELCOME' | translate }}</h1>
<div class="home-wrapper enlarged-container">
<cnsl-quickstart></cnsl-quickstart>
<ng-container *ngIf="['iam.read$'] | hasRole | async; else defaultHome">
<cnsl-onboarding></cnsl-onboarding>
</ng-container>

View File

@ -19,6 +19,7 @@
.home-title {
font-size: 2rem;
margin-bottom: 1rem;
margin-top: 3rem;
}
.home-wrapper {

View File

@ -13,10 +13,12 @@ import OnboardingModule from 'src/app/modules/onboarding/onboarding.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { HomeRoutingModule } from './home-routing.module';
import { HomeComponent } from './home.component';
import { QuickstartComponent } from 'src/app/components/quickstart/quickstart.component';
@NgModule({
declarations: [HomeComponent],
imports: [
QuickstartComponent,
CommonModule,
MatIconModule,
HasRoleModule,

View File

@ -307,7 +307,7 @@
</span>
</div>
<div class="row">
<div class="row" *ngIf="appType?.value?.createType === AppCreateType.OIDC && authMethod?.value !== 'DEVICECODE'">
<span class="left cnsl-secondary-text">
{{ 'APP.OIDC.REDIRECT' | translate }}
</span>
@ -322,7 +322,7 @@
</span>
</div>
<div class="row">
<div class="row" *ngIf="appType?.value?.createType === AppCreateType.OIDC && authMethod?.value !== 'DEVICECODE'">
<span class="left cnsl-secondary-text">
{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}
</span>

View File

@ -57,6 +57,13 @@
</cnsl-top-view>
<div class="max-width-container">
<cnsl-info-section *ngIf="isNew()" class="problem" [type]="InfoSectionType.INFO">
<div class="jumptoproject-row">
<span class="jumptoproject">{{ 'APP.PAGES.JUMPTOPROJECT' | translate }}</span>
<a [routerLink]="['/projects', projectId]" color="primary" mat-raised-button>{{ 'ACTIONS.CONFIGURE' | translate }}</a>
</div>
</cnsl-info-section>
<div
class="compliance"
*ngIf="app && app.oidcConfig && app.oidcConfig.complianceProblemsList && app.oidcConfig.complianceProblemsList?.length"

View File

@ -125,6 +125,16 @@
}
}
.jumptoproject-row {
display: flex;
justify-content: space-between;
.jumptoproject {
margin-top: 0.25rem;
display: block;
}
}
.compliance {
padding-bottom: 1rem;
padding-top: 0.5rem;

View File

@ -1,6 +1,6 @@
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
import { Location } from '@angular/common';
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { Component, OnDestroy, OnInit, ViewEncapsulation, signal } from '@angular/core';
import { AbstractControl, FormControl, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { MatCheckboxChange } from '@angular/material/checkbox';
import { MatDialog } from '@angular/material/dialog';
@ -77,6 +77,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
public authMethods: RadioItemAuthType[] = [];
private subscription?: Subscription;
public projectId: string = '';
public appId: string = '';
public app?: App.AsObject;
public environmentMap$ = this.envSvc.env.pipe(
@ -149,6 +150,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
public settingsList: SidenavSetting[] = [{ id: 'configuration', i18nKey: 'APP.CONFIGURATION' }];
public currentSetting: string | undefined = this.settingsList[0].id;
public isNew = signal<boolean>(false);
constructor(
private envSvc: EnvironmentService,
public translate: TranslateService,
@ -245,9 +247,13 @@ export class AppDetailComponent implements OnInit, OnDestroy {
public ngOnInit(): void {
const projectId = this.route.snapshot.paramMap.get('projectid');
const appId = this.route.snapshot.paramMap.get('appid');
const isNew = this.route.snapshot.queryParamMap.get('new');
this.isNew.set(isNew === 'true');
if (projectId && appId) {
this.projectId = projectId;
this.appId = appId;
this.getData(projectId, appId);
}
}
@ -635,6 +641,9 @@ export class AppDetailComponent implements OnInit, OnDestroy {
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);

View File

@ -3,6 +3,7 @@ import { RouterModule, Routes } from '@angular/router';
import { AppCreateComponent } from '../apps/app-create/app-create.component';
import { AppDetailComponent } from '../apps/app-detail/app-detail.component';
import { IntegrateAppComponent } from './integrate/integrate.component';
const routes: Routes = [
{
@ -10,6 +11,11 @@ const routes: Routes = [
component: AppCreateComponent,
data: { animation: 'AddPage' },
},
{
path: 'integrate',
component: IntegrateAppComponent,
data: { animation: 'AddPage' },
},
{
path: ':appid',
component: AppDetailComponent,

View File

@ -43,6 +43,9 @@ import { AuthMethodDialogComponent } from './app-detail/auth-method-dialog/auth-
import { AppSecretDialogComponent } from './app-secret-dialog/app-secret-dialog.component';
import { AppsRoutingModule } from './apps-routing.module';
import { RedirectUrisComponent } from './redirect-uris/redirect-uris.component';
import { IntegrateAppComponent } from './integrate/integrate.component';
import { OIDCConfigurationComponent } from 'src/app/components/oidc-configuration/oidc-configuration.component';
import { FrameworkChangeComponent } from 'src/app/components/framework-change/framework-change.component';
@NgModule({
declarations: [
@ -50,12 +53,15 @@ import { RedirectUrisComponent } from './redirect-uris/redirect-uris.component';
AppDetailComponent,
AppSecretDialogComponent,
RedirectUrisComponent,
IntegrateAppComponent,
AdditionalOriginsComponent,
AuthMethodDialogComponent,
],
imports: [
FrameworkChangeComponent,
CommonModule,
A11yModule,
OIDCConfigurationComponent,
RedirectPipeModule,
NameDialogModule,
AppRadioModule,

View File

@ -95,7 +95,7 @@ export const DEVICE_CODE_METHOD: RadioItemAuthType = {
prefix: 'DEVICECODE',
background: 'linear-gradient(40deg, rgb(56 189 248) 30%, rgb(14 165 233))',
responseType: OIDCResponseType.OIDC_RESPONSE_TYPE_CODE,
grantType: [OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE, OIDCGrantType.OIDC_GRANT_TYPE_DEVICE_CODE],
grantType: [OIDCGrantType.OIDC_GRANT_TYPE_DEVICE_CODE],
authMethod: OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_BASIC,
recommended: false,
};
@ -133,7 +133,7 @@ export function getPartialConfigFromAuthMethod(authMethod: string):
config = {
oidc: {
responseTypesList: [OIDCResponseType.OIDC_RESPONSE_TYPE_CODE],
grantTypesList: [OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE, OIDCGrantType.OIDC_GRANT_TYPE_DEVICE_CODE],
grantTypesList: [OIDCGrantType.OIDC_GRANT_TYPE_DEVICE_CODE],
authMethodType: OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE,
},
};

View File

@ -0,0 +1,138 @@
<div class="app-integrate-wrapper">
<div class="integrate-layout-container">
<div class="max-width-container">
<div class="top-control">
<button (click)="close()" mat-icon-button matTooltip="{{ 'ACTIONS.CLOSE' | translate }}">
<mat-icon *ngIf="navigation.isBackPossible">arrow_back</mat-icon>
<mat-icon *ngIf="!navigation.isBackPossible">close</mat-icon>
</button>
<span class="abort">{{ 'APP.PAGES.CREATE' | translate }}</span>
<mat-progress-spinner
class="progress-spinner"
color="primary"
*ngIf="loading"
diameter="30"
mode="indeterminate"
></mat-progress-spinner>
</div>
</div>
</div>
<div class="max-width-container">
<div class="offset-content">
<h1>{{ 'QUICKSTART.ALMOSTDONE' | translate }}</h1>
<p>{{ 'QUICKSTART.REVIEWCONFIGURATION_DESCRIPTION' | translate: { value: framework()?.title } }}</p>
<div class="grid-layout">
<div>
<cnsl-framework-change
*ngIf="framework"
class="framework-selector"
(frameworkChanged)="setFramework($event)"
></cnsl-framework-change>
<div class="steps">
<div class="step">
<span class="step-title">{{ 'PROJECT.PAGES.TITLE' | translate }}</span>
<span>{{ projectName$ | async }}</span>
</div>
<div class="step top-border">
<a *ngIf="framework()" [href]="'https://zitadel.com' + framework()?.docsLink" target="_blank"
>{{ framework()?.title }} {{ 'QUICKSTART.GUIDE' | translate }}</a
>
<a href="https://zitadel.com/docs/sdk-examples/introduction" target="_blank">{{
'QUICKSTART.BROWSEEXAMPLES' | translate
}}</a>
</div>
</div>
</div>
<div class="card-wrapper">
<cnsl-card class="review-card" title="{{ 'QUICKSTART.REVIEWCONFIGURATION' | translate }}">
<cnsl-info-section *ngIf="showRenameWarning | async" [type]="InfoSectionType.WARN"
><span class="duplicate-name-warning">{{
'QUICKSTART.DUPLICATEAPPRENAME' | translate
}}</span></cnsl-info-section
>
<cnsl-oidc-app-configuration
*ngIf="(oidcAppRequest | async)?.toObject() as config"
[name]="(oidcAppRequest | async)?.toObject()?.name"
(changeName)="editName()"
[configuration]="config"
></cnsl-oidc-app-configuration>
<cnsl-info-section *ngIf="framework()?.description as desc" [type]="InfoSectionType.INFO">
<p class="review-description">
{{ desc }}
</p>
</cnsl-info-section>
</cnsl-card>
<cnsl-card
title="{{ 'QUICKSTART.REDIRECTS' | translate }}"
description="{{ 'APP.OIDC.REDIRECTTITLE' | translate }}"
>
<cnsl-info-section [type]="InfoSectionType.ALERT">
<span class="redirect-description">
{{ 'QUICKSTART.DEVMODEWARN' | translate }}
</span>
</cnsl-info-section>
<cnsl-info-section *ngIf="(oidcAppRequest | async)?.toObject()?.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE">
<span class="redirect-description">
{{ 'APP.OIDC.REDIRECTDESCRIPTIONNATIVE' | translate }}
</span>
</cnsl-info-section>
<cnsl-info-section
*ngIf="
(oidcAppRequest | async)?.toObject()?.appType === OIDCAppType.OIDC_APP_TYPE_WEB ||
(oidcAppRequest | async)?.toObject()?.appType === OIDCAppType.OIDC_APP_TYPE_USER_AGENT
"
>
<span class="redirect-description">
{{ 'APP.OIDC.REDIRECTDESCRIPTIONWEB' | translate }}
</span>
</cnsl-info-section>
<cnsl-redirect-uris
*ngIf="requestRedirectValuesSubject$"
class="redirect-section"
[disabled]="false"
[isNative]="(oidcAppRequest | async)?.toObject()?.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
[(ngModel)]="redirectUris"
[getValues]="requestRedirectValuesSubject$"
title="{{ 'APP.OIDC.REDIRECT' | translate }}"
[devMode]="true"
data-e2e="redirect-uris"
>
</cnsl-redirect-uris>
<cnsl-redirect-uris
*ngIf="requestRedirectValuesSubject$"
class="redirect-section"
[disabled]="false"
[(ngModel)]="postLogoutUrisList"
[getValues]="requestRedirectValuesSubject$"
title="{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}"
[isNative]="(oidcAppRequest | async)?.toObject()?.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
[devMode]="true"
data-e2e="postlogout-uris"
>
</cnsl-redirect-uris>
</cnsl-card>
</div>
</div>
<div class="app-integrate-actions">
<button
mat-raised-button
[disabled]="loading"
class="create-button"
color="primary"
(click)="createApp()"
data-e2e="create-button"
>
{{ 'ACTIONS.CREATE' | translate }}
</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,214 @@
h1 {
font-weight: 500;
font-size: 2rem;
}
.integrate-layout-container {
margin: 0 -2rem 2rem -2rem;
padding: 3rem 2rem 14rem 2rem;
.top-control {
display: flex;
align-items: center;
.abort {
font-size: 1.2rem;
margin-left: 1.5rem;
text-transform: uppercase;
font-size: 14px;
opacity: 0.8;
letter-spacing: 0.05em;
}
.progress-spinner {
margin-left: 2rem;
}
}
}
.offset-content {
margin-top: -14rem;
}
.grid-layout {
margin-top: 2rem;
display: grid;
grid-template-columns: [first] 300px [second] auto;
grid-column-gap: 5rem;
.framework-selector {
margin-bottom: 2rem;
display: block;
}
.steps {
margin-top: 0.5rem;
.step {
display: flex;
flex-direction: column;
padding: 1rem 0;
.step-title {
font-weight: 700;
font-size: 12px;
opacity: 0.8;
margin-bottom: 0.5rem;
text-transform: uppercase;
}
a {
margin-bottom: 0.5rem;
}
}
}
.redirect-p {
margin-top: 0;
}
.redirect-description {
font-size: 14px;
margin: 0.25rem 0 0 0;
display: block;
}
}
@media only screen and (max-width: 900px) {
.integrate-layout-container {
margin: 0 -2rem 2rem -2rem;
background: transparent;
padding: 3rem 2rem 14rem 2rem;
}
.grid-layout {
display: flex;
flex-direction: column;
.steps {
display: none;
}
}
}
.card-wrapper {
margin-top: -1rem;
.duplicate-name-warning {
margin-top: 0.25rem;
display: block;
}
}
@mixin app-integrate-theme($theme) {
$primary: map-get($theme, primary);
$warn: map-get($theme, warn);
$background: map-get($theme, background);
$accent: map-get($theme, accent);
$primary-color: map-get($primary, 500);
$warn-color: map-get($warn, 500);
$accent-color: map-get($accent, 500);
$foreground: map-get($theme, foreground);
$is-dark-theme: map-get($theme, is-dark);
$back: map-get($background, background);
$list-background-color: map-get($background, 300);
$card-background-color: map-get($background, cards);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2));
$border-selected-color: if($is-dark-theme, #fff, #000);
.integrate-layout-container {
background: map-get($background, metadata-section);
}
.review-description {
font-size: 14px;
margin: 0.25rem 0 0 0;
display: block;
}
.grid-layout {
.step {
&.top-border {
border-top: 2px solid $border-color;
}
}
a {
color: $primary-color;
}
}
.framework-card-wrapper {
display: flex;
align-items: center;
gap: 1rem;
.framework-card {
position: relative;
flex-shrink: 0;
text-decoration: none;
border-radius: 0.5rem;
box-sizing: border-box;
transition: all 0.1s ease-in;
display: flex;
flex-direction: row;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
width: fit-content;
border: 1px solid rgba(#8795a1, 0.2);
padding: 0 0.5rem;
img {
width: 100%;
height: 100%;
max-width: 40px;
max-height: 40px;
object-fit: contain;
object-position: center;
}
.dark-only {
display: if($is-dark-theme, block, none);
}
.light-only {
display: if($is-dark-theme, none, block);
}
span {
margin: 0.5rem;
text-align: center;
color: map-get($foreground, text);
}
.action-row {
display: flex;
align-items: center;
justify-content: flex-end;
font-size: 14px;
margin-bottom: 0.5rem;
color: map-get($primary, 400);
.icon {
margin-left: 0rem;
}
}
}
}
}
.app-integrate-actions {
margin-top: 2rem;
display: flex;
align-items: center;
justify-content: flex-end;
.create-button {
height: 3.5rem;
padding: 0 4rem;
}
}

View File

@ -0,0 +1,24 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { IntegrateAppComponent } from './integrate.component';
describe('IntegrateAppComponent', () => {
let component: IntegrateAppComponent;
let fixture: ComponentFixture<IntegrateAppComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [IntegrateAppComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(IntegrateAppComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,220 @@
import { C, COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
import { StepperSelectionEvent } from '@angular/cdk/stepper';
import { Location } from '@angular/common';
import { Component, OnDestroy, OnInit, Signal, computed, effect, signal } from '@angular/core';
import { AbstractControl, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { Buffer } from 'buffer';
import { BehaviorSubject, Subject, Subscription, combineLatest } from 'rxjs';
import { debounceTime, map, takeUntil } from 'rxjs/operators';
import { RadioItemAuthType } from 'src/app/modules/app-radio/app-auth-method-radio/app-auth-method-radio.component';
import { requiredValidator } from 'src/app/modules/form-field/validators/validators';
import {
APIAuthMethodType,
OIDCAppType,
OIDCAuthMethodType,
OIDCGrantType,
OIDCResponseType,
} from 'src/app/proto/generated/zitadel/app_pb';
import {
AddAPIAppRequest,
AddAPIAppResponse,
AddOIDCAppRequest,
AddOIDCAppResponse,
AddSAMLAppRequest,
} from 'src/app/proto/generated/zitadel/management_pb';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { AppSecretDialogComponent } from '../app-secret-dialog/app-secret-dialog.component';
import { InfoSectionType } from 'src/app/modules/info-section/info-section.component';
import { Framework } from 'src/app/components/quickstart/quickstart.component';
import { OIDC_CONFIGURATIONS } from 'src/app/utils/framework';
import { NavigationService } from 'src/app/services/navigation.service';
import { NameDialogComponent } from 'src/app/modules/name-dialog/name-dialog.component';
@Component({
selector: 'cnsl-integrate',
templateUrl: './integrate.component.html',
styleUrls: ['./integrate.component.scss'],
})
export class IntegrateAppComponent implements OnInit, OnDestroy {
private destroy$: Subject<void> = new Subject();
public projectId: string = '';
public loading: boolean = false;
public InfoSectionType: any = InfoSectionType;
public framework = signal<Framework | undefined>(undefined);
public showRenameWarning: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public oidcAppRequest: BehaviorSubject<AddOIDCAppRequest> = new BehaviorSubject(new AddOIDCAppRequest());
public OIDCAppType: any = OIDCAppType;
public requestRedirectValuesSubject$: Subject<void> = new Subject();
constructor(
private activatedRoute: ActivatedRoute,
private router: Router,
private toast: ToastService,
private dialog: MatDialog,
private mgmtService: ManagementService,
private _location: Location,
private breadcrumbService: BreadcrumbService,
public navigation: NavigationService,
) {
effect(() => {
const fwId = this.framework()?.id;
const fw = this.framework();
if (fw && fwId) {
const request = OIDC_CONFIGURATIONS[fwId];
request.setProjectId(this.projectId);
request.setName(fw.title);
request.setDevMode(true);
this.requestRedirectValuesSubject$.next();
this.showRenameWarning.next(false);
this.oidcAppRequest.next(request);
return request;
} else {
const request = new AddOIDCAppRequest();
this.oidcAppRequest.next(request);
return request;
}
});
}
public projectName$ = combineLatest([this.mgmtService.ownedProjects, this.mgmtService.grantedProjects]).pipe(
map(([projects, grantedProjects]) => {
const project = projects.find((project) => project.id === this.activatedRoute.snapshot.paramMap.get('projectid'));
const grantedproject = grantedProjects.find(
(grantedproject) => grantedproject.projectId === this.activatedRoute.snapshot.paramMap.get('projectid'),
);
return project?.name ?? grantedproject?.projectName ?? '';
}),
);
public setFramework(framework: Framework | undefined) {
this.framework.set(framework);
}
public ngOnInit(): void {
const projectId = this.activatedRoute.snapshot.paramMap.get('projectid');
if (projectId) {
const breadcrumbs = [
new Breadcrumb({
type: BreadcrumbType.ORG,
routerLink: ['/org'],
}),
new Breadcrumb({
type: BreadcrumbType.PROJECT,
name: '',
param: { key: 'projectid', value: projectId },
routerLink: ['/projects', projectId],
isZitadel: false,
}),
];
this.projectId = projectId;
this.breadcrumbService.setBreadcrumb(breadcrumbs);
}
}
public ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
public close(): void {
if (this.navigation.isBackPossible) {
this._location.back();
} else {
this.router.navigate(['/projects', this.projectId]);
}
}
public createApp(): void {
this.loading = true;
this.mgmtService
.addOIDCApp(this.oidcAppRequest.getValue())
.then((resp) => {
this.loading = false;
this.showRenameWarning.next(false);
this.toast.showInfo('APP.TOAST.CREATED', true);
if (resp.clientSecret) {
this.showSavedDialog(resp);
} else {
this.router.navigate(['projects', this.projectId, 'apps', resp.appId], { queryParams: { new: true } });
}
})
.catch((error) => {
if (error.code === 6) {
this.showRenameWarning.next(true);
}
this.loading = false;
this.toast.showError(error);
});
}
public editName() {
const dialogRef = this.dialog.open(NameDialogComponent, {
data: {
name: this.oidcAppRequest.getValue()?.getName() ?? '',
titleKey: 'APP.NAMEDIALOG.TITLE',
descKey: 'APP.NAMEDIALOG.DESCRIPTION',
labelKey: 'APP.NAMEDIALOG.NAME',
},
width: '400px',
});
dialogRef.afterClosed().subscribe((name) => {
if (name && name !== this.framework()?.title) {
const request = this.oidcAppRequest.getValue();
request.setName(name);
this.showRenameWarning.next(false);
this.oidcAppRequest.next(request);
}
});
}
public showSavedDialog(added: AddOIDCAppResponse.AsObject | AddAPIAppResponse.AsObject): void {
let clientSecret = '';
if (added.clientSecret) {
clientSecret = added.clientSecret;
}
let clientId = '';
if (added.clientId) {
clientId = added.clientId;
}
const dialogRef = this.dialog.open(AppSecretDialogComponent, {
data: {
clientSecret: clientSecret,
clientId: clientId,
},
});
dialogRef.afterClosed().subscribe(() => {
this.router.navigate(['projects', this.projectId, 'apps', added.appId], { queryParams: { new: true } });
});
}
public get redirectUris() {
return this.oidcAppRequest.getValue().toObject().redirectUrisList;
}
public set redirectUris(value: string[]) {
const request = this.oidcAppRequest.getValue();
request.setRedirectUrisList(value);
this.oidcAppRequest.next(request);
}
public get postLogoutUrisList() {
return this.oidcAppRequest.getValue().toObject().postLogoutRedirectUrisList;
}
public set postLogoutUrisList(value: string[]) {
const request = this.oidcAppRequest.getValue();
request.setPostLogoutRedirectUrisList(value);
this.oidcAppRequest.next(request);
}
}

View File

@ -1,7 +1,7 @@
import { DatePipe } from '@angular/common';
import { Pipe, PipeTransform } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import * as moment from 'moment';
import moment from 'moment';
import { supportedLanguages } from 'src/app/utils/language';
@Pipe({

View File

@ -87,6 +87,12 @@ export function getMembershipColor(role: string): Color {
case 'IAM_USER_MANAGER':
color = COLORS[8];
break;
case 'IAM_ADMIN_IMPERSONATOR':
color = COLORS[17];
break;
case 'IAM_END_USER_IMPERSONATOR':
color = COLORS[9];
break;
case 'ORG_OWNER':
color = COLORS[16];
@ -106,6 +112,12 @@ export function getMembershipColor(role: string): Color {
case 'ORG_PROJECT_CREATOR':
color = COLORS[12];
break;
case 'ORG_ADMIN_IMPERSONATOR':
color = COLORS[17];
break;
case 'ORG_END_USER_IMPERSONATOR':
color = COLORS[9];
break;
case 'PROJECT_OWNER':
color = COLORS[9];

View File

@ -0,0 +1,70 @@
import { Framework } from '@netlify/framework-info/lib/types';
import { AddOIDCAppRequest } from '../proto/generated/zitadel/management_pb';
import { FrameworkName } from '@netlify/framework-info/lib/generated/frameworkNames';
import { OIDCAppType, OIDCAuthMethodType, OIDCGrantType, OIDCResponseType } from '../proto/generated/zitadel/app_pb';
type OidcAppConfigurations = {
[framework: string]: AddOIDCAppRequest;
};
export const OIDC_CONFIGURATIONS: OidcAppConfigurations = {
// user agent applications
['angular']: new AddOIDCAppRequest()
.setAppType(OIDCAppType.OIDC_APP_TYPE_USER_AGENT)
.setAuthMethodType(OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE)
.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE])
.setGrantTypesList([OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE])
.setRedirectUrisList(['http://localhost:4200/callback'])
.setPostLogoutRedirectUrisList(['http://localhost:4200']),
['react']: new AddOIDCAppRequest()
.setAppType(OIDCAppType.OIDC_APP_TYPE_USER_AGENT)
.setAuthMethodType(OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE)
.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE])
.setGrantTypesList([OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE])
.setRedirectUrisList(['http://localhost:3000/callback'])
.setPostLogoutRedirectUrisList(['http://localhost:3000']),
['vue']: new AddOIDCAppRequest()
.setAppType(OIDCAppType.OIDC_APP_TYPE_USER_AGENT)
.setAuthMethodType(OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE)
.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE])
.setGrantTypesList([OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE])
.setRedirectUrisList(['http://localhost:5173/auth/signinwin/zitadel'])
.setPostLogoutRedirectUrisList(['http://localhost:5173']),
// web applications
['next']: new AddOIDCAppRequest()
.setAppType(OIDCAppType.OIDC_APP_TYPE_WEB)
.setAuthMethodType(OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_BASIC)
.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE])
.setGrantTypesList([OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE])
.setRedirectUrisList(['http://localhost:3000/callback'])
.setPostLogoutRedirectUrisList(['http://localhost:3000']),
['java']: new AddOIDCAppRequest()
.setAppType(OIDCAppType.OIDC_APP_TYPE_WEB)
.setAuthMethodType(OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE)
.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE])
.setGrantTypesList([OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE])
.setRedirectUrisList(['http://localhost:18080/webapp/login/oauth2/code/zitadel'])
.setPostLogoutRedirectUrisList(['http://localhost:18080/webapp']),
['symfony']: new AddOIDCAppRequest()
.setAppType(OIDCAppType.OIDC_APP_TYPE_WEB)
.setAuthMethodType(OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_BASIC)
.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE])
.setGrantTypesList([OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE])
.setRedirectUrisList(['http://localhost:8000/login_check'])
.setPostLogoutRedirectUrisList(['http://localhost:8000/logout']),
['django']: new AddOIDCAppRequest()
.setAppType(OIDCAppType.OIDC_APP_TYPE_WEB)
.setAuthMethodType(OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE)
.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE])
.setGrantTypesList([OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE])
.setRedirectUrisList(['http://localhost:8000/oidc/callback/'])
.setPostLogoutRedirectUrisList(['http://localhost:8000/oidc/logout/ ']),
// native
['flutter']: new AddOIDCAppRequest()
.setAppType(OIDCAppType.OIDC_APP_TYPE_NATIVE)
.setAuthMethodType(OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE)
.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE])
.setGrantTypesList([OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE])
.setRedirectUrisList(['http://localhost:4444/auth.html', 'com.example.zitadelflutter'])
.setPostLogoutRedirectUrisList(['http://localhost:4444', 'com.example.zitadelflutter']),
};

View File

@ -119,6 +119,30 @@
"SETTINGS": "Настройки",
"CUSTOMERPORTAL": "Портал за клиенти"
},
"QUICKSTART": {
"TITLE": "Интегрирайте ZITADEL във вашето приложение",
"DESCRIPTION": "Интегрирайте ZITADEL във вашето приложение или използвайте някой от нашите образци, за да започнете за минути.",
"BTN_START": "Създаване на приложение",
"BTN_LEARNMORE": "Научете повече",
"CREATEPROJECTFORAPP": "Създаване на проект {{value}}",
"SELECT_FRAMEWORK": "Изберете рамка",
"FRAMEWORK": "Рамка",
"FRAMEWORK_OTHER": "други (OIDC, SAML, API)",
"ALMOSTDONE": "Почти сте готови.",
"REVIEWCONFIGURATION": "Конфигурация на прегледа",
"REVIEWCONFIGURATION_DESCRIPTION": "Създадохме основна конфигурация за {{value}} приложения. След създаването можете да адаптирате тази конфигурация към вашите нужди.",
"REDIRECTS": "Конфигуриране на пренасочвания",
"DEVMODEWARN": "Режимът Dev е активиран по подразбиране. Можете да актуализирате стойностите за производство по-късно.",
"GUIDE": "Guide",
"BROWSEEXAMPLES": "Преглед на примери",
"DUPLICATEAPPRENAME": "Вече съществува приложение със същото име. Моля, изберете друго име.",
"DIALOG": {
"CHANGE": {
"TITLE": "Рамка за промяна",
"DESCRIPTION": "Изберете една от наличните рамки за бърза настройка на вашето приложение."
}
}
},
"ACTIONS": {
"ACTIONS": "Действия",
"FILTER": "Филтър",
@ -138,6 +162,7 @@
"ADD": "Добавете",
"CREATE": "Създавайте",
"CONTINUE": "продължи",
"CONTINUEWITH": "Продължете с {{value}}",
"BACK": "обратно",
"CLOSE": "Близо",
"CLEAR": "ясно",
@ -181,12 +206,16 @@
"IAM_OWNER_VIEWER": "Има разрешение да прегледа целия екземпляр, включително всички организации",
"IAM_ORG_MANAGER": "Има разрешение за създаване и управление на организации",
"IAM_USER_MANAGER": "Има разрешение за създаване и управление на потребители",
"IAM_ADMIN_IMPERSONATOR": "Има разрешение да се представя за администратор и крайни потребители от всички организации",
"IAM_END_USER_IMPERSONATOR": "Има разрешение да се представя за крайни потребители от всички организации",
"ORG_OWNER": "Има разрешение за цялата организация",
"ORG_USER_MANAGER": "Има разрешение да създава и управлява потребители на организацията",
"ORG_OWNER_VIEWER": "Има разрешение за преглед на цялата организация",
"ORG_USER_PERMISSION_EDITOR": "Има разрешение за управление на потребителски безвъзмездни средства",
"ORG_PROJECT_PERMISSION_EDITOR": "Има разрешение за управление на грантове по проекти",
"ORG_PROJECT_CREATOR": "Има разрешение да създава свои собствени проекти и основни настройки",
"ORG_ADMIN_IMPERSONATOR": "Има разрешение да се представя за администратор и крайни потребители от организацията",
"ORG_END_USER_IMPERSONATOR": "Има разрешение да се представя за крайни потребители от организацията",
"PROJECT_OWNER": "Има разрешение върху целия проект",
"PROJECT_OWNER_VIEWER": "Има разрешение за преглед на целия проект",
"PROJECT_OWNER_GLOBAL": "Има разрешение върху целия проект",
@ -1153,9 +1182,13 @@
"UPDATED": "Настройките обновени."
},
"SECURITY": {
"DESCRIPTION": "Тази настройка настройва CSP да позволява рамкиране от набор от разрешени домейни. ",
"IFRAMETITLE": "iFrame",
"IFRAMEDESCRIPTION": "Тази настройка настройва CSP да позволява рамкиране от набор от разрешени домейни. ",
"IFRAMEENABLED": "Разрешаване на iFrame",
"ALLOWEDORIGINS": "Разрешени URL адреси"
"ALLOWEDORIGINS": "Разрешени URL адреси",
"IMPERSONATIONTITLE": "Имитиране",
"IMPERSONATIONENABLED": "Разрешаване на имитация",
"IMPERSONATIONDESCRIPTION": "Тази настройка позволява да се използва имитация по принцип. Обърнете внимание, че имитаторът също се нуждае от присвоени подходящи роли `*_IMPERSONATOR`."
},
"DIALOG": {
"RESET": {
@ -1944,6 +1977,7 @@
"DATECHANGED": "Променен",
"URLS": "URL адреси",
"DELETE": "Изтриване на приложение",
"JUMPTOPROJECT": "За да конфигурирате роли, оторизации и други, отидете до проекта.",
"DETAIL": {
"TITLE": "детайл",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "Nastavení",
"CUSTOMERPORTAL": "Zákaznický portál"
},
"QUICKSTART": {
"TITLE": "Integrovat ZITADEL do vaší aplikace",
"DESCRIPTION": "Integrujte ZITADEL do své aplikace nebo použijte některou z našich ukázek a začněte během několika minut.",
"BTN_START": "Vytvořit aplikaci",
"BTN_LEARNMORE": "Zjistěte více",
"CREATEPROJECTFORAPP": "Vytvořit projekt {{value}}",
"SELECT_FRAMEWORK": "Vybrat framework",
"FRAMEWORK": "Framework",
"FRAMEWORK_OTHER": "jiný (OIDC, SAML, API)",
"ALMOSTDONE": "Jste téměř hotovi.",
"REVIEWCONFIGURATION": "Přezkoumání konfigurace",
"REVIEWCONFIGURATION_DESCRIPTION": "Vytvořili jsme základní konfiguraci pro {{hodnota}} aplikace. Tuto konfiguraci můžete po vytvoření přizpůsobit svým potřebám.",
"REDIRECTS": "Konfigurace přesměrování",
"DEVMODEWARN": "Dev Mode je ve výchozím nastavení povolen. Hodnoty pro produkci můžete aktualizovat později.",
"GUIDE": "Guide",
"BROWSEEXAMPLES": "Procházet ukázky",
"DUPLICATEAPPRENAME": "Aplikace se stejným neme již existuje. Vyberte prosím jiný název.",
"DIALOG": {
"CHANGE": {
"TITLE": "Rámec změn",
"DESCRIPTION": "Vyberte si jeden z dostupných frameworků pro rychlé nastavení vaší aplikace."
}
}
},
"ACTIONS": {
"ACTIONS": "Akce",
"FILTER": "Filtr",
@ -138,6 +162,7 @@
"ADD": "Přidat",
"CREATE": "Vytvořit",
"CONTINUE": "Pokračovat",
"CONTINUEWITH": "Pokračovat s {{value}}",
"BACK": "Zpět",
"CLOSE": "Zavřít",
"CLEAR": "Vyčistit",
@ -188,12 +213,16 @@
"IAM_OWNER_VIEWER": "Má oprávnění prohlížet celou instanci, včetně všech organizací",
"IAM_ORG_MANAGER": "Má oprávnění vytvářet a spravovat organizace",
"IAM_USER_MANAGER": "Má oprávnění vytvářet a spravovat uživatele",
"IAM_ADMIN_IMPERSONATOR": "Má oprávnění vydávat se za správce a koncové uživatele ze všech organizací",
"IAM_END_USER_IMPERSONATOR": "Má oprávnění vydávat se za koncové uživatele ze všech organizací",
"ORG_OWNER": "Má oprávnění nad celou organizací",
"ORG_USER_MANAGER": "Má oprávnění vytvářet a spravovat uživatele organizace",
"ORG_OWNER_VIEWER": "Má oprávnění prohlížet celou organizaci",
"ORG_USER_PERMISSION_EDITOR": "Má oprávnění spravovat uživatelská pověření",
"ORG_PROJECT_PERMISSION_EDITOR": "Má oprávnění spravovat pověření projektu",
"ORG_PROJECT_CREATOR": "Má oprávnění vytvářet své vlastní projekty a podřízená nastavení",
"ORG_ADMIN_IMPERSONATOR": "Má oprávnění vydávat se za správce a koncové uživatele z organizace",
"ORG_END_USER_IMPERSONATOR": "Má oprávnění vydávat se za koncové uživatele z organizace",
"PROJECT_OWNER": "Má oprávnění nad celým projektem",
"PROJECT_OWNER_VIEWER": "Má oprávnění prohlížet celý projekt",
"PROJECT_OWNER_GLOBAL": "Má oprávnění nad celým projektem",
@ -1160,9 +1189,13 @@
"UPDATED": "Nastavení aktualizováno."
},
"SECURITY": {
"DESCRIPTION": "Toto nastavení nastaví CSP tak, aby povolovalo vkládání ze sady povolených domén. Všimněte si, že povolením použití iFrame riskujete umožnění clickjackingu.",
"IFRAMETITLE": "iFrame",
"IFRAMEDESCRIPTION": "Toto nastavení nastaví CSP tak, aby povolovalo vkládání ze sady povolených domén. Všimněte si, že povolením použití iFrame riskujete umožnění clickjackingu.",
"IFRAMEENABLED": "Povolit iFrame",
"ALLOWEDORIGINS": "Povolené URL"
"ALLOWEDORIGINS": "Povolené URL",
"IMPERSONATIONTITLE": "Předstírání jiné identity",
"IMPERSONATIONENABLED": "Povolit předstírání jiné identity",
"IMPERSONATIONDESCRIPTION": "Toto nastavení v zásadě umožňuje používat zosobnění. Všimněte si, že imitátor potřebuje také přiřazené příslušné role `*_IMPERSONATOR`."
},
"DIALOG": {
"RESET": {
@ -1963,6 +1996,7 @@
"DATECHANGED": "Změněno",
"URLS": "URL adresy",
"DELETE": "Smazat aplikaci",
"JUMPTOPROJECT": "Chcete-li nakonfigurovat role, oprávnění a další, přejděte do projektu.",
"DETAIL": {
"TITLE": "Detail",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "Einstellungen",
"CUSTOMERPORTAL": "Kundenportal"
},
"QUICKSTART": {
"TITEL": "Integriere ZITADEL in deine Anwendung",
"DESCRIPTION": "Integriere ZITADEL in deine Anwendung oder verwende eines unserer Beispiele, um in wenigen Minuten loszulegen.",
"BTN_START": "Anwendung erstellen",
"BTN_LEARNMORE": "Mehr erfahren",
"CREATEPROJECTFORAPP": "Projekt erstellen {{value}}",
"SELECT_FRAMEWORK": "Framework auswählen",
"FRAMEWORK": "Framework",
"FRAMEWORK_OTHER": "Andere (OIDC, SAML, API)",
"ALMOSTDONE": "Wir sind fast fertig.",
"REVIEWCONFIGURATION": "Konfiguration überprüfen",
"REVIEWCONFIGURATION_DESCRIPTION": "Wir haben eine Grundkonfiguration für {{wert}}-Anwendungen erstellt. Du kannst diese Konfiguration nach der Erstellung an deine Bedürfnisse anpassen.",
"REDIRECTS": "Redirects konfigurieren",
"DEVMODEWARN": "Der Dev-Modus ist standardmäßig aktiviert. Sie können Werte für die Produktion später aktualisieren.",
"GUIDE": "Guide",
"BROWSEEXAMPLES": "Beispiele durchsuchen",
"DUPLICATEAPPRENAME": "Es gibt bereits eine App mit demselben Neme. Bitte wählen Sie einen anderen Namen.",
"DIALOG": {
"CHANGE": {
"TITLE": "Framework ändern",
"DESCRIPTION": "Wähle eines der verfügbaren Frameworks für die schnelle Einrichtung deiner Anwendung."
}
}
},
"ACTIONS": {
"ACTIONS": "Aktionen",
"FILTER": "Filter",
@ -138,6 +162,7 @@
"ADD": "Hinzufügen",
"CREATE": "Erstellen",
"CONTINUE": "Weiter",
"CONTINUEWITH": "Mit {{value}} fortfahren",
"BACK": "Zurück",
"CLOSE": "Schliessen",
"CLEAR": "Zurücksetzen",
@ -187,12 +212,16 @@
"IAM_OWNER_VIEWER": "Hat die Leseberechtigung, die gesamte Instanz einschließlich aller Organisationen zu überprüfen",
"IAM_ORG_MANAGER": "Hat die Berechtigung zum Erstellen und Verwalten von Organisationen",
"IAM_USER_MANAGER": "Hat die Berechtigung zum Erstellen und Verwalten von Benutzern",
"IAM_ADMIN_IMPERSONATOR": "Hat die Berechtigung, sich als Administrator und Endbenutzer aller Organisationen auszugeben",
"IAM_END_USER_IMPERSONATOR": "Hat die Berechtigung, sich als Endbenutzer aller Organisationen auszugeben",
"ORG_OWNER": "Hat die Berechtigung für die gesamte Organisation",
"ORG_USER_MANAGER": "Hat die Berechtigung, Benutzer der Organisation zu erstellen und zu verwalten",
"ORG_OWNER_VIEWER": "Hat die Leseberechtigung, die gesamte Organisation zu überprüfen",
"ORG_USER_PERMISSION_EDITOR": "Verfügt über die Berechtigung zum Verwalten von User grants",
"ORG_PROJECT_PERMISSION_EDITOR": "Hat die Berechtigung, Projektberechtigungen für externe Organisationen zu verwalten",
"ORG_PROJECT_CREATOR": "Hat die Berechtigung, seine eigenen Projekte und zugrunde liegenden Einstellungen zu erstellen",
"ORG_ADMIN_IMPERSONATOR": "Hat die Berechtigung, sich als Administrator und Endbenutzer der Organisation auszugeben",
"ORG_END_USER_IMPERSONATOR": "Hat die Berechtigung, sich als Endbenutzer der Organisation auszugeben",
"PROJECT_OWNER": "Hat die Berechtigung für das gesamte Projekt",
"PROJECT_OWNER_VIEWER": "Hat die Leseberechtigung, das gesamte Projekt zu überprüfen",
"PROJECT_OWNER_GLOBAL": "Hat die Berechtigung für das gesamte Projekt",
@ -1159,9 +1188,13 @@
"UPDATED": "Einstellungen geändert"
},
"SECURITY": {
"DESCRIPTION": "Mit dieser Einstellung wird die CSP so eingestellt, dass Framing von einer Reihe zulässiger Domänen zugelassen wird. Beachten Sie, dass Sie durch die Aktivierung der Verwendung von iFrames das Risiko eingehen, Clickjacking zu ermöglichen.",
"IFRAMETITLE": "iFrame",
"IFRAMEDESCRIPTION": "Mit dieser Einstellung wird die CSP so eingestellt, dass Framing von einer Reihe zulässiger Domänen zugelassen wird. Beachten Sie, dass Sie durch die Aktivierung der Verwendung von iFrames das Risiko eingehen, Clickjacking zu ermöglichen.",
"IFRAMEENABLED": "iFrame zulassen",
"ALLOWEDORIGINS": "Zulässige URLs"
"ALLOWEDORIGINS": "Zulässige URLs",
"IMPERSONATIONTITLE": "Identitätswechsel",
"IMPERSONATIONENABLED": "Identitätswechsel zulassen",
"IMPERSONATIONDESCRIPTION": "Diese Einstellung ermöglicht grundsätzlich die Verwendung von Identitätswechseln. Beachten Sie, dass dem Imitator auch die entsprechenden `*_IMPERSONATOR`-Rollen zugewiesen werden müssen."
},
"DIALOG": {
"RESET": {
@ -1953,6 +1986,7 @@
"DATECHANGED": "Geändert",
"URLS": "URLs",
"DELETE": "App löschen",
"JUMPTOPROJECT": "Um Rollen, Berechtigungen und mehr zu konfigurieren, navigieren Sie zum Projekt.",
"DETAIL": {
"TITLE": "Detail",
"STATE": {

View File

@ -43,7 +43,7 @@
}
},
"ONBOARDING": {
"DESCRIPTION": "Your onboarding process",
"DESCRIPTION": "Your next steps",
"MOREDESCRIPTION": "more shortcuts",
"COMPLETED": "completed",
"DISMISS": "No thanks, I'm a pro.",
@ -119,6 +119,30 @@
"SETTINGS": "Settings",
"CUSTOMERPORTAL": "Customer Portal"
},
"QUICKSTART": {
"TITLE": "Integrate ZITADEL into your application",
"DESCRIPTION": "Integrate ZITADEL into your application or use one of our samples to get started in minutes.",
"BTN_START": "Create Application",
"BTN_LEARNMORE": "Learn More",
"CREATEPROJECTFORAPP": "Create Project {{value}}",
"SELECT_FRAMEWORK": "Select Framework",
"FRAMEWORK": "Framework",
"FRAMEWORK_OTHER": "Other (OIDC, SAML, API)",
"ALMOSTDONE": "You're almost done.",
"REVIEWCONFIGURATION": "Review Configuration",
"REVIEWCONFIGURATION_DESCRIPTION": "We've created a basic configuration for {{value}} applications. You can adapt this configuration to your needs after creation.",
"REDIRECTS": "Configure redirects",
"DEVMODEWARN": "Dev Mode is enabled by default. You can update values for production later.",
"GUIDE": "Guide",
"BROWSEEXAMPLES": "Browse Examples and SDKs",
"DUPLICATEAPPRENAME": "An app with the same name exists already. Please choose a different name.",
"DIALOG": {
"CHANGE": {
"TITLE": "Change Framework",
"DESCRIPTION": "Choose one of the available frameworks for quick setup of your application."
}
}
},
"ACTIONS": {
"ACTIONS": "Actions",
"FILTER": "Filter",
@ -138,6 +162,7 @@
"ADD": "Add",
"CREATE": "Create",
"CONTINUE": "Continue",
"CONTINUEWITH": "Continue with {{value}}",
"BACK": "Back",
"CLOSE": "Close",
"CLEAR": "Clear",
@ -188,12 +213,16 @@
"IAM_OWNER_VIEWER": "Has permission to review the whole instance, including all organizations",
"IAM_ORG_MANAGER": "Has permission to create and manage organizations",
"IAM_USER_MANAGER": "Has permission to create and manage users",
"IAM_ADMIN_IMPERSONATOR": "Has permission to impersonate admin and end users from all organizations",
"IAM_END_USER_IMPERSONATOR": "Has permission to impersonate end users from all organizations",
"ORG_OWNER": "Has permission over the whole organization",
"ORG_USER_MANAGER": "Has permission to create and manage users of the organization",
"ORG_OWNER_VIEWER": "Has permission to review the whole organization",
"ORG_USER_PERMISSION_EDITOR": "Has permission to manage user grants",
"ORG_PROJECT_PERMISSION_EDITOR": "Has permission to manage project grants",
"ORG_PROJECT_CREATOR": "Has permission to create his own projects and underlying settings",
"ORG_ADMIN_IMPERSONATOR": "Has permission to impersonate admin and end users from the organization",
"ORG_END_USER_IMPERSONATOR": "Has permission to impersonate end users from the organization",
"PROJECT_OWNER": "Has permission over the whole project",
"PROJECT_OWNER_VIEWER": "Has permission to review the whole project",
"PROJECT_OWNER_GLOBAL": "Has permission over the whole project",
@ -1160,9 +1189,13 @@
"UPDATED": "Settings updated."
},
"SECURITY": {
"DESCRIPTION": "This setting sets the CSP to allow framing from a set of allowed domains. Note that by enabling the use of iFrames, you run the risk of allowing clickjacking.",
"IFRAMETITLE": "iFrame",
"IFRAMEDESCRIPTION": "This setting sets the CSP to allow framing from a set of allowed domains. Note that by enabling the use of iFrames, you run the risk of allowing clickjacking.",
"IFRAMEENABLED": "Allow iFrame",
"ALLOWEDORIGINS": "Allowed URLs"
"ALLOWEDORIGINS": "Allowed URLs",
"IMPERSONATIONTITLE": "Impersonation",
"IMPERSONATIONENABLED": "Allow Impersonation",
"IMPERSONATIONDESCRIPTION": "This setting allows to use impersonation in principle. Note that the impersonator needs the appropriate `*_IMPERSONATOR` roles assigned as well."
},
"DIALOG": {
"RESET": {
@ -1964,7 +1997,7 @@
"DESCRIPTION": "Here you can edit your application data and it's configuration.",
"CREATE": "Create application",
"CREATE_SELECT_PROJECT": "Select your project first",
"CREATE_NEW_PROJECT": "or create a new one <a href='{{url}}' title='Create project'>here</a>.",
"CREATE_NEW_PROJECT": "or enter the name for your new project",
"CREATE_DESC_TITLE": "Enter Your Application Details Step by Step",
"CREATE_DESC_SUB": "A recommended configuration will be automatically generated.",
"STATE": "Status",
@ -1972,6 +2005,7 @@
"DATECHANGED": "Changed",
"URLS": "URLs",
"DELETE": "Delete App",
"JUMPTOPROJECT": "To configure roles, authorizations and more, navigate to the project.",
"DETAIL": {
"TITLE": "Detail",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "Ajustes",
"CUSTOMERPORTAL": "Portal del cliente"
},
"QUICKSTART": {
"TITLE": "Integra ZITADEL en tu aplicación",
"DESCRIPTION": "Integra ZITADEL en tu aplicación o utiliza uno de nuestros ejemplos para empezar en cuestión de minutos",
"BTN_START": "Crear aplicación",
"BTN_LEARNMORE": "Aprende más",
"CREATEPROJECTFORAPP": "Crear proyecto {{value}}",
"SELECT_FRAMEWORK": "Seleccionar marco",
"FRAMEWORK": "Framework",
"FRAMEWORK_OTHER": "Otro (OIDC, SAML, API)",
"ALMOSTDONE": "Ya casi has terminado",
"REVIEWCONFIGURATION": "Configuración de revisión",
"REVIEWCONFIGURATION_DESCRIPTION": "Hemos creado una configuración básica para aplicaciones {{value}}. Puedes adaptar esta configuración a tus necesidades después de crearla.",
"REDIRECTS": "Configurar redirecciones",
"DEVMODEWARN": "El modo de desarrollo está habilitado de forma predeterminada. Puede actualizar los valores para la producción más adelante.",
"GUIDE": "Guía",
"BROWSEEXAMPLES": "Explora ejemplos",
"DUPLICATEAPPRENAME": "Ya existe una aplicación con el mismo nombre. Por favor, elige un nombre diferente.",
"DIALOG": {
"CAMBIAR": {
"TITLE": "Cambio framework",
"DESCRIPTION": "Elige uno de los frameworks disponibles para configurar rápidamente tu aplicación."
}
}
},
"ACTIONS": {
"ACTIONS": "Acciones",
"FILTER": "Filtrar",
@ -138,6 +162,7 @@
"ADD": "Añadir",
"CREATE": "Crear",
"CONTINUE": "Continuar",
"CONTINUEWITH": "Continuar con {{value}}",
"BACK": "Atrás",
"CLOSE": "Cerrar",
"CLEAR": "Limpiar",
@ -188,12 +213,16 @@
"IAM_OWNER_VIEWER": "Tiene permiso para revisar toda la instancia, incluyendo todas las organizaciones",
"IAM_ORG_MANAGER": "Tiene permiso para crear y gestionar organizaciones",
"IAM_USER_MANAGER": "Tiene permiso para crear y gestionar usuarios",
"IAM_ADMIN_IMPERSONATOR": "Tiene permiso para hacerse pasar por administradores y usuarios finales de todas las organizaciones",
"IAM_END_USER_IMPERSONATOR": "Tiene permiso para hacerse pasar por usuarios finales de todas las organizaciones",
"ORG_OWNER": "Tiene permisos sobre toda la organización",
"ORG_USER_MANAGER": "Tiene permiso para crear y gestionar usuarios de la organización",
"ORG_OWNER_VIEWER": "TIene permiso para revisar toda la organización",
"ORG_USER_PERMISSION_EDITOR": "Tiene permiso para gestionar concesiones de usuario",
"ORG_PROJECT_PERMISSION_EDITOR": "Tiene permiso para gestionar concesiones de proyecto",
"ORG_PROJECT_CREATOR": "Tiene permiso para crear sus propios proyectos y ajustes subyacentes",
"ORG_ADMIN_IMPERSONATOR": "Tiene permiso para hacerse pasar por administradores y usuarios finales de la organización",
"ORG_END_USER_IMPERSONATOR": "Tiene permiso para hacerse pasar por usuarios finales de la organización",
"PROJECT_OWNER": "Tiene permiso sobre todo el proyecto",
"PROJECT_OWNER_VIEWER": "Tiene permiso para revisar todo el proyecto",
"PROJECT_OWNER_GLOBAL": "Tiene permiso sobre todo el proyecto",
@ -1161,9 +1190,13 @@
"UPDATED": "Ajustes actualizados."
},
"SECURITY": {
"DESCRIPTION": "Este ajuste establece el CSP para permitir el uso de frames para un grupo de dominios permitidos. Ten en cuenta que habilitando el uso de iFrames, corres el riesgo de permitir ataques de clickjacking.",
"IFRAMETITLE": "iFrame",
"IFRAMEDESCRIPTION": "Este ajuste establece el CSP para permitir el uso de frames para un grupo de dominios permitidos. Ten en cuenta que habilitando el uso de iFrames, corres el riesgo de permitir ataques de clickjacking.",
"IFRAMEENABLED": "Permitir iFrame",
"ALLOWEDORIGINS": "URLs permitidas"
"ALLOWEDORIGINS": "URLs permitidas",
"IMPERSONATIONTITLE": "Suplantación",
"IMPERSONATIONENABLED": "Permitir suplantación",
"IMPERSONATIONDESCRIPTION": "Esta configuración permite utilizar la suplantación en principio. Tenga en cuenta que el imitador también necesita que se le asignen los roles `*_IMPERSONATOR` apropiados."
},
"DIALOG": {
"RESET": {
@ -1951,6 +1984,7 @@
"DATECHANGED": "Cambiada",
"URLS": "URLs",
"DELETE": "Borrar App",
"JUMPTOPROJECT": "Para configurar roles, autorizaciones y más, navegue hasta el proyecto.",
"DETAIL": {
"TITLE": "Detalle",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "Paramètres",
"CUSTOMERPORTAL": "Customer Portal"
},
"QUICKSTART": {
"TITLE": "Intégrer ZITADEL dans votre application",
"DESCRIPTION": "Intégrez ZITADEL dans votre application ou utilisez l'un de nos échantillons pour démarrer en quelques minutes",
"BTN_START": "Créer une application",
"BTN_LEARNMORE": "En savoir plus",
"CREATEPROJECTFORAPP": "Créer un projet {{value}}",
"SELECT_FRAMEWORK": "Select Framework",
"FRAMEWORK": "Framework",
"FRAMEWORK_OTHER": "Autre (OIDC, SAML, API)",
"ALMOSTDONE": "Vous avez presque terminé",
"REVIEWCONFIGURATION": "Review Configuration",
"REVIEWCONFIGURATION_DESCRIPTION": "Nous avons créé une configuration de base pour les applications {{value}}. Vous pouvez adapter cette configuration à vos besoins après sa création.",
"REDIRECTS": "Configurer les redirections",
"DEVMODEWARN": "Le mode développement est activé par défaut. Vous pouvez mettre à jour les valeurs pour la production ultérieurement.",
"GUIDE": "Guide",
"BROWSEEXAMPLES": "Parcourez les exemples",
"DUPLICATEAPPRENAME": "Une application avec ce nom existe déjà. Veuillez choisir un autre nom.",
"DIALOG": {
"CHANGE": {
"TITLE": "Cadre de changement",
"DESCRIPTION": "Choisissez l'un des frameworks disponibles pour une configuration rapide de votre application."
}
}
},
"ACTIONS": {
"ACTIONS": "Actions",
"FILTER": "Filtrer",
@ -138,6 +162,7 @@
"ADD": "Ajouter",
"CREATE": "Créer",
"CONTINUE": "Continuer",
"CONTINUEWITH": "Continuez avec {{value}}",
"BACK": "Retour",
"CLOSE": "Fermer",
"CLEAR": "Effacer",
@ -187,12 +212,16 @@
"IAM_OWNER_VIEWER": "A le droit de passer en revue l'ensemble de l'instance, y compris toutes les organisations.",
"IAM_ORG_MANAGER": "A le droit de créer et de gérer des organisations",
"IAM_USER_MANAGER": "A le droit de créer et de gérer les utilisateurs",
"IAM_ADMIN_IMPERSONATOR": "A l'autorisation de se faire passer pour l'administrateur et les utilisateurs finaux de toutes les organisations",
"IAM_END_USER_IMPERSONATOR": "Est autorisé à usurper l'identité des utilisateurs finaux de toutes les organisations",
"ORG_OWNER": "A le droit de contrôler l'ensemble de l'organisation",
"ORG_USER_MANAGER": "A le droit de créer et de gérer les utilisateurs de l'organisation",
"ORG_OWNER_VIEWER": "A le droit de passer en revue l'ensemble de l'organisation",
"ORG_USER_PERMISSION_EDITOR": "A le droit de gérer les subventions aux utilisateurs",
"ORG_PROJECT_PERMISSION_EDITOR": "A le droit de gérer les subventions aux projets",
"ORG_PROJECT_CREATOR": "A le droit de créer ses propres projets et leurs paramètres sous-jacents.",
"ORG_ADMIN_IMPERSONATOR": "A l'autorisation de se faire passer pour l'administrateur et les utilisateurs finaux de l'organisation",
"ORG_END_USER_IMPERSONATOR": "Est autorisé à usurper l'identité des utilisateurs finaux de l'organisation",
"PROJECT_OWNER": "A le droit de gérer l'ensemble du projet",
"PROJECT_OWNER_VIEWER": "A le droit de passer en revue l'ensemble du projet",
"PROJECT_OWNER_GLOBAL": "A le droit d'accéder à l'ensemble du projet",
@ -1159,9 +1188,13 @@
"UPDATED": "Paramètres mis à jour."
},
"SECURITY": {
"DESCRIPTION": "Ce paramètre permet au CSP d'autoriser les iFrames à partir d'un ensemble de domaines autorisés. Notez qu'en autorisant l'utilisation des iFrames, vous courez le risque d'autoriser le clickjacking.",
"IFRAMETITLE": "iFrame",
"IFRAMEDESCRIPTION": "Ce paramètre permet au CSP d'autoriser les iFrames à partir d'un ensemble de domaines autorisés. Notez qu'en autorisant l'utilisation des iFrames, vous courez le risque d'autoriser le clickjacking.",
"IFRAMEENABLED": "Autoriser iFrame",
"ALLOWEDORIGINS": "URL d'origine autorisées"
"ALLOWEDORIGINS": "URL d'origine autorisées",
"IMPERSONATIONTITLE": "Usuration d'identité",
"IMPERSONATIONENABLED": "Autoriser l'usurpation d'identité",
"IMPERSONATIONDESCRIPTION": "Ce paramètre permet en principe d'utiliser l'usurpation d'identité. Notez que l'usurpateur d'identité doit également recevoir les rôles `*_IMPERSONATOR` appropriés."
},
"DIALOG": {
"RESET": {
@ -1954,6 +1987,7 @@
"DATECHANGED": "Modifié",
"URLS": "URLs",
"DELETE": "Supprimer l'application",
"JUMPTOPROJECT": "Pour configurer des rôles, des autorisations et bien plus encore, accédez au projet.",
"DETAIL": {
"TITLE": "Détail",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "Impostazioni",
"CUSTOMERPORTAL": "Customer Portal"
},
"QUICKSTART": {
"TITLE": "Integra ZITADEL nella tua applicazione",
"DESCRIPTION": "Integra ZITADEL nella tua applicazione o utilizza uno dei nostri esempi per iniziare in pochi minuti",
"BTN_START": "Crea applicazione",
"BTN_LEARNMORE": "Mostra di più",
"CREATEPROJECTFORAPP": "Crea progetto {{value}}",
"SELECT_FRAMEWORK": "Seleziona Framework",
"FRAMEWORK": "Framework",
"FRAMEWORK_OTHER": "Altro (OIDC, SAML, API)",
"ALMOSTDONE": "Hai quasi finito",
"REVIEWCONFIGURATION": "Controlla la configurazione",
"REVIEWCONFIGURATION_DESCRIPTION": "Abbiamo creato una configurazione base per le applicazioni {{value}}. Può adattare la configurazione alle proprie esigenze dopo la creazione.",
"REDIRECTS": "Configura i reindirizzamenti",
"DEVMODEWARN": "La modalità sviluppatore è abilitata per impostazione predefinita. È possibile aggiornare i valori per la produzione in un secondo momento.",
"GUIDE": "Guida",
"BROWSEEXAMPLES": "Esplora esempi e SDK",
"DUPLICATEAPPRENAME": "Un'app con lo stesso nome esiste già. Scegli un nome diverso.",
"DIALOG": {
"CHANGE": {
"TITLE": "Modifica framework",
"DESCRIPTION": "Scegliere uno dei framework disponibili per configurare rapidamente la propria applicazione"
}
}
},
"ACTIONS": {
"ACTIONS": "Azioni",
"FILTER": "Filtra",
@ -138,6 +162,7 @@
"ADD": "Aggiungi",
"CREATE": "Crea",
"CONTINUE": "Continua",
"CONTINUEWITH": "Continue con {{value}}",
"BACK": "Indietro",
"CLOSE": "chiudi",
"CLEAR": "Resetta",
@ -186,12 +211,16 @@
"IAM_OWNER_VIEWER": "Ha l'autorizzazione per esaminare l'intera istanza, comprese tutte le organizzazioni",
"IAM_ORG_MANAGER": "Ha il permesso di creare e gestire organizzazioni",
"IAM_USER_MANAGER": "Ha l'autorizzazione per creare e gestire utenti",
"IAM_ADMIN_IMPERSONATOR": "Dispone dell'autorizzazione per rappresentare l'amministratore e gli utenti finali di tutte le organizzazioni",
"IAM_END_USER_IMPERSONATOR": "Dispone dell'autorizzazione per rappresentare gli utenti finali di tutte le organizzazioni",
"ORG_OWNER": "Ha il permesso su tutta l'organizzazione",
"ORG_USER_MANAGER": "Ha l'autorizzazione per creare e gestire gli utenti dell'organizzazione",
"ORG_OWNER_VIEWER": "Ha il permesso di esaminare l'intera organizzazione",
"ORG_USER_PERMISSION_EDITOR": "Ha l'autorizzazione per gestire le autorizzazioni degli utenti",
"ORG_PROJECT_PERMISSION_EDITOR": "Ha il permesso di gestire le sovvenzioni di progetto (Project Grant)",
"ORG_PROJECT_CREATOR": "Ha il permesso di creare propri progetti e le impostazioni sottostanti",
"ORG_ADMIN_IMPERSONATOR": "Ha il permesso per rappresentare l'amministratore e gli utenti finali dell'organizzazione",
"ORG_END_USER_IMPERSONATOR": "Ha il permesso per rappresentare gli utenti finali dell'organizzazione",
"PROJECT_OWNER": "Ha il permesso per l'intero progetto",
"PROJECT_OWNER_VIEWER": "Ha il permesso di esaminare l'intero progetto",
"PROJECT_OWNER_GLOBAL": "Ha il permesso per l'intero progetto",
@ -1159,9 +1188,13 @@
"UPDATED": "Impostazioni aggiornati"
},
"SECURITY": {
"DESCRIPTION": "Questa impostazione consente al CSP di consentire il framing da un insieme di domini consentiti. Si noti che abilitando l'uso di iFrames, si corre il rischio di consentire il clickjacking.",
"IFRAMETITLE": "iFrame",
"IFRAMEDESCRIPTION": "Questa impostazione consente al CSP di consentire il framing da un insieme di domini consentiti. Si noti che abilitando l'uso di iFrames, si corre il rischio di consentire il clickjacking.",
"IFRAMEENABLED": "I Frame enabled",
"ALLOWEDORIGINS": "URL consentiti"
"ALLOWEDORIGINS": "URL consentiti",
"IMPERSONATIONTITLE": "Impersonificazione",
"IMPERSONATIONENABLED": "Consenti la rappresentazione",
"IMPERSONATIONDESCRIPTION": "Questa impostazione consente in linea di principio di utilizzare la rappresentazione. Tieni presente che il sosia ha bisogno anche dei ruoli `*_IMPERSONATOR` appropriati assegnati."
},
"DIALOG": {
"RESET": {
@ -1954,6 +1987,7 @@
"DATECHANGED": "Cambiato",
"URLS": "URLs",
"DELETE": "Rimuovi App",
"JUMPTOPROJECT": "Per configurare ruoli, autorizzazioni e altro, vai al progetto.",
"DETAIL": {
"TITLE": "Dettagli",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "設定",
"CUSTOMERPORTAL": "カスタマーポータル"
},
"QUICKSTART": {
"TITLE": "ZITADELをアプリケーションに統合する",
"DESCRIPTION": "あなたのアプリケーションにZITADELを組み込むか、当社のサンプルを使って数分で始められます。",
"BTN_START": "アプリケーションの作成",
"BTN_LEARNMORE": "さらに詳しく",
"CREATEPROJECTFORAPP": "プロジェクトの作成 {{value}}",
"SELECT_FRAMEWORK": "フレームワークを選択",
"FRAMEWORK": "フレームワーク",
"FRAMEWORK_OTHER": "他の (OIDC, SAML, API)",
"ALMOSTDONE": "あと少しだ。",
"REVIEWCONFIGURATION": "レビュー構成",
"REVIEWCONFIGURATION_DESCRIPTION": "ここでは{{value}}アプリケーションの基本設定を作成しました。この構成は作成後にあなたのニーズに合わせることができます。",
"REDIRECTS": "リダイレクトの設定",
"DEVMODEWARN": "開発モードはデフォルトで有効になっています。実稼働用の値は後で更新できます。",
"GUIDE": "ガイド",
"BROWSEEXAMPLES": "サンプルを見る",
"DUPLICATEAPPRENAME": "同じ名前のアプリがすでに存在します。別の名前を選択してください。",
"DIALOG": {
"CHANGE": {
"TITLE": "変更の枠組み",
"DESCRIPTION": "利用可能なフレームワークのいずれかを選択して、アプリケーションをすばやくセットアップできます。"
}
}
},
"ACTIONS": {
"ACTIONS": "アクション",
"FILTER": "絞り込み",
@ -138,6 +162,7 @@
"ADD": "追加",
"CREATE": "作成",
"CONTINUE": "次へ",
"CONTINUEWITH": "{{value}} に進みます",
"BACK": "戻る",
"CLOSE": "閉じる",
"CLEAR": "消去する",
@ -188,12 +213,16 @@
"IAM_OWNER_VIEWER": "すべての組織を含むインスタンス全体を閲覧する権限を持ちます",
"IAM_ORG_MANAGER": "組織の作成および管理する権限を持ちます",
"IAM_USER_MANAGER": "ユーザーの作成および管理する権限を持ちます",
"IAM_ADMIN_IMPERSONATOR": "すべての組織の管理者およびエンドユーザーになりすます権限を持っています",
"IAM_END_USER_IMPERSONATOR": "すべての組織のエンドユーザーになりすます権限を持っています",
"ORG_OWNER": "組織全体に対する権限を持ちます",
"ORG_USER_MANAGER": "組織のユーザーを作成および管理する権限を持ちます",
"ORG_OWNER_VIEWER": "組織全体を閲覧する権限を持ちます",
"ORG_USER_PERMISSION_EDITOR": "ユーザーグラントを管理する権限を持ちます",
"ORG_PROJECT_PERMISSION_EDITOR": "プロジェクトグラントを管理する権限を持ちます",
"ORG_PROJECT_CREATOR": "所有するプロジェクトと配下の設定を作成する権限を持ちます",
"ORG_ADMIN_IMPERSONATOR": "組織の管理者およびエンドユーザーになりすます権限がある",
"ORG_END_USER_IMPERSONATOR": "組織のエンドユーザーになりすます権限がある",
"PROJECT_OWNER": "特定のプロジェクト全体を管理する権限を持ちます",
"PROJECT_OWNER_VIEWER": "特定のプロジェクト全体を閲覧する権限を持ちます",
"PROJECT_OWNER_GLOBAL": "全てのプロジェクトを管理する権限を持ちます",
@ -1160,9 +1189,13 @@
"UPDATED": "設定が更新されました。"
},
"SECURITY": {
"DESCRIPTION": "この設定は、許可されたドメインのセットからのフレーミングを許可するように CSP を設定します。iFrameの使用を有効にすると、クリックジャッキングが許可される危険性があることに注意してください。",
"IFRAMETITLE": "iFrame",
"IFRAMEDESCRIPTION": "この設定は、許可されたドメインのセットからのフレーミングを許可するように CSP を設定します。iFrameの使用を有効にすると、クリックジャッキングが許可される危険性があることに注意してください。",
"IFRAMEENABLED": "iFrameを許可する",
"ALLOWEDORIGINS": "許可されたURL"
"ALLOWEDORIGINS": "許可されたURL",
"IMPERSONATIONTITLE": "偽装",
"IMPERSONATIONENABLED": "偽装を許可します",
"IMPERSONATIONDESCRIPTION": "この設定では、原則として偽装を使用できます。偽装者には、適切な `*_IMPERSONATOR` ロールも割り当てられている必要があることに注意してください。"
},
"DIALOG": {
"RESET": {
@ -1945,6 +1978,7 @@
"DATECHANGED": "更新日",
"URLS": "URL",
"DELETE": "アプリを削除する",
"JUMPTOPROJECT": "ロール、権限などを構成するには、プロジェクトに移動します。",
"DETAIL": {
"TITLE": "詳細",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "Подесувања",
"CUSTOMERPORTAL": "Портал за клиенти"
},
"QUICKSTART": {
"TITLE": "Интегрирајте го ZITADEL во вашата апликација",
"DESCRIPTION": "Интегрирајте го ZITADEL во вашата апликација или користете еден од нашите примероци за да започнете за неколку минути.",
"BTN_START": "Креирај апликација",
"BTN_LEARNMORE": "Научи повеќе",
"CREATEPROJECTFORAPP": "Креирај проект {{value}}",
"SELECT_FRAMEWORK": "Изберете Рамка",
"FRAMEWORK": "Рамка",
"FRAMEWORK_OTHER": "Друго (OIDC, SAML, API)",
"ALMOSTDONE": "Речиси сте готови.",
"REVIEWCONFIGURATION": "Прегледајте ја конфигурацијата",
"REVIEWCONFIGURATION_DESCRIPTION": "Создадовме основна конфигурација за {{value}} апликации. Можете да ја прилагодите оваа конфигурација на вашите потреби по креирањето.",
"REDIRECTS": "Конфигурирајте пренасочувања",
"DEVMODEWARN": "Режимот на развој е стандардно овозможен. Може да ги ажурирате вредностите за производство подоцна.",
"GUIDE": "Водич",
"BROWSEEXAMPLES": "Прегледај примероци",
"DUPLICATEAPPRENAME": "Веќе постои апликација со истиот непријател. Ве молиме изберете друго име.",
"DIALOG": {
"CHANGE": {
"TITLE": "Променете ја рамката",
"DESCRIPTION": "Изберете една од достапните рамки за брзо поставување на вашата апликација."
}
}
},
"ACTIONS": {
"ACTIONS": "Акции",
"FILTER": "Филтер",
@ -138,6 +162,7 @@
"ADD": "Додади",
"CREATE": "Креирај",
"CONTINUE": "Продолжи",
"CONTINUEWITH": "Продолжи со {{value}}",
"BACK": "Назад",
"CLOSE": "Затвори",
"CLEAR": "Исчисти",
@ -188,12 +213,16 @@
"IAM_OWNER_VIEWER": "Има дозвола за преглед на целата инстанца, вклучувајќи ги сите организации",
"IAM_ORG_MANAGER": "Има дозвола за креирање и менаџирање на организации",
"IAM_USER_MANAGER": "Има дозвола за креирање и менаџирање на корисници",
"IAM_ADMIN_IMPERSONATOR": "Има дозвола да се претставува како администратор и крајни корисници од сите организации",
"IAM_END_USER_IMPERSONATOR": "Има дозвола да ги имитира крајните корисници од сите организации",
"ORG_OWNER": "Има дозвола врз целата организација",
"ORG_USER_MANAGER": "Има дозвола за креирање и менаџирање на корисници во организацијата",
"ORG_OWNER_VIEWER": "Има дозвола за преглед на целата организација",
"ORG_USER_PERMISSION_EDITOR": "Има дозвола за менаџирање на овластувања на корисници",
"ORG_PROJECT_PERMISSION_EDITOR": "Има дозвола за менаџирање на овластувања на проекти",
"ORG_PROJECT_CREATOR": "Има дозвола за креирање на сопствени проекти и нивни подесувања",
"ORG_ADMIN_IMPERSONATOR": "Има дозвола да имитира администратор и крајни корисници од организацијата",
"ORG_END_USER_IMPERSONATOR": "Има дозвола да ги имитира крајните корисници од организацијата",
"PROJECT_OWNER": "Има дозвола врз целиот проект",
"PROJECT_OWNER_VIEWER": "Има дозвола за преглед на целиот проект",
"PROJECT_OWNER_GLOBAL": "Има дозвола врз целиот проект",
@ -1161,9 +1190,13 @@
"UPDATED": "Подесувањата се успешно ажурирани."
},
"SECURITY": {
"DESCRIPTION": "Ова подесување поставува CSP за дозволување на фрејминг од одредени дозволени домени. Имајте предвид дека овозможувањето на употребата на iFrame-ови може да ви изложи на ризик од clickjacking.",
"IFRAMETITLE": "iFrame",
"IFRAMEDESCRIPTION": "Ова подесување поставува CSP за дозволување на фрејминг од одредени дозволени домени. Имајте предвид дека овозможувањето на употребата на iFrame-ови може да ви изложи на ризик од clickjacking.",
"IFRAMEENABLED": "Овозможи iFrame",
"ALLOWEDORIGINS": "Дозволени URLs"
"ALLOWEDORIGINS": "Дозволени URLs",
"IMPERSONATIONTITLE": "Имитирање",
"IMPERSONATIONENABLED": "Дозволи имитирање",
"IMPERSONATIONDESCRIPTION": "Оваа поставка овозможува да се користи имитирање во принцип. Имајте предвид дека на имитаторот му требаат доделени соодветни улоги `*_IMPERSONATOR`"
},
"DIALOG": {
"RESET": {
@ -1951,6 +1984,7 @@
"DATECHANGED": "Изменето",
"URLS": "URLs",
"DELETE": "Избриши апликација",
"JUMPTOPROJECT": "За да ги конфигурирате улогите, овластувањата и друго, одете до проектот.",
"DETAIL": {
"TITLE": "Детали",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "Instellingen",
"CUSTOMERPORTAL": "Klantenportaal"
},
"QUICKSTART": {
"TITLE": "Integreer ZITADEL in uw toepassing",
"DESCRIPTION": "Integreer ZITADEL in uw toepassing of gebruik een van onze voorbeelden om binnen enkele minuten aan de slag te gaan.",
"BTN_START": "Applicatie maken",
"BTN_LEARNMORE": "Meer informatie",
"CREATEPROJECTFORAPP": "Creëer Project {{value}}",
"SELECT_FRAMEWORK": "Kies Framework",
"FRAMEWORK": "Framework",
"FRAMEWORK_OTHER": "Ander (OIDC, SAML, API)",
"ALMOSTDONE": "Je bent bijna klaar",
"REVIEWCONFIGURATION": "Reviewconfiguratie",
"REVIEWCONFIGURATION_DESCRIPTION": "We hebben een basisconfiguratie gemaakt voor {{value}} toepassingen. Je kunt deze configuratie na het aanmaken aanpassen aan je behoeften.",
"REDIRECTS": "Configureer omleidingen",
"DEVMODEWARN": "Dev-modus is standaard ingeschakeld. U kunt waarden voor productie later bijwerken.",
"GUIDE": "Guide",
"BROWSEEXAMPLES": "Bekijk voorbeelden",
"DUPLICATEAPPRENAME": "Er bestaat al een app met dezelfde aartsvijand. Kies een andere naam.",
"DIALOG": {
"CHANGE": {
"TITLE": "Verander Framework",
"DESCRIPTION": "Kies een van de beschikbare frameworks voor het snel instellen van je applicatie."
}
}
},
"ACTIONS": {
"ACTIONS": "Acties",
"FILTER": "Filter",
@ -138,6 +162,7 @@
"ADD": "Toevoegen",
"CREATE": "Aanmaken",
"CONTINUE": "Doorgaan",
"CONTINUEWITH": "Ga verder met {{value}}",
"BACK": "Terug",
"CLOSE": "Sluiten",
"CLEAR": "Leegmaken",
@ -188,12 +213,16 @@
"IAM_OWNER_VIEWER": "Heeft toestemming om de hele instantie te bekijken, inclusief alle organisaties",
"IAM_ORG_MANAGER": "Heeft toestemming om organisaties aan te maken en te beheren",
"IAM_USER_MANAGER": "Heeft toestemming om gebruikers aan te maken en te beheren",
"IAM_ADMIN_IMPERSONATOR": "Heeft toestemming om zich voor te doen als beheerder en eindgebruikers van alle organisaties",
"IAM_END_USER_IMPERSONATOR": "Heeft toestemming om eindgebruikers van alle organisaties na te bootsen",
"ORG_OWNER": "Heeft toestemming over de hele organisatie",
"ORG_USER_MANAGER": "Heeft toestemming om gebruikers van de organisatie aan te maken en te beheren",
"ORG_OWNER_VIEWER": "Heeft toestemming om de hele organisatie te bekijken",
"ORG_USER_PERMISSION_EDITOR": "Heeft toestemming om gebruikerstoegang te beheren",
"ORG_PROJECT_PERMISSION_EDITOR": "Heeft toestemming om projecttoegang te beheren",
"ORG_PROJECT_CREATOR": "Heeft toestemming om zijn eigen projecten en onderliggende instellingen aan te maken",
"ORG_ADMIN_IMPERSONATOR": "Heeft toestemming om de beheerder en eindgebruikers van de organisatie na te bootsen",
"ORG_END_USER_IMPERSONATOR": "Heeft toestemming om eindgebruikers van de organisatie na te bootsen",
"PROJECT_OWNER": "Heeft toestemming over het hele project",
"PROJECT_OWNER_VIEWER": "Heeft toestemming om het hele project te bekijken",
"PROJECT_OWNER_GLOBAL": "Heeft toestemming over het hele project",
@ -1160,9 +1189,13 @@
"UPDATED": "Instellingen bijgewerkt."
},
"SECURITY": {
"DESCRIPTION": "Deze instelling stelt de CSP in om framing van een reeks toegestane domeinen toe te staan. Let op: door het gebruik van iFrames toe te staan, loopt u het risico om clickjacking toe te staan.",
"IFRAMETITLE": "iFrame",
"IFRAMEDESCRIPTION": "Deze instelling stelt de CSP in om framing van een reeks toegestane domeinen toe te staan. Let op: door het gebruik van iFrames toe te staan, loopt u het risico om clickjacking toe te staan.",
"IFRAMEENABLED": "Sta iFrame toe",
"ALLOWEDORIGINS": "Toegestane URLs"
"ALLOWEDORIGINS": "Toegestane URLs",
"IMPERSONATIONTITLE": "Imitatie",
"IMPERSONATIONENABLED": "Imitatie toestaan",
"IMPERSONATIONDESCRIPTION": "Deze instelling maakt het in principe mogelijk om imitatie te gebruiken. Houd er rekening mee dat aan de imitator ook de juiste `*_IMPERSONATOR` rollen moeten worden toegewezen."
},
"DIALOG": {
"RESET": {
@ -1972,6 +2005,7 @@
"DATECHANGED": "Gewijzigd",
"URLS": "URL's",
"DELETE": "Verwijder App",
"JUMPTOPROJECT": "Om rollen, autorisaties en meer te configureren, navigeert u naar het project.",
"DETAIL": {
"TITLE": "Detail",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "Ustawienia",
"CUSTOMERPORTAL": "Portal klienta"
},
"QUICKSTART": {
"TITLE": "Zintegruj ZITADEL ze swoją aplikacją",
"DESCRIPTION": "Zintegruj ZITADEL ze swoją aplikacją lub skorzystaj z jednego z naszych przykładów, aby rozpocząć pracę w ciągu kilku minut",
"BTN_START": "Utwórz aplikację",
"BTN_LEARNMORE": "Dowiedz się więcej",
"CREATEPROJECTFORAPP": "Utwórz projekt {{value}}",
"SELECT_FRAMEWORK": "Wybierz Framework",
"FRAMEWORK": "Ramy",
"FRAMEWORK_OTHER": "Inny (OIDC, SAML, API)",
"ALMOSTDONE": "Już prawie skończyłeś",
"REVIEWCONFIGURATION": "Konfiguracja recenzji",
"REVIEWCONFIGURATION_DESCRIPTION": "Stworzyliśmy podstawową konfigurację dla aplikacji {{value}}. Możesz dostosować tę konfigurację do swoich potrzeb po jej utworzeniu.",
"REDIRECTS": "Skonfiguruj przekierowania",
"DEVMODEWARN": "Tryb deweloperski jest domyślnie włączony. Możesz później zaktualizować wartości dla produkcji.",
"GUIDE": "Przewodnik",
"BROWSEEXAMPLES": "Przeglądaj przykłady",
"DUPLICATEAPPRENAME": "Aplikacja o tej samej nazwie już istnieje. Proszę wybrać inną nazwę.",
"DIALOG": {
"CHANGE": {
"TITLE": "Ramy zmian",
"DESCRIPTION": "Wybierz jeden z dostępnych frameworków, aby szybko skonfigurować swoją aplikację"
}
}
},
"ACTIONS": {
"ACTIONS": "Akcje",
"FILTER": "Filtruj",
@ -138,6 +162,7 @@
"ADD": "Dodaj",
"CREATE": "Utwórz",
"CONTINUE": "Kontynuuj",
"CONTINUEWITH": "Kontynuuj z {{value}}",
"BACK": "Wstecz",
"CLOSE": "Zamknij",
"CLEAR": "Wyczyść",
@ -187,12 +212,16 @@
"IAM_OWNER_VIEWER": "Ma uprawnienie do przeglądania całej instancji, włącznie z wszystkimi organizacjami",
"IAM_ORG_MANAGER": "Ma uprawnienie do tworzenia i zarządzania organizacjami",
"IAM_USER_MANAGER": "Ma uprawnienie do tworzenia i zarządzania użytkownikami",
"IAM_ADMIN_IMPERSONATOR": "Ma uprawnienia do podszywania się pod administratora i użytkowników końcowych ze wszystkich organizacji",
"IAM_END_USER_IMPERSONATOR": "Ma uprawnienia do podszywania się pod użytkowników końcowych ze wszystkich organizacji",
"ORG_OWNER": "Ma uprawnienie nad całą organizacją",
"ORG_USER_MANAGER": "Ma uprawnienie do tworzenia i zarządzania użytkownikami organizacji",
"ORG_OWNER_VIEWER": "Ma uprawnienie do przeglądania całej organizacji",
"ORG_USER_PERMISSION_EDITOR": "Ma uprawnienie do zarządzania uprawnieniami użytkowników",
"ORG_PROJECT_PERMISSION_EDITOR": "Ma uprawnienie do zarządzania uprawnieniami projektu",
"ORG_PROJECT_CREATOR": "Ma uprawnienie do tworzenia własnych projektów i podstawowych ustawień",
"ORG_ADMIN_IMPERSONATOR": "Ma uprawnienia do podszywania się pod administratora i użytkowników końcowych z organizacji",
"ORG_END_USER_IMPERSONATOR": "Ma uprawnienia do podszywania się pod użytkowników końcowych z organizacji",
"PROJECT_OWNER": "Ma uprawnienie nad całym projektem",
"PROJECT_OWNER_VIEWER": "Ma uprawnienie do przeglądania całego projektu",
"PROJECT_OWNER_GLOBAL": "Ma uprawnienia do całego projektu",
@ -1159,9 +1188,13 @@
"UPDATED": "Ustawienia zaktualizowane."
},
"SECURITY": {
"DESCRIPTION": "To ustawienie ustawia CSP, aby pozwalało na osadzanie ramki z zestawu dozwolonych domen. Należy pamiętać, że włączenie używania iFrame oznacza ryzyko pozwolenia na clickjacking.",
"IFRAMETITLE": "iFrame",
"IFRAMEDESCRIPTION": "To ustawienie ustawia CSP, aby pozwalało na osadzanie ramki z zestawu dozwolonych domen. Należy pamiętać, że włączenie używania iFrame oznacza ryzyko pozwolenia na clickjacking.",
"IFRAMEENABLED": "Zezwól na iFrame",
"ALLOWEDORIGINS": "Dozwolone adresy URL"
"ALLOWEDORIGINS": "Dozwolone adresy URL",
"IMPERSONATIONTITLE": "Podszywanie się",
"IMPERSONATIONENABLED": "Zezwalaj na podszywanie się",
"IMPERSONATIONDESCRIPTION": "To ustawienie pozwala w zasadzie na użycie personifikacji. Należy pamiętać, że osoba personifikująca potrzebuje również przypisanych odpowiednich ról `*_IMPERSONATOR`."
},
"DIALOG": {
"RESET": {
@ -1954,6 +1987,7 @@
"DATECHANGED": "Zmienione",
"URLS": "Adresy URL",
"DELETE": "Usuń aplikację",
"JUMPTOPROJECT": "Aby skonfigurować role, uprawnienia i nie tylko, przejdź do projektu.",
"DETAIL": {
"TITLE": "Szczegóły",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "Configurações",
"CUSTOMERPORTAL": "Portal do Cliente"
},
"QUICKSTART": {
"TITLE": "Integrar a ZITADEL na sua aplicação",
"DESCRIÇÃO": "Integre a ZITADEL na sua aplicação ou utilize uma das nossas amostras para começar em minutos.",
"BTN_START": "Criar aplicação",
"BTN_LEARNMORE": "Saiba mais",
"CREATEPROJECTFORAPP": "Criar projeto {{value}}",
"SELECT_FRAMEWORK": "Selecionar estrutura",
"FRAMEWORK": "Estrutura",
"FRAMEWORK_OTHER": "Outro (OIDC, SAML, API)",
"ALMOSTDONE": "Está quase a terminar",
"REVIEWCONFIGURATION": "Configuração de revisão",
"REVIEWCONFIGURATION_DESCRIPTION": "Criámos uma configuração básica para aplicações {{value}}. Pode adaptar esta configuração às suas necessidades após a criação.",
"REDIRECTS": "Configurar redireccionamentos",
"DEVMODEWARN": "O modo Dev está habilitado por padrão. Você pode atualizar os valores para produção posteriormente.",
"GUIDE": "Guia",
"BROWSEEXAMPLES": "Navegar por exemplos",
"DUPLICATEAPPRENAME": "Já existe um aplicativo com o mesmo neme. Escolha um nome diferente.",
"DIALOG": {
"CHANGE": {
"TITLE": "Alterar Quadro",
"DESCRIÇÃO": "Escolha uma das estruturas disponíveis para uma configuração rápida da sua aplicação."
}
}
},
"ACTIONS": {
"ACTIONS": "Ações",
"FILTER": "Filtrar",
@ -138,6 +162,7 @@
"ADD": "Adicionar",
"CREATE": "Criar",
"CONTINUE": "Continuar",
"CONTINUEWITH": "Continue com {{value}}",
"BACK": "Voltar",
"CLOSE": "Fechar",
"CLEAR": "Limpar",
@ -188,12 +213,16 @@
"IAM_OWNER_VIEWER": "Tem permissão para revisar toda a instância, incluindo todas as organizações",
"IAM_ORG_MANAGER": "Tem permissão para criar e gerenciar organizações",
"IAM_USER_MANAGER": "Tem permissão para criar e gerenciar usuários",
"IAM_ADMIN_IMPERSONATOR": "Tem permissão para se passar por administradores e usuários finais de todas as organizações",
"IAM_END_USER_IMPERSONATOR": "Tem permissão para se passar por usuários finais de todas as organizações",
"ORG_OWNER": "Tem permissão sobre toda a organização",
"ORG_USER_MANAGER": "Tem permissão para criar e gerenciar usuários da organização",
"ORG_OWNER_VIEWER": "Tem permissão para revisar toda a organização",
"ORG_USER_PERMISSION_EDITOR": "Tem permissão para gerenciar concessões de usuários",
"ORG_PROJECT_PERMISSION_EDITOR": "Tem permissão para gerenciar concessões de projetos",
"ORG_PROJECT_CREATOR": "Tem permissão para criar seus próprios projetos e configurações subjacentes",
"ORG_ADMIN_IMPERSONATOR": "Tem permissão para se passar por administradores e usuários finais da organização",
"ORG_END_USER_IMPERSONATOR": "Tem permissão para se passar por usuários finais da organização",
"PROJECT_OWNER": "Tem permissão sobre todo o projeto",
"PROJECT_OWNER_VIEWER": "Tem permissão para revisar todo o projeto",
"PROJECT_OWNER_GLOBAL": "Tem permissão sobre todo o projeto",
@ -1161,9 +1190,13 @@
"UPDATED": "Configurações atualizadas."
},
"SECURITY": {
"DESCRIPTION": "Essa configuração define o CSP para permitir o enquadramento de um conjunto de domínios permitidos. Observe que, ao permitir o uso de iFrames, você corre o risco de permitir ataques de clickjacking.",
"IFRAMETITLE": "iFrame",
"IFRAMEDESCRIPTION": "Essa configuração define o CSP para permitir o enquadramento de um conjunto de domínios permitidos. Observe que, ao permitir o uso de iFrames, você corre o risco de permitir ataques de clickjacking.",
"IFRAMEENABLED": "Permitir iFrame",
"ALLOWEDORIGINS": "URLs permitidos"
"ALLOWEDORIGINS": "URLs permitidos",
"IMPERSONATIONTITLE": "Personificação",
"IMPERSONATIONENABLED": "Permitir representação",
"IMPERSONATIONDESCRIPTION": "Esta configuração permite usar a representação em princípio. Observe que o imitador também precisa das funções `*_IMPERSONATOR` apropriadas atribuídas."
},
"DIALOG": {
"RESET": {
@ -1949,6 +1982,7 @@
"DATECHANGED": "Alterado",
"URLS": "URLs",
"DELETE": "Excluir App",
"JUMPTOPROJECT": "Para configurar funções, autorizações e muito mais, navegue até o projeto.",
"DETAIL": {
"TITLE": "Detalhe",
"STATE": {

View File

@ -115,6 +115,30 @@
"SETTINGS": "Настройки",
"CUSTOMERPORTAL": "Клиентский портал"
},
"QUICKSTART": {
"TITLE": "Интегрируйте ZITADEL в свое приложение",
"ОПИСАНИЕ": "Интегрируйте ZITADEL в свое приложение или воспользуйтесь одним из наших образцов, чтобы начать работу за считанные минуты",
"BTN_START": "Создать приложение",
"BTN_LEARNMORE": "Узнать больше",
"CREATEPROJECTFORAPP": "Создать проект {{value}}",
"SELECT_FRAMEWORK": "Выбрать фреймворк",
"FRAMEWORK": "Рамка",
"FRAMEWORK_OTHER": "Другое (OIDC, SAML, API)",
"ALMOSTDONE": "Вы почти закончили",
"REVIEWCONFIGURATION": "Конфигурация обзора",
"REVIEWCONFIGURATION_DESCRIPTION": "Мы создали базовую конфигурацию для {{value}} приложений. После создания вы можете адаптировать эту конфигурацию под свои нужды.",
"REDIRECTS": "Настроить редиректы",
"DEVMODEWARN": "Режим разработки включен по умолчанию. Значения для производства можно обновить позже.",
"GUIDE": "Руководство",
"BROWSEEXAMPLES": "Просмотреть примеры",
"DUPLICATEAPPRENAME": "Приложение с таким названием уже существует. Пожалуйста, выберите другое имя.",
"DIALOG": {
"CHANGE": {
"TITLE": "Рамки изменений",
"ОПИСАНИЕ": "Выберите один из доступных фреймворков для быстрой настройки вашего приложения"
}
}
},
"ACTIONS": {
"ACTIONS": "Действия",
"FILTER": "Фильтр",
@ -134,6 +158,7 @@
"ADD": "Добавить",
"CREATE": "Создать",
"CONTINUE": "Продолжить",
"CONTINUEWITH": "Продолжить с {{value}}",
"BACK": "Назад",
"CLOSE": "Закрыть",
"CLEAR": "Очистить",
@ -184,12 +209,16 @@
"IAM_OWNER_VIEWER": "Имеет разрешение на проверку всего экземпляра, включая все организации.",
"IAM_ORG_MANAGER": "Имеет разрешение на создание и управление организациями",
"IAM_USER_MANAGER": "Имеет разрешение на создание пользователей и управление ими.",
"IAM_ADMIN_IMPERSONATOR": "Имеет разрешение выдавать себя за администратора и конечных пользователей из всех организаций",
"IAM_END_USER_IMPERSONATOR": "Имеет разрешение выдавать себя за конечных пользователей из всех организаций",
"ORG_OWNER": "Имеет разрешение на всю организацию",
"ORG_USER_MANAGER": "Имеет разрешение на создание пользователей организации и управление ими.",
"ORG_OWNER_VIEWER": "Имеет разрешение на проверку всей организации",
"ORG_USER_PERMISSION_EDITOR": "Имеет разрешение на управление разрешениями пользователей.",
"ORG_PROJECT_PERMISSION_EDITOR": "Имеет разрешение на управление разрешениями проекта",
"ORG_PROJECT_CREATOR": "Имеет разрешение на создание собственных проектов и базовых настроек.",
"ORG_ADMIN_IMPERSONATOR": "Имеет разрешение выдавать себя за администратора и конечных пользователей организации",
"ORG_END_USER_IMPERSONATOR": "Имеет разрешение выдавать себя за конечных пользователей организации",
"PROJECT_OWNER": "Имеет разрешение на весь проект",
"PROJECT_OWNER_VIEWER": "Имеет разрешение на проверку всего проекта",
"PROJECT_OWNER_GLOBAL": "Имеет разрешение на весь проект",
@ -1151,9 +1180,13 @@
"UPDATED": "Настройки обновлены."
},
"SECURITY": {
"DESCRIPTION": "Этот параметр разрешает встраивание окон через iframe для списка разрешенных доменов. Обратите внимание: разрешив встраивание окон, вы рискуете подвергнуть прилужение атакам тима clickjacking.",
"IFRAMETITLE": "iFrame",
"IFRAMEDESCRIPTION": "Этот параметр разрешает встраивание окон через iframe для списка разрешенных доменов. Обратите внимание: разрешив встраивание окон, вы рискуете подвергнуть прилужение атакам тима clickjacking.",
"IFRAMEENABLED": "Разрешить iframe",
"ALLOWEDORIGINS": "Разрешенные URL-адреса"
"ALLOWEDORIGINS": "Разрешенные URL-адреса",
"IMPERSONATIONTITLE": "Олицетворение",
"IMPERSONATIONENABLED": "Разрешить олицетворение",
"IMPERSONATIONDESCRIPTION": "Этот параметр позволяет в принципе использовать олицетворение. Обратите внимание, что имитатору также необходимо назначить соответствующие роли `*_IMPERSONATOR`."
},
"DIALOG": {
"RESET": {
@ -1948,6 +1981,7 @@
"DATECHANGED": "Дата изменения",
"URLS": "URL-адреса",
"DELETE": "Удалить приложение",
"JUMPTOPROJECT": "Чтобы настроить роли, полномочия и многое другое, перейдите к проекту.",
"DETAIL": {
"TITLE": "Деталь",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "设置",
"CUSTOMERPORTAL": "客户门户网站"
},
"QUICKSTART": {
"TITLE": "将 ZITADEL 集成到您的应用程序中",
"DESCRIPTION": "将 ZITADEL 集成到您的应用程序中,或使用我们的示例,几分钟内即可开始使用。",
"BTN_START": "创建应用程序",
"BTN_LEARNMORE": "了解更多",
"CREATEPROJECTFORAPP": "创建项目 {{value}}",
"SELECT_FRAMEWORK": "选择框架",
"FRAMEWORK": "框架",
"FRAMEWORK_OTHER": "其他 (OIDC, SAML, API)",
"ALMOSTDONE": "就快好了",
"REVIEWCONFIGURATION": "审查配置",
"REVIEWCONFIGURATION_DESCRIPTION": "我们为 {{value}} 应用程序创建了一个基本配置。创建后,您可以根据自己的需要调整该配置。",
"REDIRECTS": "配置重定向",
"DEVMODEWARN": "默认情况下启用开发模式。您可以稍后更新生产值。",
"GUIDE": "指南",
"BROWSEEXAMPLES": "浏览示例",
"DUPLICATEAPPRENAME": "具有相同 neme 的应用程序已经存在。请选择不同的名称。",
"DIALOG": {
"CHANGE": {
"TITLE": "变革框架",
"DESCRIPTION": "从可用的框架中选择一个,快速设置您的应用程序。"
}
}
},
"ACTIONS": {
"ACTIONS": "操作",
"FILTER": "过滤",
@ -138,6 +162,7 @@
"ADD": "添加",
"CREATE": "创建",
"CONTINUE": "继续",
"CONTINUEWITH": "继续 {{value}}",
"BACK": "返回",
"CLOSE": "关闭",
"CLEAR": "清除",
@ -187,12 +212,16 @@
"IAM_OWNER_VIEWER": "有权审查整个实例,包括所有组织",
"IAM_ORG_MANAGER": "有权创建和管理组织",
"IAM_USER_MANAGER": "有权创建和管理用户",
"IAM_ADMIN_IMPERSONATOR": "有权模拟所有组织的管理员和最终用户",
"IAM_END_USER_IMPERSONATOR": "有权模拟所有组织的最终用户",
"ORG_OWNER": "拥有整个组织的权限",
"ORG_USER_MANAGER": "有权创建和管理组织的用户",
"ORG_OWNER_VIEWER": "有权审查整个组织",
"ORG_USER_PERMISSION_EDITOR": "有权管理用户授权",
"ORG_PROJECT_PERMISSION_EDITOR": "有权管理项目授权",
"ORG_PROJECT_CREATOR": "有权创建自己的项目和基础设置",
"ORG_ADMIN_IMPERSONATOR": "有权模拟组织的管理员和最终用户",
"ORG_END_USER_IMPERSONATOR": "有权模拟组织的最终用户",
"PROJECT_OWNER": "拥有整个项目的权限",
"PROJECT_OWNER_VIEWER": "有权审查整个项目",
"PROJECT_OWNER_GLOBAL": "拥有整个项目的权限",
@ -1159,9 +1188,13 @@
"UPDATED": "设置已更新。"
},
"SECURITY": {
"DESCRIPTION": "此设置将CSP设置为允许来自一组允许的域的框架。请注意通过启用iFrames的使用你会有允许点击劫持的风险。",
"IFRAMETITLE": "iFrame",
"IFRAMEDESCRIPTION": "此设置将CSP设置为允许来自一组允许的域的框架。请注意通过启用iFrames的使用你会有允许点击劫持的风险。",
"IFRAMEENABLED": "允许 iFrame",
"ALLOWEDORIGINS": "允许的来源 URL"
"ALLOWEDORIGINS": "允许的来源 URL",
"IMPERSONATIONTITLE": "冒充",
"IMPERSONATIONENABLED": "允许模拟",
"IMPERSONATIONDESCRIPTION": "此设置原则上允许使用模拟。请注意,模拟者还需要分配适当的 `*_IMPERSONATOR` 角色。"
},
"DIALOG": {
"RESET": {
@ -1953,6 +1986,7 @@
"DATECHANGED": "修改于",
"URLS": "URLs",
"DELETE": "删除应用",
"JUMPTOPROJECT": "要配置角色、授权等,请导航到项目。",
"DETAIL": {
"TITLE": "详情",
"STATE": {

View File

@ -29,6 +29,7 @@
@import 'src/app/modules/top-view/top-view.component';
@import 'src/app/pages/projects/projects.component';
@import 'src/app/modules/edit-text/edit-text.component.scss';
@import 'src/app/pages/projects/apps/integrate/integrate.component.scss';
@import 'src/app/modules/providers/providers.scss';
@import 'src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component';
@import 'src/app/pages/users/user-detail/user-detail/user-detail.component';
@ -43,10 +44,14 @@
@import 'src/app/modules/form-field/field/form-field.component.scss';
@import 'src/app/modules/label/label.component.scss';
@import 'src/app/modules/string-list/string-list.component.scss';
@import 'src/app/components/quickstart/quickstart.component.scss';
@import 'src/app/components/framework-autocomplete/framework-autocomplete.component.scss';
@import 'src/app/components/framework-change/framework-change.component.scss';
@import 'src/app/modules/meta-layout/meta.scss';
@import 'src/app/pages/projects/owned-projects/project-grant-detail/project-grant-illustration/project-grant-illustration.component';
@import 'src/app/modules/accounts-card/accounts-card.component.scss';
@import 'src/app/modules/onboarding-card/onboarding-card.component.scss';
@import 'src/app/pages/app-create/app-create.component.scss';
@import 'src/app/modules/onboarding/onboarding.component.scss';
@import 'src/app/modules/filter/filter.component.scss';
@import 'src/app/modules/policies/message-texts/message-texts.component.scss';
@ -83,7 +88,9 @@
@include info-overlay-theme($theme);
@include app-auth-method-radio-theme($theme);
@include security-policy-theme($theme);
@include framework-change-theme($theme);
@include search-user-autocomplete-theme($theme);
@include quickstart-theme($theme);
@include project-role-chips-theme($theme);
@include card-theme($theme);
@include idp-settings-theme($theme);
@ -124,6 +131,7 @@
@include refresh-table-theme($theme);
@include accounts-card-theme($theme);
@include sidenav-theme($theme);
@include framework-autocomplete-theme($theme);
@include info-section-theme($theme);
@include actions-theme($theme);
@include filter-theme($theme);
@ -131,11 +139,12 @@
@include project-grid-theme($theme);
@include granted-project-detail-theme($theme);
@include user-grants-theme($theme);
@include app-create-theme($theme);
@include info-row-theme($theme);
@include redirect-uris-theme($theme);
@include action-keys-theme($theme);
@include codemirror-theme($theme);
@include contact-theme($theme);
@include app-create-theme($theme);
@include app-integrate-theme($theme);
@include domain-verification-theme($theme);
}

View File

@ -400,6 +400,7 @@ $caos-dark-app-theme: modify-palette($caos-dark-app-theme, foreground, $cnsl-dar
$background: map-get($caos-light-app-theme, background);
$foreground: map-get($caos-light-app-theme, foreground);
$primary: map-get($caos-light-app-theme, primary);
--warn: #cd3d56;
--success: #10b981;
@ -477,6 +478,7 @@ $caos-dark-app-theme: modify-palette($caos-dark-app-theme, foreground, $cnsl-dar
.dark-theme {
$background: map-get($caos-dark-app-theme, background);
$foreground: map-get($caos-dark-app-theme, foreground);
$primary: map-get($caos-dark-app-theme, primary);
--warn: #ff3b5b;
--success: #10b981;

View File

@ -4,11 +4,6 @@
$background: map-get($theme, background);
$foreground: map-get($theme, foreground);
// .data-e2e-success {
// background-color: map-get($background, cards) !important;
// color: var(--success) !important;
// }
.data-e2e-failure {
.mdc-snackbar__surface {
background-color: map-get($background, toast) !important;

View File

@ -16,6 +16,8 @@
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,

View File

@ -2910,6 +2910,22 @@
"@material/theme" "15.0.0-canary.bc9ae6c9c.0"
tslib "^2.1.0"
"@netlify/framework-info@^9.8.10":
version "9.8.10"
resolved "https://registry.yarnpkg.com/@netlify/framework-info/-/framework-info-9.8.10.tgz#a18589f132dafb5cb7f86c05a9895b9118633fe1"
integrity sha512-VT8ejAaB/XU2xRpdpQinHUO1YL3+BMx6LJ49wJk2u9Yq/VI1/gYCi5VqbqTHBQXJUlOi84YuiRlrDBsLpPr8eg==
dependencies:
ajv "^8.12.0"
filter-obj "^5.0.0"
find-up "^6.3.0"
is-plain-obj "^4.0.0"
locate-path "^7.0.0"
p-filter "^3.0.0"
p-locate "^6.0.0"
process "^0.11.10"
read-pkg-up "^9.0.0"
semver "^7.3.8"
"@ngtools/webpack@16.2.2":
version "16.2.2"
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-16.2.2.tgz#55fac744d1aca4542fb9a4ff16a48d2b384ffd37"
@ -3344,6 +3360,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.7.0.tgz#c03de4572f114a940bc2ca909a33ddb2b925e470"
integrity sha512-zI22/pJW2wUZOVyguFaUL1HABdmSVxpXrzIqkjsHmyUjNhPoWM1CKfvVuXfetHhIok4RY573cqS0mZ1SJEnoTg==
"@types/normalize-package-data@^2.4.1":
version "2.4.4"
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901"
integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==
"@types/opentype.js@^1.3.8":
version "1.3.8"
resolved "https://registry.yarnpkg.com/@types/opentype.js/-/opentype.js-1.3.8.tgz#741be92429d1c2d64b5fa79cf692f74b49d6007f"
@ -3845,6 +3866,14 @@ aggregate-error@^3.0.0:
clean-stack "^2.0.0"
indent-string "^4.0.0"
aggregate-error@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-4.0.1.tgz#25091fe1573b9e0be892aeda15c7c66a545f758e"
integrity sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==
dependencies:
clean-stack "^4.0.0"
indent-string "^5.0.0"
ajv-formats@2.1.1, ajv-formats@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz"
@ -3864,7 +3893,7 @@ ajv-keywords@^5.1.0:
dependencies:
fast-deep-equal "^3.1.3"
ajv@8.12.0, ajv@^8.0.0, ajv@^8.9.0:
ajv@8.12.0, ajv@^8.0.0, ajv@^8.12.0, ajv@^8.9.0:
version "8.12.0"
resolved "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz"
integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==
@ -4490,6 +4519,13 @@ clean-stack@^2.0.0:
resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz"
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
clean-stack@^4.0.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-4.2.0.tgz#c464e4cde4ac789f4e0735c5d75beb49d7b30b31"
integrity sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==
dependencies:
escape-string-regexp "5.0.0"
cli-cursor@3.1.0, cli-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz"
@ -5318,6 +5354,11 @@ escape-html@~1.0.3:
resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz"
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
escape-string-regexp@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
@ -5647,6 +5688,11 @@ fill-range@^7.0.1:
dependencies:
to-regex-range "^5.0.1"
filter-obj@^5.0.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-5.1.0.tgz#5bd89676000a713d7db2e197f660274428e524ed"
integrity sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==
finalhandler@1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz"
@ -5845,6 +5891,11 @@ function-bind@^1.1.1:
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
gauge@^4.0.3:
version "4.0.4"
resolved "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz"
@ -6100,6 +6151,13 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
hasown@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.1.tgz#26f48f039de2c0f8d3356c223fb8d50253519faa"
integrity sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==
dependencies:
function-bind "^1.1.2"
hdr-histogram-js@^2.0.1:
version "2.0.3"
resolved "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz"
@ -6114,6 +6172,13 @@ hdr-histogram-percentiles-obj@^3.0.0:
resolved "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz"
integrity sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==
hosted-git-info@^4.0.1:
version "4.1.0"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224"
integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==
dependencies:
lru-cache "^6.0.0"
hosted-git-info@^6.0.0:
version "6.1.1"
resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz"
@ -6345,6 +6410,11 @@ indent-string@^4.0.0:
resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
indent-string@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-5.0.0.tgz#4fd2980fccaf8622d14c64d694f4cf33c81951a5"
integrity sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==
infer-owner@^1.0.4:
version "1.0.4"
resolved "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz"
@ -6433,6 +6503,13 @@ is-core-module@^2.11.0, is-core-module@^2.8.1:
dependencies:
has "^1.0.3"
is-core-module@^2.5.0:
version "2.13.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384"
integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==
dependencies:
hasown "^2.0.0"
is-docker@^2.0.0, is-docker@^2.1.1:
version "2.2.1"
resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz"
@ -6499,6 +6576,11 @@ is-plain-obj@^3.0.0:
resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz"
integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==
is-plain-obj@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0"
integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==
is-plain-object@^2.0.4:
version "2.0.4"
resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz"
@ -7009,7 +7091,7 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
locate-path@^7.1.0:
locate-path@^7.0.0, locate-path@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-7.2.0.tgz#69cb1779bd90b35ab1e771e1f2f89a202c2a8a8a"
integrity sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==
@ -7503,6 +7585,16 @@ nopt@^6.0.0:
dependencies:
abbrev "^1.0.0"
normalize-package-data@^3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e"
integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==
dependencies:
hosted-git-info "^4.0.1"
is-core-module "^2.5.0"
semver "^7.3.4"
validate-npm-package-license "^3.0.1"
normalize-package-data@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz"
@ -7769,6 +7861,13 @@ os-tmpdir@~1.0.1, os-tmpdir@~1.0.2:
resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz"
integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
p-filter@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/p-filter/-/p-filter-3.0.0.tgz#ce50e03b24b23930e11679ab8694bd09a2d7ed35"
integrity sha512-QtoWLjXAW++uTX67HZQz1dbTpqBfiidsB6VtQUC9iR85S120+s0T5sO6s+B5MLzFcZkrEd/DGMmCjR+f2Qpxwg==
dependencies:
p-map "^5.1.0"
p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz"
@ -7818,6 +7917,13 @@ p-map@^4.0.0:
dependencies:
aggregate-error "^3.0.0"
p-map@^5.1.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-5.5.0.tgz#054ca8ca778dfa4cf3f8db6638ccb5b937266715"
integrity sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==
dependencies:
aggregate-error "^4.0.0"
p-retry@^4.5.0:
version "4.6.2"
resolved "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz"
@ -7867,7 +7973,7 @@ parent-module@^1.0.0:
dependencies:
callsites "^3.0.0"
parse-json@^5.0.0:
parse-json@^5.0.0, parse-json@^5.2.0:
version "5.2.0"
resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz"
integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
@ -8130,6 +8236,11 @@ process-nextick-args@~2.0.0:
resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
process@^0.11.10:
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
promise-inflight@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz"
@ -8307,6 +8418,25 @@ read-package-json@^6.0.0:
normalize-package-data "^5.0.0"
npm-normalize-package-bin "^3.0.0"
read-pkg-up@^9.0.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-9.1.0.tgz#38ca48e0bc6c6b260464b14aad9bcd4e5b1fbdc3"
integrity sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==
dependencies:
find-up "^6.3.0"
read-pkg "^7.1.0"
type-fest "^2.5.0"
read-pkg@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-7.1.0.tgz#438b4caed1ad656ba359b3e00fd094f3c427a43e"
integrity sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==
dependencies:
"@types/normalize-package-data" "^2.4.1"
normalize-package-data "^3.0.2"
parse-json "^5.2.0"
type-fest "^2.0.0"
readable-stream@^2.0.1, readable-stream@~2.3.6:
version "2.3.8"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz"
@ -8676,6 +8806,13 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.3.4:
version "7.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d"
integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==
dependencies:
lru-cache "^6.0.0"
send@0.18.0:
version "0.18.0"
resolved "https://registry.npmjs.org/send/-/send-0.18.0.tgz"
@ -9369,6 +9506,11 @@ type-fest@^0.21.3:
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz"
integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
type-fest@^2.0.0, type-fest@^2.5.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b"
integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz"
@ -9516,7 +9658,7 @@ v8-compile-cache@2.3.0:
resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz"
integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
validate-npm-package-license@^3.0.4:
validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4:
version "3.0.4"
resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz"
integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==

View File

@ -10,7 +10,7 @@ ZITADEL interpretes the scripts as JavaScript.
Make sure your scripts are ECMAScript 5.1(+) compliant.
Go to the [goja GitHub page](https://github.com/dop251/goja) for detailed reference about the underlying library features and limitations.
You can find great snippets and examples which show how you can use actions in [this repository](https://github.com/zitadel/actions).
Stuck customizing ZITADEL actions? Find samples for setting OIDC claims, SAML attributes, extending JIT provisioning data, calling external APIs, and more in [this repository](https://github.com/zitadel/actions).
Actions are a key feature to extend the functionality of ZITADEL and continuously improve the feature and expand the use cases. Check out our [roadmap](https://zitadel.com/roadmap) for more details.

View File

@ -52,6 +52,40 @@ The object has the following fields and methods:
- `text()` *string*
Returns the body
## Log
The log module provides you with the functionality to log to stdout.
### Import
```js
let logger = require("zitadel/log")
```
### `log()`, `warn()`, `error()` function
The logger offers three distinct log levels (info, warn, and error) to effectively communicate messages based on their severity, enhancing debugging and troubleshooting efficiency.
Use the function that reflects your log level.
- log()
- warn()
- error()
### Example
```js
logger.info("This is an info log.")
logger.warn("This is a warn log.")
logger.error("This is an error log.")
```
#### Parameters
- `msg` *string*
The message you want to print out.
## UUID
This module provides functionality to generate a UUID
@ -105,4 +139,4 @@ function setUUID(ctx, api) {
api.v1.user.appendMetadata('custom-id', uuid.v4());
}
```
```

View File

@ -0,0 +1,12 @@
---
title: Core Resources
sidebar_label: Core Resources
---
import DocCardList from '@theme/DocCardList';
ZITADEL provides multiple APIs to manage the system, instances and resources such as users, projects and more.
There are different versions and multiple services available:
<DocCardList />

View File

@ -15,7 +15,7 @@ The [OpenID Connect & OAuth endpoints](/docs/apis/openidoauth/endpoints) and [SA
## Authentication & authorization
ZITADEL implements industry standards such as OpenID Connect, OAuth 2.0, or SAML for authentication.
Please refer to our guides how to [authenticate users](/docs/guides/integrate/human-users) through an interactive authentication process and how to [authenticate service users](/docs/guides/integrate/serviceusers) with a programmatic authentication.
Please refer to our guides how to [authenticate users](/docs/guides/integrate/login/login-users) through an interactive authentication process and how to [authenticate service users](/docs/guides/integrate/serviceusers) with a programmatic authentication.
### OpenID Connect & OAuth
@ -39,7 +39,7 @@ Refer to our guide to learn how to [build your own login UI](/docs/guides/integr
## ZITADEL APIs (resource-based)
ZITADEL provides APIs for each [core resource](/docs/apis/resources):
ZITADEL provides APIs for each [core resource](/docs/apis/v2):
- [User](/docs/apis/resources/user_service)
- [Session](/docs/apis/resources/session_service)
@ -268,6 +268,8 @@ ZITADEL hosts everything under a single domain: `{instance}.zitadel.cloud` or yo
The domain is used as the OIDC issuer and as the base url for the gRPC and REST APIs, the Login and Console UI, which you'll find under `{your_domain}/ui/console/`.
Are you self-hosting and having troubles with *Instance not found* errors? [Check out this page](https://zitadel.com/docs/self-hosting/manage/custom-domain).
## API path prefixes
If you run ZITADEL on a custom domain, you may want to reuse that domain for other applications.

View File

@ -57,7 +57,7 @@ Depending on the authentication and authorization flow of your application you m
for most application types. The playground appends automatically a code challenge
for PKCE flows.
You need to append a "Code Challenge" by providing a random <span className="text-teal-600">Code Verifier</span> that is being hashed and encoded in the request to the token endpoint, please see our [guide](/guides/integrate/login-users#token-request) for more details.
You need to append a "Code Challenge" by providing a random <span className="text-teal-600">Code Verifier</span> that is being hashed and encoded in the request to the token endpoint, please see our [guide](/guides/integrate/login/oidc/login-users#token-request) for more details.
More in the [documentation](/apis/openidoauth/authn-methods) about authentication methods.
@ -123,6 +123,6 @@ This can be achieved by adding the scope `urn:zitadel:iam:org:project:id:zitadel
## How to use ZITADEL in your project
Please refer to our [guide](/guides/integrate/login-users) on how to login users.
Please refer to our [guide](/guides/integrate/login/oidc/login-users) on how to login users.
OpenID Connect certified libraries should allow you to customize the parameters and define scopes for the authorization request. You can also continue by using one of our [example applications](/docs/sdk-examples/introduction).

12
docs/docs/apis/v2.mdx Normal file
View File

@ -0,0 +1,12 @@
---
title: APIs V2 (Beta)
---
import DocCardList from '@theme/DocCardList';
APIs V2 organize access by resources (users, settings, etc.), unlike context-specific V1 APIs.
This simplifies finding the right API, especially for multi-organization resources.
**Note**: V2 is currently in [Beta](/support/software-release-cycles-support#beta) and not yet generally available (breaking changes possible). Check individual services for availability.
<DocCardList />

15
docs/docs/apis/v3.mdx Normal file
View File

@ -0,0 +1,15 @@
---
title: APIs V3 (Preview)
---
import DocCardList from '@theme/DocCardList';
APIs (V3) organize access by resources (users, settings, etc.), unlike context-specific V1 APIs.
This simplifies finding the right API, especially for multi-organization resources.
V3 also offers more flexibility over the V2 API in these points:
- User schema definition for custom user management.
- Behavior customization (API call manipulation, webhooks).
**Note**: V3 is currently in [Preview](/support/software-release-cycles-support#preview) and not yet generally available (breaking changes possible). Check individual services for availability.
<DocCardList />

View File

@ -136,16 +136,12 @@ It is also responsible to execute authorization checks. To check if a request is
### Storage Layer
As ZITADEL itself is built completely stateless only the storage layer is needed for storing things.
The storage layer of ZITADEL is responsible for multiple things. For example:
As ZITADEL itself is built completely stateless only the storage layer is needed to persist states.
The storage layer of ZITADEL is responsible for multiple tasks. For example:
- Distributing data for high availability over multiple server, data centers or regions
- Guarantee strong consistency for the command side
- Guarantee good query performance for the query side
- Ability to store data in specific data centers or regions for data residency (This is only supported with CockroachDB Cloud or Enterprise)
- Backup and restore operation for disaster recovery purpose
ZITADEL currently supports CockroachDB as first choice of storage due to its perfect match for ZITADELs needs.
Alternatively you can run ZITADEL also with Postgres which is [Enterprise Supported](/docs/support/software-release-cycles-support#partially-supported).
Make sure to read our [Production Guide](/docs/self-hosting/manage/production#prefer-cockroachdb) before you decide to use it.
ZITADEL currently supports PostgreSQL and CockroachDB..
Make sure to read our [Production Guide](/docs/self-hosting/manage/production#prefer-cockroachdb) before you decide on using one of them.

View File

@ -0,0 +1,33 @@
---
title: "ZITADEL Console: Resource management and customization"
sidebar_label: Console
---
The ZITADEL console is a web-based interface designed to facilitate the management and administration of ZITADEL resources and configurations.
It serves as a central hub where administrators can perform various tasks related to identity and access management within their organization's infrastructure.
The console is available by navigating to the [custom domain](/docs/concepts/features/custom-domain) of your instance and appending the path `/ui/console`.
Administrators can [restrict end-users from accessing the console](/docs/guides/solution-scenarios/restrict-console).
Here's an overview of what the console enables users to do:
1. **Default Settings:** [Managers](/docs/concepts/structure/managers) can access and manage the default settings of the ZITADEL system. This includes configuring authentication methods, security policies, and other system-wide parameters to meet the organization's requirements.
2. **Resource Management:** The console allows for the creation, updating, and deletion of essential resources such as organizations, users, projects, and applications. Administrators can efficiently manage these entities to ensure proper access control and governance.
3. **User Management:** Administrators can manage user accounts, including creating new user accounts, updating user profiles, resetting passwords, and deactivating or deleting user accounts as needed.
4. **Access Control:** The console provides tools for defining and managing access control policies. This includes assigning roles and permissions to users, configuring fine-grained access controls, and managing access to specific resources.
5. **Audit Logging:** The console offers access to [audit logs](/docs/concepts/features/audit-trail) that track user activity and changes made to resources and system settings. Administrators can review these logs to monitor security-related events, track changes, and maintain compliance with regulatory requirements.
6. **Customization and Branding:** The console allows organizations to customize the branding and appearance of their ZITADEL instance. This includes uploading custom logos, selecting color schemes, and applying other visual customizations to align the interface with the organization's branding guidelines.
7. **Manager Assignment:** Administrators can assign ZITADEL [managers](/docs/concepts/structure/managers) who have elevated privileges for managing resources within the organization. This allows for the delegation of administrative tasks while maintaining proper oversight and control.
Overall, the ZITADEL console serves as a comprehensive tool for administrators to configure, manage, and monitor identity and access management within their organization, providing the necessary controls to ensure security, compliance, and efficient administration of resources.
Notes:
- Detailed guide on [using the console](/docs/guides/manage/console/overview)
- [Restrict access](/docs/guides/solution-scenarios/restrict-console) to the console

View File

@ -0,0 +1,13 @@
---
title: Custom domain
sidebar_label: Custom domain
---
A ZITADEL custom domain refers to the ability for organizations to personalize the authentication experience by using their own domain name rather than the default ZITADEL domain.
This feature allows organizations to maintain their brand identity throughout the authentication process, providing a seamless and consistent user experience.
By configuring a custom domain within ZITADEL, organizations can replace the default authentication URLs with their own domain, such as "login.example.com" or "auth.companyname.com".
This not only enhances the overall user experience but also reinforces the organization's brand presence. Additionally, custom domains can contribute to trust and credibility, as users are more likely to recognize and trust URLs associated with the organization rather than generic domains. Overall, ZITADEL's custom domain feature empowers organizations to tailor the authentication process to align with their brand identity and user expectations.
Learn how to [configure a custom domain in ZITADEL Cloud](http://localhost:3000/docs/guides/manage/cloud/instances#add-custom-domain) or how to configure [custom domain when self-hosting](http://localhost:3000/docs/self-hosting/manage/custom-domain).

View File

@ -11,7 +11,7 @@ This identity is known as federated identity and the pattern behind this is iden
A service provider that specializes in brokering access control between multiple service providers (also referred to as relying parties) is called an identity broker.
Federated identity management is an arrangement that is made between two or more such identity brokers across organizations.
For example, if Google is configured as an identity provider in your organization, the user will get the option to use his Google Account on the Login Screen of ZITADEL. Because Google is registered as a trusted identity provider, the user will be able to login in with the Google account after the user is linked with an existing ZITADEL account (if he is already registered) or a new one with the claims provided by Google.
For example, if Google is configured as an identity provider in your organization, the user will get the option to use his Google Account on the Login Screen of ZITADEL. Because Google is registered as a trusted identity provider, the user will be able to login in with the Google account after the user is linked with an existing ZITADEL account (if the user is already registered) or a new one with the claims provided by Google.
![Identity Brokering](/img/guides/identity_brokering.png)

View File

@ -0,0 +1,37 @@
---
title: "Passkeys in ZITADEL: Passwordless phishing-resistant authentication"
sidebar_label: Passkeys
---
ZITADEL's passkeys feature enables passwordless authentication, offering a **smoother and more secure** login experience for your users. This document explains the essential details for developers.
### What are Passkeys?
Imagine signing in without passwords! Passkeys, replacing traditional passwords, leverage **public-key cryptography** similar to FIDO2 and WebAuthn. Users rely on their devices' **biometrics or PINs** for authentication, eliminating password burdens.
### Benefits for Developers
* **Enhanced Security:** Phishing-resistant passkeys minimize credential theft risks.
* **Streamlined User Experience:** Faster, easier logins free users from managing passwords.
* **Platform Agnostic:** Works across devices and platforms supporting passkeys.
* **Modern Standard:** Complies with the FIDO2 and WebAuthn standards.
### Features
* **Seamless Registration:** Create unique passkeys for users on various devices. Optionally pair them with specific users and choose cross-platform or platform-specific options.
* **User Control:** Users manage their passkeys directly through ZITADEL's self-service portal, allowing registration, viewing, and deletion.
* **Intuitive Login:** Users initiate passwordless login by selecting the passkey option and verifying themselves with the device's biometrics (fingerprint, face ID, etc.).
* **Robust Fallback:** Traditional password login remains available for users without passkeys.
### Developer Resources
* **Documentation:** Passkeys Guide: [https://zitadel.com/docs/guides/integrate/login-ui/passkey](/docs/guides/integrate/login-ui/passkey)
* **Create Passkey Registration Link API:** [https://zitadel.com/docs/guides/manage/user/reg-create-user](/docs/guides/manage/user/reg-create-user)
### Notes
* 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.
* Passkeys are bound to your domain, thus we recommend configuring a [custom domain](/docs/concepts/features/custom-domain.md) before setting up passkeys.
Don't hesitate to ask if you have further questions about integrating passkeys in your ZITADEL application!

View File

@ -57,43 +57,6 @@ When you login with an external identity provider, and the user does not exist i
## Login
:::info Customization and Branding
The login page can be changed by customizing different branding aspects and you can define a custom domain for the login (eg, login.acme.com).
By default, the displayed branding is defined based on the user's domain. In case you want to show the branding of a specific organization by default, you need to either pass a primary domain scope (`urn:zitadel:iam:org:domain:primary:{domainname}`) with the authorization request, or define the behavior on your Project's settings.
:::
### Web, Mobile, and Single-Page Applications
[This guide](/guides/integrate/login-users) explains in more detail the login-flows for different application types.
Human users are redirected to ZITADEL's login page and complete sign-in with the interactive login flow.
It is important to understand that ZITADEL provides a hosted login page and the device of the users opens this login page in a browser, even on Native/Mobile apps.
#### MFA / 2FA
Users are automatically prompted to provide a second factor, when
- Instance or organization [login policy](/concepts/structure/policies#login-policy) is set
- Requested by the client
- A multi-factor is setup for the user
When a multi-factor is required, but not set-up, then the user is requested to set-up an additional factor.
#### FIDO Passkeys
Users can select a button to initiate passwordless login or use a fall-back method (ie. login with username/password), if available.
The passwordless login flow follows the FIDO2 / WebAuthN standard.
Briefly explained the following happens:
- User selects button
- User's device will ask the user to provide a gesture (e.g., FaceID, Windows Hello, Fingerprint, PIN)
- The user is being redirected to the application
With the introduction of passkeys the gesture can be provided on ANY of the user's devices.
This is not strictly the device where the login flow is being executed (e.g., on a mobile device).
The user experience depends mainly on the used operating system and browser.
### SSO / Social Logins
Given an external identity provider is configured on the instance or on the organization, then:
@ -105,31 +68,7 @@ Given an external identity provider is configured on the instance or on the orga
### Machines
Machine accounts can't use an interactive login but require other means of authentication, such as privately-signed JWT or personal access tokens.
Read more about [Service Users](/guides/integrate/serviceusers) and recommended [OpenID Connect Flows](/guides/integrate/oauth-recommended-flows#different-client-profiles).
### Other Clients
We currently do not expose the Login API.
Whereas you can register users via the management API, you can't login users with our APIs.
This might be important in cases where you can't use a website (eg, Games, VR, ...).
### Account picker
A list of accounts that were used to log-in are shown to the user.
The user can click the account in the list and does not need to type the username.
Users can still login with a different user that is not in the list.
:::info
This behavior can be changed with the authorization request. Please refer to our [guide](/guides/integrate/login-users).
:::
### Password reset
Unauthenticated users can request a password reset after providing the loginname during the login flow.
- User selects reset password
- An email will be sent to the verified email address
- User opens a link and has to provide a new password
Read more about [Service Users](/guides/integrate/serviceusers) and recommended [OpenID Connect Flows](/guides/integrate/login/oidc/oauth-recommended-flows#different-client-profiles).
## Logout

View File

@ -0,0 +1,70 @@
---
title: "Opaque Tokens in Zitadel: Enhancing Application Security"
sidebar_label: Opaque tokens
---
In the context of application security, robust authentication mechanisms are essential for safeguarding sensitive data and ensuring user trust.
Opaque tokens, the default token type within the ZITADEL platform, play a crucial role in bolstering security measures.
This documentation elucidates the principles behind opaque tokens, their implementation within ZITADEL, and their advantages over alternative token types.
## What are Opaque Tokens?
Opaque tokens are a type of access token utilized in authentication processes, particularly within OAuth 2.0 and OpenID Connect (OIDC) frameworks. Unlike self-contained tokens like [JSON Web Tokens (JWT)](https://datatracker.ietf.org/doc/html/rfc7519), opaque tokens do not divulge user information directly.
Instead, they serve as opaque references to session data stored securely on the authorization server.
## Authentication Workflow with Opaque Tokens
1. **Token Generation**: When a user initiates an authentication process within an application integrated with ZITADEL, the authentication server generates a unique opaque token associated with the user's session.
2. **Token Presentation**: The generated opaque token is provided to the client, which subsequently presents it during requests to access protected resources within the application.
3. **Token Verification**: Upon receiving the opaque token, the application server interacts with the authorization server to validate its authenticity and retrieve detailed information about the user's session. This process ensures the integrity of the authentication flow and verifies the user's permissions to access requested resources.
## Benefits of Opaque Tokens in ZITADEL
1. **Reduced Token Exposure**: Opaque tokens mitigate the risk of token exposure since they do not contain sensitive user information directly. This reduces the likelihood of token-based attacks and enhances overall security posture.
2. **Enhanced Server-side Control**: With opaque tokens, validation occurs server-side, granting administrators greater control over authentication flows and access policies. This centralized approach facilitates comprehensive monitoring and enforcement of security measures, including server-side single-logout across all applications.
3. **Protection Against Token Tampering**: Opaque tokens prevent unauthorized manipulation of token contents, thereby ensuring the integrity and authenticity of authentication processes. This protection against token tampering further strengthens the security of applications integrated with ZITADEL.
## Opaque Tokens vs. JWT Tokens
When it comes to implementing authentication and authorization mechanisms within applications, developers often face the choice between different types of tokens, each with its own set of characteristics and advantages.
Two common types of tokens used in authentication protocols are opaque tokens and JSON Web Tokens (JWT).
### Structure
- **Opaque Tokens**: Opaque tokens are essentially references or pointers to information stored on the authorization server. They do not contain any meaningful user data within the token itself. Instead, they typically consist of a unique identifier (e.g., a session ID or database key) that allows the server to look up the associated session information.
- **JWT Tokens**: JSON Web Tokens, on the other hand, are self-contained tokens that contain user information in a JSON format. JWTs consist of three base64-encoded sections: header, payload, and signature. The payload contains claims or assertions about the user (e.g., user ID, roles, expiration time) that are digitally signed to ensure integrity.
### Token verification
- **Opaque Tokens**: Verifying opaque tokens requires interaction with the authorization server. When a client presents an opaque token to a resource server, the resource server sends the token to the authorization server for validation. The authorization server then checks the token's validity and returns the associated user information if the token is valid.
- **JWT Tokens**: JWT tokens can be verified locally by clients without needing to communicate with the authorization server. Clients can validate JWT signatures using public keys or shared secrets obtained during the token issuance process. This decentralized verification process can be faster and more scalable but requires securely distributing and managing keys.
### Token security and size
- **Opaque Tokens**: Since opaque tokens do not contain user information, they are inherently more secure in terms of protecting sensitive data. However, the reliance on server-side validation means there is an overhead associated with each token verification request, which can impact performance in high-throughput scenarios.
- **JWT Tokens**: JWT tokens contain user information within the token itself, which can be convenient for clients as it eliminates the need for frequent interactions with the authorization server. However, this also means that JWT tokens can potentially expose sensitive information if not handled and secured properly. Additionally, JWT tokens tend to be larger compared to opaque tokens due to the encoded payload.
### Use cases and trade-offs
- **Opaque Tokens**: Opaque tokens are well-suited for scenarios where security and confidentiality are top priorities, such as handling highly sensitive user data or complying with strict privacy regulations. They are particularly advantageous in distributed systems where centralized control over authentication and access policies is desired.
- **JWT Tokens**: JWT tokens are often preferred in scenarios where performance and scalability are critical, such as microservices architectures or API-based applications. The ability to verify tokens locally can reduce latency and minimize dependencies on external services. However, developers must carefully consider the implications of including sensitive information in JWT payloads and implement appropriate security measures.
## Conclusion
In conclusion, opaque tokens represent a foundational component in fortifying application security within ZITADEL.
By leveraging opaque tokens, organizations can establish robust authentication mechanisms, mitigate security risks, and maintain stringent control over access policies.
As organizations navigate the complex landscape of application security, integrating technologies such as opaque tokens becomes imperative for safeguarding sensitive data and fostering user trust.
Notes:
- Read more about the differences in our [blog on JWT vs. Opaque tokens](https://zitadel.com/blog/jwt-vs-opaque-tokens)
- Learn how to use [token introspection](/docs/guides/integrate/token-introspection) to validate access tokens
- Decode, verify and generate valid JWT tokens with [jwt.io](https://jwt.io/)

View File

@ -1,7 +1,12 @@
ZITADEL Managers are Users who have permission to manage ZITADEL itself. This means that those users are allowed to login into your instance console and edit certain parts of the configuration.
There are some different levels for managers.
Managers are [human users or service users](/docs/concepts/structure/users) who have permission to manage resources within ZITADEL.
- **IAM Managers**: This is the highest level. Users with IAM Manager roles are able to manage the whole Instance.
- **Org Managers**: Managers in the Organization Level are able to manage everything within the granted Organization.
- **Project Mangers**: In this level the user is able to manage a project.
- **Project Grant Manager**: The project grant manager is for projects, which are granted of another organization.
Manager permissions can be assigned to different levels in ZITADEL:
- **IAM Managers**: This is the highest level. Users with IAM Manager roles are able to manage the whole [Instance](/docs/concepts/structure/instance).
- **Org Managers**: Managers in the Organization Level are able to view or manage everything, according to their permissions, within the granted [Organization](/docs/concepts/structure/organizations).
- **Project Mangers**: In this level the user is able to manage a [project](/docs/concepts/structure/projects).
- **Project Grant Manager**: The project grant manager is for [granted projects](/docs/concepts/structure/granted_projects) by another organization.
Scope of the managers is restricted based on their level.
That means a Manager, assigned to one organization, will only get access to resources and configurations of that organization.
Only Managers on the instance level can view resources, such as users, across all organizations.

View File

@ -7,4 +7,7 @@ import ManagerDescription from "./_manager_description.mdx";
<ManagerDescription name="ManagerDescription" />
To read more on how managers are created and which roles exist read the console guide [here](../../guides/manage/console/managers).
Notes:
- Read our [guide on Managers](../../guides/manage/console/managers) to learn more about the role concept and how to use Manager roles in ZITADEL.
- [API reference](/docs/apis/resources/mgmt/management-service-list-org-member-roles) for Managers on organization level

View File

@ -22,7 +22,7 @@ Go to [Applications](./applications) for more details.
To enable another organization to use a project, the organization needs a grant to the project.
Only the selected roles will be available to the granted organization.
The granted organization will be able to manage the authorizations of his users for the granted project by himself in his own organization.
The granted organization will be able to manage the authorizations of their users for the granted project by themselves in their organization.
More about granted projects: [Granted Projects](./granted_projects)
@ -33,3 +33,15 @@ The display name is only to provide a human-readable name if needed.
And the group should enable a better handling in ZITADEL console, like give a user all the roles of a specific group. (Not implemented yet)
All applications in a project share the roles. Read more about roles [here](../../guides/manage/console/roles)
## Default Project
When creating a new ZITADEL instance you will find an automatically created project on the first created organization.
This default project does represent the ZITADEL project and is used to secure the different apps shipped with ZITADEL.
This includes the ZITADEL Management Console and APIs.
We do not recommend changing any settings or using this project for anything else, as this could influence the behavior of ZITADEL.
The default name of the project is "ZITADEL", this might differ on self-hosted instances, when you change the default name.
![Default Project](/img/guides/solution-scenarios/console-default-project.png)

View File

@ -17,7 +17,7 @@ All that is required, is a service account with an Org Owner (or another role, d
However, we recommend you read the guide on [how to access ZITADEL API](../../guides/integrate/access-zitadel-apis) and the associated guides for a basic knowledge of :
- [Recommended Authorization Flows](../../guides/integrate/oauth-recommended-flows.md)
- [Recommended Authorization Flows](../../guides/integrate/login/oidc/oauth-recommended-flows.md)
- [Service Users](../../guides/integrate/serviceusers)
> Be sure to have a valid key JSON and that its service account is either ORG_OWNER or at least ORG_OWNER_VIEWER before you continue with this guide.

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