Merge branch 'main' into next

# Conflicts:
#	.releaserc.js
This commit is contained in:
Livio Spring 2023-07-21 07:47:54 +02:00
commit e2644cf076
No known key found for this signature in database
GPG Key ID: 26BB1C2FA5952CF0
407 changed files with 35021 additions and 23014 deletions

View File

@ -1,3 +0,0 @@
*
!.gitignore
!.gitkeep

View File

@ -1,6 +1,6 @@
/.git/
# .git
.codecov
/.github/
.github
.gitignore
.dockerignore
**/Dockerfile
@ -15,8 +15,14 @@ CONTRIBUTING.md
LICENSE
README.md
SECURITY.md
**/Dockerfile
**/*.Dockerfile
pkg/grpc/*/*.pb.*
pkg/grpc/*/*.swagger.json
.goreleaser.yaml
.artifacts/
**/.artifacts
console/.angular
console/node_modules
console/src/app/proto/generated/
console/tmp/
.vscode
build/*.Dockerfile

99
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,99 @@
name: ZITADEL CI/CD
on:
push:
tags-ignore:
- "*"
branches:
- "**"
pull_request:
merge_group:
workflow_dispatch:
permissions:
contents: write
packages: write
jobs:
core:
uses: ./.github/workflows/core.yml
with:
node_version: '18'
buf_version: 'latest'
go_version: '1.20'
console:
uses: ./.github/workflows/console.yml
with:
node_version: '18'
buf_version: 'latest'
version:
uses: ./.github/workflows/version.yml
with:
semantic_version: '19.0.2'
dry_run: true
compile:
needs: [core, console, version]
uses: ./.github/workflows/compile.yml
with:
go_version: '1.20'
core_cache_key: ${{ needs.core.outputs.cache_key }}
console_cache_key: ${{ needs.console.outputs.cache_key }}
core_cache_path: ${{ needs.core.outputs.cache_path }}
console_cache_path: ${{ needs.console.outputs.cache_path }}
version: ${{ needs.version.outputs.version }}
core-unit-test:
needs: core
uses: ./.github/workflows/core-unit-test.yml
with:
go_version: '1.20'
core_cache_key: ${{ needs.core.outputs.cache_key }}
core_cache_path: ${{ needs.core.outputs.cache_path }}
core-integration-test:
needs: core
uses: ./.github/workflows/core-integration-test.yml
with:
go_version: '1.20'
core_cache_key: ${{ needs.core.outputs.cache_key }}
core_cache_path: ${{ needs.core.outputs.cache_path }}
lint:
needs: [core, console]
uses: ./.github/workflows/lint.yml
with:
go_version: '1.20'
node_version: '18'
buf_version: 'latest'
go_lint_version: 'v1.53.2'
core_cache_key: ${{ needs.core.outputs.cache_key }}
core_cache_path: ${{ needs.core.outputs.cache_path }}
container:
needs: [compile]
uses: ./.github/workflows/container.yml
with:
image_name: 'ghcr.io/zitadel/zitadel'
e2e:
uses: ./.github/workflows/e2e.yml
needs: [container]
with:
image: ${{ needs.container.outputs.image }}-debug
release:
uses: ./.github/workflows/release.yml
needs: [version, core-unit-test, core-integration-test, lint, container, e2e]
# TODO: trigger release on workflow_dispatch if: ${{ github.event_name == 'workflow_dispatch' }}
if: ${{ needs.version.outputs.published == 'true' }}
secrets:
GCR_JSON_KEY_BASE64: ${{ secrets.GCR_JSON_KEY_BASE64 }}
with:
image: ${{ needs.container.outputs.image }}
semantic_version: '19.0.2'
image_name: 'ghcr.io/zitadel/zitadel'
google_image_name: "europe-docker.pkg.dev/zitadel-common/zitadel-repo/zitadel"

View File

@ -1,4 +1,4 @@
name: "Code scanning - action"
name: "Code Scanning"
on:
push:

99
.github/workflows/compile.yml vendored Normal file
View File

@ -0,0 +1,99 @@
name: Compile
on:
workflow_call:
inputs:
go_version:
required: true
type: string
core_cache_key:
required: true
type: string
core_cache_path:
required: true
type: string
console_cache_key:
required: true
type: string
console_cache_path:
required: true
type: string
version:
required: true
type: string
jobs:
executable:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
goos: [linux, darwin, windows]
goarch: [amd64, arm64]
steps:
-
uses: actions/checkout@v3
-
uses: actions/cache/restore@v3
timeout-minutes: 1
name: restore console
with:
path: ${{ inputs.console_cache_path }}
key: ${{ inputs.console_cache_key }}
fail-on-cache-miss: true
-
uses: actions/cache/restore@v3
timeout-minutes: 1
name: restore core
with:
path: ${{ inputs.core_cache_path }}
key: ${{ inputs.core_cache_key }}
fail-on-cache-miss: true
-
uses: actions/setup-go@v4
with:
go-version: ${{ inputs.go_version }}
-
name: compile
timeout-minutes: 5
run: |
GOOS="${{matrix.goos}}" \
GOARCH="${{matrix.goarch}}" \
VERSION="${{ inputs.version }}" \
COMMIT_SHA="${{ github.sha }}" \
make compile_pipeline
-
name: create folder
run: |
mkdir zitadel-${{ matrix.goos }}-${{ matrix.goarch }}
mv zitadel zitadel-${{ matrix.goos }}-${{ matrix.goarch }}/
cp LICENSE zitadel-${{ matrix.goos }}-${{ matrix.goarch }}/
cp README.md zitadel-${{ matrix.goos }}-${{ matrix.goarch }}/
tar -cvf zitadel-${{ matrix.goos }}-${{ matrix.goarch }}.tar zitadel-${{ matrix.goos }}-${{ matrix.goarch }}
-
uses: actions/upload-artifact@v3
with:
name: zitadel-${{ matrix.goos }}-${{ matrix.goarch }}
path: zitadel-${{ matrix.goos }}-${{ matrix.goarch }}.tar
checksums:
runs-on: ubuntu-latest
needs: executable
steps:
-
uses: actions/download-artifact@v3
with:
path: executables
-
name: move files one folder up
run: mv */*.tar . && find . -type d -empty -delete
working-directory: executables
-
run: sha256sum * > checksums.txt
working-directory: executables
-
uses: actions/upload-artifact@v3
with:
name: checksums.txt
path: executables/checksums.txt

61
.github/workflows/console.yml vendored Normal file
View File

@ -0,0 +1,61 @@
name: Build console
on:
workflow_call:
inputs:
node_version:
required: true
type: string
buf_version:
required: true
type: string
outputs:
cache_key:
value: ${{ jobs.build.outputs.cache_key }}
cache_path:
value: ${{ jobs.build.outputs.cache_path }}
env:
cache_path: console/dist/console
jobs:
build:
outputs:
cache_key: ${{ steps.cache.outputs.cache-primary-key }}
cache_path: ${{ env.cache_path }}
runs-on: ubuntu-latest
steps:
-
uses: actions/checkout@v3
-
uses: actions/cache/restore@v3
timeout-minutes: 1
id: cache
with:
key: console-${{ hashFiles('console', 'proto') }}
restore-keys: |
console-
path: ${{ env.cache_path }}
-
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
uses: bufbuild/buf-setup-action@v1
with:
github_token: ${{ github.token }}
version: ${{ inputs.buf_version }}
-
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
uses: actions/setup-node@v3
with:
node-version: ${{ inputs.node_version }}
cache: 'yarn'
cache-dependency-path: console/yarn.lock
-
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
run: make console_build
-
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v3
with:
path: ${{ env.cache_path }}
key: ${{ steps.cache.outputs.cache-primary-key }}

168
.github/workflows/container.yml vendored Normal file
View File

@ -0,0 +1,168 @@
name: Container
on:
workflow_call:
inputs:
image_name:
required: true
type: string
outputs:
image:
value: '${{ inputs.image_name }}:${{ github.sha }}'
env:
default_labels: |
org.opencontainers.image.documentation=https://zitadel.com/docs
org.opencontainers.image.vendor=CAOS AG
jobs:
build:
name: zitadel
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch: [amd64,arm64]
steps:
-
uses: actions/checkout@v3
-
name: Scratch meta
id: scratch-meta
uses: docker/metadata-action@v4
with:
images: ${{ inputs.image_name }}
labels: ${{ env.default_labels}}
tags: |
type=sha,prefix=,suffix=,format=long
-
name: Debug meta
id: debug-meta
uses: docker/metadata-action@v4
with:
images: ${{ inputs.image_name }}
labels: ${{ env.default_labels}}
tags: |
type=sha,prefix=,suffix=-debug,format=long
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
driver-opts: 'image=moby/buildkit:v0.11.6'
-
name: Login to Docker registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
-
uses: actions/download-artifact@v3
with:
path: .artifacts
name: zitadel-linux-${{ matrix.arch }}
-
name: Unpack executable
run: |
tar -xvf .artifacts/zitadel-linux-${{ matrix.arch }}.tar
mv zitadel-linux-${{ matrix.arch }}/zitadel ./zitadel
-
name: Debug
id: build-debug
uses: docker/build-push-action@v4
timeout-minutes: 3
with:
context: .
file: build/Dockerfile
target: artifact
platforms: linux/${{ matrix.arch }}
push: true
labels: ${{ steps.debug-meta.outputs.labels }}
outputs: type=image,name=${{ inputs.image_name }},push-by-digest=true,name-canonical=true,push=true
-
name: Scratch
id: build-scratch
uses: docker/build-push-action@v4
timeout-minutes: 3
with:
context: .
file: build/Dockerfile
target: final
platforms: linux/${{ matrix.arch }}
push: true
labels: ${{ steps.scratch-meta.outputs.labels }}
outputs: type=image,name=${{ inputs.image_name }},push-by-digest=true,name-canonical=true,push=true
-
name: Export debug digest
run: |
mkdir -p /tmp/digests/debug
digest="${{ steps.build-debug.outputs.digest }}"
touch "/tmp/digests/debug/${digest#sha256:}"
-
name: Export scratch digest
run: |
mkdir -p /tmp/digests/scratch
digest="${{ steps.build-scratch.outputs.digest }}"
touch "/tmp/digests/scratch/${digest#sha256:}"
-
name: Upload digest
uses: actions/upload-artifact@v3
with:
name: digests
path: /tmp/digests
if-no-files-found: error
retention-days: 1
merge:
runs-on: ubuntu-latest
needs:
- build
strategy:
fail-fast: false
matrix:
image: [scratch, debug]
include:
- image: scratch
suffix: ''
- image: debug
suffix: '-debug'
steps:
-
name: Download digests
uses: actions/download-artifact@v3
with:
name: digests
path: /tmp/digests
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
driver-opts: 'image=moby/buildkit:v0.11.6'
-
name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: '${{ inputs.image_name }}'
tags: |
type=sha,prefix=,suffix=${{ matrix.suffix }},format=long
-
name: Login to Docker registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Create manifest list and push
working-directory: /tmp/digests/${{ matrix.image }}
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ inputs.image_name }}@sha256:%s ' *)
-
name: Inspect image
run: |
docker buildx imagetools inspect ${{ inputs.image_name }}:${{ github.sha }}${{ matrix.suffix }}

View File

@ -0,0 +1,153 @@
name: Integration 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
jobs:
postgres:
runs-on: ubuntu-latest
services:
postgres:
image: postgres
ports:
- 5432:5432
env:
POSTGRES_USER: zitadel
PGUSER: zitadel
POSTGRES_DB: zitadel
POSTGRES_HOST_AUTH_METHOD: trust
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
--health-start-period 10s
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
name: restore previous results
with:
key: integration-test-postgres-${{ inputs.core_cache_key }}
restore-keys: |
integration-test-postgres-core-
path: ${{ steps.go-cache-path.outputs.GO_CACHE_PATH }}
-
name: test
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
env:
ZITADEL_MASTERKEY: MasterkeyNeedsToHave32Characters
INTEGRATION_DB_FLAVOR: postgres
run: make core_integration_test
-
name: publish coverage
uses: codecov/codecov-action@v3.1.4
with:
file: profile.cov
name: core-integration-tests-postgres
flags: core-integration-tests-postgres
-
uses: actions/cache/save@v3
name: cache results
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
with:
key: integration-test-postgres-${{ inputs.core_cache_key }}
path: ${{ steps.go-cache-path.outputs.GO_CACHE_PATH }}
# TODO: enable as soon as cockroach allows `COCKROACH_ARGS`
# cockroach:
# runs-on: ubuntu-latest
# services:
# cockroach:
# image: cockroachdb/cockroach:latest
# ports:
# - 26257:26257
# - 9090:9090
# env:
# COCKROACH_ARGS: start-single-node --insecure --http-addr :9090
# options: >-
# --health-cmd "curl http://localhost:9090/health?ready=1 || exit 1"
# --health-interval 10s
# --health-timeout 5s
# --health-retries 5
# --health-start-period 10s
# 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
# 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
# timeout-minutes: 1
# name: restore previous results
# cache: id
# with:
# key: integration-test-crdb-${{ inputs.core_cache_key }}
# restore-keys: |
# integration-test-crdb-core-
# path: ${{ steps.go-cache-path.outputs.GO_CACHE_PATH }}
# -
# name: test
# if: ${{ steps.cache.outputs.cache-hit != 'true' }}
# env:
# ZITADEL_MASTERKEY: MasterkeyNeedsToHave32Characters
# INTEGRATION_DB_FLAVOR: cockroach
# run: make core_integration_test
# -
# name: publish coverage
# uses: codecov/codecov-action@v3.1.4
# with:
# file: profile.cov
# name: core-integration-tests-cockroach
# flags: core-integration-tests-cockroach
# -
# uses: actions/cache/save@v3
# name: cache results
# if: ${{ steps.cache.outputs.cache-hit != 'true' }}
# with:
# key: integration-test-crdb-${{ inputs.core_cache_key }}
# path: ${{ steps.go-cache-path.outputs.GO_CACHE_PATH }}

67
.github/workflows/core-unit-test.yml vendored Normal file
View File

@ -0,0 +1,67 @@
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
jobs:
test:
runs-on: ubuntu-latest
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
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: 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 }}

81
.github/workflows/core.yml vendored Normal file
View File

@ -0,0 +1,81 @@
name: Build core
on:
workflow_call:
inputs:
go_version:
required: true
type: string
buf_version:
required: true
type: string
node_version:
required: true
type: string
outputs:
cache_key:
value: ${{ jobs.build.outputs.cache_key }}
cache_path:
value: ${{ jobs.build.outputs.cache_path }}
env:
cache_path: |
internal/statik/statik.go
internal/notification/statik/statik.go
internal/api/ui/login/static/resources/themes/zitadel/css/zitadel.css*
internal/api/ui/login/statik/statik.go
internal/api/assets/authz.go
internal/api/assets/router.go
openapi/v2
pkg/grpc/**/*.pb.*
jobs:
build:
runs-on: ubuntu-latest
outputs:
cache_key: ${{ steps.cache.outputs.cache-primary-key }}
cache_path: ${{ env.cache_path }}
steps:
-
uses: actions/checkout@v3
-
uses: actions/cache/restore@v3
timeout-minutes: 1
id: cache
with:
key: core-${{ hashFiles( 'go.*', 'openapi', 'cmd', 'pkg/grpc/**/*.go', 'proto', 'internal') }}
restore-keys: |
core-
path: ${{ env.cache_path }}
-
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
uses: bufbuild/buf-setup-action@v1
with:
github_token: ${{ github.token }}
version: ${{ inputs.buf_version }}
-
# node to install sass
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
uses: actions/setup-node@v3
with:
node-version: ${{ inputs.node_version }}
-
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
run: npm install -g sass
-
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
uses: actions/setup-go@v4
with:
go-version: ${{ inputs.go_version }}
-
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
run: make core_build
-
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v3
with:
key: ${{ steps.cache.outputs.cache-primary-key }}
path: ${{ env.cache_path }}

View File

@ -1,66 +1,104 @@
name: "ZITADEL e2e Tests"
on:
workflow_run:
workflows: [ZITADEL Release]
types:
- completed
workflow_dispatch:
workflow_call:
inputs:
releaseversion:
description: 'Release version to test'
image:
required: true
default: 'latest'
type: string
jobs:
test:
strategy:
fail-fast: false
matrix:
browser: [firefox, chrome]
runs-on: ubuntu-20.04
services:
# we currently use postgres because cockroach doesn't work
postgres:
image: postgres
ports:
- 5432:5432
env:
POSTGRES_USER: zitadel
PGUSER: zitadel
POSTGRES_DB: zitadel
POSTGRES_HOST_AUTH_METHOD: trust
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
--health-start-period 10s
zitadel:
image: ${{ inputs.image }}
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
options: >-
--health-cmd "zitadel ready"
--health-interval 10s
--health-timeout 5s
--health-retries 5
--health-start-period 10s
--add-host host.docker.internal:host-gateway
ports:
- 8080:8080
env:
ZITADEL_ARGS: "start-from-init --masterkeyFromEnv"
ZITADEL_MASTERKEY: MasterkeyNeedsToHave32Characters
ZITADEL_LOG_LEVEL: debug
ZITADEL_EXTERNALDOMAIN: localhost
ZITADEL_EXTERNALSECURE: "false"
ZITADEL_TLS_ENABLED: "false"
ZITADEL_DATABASE_POSTGRES_HOST: postgres
ZITADEL_DATABASE_POSTGRES_PORT: "5432"
ZITADEL_DATABASE_POSTGRES_DATABASE: zitadel
ZITADEL_DATABASE_POSTGRES_MAXOPENCONNS: "20"
ZITADEL_DATABASE_POSTGRES_MAXIDLECONNS: "10"
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: zitadel
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED: "false"
ZITADEL_LOGSTORE_ACCESS_DATABASE_ENABLED: "true"
ZITADEL_LOGSTORE_ACCESS_DATABASE_DEBOUNCE_MINFREQUENCY: 0s
ZITADEL_LOGSTORE_ACCESS_DATABASE_DEBOUNCE_MAXBULKSIZE: "0"
ZITADEL_LOGSTORE_EXECUTION_DATABASE_ENABLED: "true"
ZITADEL_LOGSTORE_EXECUTION_STDOUT_ENABLED: "false"
ZITADEL_QUOTAS_ACCESS_EXHAUSTEDCOOKIEKEY: "zitadel.quota.limiting"
ZITADEL_QUOTAS_ACCESS_EXHAUSTEDCOOKIEMAXAGE: 60s
ZITADEL_PROJECTIONS_CUSTOMIZATIONS_NOTIFICATIONSQUOTAS_REQUEUEEVERY: 1s
ZITADEL_DEFAULTINSTANCE_LOGINPOLICY_MFAINITSKIPLIFETIME: "0"
ZITADEL_SYSTEMAPIUSERS: "{\"cypress\": {\"keyData\": \"LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF6aStGRlNKTDdmNXl3NEtUd3pnTQpQMzRlUEd5Y20vTStrVDBNN1Y0Q2d4NVYzRWFESXZUUUtUTGZCYUVCNDV6YjlMdGpJWHpEdzByWFJvUzJoTzZ0CmgrQ1lRQ3ozS0N2aDA5QzBJenhaaUIySVMzSC9hVCs1Qng5RUZZK3ZuQWtaamNjYnlHNVlOUnZtdE9sbnZJZUkKSDdxWjB0RXdrUGZGNUdFWk5QSlB0bXkzVUdWN2lvZmRWUVMxeFJqNzMrYU13NXJ2SDREOElkeWlBQzNWZWtJYgpwdDBWajBTVVgzRHdLdG9nMzM3QnpUaVBrM2FYUkYwc2JGaFFvcWRKUkk4TnFnWmpDd2pxOXlmSTV0eXhZc3duCitKR3pIR2RIdlczaWRPRGxtd0V0NUsycGFzaVJJV0syT0dmcSt3MEVjbHRRSGFidXFFUGdabG1oQ2tSZE5maXgKQndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==\"}}"
runs-on: ubuntu-latest
env:
ZITADEL_IMAGE_REGISTRY: 'ghcr.io/zitadel/zitadel'
ZITADEL_IMAGE: ${{ inputs.image }}
steps:
- name: Checkout Repository
-
name: Checkout Repository
uses: actions/checkout@v3
- name: Set TAG env manual trigger
if: github.event_name == 'workflow_dispatch'
run: echo "ZITADEL_IMAGE=${ZITADEL_IMAGE_REGISTRY}:${{ github.event.inputs.releaseversion }}" >> $GITHUB_ENV
- name: get latest tag
uses: actions-ecosystem/action-get-latest-tag@v1
id: get-latest-tag
with:
semver_only: true
- name: Set TAG env on ZITADEL release
if: github.event_name == 'workflow_run'
run: echo "ZITADEL_IMAGE=${ZITADEL_IMAGE_REGISTRY}:${{ steps.get-latest-tag.outputs.tag }}" >> $GITHUB_ENV
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
-
name: Cypress run
uses: cypress-io/github-action@v5
env:
CYPRESS_BASE_URL: http://localhost:8080/ui/console
CYPRESS_WEBHOOK_HANDLER_HOST: host.docker.internal
CYPRESS_DATABASE_CONNECTION_URL: 'postgresql://zitadel@localhost:5432/zitadel'
CYPRESS_BACKEND_URL: http://localhost:8080
with:
driver: docker
install: true
- name: Test ${{ matrix.browser }}
run: docker compose run --service-ports e2e --browser ${{ matrix.browser }}
working-directory: e2e/config/host.docker.internal
- name: Ensure Artifacts Directory Exists
run: mkdir -p ./.artifacts
- name: Save ZITADEL Logs
if: always()
run: docker compose logs zitadel > ../../../.artifacts/e2e-compose-zitadel.log
working-directory: e2e/config/host.docker.internal
- name: Save Prepare Logs
if: always()
run: docker compose logs prepare > ../../../.artifacts/e2e-compose-prepare.log
working-directory: e2e/config/host.docker.internal
- name: Archive production tests ${{ matrix.browser }}
if: always()
working-directory: e2e
browser: ${{ matrix.browser }}
command: npm run e2e
config-file: cypress.config.ts
-
uses: actions/upload-artifact@v3
if: always()
with:
name: production-tests-${{ matrix.browser }}
path: |
e2e/cypress/results
e2e/cypress/videos
e2e/cypress/screenshots
.artifacts/e2e-compose-zitadel.log
.artifacts/e2e-compose-prepare.log
e2e/cypress/videos
e2e/cypress/results
retention-days: 30

74
.github/workflows/homebrew.yml vendored Normal file
View File

@ -0,0 +1,74 @@
name: ZITADEL Update Homebrew Formula
on:
release:
types: [published]
jobs:
homebrew-releaser:
runs-on: ubuntu-latest
name: homebrew-releaser
steps:
- name: Release my project to my Homebrew tap
uses: Justintime50/homebrew-releaser@v1
with:
# The name of the homebrew tap to publish your formula to as it appears on GitHub.
# Required - strings
homebrew_owner: zitadel
homebrew_tap: homebrew-tap
# The name of the folder in your homebrew tap where formula will be committed to.
# Default is shown - string
formula_folder: Formula
# The Personal Access Token (saved as a repo secret) that has `repo` permissions for the repo running the action AND Homebrew tap you want to release to.
# Required - string
github_token: ${{ secrets.CR_PAT }}
# Git author info used to commit to the homebrew tap.
# Defaults are shown - strings
commit_owner: homebrew-releaser
commit_email: homebrew-releaser@zitadel.com
# Custom dependencies in case other formulas are needed to build the current one.
# Optional - multiline string
depends_on: |
"go" => :optional
"git"
# Custom install command for your formula.
# Required - string
install: 'bin.install "zitadel"'
# Custom test command for your formula so you can run `brew test`.
# Optional - string
test: 'ystem "#{bin}/zitadel -v"'
# Allows you to set a custom download strategy. Note that you'll need
# to implement the strategy and add it to your tap repository.
# Example: https://docs.brew.sh/Formula-Cookbook#specifying-the-download-strategy-explicitly
# Optional - string
# download_strategy: CurlDownloadStrategy
# Allows you to add a custom require_relative at the top of the formula template.
# Optional - string
# custom_require: custom_download_strategy
# Adds URL and checksum targets for different OS and architecture pairs. Using this option assumes
# a tar archive exists on your GitHub repo with the following URL pattern (this cannot be customized):
# https://github.com/{GITHUB_OWNER}/{REPO_NAME}/releases/download/{TAG}/{REPO_NAME}-{VERSION}-{OPERATING_SYSTEM}-{ARCHITECTURE}.tar.gz'
# Darwin AMD pre-existing path example: https://github.com/justintime50/myrepo/releases/download/v1.2.0/myrepo-1.2.0-darwin-amd64.tar.gz
# Linux ARM pre-existing path example: https://github.com/justintime50/myrepo/releases/download/v1.2.0/myrepo-1.2.0-linux-arm64.tar.gz
# Optional - booleans
target_darwin_amd64: true
target_darwin_arm64: true
target_linux_amd64: true
target_linux_arm64: true
# Skips committing the generated formula to a homebrew tap (useful for local testing).
# Default is shown - boolean
skip_commit: true
# Logs debugging info to console.
# Default is shown - boolean
debug: true

View File

@ -1,51 +0,0 @@
name: Integration tests
on:
push:
tags-ignore:
- '**'
pull_request:
branches:
- '**'
jobs:
run:
strategy:
matrix:
db: [cockroach, postgres]
runs-on: ubuntu-20.04
env:
DOCKER_BUILDKIT: 1
INTEGRATION_DB_FLAVOR: ${{ matrix.db }}
ZITADEL_MASTERKEY: MasterkeyNeedsToHave32Characters
steps:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Source checkout
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
driver: docker
install: true
- name: Generate gRPC definitions
run: docker build -f build/grpc/Dockerfile -t zitadel-base:local .
- name: Copy gRPC definitions
run: docker build -f build/zitadel/Dockerfile . -t zitadel-go-base --target go-copy -o .
- name: Download Go modules
run: go mod download
- name: Start ${{ matrix.db }} database
run: docker compose -f internal/integration/config/docker-compose.yaml up --wait ${INTEGRATION_DB_FLAVOR}
- name: Run zitadel init and setup
run: |
go run main.go init --config internal/integration/config/zitadel.yaml --config internal/integration/config/${INTEGRATION_DB_FLAVOR}.yaml
go run main.go setup --masterkeyFromEnv --config internal/integration/config/zitadel.yaml --config internal/integration/config/${INTEGRATION_DB_FLAVOR}.yaml
- name: Run integration tests
run: go test -tags=integration -race -p 1 -v -coverprofile=profile.cov -coverpkg=./internal/...,./cmd/... ./internal/integration ./internal/api/grpc/...
- name: Publish go coverage
uses: codecov/codecov-action@v3.1.0
with:
file: profile.cov
name: integration-tests

104
.github/workflows/lint.yml vendored Normal file
View File

@ -0,0 +1,104 @@
name: Lint
on:
workflow_call:
inputs:
node_version:
required: true
type: string
go_version:
required: true
type: string
buf_version:
required: true
type: string
go_lint_version:
required: true
type: string
core_cache_key:
required: true
type: string
core_cache_path:
required: true
type: string
jobs:
lint-skip:
name: lint skip
runs-on: ubuntu-latest
if: ${{ github.event_name != 'pull_request' }}
steps:
- name: Lint skip
run: |
echo "Linting outside of pull requests is skipped"
api:
name: api
runs-on: ubuntu-latest
continue-on-error: true
if: ${{ github.event_name == 'pull_request' }}
steps:
-
uses: actions/checkout@v3
-
uses: bufbuild/buf-setup-action@v1
with:
version: ${{ inputs.buf_version }}
github_token: ${{ secrets.GITHUB_TOKEN }}
-
name: lint
uses: bufbuild/buf-lint-action@v1
-
uses: bufbuild/buf-breaking-action@v1
with:
against: "https://github.com/${{ github.repository }}.git#branch=main"
console:
if: ${{ github.event_name == 'pull_request' }}
name: console
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v3
-
uses: actions/setup-node@v3
with:
node-version: ${{ inputs.node_version }}
cache: 'yarn'
cache-dependency-path: console/yarn.lock
-
run: cd console && yarn install
-
name: lint
run: make console_lint
core:
name: core
runs-on: ubuntu-latest
if: ${{ github.event_name == 'pull_request' }}
steps:
-
name: Checkout
uses: actions/checkout@v3
-
uses: actions/setup-go@v4
with:
go-version: ${{ github.event.inputs.go_version }}
-
uses: actions/cache/restore@v3
timeout-minutes: 1
name: restore core
with:
path: ${{ inputs.core_cache_path }}
key: ${{ inputs.core_cache_key }}
fail-on-cache-miss: true
-
uses: golangci/golangci-lint-action@v3
with:
version: ${{ inputs.go_lint_version }}
github-token: ${{ github.token }}
only-new-issues: true
skip-pkg-cache: true
skip-build-cache: true

75
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,75 @@
name: Release
on:
workflow_call:
inputs:
semantic_version:
required: true
type: string
image:
required: true
type: string
image_name:
required: true
type: string
google_image_name:
required: true
type: string
secrets:
GCR_JSON_KEY_BASE64:
description: 'base64 endcrypted key to connect to Google'
required: true
jobs:
version:
uses: ./.github/workflows/version.yml
with:
semantic_version: ${{ inputs.semantic_version }}
dry_run: true
docker:
runs-on: ubuntu-22.04
needs: [ version ]
steps:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
-
name: Login to Docker registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Login to Google Artifact Registry
uses: docker/login-action@v2
with:
registry: europe-docker.pkg.dev
username: _json_key_base64
password: ${{ secrets.GCR_JSON_KEY_BASE64 }}
-
name: Publish ${{ needs.version.outputs.version }}
run: |
docker buildx imagetools create \
--tag ${{ inputs.image_name }}:${{ needs.version.outputs.version }} \
${{ inputs.image }}
docker buildx imagetools create \
--tag ${{ inputs.image_name }}:${{ needs.version.outputs.version }}-debug \
${{ inputs.image }}-debug
docker buildx imagetools create \
--tag ${{ inputs.google_image_name }}:${{ needs.version.outputs.version }} \
${{ inputs.image }}
-
name: Publish latest
if: ${{ github.ref_name == 'main' }}
run: |
docker buildx imagetools create \
--tag ${{ inputs.image_name }}:latest \
${{ inputs.image }}
docker buildx imagetools create \
--tag ${{ inputs.image_name }}:latest-debug \
${{ inputs.image }}-debug

View File

@ -1,75 +0,0 @@
name: ZITADEL PR
on:
pull_request:
paths-ignore:
- 'docs/**'
- 'guides/**'
- '**.md'
- 'release-channels.yaml'
jobs:
Build-ZITADEL:
runs-on: ubuntu-20.04
env:
DOCKER_BUILDKIT: 1
steps:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Source checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
driver: docker
install: true
- name: Install GoReleaser
uses: goreleaser/goreleaser-action@v3
with:
install-only: true
version: v1.10.3
- name: Build and Unit Test
run: GOOS="linux" GOARCH="amd64" goreleaser build --id prod --snapshot --single-target --rm-dist --output .artifacts/zitadel/zitadel
- name: linting
uses: golangci/golangci-lint-action@v3
with:
version: v1.52
only-new-issues: true
skip-pkg-cache: true
- name: Publish go coverage
uses: codecov/codecov-action@v3.1.0
with:
file: .artifacts/codecov/profile.cov
name: unit-tests
# As goreleaser doesn't build a dockerfile in snapshot mode, we have to build it here
- name: Build Docker Image
run: docker build -t zitadel:pr --file build/Dockerfile .artifacts/zitadel
- name: Run E2E Tests
run: docker compose run --service-ports e2e --browser chrome
working-directory: e2e/config/host.docker.internal
env:
ZITADEL_IMAGE: zitadel:pr
- name: Save ZITADEL Logs
if: always()
run: docker compose logs zitadel > ../../../.artifacts/e2e-compose-zitadel.log
working-directory: e2e/config/host.docker.internal
- name: Save Prepare Logs
if: always()
run: docker compose logs prepare > ../../../.artifacts/e2e-compose-prepare.log
working-directory: e2e/config/host.docker.internal
- name: Archive Test Results
if: always()
uses: actions/upload-artifact@v3
with:
name: pull-request-tests
path: |
e2e/cypress/results
e2e/cypress/videos
e2e/cypress/screenshots
.artifacts/e2e-compose-zitadel.log
.artifacts/e2e-compose-prepare.log
retention-days: 30

View File

@ -1,21 +0,0 @@
# ATTENTION: Although this workflow doesn't do much, it is still important.
# It is complementary to the workflow in the file test-code.yml.
# It enables to exclude files for the workflow and still mark the Test job as required without having pending PRs.
# GitHub recommends this solution here:
# https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks
name: ZITADEL PR
on:
pull_request:
paths:
- 'docs/**'
- 'guides/**'
- '**.md'
- 'release-channels.yaml'
jobs:
Build-ZITADEL:
runs-on: ubuntu-20.04
steps:
- run: 'echo "No tests for docs are implemented, yet"'

45
.github/workflows/version.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Version
on:
workflow_call:
inputs:
semantic_version:
required: true
type: string
dry_run:
required: true
type: boolean
outputs:
version:
value: ${{ jobs.generate.outputs.version }}
published:
value: ${{jobs.generate.outputs.published }}
jobs:
generate:
runs-on: ubuntu-22.04
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
outputs:
version: ${{ steps.output.outputs.VERSION }}
published: ${{ steps.semantic.outputs.new_release_published }}
steps:
-
name: Source checkout
uses: actions/checkout@v3
-
name: Semantic Release
uses: cycjimmy/semantic-release-action@v3
id: semantic
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
dry_run: ${{ inputs.dry_run }}
semantic_version: ${{ inputs.semantic_version }}
extra_plugins: |
@semantic-release/exec@6.0.3
-
name: output
id: output
run:
if [[ ! -z "${{ steps.semantic.outputs.new_release_version }}" ]]; then echo "VERSION=v${{ steps.semantic.outputs.new_release_version }}" >> "$GITHUB_OUTPUT"; else echo "VERSION=" >> "$GITHUB_OUTPUT";fi

View File

@ -1,88 +0,0 @@
name: ZITADEL Release
on:
push:
tags-ignore:
- "*"
workflow_dispatch:
permissions:
contents: write
packages: write
jobs:
Build:
runs-on: ubuntu-20.04
env:
DOCKER_BUILDKIT: 1
steps:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Source checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Fetch all tags
run: git fetch --force --tags
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
driver: docker
install: true
- name: Tag
id: semantic
uses: cycjimmy/semantic-release-action@v2
with:
dry_run: false
semantic_version: 19.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: GitHub Container Registry Login
if: steps.semantic.outputs.new_release_published == 'true' && github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: tibdex/github-app-token@v1
id: generate-token
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Google Artifact Registry Login
if: steps.semantic.outputs.new_release_published == 'true' && github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
uses: docker/login-action@v2
with:
registry: europe-docker.pkg.dev
username: _json_key_base64
password: ${{ secrets.GCR_JSON_KEY_BASE64 }}
- uses: goreleaser/goreleaser-action@v3
name: Publish ZITADEL
if: steps.semantic.outputs.new_release_published == 'true' && github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
with:
distribution: goreleaser
version: v1.11.0
args: release --timeout 50m
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_TOKEN_TAP: ${{ steps.generate-token.outputs.token }}
RELEASE_VERSION: ${{ steps.semantic.outputs.release-version }} # I think this line is not needed. Nevertheless, it's explicit
DISCORD_WEBHOOK_ID: "976058224484687932"
DISCORD_WEBHOOK_TOKEN: "${{ secrets.DISCORD_WEBHOOK_TOKEN }}"
- name: Publish go coverage
uses: codecov/codecov-action@v3.1.0
with:
file: .artifacts/codecov/profile.cov
name: go-codecov
- name: Bump Chart Version
uses: peter-evans/repository-dispatch@v2
if: steps.semantic.outputs.new_release_published == 'true' && github.ref == 'refs/heads/next'
with:
token: ${{ steps.generate-token.outputs.token }}
repository: zitadel/zitadel-charts
event-type: zitadel-released
client-payload: '{"semanticoutputs": "${{ steps.semantic.outputs }}"}'

7
.gitignore vendored
View File

@ -55,10 +55,17 @@ openapi/**/*.json
/internal/api/assets/authz.go
/internal/api/assets/router.go
/internal/api/ui/console/static/*
!/internal/api/ui/console/static/gitkeep
docs/docs/apis/auth
docs/docs/apis/admin
docs/docs/apis/mgmt
docs/docs/apis/system
docs/docs/apis/proto
**/.sass-cache
/internal/api/ui/login/static/resources/themes/zitadel/css/zitadel.css
/internal/api/ui/login/static/resources/themes/zitadel/css/zitadel.css.map
zitadel
zitadel-*-*
# local
build/local/*.env

View File

@ -6,7 +6,7 @@ issues:
max-same-issues: 0
run:
concurrency: 2
concurrency: 4
timeout: 10m
go: '1.19'
skip-dirs:

View File

@ -1,126 +0,0 @@
project_name: zitadel
release:
github:
owner: zitadel
name: zitadel
draft: false
# If set to auto, will mark the release as not ready for production
# in case there is an indicator for this in the tag e.g. v1.0.0-rc1
# If set to true, will mark the release as not ready for production.
# Default is false.
prerelease: auto
before:
hooks:
# this file would invalidate go source caches
- sh -c "rm openapi/statik/statik.go || true"
- docker build -f build/grpc/Dockerfile -t zitadel-base:local .
- docker build -f build/zitadel/Dockerfile . -t zitadel-go-test --target go-codecov -o .artifacts/codecov
- docker build -f build/zitadel/Dockerfile . -t zitadel-go-base --target go-copy -o .artifacts/grpc/go-client
- sh -c "find pkg/grpc -name '*.pb*.go' -delete"
- sh -c "cp -r .artifacts/grpc/go-client/* ."
- docker build -f build/console/Dockerfile . -t zitadel-npm-console --target angular-export -o .artifacts/console
- sh -c "cp -r .artifacts/console/* internal/api/ui/console/static/"
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
goarch:
- amd64
- arm64
ldflags: -s -w -X github.com/zitadel/zitadel/cmd/build.version={{.Version}} -X github.com/zitadel/zitadel/cmd/build.commit={{.Commit}} -X github.com/zitadel/zitadel/cmd/build.date={{.Date}}
dist: .artifacts/goreleaser
dockers:
- image_templates:
- ghcr.io/zitadel/zitadel:{{ .Tag }}-amd64
- europe-docker.pkg.dev/zitadel-common/zitadel-repo/zitadel:{{ .Tag }}-amd64
goarch: amd64
use: buildx
dockerfile: build/Dockerfile
build_flag_templates:
- "--platform=linux/amd64"
- image_templates:
- ghcr.io/zitadel/zitadel:{{ .Tag }}-arm64
- ghcr.io/zitadel/zitadel:{{ .ShortCommit }}-arm64
goarch: arm64
use: buildx
dockerfile: build/Dockerfile
build_flag_templates:
- "--platform=linux/arm64"
docker_manifests:
- id: zitadel-latest
name_template: ghcr.io/zitadel/zitadel:latest
image_templates:
- ghcr.io/zitadel/zitadel:{{ .Tag }}-amd64
- ghcr.io/zitadel/zitadel:{{ .Tag }}-arm64
# Skips can and shall be set for individual manifests same as in dockers
skip_push: auto
- id: zitadel-Tag
name_template: ghcr.io/zitadel/zitadel:{{ .Tag }}
image_templates:
- ghcr.io/zitadel/zitadel:{{ .Tag }}-amd64
- ghcr.io/zitadel/zitadel:{{ .Tag }}-arm64
archives:
- name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
format_overrides:
- goos: windows
format: zip
files:
- README.md
- LICENSE
gomod:
proxy: false
checksum:
name_template: "checksums.txt"
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
brews:
- tap:
owner: zitadel
name: homebrew-tap
token: "{{ .Env.GORELEASER_TOKEN_TAP }}"
folder: Formula
goarm: "7"
homepage: https://zitadel.com
description: Open source identity solution built for the container and cloud era
license: Apache 2.0
test: |
system "#{bin}/zitadel -v"
dependencies:
- name: go
type: optional
- name: git
install: |-
bin.install "zitadel"
# If set to auto, the release will not be uploaded to the homebrew tap
# in case there is an indicator for prerelease in the tag e.g. v1.0.0-rc1
# Default is false.
skip_upload: auto
announce:
discord:
enabled: true
message_template: "ZITADEL {{ .Tag }} is ready! Check the notes: https://github.com/zitadel/zitadel/releases/tag/{{ .Tag }}"

View File

@ -1,10 +1,12 @@
module.exports = {
branches: [
{ name: 'main' },
{ name: 'next' },
{ name: 'rc', prerelease: true },
],
plugins: [
"@semantic-release/commit-analyzer"
]
branches: [
{ name: "main" },
{ name: "next" },
{ name: "rc", prerelease: true },
],
plugins: [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/github"
],
};

View File

@ -34,26 +34,30 @@ Follow [@zitadel](https://twitter.com/zitadel) on twitter
We strongly recommend to [talk to us](https://zitadel.com/contact) before you start contributing to streamline our and your work.
We accept contributions through pull requests. You need a github account for that. If you are unfamiliar with git have a look at Github's documentation on [creating forks](https://help.github.com/articles/fork-a-repo) and [creating pull requests](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork). Please draft the pull request as soon as possible. Go through the following checklist before you submit the final pull request:
We accept contributions through pull requests.
You need a github account for that.
If you are unfamiliar with git have a look at Github's documentation on [creating forks](https://help.github.com/articles/fork-a-repo) and [creating pull requests](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork).
Please draft the pull request as soon as possible.
Go through the following checklist before you submit the final pull request:
### Submit a pull request (PR)
1. [Fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) the [zitadel/zitadel](https://github.com/zitadel/zitadel) repository on GitHub
2. On your fork, commit your changes to a new branch
`git checkout -b my-fix-branch main`
`git checkout -b my-fix-branch main`
3. Make your changes following the [guidelines](#contribute) in this guide. Make sure that all tests pass.
4. Commit the changes on the new branch
`git commit --all`
`git commit --all`
5. [Merge](https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging) the latest commit of the `main`-branch
6. Push the changes to your branch on Github
`git push origin my-fix-branch`
`git push origin my-fix-branch`
7. Use [Semantic Release commit messages](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#type) to simplify creation of release notes. In the title of the pull request [correct tagging](#commit-messages) is required and will be requested by the reviewers.
@ -61,7 +65,8 @@ We accept contributions through pull requests. You need a github account for tha
### Review a pull request
The reviewers will provide you feedback and approve your changes as soon as they are satisfied. If we ask you for changes in the code, you can follow the [GitHub Guide](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/incorporating-feedback-in-your-pull-request) to incorporate feedback in your pull request.
The reviewers will provide you feedback and approve your changes as soon as they are satisfied.
If we ask you for changes in the code, you can follow the [GitHub Guide](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/incorporating-feedback-in-your-pull-request) to incorporate feedback in your pull request.
<!-- TODO: how to do this via git -->
<!-- TODO: change commit message via git -->
@ -88,6 +93,16 @@ This is optional to indicate which component is affected. In doubt, leave blank
Provide a brief description of the change.
### Quality assurance
Please make sure you cover your changes with tests before marking a Pull Request as ready for review:
- [ ] Integration tests against the gRPC server ensure that one or multiple API calls that belong together return the expected results.
- [ ] Integration tests against the gRPC server ensure that probable good and bad read and write permissions are tested.
- [ ] Integration tests against the gRPC server ensure that the API is easily usable despite eventual consistency.
- [ ] Integration tests against the gRPC server ensure that all probable login and registration flows are covered."
- [ ] Integration tests ensure that certain commands send expected notifications.
## Contribute
The code consists of the following parts:
@ -111,7 +126,7 @@ We add the label "good first issue" for problems we think are a good starting po
By executing the commands from this section, you run everything you need to develop the ZITADEL backend locally.
Using [Docker Compose](https://docs.docker.com/compose/), you run a [CockroachDB](https://www.cockroachlabs.com/docs/stable/start-a-local-cluster-in-docker-mac.html) on your local machine.
With [goreleaser](https://opencollective.com/goreleaser), you build a debuggable ZITADEL binary and run it using [delve](https://github.com/go-delve/delve).
With [make](https://www.gnu.org/software/make/), you build a debuggable ZITADEL binary and run it using [delve](https://github.com/go-delve/delve).
Then, you test your changes via the console your binary is serving at http://<span because="breaks the link"></span>localhost:8080 and by verifying the database.
Once you are happy with your changes, you run end-to-end tests and tear everything down.
@ -120,8 +135,7 @@ ZITADEL uses [golangci-lint](https://golangci-lint.run) for code quality checks.
The commands in this section are tested against the following software versions:
- [Docker version 20.10.17](https://docs.docker.com/engine/install/)
- [Goreleaser version v1.8.3](https://goreleaser.com/install/)
- [Go version 1.19](https://go.dev/doc/install)
- [Go version 1.20](https://go.dev/doc/install)
- [Delve 1.9.1](https://github.com/go-delve/delve/tree/v1.9.1/Documentation/installation)
Make some changes to the source code, then run the database locally.
@ -134,16 +148,15 @@ docker compose --file ./e2e/docker-compose.yaml up --detach db
Build the binary. This takes some minutes, but you can speed up rebuilds.
```bash
# You just need goreleasers build part (--snapshot) and you just need to target your current platform (--single-target)
goreleaser build --id dev --snapshot --single-target --rm-dist --output .artifacts/zitadel/zitadel
make compile
```
> Note: With this command, several steps are executed.
> For speeding up rebuilds, you can reexecute only specific steps you think are necessary based on your changes.
> Generating gRPC stubs: `DOCKER_BUILDKIT=1 docker build -f build/zitadel/Dockerfile . --target go-copy -o .`
> Running unit tests: `DOCKER_BUILDKIT=1 docker build -f build/zitadel/Dockerfile . --target go-codecov`
> Generating the console: `DOCKER_BUILDKIT=1 docker build -f build/console/Dockerfile . --target angular-export -o internal/api/ui/console/static/`
> Build the binary: `goreleaser build --id dev --snapshot --single-target --rm-dist --output .artifacts/zitadel/zitadel --skip-before`
> Generating gRPC stubs: `make core_api`
> Running unit tests: `make core_unit_test`
> Generating the console: `make console_build console_move`
> Build the binary: `make compile`
You can now run and debug the binary in .artifacts/zitadel/zitadel using your favourite IDE, for example GoLand.
You can test if ZITADEL does what you expect by using the UI at http://localhost:8080/ui/console.
@ -151,19 +164,20 @@ Also, you can verify the data by running `cockroach sql --database zitadel --ins
As soon as you are ready to battle test your changes, run the end-to-end tests.
#### Running the tests with docker
#### Running the tests
Running the tests with docker doesn't require you to take care of other dependencies than docker and goreleaser.
Running the tests with docker doesn't require you to take care of other dependencies than docker and make.
```bash
# Build the production binary (unit tests are executed, too)
goreleaser build --id prod --snapshot --single-target --rm-dist --output .artifacts/zitadel/zitadel
make core_build console_build
GOOS=linux make compile_pipeline
# Pack the binary into a docker image
DOCKER_BUILDKIT=1 docker build --file build/Dockerfile .artifacts/zitadel -t zitadel:local
DOCKER_BUILDKIT=1 docker build --file build/Dockerfile . -t zitadel:local
# If you made changes in the e2e directory, make sure you reformat the files
(cd ./e2e && npm run lint:fix)
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
@ -206,9 +220,7 @@ In order to run the integrations tests for the gRPC API, PostgreSQL and Cockroac
```bash
export INTEGRATION_DB_FLAVOR="cockroach" ZITADEL_MASTERKEY="MasterkeyNeedsToHave32Characters"
docker compose -f internal/integration/config/docker-compose.yaml up --wait ${INTEGRATION_DB_FLAVOR}
go run main.go init --config internal/integration/config/zitadel.yaml --config internal/integration/config/${INTEGRATION_DB_FLAVOR}.yaml
go run main.go setup --masterkeyFromEnv --config internal/integration/config/zitadel.yaml --config internal/integration/config/${INTEGRATION_DB_FLAVOR}.yaml
go test -count 1 -tags=integration -race -p 1 ./internal/integration ./internal/api/grpc/...
make core_integration_test
docker compose -f internal/integration/config/docker-compose.yaml down
```
@ -221,7 +233,7 @@ Using [Docker Compose](https://docs.docker.com/compose/), you run [CockroachDB](
You use the ZITADEL container as backend for your console.
The console is run in your [Node](https://nodejs.org/en/about/) environment using [a local development server for Angular](https://angular.io/cli/serve#ng-serve), so you have fast feedback about your changes.
We use angular-eslint/Prettier for linting/formatting, so please run `npm run lint:fix` before committing. (VSCode users, check out [this ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) and [this Prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) to fix lint and formatting issues in development)
We use angular-eslint/Prettier for linting/formatting, so please run `yarn lint:fix` before committing. (VSCode users, check out [this ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) and [this Prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) to fix lint and formatting issues in development)
Once you are happy with your changes, you run end-to-end tests and tear everything down.
@ -269,16 +281,16 @@ You can run the local console development server now.
```bash
# Install npm dependencies
npm install
yarn install
# Generate source files from Protos
npm run generate
yarn generate
# Start the server
npm start
yarn start
# If you don't want to develop against http://localhost:8080, you can use another environment
ENVIRONMENT_JSON_URL=https://my-cloud-instance-abcdef.zitadel.cloud/ui/console/assets/environment.json npm start
ENVIRONMENT_JSON_URL=https://my-cloud-instance-abcdef.zitadel.cloud/ui/console/assets/environment.json yarn start
```
Navigate to http://localhost:4200/.
@ -288,7 +300,7 @@ Open another shell.
```bash
# Reformat your console code
npm run lint:fix
yarn lint:fix
# Change to the e2e directory
cd .. && cd e2e/
@ -335,13 +347,13 @@ Please refer to the [README](./docs/README.md) for more information and local te
### Style guide
- **Code with variables**: Make sure that code snippets can be used by setting environment variables, instead of manually replacing a placeholder.
- **Embedded files**: When embedding mdx files, make sure the template ist prefixed by "_" (lowdash). The content will be rendered inside the parent page, but is not accessible individually (eg, by search).
- **Embedded files**: When embedding mdx files, make sure the template ist prefixed by "\_" (lowdash). The content will be rendered inside the parent page, but is not accessible individually (eg, by search).
- **Don't repeat yourself**: When using the same content in multiple places, save and manage the content as separate file and make use of embedded files to import it into other docs pages.
- **Embedded code**: You can embed code snippets from a repository. See the [plugin](https://github.com/saucelabs/docusaurus-theme-github-codeblock#usage) for usage.
Following the [Google style guide](https://developers.google.com/style) is highly recommended. Its clear and concise guidelines ensure consistency and effective communication within the wider developer community.
The style guide covers a lot of material, so their [highlights](https://developers.google.com/style/highlights) page provides an overview of its most important points. Some of the points stated in the highlights that we care about most are given below:
The style guide covers a lot of material, so their [highlights](https://developers.google.com/style/highlights) page provides an overview of its most important points. Some of the points stated in the highlights that we care about most are given below:
- Be conversational and friendly without being frivolous.
- Use sentence case for document titles and section headings.
@ -414,7 +426,6 @@ Priority shows you the priority the ZITADEL team has given this issue. In genera
- **🏕 Medium**: After all the high issues are done these will be next.
- **🏝 Low**: This is low in priority and will probably not be implemented in the next time or just if someone has some time in between.
#### Complexity
This should give you an indication how complex the issue is. It's not about the hours or effort it takes.
@ -448,4 +459,3 @@ The language shows you in which programming language the affected part is writte
- **lang: angular**
- **lang: go**
- **lang: javascript**

112
Makefile Normal file
View File

@ -0,0 +1,112 @@
go_bin := "$$(go env GOPATH)/bin"
gen_authopt_path := "$(go_bin)/protoc-gen-authoption"
gen_zitadel_path := "$(go_bin)/protoc-gen-zitadel"
now := $(shell date --rfc-3339=seconds | sed 's/ /T/')
VERSION ?= development
COMMIT_SHA ?= $(shell git rev-parse HEAD)
.PHONY: compile
compile: core_build console_build compile_pipeline
.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)' "
chmod +x zitadel
.PHONY: core_dependencies
core_dependencies:
go mod download
.PHONY: core_static
core_static:
go install github.com/rakyll/statik@v0.1.7
go generate internal/api/ui/login/statik/generate.go
go generate internal/api/ui/login/static/resources/generate.go
go generate internal/notification/statik/generate.go
go generate internal/statik/generate.go
.PHONY: core_assets
core_assets:
mkdir -p docs/apis/assets
go run internal/api/assets/generator/asset_generator.go -directory=internal/api/assets/generator/ -assets=docs/apis/assets/assets.md
.PHONY: core_api_generator
core_api_generator:
ifeq (,$(wildcard $(gen_authopt_path)))
go install internal/protoc/protoc-gen-authoption/main.go \
&& mv $$(go env GOPATH)/bin/main $(gen_authopt_path)
endif
ifeq (,$(wildcard $(gen_zitadel_path)))
go install internal/protoc/protoc-gen-zitadel/main.go \
&& mv $$(go env GOPATH)/bin/main $(gen_zitadel_path)
endif
.PHONY: core_grpc_dependencies
core_grpc_dependencies:
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.30
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v2.15.2
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@v2.15.2
go install github.com/envoyproxy/protoc-gen-validate@v0.10.1
.PHONY: core_api
core_api: core_api_generator core_grpc_dependencies
buf generate
mkdir -p pkg/grpc
cp -r .artifacts/grpc/github.com/zitadel/zitadel/pkg/grpc/* pkg/grpc/
mkdir -p openapi/v2/zitadel
cp -r .artifacts/grpc/zitadel/ openapi/v2/zitadel
.PHONY: core_build
core_build: core_dependencies core_api core_static core_assets
.PHONY: console_move
console_move:
cp -r console/dist/console/* internal/api/ui/console/static
.PHONY: console_dependencies
console_dependencies:
cd console && \
yarn install --immutable
.PHONY: console_client
console_client:
cd console && \
yarn generate
.PHONY: console_build
console_build: console_dependencies console_client
cd console && \
yarn build
.PHONY: clean
clean:
$(RM) .artifacts/grpc
$(RM) $(gen_authopt_path)
$(RM) $(gen_zitadel_path)
.PHONY: core_unit_test
core_unit_test:
go test -race -coverprofile=profile.cov ./...
.PHONY: core_integration_test
core_integration_test:
go build -o zitadel main.go
./zitadel init --config internal/integration/config/zitadel.yaml --config internal/integration/config/${INTEGRATION_DB_FLAVOR}.yaml
./zitadel setup --masterkeyFromEnv --config internal/integration/config/zitadel.yaml --config internal/integration/config/${INTEGRATION_DB_FLAVOR}.yaml
$(RM) zitadel
go test -tags=integration -race -p 1 -v -coverprofile=profile.cov -coverpkg=./internal/...,./cmd/... ./internal/integration ./internal/api/grpc/... ./internal/notification/handlers/... ./internal/api/oidc/...
.PHONY: console_lint
console_lint:
cd console && \
yarn lint
.PHONE: core_lint
core_lint:
golangci-lint run \
--timeout 10m \
--config ./.golangci.yaml \
--out-format=github-actions \
--concurrency=$$(getconf _NPROCESSORS_ONLN)

21
buf.gen.yaml Normal file
View File

@ -0,0 +1,21 @@
version: v1
plugins:
- plugin: go
out: .artifacts/grpc
- plugin: go-grpc
out: .artifacts/grpc
- plugin: grpc-gateway
out: .artifacts/grpc
opt:
- allow_delete_body=true
- plugin: openapiv2
out: .artifacts/grpc
opt:
- allow_delete_body=true
- plugin: validate
out: .artifacts/grpc
opt: lang=go
- plugin: authoption
out: .artifacts/grpc
- plugin: zitadel
out: .artifacts/grpc

View File

@ -1,20 +1,32 @@
#######################
## Final Production Image
#######################
FROM alpine:3 as artifact
COPY zitadel /app/zitadel
RUN adduser -D zitadel && \
chown zitadel /app/zitadel && \
chmod +x /app/zitadel
FROM --platform=$TARGETPLATFORM debian:latest as artifact
ENV ZITADEL_ARGS=
ARG TARGETPLATFORM
RUN apt-get update && apt-get install ca-certificates -y
COPY build/entrypoint.sh /app/entrypoint.sh
COPY zitadel /app/zitadel
RUN useradd -s "" --home / zitadel && \
chown zitadel /app/zitadel && \
chmod +x /app/zitadel && \
chown zitadel /app/entrypoint.sh && \
chmod +x /app/entrypoint.sh
WORKDIR /app
ENV PATH="/app:${PATH}"
USER zitadel
ENTRYPOINT ["/app/entrypoint.sh"]
FROM --platform=$TARGETPLATFORM scratch as final
ARG TARGETPLATFORM
#######################
## Scratch Image
#######################
FROM scratch as final
COPY --from=artifact /etc/passwd /etc/passwd
COPY --from=artifact /etc/ssl/certs /etc/ssl/certs
COPY --from=artifact /app /
USER zitadel
HEALTHCHECK NONE
ENTRYPOINT ["/zitadel"]
COPY --from=artifact /app/zitadel /app/zitadel
HEALTHCHECK NONE
USER zitadel
ENTRYPOINT ["/app/zitadel"]

View File

@ -1,30 +0,0 @@
ARG NODE_VERSION=18
#######################
## With this step we prepare all node_modules, this helps caching the build
## Speed up this step by mounting your local node_modules directory
## We also copy and generate the source code
#######################
FROM node:${NODE_VERSION} as npm-base
WORKDIR /console
# Dependencies
COPY console/package.json console/package-lock.json ./
RUN npm ci
# Sources
COPY console .
COPY proto/ /proto/
#######################
## angular lint workspace and prod build
#######################
FROM npm-base as angular-build
RUN npm run lint
RUN npm run build
#######################
## Only Copy Assets
#######################
FROM scratch as angular-export
COPY --from=angular-build /console/dist/console .

17
build/entrypoint.sh Executable file
View File

@ -0,0 +1,17 @@
#!/bin/bash
case $@ in
sh*)
${@:3}
;;
bash*)
${@:5}
;;
*)
if [[ ! -z "$@" ]]
then
ZITADEL_ARGS="$@"
fi
/app/zitadel ${ZITADEL_ARGS}
;;
esac

View File

@ -1,47 +0,0 @@
#ARG BUILDARCH=x86_64
#######################
## These steps set platform / arch type specific variables
#######################
FROM alpine:3 AS arm64-base
ENV PROTOC_ARCH aarch_64
FROM alpine:3 AS amd64-base
ENV PROTOC_ARCH x86_64
#######################
## This step sets up the folder structure,
## initalices go mods,
## downloads the protofiles,
## protoc and protoc-gen-grpc-web for later use
#######################
FROM ${BUILDARCH}-base
ARG PROTOC_VERSION=22.3
ARG PROTOC_ZIP=protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip
ARG GRPC_WEB_VERSION=1.3.0
ARG GATEWAY_VERSION=2.15.2
ARG VALIDATOR_VERSION=0.10.1
# no arm specific version available and x86 works fine at the moment:
ARG GRPC_WEB=protoc-gen-grpc-web-${GRPC_WEB_VERSION}-linux-x86_64
RUN apk add tar curl
WORKDIR /proto
#protoc
RUN apk add tar curl \
&& curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/$PROTOC_ZIP \
&& unzip -o $PROTOC_ZIP -d /usr/local bin/protoc \
&& unzip -o $PROTOC_ZIP -d /proto include/* \
&& rm -f $PROTOC_ZIP \
&& curl -OL https://github.com/grpc/grpc-web/releases/download/${GRPC_WEB_VERSION}/${GRPC_WEB} \
&& mv ${GRPC_WEB} /usr/local/bin/protoc-gen-grpc-web \
&& chmod +x /usr/local/bin/protoc-gen-grpc-web \
&& curl https://raw.githubusercontent.com/envoyproxy/protoc-gen-validate/v${VALIDATOR_VERSION}/validate/validate.proto --create-dirs -o include/validate/validate.proto \
&& curl https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/v${GATEWAY_VERSION}/protoc-gen-openapiv2/options/annotations.proto --create-dirs -o include/protoc-gen-openapiv2/options/annotations.proto \
&& curl https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/v${GATEWAY_VERSION}/protoc-gen-openapiv2/options/openapiv2.proto --create-dirs -o include/protoc-gen-openapiv2/options/openapiv2.proto \
&& curl https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto --create-dirs -o include/google/api/annotations.proto \
&& curl https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto --create-dirs -o include/google/api/http.proto \
&& curl https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/field_behavior.proto --create-dirs -o include/google/api/field_behavior.proto
#zitadel protos
COPY proto/ include/.

302
build/workflow.Dockerfile Normal file
View File

@ -0,0 +1,302 @@
# ##############################################################################
# core
# ##############################################################################
# #######################################
# download dependencies
# #######################################
FROM golang:buster AS core-deps
WORKDIR /go/src/github.com/zitadel/zitadel
COPY go.mod .
COPY go.sum .
RUN go mod download
# #######################################
# compile custom protoc plugins
# #######################################
FROM golang:buster AS core-api-generator
WORKDIR /go/src/github.com/zitadel/zitadel
COPY go.mod .
COPY go.sum .
COPY internal/protoc internal/protoc
COPY pkg/grpc/protoc/v2 pkg/grpc/protoc/v2
RUN go install internal/protoc/protoc-gen-authoption/main.go \
&& mv $(go env GOPATH)/bin/main $(go env GOPATH)/bin/protoc-gen-authoption \
&& go install internal/protoc/protoc-gen-zitadel/main.go \
&& mv $(go env GOPATH)/bin/main $(go env GOPATH)/bin/protoc-gen-zitadel
# #######################################
# build backend stub
# #######################################
FROM golang:buster AS core-api
WORKDIR /go/src/github.com/zitadel/zitadel
COPY go.mod .
COPY go.sum .
COPY proto proto
COPY buf.*.yaml .
COPY Makefile Makefile
COPY --from=core-api-generator /go/bin /usr/local/bin
RUN make grpc
# #######################################
# generate code for login ui
# #######################################
FROM golang:buster AS core-login
WORKDIR /go/src/github.com/zitadel/zitadel
COPY Makefile Makefile
COPY internal/api/ui/login/static internal/api/ui/login/static
COPY internal/api/ui/login/statik internal/api/ui/login/statik
COPY internal/notification/static internal/notification/static
COPY internal/notification/statik internal/notification/statik
COPY internal/static internal/static
COPY internal/statik internal/statik
RUN make static
# #######################################
# generate code for assets
# #######################################
FROM golang:buster AS core-assets
WORKDIR /go/src/github.com/zitadel/zitadel
COPY go.mod .
COPY go.sum .
COPY Makefile Makefile
COPY internal/api/assets/generator internal/api/assets/generator
COPY internal/config internal/config
COPY internal/errors internal/errors
COPY --from=core-api /go/src/github.com/zitadel/zitadel/openapi/v2 openapi/v2
RUN make assets
# #######################################
# Gather all core files
# #######################################
FROM core-deps AS core-gathered
COPY cmd cmd
COPY internal internal
COPY pkg pkg
COPY proto proto
COPY openapi openapi
COPY statik statik
COPY main.go main.go
COPY --from=core-api /go/src/github.com/zitadel/zitadel .
COPY --from=core-login /go/src/github.com/zitadel/zitadel .
COPY --from=core-assets /go/src/github.com/zitadel/zitadel/internal ./internal
# ##############################################################################
# build console
# ##############################################################################
# #######################################
# download console dependencies
# #######################################
FROM node:18-buster AS console-deps
WORKDIR /zitadel/console
COPY console/package.json .
COPY console/yarn.lock .
RUN yarn install --frozen-lockfile
# #######################################
# generate console client
# #######################################
FROM node:18-buster AS console-client
WORKDIR /zitadel/console
# install buf
COPY --from=bufbuild/buf:latest /usr/local/bin/* /usr/local/bin/
ENV PATH="/usr/local/bin:${PATH}"
COPY console/package.json .
COPY console/buf.*.yaml .
COPY proto ../proto
RUN yarn generate
# #######################################
# Gather all console files
# #######################################
FROM console-deps as console-gathered
COPY --from=console-client /zitadel/console/src/app/proto/generated src/app/proto/generated
COPY console/src src
COPY console/angular.json .
COPY console/ngsw-config.json .
COPY console/tsconfig* .
# #######################################
# Build console
# #######################################
FROM console-gathered AS console
RUN yarn build
# ##############################################################################
# build the executable
# ##############################################################################
# #######################################
# build executable
# #######################################
FROM core-gathered AS compile
ARG GOOS
ARG GOARCH
COPY --from=console /zitadel/console/dist/console internal/api/ui/console/static/
RUN go build -o zitadel -ldflags="-s -w -race" \
&& chmod +x zitadel
ENTRYPOINT [ "./zitadel" ]
# #######################################
# copy executable
# #######################################
FROM scratch AS copy-executable
ARG GOOS
ARG GOARCH
COPY --from=compile /go/src/github.com/zitadel/zitadel/zitadel /.artifacts/zitadel
# ##############################################################################
# tests
# ##############################################################################
FROM ubuntu/postgres:latest AS test-core-base
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
apt-get install -y --no-install-recommends \
gcc \
make \
ca-certificates \
gcc \
&& \
update-ca-certificates; \
rm -rf /var/lib/apt/lists/*
# install go
COPY --from=golang:latest /usr/local/go/ /usr/local/go/
ENV PATH="/go/bin:/usr/local/go/bin:${PATH}"
WORKDIR /go/src/github.com/zitadel/zitadel
# default vars
ENV DB_FLAVOR=postgres
ENV POSTGRES_USER=zitadel
ENV POSTGRES_DB=zitadel
ENV POSTGRES_PASSWORD=postgres
ENV POSTGRES_HOST_AUTH_METHOD=trust
ENV PGUSER=zitadel
ENV PGDATABASE=zitadel
ENV PGPASSWORD=postgres
ENV CGO_ENABLED=1
# copy zitadel files
COPY --from=core-deps /go/pkg/mod /root/go/pkg/mod
COPY --from=core-gathered /go/src/github.com/zitadel/zitadel .
# #######################################
# unit test core
# #######################################
FROM test-core-base AS test-core-unit
RUN go test -race -v -coverprofile=profile.cov ./...
# #######################################
# coverage output
# #######################################
FROM scratch AS coverage-core-unit
COPY --from=test-core-unit /go/src/github.com/zitadel/zitadel/profile.cov /coverage/
# #######################################
# integration test core
# #######################################
FROM test-core-base AS test-core-integration
ENV DB_FLAVOR=cockroach
# install cockroach
COPY --from=cockroachdb/cockroach:latest /cockroach/cockroach /usr/local/bin/
ENV COCKROACH_BINARY=/cockroach/cockroach
ENV ZITADEL_MASTERKEY=MasterkeyNeedsToHave32Characters
COPY build/core-integration-test.sh /usr/local/bin/run-tests.sh
RUN chmod +x /usr/local/bin/run-tests.sh
RUN run-tests.sh
# #######################################
# coverage output
# #######################################
FROM scratch AS coverage-core-integration
COPY --from=test-core-integration /go/src/github.com/zitadel/zitadel/profile.cov /coverage/
# ##############################################################################
# linting
# ##############################################################################
# #######################################
# api
# #######################################
FROM bufbuild/buf:latest AS lint-api
COPY proto proto
COPY buf.*.yaml .
RUN buf lint
# #######################################
# console
# #######################################
FROM console-gathered AS lint-console
COPY console/.eslintrc.js .
COPY console/.prettier* .
RUN yarn lint
# #######################################
# core
# #######################################
FROM golangci/golangci-lint:latest AS lint-core
ARG LINT_EXIT_CODE=1
WORKDIR /go/src/github.com/zitadel/zitadel
COPY .golangci.yaml .
COPY .git/ .git/
COPY --from=core-deps /go/pkg/mod /go/pkg/mod
COPY --from=core-gathered /go/src/github.com/zitadel/zitadel .
RUN git fetch https://github.com/zitadel/zitadel main:main
RUN golangci-lint run \
--timeout 10m \
--config ./.golangci.yaml \
--out-format=github-actions:report,colored-line-number \
--issues-exit-code=${LINT_EXIT_CODE} \
--concurrency=$(getconf _NPROCESSORS_ONLN)
# #######################################
# report output
# #######################################
FROM scratch AS lint-core-report
COPY --from=lint-core /go/src/github.com/zitadel/zitadel/report .

View File

@ -1,107 +0,0 @@
ARG GO_VERSION=1.19
#######################
## Go dependencies
## Speed up this step by mounting your local go mod pkg directory
#######################
FROM golang:${GO_VERSION} as go-dep
WORKDIR /go/src/github.com/zitadel/zitadel
#download modules
COPY go.mod ./
COPY go.sum ./
RUN go mod download
# install tools
COPY tools ./tools
RUN ./tools/install.sh
#######################
## generates static files
#######################
FROM go-dep AS go-static
COPY internal/api/ui/login/static internal/api/ui/login/static
COPY internal/api/ui/login/statik internal/api/ui/login/statik
COPY internal/notification/static internal/notification/static
COPY internal/notification/statik internal/notification/statik
COPY internal/static internal/static
COPY internal/statik internal/statik
RUN go generate internal/api/ui/login/statik/generate.go \
&& go generate internal/api/ui/login/static/generate.go \
&& go generate internal/notification/statik/generate.go \
&& go generate internal/statik/generate.go
#######################
## generates grpc stub
#######################
FROM go-static AS go-stub
COPY --from=zitadel-base:local /proto /proto
COPY --from=zitadel-base:local /usr/local/bin /usr/local/bin/.
COPY build/zitadel/generate-grpc.sh build/zitadel/generate-grpc.sh
COPY internal/protoc internal/protoc
COPY openapi/statik openapi/statik
COPY internal/api/assets/generator internal/api/assets/generator
COPY internal/config internal/config
COPY internal/errors internal/errors
RUN build/zitadel/generate-grpc.sh && \
go generate openapi/statik/generate.go && \
mkdir -p docs/apis/assets/ && \
go run internal/api/assets/generator/asset_generator.go -directory=internal/api/assets/generator/ -assets=docs/apis/assets/assets.md
#######################
## Go base build
#######################
FROM go-stub as go-base
# copy remaining zitadel files
COPY cmd cmd
COPY internal internal
COPY pkg pkg
COPY openapi openapi
#######################
## copy for local dev
#######################
FROM scratch as go-copy
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/internal/api/ui/login/statik/statik.go internal/api/ui/login/statik/statik.go
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/internal/notification/statik/statik.go internal/notification/statik/statik.go
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/internal/statik/statik.go internal/statik/statik.go
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/openapi/statik/statik.go openapi/statik/statik.go
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/pkg/grpc pkg/grpc
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/openapi/v2/zitadel openapi/v2/zitadel
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/openapi/statik/statik.go openapi/statik/statik.go
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/internal/protoc/protoc-gen-authoption/authoption/options.pb.go internal/protoc/protoc-gen-authoption/authoption/options.pb.go
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/docs/apis/proto docs/docs/apis/proto
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/docs/apis/assets docs/docs/apis/assets
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/internal/api/assets/authz.go ./internal/api/assets/authz.go
COPY --from=go-stub /go/src/github.com/zitadel/zitadel/internal/api/assets/router.go ./internal/api/assets/router.go
#######################
## Go test
#######################
FROM go-base as go-test
ARG COCKROACH_BINARY=/usr/local/bin/cockroach
ARG COCKROACH_VERSION=v22.2.2
RUN apt install openssl tzdata tar
# cockroach binary used to backup database
RUN mkdir /usr/local/lib/cockroach
RUN wget -qO- https://binaries.cockroachdb.com/cockroach-${COCKROACH_VERSION}.linux-amd64.tgz \
| tar xvz && cp -i cockroach-${COCKROACH_VERSION}.linux-amd64/cockroach /usr/local/bin/
RUN rm -r cockroach-${COCKROACH_VERSION}.linux-amd64
# Migrations for cockroach-secure
RUN go install github.com/rakyll/statik \
&& go test -race -v -coverprofile=profile.cov $(go list ./... | grep -v /operator/)
#######################
## Go test results
#######################
FROM scratch as go-codecov
COPY --from=go-test /go/src/github.com/zitadel/zitadel/profile.cov profile.cov

View File

@ -1,111 +0,0 @@
#!/bin/sh
set -eux
echo "Generate grpc"
OPENAPI_PATH=${GOPATH}/src/github.com/zitadel/zitadel/openapi/v2
ZITADEL_PATH=${GOPATH}/src/github.com/zitadel/zitadel
GRPC_PATH=${ZITADEL_PATH}/pkg/grpc
PROTO_PATH=/proto/include/zitadel
DOCS_PATH=${ZITADEL_PATH}/docs/apis/proto
# generate go stub and grpc code for all files
protoc \
-I=/proto/include/ \
--go_out $GOPATH/src \
--go-grpc_out $GOPATH/src \
--validate_out=lang=go:${GOPATH}/src \
$(find ${PROTO_PATH} -iname *.proto)
# install authoption and zitadel proto compiler
go install ${ZITADEL_PATH}/internal/protoc/protoc-gen-auth
go install ${ZITADEL_PATH}/internal/protoc/protoc-gen-zitadel
# output folder for openapi v2
mkdir -p ${OPENAPI_PATH}
mkdir -p ${DOCS_PATH}
# generate additional output
protoc \
-I=/proto/include \
--grpc-gateway_out ${GOPATH}/src \
--grpc-gateway_opt logtostderr=true \
--openapiv2_out ${OPENAPI_PATH} \
--openapiv2_opt logtostderr=true \
--auth_out ${GOPATH}/src \
--validate_out=lang=go:${GOPATH}/src \
${PROTO_PATH}/system.proto
protoc \
-I=/proto/include \
--grpc-gateway_out ${GOPATH}/src \
--grpc-gateway_opt logtostderr=true \
--openapiv2_out ${OPENAPI_PATH} \
--openapiv2_opt logtostderr=true \
--auth_out ${GOPATH}/src \
--validate_out=lang=go:${GOPATH}/src \
${PROTO_PATH}/admin.proto
protoc \
-I=/proto/include \
--grpc-gateway_out ${GOPATH}/src \
--grpc-gateway_opt logtostderr=true \
--grpc-gateway_opt allow_delete_body=true \
--openapiv2_out ${OPENAPI_PATH} \
--openapiv2_opt logtostderr=true \
--openapiv2_opt allow_delete_body=true \
--auth_out ${GOPATH}/src \
--validate_out=lang=go:${GOPATH}/src \
${PROTO_PATH}/management.proto
protoc \
-I=/proto/include \
--grpc-gateway_out ${GOPATH}/src \
--grpc-gateway_opt logtostderr=true \
--grpc-gateway_opt allow_delete_body=true \
--openapiv2_out ${OPENAPI_PATH} \
--openapiv2_opt logtostderr=true \
--openapiv2_opt allow_delete_body=true \
--auth_out=${GOPATH}/src \
--validate_out=lang=go:${GOPATH}/src \
${PROTO_PATH}/auth.proto
protoc \
-I=/proto/include \
--grpc-gateway_out ${GOPATH}/src \
--grpc-gateway_opt logtostderr=true \
--grpc-gateway_opt allow_delete_body=true \
--openapiv2_out ${OPENAPI_PATH} \
--openapiv2_opt logtostderr=true \
--openapiv2_opt allow_delete_body=true \
--zitadel_out=${GOPATH}/src \
--validate_out=lang=go:${GOPATH}/src \
${PROTO_PATH}/user/v2alpha/user_service.proto
protoc \
-I=/proto/include \
--grpc-gateway_out ${GOPATH}/src \
--grpc-gateway_opt logtostderr=true \
--grpc-gateway_opt allow_delete_body=true \
--openapiv2_out ${OPENAPI_PATH} \
--openapiv2_opt logtostderr=true \
--openapiv2_opt allow_delete_body=true \
--zitadel_out=${GOPATH}/src \
--validate_out=lang=go:${GOPATH}/src \
${PROTO_PATH}/session/v2alpha/session_service.proto
protoc \
-I=/proto/include \
--grpc-gateway_out ${GOPATH}/src \
--grpc-gateway_opt logtostderr=true \
--grpc-gateway_opt allow_delete_body=true \
--openapiv2_out ${OPENAPI_PATH} \
--openapiv2_opt logtostderr=true \
--openapiv2_opt allow_delete_body=true \
--zitadel_out=${GOPATH}/src \
--validate_out=lang=go:${GOPATH}/src \
${PROTO_PATH}/settings/v2alpha/settings_service.proto
echo "done generating grpc"

View File

@ -14,6 +14,29 @@ Tracing:
Fraction: 1.0
MetricPrefix: zitadel
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
# 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.
# Configure delivery guarantees and intervals in the section Projections.Customizations.Telemetry
# The endpoints can be reconfigured at runtime.
# Ten redirects are followed.
# If you change this configuration at runtime, remaining data that is not successfully delivered to the old endpoints is sent to the new endpoints.
Endpoints:
- https://httpbin.org/post
# These headers are sent with every request to the configured endpoints.
Headers:
# single-value: "single-value"
# multi-value:
# - "multi-value-1"
# - "multi-value-2"
# The maximum number of data points that are queried before they are sent to the configured endpoints.
Limit: 100 # ZITADEL_TELEMETRY_LIMIT
# Port ZITADEL will listen on
Port: 8080
# Port ZITADEL is exposed on, it can differ from port e.g. if you proxy the traffic
@ -169,17 +192,29 @@ Projections:
BulkLimit: 2000
# The Notifications projection is used for sending emails and SMS to users
Notifications:
# As notification projections don't result in database statements, retries don't have an effect
# As notification projections don't result in database statements, retries don't have any effects
MaxFailureCount: 0
# The NotificationsQuotas projection is used for calling quota webhooks
NotificationsQuotas:
# Delivery guarantee requirements are probably higher for quota webhooks
# In case of failed deliveries, ZITADEL retries to send the data points to the configured endpoints, but only for active instances.
# An instance is active, as long as there are projected events on the instance, that are not older than the HandleActiveInstances duration.
# Delivery guarantee requirements are higher for quota webhooks
# Defaults to 45 days
HandleActiveInstances: 1080h
# As quota notification projections don't result in database statements, retries don't have an effect
# As quota notification projections don't result in database statements, retries don't have any effects
MaxFailureCount: 0
# Quota notifications are not so time critical. Setting RequeueEvery every five minutes doesn't annoy the db too much.
# Quota notifications are not so time critical. Setting RequeueEvery every five minutes doesn't annoy the database too much.
RequeueEvery: 300s
Telemetry:
# In case of failed deliveries, ZITADEL retries to send the data points to the configured endpoints, but only for active instances.
# An instance is active, as long as there are projected events on the instance, that are not older than the HandleActiveInstances duration.
# Telemetry delivery guarantee requirements are a bit higher than normal data projections, as they are not interactively retryable.
# Defaults to 15 days
HandleActiveInstances: 360h
# As sending telemetry data doesn't result in database statements, retries don't have any effects
MaxFailureCount: 0
# Telemetry data synchronization is not time critical. Setting RequeueEvery to 55 minutes doesn't annoy the database too much.
RequeueEvery: 3300s
Auth:
SearchLimit: 1000
@ -235,6 +270,8 @@ OIDC:
Path: /oauth/v2/keys
DeviceAuth:
Path: /oauth/v2/device_authorization
DefaultLoginURLV2: "/login?authRequest="
DefaultLogoutURLV2: "/logout?post_logout_redirect="
SAML:
ProviderConfig:
@ -320,6 +357,51 @@ SystemDefaults:
PasswordSaltCost: 14
MachineKeySize: 2048
ApplicationKeySize: 2048
PasswordHasher:
# Set hasher configuration for user passwords.
# Passwords previously hashed with a different algorithm
# or cost are automatically re-hashed using this config,
# upon password validation or update.
Hasher:
Algorithm: "bcrypt"
Cost: 14
# Other supported Hasher configs:
# Hasher:
# Algorithm: "argon2i"
# Time: 3
# Memory: 32768
# Threads: 4
# Hasher:
# Algorithm: "argon2id"
# Time: 1
# Memory: 65536
# Threads: 4
# Hasher:
# Algorithm: "scrypt"
# Cost: 15
# Verifiers enable the possibility of verifying
# passwords that are previously hashed using another
# algorithm then the Hasher.
# This can be used when migrating from one algorithm to another,
# or when importing users with hashed passwords.
# There is no need to enable a Verifier of the same algorithm
# as the Hasher.
#
# The format of the encoded hash strings must comply
# with https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
# https://passlib.readthedocs.io/en/stable/modular_crypt_format.html
#
# Supported verifiers: (uncomment to enable)
# Verifiers:
# - "argon2" # verifier for both argon2i and argon2id.
# - "bcrypt"
# - "md5"
# - "scrypt"
Multifactors:
OTP:
# If this is empty, the issuer is the requested domain
@ -1037,6 +1119,10 @@ InternalAuthZ:
- "org.create"
- "policy.read"
- "user.self.delete"
- Role: "ORG_USER_SELF_MANAGER"
Permissions:
- "policy.read"
- "user.self.delete"
- Role: "PROJECT_OWNER_GLOBAL"
Permissions:
- "org.global.read"

34
cmd/ready/config.go Normal file
View File

@ -0,0 +1,34 @@
package ready
import (
"time"
"github.com/mitchellh/mapstructure"
"github.com/spf13/viper"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/config/hook"
)
type Config struct {
Log *logging.Config
Port uint16
}
func MustNewConfig(v *viper.Viper) *Config {
config := new(Config)
err := v.Unmarshal(config,
viper.DecodeHook(mapstructure.ComposeDecodeHookFunc(
hook.Base64ToBytesHookFunc(),
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToTimeHookFunc(time.RFC3339),
mapstructure.StringToSliceHookFunc(","),
)),
)
logging.OnError(err).Fatal("unable to read default config")
err = config.Log.SetLogger()
logging.OnError(err).Fatal("unable to set logger")
return config
}

37
cmd/ready/ready.go Normal file
View File

@ -0,0 +1,37 @@
package ready
import (
"net"
"net/http"
"os"
"strconv"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/zitadel/logging"
)
func New() *cobra.Command {
return &cobra.Command{
Use: "ready",
Short: "Checks if zitadel is ready",
Long: "Checks if zitadel is ready",
Run: func(cmd *cobra.Command, args []string) {
config := MustNewConfig(viper.GetViper())
if !ready(config) {
os.Exit(1)
}
},
}
}
func ready(config *Config) bool {
res, err := http.Get("http://" + net.JoinHostPort("localhost", strconv.Itoa(int(config.Port))) + "/debug/ready")
if err != nil {
logging.WithError(err).Warn("ready check failed")
return false
}
defer res.Body.Close()
logging.WithFields("status", res.StatusCode).Warn("ready check failed")
return res.StatusCode == 200
}

View File

@ -88,6 +88,9 @@ func (mig *FirstInstance) Execute(ctx context.Context) error {
nil,
nil,
nil,
0,
0,
0,
)
if err != nil {
return err

View File

@ -17,6 +17,7 @@ type externalConfigChange struct {
currentExternalDomain string
currentExternalSecure bool
currentExternalPort uint16
defaults systemdefaults.SystemDefaults
}
func (mig *externalConfigChange) SetLastExecution(lastRun map[string]interface{}) {
@ -35,7 +36,7 @@ func (mig *externalConfigChange) Check() bool {
func (mig *externalConfigChange) Execute(ctx context.Context) error {
cmd, err := command.StartCommands(
mig.es,
systemdefaults.SystemDefaults{},
mig.defaults,
nil,
nil,
nil,
@ -53,6 +54,9 @@ func (mig *externalConfigChange) Execute(ctx context.Context) error {
nil,
nil,
nil,
0,
0,
0,
)
if err != nil {

View File

@ -104,6 +104,7 @@ func Setup(config *Config, steps *Steps, masterKey string) {
ExternalDomain: config.ExternalDomain,
ExternalPort: config.ExternalPort,
ExternalSecure: config.ExternalSecure,
defaults: config.SystemDefaults,
},
&projectionTables{
es: eventstoreClient,

View File

@ -1,6 +1,8 @@
package start
import (
"encoding/json"
"reflect"
"time"
"github.com/mitchellh/mapstructure"
@ -25,6 +27,7 @@ import (
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/logstore"
"github.com/zitadel/zitadel/internal/notification/handlers"
"github.com/zitadel/zitadel/internal/query/projection"
static_config "github.com/zitadel/zitadel/internal/static/config"
metrics "github.com/zitadel/zitadel/internal/telemetry/metrics/config"
@ -58,13 +61,14 @@ type Config struct {
EncryptionKeys *encryptionKeyConfig
DefaultInstance command.InstanceSetup
AuditLogRetention time.Duration
SystemAPIUsers map[string]*internal_authz.SystemAPIUser
SystemAPIUsers SystemAPIUsers
CustomerPortal string
Machine *id.Config
Actions *actions.Config
Eventstore *eventstore.Config
LogStore *logstore.Configs
Quotas *QuotasConfig
Telemetry *handlers.TelemetryPusherConfig
}
type QuotasConfig struct {
@ -83,6 +87,7 @@ func MustNewConfig(v *viper.Viper) *Config {
mapstructure.StringToSliceHookFunc(","),
database.DecodeHook,
actions.HTTPConfigDecodeHook,
systemAPIUsersDecodeHook,
)),
)
logging.OnError(err).Fatal("unable to read config")
@ -114,3 +119,22 @@ type encryptionKeyConfig struct {
CSRFCookieKeyID string
UserAgentCookieKeyID string
}
type SystemAPIUsers map[string]*internal_authz.SystemAPIUser
func systemAPIUsersDecodeHook(from, to reflect.Value) (any, error) {
if to.Type() != reflect.TypeOf(SystemAPIUsers{}) {
return from.Interface(), nil
}
data, ok := from.Interface().(string)
if !ok {
return from.Interface(), nil
}
users := make(SystemAPIUsers)
err := json.Unmarshal([]byte(data), &users)
if err != nil {
return nil, err
}
return users, nil
}

View File

@ -13,6 +13,8 @@ import (
"time"
clockpkg "github.com/benbjohnson/clock"
"github.com/common-nighthawk/go-figure"
"github.com/fatih/color"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@ -22,6 +24,7 @@ import (
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"github.com/zitadel/zitadel/cmd/build"
"github.com/zitadel/zitadel/cmd/key"
cmd_tls "github.com/zitadel/zitadel/cmd/tls"
"github.com/zitadel/zitadel/internal/actions"
@ -32,6 +35,7 @@ import (
"github.com/zitadel/zitadel/internal/api/grpc/admin"
"github.com/zitadel/zitadel/internal/api/grpc/auth"
"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/session/v2"
"github.com/zitadel/zitadel/internal/api/grpc/settings/v2"
"github.com/zitadel/zitadel/internal/api/grpc/system"
@ -110,6 +114,8 @@ type Server struct {
}
func startZitadel(config *Config, masterKey string, server chan<- *Server) error {
showBasicInformation(config)
ctx := context.Background()
dbClient, err := database.Connect(config.Database, false)
@ -192,6 +198,9 @@ func startZitadel(config *Config, masterKey string, server chan<- *Server) error
&http.Client{},
permissionCheck,
sessionTokenVerifier,
config.OIDC.DefaultAccessTokenLifetime,
config.OIDC.DefaultRefreshTokenExpiration,
config.OIDC.DefaultRefreshTokenIdleExpiration,
)
if err != nil {
return fmt.Errorf("cannot start commands: %w", err)
@ -207,14 +216,14 @@ func startZitadel(config *Config, masterKey string, server chan<- *Server) error
return err
}
usageReporter := logstore.UsageReporterFunc(commands.ReportUsage)
usageReporter := logstore.UsageReporterFunc(commands.ReportQuotaUsage)
actionsLogstoreSvc := logstore.New(queries, usageReporter, actionsExecutionDBEmitter, actionsExecutionStdoutEmitter)
if actionsLogstoreSvc.Enabled() {
logging.Warn("execution logs are currently in beta")
}
actions.SetLogstoreService(actionsLogstoreSvc)
notification.Start(ctx, config.Projections.Customizations["notifications"], config.Projections.Customizations["notificationsquotas"], config.ExternalPort, config.ExternalSecure, commands, queries, eventstoreClient, assets.AssetAPIFromDomain(config.ExternalSecure, config.ExternalPort), config.SystemDefaults.Notifications.FileSystemPath, keys.User, keys.SMTP, keys.SMS)
notification.Start(ctx, config.Projections.Customizations["notifications"], config.Projections.Customizations["notificationsquotas"], config.Projections.Customizations["telemetry"], *config.Telemetry, config.ExternalDomain, config.ExternalPort, config.ExternalSecure, commands, queries, eventstoreClient, assets.AssetAPIFromDomain(config.ExternalSecure, config.ExternalPort), config.SystemDefaults.Notifications.FileSystemPath, keys.User, keys.SMTP, keys.SMS)
router := mux.NewRouter()
tlsConfig, err := config.TLS.Config()
@ -344,6 +353,7 @@ func startAPIs(
if err := apis.RegisterService(ctx, session.CreateServer(commands, queries, permissionCheck)); err != nil {
return err
}
if err := apis.RegisterService(ctx, settings.CreateServer(commands, queries, config.ExternalSecure)); err != nil {
return err
}
@ -397,6 +407,11 @@ func startAPIs(
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, oidcProvider, config.ExternalSecure)); err != nil {
return 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
@ -444,3 +459,29 @@ func shutdownServer(ctx context.Context, server *http.Server) error {
logging.New().Info("server shutdown gracefully")
return nil
}
func showBasicInformation(startConfig *Config) {
fmt.Println(color.MagentaString(figure.NewFigure("Zitadel", "", true).String()))
http := "http"
if startConfig.TLS.Enabled || startConfig.ExternalSecure {
http = "https"
}
consoleURL := fmt.Sprintf("%s://%s:%v/ui/console\n", http, startConfig.ExternalDomain, startConfig.ExternalPort)
healthCheckURL := fmt.Sprintf("%s://%s:%v/debug/healthz\n", http, startConfig.ExternalDomain, startConfig.ExternalPort)
insecure := !startConfig.TLS.Enabled && !startConfig.ExternalSecure
fmt.Printf(" ===============================================================\n\n")
fmt.Printf(" Version : %s\n", build.Version())
fmt.Printf(" TLS enabled : %v\n", startConfig.TLS.Enabled)
fmt.Printf(" External Secure : %v\n", startConfig.ExternalSecure)
fmt.Printf(" Console URL : %s", color.BlueString(consoleURL))
fmt.Printf(" Health Check URL : %s", color.BlueString(healthCheckURL))
if insecure {
fmt.Printf("\n %s: you're using plain http without TLS. Be aware this is \n", color.RedString("Warning"))
fmt.Printf(" not a secure setup and should only be used for test systems. \n")
fmt.Printf(" Visit: %s \n", color.CyanString("https://zitadel.com/docs/self-hosting/manage/tls_modes"))
}
fmt.Printf("\n ===============================================================\n\n")
}

View File

@ -15,6 +15,7 @@ import (
"github.com/zitadel/zitadel/cmd/build"
"github.com/zitadel/zitadel/cmd/initialise"
"github.com/zitadel/zitadel/cmd/key"
"github.com/zitadel/zitadel/cmd/ready"
"github.com/zitadel/zitadel/cmd/setup"
"github.com/zitadel/zitadel/cmd/start"
)
@ -55,6 +56,7 @@ func New(out io.Writer, in io.Reader, args []string, server chan<- *start.Server
start.NewStartFromInit(server),
start.NewStartFromSetup(server),
key.New(),
ready.New(),
)
cmd.InitDefaultVersionFlag()

View File

@ -26,6 +26,9 @@
"assets": ["src/favicon.ico", "src/assets", "src/manifest.webmanifest"],
"styles": ["src/styles.scss"],
"scripts": ["./node_modules/tinycolor2/dist/tinycolor-min.js"],
"stylePreprocessorOptions": {
"includePaths": ["node_modules"]
},
"allowedCommonJsDependencies": [
"fast-sha256",
"buffer",
@ -46,7 +49,8 @@
"inline": false
},
"styles": {
"inlineCritical": false
"inlineCritical": false,
"minify": false
}
},
"budgets": [

16940
console/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
import { CommonModule, registerLocaleData } from '@angular/common';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import localePt from '@angular/common/locales/pt';
import localeBg from '@angular/common/locales/bg';
import localeDe from '@angular/common/locales/de';
import localeEn from '@angular/common/locales/en';
@ -7,6 +8,7 @@ import localeEs from '@angular/common/locales/es';
import localeFr from '@angular/common/locales/fr';
import localeIt from '@angular/common/locales/it';
import localeJa from '@angular/common/locales/ja';
import localeMk from '@angular/common/locales/mk';
import localePl from '@angular/common/locales/pl';
import localeZh from '@angular/common/locales/zh';
import { APP_INITIALIZER, NgModule } from '@angular/core';
@ -83,6 +85,10 @@ registerLocaleData(localeZh);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/zh.json'));
registerLocaleData(localeBg);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/bg.json'));
registerLocaleData(localePt);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/pt.json'));
registerLocaleData(localeMk);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/mk.json'));
export class WebpackTranslateLoader implements TranslateLoader {
getTranslation(lang: string): Observable<any> {

View File

@ -1,11 +1,11 @@
import { SelectionModel } from '@angular/cdk/collections';
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { MatLegacyTableDataSource as MatTableDataSource } from '@angular/material/legacy-table';
import { Router, RouterLink } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { Duration } from 'google-protobuf/google/protobuf/duration_pb';
import { BehaviorSubject, Observable } from 'rxjs';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import {
ListProvidersRequest as AdminListProvidersRequest,
ListProvidersResponse as AdminListProvidersResponse,
@ -42,7 +42,7 @@ import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component';
templateUrl: './idp-table.component.html',
styleUrls: ['./idp-table.component.scss'],
})
export class IdpTableComponent implements OnInit {
export class IdpTableComponent implements OnInit, OnDestroy {
@Input() public serviceType!: PolicyComponentServiceType;
@Input() service!: AdminService | ManagementService;
@ViewChild(PaginatorComponent) public paginator!: PaginatorComponent;
@ -62,6 +62,8 @@ export class IdpTableComponent implements OnInit {
public IDPStylingType: any = IDPStylingType;
public loginPolicy!: LoginPolicy.AsObject;
private reloadIDPs$: Subject<void> = new Subject();
constructor(
private workflowService: OverlayWorkflowService,
public translate: TranslateService,
@ -72,6 +74,16 @@ export class IdpTableComponent implements OnInit {
this.selection.changed.subscribe(() => {
this.changedSelection.emit(this.selection.selected);
});
this.reloadIDPs$.subscribe(() => {
this.getIdps()
.then((resp) => {
this.idps = resp;
})
.catch((error) => {
this.toast.showError(error);
});
});
}
ngOnInit(): void {
@ -85,6 +97,10 @@ export class IdpTableComponent implements OnInit {
}
}
ngOnDestroy(): void {
this.reloadIDPs$.complete();
}
public isAllSelected(): boolean {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
@ -332,13 +348,7 @@ export class IdpTableComponent implements OnInit {
this.toast.showInfo('IDP.TOAST.ADDED', true);
setTimeout(() => {
this.getIdps()
.then((resp) => {
this.idps = resp;
})
.catch((error) => {
this.toast.showError(error);
});
this.reloadIDPs$.next();
}, 2000);
});
})
@ -351,13 +361,7 @@ export class IdpTableComponent implements OnInit {
.then(() => {
this.toast.showInfo('IDP.TOAST.ADDED', true);
setTimeout(() => {
this.getIdps()
.then((resp) => {
this.idps = resp;
})
.catch((error) => {
this.toast.showError(error);
});
this.reloadIDPs$.next();
}, 2000);
})
.catch((error) => {
@ -370,13 +374,7 @@ export class IdpTableComponent implements OnInit {
.then(() => {
this.toast.showInfo('IDP.TOAST.ADDED', true);
setTimeout(() => {
this.getIdps()
.then((resp) => {
this.idps = resp;
})
.catch((error) => {
this.toast.showError(error);
});
this.reloadIDPs$.next();
}, 2000);
})
.catch((error) => {
@ -385,72 +383,71 @@ export class IdpTableComponent implements OnInit {
}
}
public removeIdp(idp: Provider.AsObject): Promise<any> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
if (this.isDefault) {
return this.addLoginPolicy()
.then(() => {
this.loginPolicy.isDefault = false;
return (this.service as ManagementService)
public removeIdp(idp: Provider.AsObject): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.CONTINUE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'IDP.REMOVE_WARN_TITLE',
descriptionKey: 'IDP.REMOVE_WARN_DESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe((resp) => {
if (resp) {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
if (this.isDefault) {
this.addLoginPolicy()
.then(() => {
this.loginPolicy.isDefault = false;
return (this.service as ManagementService)
.removeIDPFromLoginPolicy(idp.id)
.then(() => {
this.toast.showInfo('IDP.TOAST.REMOVED', true);
setTimeout(() => {
this.reloadIDPs$.next();
}, 2000);
})
.catch((error) => {
this.toast.showError(error);
});
})
.catch((error) => {
this.toast.showError(error);
});
break;
} else {
(this.service as ManagementService)
.removeIDPFromLoginPolicy(idp.id)
.then(() => {
this.toast.showInfo('IDP.TOAST.REMOVED', true);
setTimeout(() => {
this.getIdps()
.then((resp) => {
this.idps = resp;
})
.catch((error) => {
this.toast.showError(error);
});
this.reloadIDPs$.next();
}, 2000);
})
.catch((error) => {
this.toast.showError(error);
});
})
.catch((error) => {
this.toast.showError(error);
});
} else {
return (this.service as ManagementService)
.removeIDPFromLoginPolicy(idp.id)
.then(() => {
this.toast.showInfo('IDP.TOAST.REMOVED', true);
setTimeout(() => {
this.getIdps()
.then((resp) => {
this.idps = resp;
})
.catch((error) => {
this.toast.showError(error);
});
}, 2000);
})
.catch((error) => {
this.toast.showError(error);
});
break;
}
case PolicyComponentServiceType.ADMIN:
(this.service as AdminService)
.removeIDPFromLoginPolicy(idp.id)
.then(() => {
this.toast.showInfo('IDP.TOAST.REMOVED', true);
setTimeout(() => {
this.reloadIDPs$.next();
}, 2000);
})
.catch((error) => {
this.toast.showError(error);
});
break;
}
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService)
.removeIDPFromLoginPolicy(idp.id)
.then(() => {
this.toast.showInfo('IDP.TOAST.REMOVED', true);
setTimeout(() => {
this.getIdps()
.then((resp) => {
this.idps = resp;
})
.catch((error) => {
this.toast.showError(error);
});
}, 2000);
})
.catch((error) => {
this.toast.showError(error);
});
}
}
});
}
public isEnabled(idp: Provider.AsObject): boolean {

View File

@ -99,6 +99,28 @@
{{ 'POLICY.DATA.FORCEMFA' | translate }}
</mat-checkbox>
</div>
<div *ngIf="loginData" class="login-policy-row">
<mat-checkbox
card-actions
class="login-policy-toggle"
color="primary"
ngDefaultControl
[(ngModel)]="loginData.forceMfaLocalOnly"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
{{ 'POLICY.DATA.FORCEMFALOCALONLY' | translate }}
</mat-checkbox>
</div>
<cnsl-card class="max-card-width" *ngIf="loginData">
<cnsl-factor-table
[service]="service"

View File

@ -152,6 +152,7 @@ export class LoginPolicyComponent implements OnInit {
mgmtreq.setAllowRegister(this.loginData.allowRegister);
mgmtreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
mgmtreq.setForceMfa(this.loginData.forceMfa);
mgmtreq.setForceMfaLocalOnly(this.loginData.forceMfaLocalOnly);
mgmtreq.setPasswordlessType(this.loginData.passwordlessType);
mgmtreq.setHidePasswordReset(this.loginData.hidePasswordReset);
mgmtreq.setMultiFactorsList(this.loginData.multiFactorsList);
@ -185,6 +186,7 @@ export class LoginPolicyComponent implements OnInit {
mgmtreq.setAllowRegister(this.loginData.allowRegister);
mgmtreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
mgmtreq.setForceMfa(this.loginData.forceMfa);
mgmtreq.setForceMfaLocalOnly(this.loginData.forceMfaLocalOnly);
mgmtreq.setPasswordlessType(this.loginData.passwordlessType);
mgmtreq.setHidePasswordReset(this.loginData.hidePasswordReset);
mgmtreq.setDisableLoginWithEmail(this.loginData.disableLoginWithEmail);
@ -217,6 +219,7 @@ export class LoginPolicyComponent implements OnInit {
adminreq.setAllowRegister(this.loginData.allowRegister);
adminreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
adminreq.setForceMfa(this.loginData.forceMfa);
adminreq.setForceMfaLocalOnly(this.loginData.forceMfaLocalOnly);
adminreq.setPasswordlessType(this.loginData.passwordlessType);
adminreq.setHidePasswordReset(this.loginData.hidePasswordReset);
adminreq.setDisableLoginWithEmail(this.loginData.disableLoginWithEmail);

View File

@ -16,6 +16,7 @@
color="primary"
class="cnsl-action-button"
mat-raised-button
data-e2e="create-project-role-button"
>
<mat-icon data-e2e="add-new-role" class="icon">add</mat-icon>
<span>{{ 'ACTIONS.NEW' | translate }}</span>

View File

@ -0,0 +1,32 @@
<form>
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'PROJECT.GRANT.CREATE.SEL_ORG_FORMFIELD' | translate }}</cnsl-label>
<input
cnslInput
type="text"
placeholder="Organization XY"
#nameInput
[formControl]="myControl"
[matAutocomplete]="auto"
data-e2e="add-org-input"
/>
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)" [displayWith]="displayFn">
<mat-option *ngIf="isLoading" class="is-loading">
<mat-spinner diameter="30"></mat-spinner>
</mat-option>
<mat-option *ngFor="let org of filteredOrgs" [value]="org">
<div class="org-option" data-e2e="org-option">
<div class="org-option-column">
<span>{{ org.name }}</span>
<span class="fill-space"></span>
<span class="smaller cnsl-secondary-text">{{ org.primaryDomain }}</span>
</div>
</div>
</mat-option>
</mat-autocomplete>
<span class="org-autocomplete-target-desc">
{{ 'PROJECT.GRANT.CREATE.SEL_ORG_DESC' | translate }}
</span>
</cnsl-form-field>
</form>

View File

@ -0,0 +1,38 @@
.full-width {
width: 100%;
}
input {
max-width: 500px;
}
.org-option {
display: flex;
align-items: center;
.org-option-column {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
width: 100%;
span {
line-height: normal;
}
.fill-space {
flex: 1;
}
.smaller {
font-size: 13px;
}
}
}
.org-autocomplete-target-desc {
font-size: 14px;
display: block;
margin-top: 0.5rem;
}

View File

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

View File

@ -0,0 +1,83 @@
import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
import { UntypedFormControl } from '@angular/forms';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { MatLegacyAutocomplete as MatAutocomplete } from '@angular/material/legacy-autocomplete';
import { debounceTime, from, map, Subject, switchMap, takeUntil, tap } from 'rxjs';
import { TextQueryMethod } from 'src/app/proto/generated/zitadel/object_pb';
import { Org, OrgNameQuery, OrgQuery, OrgState, OrgStateQuery } from 'src/app/proto/generated/zitadel/org_pb';
import { AuthenticationService } from 'src/app/services/authentication.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
@Component({
selector: 'cnsl-search-org-autocomplete',
templateUrl: './search-org-autocomplete.component.html',
styleUrls: ['./search-org-autocomplete.component.scss'],
})
export class SearchOrgAutocompleteComponent implements OnInit, OnDestroy {
public selectable: boolean = true;
public myControl: UntypedFormControl = new UntypedFormControl();
public filteredOrgs: Array<Org.AsObject> = [];
public isLoading: boolean = false;
@ViewChild('auto') public matAutocomplete!: MatAutocomplete;
@Output() public selectionChanged: EventEmitter<Org.AsObject> = new EventEmitter();
private unsubscribed$: Subject<void> = new Subject();
constructor(public authService: AuthenticationService, private auth: GrpcAuthService) {
this.myControl.valueChanges
.pipe(
takeUntil(this.unsubscribed$),
debounceTime(200),
tap(() => (this.isLoading = true)),
switchMap((value) => {
const stateQuery = new OrgQuery();
const orgStateQuery = new OrgStateQuery();
orgStateQuery.setState(OrgState.ORG_STATE_ACTIVE);
stateQuery.setStateQuery(orgStateQuery);
let queries: OrgQuery[] = [stateQuery];
if (value) {
const nameQuery = new OrgQuery();
const orgNameQuery = new OrgNameQuery();
orgNameQuery.setName(value);
orgNameQuery.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
nameQuery.setNameQuery(orgNameQuery);
queries = [stateQuery, nameQuery];
}
return from(this.auth.listMyProjectOrgs(undefined, 0, queries)).pipe(
map((resp) => {
return resp.resultList.sort((left, right) => left.name.localeCompare(right.name));
}),
);
}),
)
.subscribe((returnValue) => {
this.isLoading = false;
this.filteredOrgs = returnValue;
});
}
public ngOnInit(): void {
const query = new OrgQuery();
const orgStateQuery = new OrgStateQuery();
orgStateQuery.setState(OrgState.ORG_STATE_ACTIVE);
query.setStateQuery(orgStateQuery);
this.auth.listMyProjectOrgs(undefined, 0, [query]).then((orgs) => {
this.filteredOrgs = orgs.resultList;
});
}
public ngOnDestroy(): void {
this.unsubscribed$.next();
}
public displayFn(org?: Org.AsObject): string {
return org ? `${org.name}` : '';
}
public selected(event: MatAutocompleteSelectedEvent): void {
this.selectionChanged.emit(event.option.value);
}
}

View File

@ -0,0 +1,32 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatIconModule } from '@angular/material/icon';
import { MatLegacyAutocompleteModule as MatAutocompleteModule } from '@angular/material/legacy-autocomplete';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyChipsModule as MatChipsModule } from '@angular/material/legacy-chips';
import { MatLegacyProgressSpinnerModule as MatProgressSpinnerModule } from '@angular/material/legacy-progress-spinner';
import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select';
import { TranslateModule } from '@ngx-translate/core';
import { InputModule } from 'src/app/modules/input/input.module';
import { SearchOrgAutocompleteComponent } from './search-org-autocomplete.component';
@NgModule({
declarations: [SearchOrgAutocompleteComponent],
imports: [
CommonModule,
MatAutocompleteModule,
MatChipsModule,
MatButtonModule,
InputModule,
MatIconModule,
ReactiveFormsModule,
MatProgressSpinnerModule,
FormsModule,
TranslateModule,
MatSelectModule,
],
exports: [SearchOrgAutocompleteComponent],
})
export class SearchOrgAutocompleteModule {}

View File

@ -44,7 +44,7 @@
<div *ngIf="http">
<p>HTTP TOKEN</p>
<p class="entry">{{ http.url }}.txt</p>
<p class="entry">{{ http.url }}</p>
<div class="btn-container">
<button mat-stroked-button (click)="saveFile()" color="primary">{{ 'ORG.PAGES.DOWNLOAD_FILE' | translate }}</button>

View File

@ -6,20 +6,8 @@
>
<ng-container *ngIf="currentCreateStep === 1">
<h1>{{ 'PROJECT.GRANT.CREATE.SEL_ORG' | translate }}</h1>
<p>{{ 'PROJECT.GRANT.CREATE.SEL_ORG_DESC' | translate }}</p>
<form (ngSubmit)="searchOrg(domain.value)">
<cnsl-form-field class="org-domain">
<cnsl-label>{{ 'PROJECT.GRANT.CREATE.SEL_ORG_FORMFIELD' | translate }}</cnsl-label>
<input cnslInput #domain />
</cnsl-form-field>
<button [disabled]="domain.value.length === 0" color="primary" type="submit" class="domain-button" mat-raised-button>
{{ 'PROJECT.GRANT.CREATE.SEL_ORG_BUTTON' | translate }}
</button>
</form>
<span *ngIf="org"> {{ 'PROJECT.GRANT.CREATE.FOR_ORG' | translate }} {{ org.name }} </span>
<cnsl-search-org-autocomplete class="block" (selectionChanged)="selectOrg($event)"> </cnsl-search-org-autocomplete>
<span *ngIf="org"> {{ 'PROJECT.GRANT.CREATE.FOR_ORG' | translate }} {{ org.name }} - {{ org.primaryDomain }} </span>
</ng-container>
<ng-container *ngIf="currentCreateStep === 2">
@ -37,7 +25,15 @@
<div class="btn-container">
<ng-container *ngIf="currentCreateStep === 1">
<button [disabled]="!org" (click)="next()" color="primary" mat-raised-button class="big-button" cdkFocusInitial>
<button
[disabled]="!org"
(click)="next()"
color="primary"
mat-raised-button
class="big-button"
cdkFocusInitial
data-e2e="project-grant-continue"
>
{{ 'ACTIONS.CONTINUE' | translate }}
</button>
</ng-container>
@ -46,7 +42,15 @@
<button (click)="previous()" mat-stroked-button class="small-button">
{{ 'ACTIONS.BACK' | translate }}
</button>
<button color="primary" [disabled]="!org" (click)="addGrant()" mat-raised-button class="big-button" cdkFocusInitial>
<button
color="primary"
[disabled]="!org"
(click)="addGrant()"
mat-raised-button
class="big-button"
cdkFocusInitial
data-e2e="save-project-grant-button"
>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</ng-container>

View File

@ -88,6 +88,10 @@ export class ProjectGrantCreateComponent implements OnInit, OnDestroy {
}
}
public selectOrg(org: Org.AsObject): void {
this.org = org;
}
public selectRoles(roles: string[]): void {
this.rolesKeyList = roles;
}

View File

@ -15,6 +15,7 @@ import { InputModule } from 'src/app/modules/input/input.module';
import { ProjectRolesTableModule } from 'src/app/modules/project-roles-table/project-roles-table.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { SearchOrgAutocompleteModule } from 'src/app/modules/search-org-autocomplete/search-org-autocomplete.module';
import { ProjectGrantCreateRoutingModule } from './project-grant-create-routing.module';
import { ProjectGrantCreateComponent } from './project-grant-create.component';
@ -38,6 +39,7 @@ import { ProjectGrantCreateComponent } from './project-grant-create.component';
MatProgressSpinnerModule,
FormsModule,
TranslateModule,
SearchOrgAutocompleteModule,
],
})
export default class ProjectGrantCreateModule {}

View File

@ -17,6 +17,7 @@
class="cnsl-action-button"
mat-raised-button
[routerLink]="['/projects', projectId, 'projectgrants', 'create']"
data-e2e="create-project-grant-button"
>
<mat-icon class="icon">add</mat-icon>
<span>{{ 'ACTIONS.NEW' | translate }}</span>

View File

@ -12,15 +12,15 @@
<div class="newrole">
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'PROJECT.ROLE.KEY' | translate }}</cnsl-label>
<input cnslInput formControlName="key" />
<input cnslInput formControlName="key" data-e2e="role-key-input" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'PROJECT.ROLE.DISPLAY_NAME' | translate }}</cnsl-label>
<input cnslInput formControlName="displayName" />
<input cnslInput formControlName="displayName" data-e2e="role-display-name-input" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'PROJECT.ROLE.GROUP' | translate }}</cnsl-label>
<input cnslInput formControlName="group" />
<input cnslInput formControlName="group" data-e2e="role-group-input" />
</cnsl-form-field>
</div>
<button
@ -36,7 +36,14 @@
</ng-container>
</div>
<button class="add-line-btn" color="primary" type="button" mat-stroked-button (click)="addEntry()">
<button
class="add-line-btn"
color="primary"
type="button"
data-e2e="new-project-role-button"
mat-stroked-button
(click)="addEntry()"
>
{{ 'PROJECT.ROLE.ADDNEWLINE' | translate }}
</button>

View File

@ -1,3 +1,3 @@
export const supportedLanguages = ['de', 'en', 'es', 'fr', 'it', 'ja', 'pl', 'zh', 'bg'];
export const supportedLanguagesRegexp: RegExp = /de|en|es|fr|it|ja|pl|zh|bg/;
export const supportedLanguages = ['de', 'en', 'es', 'fr', 'it', 'ja', 'pl', 'zh', 'bg', 'pt', 'mk'];
export const supportedLanguagesRegexp: RegExp = /de|en|es|fr|it|ja|pl|zh|bg|pt|mk/;
export const fallbackLanguage: string = 'en';

View File

@ -1034,7 +1034,9 @@
"ja": "日本語",
"pl": "Полски",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"SMTP": {
"TITLE": "SMTP настройки",
@ -1234,7 +1236,9 @@
"ja": "日本語",
"pl": "Полски",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"KEYS": {
"emailVerificationDoneText": "Проверката на имейл е извършена",
@ -1333,6 +1337,8 @@
"ALLOWREGISTER_DESC": "Ако опцията е избрана, в входа се появява допълнителна стъпка за регистрация на потребител.",
"FORCEMFA": "Сила MFA",
"FORCEMFA_DESC": "Ако опцията е избрана, потребителите трябва да конфигурират втори фактор за влизане.",
"FORCEMFALOCALONLY": "Принудително MFA за локални потребители",
"FORCEMFALOCALONLY_DESC": "Ако е избрана опцията, локалните удостоверени потребители трябва да конфигурират втори фактор за влизане.",
"HIDEPASSWORDRESET": "Скриване на нулиране на парола",
"HIDEPASSWORDRESET_DESC": "Ако опцията е избрана, потребителят не може да нулира паролата си в процеса на влизане.",
"HIDELOGINNAMESUFFIX": "Скриване на суфикса на името за влизане",
@ -1500,12 +1506,11 @@
"SEL_PROJECT": "Търсене на проект",
"SEL_ROLES": "Изберете ролите, които искате да бъдат добавени към субсидията",
"SEL_USER": "Изберете потребители",
"SEL_ORG": "Задайте домейна",
"SEL_ORG_DESC": "Въведете пълния домейн, за да посочите организацията, която да предоставите.",
"ORG_TITLE": "Организация",
"SEL_ORG": "Търсете организация",
"SEL_ORG_DESC": "Потърсете организацията за отпускане.",
"ORG_DESCRIPTION": "На път сте да предоставите потребител за организацията {{name}}.",
"ORG_DESCRIPTION_DESC": "Превключете контекста в заглавката по-горе, за да предоставите потребител за друга организация.",
"SEL_ORG_FORMFIELD": "Пълен домейн",
"SEL_ORG_FORMFIELD": "Организация",
"SEL_ORG_BUTTON": "Организация на търсенето",
"FOR_ORG": "Безвъзмездната помощ е създадена за:"
},
@ -1777,6 +1782,8 @@
"DELETE": "Изтрий",
"DELETE_TITLE": "Изтриване на IDP",
"DELETE_DESCRIPTION": "На път сте да изтриете доставчик на самоличност. ",
"REMOVE_WARN_TITLE": "Премахване на IDP",
"REMOVE_WARN_DESCRIPTION": "На път сте да премахнете доставчик на самоличност. Това ще премахне избора на наличен IDP за вашите потребители и вече регистрираните потребители няма да могат да влязат отново. Сигурни ли сте, че ще продължите?",
"DELETE_SELECTION_TITLE": "Изтриване на IDP",
"DELETE_SELECTION_DESCRIPTION": "На път сте да изтриете доставчик на самоличност. ",
"EMPTY": "Няма наличен IDP",
@ -2099,7 +2106,9 @@
"ja": "日本語",
"pl": "Полски",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "португалски",
"mk": "Македонски"
},
"MEMBER": {
"ADD": "Добавяне на мениджър",

View File

@ -1040,7 +1040,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"SMTP": {
"TITLE": "SMTP Einstellungen",
@ -1240,7 +1242,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"KEYS": {
"emailVerificationDoneText": "Email Verification erfolgreich",
@ -1337,8 +1341,10 @@
"ALLOWUSERNAMEPASSWORD_DESC": "Der konventionelle Login mit Benutzername und Passwort wird erlaubt.",
"ALLOWEXTERNALIDP_DESC": "Der Login wird für die darunter liegenden Identitätsanbieter erlaubt.",
"ALLOWREGISTER_DESC": "Ist die Option gewählt, erscheint im Login ein zusätzlicher Schritt zum Registrieren eines Benutzers.",
"FORCEMFA": "Mfa erzwingen",
"FORCEMFA": "MFA erzwingen",
"FORCEMFA_DESC": "Ist die Option gewählt, müssen Benutzer einen zweiten Faktor für den Login verwenden.",
"FORCEMFALOCALONLY": "MFA für lokale Users erzwingen",
"FORCEMFALOCALONLY_DESC": "Ist die Option gewählt, müssen lokal authentifizierte Benutzer einen zweiten Faktor für den Login verwenden.",
"HIDEPASSWORDRESET": "Passwort vergessen ausblenden",
"HIDEPASSWORDRESET_DESC": "Ist die Option gewählt, ist es nicht möglich im Login das Passwort zurück zusetzen via Passwort vergessen Link.",
"HIDELOGINNAMESUFFIX": "Loginname Suffix ausblenden",
@ -1506,13 +1512,11 @@
"SEL_ROLES": "Selektiere die gewünschten Rollen für das Erstellen der Berechtigung.",
"SEL_PROJECT": "Suchen nach Projekt",
"SEL_USER": "Benutzer auswählen",
"SEL_ORG": "Suchen nach Domain",
"SEL_ORG_DESC": "Gebe die vollständige Domain ein, um Resultate zu erhalten.",
"ORG_TITLE": "Organisation",
"SEL_ORG": "Durchsuchen Sie eine Organisation",
"SEL_ORG_DESC": "Suchen Sie nach der zu gewährenden Organisation.",
"ORG_DESCRIPTION": "Du bist im Begriff, einen Benutzer für die Organisation {{name}} zu berechtigen.",
"ORG_DESCRIPTION_DESC": "Wechsle den Kontext, um die Organisation zu wechseln.",
"SEL_ORG_FORMFIELD": "Vollständige Domain",
"SEL_ORG_BUTTON": "Suche Organisation",
"SEL_ORG_FORMFIELD": "Organisation",
"FOR_ORG": "Die Berechtigung wird erstellt für:"
},
"DETAIL": {
@ -1784,6 +1788,8 @@
"DELETE": "Löschen",
"DELETE_TITLE": "IDP löschen",
"DELETE_DESCRIPTION": "Sie sind im Begriff einen Identitätsanbieter zu löschen. Die dadurch hervorgerufenen Änderungen sind unwiderruflich. Wollen Sie dies wirklich tun?",
"REMOVE_WARN_TITLE": "IDP entfernen?",
"REMOVE_WARN_DESCRIPTION": "Sie sind dabei, einen Identitätsanbieter zu entfernen. Dadurch wird die Auswahl des verfügbaren IDP für Ihre Benutzer entfernt und bereits registrierte Benutzer können sich nicht erneut anmelden. Wollen Sie wirklich fortfahren?",
"DELETE_SELECTION_TITLE": "Identitätsanbieter löschen",
"DELETE_SELECTION_DESCRIPTION": "Sie sind im Begriff mehrere Identitätsanbieter zu löschen. Die dadurch hervorgerufenen Änderungen sind unwiderruflich. Wollen Sie dies wirklich tun?",
"EMPTY": "Kein IDP vorhanden",
@ -2109,7 +2115,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"MEMBER": {
"ADD": "Manager hinzufügen",

View File

@ -486,8 +486,8 @@
"CHANGEUSERNAME": "modify",
"CHANGEUSERNAME_TITLE": "Change username",
"CHANGEUSERNAME_DESC": "Enter the new name in the field below.",
"FIRSTNAME": "First Name",
"LASTNAME": "Last Name",
"FIRSTNAME": "Given Name",
"LASTNAME": "Family Name",
"NICKNAME": "Nickname",
"DISPLAYNAME": "Display Name",
"PREFERRED_LANGUAGE": "Language",
@ -1041,7 +1041,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"SMTP": {
"TITLE": "SMTP Settings",
@ -1241,7 +1243,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"KEYS": {
"emailVerificationDoneText": "Email verification done",
@ -1294,8 +1298,8 @@
"PC": "Password Change"
},
"CHIPS": {
"firstname": "Firstname",
"lastname": "Lastname",
"firstname": "Given name",
"lastname": "Family name",
"code": "Code",
"preferredLoginName": "Preferred Login Name",
"displayName": "Displayname",
@ -1340,6 +1344,8 @@
"ALLOWREGISTER_DESC": "If the option is selected, an additional step for registering a user appears in the login.",
"FORCEMFA": "Force MFA",
"FORCEMFA_DESC": "If the option is selected, users have to configure a second factor for login.",
"FORCEMFALOCALONLY": "Force MFA for local authenticated users",
"FORCEMFALOCALONLY_DESC": "If the option is selected, local authenticated users have to configure a second factor for login.",
"HIDEPASSWORDRESET": "Hide Password reset",
"HIDEPASSWORDRESET_DESC": "If the option is selected, the user can't reset his password in the login process.",
"HIDELOGINNAMESUFFIX": "Hide Loginname suffix",
@ -1499,7 +1505,7 @@
"GRANT": {
"EMPTY": "No granted organization.",
"TITLE": "Project Grants",
"DESCRIPTION": "Allow an other organization to use your project.",
"DESCRIPTION": "Allow another organization to use your project.",
"EDITTITLE": "Edit roles",
"CREATE": {
"TITLE": "Create Organization Grant",
@ -1507,13 +1513,11 @@
"SEL_PROJECT": "Search for a project",
"SEL_ROLES": "Select the roles you want to be added to the grant",
"SEL_USER": "Select users",
"SEL_ORG": "Set the domain",
"SEL_ORG_DESC": "Enter the complete domain to specify the organization to grant.",
"ORG_TITLE": "Organization",
"SEL_ORG": "Search an organization",
"SEL_ORG_DESC": "Search the organization to grant.",
"ORG_DESCRIPTION": "You are about to grant a user for the organization {{name}}.",
"ORG_DESCRIPTION_DESC": "Switch the context in the header above to grant a user for another organization.",
"SEL_ORG_FORMFIELD": "Complete Domain",
"SEL_ORG_BUTTON": "Search Organization",
"SEL_ORG_FORMFIELD": "Organization",
"FOR_ORG": "The grant is created for:"
},
"DETAIL": {
@ -1766,8 +1770,8 @@
"DISPLAYNAMEATTRIBUTE": "Displayname attribute",
"EMAILATTRIBUTEATTRIBUTE": "Email attribute",
"EMAILVERIFIEDATTRIBUTE": "Email verified attribute",
"FIRSTNAMEATTRIBUTE": "Firstname attribute",
"LASTNAMEATTRIBUTE": "Lastname attribute",
"FIRSTNAMEATTRIBUTE": "Given name attribute",
"LASTNAMEATTRIBUTE": "Family name attribute",
"NICKNAMEATTRIBUTE": "Nickname attribute",
"PHONEATTRIBUTE": "Phone attribute",
"PHONEVERIFIEDATTRIBUTE": "Phone verified attribute",
@ -1784,6 +1788,8 @@
"DELETE": "Delete",
"DELETE_TITLE": "Delete IDP",
"DELETE_DESCRIPTION": "You are about to delete an identity provider. The resulting changes are irrevocable. Do you really want to do this?",
"REMOVE_WARN_TITLE": "Remove IDP",
"REMOVE_WARN_DESCRIPTION": "You are about to remove an identity provider. This will remove the selection of the available IDP for your users and already registered users won't be able to login again. Are you sure to continue?",
"DELETE_SELECTION_TITLE": "Delete IDP",
"DELETE_SELECTION_DESCRIPTION": "You are about to delete an identity provider. The resulting changes are irrevocable. Do you really want to do this?",
"EMPTY": "No IDP available",
@ -2106,7 +2112,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"MEMBER": {
"ADD": "Add a Manager",

View File

@ -1041,7 +1041,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"SMTP": {
"TITLE": "Ajustes SMTP",
@ -1241,7 +1243,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"KEYS": {
"emailVerificationDoneText": "Verificación de email realizada",
@ -1340,6 +1344,8 @@
"ALLOWREGISTER_DESC": "Si esta opción es seleccionada, aparece un paso adicional durante el inicio de sesión para registrar un usuario.",
"FORCEMFA": "Forzar MFA",
"FORCEMFA_DESC": "Si esta opción es seleccionada, los usuarios tendrán que configurar un doble factor para iniciar sesión.",
"FORCEMFALOCALONLY": "Forzar MFA para usuarios locales",
"FORCEMFALOCALONLY_DESC": "Si esta opción es seleccionada, los usuarios autenticados localmente tendrán que configurar un doble factor para iniciar sesión",
"HIDEPASSWORDRESET": "Ocultar restablecer contraseña",
"HIDEPASSWORDRESET_DESC": "Si esta opción es seleccionada, el usuario no podrá restablecer su contraseña en el proceso de inicio de sesión.",
"HIDELOGINNAMESUFFIX": "Ocultar sufijo del nombre de inicio de sesión",
@ -1507,13 +1513,11 @@
"SEL_PROJECT": "Buscar un proyecto",
"SEL_ROLES": "Selecciona los roles que quieres que se añadan a la concesión",
"SEL_USER": "Seleccionar usuarios",
"SEL_ORG": "Establecer el dominio",
"SEL_ORG_DESC": "Introduce el dominio completo para especificar la organización concesionaria.",
"ORG_TITLE": "Organización",
"SEL_ORG": "Buscar una organización",
"SEL_ORG_DESC": "Busca la organización concesionaria.",
"ORG_DESCRIPTION": "Estás a punto de conceder acceso a un usuario para la organización {{name}}.",
"ORG_DESCRIPTION_DESC": "Cambia el contexto en la cabecera superior para conceder acceso a un usuario para otra organización.",
"SEL_ORG_FORMFIELD": "Completar dominio",
"SEL_ORG_BUTTON": "Buscar organización",
"SEL_ORG_FORMFIELD": "Organización",
"FOR_ORG": "La concesión se creó para:"
},
"DETAIL": {
@ -1784,6 +1788,8 @@
"DELETE": "Borrar",
"DELETE_TITLE": "Borrar IDP",
"DELETE_DESCRIPTION": "Estás a punto de borrar un proveedor de identidad. Los cambios son irrevocables. ¿Estás seguro de que quieres hacer esto?",
"REMOVE_WARN_TITLE": "Quitar desplazado interno",
"REMOVE_WARN_DESCRIPTION": "Está a punto de eliminar un proveedor de identidad. Esto eliminará la selección del IDP disponible para sus usuarios y los usuarios ya registrados no podrán volver a iniciar sesión. ¿Estás seguro de continuar?",
"DELETE_SELECTION_TITLE": "Borrar IDP",
"DELETE_SELECTION_DESCRIPTION": "Estás a punto de borrar un proveedor de identidad. Los cambios resultantes son irrevocables. ¿Estás seguro de que quieres hacer esto?",
"EMPTY": "No hay IDP disponible",
@ -2106,7 +2112,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"MEMBER": {
"ADD": "Añadir un Mánager",

View File

@ -1040,7 +1040,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"SMTP": {
"TITLE": "Paramètres SMTP",
@ -1240,7 +1242,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"KEYS": {
"emailVerificationDoneText": "Vérification de l'email effectuée",
@ -1339,6 +1343,8 @@
"ALLOWREGISTER_DESC": "Si l'option est sélectionnée, une étape supplémentaire pour l'enregistrement d'un utilisateur apparaît dans la connexion.",
"FORCEMFA": "Forcer MFA",
"FORCEMFA_DESC": "Si l'option est sélectionnée, les utilisateurs doivent configurer un deuxième facteur pour la connexion.",
"FORCEMFALOCALONLY": "Forcer MFA pour les utilisateurs locaux",
"FORCEMFALOCALONLY_DESC": "Si l'option est sélectionnée, les utilisateurs locaux authentifiés doivent configurer un deuxième facteur pour la connexion.",
"HIDEPASSWORDRESET": "Masquer la réinitialisation du mot de passe",
"HIDEPASSWORDRESET_DESC": "Si l'option est sélectionnée, l'utilisateur ne peut pas réinitialiser son mot de passe lors du processus de connexion.",
"HIDELOGINNAMESUFFIX": "Masquer le suffixe du nom de connexion",
@ -1506,13 +1512,11 @@
"SEL_PROJECT": "Rechercher un projet",
"SEL_ROLES": "Sélectionnez les rôles que vous souhaitez ajouter à l'autorisation.",
"SEL_USER": "Sélectionnez les utilisateurs",
"SEL_ORG": "Définir le domaine",
"SEL_ORG_DESC": "Entrez le domaine complet pour spécifier l'organisation à accorder.",
"ORG_TITLE": "Organisation",
"SEL_ORG": "Rechercher une organisation",
"SEL_ORG_DESC": "Rechercher l'organisme à accorder",
"ORG_DESCRIPTION": "Vous êtes sur le point d'accorder un utilisateur pour l'organisation{{name}}.",
"ORG_DESCRIPTION_DESC": "Changez le contexte dans l'en-tête ci-dessus pour accorder un utilisateur pour une autre organisation.",
"SEL_ORG_FORMFIELD": "Domaine complet",
"SEL_ORG_BUTTON": "Rechercher une organisation",
"SEL_ORG_FORMFIELD": "Organisation",
"FOR_ORG": "L'autorisation est créée pour"
},
"DETAIL": {
@ -1788,6 +1792,8 @@
"DELETE": "Supprimer",
"DELETE_TITLE": "Supprimer Idp",
"DELETE_DESCRIPTION": "Vous êtes sur le point de supprimer un fournisseur d'identité. Les changements qui en résultent sont irrévocables. Voulez-vous vraiment le faire ?",
"REMOVE_WARN_TITLE": "Supprimer le fournisseur d'identité",
"REMOVE_WARN_DESCRIPTION": "Vous êtes sur le point de supprimer un fournisseur d'identité. Cela supprimera la sélection de l'IDP disponible pour vos utilisateurs et les utilisateurs déjà enregistrés ne pourront plus se reconnecter. Êtes-vous sûr de continuer ?",
"DELETE_SELECTION_TITLE": "Supprimer Idp",
"DELETE_SELECTION_DESCRIPTION": "Vous êtes sur le point de supprimer un fournisseur d'identité. Les changements qui en résultent sont irrévocables. Voulez-vous vraiment le faire ?",
"EMPTY": "Aucun IDP disponible",
@ -2098,7 +2104,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"MEMBER": {
"ADD": "Ajouter un manager",

View File

@ -1040,7 +1040,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"SMTP": {
"TITLE": "Impostazioni SMTP",
@ -1240,7 +1242,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"KEYS": {
"emailVerificationDoneText": "Verifica dell'e-mail terminata con successo.",
@ -1339,6 +1343,8 @@
"ALLOWREGISTER_DESC": "Se l'opzione \u00e8 selezionata, nel login apparirà un passo aggiuntivo per la registrazione di un utente.",
"FORCEMFA": "Forza MFA",
"FORCEMFA_DESC": "Se l'opzione \u00e8 selezionata, gli utenti devono configurare un secondo fattore per il login.",
"FORCEMFALOCALONLY": "Forza MFA per gli utenti locali",
"FORCEMFALOCALONLY_DESC": "Se l'opzione è selezionata, gli utenti locali autenticati devono configurare un secondo fattore per l'accesso.",
"HIDEPASSWORDRESET": "Nascondi ripristino della password",
"HIDEPASSWORDRESET_DESC": "Se l'opzione \u00e8 selezionata, l'utente non pu\u00f2 resettare la sua password nel interfaccia login.",
"HIDELOGINNAMESUFFIX": "Nascondi il suffisso del nome utente",
@ -1506,13 +1512,11 @@
"SEL_PROJECT": "Cerca un progetto",
"SEL_ROLES": "Seleziona i ruoli che vuoi aggiungere",
"SEL_USER": "Seleziona utenti",
"SEL_ORG": "Impostare il dominio",
"SEL_ORG_DESC": "Inserisci il dominio completo per specificare l'organizzazione da concedere.",
"ORG_TITLE": "Organizzazione",
"SEL_ORG": "Cerca un'organizzazione",
"SEL_ORG_DESC": "Cerca l'organizzazione da concedere.",
"ORG_DESCRIPTION": "Stai per concedere un utente per l'organizzazione {{name}}.",
"ORG_DESCRIPTION_DESC": "Cambia il contesto nell'intestazione qui sopra per concedere un utente per un'altra organizzazione.",
"SEL_ORG_FORMFIELD": "Dominio completo",
"SEL_ORG_BUTTON": "Ricerca organizzazione",
"SEL_ORG_FORMFIELD": "Organizzazione",
"FOR_ORG": "Org grant \u00e8 creato per:"
},
"DETAIL": {
@ -1788,6 +1792,8 @@
"DELETE": "Rimuovi IDP",
"DELETE_TITLE": "Rimuovi IDP",
"DELETE_DESCRIPTION": "Stai per rimuovere un fornitore di identit\u00e0. I cambiamenti risultanti sono irrevocabili. Vuoi davvero farlo?",
"REMOVE_WARN_TITLE": "Rimuovi IDP",
"REMOVE_WARN_DESCRIPTION": "Stai per rimuovere un provider di identità. Questo rimuoverà la selezione dell'IDP disponibile per i tuoi utenti e gli utenti già registrati non potranno accedere nuovamente. Sei sicuro di continuare?",
"DELETE_SELECTION_TITLE": "Rimuovere IDP",
"DELETE_SELECTION_DESCRIPTION": "Stai per rimuovere un fornitore di identit\u00e0. I cambiamenti risultanti sono irrevocabili. Vuoi davvero farlo?",
"EMPTY": "Nessun IDP disponible",
@ -2110,7 +2116,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"MEMBER": {
"ADD": "Aggiungi un manager",

View File

@ -1041,7 +1041,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"SMTP": {
"TITLE": "SMTP設定",
@ -1236,7 +1238,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"KEYS": {
"emailVerificationDoneText": "メール認証が完了しました",
@ -1335,6 +1339,8 @@
"ALLOWREGISTER_DESC": "このオプションが選択されている場合、ユーザーを登録するための追加のステップがログインに表示されます。",
"FORCEMFA": "MFAを強制する",
"FORCEMFA_DESC": "このオプションが選択されている場合、ユーザーはログイン用の二要素認証を構成する必要があります。",
"FORCEMFALOCALONLY": "ローカル ユーザーに MFA を強制する",
"FORCEMFALOCALONLY_DESC": "オプションが選択されている場合、ローカル認証されたユーザーはログインの 2 番目の要素を構成する必要があります。",
"HIDEPASSWORDRESET": "パスワードリセットを非表示にする",
"HIDEPASSWORDRESET_DESC": "このオプションが選択されている場合、ユーザーはログイン過程ででパスワードをリセットできません。",
"HIDELOGINNAMESUFFIX": "ログイン名の接尾辞を非表示にする",
@ -1502,13 +1508,11 @@
"SEL_PROJECT": "プロジェクトを検索する",
"SEL_ROLES": "許可するロールを選択する",
"SEL_USER": "ユーザーを選択する",
"SEL_ORG": "ドメインを設定する",
"SEL_ORG_DESC": "完全なドメインを入力して、アクセスを許可する組織を指定する。",
"ORG_TITLE": "組織",
"SEL_ORG": "組織を検索する",
"SEL_ORG_DESC": "付与する組織を検索する",
"ORG_DESCRIPTION": "組織 {{name}} にユーザーをグラントします。",
"ORG_DESCRIPTION_DESC": "上記のヘッダーのコンテキストを切り替えることで、別組織のユーザーにグラントできます。",
"SEL_ORG_FORMFIELD": "完全なドメイン",
"SEL_ORG_BUTTON": "組織を検索する",
"SEL_ORG_FORMFIELD": "組織",
"FOR_ORG": "グラントが以下に対して作成されます:"
},
"DETAIL": {
@ -1779,6 +1783,8 @@
"DELETE": "削除",
"DELETE_TITLE": "IDPの削除",
"DELETE_DESCRIPTION": "IDプロバイダーを削除しようとしています。変更は取消できません。本当によろしいですか",
"REMOVE_WARN_TITLE": "IDPを削除する",
"REMOVE_WARN_DESCRIPTION": "ID プロバイダーを削除しようとしています。これにより、ユーザーが使用できる IDP の選択が削除され、すでに登録されているユーザーは再度ログインできなくなります。続けてもよろしいですか?",
"DELETE_SELECTION_TITLE": "IDPの削除",
"DELETE_SELECTION_DESCRIPTION": "IDプロバイダーを削除しようとしています。変更は取消できません。本当によろしいですか",
"EMPTY": "IDPは利用できません",
@ -2101,7 +2107,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"MEMBER": {
"ADD": "マネージャーを追加する",

File diff suppressed because it is too large Load Diff

View File

@ -1040,7 +1040,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"SMTP": {
"TITLE": "Ustawienia SMTP",
@ -1240,7 +1242,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"KEYS": {
"emailVerificationDoneText": "Weryfikacja adresu e-mail zakończona",
@ -1339,6 +1343,8 @@
"ALLOWREGISTER_DESC": "Jeśli ta opcja jest zaznaczona, pojawi się dodatkowy krok rejestracji użytkownika w procesie logowania.",
"FORCEMFA": "Wymuś MFA",
"FORCEMFA_DESC": "Jeśli ta opcja jest zaznaczona, użytkownicy muszą skonfigurować drugi czynnik logowania.",
"FORCEMFALOCALONLY": "Wymuś MFA dla lokalnych użytkowników",
"FORCEMFALOCALONLY_DESC": "Jeśli ta opcja jest zaznaczona, lokalni uwierzytelnieni użytkownicy muszą skonfigurować drugi czynnik logowania.",
"HIDEPASSWORDRESET": "Ukryj reset hasła",
"HIDEPASSWORDRESET_DESC": "Jeśli ta opcja jest zaznaczona, użytkownik nie może zresetować swojego hasła w procesie logowania.",
"HIDELOGINNAMESUFFIX": "Ukryj sufiks nazwy użytkownika",
@ -1506,13 +1512,11 @@
"SEL_PROJECT": "Wyszukaj projekt",
"SEL_ROLES": "Wybierz role, które mają zostać dodane do udzielenia ",
"SEL_USER": "Wybierz użytkowników",
"SEL_ORG": "Ustaw domenę",
"SEL_ORG_DESC": "Wprowadź pełną domenę, aby określić organizację, której chcesz udzielić dostępu.",
"ORG_TITLE": "Organizacja",
"SEL_ORG": "Wyszukaj organizację",
"SEL_ORG_DESC": "Wyszukaj organizację, której chcesz przyznać.",
"ORG_DESCRIPTION": "Masz zamiar udzielić użytkownikowi dostęp dla organizacji {{name}}.",
"ORG_DESCRIPTION_DESC": "Przełącz kontekst w nagłówku powyżej, aby udzielić użytkownikowi dostępu dla innej organizacji.",
"SEL_ORG_FORMFIELD": "Pełna domena",
"SEL_ORG_BUTTON": "Wyszukaj organizację",
"SEL_ORG_FORMFIELD": "Organizacja",
"FOR_ORG": "Dostęp udzielany:"
},
"DETAIL": {
@ -1788,6 +1792,8 @@
"DELETE": "Usuń",
"DELETE_TITLE": "Usuń dostawcę tożsamości",
"DELETE_DESCRIPTION": "Zamierzasz usunąć dostawcę tożsamości. Spowoduje to nieodwracalne zmiany. Czy na pewno chcesz to zrobić?",
"REMOVE_WARN_TITLE": "Usuń dostawcę tożsamości",
"REMOVE_WARN_DESCRIPTION": "Zamierzasz usunąć dostawcę tożsamości. Spowoduje to usunięcie wyboru dostępnego dostawcy tożsamości dla Twoich użytkowników, a już zarejestrowani użytkownicy nie będą mogli zalogować się ponownie. Czy na pewno chcesz kontynuować?",
"DELETE_SELECTION_TITLE": "Usuń dostawcę tożsamości",
"DELETE_SELECTION_DESCRIPTION": "Zamierzasz usunąć dostawcę tożsamości. Spowoduje to nieodwracalne zmiany. Czy na pewno chcesz to zrobić?",
"EMPTY": "Brak dostawcy tożsamości dostępnego",
@ -2110,7 +2116,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"MEMBER": {
"ADD": "Dodaj managera",

File diff suppressed because it is too large Load Diff

View File

@ -1040,7 +1040,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"SMTP": {
"TITLE": "SMTP 设置",
@ -1239,7 +1241,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"KEYS": {
"emailVerificationDoneText": "电子邮件验证完成",
@ -1338,6 +1342,8 @@
"ALLOWREGISTER_DESC": "如果选择了该选项,登录中会出现一个用于注册用户的附加步骤。",
"FORCEMFA": "强制使用 MFA",
"FORCEMFA_DESC": "如果选择该选项,用户必须配置第二身份认证登录因素。",
"FORCEMFALOCALONLY": "对本地用户强制执行 MFA",
"FORCEMFALOCALONLY_DESC": "如果选择该选项,本地经过身份验证的用户必须配置第二个登录因素。",
"HIDEPASSWORDRESET": "隐藏密码重置按钮",
"HIDEPASSWORDRESET_DESC": "如果选择该选项,则用户无法在登录过程中重置其密码。",
"HIDELOGINNAMESUFFIX": "隐藏登录名后缀",
@ -1505,13 +1511,11 @@
"SEL_PROJECT": "搜索项目",
"SEL_ROLES": "选择要添加到授权中的角色",
"SEL_USER": "选择一个或多个用户",
"SEL_ORG": "选择一个组织",
"SEL_ORG_DESC": "输入完整的域以指定要授予的组织。",
"ORG_TITLE": "组织",
"SEL_ORG": "搜索组织",
"SEL_ORG_DESC": "搜索要授予的组织",
"ORG_DESCRIPTION": "您即将授予组织 {{name}} 的用户。",
"ORG_DESCRIPTION_DESC": "切换上面标题中的上下文以授予另一个组织的用户。",
"SEL_ORG_FORMFIELD": "完整域名",
"SEL_ORG_BUTTON": "搜索组织",
"SEL_ORG_FORMFIELD": "组织",
"FOR_ORG": "授予组织:"
},
"DETAIL": {
@ -1787,6 +1791,8 @@
"DELETE": "删除",
"DELETE_TITLE": "删除 IDP",
"DELETE_DESCRIPTION": "您即将删除身份提供者。由此产生的变化是不可撤销的。你真的想这样做吗?",
"REMOVE_WARN_TITLE": "删除国内流离失所者",
"REMOVE_WARN_DESCRIPTION": "您即将删除身份提供者。这将为您的用户删除可用 IDP 的选择,并且已经注册的用户将无法再次登录。您确定要继续吗?",
"DELETE_SELECTION_TITLE": "删除 IDP",
"DELETE_SELECTION_DESCRIPTION": "您即将删除身份提供者。由此产生的变化是不可撤销的。你真的想这样做吗?",
"EMPTY": "没有可用的 IDP",
@ -2097,7 +2103,9 @@
"ja": "日本語",
"pl": "Polski",
"zh": "简体中文",
"bg": "Български"
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
},
"MEMBER": {
"ADD": "添加管理者",

9721
console/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

259
docs/apis/assets/assets.md Executable file
View File

@ -0,0 +1,259 @@
---
title: zitadel/assets
---
## AssetsService
### UploadDefaultLabelPolicyFont()
> UploadDefaultLabelPolicyFont()
POST: /instance/policy/label/font
### GetDefaultLabelPolicyFont()
> GetDefaultLabelPolicyFont()
GET: /instance/policy/label/font
### GetPreviewDefaultLabelPolicyFont()
> GetPreviewDefaultLabelPolicyFont()
GET: /instance/policy/label/font/_preview
### UploadDefaultLabelPolicyIcon()
> UploadDefaultLabelPolicyIcon()
POST: /instance/policy/label/icon
### UploadDefaultLabelPolicyIcon()
> UploadDefaultLabelPolicyIconDark()
POST: /instance/policy/label/icon/dark
### GetDefaultLabelPolicyIcon()
> GetDefaultLabelPolicyIcon()
GET: /instance/policy/label/icon
### GetDefaultLabelPolicyIcon()
> GetDefaultLabelPolicyIconDark()
GET: /instance/policy/label/icon/dark
### GetPreviewDefaultLabelPolicyIcon()
> GetPreviewDefaultLabelPolicyIcon()
GET: /instance/policy/label/icon/_preview
### GetPreviewDefaultLabelPolicyIcon()
> GetPreviewDefaultLabelPolicyIconDark()
GET: /instance/policy/label/icon/dark/_preview
### UploadDefaultLabelPolicyLogo()
> UploadDefaultLabelPolicyLogo()
POST: /instance/policy/label/logo
### UploadDefaultLabelPolicyLogo()
> UploadDefaultLabelPolicyLogoDark()
POST: /instance/policy/label/logo/dark
### GetDefaultLabelPolicyLogo()
> GetDefaultLabelPolicyLogo()
GET: /instance/policy/label/logo
### GetDefaultLabelPolicyLogo()
> GetDefaultLabelPolicyLogoDark()
GET: /instance/policy/label/logo/dark
### GetPreviewDefaultLabelPolicyLogo()
> GetPreviewDefaultLabelPolicyLogo()
GET: /instance/policy/label/logo/_preview
### GetPreviewDefaultLabelPolicyLogo()
> GetPreviewDefaultLabelPolicyLogoDark()
GET: /instance/policy/label/logo/dark/_preview
### UploadOrgLabelPolicyFont()
> UploadOrgLabelPolicyFont()
POST: /org/policy/label/font
### GetOrgLabelPolicyFont()
> GetOrgLabelPolicyFont()
GET: /org/policy/label/font
### GetPreviewOrgLabelPolicyFont()
> GetPreviewOrgLabelPolicyFont()
GET: /org/policy/label/font/_preview
### UploadOrgLabelPolicyIcon()
> UploadOrgLabelPolicyIcon()
POST: /org/policy/label/icon
### UploadOrgLabelPolicyIcon()
> UploadOrgLabelPolicyIconDark()
POST: /org/policy/label/icon/dark
### GetOrgLabelPolicyIcon()
> GetOrgLabelPolicyIcon()
GET: /org/policy/label/icon
### GetOrgLabelPolicyIcon()
> GetOrgLabelPolicyIconDark()
GET: /org/policy/label/icon/dark
### GetPreviewOrgLabelPolicyIcon()
> GetPreviewOrgLabelPolicyIcon()
GET: /org/policy/label/icon/_preview
### GetPreviewOrgLabelPolicyIcon()
> GetPreviewOrgLabelPolicyIconDark()
GET: /org/policy/label/icon/dark/_preview
### UploadOrgLabelPolicyLogo()
> UploadOrgLabelPolicyLogo()
POST: /org/policy/label/logo
### UploadOrgLabelPolicyLogo()
> UploadOrgLabelPolicyLogoDark()
POST: /org/policy/label/logo/dark
### GetOrgLabelPolicyLogo()
> GetOrgLabelPolicyLogo()
GET: /org/policy/label/logo
### GetOrgLabelPolicyLogo()
> GetOrgLabelPolicyLogoDark()
GET: /org/policy/label/logo/dark
### GetPreviewOrgLabelPolicyLogo()
> GetPreviewOrgLabelPolicyLogo()
GET: /org/policy/label/logo/_preview
### GetPreviewOrgLabelPolicyLogo()
> GetPreviewOrgLabelPolicyLogoDark()
GET: /org/policy/label/logo/dark/_preview
### UploadMyUserAvatar()
> UploadMyUserAvatar()
POST: /users/me/avatar
### GetMyUserAvatar()
> GetMyUserAvatar()
GET: /users/me/avatar

View File

@ -36,9 +36,9 @@ The first parameter contains the following fields
- `appendMetadata(string, Any)`
The first parameter represents the key and the second a value which will be stored
- `setFirstName(string)`
Sets the first name
Sets the given name
- `setLastName(string)`
Sets the last name
Sets the family name
- `setNickName(string)`
Sets the nickname
- `setDisplayName(string)`
@ -75,9 +75,9 @@ A user selected **Register** on the overview page after external authentication.
- `metadata`
Array of [*metadata*](./objects#metadata-with-value-as-bytes). This function is deprecated, please use `api.v1.user.appendMetadata`
- `setFirstName(string)`
Sets the first name
Sets the given name
- `setLastName(string)`
Sets the last name
Sets the family name
- `setNickName(string)`
Sets the nick name
- `setDisplayName(string)`

View File

@ -35,7 +35,7 @@ Open the Console (`https://{YourDomain}.zitadel.cloud/ui/console/projects`) and
Then on the project detail page click on new application and enter a name for this app.
Let's call this one `portal-web`.
Select `Web`, continue, `CODE`, then enter `http://localhost:3000/api/auth/callback/zitadel` for the redirect, and `http://localhost:3000` for the post redirect. Then press on `create`.
Because the requests from your NextJS application to ZITADEL are made on the server side, you can safely select `CODE`. With this you still get a secret which is then usable alongside PKCE. Your secret never gets exposed on the browser since it is kept in your NextJS server.
Because the requests from your NextJS application to ZITADEL are made on the server side, you can safely select `CODE`. You will get a secret at the end of the stepper. With NextAuth your secret never gets exposed on the browser since it is kept in your NextJS server.
Copy the "Resource Id" of the project `Portal` as you will need this in your environment configuration file later.

View File

@ -19,7 +19,7 @@ yarn install
then to run the app:
```bash
npm run dev
yarn dev
```
then open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
@ -28,10 +28,11 @@ then open [http://localhost:3000](http://localhost:3000) with your browser to se
Before we can start building our application, we have to do a few configuration steps in ZITADEL Console.
You will need to provide some information about your app.
Navigate to your Project, then add a new application at the top of the page.
Select Web application type and continue.
We recommend you use [Authorization Code](/apis/openidoauth/grant-types#authorization-code) in combination with [Proof Key for Code Exchange (PKCE)](/apis/openidoauth/grant-types#proof-key-for-code-exchange) for all web applications.
As the requests from your application to ZITADEL are made on NextJS serverside, you can select `CODE` in the next step. This makes sure you still get a secret which is then used in combination with PKCE. Note that the secret never gets exposed on the browser and is therefore kept in a confidential environment.
We use [Authorization Code](/apis/openidoauth/grant-types#authorization-code)for our NextJS application.
Select `CODE` in the next step. This makes sure you still get a secret. Note that the secret never gets exposed on the browser and is therefore kept in a confidential environment.
![Create app in console](/img/nextjs/app-create.png)
@ -135,15 +136,11 @@ ZitadelProvider({
...
```
We recommend using the Authentication Code flow secured by PKCE for the Authentication flow.
To be able to connect to ZITADEL, navigate to your Console Projects, create or select an existing project and add your app selecting WEB, then PKCE, and then add `http://localhost:3000/api/auth/callback/zitadel` as redirect url to your app.
To be able to connect to ZITADEL, make sure to add `http://localhost:3000/api/auth/callback/zitadel` as redirect url to your app.
For simplicity reasons we set the default to the one that next-auth provides us. You'll be able to change the redirect later if you want to.
Hit Create, then in the detail view of your application make sure to enable dev mode. Dev mode ensures that you can start an auth flow from a non https endpoint for testing.
> Note that we get a clientId but no clientSecret because it is not needed for our authentication flow.
Now go to Token settings and check the checkbox for **User Info inside ID Token** to get your users name directly on authentication.
### Environment

View File

@ -0,0 +1,48 @@
---
title: Migrate from Generic Provider to specific Identity Provider
sidebar_label: Migrate IDP
---
## Migrate Generic OIDC Provider
You can migrate from a generic OIDC provider to the following supported templates:
- AzureAD
- Google
To migrate, you either use the [Migrate Generic OIDC Identity Provider (Instance)](/docs/apis/resources/admin/admin-service-migrate-generic-oidc-provider#migrate-generic-oidc-identity-provider) or [Migrate Generic OIDC Identity Provider (Organization)](/docs/apis/resources/mgmt/management-service-migrate-generic-oidc-provider#migrate-generic-oidc-identity-provider) API request.
These calls change the type of the provider and don't delete any linked users.
:::note Linked users will not notice the change and be able to login as usual.
:::
### Google Configuration
The available configuration is described in [Google Configuration](./google).
### AzureAD Configuration
The available configuration is described in [AzureAD Configuration](./azure-ad).
## Migrate with Terraform
Please note that you only have to perform this migration if you already have an existing IDP with linked users, that should not loose the connection to the provider.
If that isn't your case please just add a new provider from scratch.
To migrate to a specific provider, you need to follow a few essential steps:
1. Create a desired IDP as Terraform resource for example [Google](https://registry.terraform.io/providers/zitadel/zitadel/latest/docs/resources/idp_google).
2. Make the corresponding API call to [migrate the IDP](./migrate#google-configuration), save the ID of the IDP for the import
3. Before applying the Terraform resources again, import the new IDP resource.
```bash
#resource "zitadel_idp_google" "google" {
# name = "Google"
# client_id = "182902..."
# client_secret = "GOCSPX-*****"
# scopes = ["openid", "profile", "email"]
# is_linking_allowed = false
# is_creation_allowed = true
# is_auto_creation = false
# is_auto_update = true
#}
# terraform import zitadel_idp_google.*resource_name* *id*:*client_secret*
terraform import zitadel_idp_google.google 222302827723096428:GOCSPX-*****
You have now migrated your provider and you should be able to apply the resource again. There should be no changes and the IDP is maintained by Terraform again.

View File

@ -0,0 +1,12 @@
Now that you have started the registration within ZITADEL, you have to register the credentials in the browser.
This requires a call to the browser api and looks something like the following.
Make sure to send the public key credential creation options you got in the previous request from ZITADEL.
```bash
const credential = await navigator.credentials.create({
publicKey: publicKeyCredentialCreationOptions
});
```
For more information about WebAuthN and registering credential flow, read the following guide:
[Registering a WebAuthN Credentials](https://webauthn.guide/#registration)

View File

@ -0,0 +1,46 @@
Your user has successfully authenticated, and now you ask him if he wants to setup MFA to have a more secure account.
When he starts the configuration, first you want him to show the possible methods.
You can either list it implicitly or call the settings service from ZITADEL to get what is configured on the login settings.
More detailed information about the API: [Get Login Settings Documentation](/apis/resources/settings_service/settings-service-get-login-settings)
Request Example:
```bash
curl --request GET \
--url https://$ZITADEL_DOMAIN/v2alpha/settings/login \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''
```
Response Example:
The relevant part for the list is the second factor and multi factor list.
```bash
{
"details": {
"sequence": "293",
"changeDate": "2023-03-29T14:16:55.570482Z",
"resourceOwner": "163840776835432705"
},
"settings": {
"allowUsernamePassword": true,
"allowRegister": true,
"allowExternalIdp": true,
"passkeysType": "PASSKEYS_TYPE_ALLOWED",
"passwordCheckLifetime": "864000s",
"externalLoginCheckLifetime": "864000s",
"mfaInitSkipLifetime": "2592000s",
"secondFactorCheckLifetime": "64800s",
"multiFactorCheckLifetime": "43200s",
"secondFactors": [
"SECOND_FACTOR_TYPE_OTP",
"SECOND_FACTOR_TYPE_U2F"
],
"multiFactors": [
"MULTI_FACTOR_TYPE_U2F_WITH_VERIFICATION"
],
"resourceOwnerType": "RESOURCE_OWNER_TYPE_ORG"
}
}
```

View File

@ -96,13 +96,13 @@ curl --request POST \
"idpId": "218528353504723201",
"rawInformation": {
"User": {
"email": "fabienne@rootd.ch",
"email": "minni@mouse.com",
"email_verified": true,
"family_name": "Bühler",
"given_name": "Fabienne",
"hd": "rootd.ch",
"family_name": "Mouse",
"given_name": "Minnie",
"hd": "mouse.com",
"locale": "de",
"name": "Fabienne Bühler",
"name": "Minnie Mouse",
"picture": "https://lh3.googleusercontent.com/a/AAcKTtf973Q6NH8KzKTMEZELPU9lx45WpQ9FRBuxFdPb=s96-c",
"sub": "111392805975715856637"
}

View File

@ -0,0 +1,191 @@
---
title: Multi-Factor (MFA)
sidebar_label: Multi-Factor (MFA)
---
import MfaOptions from './_list-mfa-options.mdx';
import BrowserRegisterWebAuthN from './_browser_register_webauthn.mdx';
Multi-factor authentication (MFA) is a multi-step account authentication which requires to user to enter more than only the password.
It is highly recommended to use MFA or [Passkeys](./passkey) to make your user accounts more secure.
ZITADEL supports two different Methods:
- Time-based one time password (TOTP), which are Authenticator apps like Google/Microsoft Authenticator, Authy, etc
- Universal Second Factor (U2F), which is authentication with your device like Windows Hello, Apple FaceID, Fingerprint, FIDO2 keys, Yubikey, etc.
## TOTP Registration
### Flow
![Register TOTP](/img/guides/login-ui/register-totp-flow.png)
### List the Possible Methods
<MfaOptions/>
### Start TOTP Registration
The user has selected to setup Time-based One-Time-Password (TOTP).
To show the user the QR to register TOTP with his Authenticator App like Google/Microsoft Authenticator or Authy you have to start the registration on the ZITADEL API.
Generate the QR Code with the URI from the response.
For users that do not have a QR Code reader make sure to also show the secret, to enable manual configuration.
More detailed information about the API: [Start TOTP Registration Documentation](/apis/resources/user_service/user-service-register-totp)
Request Example:
```bash
curl --request POST \
--url https://$ZITADEL_DOMAIN/v2alpha/users/$USER_ID/totp \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''
--header 'Content-Type: application/json' \
--data '{}'
```
Response Example:
```bash
{
"details": {
"sequence": "2",
"changeDate": "2023-06-28",
"resourceOwner": "69629023906488334"
},
"uri": "otpauth://totp/ZITADEL:minni-mouse@mouse.com?algorithm=SHA1&digits=6&issuer=ZITADEL&period=30&secret=TJOPWSDYILLHXFV4MLKNNJOWFG7VSDCK",
"secret": "TJOPWSDYILLHXFV4MLKNNJOWFG7VSDCK"
}
```
### Verify TOTP Registration
When the user has added the account to his authenticator app, he has to enter the code from the App to finish the registration.
This code has to be sent to the verify endpoint in ZITADEL.
More detailed information about the API: [Verify TOTP Documentation](/apis/resources/user_service/user-service-verify-totp-registration)
Request Example:
```bash
curl --request POST \
--url https://$ZITADEL_DOMAIN/v2alpha/users/$USER_ID/totp/_verify \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''
--header 'Content-Type: application/json' \
--data '{
"code": "123456"
}'
```
## U2F Registration
### Flow
![Register U2F](/img/guides/login-ui/register-u2f-flow.png)
### List the Possible Methods
<MfaOptions/>
### Start U2F Registration
The user has selected to setup Universal Second Factor (U2F).
To be able to authenticate in the browser you have to start the u2f registration within ZITADEL.
More detailed information about the API: [Start U2F Registration Documentation](/apis/resources/user_service/user-service-register-u-2-f)
Request Example:
```bash
curl --request POST \
--url https://$ZITADEL_DOMAIN/v2alpha/users/$USER_ID/u2f \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''
--header 'Content-Type: application/json' \
--data '{
"domain": "acme.com"
}'
```
Response Example:
```bash
{
"details": {
"sequence": "2",
"changeDate": "2023-07-03",
"resourceOwner": "69629023906488334"
},
"u2fId": "163840776835432705",
"publicKeyCredentialCreationOptions": {
"publicKey": {
"attestation": "none",
"authenticatorSelection": {
"userVerification": "required"
},
"challenge": "XaMYwWOZ5hj6pwtwJJlpcI-ExkO5TxevBMG4R8DoKQQ",
"excludeCredentials": [
{
"id": "tVp1QfYhT8DkyEHVrv7blnpAo2YJzbZgZNBf7zPs6CI",
"type": "public-key"
}
],
"pubKeyCredParams": [
{
"alg": -7,
"type": "public-key"
}
],
"rp": {
"id": "localhost",
"name": "ZITADEL"
},
"timeout": 300000,
"user": {
"displayName": "Minie Mouse",
"id": "MjE1NTk4MDAwNDY0OTk4OTQw",
"name": "minie-mouse"
}
}
}
}
```
### Register new U2F on current device
<BrowserRegisterWebAuthN/>
### Verify U2F Registration
In the next request you have to verify the U2F within ZITADEL.
Include the public key credential you got from the browser in your request.
You can give the U2F a name, which makes it easier for the user to identify the registered authentication methods.
Example: Google Pixel, iCloud Keychain, Yubikey, etc
More detailed information about the API: [Verify U2F Documentation](/apis/resources/user_service/user-service-verify-u-2-f-registration)
Example Request:
```bash
curl --request POST \
--url https://$ZITADEL_DOMAIN/v2alpha/users/$USER_ID/u2f/$PASSKEY_ID \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"publicKeyCredential": {
"type": "public-key",
"id": "pawVarF4xPxLFmfCnRkwXWeTrKGzabcAi92LEI1WC00",
"rawId": "pawVarF4xPxLFmfCnRkwXWeTrKGzabcAi92LEI1WC00",
"response": {
"attestationObject": "o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZyZjc2lnWEcwRQIgRKS3VpeE9tfExXRzkoUKnG4rQWPvtSSt4YtDGgTx32oCIQDPey-2YJ4uIg-QCM4jj6aE2U3tgMFM_RP7Efx6xRu3JGhhdXRoRGF0YVikSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAADju76085Yhmlt1CEOHkwLQAIKWsFWqxeMT8SxZnwp0ZMF1nk6yhs2m3AIvdixCNVgtNpQECAyYgASFYIMGUDSP2FAQn2MIfPMy7cyB_Y30VqixVgGULTBtFjfRiIlggjUGfQo3_-CrMmH3S-ZQkFKWKnNBQEAMkFtG-9A4zqW0",
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiQlhXdHh0WGxJeFZZa0pHT1dVaUVmM25zby02aXZKdWw2YmNmWHdMVlFIayIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWxob3N0OjgwODAifQ"
}
},
"tokenName": "Google Pixel"
}'
```
You have successfully registered a new U2F to the user.

View File

@ -0,0 +1,313 @@
---
title: Passkeys
sidebar_label: Passkeys
---
Passkeys are a replacement for passwords that provide faster, easier, and more secure sign-ins to websites and apps even across multiple devices.
Unlike passwords, passkeys are phishing-resistant and can improve the user experience and security at the same time.
Passkeys and there underlying protocols are a standard defined by the [FIDO Standard](https://fidoalliance.org/) .
## Register Passkey
### Flow
![Passkey Registration](/img/guides/login-ui/passkey-registration-flow.png)
There are two options to onboard users with passkeys:
1. You send the user a link (email, sms, ...) with an embedded code, so the user is able to register passkey on any capable device
2. You start the passkey registration directly on the current device used by a user
### Send Registration Link
When you want to send a link to your user, that enables him to register a new passkey device, you can choose if ZITADEL sends the code or ZITADEL can return the code in the API response. This way you can send a link through any channel of your choice (email, sms, phone, in-person, postal, ...).
If you asked ZITADEL to send the link to the user please make sure to populate the link with the needed values that point towards your registration UI.
More detailed information about the API: [Send Registration Link Documentation](/apis/resources/user_service/user-service-create-passkey-registration-link)
Request Example:
Send either the sendLink or the returnCode (empty message) in the request body, depending on the use case you have.
```bash
curl --request POST \
--url https://$ZITADEL_DOMAIN/v2alpha/users/$USER_ID/passkeys/registration_link \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"sendLink": {
"urlTemplate": "https://example.com/passkey/register?userID={{.UserID}}&orgID={{.OrgID}}&codeID={{.CodeID}}&code={{.Code}}"
},
"returnCode": {}
}'
```
Response Example:
The code is only filled if returnCode has been requested in the Request.Passkey
```bash
{
"details": {
"sequence": "632",
"changeDate": "2023-06-28T08:09:51.257699Z",
"resourceOwner": "163840776835432705"
},
"code": {
"id": "220526087715684609",
"code": "2KEpIeQGSBBd"
}
}
```
### Start Passkey Registration
When starting the passkey registration you can optionally send the registration code from the step above to ZITADEL to pair it with a specific user.
By specifying the authenticator type you can choose if the passkey should be cross platform or not. Per default all types are allowed:
- PASSKEY_AUTHENTICATOR_UNSPECIFIED
- PASSKEY_AUTHENTICATOR_PLATFORM
- PASSKEY_AUTHENTICATOR_CROSS_PLATFORM
The API response will provide you the public key credential options, this will be used by the browser to obtain a signed challenge.
More detailed information about the API: [Start Passkey Registration Documentation](/apis/resources/user_service/user-service-register-passkey)
Request Example:
The code only has to be filled if the user did get a registration code.
```bash
curl --request POST \
--url https://$ZITADEL_DOMAIN/v2alpha/users/$USER_ID/passkeys \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"code": {
"id": "220526087715684609",
"code": "2KEpIeQGSBBd"
},
"authenticator": "PASSKEY_AUTHENTICATOR_UNSPECIFIED"
}'
```
Response Example:
```bash
{
"details": {
"sequence": "633",
"changeDate": "2023-06-28T08:10:26.725981Z",
"resourceOwner": "163840776835432705"
},
"passkeyId": "220526147258024193",
"publicKeyCredentialCreationOptions": {
"publicKey": {
"attestation": "none",
"authenticatorSelection": {
"userVerification": "required"
},
"challenge": "JM1uLbVQR2xZJ210DA7E-3j0Cd9rHKUSmc8NyIJBtAY",
"pubKeyCredParams": [
{
"alg": -7,
"type": "public-key"
},
{
"alg": -35,
"type": "public-key"
},
{
"alg": -36,
"type": "public-key"
},
{
"alg": -257,
"type": "public-key"
},
{
"alg": -258,
"type": "public-key"
},
{
"alg": -259,
"type": "public-key"
},
{
"alg": -37,
"type": "public-key"
},
{
"alg": -38,
"type": "public-key"
},
{
"alg": -39,
"type": "public-key"
},
{
"alg": -8,
"type": "public-key"
}
],
"rp": {
"id": "example.domain.com",
"name": "ZITADEL"
},
"timeout": 300000,
"user": {
"displayName": "Minnie Mouse",
"id": "MjE4NjYyNTk2OTE4NjQwODk3",
"name": "minni-mouse@mouse.com"
}
}
}
}
```
### Register new Passkey on current device
<BrowserRegisterWebAuthN/>
### Verify Passkey in ZITADEL
In the next request you have to verify the Passkey within ZITADEL.
Include the public key credential you got from the browser in your request.
You can give the Passkey a name, which makes it easier for the user to identify the registered authentication methods.
Example: Google Pixel, iCloud Keychain, Yubikey, etc
More detailed information about the API: [Verify Passkey Registration Documentation](/apis/resources/user_service/user-service-verify-passkey-registration)
Example Request:
```bash
curl --request POST \
--url https://$ZITADEL_DOMAIN/v2alpha/users/$USER_ID/passkeys/$PASSKEY_ID \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"publicKeyCredential": {
"type": "public-key",
"id": "pawVarF4xPxLFmfCnRkwXWeTrKGzabcAi92LEI1WC00",
"rawId": "pawVarF4xPxLFmfCnRkwXWeTrKGzabcAi92LEI1WC00",
"response": {
"attestationObject": "o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZyZjc2lnWEcwRQIgRKS3VpeE9tfExXRzkoUKnG4rQWPvtSSt4YtDGgTx32oCIQDPey-2YJ4uIg-QCM4jj6aE2U3tgMFM_RP7Efx6xRu3JGhhdXRoRGF0YVikSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAADju76085Yhmlt1CEOHkwLQAIKWsFWqxeMT8SxZnwp0ZMF1nk6yhs2m3AIvdixCNVgtNpQECAyYgASFYIMGUDSP2FAQn2MIfPMy7cyB_Y30VqixVgGULTBtFjfRiIlggjUGfQo3_-CrMmH3S-ZQkFKWKnNBQEAMkFtG-9A4zqW0",
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiQlhXdHh0WGxJeFZZa0pHT1dVaUVmM25zby02aXZKdWw2YmNmWHdMVlFIayIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWxob3N0OjgwODAifQ"
}
},
"passkeyName": "Google Pixel"
}'
```
You have successfully registered a new passkey to the user.
Next step is to authenticate the user with the new registered passkey.
## Login with Passkey
### Flow
![Passkey Login](/img/guides/login-ui/passkey-login-flow.png)
### Create Session
First step is to ask the user for his username and create a new session with the ZITADEL API.
When creating the new session make sure to include the challenge for passkey.
The response will include the public key credential request options for the passkey in the challenges.
More detailed information about the API: [Create Session Documentation](/apis/resources/session_service/session-service-create-session)
Example Request:
```bash
curl --request POST \
--url https://$ZITADEL_DOMAIN/v2alpha/sessions \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"checks": {
"user": {
"loginName": "minni-mouse@mouse.com"
}
},
"metadata": {},
"challenges": [
"CHALLENGE_KIND_PASSKEY"
]
}'
```
Example Response:
```bash
{
"details": {
"sequence": "2",
"changeDate": "2023-06-27",
"resourceOwner": "69629023906488334"
},
"sessionId": "d654e6ba-70a3-48ef-a95d-37c8d8a7901a",
"sessionToken": "string",
"challenges": {
"passkey": {
"publicKeyCredentialRequestOptions": {
"publicKey": {
"allowCredentials": [
{
"id": "ATmqBg-99qyOZk2zloPdJQyS2R7IkFT7v9Hoos_B_nM",
"type": "public-key"
}
],
"challenge": "GAOHYz2jE69kJMYo6Laij8yWw9-dKKgbViNhfuy0StA",
"rpId": "example.domain.com",
"timeout": 300000,
"userVerification": "required"
}
}
}
}
}
```
### Signing in Browser
After starting the passkey authentication on the side of ZITADEL you have to challenge the browser.
To do this you need to call the browser API to get the credentials.
Make sure to send the public key credential request options you got from ZITADEL.
```bash
const credential = await navigator.credentials.get({
publicKey: publicKeyCredentialRequestOptions
});
```
Read the [WebAuthN Guide](https://webauthn.guide/#authentication) for more information about "Authenticating with a WebAuthN Credential".
### Update Session with Passkey
Now that you have successfully authenticated in the browser, you can update the session of the user.
Fill the passkey checks with the credential assertion data you get from the browser.
More detailed information about the API: [Update Session Documentation](/apis/resources/session_service/session-service-set-session)
Example Request:
```bash
curl --request PATCH \
--url https://$ZITADEL_DOMAIN/v2alpha/sessions/218480890961985793 \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"''\
--header 'Content-Type: application/json' \
--data '{
"sessionToken": "yMDi6uVPJAcphbbz0LaxC07ihWkNTe7m0Xqch8SzfM5Cz3HSIQIDZ65x1f5Qal0jxz0MEyo-_zYcUg",
"checks": {
"passkey": {
"credentialAssertionData": {}
}
}
}'
```

View File

@ -0,0 +1,109 @@
---
title: Password Reset/Change
---
When your user is on the password screen and has forgotten his password you will probably want him to be able to reset by himself.
## Flow
![Register and Login Flow](/img/guides/login-ui/password-reset-flow.png)
## Request Password Reset
First you will have to make a request, to ask for a password reset.
The goal is to send the user a verification code, which he can use to verify the password reset request.
There are two possible ways: You can either let ZITADEL send the notification with the verification code, or you can ask ZITADEL for returning the code and send the email by yourself.
[Request Password Reset Documentation](/apis/resources/user_service/user-service-password-reset)
### ZITADEL sends the verification message
When you want ZITADEL to send the verification code you can define the notification channel.
Per default the verification code will be sent to the email address of the user.
Make sure to also include the URL Template to customize the reset link in the email sent to the user.
### Request
```bash
curl --request POST \
--url https://$ZITADEL_DOMAIN/v2alpha/users/$USER_ID/password_reset \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"'' \
--header 'Content-Type: application/json' \
--data '{
"sendLink": {
"notificationType": "NOTIFICATION_TYPE_Email",
"urlTemplate": "https://example.com/password/changey?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}"
}
}'
```
### ZITADEL returns the code
Send the request with asking for the return Code in the body of the request.
#### Request
```bash
curl --request POST \
--url https://$ZITADEL_DOMAIN/v2alpha/users/$USER_ID/password_reset \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"'' \
--header 'Content-Type: application/json' \
--data '{
"returnCode": {}
}'
```
#### Response
You will get the verification code in the response:
```bash
{
"details": {
"sequence": "625",
"changeDate": "2023-06-27T15:02:10.321773Z",
"resourceOwner": "163840776835432705"
},
"verificationCode": "IBJMUC"
}
```
## Send Verification Code
The verification code is generated and ZITADEL has sent it with the defined channel (email or sms) to your users.
If you have chosen to get the code back in the response, you should now send the code to your user.
## Change Password
The next screen should allow the user to enter the verification code and a new password.
From a user experience perspective it is nice to prefill the verification code, so the user doesn't have to do manually.
As soon as the user has typed the new password, you can send the change password request.
The change password request allows you to set a new password for the user.
[Change Password Documentation](/apis/resources/user_service/user-service-set-password)
:::note
This request can be used in the password reset flow as well as to let your user change the password manually.
In this case it requires additionally the current password instead of the verification code.
:::
### Request
```bash
curl --request POST \
--url https://$ZITADEL_DOMAIN/v2alpha/users/$USER_ID/password \
--header 'Accept: application/json' \
--header 'Authorization: Bearer '"$TOKEN"'' \
--header 'Content-Type: application/json' \
--data '{
"newPassword": {
"password": "Secr3tP4ssw0rd!",
"changeRequired": false
},
"verificationCode": "48CDAP"
}'
```

View File

@ -10,7 +10,7 @@ sidebar_label: Username and Password
## Register
First, we create a new user with a username and password. In the example below we add a user with profile data, a verified email address, and a password.
[Create User Documentation](https://zitadel.com/docs/apis/resources/user_service/user-service-add-human-user)
[Create User Documentation](/apis/resources/user_service/user-service-add-human-user)
### Request

View File

@ -0,0 +1,21 @@
Upon successful introspection, regardless of the token type or introspection method, a response with the boolean `active` is returned, indicating if the provided token is active and if the requesting client is part of the token audience. If `active` is true, further information will be provided:
| **Property** | **Description** |
| --- | --- |
| `aud` | The audience of the token |
| `client_id` | The client_id of the application the token was issued to |
| `exp` | Time the token expires (as unix time) |
| `iat` | Time the token was issued at (as unix time) |
| `iss` | Issuer of the token |
| `jti` | Unique id of the token |
| `nbf` | Time the token must not be used before (as unix time) |
| `scope` | Space delimited list of scopes granted to the token |
| `token_type` | Type of the inspected token. Value is always Bearer |
| `username` | ZITADEL's login name of the user. Consists of username@primarydomain |
Depending on the granted scopes, additional information about the authorized user is provided.
If the authorization fails, an HTTP 401 with invalid_client will be returned.
In summary, the introspection endpoint plays a crucial role in validating access tokens, either opaque or JWT, ensuring that they are not revoked.

View File

@ -0,0 +1,103 @@
---
title: Basic authentication
---
import IntrospectionResponse from './_introspection-response.mdx';
This is a guide on how to secure your API using [Basic Authentication](https://zitadel.com/docs/apis/openidoauth/authn-methods#client-secret-basic).
## Register the API in ZITADEL
1. Go to your project and click on the **New** button as shown below.
<img
src="/docs/img/guides/integrate/token-introspection-basic-auth-1.png"
width="75%"
alt="Register the API"
/>
2. Give a name to your application (Test API 2 is the name given below) and select type **API**.
<img
src="/docs/img/guides/integrate/token-introspection-basic-auth-2.png"
width="75%"
alt="Register the API"
/>
3. Select **Basic** as the authentication method and click **Continue**.
<img
src="/docs/img/guides/integrate/token-introspection-basic-auth-3.png"
width="75%"
alt="Register the API"
/>
4. Now review your configuration and click **Create**.
<img
src="/docs/img/guides/integrate/token-introspection-basic-auth-4.png"
width="75%"
alt="Register the API"
/>
5. You will now see the APIs **Client ID** and the **Client Secret**. Copy them and click **Close**.
<img
src="/docs/img/guides/integrate/token-introspection-basic-auth-5.png"
width="75%"
alt="Register the API"
/>
6. When you click **URLs** on the left, you will see the relevant OIDC URLs. Note down the **issuer** URL, **token_endpoint** and **introspection_endpoint**.
<img
src="/docs/img/guides/integrate/token-introspection-basic-auth-6.png"
width="75%"
alt="Register the API"
/>
7. Also note down the **Resource ID** of your project.
<img
src="/docs/img/guides/integrate/token-introspection-basic-auth-7.png"
width="75%"
alt="Register the API"
/>
## Token introspection
With Basic Authentication, you will receive a Client ID and Client Secret for your API. Send your client_id and client_secret as a Basic Auth Header in the following format:
```
Authorization: "Basic " + base64( formUrlEncode(client_id) + ":" + formUrlEncode(client_secret) )
```
The request from the API to the introspection endpoint should be in the following format:
```
curl --request POST \
--url {your_domain}/oauth/v2/introspect \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic {your_basic_auth_header}' \
--data token=VjVxyCZmRmWYqd3_F5db9Pb9mHR5fqzhn...
```
Here's an example of how this is done in Python code:
```
def introspect_token(self, token_string):
url = ZITADEL_INTROSPECTION_URL
data = {'token': token_string, 'token_type_hint': 'access_token', 'scope': 'openid'}
auth = HTTPBasicAuth(API_CLIENT_ID, API_CLIENT_SECRET)
resp = requests.post(url, data=data, auth=auth)
resp.raise_for_status()
return resp.json()
```
## Introspection response
<IntrospectionResponse/>
Follow this [tutorial](https://github.com/zitadel/examples-api-access-and-token-introspection/tree/main/api-basic-authentication) to learn how to register an API application using Basic Auth with ZITADEL and test it.

View File

@ -0,0 +1,192 @@
---
title: JSON Web Token profile
---
import IntrospectionResponse from './_introspection-response.mdx';
This is a guide on how to secure your API using [JSON Web Token (JWT) profile (recommended)](https://zitadel.com/docs/apis/openidoauth/authn-methods#client-secret-basic).
## Register the API in ZITADEL and generate private and public keys
1. Go to your project and click on the **New** button as shown below.
<img
src="/docs/img/guides/integrate/token-introspection-jwt-profile-1.png"
width="75%"
alt="Register the API"
/>
2. Give a name to your application (Test API is the name given below) and select type **API**.
<img
src="/docs/img/guides/integrate/token-introspection-jwt-profile-2.png"
width="75%"
alt="Register the API"
/>
3. Select **JWT** as the authentication method and click **Continue**.
<img
src="/docs/img/guides/integrate/token-introspection-jwt-profile-3.png"
width="75%"
alt="Register the API"
/>
4. Now review your configuration and click **Create**.
<img
src="/docs/img/guides/integrate/token-introspection-jwt-profile-4.png"
width="75%"
alt="Register the API"
/>
5. You will now see the APIs **Client ID**. You will not see a client secret because we are using a private JWT key.
<img
src="/docs/img/guides/integrate/token-introspection-jwt-profile-5.png"
width="75%"
alt="Register the API"
/>
6. Next, we must create the key pairs. Click on **New**.
<img
src="/docs/img/guides/integrate/token-introspection-jwt-profile-6.png"
width="75%"
alt="Register the API"
/>
7. Select **JSON** as the type of key. You can also set an expiration time for the key or leave it empty. Click on **Add**.
<img
src="/docs/img/guides/integrate/token-introspection-jwt-profile-7.png"
width="75%"
alt="Register the API"
/>
8. Download the created key by clicking the **Download** button and then click **Close**.
<img
src="/docs/img/guides/integrate/token-introspection-jwt-profile-8.png"
width="75%"
alt="Register the API"
/>
9. The key will be downloaded.
<img
src="/docs/img/guides/integrate/token-introspection-jwt-profile-9.png"
width="75%"
alt="Register the API"
/>
10. When you click on URLs on the left, you will see the relevant OIDC URLs. Note down the **issuer** URL, **token_endpoint** and **introspection_endpoint**.
<img
src="/docs/img/guides/integrate/token-introspection-jwt-profile-10.png"
width="75%"
alt="Register the API"
/>
11. The key that you downloaded will be of the following format.
```
{
"type":"application",
"keyId":"<YOUR_KEY_ID>",
"key":"-----BEGIN RSA PRIVATE KEY-----\<YOUR_PRIVATE_KEY>\n-----END RSA PRIVATE KEY-----\n",
"appId":"<YOUR_APP_ID>",
"clientId":"<YOUR_CLIENT_ID>"
}
```
12. Also note down the **Resource ID** of your project.
<img
src="/docs/img/guides/integrate/token-introspection-jwt-profile-11.png"
width="75%"
alt="Register the API"
/>
## Token introspection
You must send a client_assertion as a JWT signed with the APIs private key for ZITADEL to validate the signature against the registered public key.
Request parameters:
| **Parameter** | **Description** |
---|---
| `client_assertion` | When using JWT profile for token or introspection endpoints, you must provide a JWT as an assertion generated with the structure shown below and signed with the downloaded key. |
| `client_assertion_type` | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
You must create your client assertion JWT with the following format:
Header:
```json
{
"alg": "RS256",
"kid": "81693565968962154" (keyId from your key file)
}
```
Payload:
```json
{
"iss": "78366401571920522@acme", (clientId from your key file)
"sub": "78366401571920522@acme", (clientId from your key file)
"aud": "https://{your_domain}", (your ZITADEL domain/issuer URL)
"exp": 1605183582, (Unix timestamp of the expiry)
"iat": 1605179982 (Unix timestamp of the creation signing time of the JWT, MUST NOT be older than 1h)
}
```
Create the JSON Web Token with the above header and payload, and sign it with the private key in your key file. You can do this programmatically or use tools like [https://github.com/zitadel/zitadel-tools](https://github.com/zitadel/zitadel-tools) and [https://dinochiesa.github.io/jwt/](https://dinochiesa.github.io/jwt/).
The request from the API to the introspection endpoint should be in the following format:
```bash
curl --request POST \
--url {your_domain}/oauth/v2/introspect \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
--data client_assertion=eyJhbGciOiJSUzI1Ni... \
--data token=VjVxyCZmRmWYqd3_F5db9Pb9mHR
```
Here's an example of how this is done in Python code:
```python
def introspect_token(self, token_string):
#Create JWT for client assertion
payload = {
"iss": API_PRIVATE_KEY_FILE["client_id"],
"sub": API_PRIVATE_KEY_FILE["client_id"],
"aud": ZITADEL_DOMAIN,
"exp": int(time.time()) + 60 * 60, # Expires in 1 hour
"iat": int(time.time())
}
headers = {
"alg": "RS256",
"kid": API_PRIVATE_KEY_FILE["key_id"]
}
jwt_token = jwt.encode(payload, API_PRIVATE_KEY_FILE["private_key"], algorithm="RS256", headers=headers)
#Send introspection request
headers = {"Content-Type": "application/x-www-form-urlencoded"}
data = {
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": jwt_token,
"token": token_string
}
response = requests.post(ZITADEL_INTROSPECTION_URL, headers=headers, data=data)
response.raise_for_status()
token_data = response.json()
print(f"Token data from introspection: {token_data}")
return token_data
```
## Introspection response
<IntrospectionResponse/>
Follow this [tutorial](https://github.com/zitadel/examples-api-access-and-token-introspection/tree/main/api-jwt) to learn how to register an API application using JWT Profile with ZITADEL and test it.

View File

@ -125,6 +125,7 @@ Secondfactors (2FA):
Force a user to register and use a multifactor authentication, by checking the option "Force MFA".
Ensure that you have added the MFA methods you want to allow.
Or you can enable the "Force MFA for local authenticated users", which will enforce this rule only on local authentication, but not on users authenticated through an Identity Provider.
### Login Lifetimes

View File

@ -19,7 +19,11 @@ You would have to create roles for administration and your clients in this very
To create a project, navigate to your organization, then projects or directly via <https://{your_domain}.zitadel.cloud/ui/console/projects>, and then click the button to create a new project.
<img alt="Empty Project" src="/docs/img/console_projects_empty.png" width="270px" />
<img
alt="Empty Project"
src="/docs/img/console_projects_empty.png"
width="270px"
/>
then enter your project name and continue.
@ -51,7 +55,7 @@ Organizations can then create authorizations for their users on their own. The p
<img src="/docs/img/guides/console/grantsmenu.png" alt="Grants" width="170px" />
2. Enter the domain of the organization you want to grant (go to the organization detail page if you can't remember it), hit the search button and continue.
2. Search the organization you want to grant using the auto complete input and continue.
3. Select some roles you would like to grant to the organization and confirm.
4. You should now see the granted organization in the section **grants**.

View File

@ -43,6 +43,8 @@ ZITADEL is available in the following languages
- Polishpl
- 简体中文zh
- Bulgarian (bg)
- Portuguese (pt)
- Macedonian (mk)
A language is displayed based on your agent's language header. The default language is English.

View File

@ -0,0 +1,65 @@
---
title: Frontend and API communication
---
This guide contains a use case and ZITADEL integration.
In a typical web application architecture, the front-end and back-end communicate to exchange data and provide functionality to users. Let's consider a use case where a front-end application needs to communicate with a back-end API using secure authentication and authorization. Lets explore how ZITADEL can be used to add front-end login and facilitate this communication.
Single-Page Applications (SPAs) are web applications that run entirely in the browser, without a back-end server. In ZITADEL SPAs should use the Authorization Code Grant with PKCE or the Implicit Grant (if PKCE is not feasible) to obtain an access token.
While APIs are vital for communication between applications and services, they don't directly participate in user authentication. Instead, they often authorize client requests based on access tokens issued by an authorization server. APIs in ZITADEL use grant types like JWT Profile or Basic Authentication to access the authorization server's introspection endpoint for token validation.
## A real-world scenario
Suppose there is a news portal web app that allows users to browse through various news articles and personalize their news feed based on their preferences. The back-end API handles fetching and delivering news content from the database.
**Front-End Login**: A user visits the news portal and opts to log in for a personalized news feed. They are redirected to the Identity Providers (IdP) login page, where they authenticate with their credentials. Upon successful authentication, the IdP issues access and ID tokens to the front-end app.
** Back-End API Communication**: When the user browses through the news feed, the front-end app makes an API request to the back-end, including the access token in the Authorization header. The back-end API, upon receiving the request, uses the IdPs introspection endpoint to validate the access token. Once validated, it fetches personalized news data based on the user's preferences from the news database.
While it is true that the back-end API typically needs to authenticate with the IdP, in this specific use case, the back-end API can work with a client credential / JWT since it's not a public client. This means that instead of relying on user-specific authentication, the back-end API can obtain a client credential (such as a client ID and client secret) from the IdP to authenticate itself and validate the access token received from the front-end app. This approach ensures secure communication between the front-end app, the back-end API, and the IdP, while still allowing the back-end API to access user-specific data and provide personalized news feeds.
## A simplified example with a React frontend and a Python Flask API
In this example, the application is a web-based quote generator that employs a secure user authentication system via ZITADEL. The functionality of the app is outlined below:
1. Upon starting, the application provides a login button
2. The user is then redirected to ZITADEL's login page to enter their credentials.
3. Once the login is successful, the application then greets the user by extracting the user's name, thus providing a personalized experience.
4. The application presents an option for the user to generate a quote via a button. Upon pressing this button, the front-end application communicates with the back-end API using the user's access token received from ZITADEL.
5. The back-end API introspects the access token for validity using ZITADEL's introspection endpoint. If the token is valid, the API generates a quote and sends it as a response to the front-end application, which is then displayed to the user in their browser.
## Setting up the applications and ZITADEL
All code and instructions to run the sample application can be found at [https://github.com/zitadel/example-quote-generator-app/](https://github.com/zitadel/example-quote-generator-app/). You can also find the steps for the integration between the front-end, back-end API, and ZITADEL in the README.md.
You can create the front-end application (User Agent) and the API in the same project or in a different project. In this example, we have created both in one. Configure the applications with appropriate settings (as instructed).
<img src="/docs/img/guides/solution-scenarios/frontend-calling-backend-API_1.png" alt="User Agent and API applications in a single project"/>
### Front-end login with ZITADEL
- You must create a User Agent application in your project to add login to your React application using the Authorization Code with PKCE flow. This allows the front-end application to integrate with ZITADEL to enable user authentication and authorization.
- In the React front-end application, configure the ZITADEL OIDC client settings, including the client ID, ZITADEL URLs, redirect URIs, and required scopes.
- Implement the login flow, authentication callbacks, and token handling logic in the front-end application.
- When a user visits the front-end application, they are presented with a login option.
- Upon clicking the login button, the frontend initiates the Authorization Code with PKCE authentication flow and redirects the user to the ZITADEL login page. The user enters their credentials and authenticates with ZITADEL. Authorization Code Flow returns an authorization code to the client application, which can then exchange it for an ID token and an access token directly. This provides the benefit of not exposing any tokens to the user agent and possibly other malicious applications with access to the user agent.
- You must set up the required scopes and claims to ensure the front-end and back-end can exchange data securely. Its important to note that when specifying the scope when calling the token API, the scope must contain the project ID of the ZITADEL project in which the API resides (to enable token validation by the back-end API):`scope:'openid profile email urn:zitadel:iam:org:project:id:<API_PROJECT_ID>:aud'`
- Also, we want to include user info inside the token to avoid calling the user info endpoint, so go to Token Settings in the front-end app and select User Info inside ID Token.
- After a successful authentication, ZITADEL generates an access token and an ID token.
- The front-end application receives these tokens and stores them securely (e.g., in browser storage).
### Token exchange and user information
- Once the frontend obtains the tokens, it can extract certain information from the ID token itself (e.g., user ID, email, etc.) without making an additional request.
- If more user information is required, the frontend can use the access token to call the ZITADEL User Info endpoint. This endpoint provides additional user details, such as name, profile picture, etc.
### Back-end API communication
- To communicate with the back-end API, the front-end includes the access token in the Authorization header of API requests.
- The back-end API receives the request and needs to validate and authorize the token before processing the request.
- The API performs token introspection using ZITADEL's introspection endpoint to validate the access token. This API uses Basic Authentication to invoke the [introspection endpoint](/docs/apis/openidoauth/endpoints#introspection_endpoint), which means it sends its client ID and client secret along with the access token received.
- If the token is valid and active, the API proceeds to handle the requested action or fetch data from the underlying data sources.

View File

@ -8,7 +8,7 @@
<td><strong>Basic data</strong></td>
<td>
<ul>
<li>Surname and first name</li>
<li>Family and given name</li>
<li>Email addresses</li>
<li>User name</li>
</ul>

View File

@ -21,7 +21,7 @@ Switzerland
Our representative in the EU is
**VGS Datenschutzpartner UG**
**VGS Datenschutzpartner GmbH**
Am Kaiserkai 69
20457 Hamburg
Germany

View File

@ -8,6 +8,8 @@ import DockerComposeSaSource from '!!raw-loader!./docker-compose-sa.yaml'
import Disclaimer from './_disclaimer.mdx'
import DefaultUser from './_defaultuser.mdx'
import Next from './_next.mdx'
import NoteInstanceNotFound from './troubleshooting/_note_instance_not_found.mdx';
The setup is tested against Docker version 20.10.17 and Docker Compose version v2.2.3
@ -29,6 +31,8 @@ docker compose up --detach
<DefaultUser components={props.components} />
<NoteInstanceNotFound/>
## VideoGuide
<iframe width="100%" height="315" src="https://www.youtube.com/embed/-02FaoN9Fko" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

View File

@ -5,6 +5,8 @@ title: Knative
import Disclaimer from './_disclaimer.mdx'
import DefaultUser from './_defaultuser.mdx'
import Next from './_next.mdx'
import NoteInstanceNotFound from './troubleshooting/_note_instance_not_found.mdx';
## Install Knative
@ -31,7 +33,7 @@ kn service create zitadel \
--env ZITADEL_EXTERNALPORT=80 \
--env ZITADEL_TLS_ENABLED=false \
--env ZITADEL_EXTERNALDOMAIN=zitadel.default.127.0.0.1.sslip.io \
--arg "start-from-init" --arg "--masterkey" --arg "MasterkeyNeedsToHave32Characters"
--arg "start-from-init" --arg "--masterkey" --arg "MasterkeyNeedsToHave32Characters"
```
### Knavite yaml
@ -59,6 +61,8 @@ If you didn't configure something else, this is the default IAM admin users logi
* username: zitadel-admin@<span></span>zitadel.zitadel.default.127.0.0.1.sslip.io
* password: Password1!
<NoteInstanceNotFound/>
## VideoGuide
<iframe width="100%" height="315" src="https://www.youtube.com/embed/m3TXmz3cK7E" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<Next components={props.components} />

View File

@ -5,6 +5,8 @@ title: Kubernetes
import Disclaimer from './_disclaimer.mdx'
import DefaultUser from './_defaultuser.mdx'
import Next from './_next.mdx'
import NoteInstanceNotFound from './troubleshooting/_note_instance_not_found.mdx';
Installation and configuration details are described in the [open source ZITADEL charts repo](https://github.com/zitadel/zitadel-charts).
By default, the chart installs a secure and highly available ZITADEL instance.
@ -46,6 +48,8 @@ kubectl port-forward svc/my-zitadel 8080
<DefaultUser components={props.components} />
<NoteInstanceNotFound/>
## Setup ZITADEL and a Service Account Admin
With this setup, you don't create a human user that has the IAM_OWNER role.

View File

@ -5,6 +5,8 @@ title: Linux
import Disclaimer from './_disclaimer.mdx'
import DefaultUser from './_defaultuser.mdx'
import Next from './_next.mdx'
import NoteInstanceNotFound from './troubleshooting/_note_instance_not_found.mdx';
## Install CockroachDB
@ -38,6 +40,8 @@ ZITADEL_EXTERNALSECURE=false zitadel start-from-init --masterkey "MasterkeyNeeds
<DefaultUser components={props.components} />
<NoteInstanceNotFound/>
## VideoGuide
<iframe width="100%" height="315" src="https://www.youtube.com/embed/YVLua-q7dbs" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
@ -54,3 +58,5 @@ This key can be used to provision resources with for example [Terraform](/docs/g
<Next components={props.components} />
<Disclaimer components={props.components} />

View File

@ -21,7 +21,6 @@ providers:
filename: /etc/traefik/traefik.yaml
http:
middlewares:
zitadel:
headers:
@ -29,7 +28,7 @@ http:
allowedHosts:
- 'my.domain'
customRequestHeaders:
:authority: 'my.domain'
authority: 'my.domain'
redirect-to-https:
redirectScheme:
scheme: https

View File

@ -8,6 +8,7 @@ import ExampleTraefikSource from '!!raw-loader!./example-traefik.yaml'
import ExampleZITADELConfigSource from '!!raw-loader!./example-zitadel-config.yaml'
import ExampleZITADELSecretsSource from '!!raw-loader!./example-zitadel-secrets.yaml'
import ExampleZITADELInitStepsSource from '!!raw-loader!./example-zitadel-init-steps.yaml'
import NoteInstanceNotFound from '../troubleshooting/_note_instance_not_found.mdx';
With this example configuration, you create a near production environment for ZITADEL with [Docker Compose](https://docs.docker.com/compose/).
@ -61,7 +62,7 @@ export ZITADEL_MASTERKEY="$(tr -dc A-Za-z0-9 </dev/urandom | head -c 32)"
docker compose up --detach
```
Make `127.0.0.1` available at `my.domain`. For example, this can be achived with an entry `127.0.1.1 my.domain` in the `/etc/hosts` file.
Make `127.0.0.1` available at `my.domain`. For example, this can be achieved with an entry `127.0.0.1 my.domain` in the `/etc/hosts` file.
Open your favorite internet browser at [https://my.domain/ui/console/](https://my.domain/ui/console/).
You can safely proceed, if your browser warns you about the insecure self-signed TLS certificate.
@ -71,6 +72,8 @@ This is the IAM admin users login according to your configuration in the [exampl
Read more about [the login process](/guides/integrate/login-users).
<NoteInstanceNotFound/>
## Troubleshooting
You can connect to cockroach like this: `docker exec -it loadbalancing-example-my-cockroach-db-1 cockroach sql --host my-cockroach-db --certs-dir /cockroach/certs/`

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