mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 10:37:44 +00:00
Merge remote-tracking branch 'origin/next-rc' into next
# Conflicts: # go.mod # go.sum # internal/api/grpc/oidc/v2/integration_test/oidc_test.go # internal/api/grpc/oidc/v2beta/integration_test/oidc_test.go # internal/api/grpc/session/v2/session.go # internal/api/grpc/session/v2beta/session.go # internal/api/grpc/session/v2beta/session_test.go # internal/api/scim/integration_test/testdata/users_replace_test_minimal_with_email_type.json # internal/api/scim/integration_test/users_create_test.go # internal/api/scim/integration_test/users_get_test.go # internal/api/scim/integration_test/users_replace_test.go # internal/api/scim/integration_test/users_update_test.go # internal/queue/queue.go
This commit is contained in:
16
.devcontainer/base/Dockerfile
Normal file
16
.devcontainer/base/Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
||||
FROM mcr.microsoft.com/devcontainers/typescript-node:20-bookworm
|
||||
|
||||
ENV SHELL=/bin/bash \
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
LANG=C.UTF-8 \
|
||||
LC_ALL=C.UTF-8 \
|
||||
CI=1 \
|
||||
PNPM_HOME=/home/node/.local/share/pnpm \
|
||||
PATH=/home/node/.local/share/pnpm:$PATH
|
||||
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get --no-install-recommends install -y \
|
||||
libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6 xauth xvfb && \
|
||||
apt-get clean && \
|
||||
corepack enable && COREPACK_ENABLE_DOWNLOAD_PROMPT=0 corepack prepare pnpm@9.1.2 --activate
|
28
.devcontainer/base/devcontainer.json
Normal file
28
.devcontainer/base/devcontainer.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/devcontainers/spec/refs/heads/main/schemas/devContainer.schema.json",
|
||||
"name": "devcontainer",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "devcontainer",
|
||||
"workspaceFolder": "/workspaces",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/go:1": {
|
||||
"version": "1.24"
|
||||
},
|
||||
"ghcr.io/guiyomh/features/golangci-lint:0": {},
|
||||
"ghcr.io/jungaretti/features/make:1": {}
|
||||
},
|
||||
"forwardPorts": [
|
||||
3000,
|
||||
3001,
|
||||
4200,
|
||||
8080
|
||||
],
|
||||
"onCreateCommand": "pnpm install -g sass@1.64.1",
|
||||
"customizations": {
|
||||
"jetbrains": {
|
||||
"settings": {
|
||||
"com.intellij:app:HttpConfigurable.use_proxy_pac": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
239
.devcontainer/base/docker-compose.yml
Normal file
239
.devcontainer/base/docker-compose.yml
Normal file
@@ -0,0 +1,239 @@
|
||||
x-build-cache: &build-cache
|
||||
cache_from:
|
||||
- type=gha
|
||||
cache_to:
|
||||
- type=gha,mode=max
|
||||
|
||||
services:
|
||||
|
||||
devcontainer:
|
||||
container_name: devcontainer
|
||||
build:
|
||||
context: .
|
||||
<<: *build-cache
|
||||
volumes:
|
||||
- ../../:/workspaces:cached
|
||||
- /tmp/.X11-unix:/tmp/.X11-unix:cached
|
||||
- home-dir:/home/node:delegated
|
||||
command: sleep infinity
|
||||
working_dir: /workspaces
|
||||
environment:
|
||||
ZITADEL_DATABASE_POSTGRES_HOST: db
|
||||
ZITADEL_EXTERNALSECURE: false
|
||||
|
||||
db:
|
||||
container_name: db
|
||||
image: postgres:17.0-alpine3.19
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
PGUSER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "pg_isready" ]
|
||||
interval: "10s"
|
||||
timeout: "30s"
|
||||
retries: 5
|
||||
start_period: "20s"
|
||||
ports:
|
||||
- "5432:5432"
|
||||
|
||||
mock-zitadel:
|
||||
container_name: mock-zitadel
|
||||
build:
|
||||
context: ../../apps/login/integration/core-mock
|
||||
<<: *build-cache
|
||||
ports:
|
||||
- 22220:22220
|
||||
- 22222:22222
|
||||
|
||||
login-integration:
|
||||
container_name: login-integration
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: build/login/Dockerfile
|
||||
<<: *build-cache
|
||||
image: "${LOGIN_TAG:-zitadel-login:local}"
|
||||
env_file: ../../apps/login/.env.test
|
||||
network_mode: service:devcontainer
|
||||
environment:
|
||||
NODE_ENV: test
|
||||
PORT: 3001
|
||||
depends_on:
|
||||
mock-zitadel:
|
||||
condition: service_started
|
||||
|
||||
zitadel:
|
||||
image: "${ZITADEL_TAG:-ghcr.io/zitadel/zitadel:v4.0.0-rc.2}"
|
||||
container_name: zitadel
|
||||
command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --config /zitadel.yaml --steps /zitadel.yaml'
|
||||
volumes:
|
||||
- ../../apps/login/acceptance/pat:/pat:delegated
|
||||
- ../../apps/login/acceptance/zitadel.yaml:/zitadel.yaml:cached
|
||||
network_mode: service:devcontainer
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- /app/zitadel
|
||||
- ready
|
||||
- --config
|
||||
- /zitadel.yaml
|
||||
depends_on:
|
||||
db:
|
||||
condition: "service_healthy"
|
||||
|
||||
configure-login:
|
||||
container_name: configure-login
|
||||
restart: no
|
||||
build:
|
||||
context: ../../apps/login/acceptance/setup
|
||||
dockerfile: ../go-command.Dockerfile
|
||||
<<: *build-cache
|
||||
entrypoint: "./setup.sh"
|
||||
network_mode: service:devcontainer
|
||||
environment:
|
||||
PAT_FILE: /pat/zitadel-admin-sa.pat
|
||||
ZITADEL_API_URL: http://localhost:8080
|
||||
WRITE_ENVIRONMENT_FILE: /login-env/.env.test.local
|
||||
SINK_EMAIL_INTERNAL_URL: http://sink:3333/email
|
||||
SINK_SMS_INTERNAL_URL: http://sink:3333/sms
|
||||
SINK_NOTIFICATION_URL: http://sink:3333/notification
|
||||
LOGIN_BASE_URL: http://localhost:3000/ui/v2/login/
|
||||
ZITADEL_API_DOMAIN: localhost
|
||||
ZITADEL_ADMIN_USER: zitadel-admin@zitadel.localhost
|
||||
volumes:
|
||||
- ../../apps/login/acceptance/pat:/pat:cached # Read the PAT file from zitadels setup
|
||||
- ../../apps/login:/login-env:delegated # Write the environment variables file for the login
|
||||
depends_on:
|
||||
zitadel:
|
||||
condition: "service_healthy"
|
||||
|
||||
login-acceptance:
|
||||
container_name: login
|
||||
image: "${LOGIN_TAG:-ghcr.io/zitadel/zitadel-login:v4.0.0-rc.2}"
|
||||
network_mode: service:devcontainer
|
||||
volumes:
|
||||
- ../../apps/login/.env.test.local:/env-files/.env:cached
|
||||
depends_on:
|
||||
configure-login:
|
||||
condition: service_completed_successfully
|
||||
|
||||
mock-notifications:
|
||||
container_name: mock-notifications
|
||||
build:
|
||||
context: ../../apps/login/acceptance/sink
|
||||
dockerfile: ../go-command.Dockerfile
|
||||
args:
|
||||
- LOGIN_TEST_ACCEPTANCE_GOLANG_TAG=${LOGIN_TEST_ACCEPTANCE_GOLANG_TAG:-golang:1.24-alpine}
|
||||
<<: *build-cache
|
||||
environment:
|
||||
PORT: '3333'
|
||||
command:
|
||||
- -port
|
||||
- '3333'
|
||||
- -email
|
||||
- '/email'
|
||||
- -sms
|
||||
- '/sms'
|
||||
- -notification
|
||||
- '/notification'
|
||||
ports:
|
||||
- "3333:3333"
|
||||
depends_on:
|
||||
configure-login:
|
||||
condition: "service_completed_successfully"
|
||||
|
||||
mock-oidcrp:
|
||||
container_name: mock-oidcrp
|
||||
build:
|
||||
context: ../../apps/login/acceptance/oidcrp
|
||||
dockerfile: ../go-command.Dockerfile
|
||||
args:
|
||||
- LOGIN_TEST_ACCEPTANCE_GOLANG_TAG=${LOGIN_TEST_ACCEPTANCE_GOLANG_TAG:-golang:1.24-alpine}
|
||||
<<: *build-cache
|
||||
network_mode: service:devcontainer
|
||||
environment:
|
||||
API_URL: 'http://localhost:8080'
|
||||
API_DOMAIN: 'localhost'
|
||||
PAT_FILE: '/pat/zitadel-admin-sa.pat'
|
||||
LOGIN_URL: 'http://localhost:3000/ui/v2/login'
|
||||
ISSUER: 'http://localhost:8000'
|
||||
HOST: 'localhost'
|
||||
PORT: '8000'
|
||||
SCOPES: 'openid profile email'
|
||||
volumes:
|
||||
- ../../apps/login/acceptance/pat:/pat:cached
|
||||
depends_on:
|
||||
configure-login:
|
||||
condition: "service_completed_successfully"
|
||||
|
||||
# mock-oidcop:
|
||||
# container_name: mock-oidcop
|
||||
# build:
|
||||
# context: ../../apps/login/acceptance/idp/oidc
|
||||
# dockerfile: ../../go-command.Dockerfile
|
||||
# args:
|
||||
# - LOGIN_TEST_ACCEPTANCE_GOLANG_TAG=${LOGIN_TEST_ACCEPTANCE_GOLANG_TAG:-golang:1.24-alpine}
|
||||
# <<: *build-cache
|
||||
# network_mode: service:devcontainer
|
||||
# environment:
|
||||
# API_URL: 'http://localhost:8080'
|
||||
# API_DOMAIN: 'localhost'
|
||||
# PAT_FILE: '/pat/zitadel-admin-sa.pat'
|
||||
# SCHEMA: 'http'
|
||||
# HOST: 'localhost'
|
||||
# PORT: "8004"
|
||||
# volumes:
|
||||
# - "../apps/login/packages/acceptance/pat:/pat:cached"
|
||||
# depends_on:
|
||||
# configure-login:
|
||||
# condition: "service_completed_successfully"
|
||||
|
||||
mock-samlsp:
|
||||
container_name: mock-samlsp
|
||||
build:
|
||||
context: ../../apps/login/acceptance/samlsp
|
||||
dockerfile: ../go-command.Dockerfile
|
||||
args:
|
||||
- LOGIN_TEST_ACCEPTANCE_GOLANG_TAG=${LOGIN_TEST_ACCEPTANCE_GOLANG_TAG:-golang:1.24-alpine}
|
||||
<<: *build-cache
|
||||
network_mode: service:devcontainer
|
||||
environment:
|
||||
API_URL: 'http://localhost:8080'
|
||||
API_DOMAIN: 'localhost'
|
||||
PAT_FILE: '/pat/zitadel-admin-sa.pat'
|
||||
LOGIN_URL: 'http://localhost:3000/ui/v2/login'
|
||||
IDP_URL: 'http://localhost:8080/saml/v2/metadata'
|
||||
HOST: 'http://localhost:8001'
|
||||
PORT: '8001'
|
||||
volumes:
|
||||
- "../apps/login/packages/acceptance/pat:/pat:cached"
|
||||
depends_on:
|
||||
configure-login:
|
||||
condition: "service_completed_successfully"
|
||||
# mock-samlidp:
|
||||
# container_name: mock-samlidp
|
||||
# build:
|
||||
# context: ../../apps/login/acceptance/idp/saml
|
||||
# dockerfile: ../../go-command.Dockerfile
|
||||
# args:
|
||||
# - LOGIN_TEST_ACCEPTANCE_GOLANG_TAG=${LOGIN_TEST_ACCEPTANCE_GOLANG_TAG:-golang:1.24-alpine}
|
||||
# <<: *build-cache
|
||||
# network_mode: service:devcontainer
|
||||
# environment:
|
||||
# API_URL: 'http://localhost:8080'
|
||||
# API_DOMAIN: 'localhost'
|
||||
# PAT_FILE: '/pat/zitadel-admin-sa.pat'
|
||||
# SCHEMA: 'http'
|
||||
# HOST: 'localhost'
|
||||
# PORT: "8003"
|
||||
# volumes:
|
||||
# - "../apps/login/packages/acceptance/pat:/pat"
|
||||
# depends_on:
|
||||
# configure-login:
|
||||
# condition: "service_completed_successfully"
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
home-dir:
|
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"name": "zitadel",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "devcontainer",
|
||||
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/go:1": {
|
||||
"version": "1.22"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {},
|
||||
"ghcr.io/guiyomh/features/golangci-lint:0": {},
|
||||
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {},
|
||||
"ghcr.io/jungaretti/features/make:1": {}
|
||||
},
|
||||
"forwardPorts": [
|
||||
3000,
|
||||
4200,
|
||||
8080
|
||||
],
|
||||
"onCreateCommand": "npm install -g sass@1.64.1"
|
||||
}
|
@@ -1,31 +0,0 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
devcontainer:
|
||||
image: mcr.microsoft.com/devcontainers/base:ubuntu
|
||||
volumes:
|
||||
- ../..:/workspaces:cached
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
network_mode: service:db
|
||||
command: sleep infinity
|
||||
environment:
|
||||
ZITADEL_DATABASE_POSTGRES_HOST: db
|
||||
ZITADEL_DATABASE_POSTGRES_PORT: 5432
|
||||
ZITADEL_DATABASE_POSTGRES_DATABASE: zitadel
|
||||
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel
|
||||
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel
|
||||
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
|
||||
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: postgres
|
||||
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: postgres
|
||||
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
|
||||
ZITADEL_EXTERNALSECURE: false
|
||||
db:
|
||||
image: postgres:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
PGUSER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
21
.devcontainer/login-integration-debug/devcontainer.json
Normal file
21
.devcontainer/login-integration-debug/devcontainer.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/devcontainers/spec/refs/heads/main/schemas/devContainer.schema.json",
|
||||
"name": "login-integration-debug",
|
||||
"dockerComposeFile": [
|
||||
"../base/docker-compose.yml",
|
||||
"docker-compose.yml"
|
||||
],
|
||||
"service": "login-integration-debug",
|
||||
"runServices": ["login-integration-debug"],
|
||||
"workspaceFolder": "/workspaces",
|
||||
"forwardPorts": [3001],
|
||||
"onCreateCommand": "pnpm install --recursive",
|
||||
"postAttachCommand": "pnpm turbo daemon clean; pnpm turbo @zitadel/login#dev test:integration:login:debug",
|
||||
"customizations": {
|
||||
"jetbrains": {
|
||||
"settings": {
|
||||
"com.intellij:app:HttpConfigurable.use_proxy_pac": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
.devcontainer/login-integration-debug/docker-compose.yml
Normal file
9
.devcontainer/login-integration-debug/docker-compose.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
login-integration-debug:
|
||||
extends:
|
||||
file: ../base/docker-compose.yml
|
||||
service: devcontainer
|
||||
container_name: login-integration-debug
|
||||
depends_on:
|
||||
mock-zitadel:
|
||||
condition: service_started
|
19
.devcontainer/login-integration/devcontainer.json
Normal file
19
.devcontainer/login-integration/devcontainer.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/devcontainers/spec/refs/heads/main/schemas/devContainer.schema.json",
|
||||
"name": "login-integration",
|
||||
"dockerComposeFile": [
|
||||
"../base/docker-compose.yml"
|
||||
],
|
||||
"service": "devcontainer",
|
||||
"runServices": ["login-integration"],
|
||||
"workspaceFolder": "/workspaces",
|
||||
"forwardPorts": [3001],
|
||||
"onCreateCommand": "pnpm install --frozen-lockfile --recursive && cd apps/login/packages/integration && pnpm cypress install && pnpm test:integration:login",
|
||||
"customizations": {
|
||||
"jetbrains": {
|
||||
"settings": {
|
||||
"com.intellij:app:HttpConfigurable.use_proxy_pac": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
21
.devcontainer/turbo-lint-unit-debug/devcontainer.json
Normal file
21
.devcontainer/turbo-lint-unit-debug/devcontainer.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/devcontainers/spec/refs/heads/main/schemas/devContainer.schema.json",
|
||||
"name": "turbo-lint-unit-debug",
|
||||
"dockerComposeFile": [
|
||||
"../base/docker-compose.yml",
|
||||
"docker-compose.yml"
|
||||
],
|
||||
"service": "turbo-lint-unit-debug",
|
||||
"runServices": ["turbo-lint-unit-debug"],
|
||||
"workspaceFolder": "/workspaces",
|
||||
"forwardPorts": [3001],
|
||||
"onCreateCommand": "pnpm install --recursive",
|
||||
"postAttachCommand": "pnpm turbo daemon clean; pnpm turbo watch lint test:unit",
|
||||
"customizations": {
|
||||
"jetbrains": {
|
||||
"settings": {
|
||||
"com.intellij:app:HttpConfigurable.use_proxy_pac": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
6
.devcontainer/turbo-lint-unit-debug/docker-compose.yml
Normal file
6
.devcontainer/turbo-lint-unit-debug/docker-compose.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
services:
|
||||
turbo-lint-unit-debug:
|
||||
extends:
|
||||
file: ../base/docker-compose.yml
|
||||
service: devcontainer
|
||||
container_name: turbo-lint-unit-debug
|
18
.devcontainer/turbo-lint-unit/devcontainer.json
Normal file
18
.devcontainer/turbo-lint-unit/devcontainer.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/devcontainers/spec/refs/heads/main/schemas/devContainer.schema.json",
|
||||
"name": "turbo-lint-unit",
|
||||
"dockerComposeFile": [
|
||||
"../base/docker-compose.yml"
|
||||
],
|
||||
"service": "devcontainer",
|
||||
"runServices": ["devcontainer"],
|
||||
"workspaceFolder": "/workspaces",
|
||||
"postStartCommand": "pnpm install --frozen-lockfile --recursive && pnpm turbo lint test:unit",
|
||||
"customizations": {
|
||||
"jetbrains": {
|
||||
"settings": {
|
||||
"com.intellij:app:HttpConfigurable.use_proxy_pac": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
13
.github/dependabot.yml
vendored
13
.github/dependabot.yml
vendored
@@ -22,6 +22,19 @@ updates:
|
||||
commit-message:
|
||||
prefix: chore
|
||||
include: scope
|
||||
- package-ecosystem: npm
|
||||
directory: '/login'
|
||||
open-pull-requests-limit: 3
|
||||
schedule:
|
||||
interval: daily
|
||||
groups:
|
||||
prod:
|
||||
dependency-type: production
|
||||
dev:
|
||||
dependency-type: development
|
||||
ignore:
|
||||
- dependency-name: "eslint"
|
||||
versions: [ "9.x" ]
|
||||
- package-ecosystem: gomod
|
||||
groups:
|
||||
go:
|
||||
|
31
.github/workflows/build.yml
vendored
31
.github/workflows/build.yml
vendored
@@ -18,6 +18,7 @@ permissions:
|
||||
packages: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
core:
|
||||
@@ -30,6 +31,11 @@ jobs:
|
||||
uses: ./.github/workflows/console.yml
|
||||
with:
|
||||
node_version: "20"
|
||||
|
||||
docs:
|
||||
uses: ./.github/workflows/docs.yml
|
||||
with:
|
||||
node_version: "20"
|
||||
buf_version: "latest"
|
||||
|
||||
version:
|
||||
@@ -47,6 +53,7 @@ jobs:
|
||||
core_cache_path: ${{ needs.core.outputs.cache_path }}
|
||||
console_cache_path: ${{ needs.console.outputs.cache_path }}
|
||||
version: ${{ needs.version.outputs.version }}
|
||||
node_version: "20"
|
||||
|
||||
core-unit-test:
|
||||
needs: core
|
||||
@@ -72,7 +79,7 @@ jobs:
|
||||
with:
|
||||
node_version: "18"
|
||||
buf_version: "latest"
|
||||
go_lint_version: "v1.64.8"
|
||||
go_lint_version: "latest"
|
||||
core_cache_key: ${{ needs.core.outputs.cache_key }}
|
||||
core_cache_path: ${{ needs.core.outputs.cache_path }}
|
||||
|
||||
@@ -86,6 +93,15 @@ jobs:
|
||||
with:
|
||||
build_image_name: "ghcr.io/zitadel/zitadel-build"
|
||||
|
||||
login-container:
|
||||
uses: ./.github/workflows/login-container.yml
|
||||
permissions:
|
||||
packages: write
|
||||
id-token: write
|
||||
with:
|
||||
login_build_image_name: "ghcr.io/zitadel/zitadel-login-build"
|
||||
node_version: "20"
|
||||
|
||||
e2e:
|
||||
uses: ./.github/workflows/e2e.yml
|
||||
needs: [compile]
|
||||
@@ -98,7 +114,15 @@ jobs:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
needs:
|
||||
[version, core-unit-test, core-integration-test, lint, container, e2e]
|
||||
[
|
||||
version,
|
||||
core-unit-test,
|
||||
core-integration-test,
|
||||
lint,
|
||||
container,
|
||||
login-container,
|
||||
e2e,
|
||||
]
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
secrets:
|
||||
GCR_JSON_KEY_BASE64: ${{ secrets.GCR_JSON_KEY_BASE64 }}
|
||||
@@ -109,3 +133,6 @@ jobs:
|
||||
semantic_version: "23.0.7"
|
||||
image_name: "ghcr.io/zitadel/zitadel"
|
||||
google_image_name: "europe-docker.pkg.dev/zitadel-common/zitadel-repo/zitadel"
|
||||
build_image_name_login: ${{ needs.login-container.outputs.login_build_image }}
|
||||
image_name_login: "ghcr.io/zitadel/zitadel-login"
|
||||
google_image_name_login: "europe-docker.pkg.dev/zitadel-common/zitadel-repo/zitadel-login"
|
||||
|
119
.github/workflows/compile.yml
vendored
119
.github/workflows/compile.yml
vendored
@@ -18,6 +18,9 @@ on:
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
node_version:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
executable:
|
||||
@@ -27,70 +30,60 @@ jobs:
|
||||
matrix:
|
||||
goos: [linux, darwin, windows]
|
||||
goarch: [amd64, arm64]
|
||||
|
||||
|
||||
steps:
|
||||
-
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
uses: actions/cache/restore@v4
|
||||
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@v4
|
||||
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@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
-
|
||||
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 -czvf zitadel-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz zitadel-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
-
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: zitadel-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
path: zitadel-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache/restore@v4
|
||||
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@v4
|
||||
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@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- 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 -czvf zitadel-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz zitadel-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: zitadel-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
path: zitadel-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz
|
||||
|
||||
checksums:
|
||||
runs-on: ubuntu-latest
|
||||
needs: executable
|
||||
needs: [executable]
|
||||
steps:
|
||||
-
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: executables
|
||||
-
|
||||
name: move files one folder up
|
||||
run: mv */*.tar.gz . && find . -type d -empty -delete
|
||||
working-directory: executables
|
||||
-
|
||||
run: sha256sum * > checksums.txt
|
||||
working-directory: executables
|
||||
-
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: checksums.txt
|
||||
path: executables/checksums.txt
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: executables
|
||||
pattern: 'zitadel-*-*'
|
||||
- name: move files one folder up
|
||||
run: mv */*.tar.gz . && find . -type d -empty -delete
|
||||
working-directory: executables
|
||||
- run: sha256sum * > checksums.txt
|
||||
working-directory: executables
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: checksums.txt
|
||||
path: executables/checksums.txt
|
||||
|
71
.github/workflows/console.yml
vendored
71
.github/workflows/console.yml
vendored
@@ -1,19 +1,16 @@
|
||||
name: Build console
|
||||
|
||||
on:
|
||||
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 }}
|
||||
value: ${{ jobs.build.outputs.cache_path }}
|
||||
|
||||
env:
|
||||
cache_path: console/dist/console
|
||||
@@ -25,38 +22,32 @@ jobs:
|
||||
cache_path: ${{ env.cache_path }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
uses: actions/cache/restore@v4
|
||||
timeout-minutes: 1
|
||||
continue-on-error: true
|
||||
id: cache
|
||||
with:
|
||||
key: console-${{ hashFiles('console', 'proto', '!console/dist') }}
|
||||
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@v4
|
||||
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@v4
|
||||
with:
|
||||
path: ${{ env.cache_path }}
|
||||
key: ${{ steps.cache.outputs.cache-primary-key }}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache/restore@v4
|
||||
timeout-minutes: 1
|
||||
continue-on-error: true
|
||||
id: cache
|
||||
with:
|
||||
key: console-${{ hashFiles('console', 'proto', '!console/dist') }}
|
||||
restore-keys: |
|
||||
console-
|
||||
path: ${{ env.cache_path }}
|
||||
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||
uses: pnpm/action-setup@v4
|
||||
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ inputs.node_version }}
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: pnpm-lock.yaml
|
||||
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||
name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||
name: Build console with Turbo
|
||||
run: pnpm turbo build --filter=./console
|
||||
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: ${{ env.cache_path }}
|
||||
key: ${{ steps.cache.outputs.cache-primary-key }}
|
||||
|
4
.github/workflows/container.yml
vendored
4
.github/workflows/container.yml
vendored
@@ -79,7 +79,7 @@ jobs:
|
||||
context: .
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
file: build/Dockerfile
|
||||
file: build/zitadel/Dockerfile
|
||||
target: artifact
|
||||
platforms: linux/${{ matrix.arch }}
|
||||
push: true
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
context: .
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
file: build/Dockerfile
|
||||
file: build/zitadel/Dockerfile
|
||||
target: final
|
||||
platforms: linux/${{ matrix.arch }}
|
||||
push: true
|
||||
|
1
.github/workflows/core.yml
vendored
1
.github/workflows/core.yml
vendored
@@ -25,6 +25,7 @@ env:
|
||||
internal/api/assets/router.go
|
||||
openapi/v2
|
||||
pkg/grpc/**/*.pb.*
|
||||
pkg/grpc/**/*.connect.go
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
61
.github/workflows/docs.yml
vendored
Normal file
61
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Build docs
|
||||
|
||||
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: docs/build
|
||||
|
||||
jobs:
|
||||
build:
|
||||
outputs:
|
||||
cache_key: ${{ steps.cache.outputs.cache-primary-key }}
|
||||
cache_path: ${{ env.cache_path }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache/restore@v4
|
||||
timeout-minutes: 1
|
||||
continue-on-error: true
|
||||
id: cache
|
||||
with:
|
||||
key: docs-${{ hashFiles('docs', 'proto', '!docs/build', '!docs/node_modules', '!docs/protoc-gen-connect-openapi') }}
|
||||
restore-keys: |
|
||||
docs-
|
||||
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: pnpm/action-setup@v4
|
||||
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ inputs.node_version }}
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: pnpm-lock.yaml
|
||||
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||
name: Install dependencies
|
||||
run: pnpm install
|
||||
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||
name: Build docs with Turbo
|
||||
run: pnpm turbo build --filter=./docs
|
||||
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: ${{ env.cache_path }}
|
||||
key: ${{ steps.cache.outputs.cache-primary-key }}
|
37
.github/workflows/e2e.yml
vendored
37
.github/workflows/e2e.yml
vendored
@@ -12,44 +12,47 @@ jobs:
|
||||
browser: [firefox, chrome]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout Repository
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: .artifacts
|
||||
name: zitadel-linux-amd64
|
||||
-
|
||||
name: Unpack executable
|
||||
- name: Unpack executable
|
||||
run: |
|
||||
tar -xvf .artifacts/zitadel-linux-amd64.tar.gz
|
||||
mv zitadel-linux-amd64/zitadel ./zitadel
|
||||
-
|
||||
name: Set up QEMU
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Start DB and ZITADEL
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: pnpm-lock.yaml
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Install Cypress binary
|
||||
run: cd ./e2e && pnpm exec cypress install
|
||||
- name: Start DB and ZITADEL
|
||||
run: |
|
||||
cd ./e2e
|
||||
ZITADEL_IMAGE=zitadel:local docker compose up --detach --wait
|
||||
-
|
||||
name: Cypress run
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@v6
|
||||
env:
|
||||
CYPRESS_BASE_URL: http://localhost:8080/ui/console
|
||||
CYPRESS_WEBHOOK_HANDLER_HOST: host.docker.internal
|
||||
CYPRESS_DATABASE_CONNECTION_URL: 'postgresql://root@localhost:26257/zitadel'
|
||||
CYPRESS_DATABASE_CONNECTION_URL: "postgresql://root@localhost:26257/zitadel"
|
||||
CYPRESS_BACKEND_URL: http://localhost:8080
|
||||
with:
|
||||
working-directory: e2e
|
||||
browser: ${{ matrix.browser }}
|
||||
config-file: cypress.config.ts
|
||||
-
|
||||
uses: actions/upload-artifact@v4
|
||||
install: false
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: production-tests-${{ matrix.browser }}
|
||||
|
91
.github/workflows/lint.yml
vendored
91
.github/workflows/lint.yml
vendored
@@ -20,7 +20,6 @@ on:
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
|
||||
lint-skip:
|
||||
name: lint skip
|
||||
runs-on: ubuntu-latest
|
||||
@@ -36,64 +35,52 @@ jobs:
|
||||
continue-on-error: true
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
steps:
|
||||
-
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
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=${{ github.base_ref }}"
|
||||
- uses: actions/checkout@v4
|
||||
- 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=${{ github.base_ref }}"
|
||||
|
||||
console:
|
||||
turbo-lint-unit:
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
name: console
|
||||
name: turbo-lint-unit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
uses: actions/setup-node@v4
|
||||
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
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Run lint and unit tests in dev container
|
||||
uses: devcontainers/ci@v0.3
|
||||
with:
|
||||
push: never
|
||||
configFile: .devcontainer/turbo-lint-unit/devcontainer.json
|
||||
runCmd: echo "Successfully ran lint and unit tests in dev container postStartCommand"
|
||||
|
||||
core:
|
||||
name: core
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
-
|
||||
uses: actions/cache/restore@v4
|
||||
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@v6
|
||||
with:
|
||||
version: ${{ inputs.go_lint_version }}
|
||||
github-token: ${{ github.token }}
|
||||
only-new-issues: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- uses: actions/cache/restore@v4
|
||||
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@v8
|
||||
with:
|
||||
version: ${{ inputs.go_lint_version }}
|
||||
github-token: ${{ github.token }}
|
||||
only-new-issues: true
|
||||
|
70
.github/workflows/login-container.yml
vendored
Normal file
70
.github/workflows/login-container.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: Login Container
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
login_build_image_name:
|
||||
description: 'The image repository name of the standalone login image'
|
||||
type: string
|
||||
required: true
|
||||
node_version:
|
||||
required: true
|
||||
type: string
|
||||
outputs:
|
||||
login_build_image:
|
||||
description: 'The full image tag of the standalone login image'
|
||||
value: '${{ inputs.login_build_image_name }}:${{ github.sha }}'
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
env:
|
||||
default_labels: |
|
||||
org.opencontainers.image.documentation=https://zitadel.com/docs
|
||||
org.opencontainers.image.vendor=CAOS AG
|
||||
org.opencontainers.image.licenses=MIT
|
||||
|
||||
jobs:
|
||||
login-container:
|
||||
name: Build Login Container
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Login meta
|
||||
id: login-meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ inputs.login_build_image_name }}
|
||||
labels: ${{ env.default_labels}}
|
||||
annotations: |
|
||||
manifest:org.opencontainers.image.licenses=MIT
|
||||
tags: |
|
||||
type=sha,prefix=,suffix=,format=long
|
||||
- name: Login to Docker registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Bake login multi-arch
|
||||
uses: docker/bake-action@v6
|
||||
env:
|
||||
NODE_VERSION: ${{ inputs.node_version }}
|
||||
with:
|
||||
source: .
|
||||
push: true
|
||||
provenance: true
|
||||
sbom: true
|
||||
targets: login-standalone
|
||||
set: |
|
||||
*.cache-from=type=gha
|
||||
*.cache-to=type=gha,mode=max
|
||||
files: |
|
||||
./apps/login/docker-bake.hcl
|
||||
./apps/login/docker-bake-release.hcl
|
||||
./docker-bake.hcl
|
||||
cwd://${{ steps.login-meta.outputs.bake-file }}
|
4
.github/workflows/ready_for_review.yml
vendored
4
.github/workflows/ready_for_review.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
|
||||
Please make sure you tick the following checkboxes before marking this Pull Request (PR) as ready for review:
|
||||
|
||||
- [ ] I am happy with the code
|
||||
- [ ] I have reviewed my changes and would approve it
|
||||
- [ ] Documentations and examples are up-to-date
|
||||
- [ ] Logical behavior changes are tested automatically
|
||||
- [ ] No debug or dead code
|
||||
@@ -28,4 +28,4 @@ jobs:
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: content
|
||||
})
|
||||
})
|
||||
|
71
.github/workflows/release.yml
vendored
71
.github/workflows/release.yml
vendored
@@ -15,6 +15,15 @@ on:
|
||||
google_image_name:
|
||||
required: true
|
||||
type: string
|
||||
build_image_name_login:
|
||||
required: true
|
||||
type: string
|
||||
image_name_login:
|
||||
required: true
|
||||
type: string
|
||||
google_image_name_login:
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
GCR_JSON_KEY_BASE64:
|
||||
description: 'base64 endcrypted key to connect to Google'
|
||||
@@ -96,6 +105,12 @@ jobs:
|
||||
docker buildx imagetools create \
|
||||
--tag ${{ inputs.google_image_name }}:${{ needs.version.outputs.version }} \
|
||||
${{ inputs.build_image_name }}
|
||||
docker buildx imagetools create \
|
||||
--tag ${{ inputs.image_name_login }}:${{ needs.version.outputs.version }} \
|
||||
${{ inputs.build_image_name_login }}
|
||||
docker buildx imagetools create \
|
||||
--tag ${{ inputs.google_image_name_login }}:${{ needs.version.outputs.version }} \
|
||||
${{ inputs.build_image_name_login }}
|
||||
-
|
||||
name: Publish latest
|
||||
if: ${{ github.ref_name == 'next' }}
|
||||
@@ -106,6 +121,9 @@ jobs:
|
||||
docker buildx imagetools create \
|
||||
--tag ${{ inputs.image_name }}:latest-debug \
|
||||
${{ inputs.build_image_name }}-debug
|
||||
docker buildx imagetools create \
|
||||
--tag ${{ inputs.image_name_login }}:latest \
|
||||
${{ inputs.build_image_name_login }}
|
||||
|
||||
homebrew-tap:
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -146,3 +164,56 @@ jobs:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
run: |
|
||||
gh workflow -R zitadel/zitadel-charts run bump.yml
|
||||
|
||||
npm-packages:
|
||||
runs-on: ubuntu-latest
|
||||
needs: version
|
||||
if: ${{ github.ref_name == 'next' }}
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: login
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Create Release Pull Request
|
||||
uses: changesets/action@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
version: ${{ needs.version.outputs.version }}
|
||||
cwd: packages
|
||||
createGithubReleases: false
|
||||
|
||||
login-repo:
|
||||
runs-on: ubuntu-latest
|
||||
needs: version
|
||||
if: ${{ github.ref_name == 'next' }}
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Push Subtree
|
||||
run: make login_push LOGIN_REMOTE_BRANCH=mirror-zitadel-repo
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: 'chore: mirror zitadel repo'
|
||||
branch: mirror-zitadel-repo
|
||||
title: 'chore: mirror zitadel repo'
|
||||
body: 'This PR updates the login repository with the latest changes from the zitadel repository.'
|
||||
base: main
|
||||
reviewers: |
|
||||
@peintnermax
|
||||
@eliobischof
|
||||
|
14
.gitignore
vendored
14
.gitignore
vendored
@@ -7,6 +7,7 @@
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
!/**/.env.test
|
||||
|
||||
# Coverage
|
||||
coverage.txt
|
||||
@@ -52,7 +53,8 @@ console/src/app/proto/generated/
|
||||
!pkg/grpc/protoc/v2/options.pb.go
|
||||
**.proto.mock.go
|
||||
**.pb.*.go
|
||||
**.gen.go
|
||||
pkg/**/**.connect.go
|
||||
**.gen.go
|
||||
openapi/**/*.json
|
||||
/internal/api/assets/authz.go
|
||||
/internal/api/assets/router.go
|
||||
@@ -67,6 +69,7 @@ docs/docs/apis/proto
|
||||
/internal/api/ui/login/static/resources/themes/zitadel/css/zitadel.css
|
||||
/internal/api/ui/login/static/resources/themes/zitadel/css/zitadel.css.map
|
||||
zitadel-*-*
|
||||
!apps/**/zitadel-*-*
|
||||
|
||||
# local
|
||||
build/local/*.env
|
||||
@@ -83,7 +86,14 @@ go.work.sum
|
||||
.netlify
|
||||
|
||||
load-test/node_modules
|
||||
load-test/yarn-error.log
|
||||
load-test/pnpm-debug.log
|
||||
load-test/dist
|
||||
load-test/output/*
|
||||
.vercel
|
||||
|
||||
# Turbo
|
||||
.turbo/
|
||||
**/.turbo/
|
||||
|
||||
# PNPM
|
||||
.pnpm-store
|
376
.golangci.yaml
376
.golangci.yaml
@@ -1,308 +1,148 @@
|
||||
issues:
|
||||
new-from-rev: main
|
||||
# Set to 0 to disable.
|
||||
max-issues-per-linter: 0
|
||||
# Set to 0 to disable.
|
||||
max-same-issues: 0
|
||||
exclude-dirs:
|
||||
- .artifacts
|
||||
- .backups
|
||||
- .codecov
|
||||
- .github
|
||||
- .keys
|
||||
- .vscode
|
||||
- build
|
||||
- console
|
||||
- deploy
|
||||
- docs
|
||||
- guides
|
||||
- internal/api/ui/login/static
|
||||
- openapi
|
||||
- proto
|
||||
- tools
|
||||
|
||||
version: "2"
|
||||
run:
|
||||
concurrency: 4
|
||||
timeout: 10m
|
||||
go: '1.22'
|
||||
go: "1.24"
|
||||
linters:
|
||||
enable:
|
||||
# Simple linter to check that your code does not contain non-ASCII identifiers [fast: true, auto-fix: false]
|
||||
- asciicheck
|
||||
# checks whether HTTP response body is closed successfully [fast: false, auto-fix: false]
|
||||
- bodyclose
|
||||
# check the function whether use a non-inherited context [fast: false, auto-fix: false]
|
||||
- contextcheck
|
||||
# Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false]
|
||||
- gocognit
|
||||
# Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
|
||||
- unused
|
||||
# Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: false, auto-fix: false]
|
||||
- errcheck
|
||||
# Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`. [fast: false, auto-fix: false]
|
||||
- errname
|
||||
# errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. [fast: false, auto-fix: false]
|
||||
- errorlint
|
||||
# check exhaustiveness of enum switch statements [fast: false, auto-fix: false]
|
||||
- exhaustive
|
||||
# Gci controls golang package import order and makes it always deterministic. [fast: true, auto-fix: false]
|
||||
- gci
|
||||
# Provides diagnostics that check for bugs, performance and style issues. [fast: false, auto-fix: false]
|
||||
- gocognit
|
||||
- gocritic
|
||||
# Linter for Go source code that specializes in simplifying a code [fast: false, auto-fix: false]
|
||||
- gosimple
|
||||
# Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: false, auto-fix: false]
|
||||
- govet
|
||||
# Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
|
||||
- ineffassign
|
||||
# Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
|
||||
- misspell
|
||||
# Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
|
||||
- nakedret
|
||||
# Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: false, auto-fix: false]
|
||||
- staticcheck
|
||||
# Like the front-end of a Go compiler, parses and type-checks Go code [fast: false, auto-fix: false]
|
||||
- typecheck
|
||||
# Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: false]
|
||||
- nolintlint
|
||||
# Checks for misuse of Sprintf to construct a host with port in a URL.
|
||||
- nosprintfhostport
|
||||
# checks whether Err of rows is checked successfully in `sql.Rows` [fast: false, auto-fix: false]
|
||||
- rowserrcheck
|
||||
# Checks that sql.Rows and sql.Stmt are closed. [fast: false, auto-fix: false]
|
||||
- sqlclosecheck
|
||||
# Remove unnecessary type conversions [fast: false, auto-fix: false]
|
||||
- unconvert
|
||||
disable:
|
||||
# Checks for dangerous unicode character sequences [fast: true, auto-fix: false]
|
||||
# not needed because github does that out of the box
|
||||
- bidichk
|
||||
# containedctx is a linter that detects struct contained context.Context field [fast: true, auto-fix: false]
|
||||
# using contextcheck which looks more active
|
||||
- containedctx
|
||||
# checks function and package cyclomatic complexity [fast: false, auto-fix: false]
|
||||
# not use because gocognit is used
|
||||
- cyclop
|
||||
# The owner seems to have abandoned the linter. Replaced by unused.
|
||||
# deprecated, replaced by unused
|
||||
- deadcode
|
||||
# check declaration order and count of types, constants, variables and functions [fast: true, auto-fix: false]
|
||||
# FUTURE: IMO it sometimes makes sense to declare consts or types after a func
|
||||
- decorder
|
||||
# Go linter that checks if package imports are in a list of acceptable packages [fast: false, auto-fix: false]
|
||||
# not required because of dependabot
|
||||
- depguard
|
||||
# Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
|
||||
# FUTURE: old code is not compatible
|
||||
- dogsled
|
||||
# Tool for code clone detection [fast: true, auto-fix: false]
|
||||
# FUTURE: old code is not compatible
|
||||
- dupl
|
||||
# checks for duplicate words in the source code
|
||||
# not sure if it makes sense
|
||||
- dupword
|
||||
# check for two durations multiplied together [fast: false, auto-fix: false]
|
||||
# FUTURE: checks for accident `1 * time.Second * time.Second`
|
||||
- durationcheck
|
||||
# Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted. [fast: false, auto-fix: false]
|
||||
# FUTURE: use asap, because we use json alot. nice feature is possiblity to check if err check is required
|
||||
- errchkjson
|
||||
# execinquery is a linter about query string checker in Query function which reads your Go src files and warning it finds
|
||||
# FUTURE: might find some errors in sql queries
|
||||
- execinquery
|
||||
# Checks if all struct's fields are initialized [fast: false, auto-fix: false]
|
||||
# deprecated
|
||||
- exhaustivestruct
|
||||
# Checks if all structure fields are initialized
|
||||
# Not all fields have to be initialized
|
||||
- exhaustruct
|
||||
# checks for pointers to enclosing loop variables [fast: false, auto-fix: false]
|
||||
# FUTURE: finds bugs hard to find, could occur much later
|
||||
- exportloopref
|
||||
# Forbids identifiers [fast: true, auto-fix: false]
|
||||
# see no reason. allows to define regexp which are not allowed to use
|
||||
- forbidigo
|
||||
# finds forced type assertions [fast: true, auto-fix: false]
|
||||
# not used because we mostly use `_, _ = a.(int)`
|
||||
- forcetypeassert
|
||||
# Tool for detection of long functions [fast: true, auto-fix: false]
|
||||
# not used because it ignores complexity
|
||||
- funlen
|
||||
# check that no global variables exist [fast: true, auto-fix: false]
|
||||
# We use some global variables which is ok IMO
|
||||
- gochecknoglobals
|
||||
# Checks that no init functions are present in Go code [fast: true, auto-fix: false]
|
||||
# we use inits for the database abstraction
|
||||
- gochecknoinits
|
||||
# Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
|
||||
# FUTURE: might be cool to check
|
||||
- goconst
|
||||
# Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false]
|
||||
# not used because cyclop also checks complexity of package
|
||||
- gocyclo
|
||||
# Check if comments end in a period [fast: true, auto-fix: true]
|
||||
# FUTURE: checks if comments are written as specified
|
||||
- godot
|
||||
# Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false]
|
||||
# FUTURE: maybe makes sense later. IMO some view todos are ok for later tasks.
|
||||
- godox
|
||||
# Golang linter to check the errors handling expressions [fast: false, auto-fix: false]
|
||||
# Not used in favore of errorlint
|
||||
- goerr113
|
||||
# Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true]
|
||||
# ignored in favor of goimports
|
||||
- gofmt
|
||||
# Gofumpt checks whether code was gofumpt-ed. [fast: true, auto-fix: true]
|
||||
# ignored in favor of goimports
|
||||
- gofumpt
|
||||
# Checks is file header matches to pattern [fast: true, auto-fix: false]
|
||||
# ignored because we don't write licenses as headers
|
||||
- goheader
|
||||
# In addition to fixing imports, goimports also formats your code in the same style as gofmt. [fast: true, auto-fix: true]
|
||||
# ignored in favor of gci
|
||||
- goimports
|
||||
#deprecated]: Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: false, auto-fix: false]
|
||||
# ignored in favor of goimports
|
||||
- golint
|
||||
# An analyzer to detect magic numbers. [fast: true, auto-fix: false]
|
||||
# FUTURE: not that critical at the moment
|
||||
- gomnd
|
||||
# Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. [fast: true, auto-fix: false]
|
||||
# FUTURE: not a problem at the moment
|
||||
- gomoddirectives
|
||||
# Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. [fast: true, auto-fix: false]
|
||||
# FUTURE: maybe interesting because of licenses
|
||||
- gomodguard
|
||||
# Checks that printf-like functions are named with `f` at the end [fast: true, auto-fix: false]
|
||||
# FUTURE: not a problem at the moment
|
||||
- goprintffuncname
|
||||
# Inspects source code for security problems [fast: false, auto-fix: false]
|
||||
# TODO: I think it would be more interesting to integrate into gh code scanning: https://github.com/securego/gosec#integrating-with-code-scanning
|
||||
- gosec
|
||||
# An analyzer to analyze expression groups. [fast: true, auto-fix: false]
|
||||
# I think the groups (vars, consts, imports, ...) we have atm are ok
|
||||
- grouper
|
||||
# Checks that your code uses short syntax for if-statements whenever possible [fast: true, auto-fix: false]
|
||||
# Dont't use its deprecated
|
||||
- ifshort
|
||||
# Enforces consistent import aliases [fast: false, auto-fix: false]
|
||||
# FUTURE: aliasing of imports is more or less consistent
|
||||
- importas
|
||||
# A linter that checks the number of methods inside an interface.
|
||||
# No need at the moment, repository abstraction was removed
|
||||
- interfacebloat
|
||||
# A linter that suggests interface types
|
||||
# Don't use it's archived
|
||||
- interfacer
|
||||
# Accept Interfaces, Return Concrete Types [fast: false, auto-fix: false]
|
||||
# FUTURE: check if no interface is returned
|
||||
- ireturn
|
||||
# Reports long lines [fast: true, auto-fix: false]
|
||||
# FUTURE: would make code more readable
|
||||
- lll
|
||||
# Checks key valur pairs for common logger libraries (kitlog,klog,logr,zap).
|
||||
# FUTURE: useable as soon as we switch logger library
|
||||
- loggercheck
|
||||
# maintidx measures the maintainability index of each function. [fast: true, auto-fix: false]
|
||||
# not used because volume of halstead complexity feels strange as measurement https://en.wikipedia.org/wiki/Halstead_complexity_measures
|
||||
- maintidx
|
||||
# Finds slice declarations with non-zero initial length [fast: false, auto-fix: false]
|
||||
# I would prefer to use https://github.com/alexkohler/prealloc
|
||||
- makezero
|
||||
# Reports deeply nested if statements [fast: true, auto-fix: false]
|
||||
# focus only on if's
|
||||
- nestif
|
||||
# Finds the code that returns nil even if it checks that the error is not nil. [fast: false, auto-fix: false]
|
||||
# FUTURE: check if it is allowed to return nil partially in error catch
|
||||
- nilerr
|
||||
# Checks that there is no simultaneous return of `nil` error and an invalid value. [fast: false, auto-fix: false]
|
||||
# FUTURE: would reduce checks and panics
|
||||
- errcheck
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- unused
|
||||
- nilnil
|
||||
disable:
|
||||
- bidichk
|
||||
- containedctx
|
||||
- cyclop
|
||||
- decorder
|
||||
- depguard
|
||||
- dogsled
|
||||
- dupl
|
||||
- dupword
|
||||
- durationcheck
|
||||
- err113
|
||||
- errchkjson
|
||||
- exhaustruct
|
||||
- forbidigo
|
||||
- forcetypeassert
|
||||
- funlen
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- goconst
|
||||
- gocyclo
|
||||
- godot
|
||||
- godox
|
||||
- goheader
|
||||
- gomoddirectives
|
||||
- gomodguard
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- grouper
|
||||
- importas
|
||||
- interfacebloat
|
||||
- ireturn
|
||||
- lll
|
||||
- loggercheck
|
||||
- maintidx
|
||||
- makezero
|
||||
- mnd
|
||||
- nestif
|
||||
- nilerr
|
||||
- nilnil
|
||||
# nlreturn checks for a new line before return and branch statements to increase code clarity [fast: true, auto-fix: false]
|
||||
# DISCUSS: IMO the readability of does not always increase using more empty lines
|
||||
- nlreturn
|
||||
# noctx finds sending http request without context.Context [fast: false, auto-fix: false]
|
||||
# only interesting if using http
|
||||
- noctx
|
||||
# Reports all names returns
|
||||
# Named returns are not allowed which IMO reduces readability of code
|
||||
- nonamedreturns
|
||||
# detects snake case of variable naming and function name.
|
||||
# has not been a problem in our code and deprecated
|
||||
- nosnakecase
|
||||
# paralleltest detects missing usage of t.Parallel() method in your Go test [fast: true, auto-fix: false]
|
||||
# FUTURE: will break all of our tests
|
||||
- paralleltest
|
||||
# Finds slice declarations that could potentially be preallocated [fast: true, auto-fix: false]
|
||||
# FUTURE: would improve performance
|
||||
- prealloc
|
||||
# find code that shadows one of Go's predeclared identifiers [fast: true, auto-fix: false]
|
||||
# FUTURE: checks for overwrites
|
||||
- predeclared
|
||||
# Check Prometheus metrics naming via promlint [fast: true, auto-fix: false]
|
||||
# Not interesting at the moment
|
||||
- promlinter
|
||||
# Checks that package variables are not reassigned
|
||||
# FUTURE: checks if vars like Err's are reassigned which might break code
|
||||
- reassign
|
||||
# Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. [fast: false, auto-fix: false]
|
||||
# Linter aggregator, would allow to use less other linters
|
||||
- revive
|
||||
# checks for unpinned variables in go programs
|
||||
# deprecated
|
||||
- scopelint
|
||||
# Finds unused struct fields [fast: false, auto-fix: false]
|
||||
# deprecated, replaced by unused
|
||||
- structcheck
|
||||
# Stylecheck is a replacement for golint [fast: false, auto-fix: false]
|
||||
# we use goimports
|
||||
- stylecheck
|
||||
# Checks the struct tags. [fast: true, auto-fix: false]
|
||||
# FUTURE: would help for new structs
|
||||
- tagliatelle
|
||||
# tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 [fast: false, auto-fix: false]
|
||||
# FUTURE: currently are no env vars set
|
||||
- tenv
|
||||
# linter checks if examples are testable (have an expected output)
|
||||
# FUTURE: as soon as examples are added
|
||||
- testableexamples
|
||||
# linter that makes you use a separate _test package [fast: true, auto-fix: false]
|
||||
# don't use because we test some unexported functions
|
||||
- testpackage
|
||||
# thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers [fast: false, auto-fix: false]
|
||||
# FUTURE: nice to improve test quality
|
||||
- thelper
|
||||
# tparallel detects inappropriate usage of t.Parallel() method in your Go test codes [fast: false, auto-fix: false]
|
||||
# FUTURE: nice to improve test quality
|
||||
- tparallel
|
||||
# Reports unused function parameters [fast: false, auto-fix: false]
|
||||
# DISCUSS: nice idea and would improve code quality, but how to handle false positives?
|
||||
- unparam
|
||||
# A linter that detect the possibility to use variables/constants from the Go standard library.
|
||||
# FUTURE: improves code quality
|
||||
- usestdlibvars
|
||||
# Finds unused global variables and constants [fast: false, auto-fix: false]
|
||||
# deprecated, replaced by unused
|
||||
- varcheck
|
||||
# checks that the length of a variable's name matches its scope [fast: false, auto-fix: false]
|
||||
# I would not use it because it more or less checks if var lenght matches
|
||||
- varnamelen
|
||||
# wastedassign finds wasted assignment statements. [fast: false, auto-fix: false]
|
||||
# FUTURE: would improve code quality (maybe already checked by vet?)
|
||||
- wastedassign
|
||||
# Tool for detection of leading and trailing whitespace [fast: true, auto-fix: true]
|
||||
# Not sure if it improves code readability
|
||||
- whitespace
|
||||
# Checks that errors returned from external packages are wrapped [fast: false, auto-fix: false]
|
||||
# FUTURE: improves UX because all the errors will be ZITADEL errors
|
||||
- wrapcheck
|
||||
# Whitespace Linter - Forces you to use empty lines! [fast: true, auto-fix: false]
|
||||
# FUTURE: improves code quality by allowing and blocking line breaks
|
||||
- wsl
|
||||
linters-settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard # Standard section: captures all standard packages.
|
||||
- default # Default section: contains all imports that could not be matched to another section type.
|
||||
- prefix(github.com/zitadel/zitadel) # Custom section: groups all imports with the specified Prefix.
|
||||
custom-order: true
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- .artifacts
|
||||
- .backups
|
||||
- .codecov
|
||||
- .github
|
||||
- .keys
|
||||
- .vscode
|
||||
- build
|
||||
- console
|
||||
- deploy
|
||||
- docs
|
||||
- guides
|
||||
- internal/api/ui/login/static
|
||||
- openapi
|
||||
- proto
|
||||
- tools
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
issues:
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
new-from-rev: main
|
||||
formatters:
|
||||
enable:
|
||||
- gci
|
||||
settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/zitadel/zitadel)
|
||||
custom-order: true
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- .artifacts
|
||||
- .backups
|
||||
- .codecov
|
||||
- .github
|
||||
- .keys
|
||||
- .vscode
|
||||
- build
|
||||
- console
|
||||
- deploy
|
||||
- docs
|
||||
- guides
|
||||
- internal/api/ui/login/static
|
||||
- openapi
|
||||
- proto
|
||||
- tools
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
2
.npmrc
Normal file
2
.npmrc
Normal file
@@ -0,0 +1,2 @@
|
||||
auto-install-peers = true
|
||||
ignore-scripts = "postman-code-generators"
|
@@ -23,6 +23,7 @@ If you are using Zitadel, please consider adding yourself as a user with a quick
|
||||
| OpenAIP | [@openaip](https://github.com/openAIP) | Using Zitadel Cloud for everything related to user authentication. |
|
||||
| Smat.io | [@smatio](https://github.com/smatio) - [@lukasver](https://github.com/lukasver) | Zitadel for authentication in cloud applications while offering B2B portfolio management solutions for professional investors |
|
||||
| roclub GmbH | [@holgerson97](https://github.com/holgerson97) | Roclub builds a telehealth application to enable remote MRI/CT examinations. |
|
||||
| CEEX AG | [@cleanenergyexchange](https://github.com/cleanenergyexchange) | Using Zitadel cloud for our SaaS products that support the sustainabel energy transistion |
|
||||
| Organization Name | contact@example.com | Description of how they use Zitadel |
|
||||
| Individual Name | contact@example.com | Description of how they use Zitadel |
|
||||
|
||||
|
172
API_DESIGN.md
172
API_DESIGN.md
@@ -48,6 +48,52 @@ When creating a new service, start with version `2`, as version `1` is reserved
|
||||
|
||||
Please check out the structure Buf style guide for more information about the folder and package structure: https://buf.build/docs/best-practices/style-guide/
|
||||
|
||||
### Deprecations
|
||||
|
||||
As a rule of thumb, redundant API methods are deprecated.
|
||||
|
||||
- The proto option `grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation.deprecated` MUST be set to true.
|
||||
- One or more links to recommended replacement methods MUST be added to the deprecation message as a proto comment above the rpc spec.
|
||||
- Guidance for switching to the recommended methods for common use cases SHOULD be added as a proto comment above the rpc spec.
|
||||
|
||||
#### Example
|
||||
|
||||
```protobuf
|
||||
// Delete the user phone
|
||||
//
|
||||
// Deprecated: [Update the user's phone field](apis/resources/user_service_v2/user-service-update-user.api.mdx) to remove the phone number.
|
||||
//
|
||||
// Delete the phone number of a user.
|
||||
rpc RemovePhone(RemovePhoneRequest) returns (RemovePhoneResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/v2/users/{user_id}/phone"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "authenticated"
|
||||
}
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
deprecated: true;
|
||||
responses: {
|
||||
key: "200"
|
||||
value: {
|
||||
description: "OK";
|
||||
}
|
||||
};
|
||||
responses: {
|
||||
key: "404";
|
||||
value: {
|
||||
description: "User ID does not exist.";
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Explicitness
|
||||
|
||||
Make the handling of the API as explicit as possible. Do not make assumptions about the client's knowledge of the system or the API.
|
||||
@@ -56,6 +102,10 @@ Provide clear and concise documentation for the API.
|
||||
Do not rely on implicit fallbacks or defaults if the client does not provide certain parameters.
|
||||
Only use defaults if they are explicitly documented, such as returning a result set for the whole instance if no filter is provided.
|
||||
|
||||
Some API calls may create multiple resources such as in the case of `zitadel.org.v2.AddOrganization`, where you can create an organization AND multiple users as admin.
|
||||
In such cases the response should include **ALL** created resources and their ids. This removes any ambiguity from the users perspective whether or not
|
||||
the additional resources were created and it also helps in testing.
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
Names of resources, fields and methods MUST be descriptive and consistent.
|
||||
@@ -69,6 +119,8 @@ For example, use `organization_id` instead of **org_id** or **resource_owner** f
|
||||
|
||||
#### Resources and Fields
|
||||
|
||||
##### Context information in Requests
|
||||
|
||||
When a context is required for creating a resource, the context is added as a field to the resource.
|
||||
For example, when creating a new user, the organization's id is required. The `organization_id` is added as a field to the `CreateUserRequest`.
|
||||
|
||||
@@ -83,9 +135,70 @@ message CreateUserRequest {
|
||||
```
|
||||
|
||||
Only allow providing a context where it is required. The context MUST not be provided if not required.
|
||||
If the context is required but deferrable, the context can be defaulted.
|
||||
For example, creating an Authorization without an organization id will default the organization id to the projects resource owner.
|
||||
For example, when retrieving or updating a user, the `organization_id` is not required, since the user can be determined by the user's id.
|
||||
However, it is possible to provide the `organization_id` as a filter to retrieve a list of users of a specific organization.
|
||||
|
||||
##### Context information in Responses
|
||||
|
||||
When the action of creation, update or deletion of a resource was successful, the returned response has to include the time of the operation and the generated identifiers.
|
||||
This is achieved through the addition of a timestamp attribute with the operation as a prefix, and the generated information as separate attributes.
|
||||
|
||||
```protobuf
|
||||
message SetExecutionResponse {
|
||||
// The timestamp of the execution set.
|
||||
google.protobuf.Timestamp set_date = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"2024-12-18T07:50:47.492Z\"";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message CreateTargetResponse {
|
||||
// The unique identifier of the newly created target.
|
||||
string id = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"69629012906488334\"";
|
||||
}
|
||||
];
|
||||
// The timestamp of the target creation.
|
||||
google.protobuf.Timestamp creation_date = 2 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"2024-12-18T07:50:47.492Z\"";
|
||||
}
|
||||
];
|
||||
// Key used to sign and check payload sent to the target.
|
||||
string signing_key = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"98KmsU67\""
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message UpdateProjectGrantResponse {
|
||||
// The timestamp of the change of the project grant.
|
||||
google.protobuf.Timestamp change_date = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message DeleteProjectGrantResponse {
|
||||
// The timestamp of the deletion of the project grant.
|
||||
// Note that the deletion date is only guaranteed to be set if the deletion was successful during the request.
|
||||
// In case the deletion occurred in a previous request, the deletion date might be empty.
|
||||
google.protobuf.Timestamp deletion_date = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||
}
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
##### Global messages
|
||||
|
||||
Prevent the creation of global messages that are used in multiple resources unless they always follow the same pattern.
|
||||
Use dedicated fields as described above or create a separate message for the specific context, that is only used in the boundary of the same resource.
|
||||
For example, settings might be set as a default on the instance level, but might be overridden on the organization level.
|
||||
@@ -95,6 +208,10 @@ The same applies to messages that are returned by multiple resources.
|
||||
For example, information about the `User` might be different when managing the user resource itself than when it's returned
|
||||
as part of an authorization or a manager role, where only limited information is needed.
|
||||
|
||||
On the other hand, types that always follow the same pattern and are used in multiple resources, such as `IDFilter`, `TimestampFilter` or `InIDsFilter` SHOULD be globalized and reused.
|
||||
|
||||
##### Re-using messages
|
||||
|
||||
Prevent reusing messages for the creation and the retrieval of a resource.
|
||||
Returning messages might contain additional information that is not required or even not available for the creation of the resource.
|
||||
What might sound obvious when designing the CreateUserRequest for example, where only an `organization_id` but not the
|
||||
@@ -158,7 +275,7 @@ Additionally, state changes, specific actions or operations that do not fit into
|
||||
The API uses OAuth 2 for authorization. There are corresponding middlewares that check the access token for validity and
|
||||
automatically return an error if the token is invalid.
|
||||
|
||||
Permissions grated to the user might be organization specific and can therefore only be checked based on the queried resource.
|
||||
Permissions granted to the user might be organization specific and can therefore only be checked based on the queried resource.
|
||||
In such case, the API does not check the permissions itself but relies on the checks of the functions that are called by the API.
|
||||
If the permission can be checked by the API itself, e.g. if the permission is instance wide, it can be annotated on the endpoint in the proto file (see below).
|
||||
In any case, the required permissions need to be documented in the [API documentation](#documentation).
|
||||
@@ -186,33 +303,54 @@ In case the permission cannot be checked by the API itself, but all requests nee
|
||||
};
|
||||
```
|
||||
|
||||
## Pagination
|
||||
## Listing resources
|
||||
|
||||
The API uses pagination for listing resources. The client can specify a limit and an offset to retrieve a subset of the resources.
|
||||
Additionally, the client can specify sorting options to sort the resources by a specific field.
|
||||
|
||||
Most listing methods SHOULD provide use the `ListQuery` message to allow the client to specify the limit, offset, and sorting options.
|
||||
```protobuf
|
||||
### Pagination
|
||||
|
||||
// ListQuery is a general query object for lists to allow pagination and sorting.
|
||||
message ListQuery {
|
||||
uint64 offset = 1;
|
||||
// limit is the maximum amount of objects returned. The default is set to 100
|
||||
// with a maximum of 1000 in the runtime configuration.
|
||||
// If the limit exceeds the maximum configured ZITADEL will throw an error.
|
||||
// If no limit is present the default is taken.
|
||||
uint32 limit = 2;
|
||||
// Asc is the sorting order. If true the list is sorted ascending, if false
|
||||
// the list is sorted descending. The default is descending.
|
||||
bool asc = 3;
|
||||
Most listing methods SHOULD use the `PaginationRequest` message to allow the client to specify the limit, offset, and sorting options.
|
||||
```protobuf
|
||||
message ListTargetsRequest {
|
||||
// List limitations and ordering.
|
||||
optional zitadel.filter.v2beta.PaginationRequest pagination = 1;
|
||||
// The field the result is sorted by. The default is the creation date. Beware that if you change this, your result pagination might be inconsistent.
|
||||
optional TargetFieldName sorting_column = 2 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
default: "\"TARGET_FIELD_NAME_CREATION_DATE\""
|
||||
}
|
||||
];
|
||||
// Define the criteria to query for.
|
||||
repeated TargetSearchFilter filters = 3;
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
|
||||
example: "{\"pagination\":{\"offset\":0,\"limit\":0,\"asc\":true},\"sortingColumn\":\"TARGET_FIELD_NAME_CREATION_DATE\",\"filters\":[{\"targetNameFilter\":{\"targetName\":\"ip_allow_list\",\"method\":\"TEXT_FILTER_METHOD_EQUALS\"}},{\"inTargetIdsFilter\":{\"targetIds\":[\"69629023906488334\",\"69622366012355662\"]}}]}";
|
||||
};
|
||||
}
|
||||
```
|
||||
On the corresponding responses the `ListDetails` can be used to return the total count of the resources
|
||||
|
||||
On the corresponding responses the `PaginationResponse` can be used to return the total count of the resources
|
||||
and allow the user to handle their offset and limit accordingly.
|
||||
|
||||
The API MUST enforce a reasonable maximum limit for the number of resources that can be retrieved and returned in a single request.
|
||||
The default limit is set to 100 and the maximum limit is set to 1000. If the client requests a limit that exceeds the maximum limit, an error is returned.
|
||||
|
||||
### Filter method
|
||||
|
||||
All filters in List operations SHOULD provide a method if not already specified by the filters name.
|
||||
```protobuf
|
||||
message TargetNameFilter {
|
||||
// Defines the name of the target to query for.
|
||||
string target_name = 1 [
|
||||
(validate.rules).string = {max_len: 200}
|
||||
];
|
||||
// Defines which text comparison method used for the name query.
|
||||
zitadel.filter.v2beta.TextFilterMethod method = 2 [
|
||||
(validate.rules).enum.defined_only = true
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The API returns machine-readable errors in the response body. This includes a status code, an error code and possibly
|
||||
@@ -371,4 +509,4 @@ message VerifyEmailRequest{
|
||||
];
|
||||
}
|
||||
|
||||
```
|
||||
```
|
||||
|
424
CONTRIBUTING.md
424
CONTRIBUTING.md
@@ -1,4 +1,5 @@
|
||||
# Contributing to ZITADEL
|
||||
# Contributing to Zitadel
|
||||
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -12,24 +13,29 @@ If you want to give an answer or be part of discussions please be kind. Treat ot
|
||||
|
||||
## What can I contribute?
|
||||
|
||||
For people who are new to ZITADEL: We flag issues which are a good starting point to start contributing. You find them [here](https://github.com/zitadel/zitadel/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
|
||||
For people who are new to Zitadel: We flag issues which are a good starting point to start contributing.
|
||||
You find them [here](https://github.com/zitadel/zitadel/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
|
||||
We add the label "good first issue" for problems we think are a good starting point to contribute to Zitadel.
|
||||
|
||||
Make ZITADEL more popular and give it a ⭐
|
||||
- [Issues for first time contributors](https://github.com/zitadel/zitadel/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
|
||||
- [All issues](https://github.com/zitadel/zitadel/issues)
|
||||
|
||||
Help shaping the future of ZITADEL:
|
||||
Help shaping the future of Zitadel:
|
||||
|
||||
- Join our [chat](https://zitadel.com/chat) and discuss with us or others.
|
||||
- Ask or answer questions in the [issues section](https://github.com/zitadel/zitadel/issues)
|
||||
- Share your thoughts and ideas in the [discussions section](https://github.com/zitadel/zitadel/discussions)
|
||||
|
||||
Make Zitadel more popular and give it a ⭐
|
||||
|
||||
Follow [@zitadel](https://twitter.com/zitadel) on twitter
|
||||
|
||||
[Contribute](#how-to-contribute)
|
||||
|
||||
- [Contribute code](#contribute)
|
||||
- If you found a mistake on our [docs page](https://zitadel.com/docs) or something is missing please read [the docs section](#contribute-docs)
|
||||
- If you found a mistake on our [docs page](https://zitadel.com/docs) or something is missing please read [the docs section](contribute-docs)
|
||||
- [Translate](#contribute-internationalization) and improve texts
|
||||
|
||||
Follow [@zitadel](https://twitter.com/zitadel) on twitter
|
||||
|
||||
## How to contribute
|
||||
|
||||
We strongly recommend to [talk to us](https://zitadel.com/contact) before you start contributing to streamline our and your work.
|
||||
@@ -40,6 +46,21 @@ If you are unfamiliar with git have a look at Github's documentation on [creatin
|
||||
Please draft the pull request as soon as possible.
|
||||
Go through the following checklist before you submit the final pull request:
|
||||
|
||||
### Components
|
||||
|
||||
The code consists of the following parts:
|
||||
|
||||
| name | description | language | where to find | Development Guide |
|
||||
| --------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | -------------------------------------------------- |
|
||||
| backend | Service that serves the grpc(-web) and RESTful API | [go](https://go.dev) | [API implementation](./internal/api/grpc) | [Contribute to Backend](contribute-backend) |
|
||||
| API definitions | Specifications of the API | [Protobuf](https://developers.google.com/protocol-buffers) | [./proto/zitadel](./proto/zitadel) | [Contribute to Backend](contribute-backend) |
|
||||
| console | Frontend the user interacts with after log in | [Angular](https://angular.io), [Typescript](https://www.typescriptlang.org) | [./console](./console) | [Contribute to Frontend](contribute-frontend) |
|
||||
| login | Modern authentication UI built with Next.js | [Next.js](https://nextjs.org), [React](https://reactjs.org), [TypeScript](https://www.typescriptlang.org) | [./login](./login) | [Contribute to Frontend](contribute-frontend) |
|
||||
| docs | Project documentation made with docusaurus | [Docusaurus](https://docusaurus.io/) | [./docs](./docs) | [Contribute to Frontend](contribute-frontend) |
|
||||
| translations | Internationalization files for default languages | YAML | [./console](./console) and [./internal](./internal) | [Contribute Translations](contribute-translations) |
|
||||
|
||||
Please follow the guides to validate and test the code before you contribute.
|
||||
|
||||
### 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
|
||||
@@ -104,25 +125,6 @@ Please make sure you cover your changes with tests before marking a Pull Request
|
||||
- [ ] Integration tests ensure that certain commands emit expected events that trigger notifications.
|
||||
- [ ] Integration tests ensure that certain events trigger expected notifications.
|
||||
|
||||
## Contribute
|
||||
|
||||
The code consists of the following parts:
|
||||
|
||||
| name | description | language | where to find |
|
||||
| --------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------- | -------------------------------------------------- |
|
||||
| backend | Service that serves the grpc(-web) and RESTful API | [go](https://go.dev) | [API implementation](./internal/api/grpc) |
|
||||
| console | Frontend the user interacts with after log in | [Angular](https://angular.io), [Typescript](https://www.typescriptlang.org) | [./console](./console) |
|
||||
| login | Server side rendered frontend the user interacts with during login | [go](https://go.dev), [go templates](https://pkg.go.dev/html/template) | [./internal/api/ui/login](./internal/api/ui/login) |
|
||||
| API definitions | Specifications of the API | [Protobuf](https://developers.google.com/protocol-buffers) | [./proto/zitadel](./proto/zitadel) |
|
||||
| docs | Project documentation made with docusaurus | [Docusaurus](https://docusaurus.io/) | [./docs](./docs) |
|
||||
|
||||
Please validate and test the code before you contribute.
|
||||
|
||||
We add the label "good first issue" for problems we think are a good starting point to contribute to ZITADEL.
|
||||
|
||||
- [Issues for first time contributors](https://github.com/zitadel/zitadel/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
|
||||
- [All issues](https://github.com/zitadel/zitadel/issues)
|
||||
|
||||
### General Guidelines
|
||||
|
||||
#### Gender Neutrality and Inclusive Language
|
||||
@@ -143,34 +145,62 @@ Choose alternative words depending on the context.
|
||||
|
||||
### API
|
||||
|
||||
ZITADEL follows an API first approach. This means all features can not only be accessed via the UI but also via the API.
|
||||
Zitadel follows an API first approach. This means all features can not only be accessed via the UI but also via the API.
|
||||
The API is designed to be used by different clients, such as web applications, mobile applications, and other services.
|
||||
Therefore, the API is designed to be easy to use, consistent, and reliable.
|
||||
Please check out the dedicated [API guidelines](./API_DESIGN.md) page when contributing to the API.
|
||||
|
||||
### Developing ZITADEL with Dev Containers
|
||||
|
||||
Follow the instructions provided by your code editor/IDE to initiate the development container. This typically involves opening the "Command Palette" or similar functionality and searching for commands related to "Dev Containers" or "Remote Containers". The quick start guide for VS Code can found [here](https://code.visualstudio.com/docs/devcontainers/containers#_quick-start-open-an-existing-folder-in-a-container)
|
||||
#### <a name="dev-containers"></>Developing Zitadel with Dev Containers
|
||||
|
||||
When you are connected to the container run the following commands to start ZITADEL.
|
||||
You can use dev containers if you'd like to make sure you have the same development environment like the corresponding GitHub PR checks use.
|
||||
The following dev containers are available:
|
||||
|
||||
- **.devcontainer/base/devcontainer.json**: Contains everything you need to run whatever you want.
|
||||
- **.devcontainer/turbo-lint-unit/devcontainer.json**: Runs a dev container that executes frontent linting and unit tests and then exits. This is useful to reproduce the corresponding GitHub PR check.
|
||||
- **.devcontainer/turbo-lint-unit-debug/devcontainer.json**: Runs a dev container that executes frontent linting and unit tests in watch mode. You can fix the errors right away and have immediate feedback.
|
||||
- **.devcontainer/login-integration/devcontainer.json**: Runs a dev container that executes login integration tests and then exits. This is useful to reproduce the corresponding GitHub PR check.
|
||||
- **.devcontainer/login-integration-debug/devcontainer.json**: Runs a dev container that spins up the login in a hot-reloading dev server and executes login integration tests interactively. You can fix the errors right away and have immediate feedback.
|
||||
|
||||
You can also run the GitHub PR checks locally in dev containers without having to connect to a dev container.
|
||||
|
||||
|
||||
The following pnpm commands use the [devcontainer CLI](https://github.com/devcontainers/cli/) and exit when the checks are done.
|
||||
The minimal system requirements are having Docker and the devcontainers CLI installed.
|
||||
If you don't have the node_modules installed already, you need to install the devcontainers CLI manually. Run `npm i -g @devcontainers/cli`. Alternatively, the [official Microsoft VS Code extension for Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) offers a command `Dev Containers: Install devcontainer CLI`
|
||||
|
||||
|
||||
```bash
|
||||
npm run devcontainer:lint-unit
|
||||
npm run devcontainer:integration:login
|
||||
```
|
||||
|
||||
If you don't have NPM installed, copy and execute the scripts from the package.json directly.
|
||||
|
||||
To connect to a dev container to have full IDE support, follow the instructions provided by your code editor/IDE to initiate the dev container.
|
||||
This typically involves opening the "Command Palette" or similar functionality and searching for commands related to "Dev Containers" or "Remote Containers".
|
||||
The quick start guide for VS Code can found [here](https://code.visualstudio.com/docs/devcontainers/containers#_quick-start-open-an-existing-folder-in-a-container)
|
||||
|
||||
For example, to build and run the Zitadel binary in a dev container, connect your IDE to the dev container described in .devcontainer/base/devcontainer.json.
|
||||
Run the following commands inside the container to start Zitadel.
|
||||
|
||||
```bash
|
||||
make compile && ./zitadel start-from-init --masterkey MasterkeyNeedsToHave32Characters --tlsMode disabled
|
||||
```
|
||||
|
||||
ZITADEL serves traffic as soon as you can see the following log line:
|
||||
Zitadel serves traffic as soon as you can see the following log line:
|
||||
|
||||
`INFO[0001] server is listening on [::]:8080`
|
||||
|
||||
### Backend/login
|
||||
## <a name="backend"></a>Contribute Backend Code
|
||||
|
||||
By executing the commands from this section, you run everything you need to develop the ZITADEL backend locally.
|
||||
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 [PostgreSQL](https://www.postgresql.org/download/) on your local machine.
|
||||
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).
|
||||
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.
|
||||
|
||||
ZITADEL uses [golangci-lint](https://golangci-lint.run) for code quality checks. Please use [this configuration](.golangci.yaml) when running `golangci-lint`. We recommend to set golangci-lint as linter in your IDE.
|
||||
Zitadel uses [golangci-lint v2](https://golangci-lint.run) for code quality checks. Please use [this configuration](.golangci.yaml) when running `golangci-lint`. We recommend to set golangci-lint as linter in your IDE.
|
||||
|
||||
The commands in this section are tested against the following software versions:
|
||||
|
||||
@@ -199,10 +229,10 @@ make compile
|
||||
> 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.
|
||||
You can test if Zitadel does what you expect by using the UI at http://localhost:8080/ui/console.
|
||||
Also, you can verify the data by running `psql "host=localhost dbname=zitadel sslmode=disable"` and running SQL queries.
|
||||
|
||||
#### Run Local Unit Tests
|
||||
### Run Local Unit Tests
|
||||
|
||||
To test the code without dependencies, run the unit tests:
|
||||
|
||||
@@ -210,11 +240,11 @@ To test the code without dependencies, run the unit tests:
|
||||
make core_unit_test
|
||||
```
|
||||
|
||||
#### Run Local Integration Tests
|
||||
### Run Local Integration Tests
|
||||
|
||||
Integration tests are run as gRPC clients against a running ZITADEL server binary.
|
||||
Integration tests are run as gRPC clients against a running Zitadel server binary.
|
||||
The server binary is typically [build with coverage enabled](https://go.dev/doc/build-cover).
|
||||
It is also possible to run a ZITADEL sever in a debugger and run the integrations tests like that. In order to run the server, a database is required.
|
||||
It is also possible to run a Zitadel sever in a debugger and run the integrations tests like that. In order to run the server, a database is required.
|
||||
|
||||
In order to prepare the local system, the following will bring up the database, builds a coverage binary, initializes the database and starts the sever.
|
||||
|
||||
@@ -237,7 +267,7 @@ To run all available integration tests:
|
||||
make core_integration_test_packages
|
||||
```
|
||||
|
||||
When you change any ZITADEL server code, be sure to rebuild and restart the server before the next test run.
|
||||
When you change any Zitadel server code, be sure to rebuild and restart the server before the next test run.
|
||||
|
||||
```bash
|
||||
make core_integration_server_stop core_integration_server_start
|
||||
@@ -251,69 +281,83 @@ make core_integration_server_stop core_integration_db_down
|
||||
|
||||
The test binary has the race detector enabled. `core_core_integration_server_stop` checks for any race logs reported by Go and will print them along a `66` exit code when found. Note that the actual race condition may have happened anywhere during the server lifetime, including start, stop or serving gRPC requests during tests.
|
||||
|
||||
#### Run Local End-to-End Tests
|
||||
### Run Local End-to-End Tests
|
||||
|
||||
To test the whole system, including the console UI and the login UI, run the E2E tests.
|
||||
|
||||
```bash
|
||||
# Build the production docker image
|
||||
export ZITADEL_IMAGE=zitadel:local GOOS=linux
|
||||
export Zitadel_IMAGE=zitadel:local GOOS=linux
|
||||
make docker_image
|
||||
|
||||
# If you made changes in the e2e directory, make sure you reformat the files
|
||||
make console_lint
|
||||
pnpm turbo lint:fix --filter=e2e
|
||||
|
||||
# Run the tests
|
||||
docker compose --file ./e2e/config/host.docker.internal/docker-compose.yaml run --service-ports e2e
|
||||
docker compose --file ./e2e/docker-compose.yaml run --service-ports e2e
|
||||
```
|
||||
|
||||
When you are happy with your changes, you can cleanup your environment.
|
||||
|
||||
```bash
|
||||
# Stop and remove the docker containers for zitadel and the database
|
||||
docker compose --file ./e2e/config/host.docker.internal/docker-compose.yaml down
|
||||
docker compose --file ./e2e/docker-compose.yaml down
|
||||
```
|
||||
|
||||
#### Run Local End-to-End Tests Against Your Dev Server Console
|
||||
### Run Local End-to-End Tests Against Your Dev Server Console
|
||||
|
||||
If you also make [changes to the console](#console), you can run the test suite against your locally built backend code and frontend server.
|
||||
But you will have to install the relevant node dependencies.
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
(cd ./e2e && npm install)
|
||||
# Install dependencies (from repository root)
|
||||
pnpm install
|
||||
|
||||
# Run the tests interactively
|
||||
(cd ./e2e && npm run open:golangangular)
|
||||
pnpm run open:golangangular
|
||||
|
||||
# Run the tests non-interactively
|
||||
(cd ./e2e && npm run e2e:golangangular)
|
||||
pnpm run e2e:golangangular
|
||||
```
|
||||
|
||||
When you are happy with your changes, you can cleanup your environment.
|
||||
|
||||
```bash
|
||||
# Stop and remove the docker containers for zitadel and the database
|
||||
docker compose --file ./e2e/config/host.docker.internal/docker-compose.yaml down
|
||||
docker compose --file ./e2e/docker-compose.yaml down
|
||||
```
|
||||
|
||||
### Console
|
||||
## Contribute Frontend Code
|
||||
|
||||
By executing the commands from this section, you run everything you need to develop the console locally.
|
||||
Using [Docker Compose](https://docs.docker.com/compose/), you run [PostgreSQL](https://www.postgresql.org/download/) and the [latest release of ZITADEL](https://github.com/zitadel/zitadel/releases/latest) on your local machine.
|
||||
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.
|
||||
This repository uses **pnpm** as package manager and **Turbo** for build orchestration.
|
||||
All frontend packages are managed as a monorepo with shared dependencies and optimized builds:
|
||||
|
||||
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)
|
||||
- [apps/login](contribute-login) (depends on packages/zitadel-client and packages/zitadel-proto)
|
||||
- apps/login/integration
|
||||
- apps/login/acceptance
|
||||
- [console](contribute-console) (depends on packages/zitadel-client)
|
||||
- packages/zitadel-client
|
||||
- packages/zitadel-proto
|
||||
- [docs](contribute-docs)
|
||||
|
||||
Once you are happy with your changes, you run end-to-end tests and tear everything down.
|
||||
### <a name="frontend-dev-requirements"></a>Frontend Development Requirements
|
||||
|
||||
The frontend components are run in a [Node](https://nodejs.org/en/about/) environment and are managed using the pnpm package manager and the Turborepo orchestrator.
|
||||
|
||||
> [!INFO]
|
||||
> Some [dev containers are available](dev-containers) for remote development with docker and pipeline debugging in isolated environments.
|
||||
> If you don't want to use one of the dev containers, you can develop the frontend components directly on your local machine.
|
||||
> To do so, proceed with installing the necessary dependencies.
|
||||
|
||||
We use **pnpm** as package manager and **Turbo** for build orchestration. Use angular-eslint/Prettier for linting/formatting.
|
||||
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 during development.
|
||||
|
||||
The commands in this section are tested against the following software versions:
|
||||
|
||||
- [Docker version 20.10.17](https://docs.docker.com/engine/install/)
|
||||
- [Node version v16.17.0](https://nodejs.org/en/download/)
|
||||
- [npm version 8.18.0](https://docs.npmjs.com/try-the-latest-stable-version-of-npm)
|
||||
- [Cypress runtime dependencies](https://docs.cypress.io/guides/continuous-integration/introduction#Dependencies)
|
||||
- [Node version v20.x](https://nodejs.org/en/download/)
|
||||
- [pnpm version 9.x](https://pnpm.io/installation)
|
||||
|
||||
To run tests with Cypress, ensure you have installed the required [Cypress runtime dependencies](https://docs.cypress.io/guides/continuous-integration/introduction#Dependencies)
|
||||
|
||||
<details>
|
||||
<summary>Note for WSL2 on Windows 10</summary>
|
||||
@@ -325,97 +369,165 @@ The commands in this section are tested against the following software versions:
|
||||
4. When starting XLaunch, make sure to disable access control
|
||||
</details>
|
||||
|
||||
### <a name="contribute-login"></a>Contribute to Login
|
||||
|
||||
The Login UI is a Next.js application that provides the user interface for authentication flows.
|
||||
It's located in the `apps/login` directory and uses pnpm and Turbo for development.
|
||||
|
||||
To start developing the login, make sure your system has the [required system dependencies](frontend-dev-requirements) installed.
|
||||
|
||||
#### Development Setup
|
||||
|
||||
```bash
|
||||
# Start from the root of the repository
|
||||
# Start the database and Zitadel backend
|
||||
docker compose --file ./apps/login/acceptance/docker-compose.yaml up --detach zitadel
|
||||
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Option 1: Run login development server with Turbo (recommended)
|
||||
pnpm turbo dev --filter=@zitadel/login
|
||||
|
||||
# Option 2: Build and serve login (production build)
|
||||
pnpm turbo build --filter=@zitadel/login
|
||||
cd ./login && pnpm start
|
||||
```
|
||||
|
||||
The login UI is available at http://localhost:3000.
|
||||
|
||||
#### Login Architecture
|
||||
|
||||
The login application consists of multiple packages:
|
||||
|
||||
- `@zitadel/login` - Main Next.js application
|
||||
- `@zitadel/client` - TypeScript client library for Zitadel APIs
|
||||
- `@zitadel/proto` - Protocol buffer definitions and generated code
|
||||
|
||||
The build process uses Turbo to orchestrate dependencies:
|
||||
|
||||
1. Proto generation (`@zitadel/proto#generate`)
|
||||
2. Client library build (`@zitadel/client#build`)
|
||||
3. Login application build (`@zitadel/login#build`)
|
||||
|
||||
#### Pass Quality Checks
|
||||
|
||||
Reproduce the pipelines linting and testing for the login.
|
||||
|
||||
```bash
|
||||
pnpm turbo quality --filter=./apps/login/* --filter=./packages/*
|
||||
```
|
||||
|
||||
Fix the [quality checks](troubleshoot-frontend), add new checks that cover your changes and mark your pull request as ready for review when the pipeline checks pass.
|
||||
|
||||
### <a name="contribute-console"></a>Contribute to Console
|
||||
|
||||
To start developing the console, make sure your system has the [required system dependencies](frontend-dev-requirements) installed.
|
||||
Then, you need to decide which Zitadel instance you would like to target.
|
||||
- The easiest starting point is to [configure your environment](console-dev-existing-zitadel) to use a [Zitadel cloud](https://zitadel.com) instance.
|
||||
- Alternatively, you can [start a local Zitadel instance from scratch and develop against it](console-dev-local-zitadel).
|
||||
|
||||
#### <a name="console-dev-existing-zitadel"></a>Develop against an already running Zitadel instance
|
||||
|
||||
By default, `pnpm dev --filter=console` targets a Zitadel API running at http://localhost:8080.
|
||||
To change this, export the link to your environment.json in your environment variables.
|
||||
|
||||
```bash
|
||||
export ENVIRONMENT_JSON_URL=https://my-cloud-instance-abcdef.us1.zitadel.cloud/ui/console/assets/environment.json
|
||||
```
|
||||
|
||||
Proceed [with configuring your console redirect URIs](console-redirect).
|
||||
|
||||
#### <a name="console-dev-local-zitadel"></a>Develop against a local Zitadel instance from scratch
|
||||
|
||||
By executing the commands from this section, you run everything you need to develop the console locally.
|
||||
Using [Docker Compose](https://docs.docker.com/compose/), you run [PostgreSQL](https://www.postgresql.org/download/) and the [latest release of Zitadel](https://github.com/zitadel/zitadel/releases/latest) on your local machine.
|
||||
You use the Zitadel container as backend for your console.
|
||||
|
||||
Run the database and the latest backend locally.
|
||||
|
||||
```bash
|
||||
# Change to the console directory
|
||||
cd ./console
|
||||
|
||||
# Start from the root of the repository
|
||||
# You just need the db and the zitadel services to develop the console against.
|
||||
docker compose --file ../e2e/docker-compose.yaml up --detach zitadel
|
||||
docker compose --file ./e2e/docker-compose.yaml up --detach zitadel
|
||||
```
|
||||
|
||||
When the backend is ready, you have the latest zitadel exposed at http://localhost:8080.
|
||||
You can now run a local development server with live code reloading at http://localhost:4200.
|
||||
To allow console access via http://localhost:4200, you have to configure the ZITADEL backend.
|
||||
When Zitadel accepts traffic, navigate to http://localhost:8080/ui/console/projects?login_hint=zitadel-admin@zitadel.localhost and log in with _Password1!_.
|
||||
|
||||
1. Navigate to <http://localhost:8080/ui/console/projects>.
|
||||
2. When prompted, login with _zitadel-admin@<span because="breaks the mailto"></span>zitadel.localhost_ and _Password1!_
|
||||
3. Select the _ZITADEL_ project.
|
||||
Proceed [with configuring your console redirect URIs](console-redirect).
|
||||
|
||||
#### <a name="console-redirect"></a> Configure Console redirect URI
|
||||
|
||||
To allow console access via http://localhost:4200, you have to configure the Zitadel backend.
|
||||
|
||||
1. Navigate to /ui/console/projects in your target Zitadel instance.
|
||||
3. Select the _Zitadel_ project.
|
||||
4. Select the _Console_ application.
|
||||
5. Select _Redirect Settings_
|
||||
6. Add _http://<span because="breaks the link"></span>localhost:4200/auth/callback_ to the _Redirect URIs_
|
||||
7. Add _http://<span because="breaks the link"></span>localhost:4200/signedout_ to the _Post Logout URIs_
|
||||
8. Select the _Save_ button
|
||||
|
||||
You can run the local console development server now.
|
||||
#### Develop
|
||||
|
||||
Run the local console development server.
|
||||
|
||||
```bash
|
||||
# Install npm dependencies
|
||||
yarn install
|
||||
# Install dependencies (from repository root)
|
||||
pnpm install
|
||||
|
||||
# Generate source files from Protos
|
||||
yarn generate
|
||||
# Option 1: Run console development server with live reloading and dependency rebuilds
|
||||
pnpm turbo dev --filter=console
|
||||
|
||||
# Start the server
|
||||
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 yarn start
|
||||
# Option 2: Build and serve console (production build)
|
||||
pnpm turbo build --filter=console
|
||||
pnpm turbo serve --filter=console
|
||||
```
|
||||
|
||||
Navigate to http://localhost:4200/.
|
||||
Make some changes to the source code and see how the browser is automatically updated.
|
||||
After making changes to the code, you should run the end-to-end-tests.
|
||||
Open another shell.
|
||||
|
||||
#### Pass Quality Checks
|
||||
|
||||
Reproduce the pipelines linting and testing for the console.
|
||||
|
||||
```bash
|
||||
# Reformat your console code
|
||||
yarn lint:fix
|
||||
|
||||
# Change to the e2e directory
|
||||
cd .. && cd e2e/
|
||||
|
||||
# If you made changes in the e2e directory, make sure you reformat the files here too
|
||||
npm run lint:fix
|
||||
|
||||
# Install npm dependencies
|
||||
npm install
|
||||
|
||||
# Run all e2e tests
|
||||
npm run e2e:angular -- --headed
|
||||
pnpm turbo quality --filter=console --filter=e2e
|
||||
```
|
||||
|
||||
You can also open the test suite interactively for fast feedback on specific tests.
|
||||
Fix the [quality checks](troubleshoot-frontend), add new checks that cover your changes and mark your pull request as ready for review when the pipeline checks pass.
|
||||
|
||||
### <a name="contribute-docs"></a>Contribute to Docs
|
||||
|
||||
Project documentation is made with Docusaurus and is located under [./docs](./docs). The documentation uses **pnpm** and **Turbo** for development and build processes.
|
||||
|
||||
#### Local Development
|
||||
|
||||
```bash
|
||||
# Run tests interactively
|
||||
npm run open:angular
|
||||
# Install dependencies (from repository root)
|
||||
pnpm install
|
||||
|
||||
# Option 1: Run docs development server with Turbo (recommended)
|
||||
pnpm turbo dev --filter=zitadel-docs
|
||||
|
||||
# Option 2: Build and serve docs (production build)
|
||||
pnpm turbo build --filter=zitadel-docs
|
||||
cd ./docs && pnpm serve
|
||||
```
|
||||
|
||||
If you also make [changes to the backend code](#backend--login), you can run the test against your locally built backend code and frontend server
|
||||
The docs build process automatically:
|
||||
|
||||
```bash
|
||||
npm run open:golangangular
|
||||
npm run e2e:golangangular
|
||||
```
|
||||
1. Downloads required protoc plugins
|
||||
2. Generates gRPC documentation from proto files
|
||||
3. Generates API documentation from OpenAPI specs
|
||||
4. Copies configuration files
|
||||
5. Builds the Docusaurus site
|
||||
|
||||
When you are happy with your changes, you can format your code and cleanup your environment
|
||||
#### Local testing
|
||||
|
||||
```bash
|
||||
# Stop and remove the docker containers for zitadel and the database
|
||||
docker compose down
|
||||
```
|
||||
The documentation server will be available at http://localhost:3000 with live reload for fast development feedback.
|
||||
|
||||
## Contribute docs
|
||||
|
||||
Project documentation is made with docusaurus and is located under [./docs](./docs).
|
||||
|
||||
### Local testing
|
||||
|
||||
Please refer to the [README](./docs/README.md) for more information and local testing.
|
||||
|
||||
### Style guide
|
||||
#### 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).
|
||||
@@ -431,14 +543,54 @@ The style guide covers a lot of material, so their [highlights](https://develope
|
||||
- Use active voice: make clear who's performing the action.
|
||||
- Use descriptive link text.
|
||||
|
||||
### Docs pull request
|
||||
#### Docs pull request
|
||||
|
||||
When making a pull request use `docs(<scope>): <short summary>` as title for the semantic release.
|
||||
Scope can be left empty (omit the brackets) or refer to the top navigation sections.
|
||||
|
||||
## Contribute internationalization
|
||||
#### Pass Quality Checks
|
||||
|
||||
ZITADEL loads translations from four files:
|
||||
Reproduce the pipelines linting checks for the docs.
|
||||
|
||||
```bash
|
||||
pnpm turbo quality --filter=docs
|
||||
```
|
||||
|
||||
Fix the [quality checks](troubleshoot-frontend), add new checks that cover your changes and mark your pull request as ready for review when the pipeline checks pass.
|
||||
|
||||
### <a name="troubleshoot-frontend"></a>Troubleshoot Frontend Quality Checks
|
||||
|
||||
To debug and fix failing tasks, execute them individually using the `--filter` flag.
|
||||
|
||||
We recommend to use [one of the dev containers](dev-containers) to reproduce pipeline issues.
|
||||
|
||||
```bash
|
||||
# to reproduce linting error in the console:
|
||||
pnpm lint --filter=console
|
||||
# To fix them:
|
||||
pnpm lint:fix --filter=console
|
||||
```
|
||||
|
||||
More tasks that are runnable on-demand.
|
||||
Some tasks have variants like `pnpm test:e2e:angulargolang`,
|
||||
others support arguments and flags like `pnpm test:integration run --spec apps/login/integration/integration/login.cy.ts`.
|
||||
For the turbo commands, check your options with `pnpm turbo --help`
|
||||
|
||||
| Command | Description | Example |
|
||||
| ------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `pnpm turbo run generate` | Generate stubs from Proto files | Generate API docs: `pnpm turbo run generate --filter zitadel-docs` |
|
||||
| `pnpm turbo build` | Build runnable JavaScript code | Regenerate the proto stubs and build the @zitadel/client package: `pnpm turbo build --filter @zitadel/client` |
|
||||
| `pnpm turbo quality` | Reproduce the pipeline quality checks | Run login-related quality checks `pnpm turbo quality --filter './apps/login/*' --filter './packages/*'` |
|
||||
| `pnpm turbo lint` | Check linting issues | Check login-related linting issues for differences with main `pnpm turbo lint --filter=[main...HEAD] --filter .'/apps/login/**/*' --filter './packages/*'` |
|
||||
| `pnpm turbo lint:fix` | Fix linting issues | Fix console-relevant linting issues `pnpm turbo lint:fix --filter console --filter './packages/*' --filter zitadel-e2e` |
|
||||
| `pnpm turbo test:unit` | Run unit tests. Rerun on file changes | Run unit tests in all packages in and watch for file changes `pnpm turbo watch test:unit` |
|
||||
| `pnpm turbo test:e2e` | Run the Cypress CLI for console e2e tests | Test interactively against the console in a local dev server and Zitadel in a container: `pnpm turbo test:e2e:angular open` |
|
||||
| `pnpm turbo down` | Remove containers and volumes | Shut down containers from the integration test setup `pnpm turbo down` |
|
||||
| `pnpm turbo clean` | Remove downloaded dependencies and other generated files | Remove generated docs `pnpm turbo clean --filter zitadel-docs` |
|
||||
|
||||
## <a name="contribute-translations"></a>Contribute Translations
|
||||
|
||||
Zitadel loads translations from four files:
|
||||
|
||||
- [Console texts](./console/src/assets/i18n)
|
||||
- [Login interface](./internal/api/ui/login/static/i18n)
|
||||
@@ -449,13 +601,14 @@ You may edit the texts in these files or create a new file for additional langua
|
||||
Please make sure that the languages within the files remain in their own language, e.g. German must always be `Deutsch.
|
||||
If you have added support for a new language, please also ensure that it is added in the list of languages in all the other language files.
|
||||
|
||||
You also have to add some changes to the following files:
|
||||
You also have to add some changes to the following files:
|
||||
|
||||
- [Register Local File](./console/src/app/app.module.ts)
|
||||
- [Add Supported Language](./console/src/app/utils/language.ts)
|
||||
- [Customized Text Docs](./docs/docs/guides/manage/customize/texts.md)
|
||||
- [Add language option](./internal/api/ui/login/static/templates/external_not_found_option.html)
|
||||
|
||||
## Want to start ZITADEL?
|
||||
## Want to start Zitadel?
|
||||
|
||||
You can find an installation guide for all the different environments here:
|
||||
[https://zitadel.com/docs/self-hosting/deploy/overview](https://zitadel.com/docs/self-hosting/deploy/overview)
|
||||
@@ -466,14 +619,14 @@ You can find an installation guide for all the different environments here:
|
||||
|
||||
## Product management
|
||||
|
||||
The ZITADEL Team works with an agile product management methodology.
|
||||
The Zitadel Team works with an agile product management methodology.
|
||||
You can find all the issues prioritized and ordered in the [product board](https://github.com/orgs/zitadel/projects/2/views/1).
|
||||
|
||||
### Sprint
|
||||
|
||||
We want to deliver a new release every second week. So we plan everything in two-week sprints.
|
||||
Each Tuesday we estimate new issues and on Wednesday the last sprint will be reviewed and the next one will be planned.
|
||||
After a sprint ends a new version of ZITADEL will be released, and publish to [ZITADEL Cloud](https://zitadel.cloud) the following Monday.
|
||||
After a sprint ends a new version of Zitadel will be released, and publish to [Zitadel Cloud](https://zitadel.cloud) the following Monday.
|
||||
|
||||
If there are some critical or urgent issues we will have a look at it earlier, than the two weeks.
|
||||
To show the community the needed information, each issue gets attributes and labels.
|
||||
@@ -493,15 +646,16 @@ The state should reflect the progress of the issue and what is going on right no
|
||||
- **🔖 Ready**: The issue is ready to take into a sprint. Difference to "prioritized..." is that the complexity is defined by the team.
|
||||
- **📋 Sprint backlog**: The issue is scheduled for the current sprint.
|
||||
- **🏗 In progress**: Someone is working on this issue right now. The issue will get an assignee as soon as it is in progress.
|
||||
- **❌ Blocked**: The issue is blocked until another issue is resolved/done.
|
||||
- **👀 In review**: The issue is in review. Please add someone to review your issue or let us know that it is ready to review with a comment on your pull request.
|
||||
- **✅ Done**: The issue is implemented and merged to main.
|
||||
|
||||
#### Priority
|
||||
|
||||
Priority shows you the priority the ZITADEL team has given this issue. In general the higher the demand from customers and community for the feature, the higher the priority.
|
||||
Priority shows you the priority the Zitadel team has given this issue. In general the higher the demand from customers and community for the feature, the higher the priority.
|
||||
|
||||
- **🌋 Critical**: This is a security issue or something that has to be fixed urgently, because the software is not usable or highly vulnerable.
|
||||
- **🏔 High**: These are the issues the ZITADEL team is currently focusing on and will be implemented as soon as possible.
|
||||
- **🏔 High**: These are the issues the Zitadel team is currently focusing on and will be implemented as soon as possible.
|
||||
- **🏕 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.
|
||||
|
||||
@@ -516,18 +670,18 @@ Everything that is higher than 8 should be split in smaller parts.
|
||||
|
||||
There are a few general labels that don't belong to a specific category.
|
||||
|
||||
- **good first issue**: This label shows contributors, that it is an easy entry point to start developing on ZITADEL.
|
||||
- **help wanted**: The author is seeking help on this topic, this may be from an internal ZITADEL team member or external contributors.
|
||||
- **good first issue**: This label shows contributors, that it is an easy entry point to start developing on Zitadel.
|
||||
- **help wanted**: The author is seeking help on this topic, this may be from an internal Zitadel team member or external contributors.
|
||||
|
||||
#### Category
|
||||
|
||||
The category shows which part of ZITADEL is affected.
|
||||
The category shows which part of Zitadel is affected.
|
||||
|
||||
- **category: backend**: The backend includes the APIs, event store, command and query side. This is developed in golang.
|
||||
- **category: ci**: ci is all about continues integration and pipelines.
|
||||
- **category: design**: All about the ux/ui of ZITADEL
|
||||
- **category: design**: All about the ux/ui of Zitadel
|
||||
- **category: docs**: Adjustments or new documentations, this can be found in the docs folder.
|
||||
- **category: frontend**: The frontend concerns on the one hand the ZITADEL management console (Angular) and on the other hand the login (gohtml)
|
||||
- **category: frontend**: The frontend concerns on the one hand the Zitadel management console (Angular) and on the other hand the login (gohtml)
|
||||
- **category: infra**: Infrastructure does include many different parts. E.g Terraform-provider, docker, metrics, etc.
|
||||
- **category: translation**: Everything concerning translations or new languages
|
||||
|
||||
|
@@ -18,6 +18,14 @@ The following files and directories, including their subdirectories, are license
|
||||
proto/
|
||||
```
|
||||
|
||||
|
||||
The following files and directories, including their subdirectories, are licensed under the [MIT License](https://opensource.org/license/mit/):
|
||||
|
||||
```
|
||||
login/
|
||||
clients/
|
||||
```
|
||||
|
||||
## Community Contributions
|
||||
|
||||
To maintain a clear licensing structure and facilitate community contributions, all contributions must be licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) to be accepted. By submitting a contribution, you agree to this licensing.
|
||||
|
61
Makefile
61
Makefile
@@ -12,12 +12,22 @@ ZITADEL_MASTERKEY ?= MasterkeyNeedsToHave32Characters
|
||||
|
||||
export GOCOVERDIR ZITADEL_MASTERKEY
|
||||
|
||||
LOGIN_REMOTE_NAME := login
|
||||
LOGIN_REMOTE_URL ?= https://github.com/zitadel/typescript.git
|
||||
LOGIN_REMOTE_BRANCH ?= main
|
||||
|
||||
.PHONY: compile
|
||||
compile: core_build console_build compile_pipeline
|
||||
|
||||
.PHONY: docker_image
|
||||
docker_image: compile
|
||||
DOCKER_BUILDKIT=1 docker build -f build/Dockerfile -t $(ZITADEL_IMAGE) .
|
||||
docker_image:
|
||||
@if [ ! -f ./zitadel ]; then \
|
||||
echo "Compiling zitadel binary"; \
|
||||
$(MAKE) compile; \
|
||||
else \
|
||||
echo "Reusing precompiled zitadel binary"; \
|
||||
fi
|
||||
DOCKER_BUILDKIT=1 docker build -f build/zitadel/Dockerfile -t $(ZITADEL_IMAGE) .
|
||||
|
||||
.PHONY: compile_pipeline
|
||||
compile_pipeline: console_move
|
||||
@@ -68,12 +78,13 @@ core_grpc_dependencies:
|
||||
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@v2.22.0 # https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2?tab=versions
|
||||
go install github.com/envoyproxy/protoc-gen-validate@v1.1.0 # https://pkg.go.dev/github.com/envoyproxy/protoc-gen-validate?tab=versions
|
||||
go install github.com/bufbuild/buf/cmd/buf@v1.45.0 # https://pkg.go.dev/github.com/bufbuild/buf/cmd/buf?tab=versions
|
||||
go install connectrpc.com/connect/cmd/protoc-gen-connect-go@v1.18.1 # https://pkg.go.dev/connectrpc.com/connect/cmd/protoc-gen-connect-go?tab=versions
|
||||
|
||||
.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/
|
||||
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
|
||||
|
||||
@@ -86,18 +97,11 @@ console_move:
|
||||
|
||||
.PHONY: console_dependencies
|
||||
console_dependencies:
|
||||
cd console && \
|
||||
yarn install --immutable
|
||||
|
||||
.PHONY: console_client
|
||||
console_client:
|
||||
cd console && \
|
||||
yarn generate
|
||||
npx pnpm install --frozen-lockfile --filter=./console
|
||||
|
||||
.PHONY: console_build
|
||||
console_build: console_dependencies console_client
|
||||
cd console && \
|
||||
yarn build
|
||||
console_build: console_dependencies
|
||||
npx pnpm turbo build --filter=./console
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@@ -112,7 +116,7 @@ core_unit_test:
|
||||
|
||||
.PHONY: core_integration_db_up
|
||||
core_integration_db_up:
|
||||
docker compose -f internal/integration/config/docker-compose.yaml up --pull always --wait cache
|
||||
docker compose -f internal/integration/config/docker-compose.yaml up --pull always --wait cache postgres
|
||||
|
||||
.PHONY: core_integration_db_down
|
||||
core_integration_db_down:
|
||||
@@ -155,8 +159,7 @@ core_integration_test: core_integration_server_start core_integration_test_packa
|
||||
|
||||
.PHONY: console_lint
|
||||
console_lint:
|
||||
cd console && \
|
||||
yarn lint
|
||||
npx pnpm turbo lint --filter=./console
|
||||
|
||||
.PHONY: core_lint
|
||||
core_lint:
|
||||
@@ -165,3 +168,29 @@ core_lint:
|
||||
--config ./.golangci.yaml \
|
||||
--out-format=github-actions \
|
||||
--concurrency=$$(getconf _NPROCESSORS_ONLN)
|
||||
|
||||
.PHONY: login_pull
|
||||
login_pull: login_ensure_remote
|
||||
@echo "Pulling changes from the 'apps/login' subtree on remote $(LOGIN_REMOTE_NAME) branch $(LOGIN_REMOTE_BRANCH)"
|
||||
git fetch $(LOGIN_REMOTE_NAME) $(LOGIN_REMOTE_BRANCH)
|
||||
git merge -s ours --allow-unrelated-histories $(LOGIN_REMOTE_NAME)/$(LOGIN_REMOTE_BRANCH) -m "Synthetic merge to align histories"
|
||||
git push
|
||||
|
||||
.PHONY: login_push
|
||||
login_push: login_ensure_remote
|
||||
@echo "Pushing changes to the 'apps/login' subtree on remote $(LOGIN_REMOTE_NAME) branch $(LOGIN_REMOTE_BRANCH)"
|
||||
git subtree split --prefix=apps/login -b login-sync-tmp
|
||||
git checkout login-sync-tmp
|
||||
git fetch $(LOGIN_REMOTE_NAME) main
|
||||
git merge -s ours --allow-unrelated-histories $(LOGIN_REMOTE_NAME)/main -m "Synthetic merge to align histories"
|
||||
git push $(LOGIN_REMOTE_NAME) login-sync-tmp:$(LOGIN_REMOTE_BRANCH)
|
||||
git checkout -
|
||||
git branch -D login-sync-tmp
|
||||
|
||||
login_ensure_remote:
|
||||
@if ! git remote get-url $(LOGIN_REMOTE_NAME) > /dev/null 2>&1; then \
|
||||
echo "Adding remote $(LOGIN_REMOTE_NAME)"; \
|
||||
git remote add $(LOGIN_REMOTE_NAME) $(LOGIN_REMOTE_URL); \
|
||||
else \
|
||||
echo "Remote $(LOGIN_REMOTE_NAME) already exists."; \
|
||||
fi
|
||||
|
@@ -193,7 +193,7 @@ Use [Console](https://zitadel.com/docs/guides/manage/console/overview) or our [A
|
||||
### Login V2
|
||||
|
||||
Check out our new Login V2 version in our [typescript repository](https://github.com/zitadel/typescript) or in our [documentation](https://zitadel.com/docs/guides/integrate/login/hosted-login#hosted-login-version-2-beta)
|
||||
[]
|
||||

|
||||
|
||||
## Security
|
||||
|
||||
|
21
apps/login/.dockerignore
Normal file
21
apps/login/.dockerignore
Normal file
@@ -0,0 +1,21 @@
|
||||
*
|
||||
|
||||
!constants
|
||||
!scripts
|
||||
!src
|
||||
!public
|
||||
!locales
|
||||
!next.config.mjs
|
||||
!next-env-vars.d.ts
|
||||
!next-env.d.ts
|
||||
!tailwind.config.js
|
||||
!tsconfig.json
|
||||
!package.json
|
||||
!pnpm-lock.yaml
|
||||
|
||||
**/*.md
|
||||
**/*.png
|
||||
**/node_modules
|
||||
**/.turbo
|
||||
**/*.test.ts
|
||||
**/*.test.tsx
|
5
apps/login/.env.test
Normal file
5
apps/login/.env.test
Normal file
@@ -0,0 +1,5 @@
|
||||
NEXT_PUBLIC_BASE_PATH="/ui/v2/login"
|
||||
ZITADEL_API_URL=http://mock-zitadel:22222
|
||||
ZITADEL_SERVICE_USER_TOKEN="yolo"
|
||||
EMAIL_VERIFICATION=true
|
||||
DEBUG=true
|
24
apps/login/.eslintrc.cjs
Normal file
24
apps/login/.eslintrc.cjs
Normal file
@@ -0,0 +1,24 @@
|
||||
module.exports = {
|
||||
parser: "@typescript-eslint/parser",
|
||||
extends: ["next", "prettier"],
|
||||
plugins: ["@typescript-eslint"],
|
||||
rules: {
|
||||
"@next/next/no-html-link-for-pages": "off",
|
||||
"@next/next/no-img-element": "off",
|
||||
"react/no-unescaped-entities": "off",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", {
|
||||
argsIgnorePattern: "^_" ,
|
||||
varsIgnorePattern: "^_" ,
|
||||
}],
|
||||
"no-undef": "off",
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
};
|
63
apps/login/.github/ISSUE_TEMPLATE/bug.yaml
vendored
Normal file
63
apps/login/.github/ISSUE_TEMPLATE/bug.yaml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
name: 🐛 Bug Report
|
||||
description: "Create a bug report to help us improve ZITADEL Typescript Library."
|
||||
title: "[Bug]: "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
- type: checkboxes
|
||||
id: preflight
|
||||
attributes:
|
||||
label: Preflight Checklist
|
||||
options:
|
||||
- label:
|
||||
I could not find a solution in the documentation, the existing issues or discussions
|
||||
required: true
|
||||
- label:
|
||||
I have joined the [ZITADEL chat](https://zitadel.com/chat)
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: Which version of ZITADEL Typescript Library are you using.
|
||||
- type: textarea
|
||||
id: impact
|
||||
attributes:
|
||||
label: Describe the problem caused by this bug
|
||||
description: A clear and concise description of the problem you have and what the bug is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: To reproduce
|
||||
description: Steps to reproduce the behaviour
|
||||
placeholder: |
|
||||
Steps to reproduce the behavior:
|
||||
1. ...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If applicable, add screenshots to help explain your problem.
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
- type: textarea
|
||||
id: config
|
||||
attributes:
|
||||
label: Relevant Configuration
|
||||
description: Add any relevant configurations that could help us. Make sure to redact any sensitive information.
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Please add any other infos that could be useful.
|
4
apps/login/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
4
apps/login/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: 💬 ZITADEL Community Chat
|
||||
url: https://zitadel.com/chat
|
30
apps/login/.github/ISSUE_TEMPLATE/docs.yaml
vendored
Normal file
30
apps/login/.github/ISSUE_TEMPLATE/docs.yaml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: 📄 Documentation
|
||||
description: Create an issue for missing or wrong documentation.
|
||||
labels: ["docs"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this issue.
|
||||
- type: checkboxes
|
||||
id: preflight
|
||||
attributes:
|
||||
label: Preflight Checklist
|
||||
options:
|
||||
- label:
|
||||
I could not find a solution in the existing issues, docs, nor discussions
|
||||
required: true
|
||||
- label:
|
||||
I have joined the [ZITADEL chat](https://zitadel.com/chat)
|
||||
- type: textarea
|
||||
id: docs
|
||||
attributes:
|
||||
label: Describe the docs your are missing or that are wrong
|
||||
placeholder: As a [type of user], I want [some goal] so that [some reason].
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Please add any other infos that could be useful.
|
54
apps/login/.github/ISSUE_TEMPLATE/improvement.yaml
vendored
Normal file
54
apps/login/.github/ISSUE_TEMPLATE/improvement.yaml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: 🛠️ Improvement
|
||||
description: "Create an new issue for an improvment in ZITADEL"
|
||||
labels: ["improvement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this improvement request
|
||||
- type: checkboxes
|
||||
id: preflight
|
||||
attributes:
|
||||
label: Preflight Checklist
|
||||
options:
|
||||
- label:
|
||||
I could not find a solution in the existing issues, docs, nor discussions
|
||||
required: true
|
||||
- label:
|
||||
I have joined the [ZITADEL chat](https://zitadel.com/chat)
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Describe your problem
|
||||
description: Please describe your problem this improvement is supposed to solve.
|
||||
placeholder: Describe the problem you have
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: Describe your ideal solution
|
||||
description: Which solution do you propose?
|
||||
placeholder: As a [type of user], I want [some goal] so that [some reason].
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: Which version of the typescript library are you using.
|
||||
- type: dropdown
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: How do you use ZITADEL?
|
||||
options:
|
||||
- ZITADEL Cloud
|
||||
- Self-hosted
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Please add any other infos that could be useful.
|
54
apps/login/.github/ISSUE_TEMPLATE/proposal.yaml
vendored
Normal file
54
apps/login/.github/ISSUE_TEMPLATE/proposal.yaml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: 💡 Proposal / Feature request
|
||||
description: "Create an issue for a feature request/proposal."
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this proposal / feature reqeust
|
||||
- type: checkboxes
|
||||
id: preflight
|
||||
attributes:
|
||||
label: Preflight Checklist
|
||||
options:
|
||||
- label:
|
||||
I could not find a solution in the existing issues, docs, nor discussions
|
||||
required: true
|
||||
- label:
|
||||
I have joined the [ZITADEL chat](https://zitadel.com/chat)
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Describe your problem
|
||||
description: Please describe your problem this proposal / feature is supposed to solve.
|
||||
placeholder: Describe the problem you have.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: Describe your ideal solution
|
||||
description: Which solution do you propose?
|
||||
placeholder: As a [type of user], I want [some goal] so that [some reason].
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: Which version of the Typescript Library are you using.
|
||||
- type: dropdown
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: How do you use ZITADEL?
|
||||
options:
|
||||
- ZITADEL Cloud
|
||||
- Self-hosted
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Please add any other infos that could be useful.
|
BIN
apps/login/.github/custom-i18n.png
vendored
Normal file
BIN
apps/login/.github/custom-i18n.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 83 KiB |
21
apps/login/.github/dependabot.example.yml
vendored
Normal file
21
apps/login/.github/dependabot.example.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: '/'
|
||||
open-pull-requests-limit: 1
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
|
||||
- package-ecosystem: npm
|
||||
directory: '/'
|
||||
open-pull-requests-limit: 3
|
||||
schedule:
|
||||
interval: daily
|
||||
groups:
|
||||
prod:
|
||||
dependency-type: production
|
||||
dev:
|
||||
dependency-type: development
|
||||
ignore:
|
||||
- dependency-name: "eslint"
|
||||
versions: [ "9.x" ]
|
13
apps/login/.github/pull_request_template.md
vendored
Normal file
13
apps/login/.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
### Definition of Ready
|
||||
|
||||
- [ ] I am happy with the code
|
||||
- [ ] Short description of the feature/issue is added in the pr description
|
||||
- [ ] PR is linked to the corresponding user story
|
||||
- [ ] Acceptance criteria are met
|
||||
- [ ] All open todos and follow ups are defined in a new ticket and justified
|
||||
- [ ] Deviations from the acceptance criteria and design are agreed with the PO and documented.
|
||||
- [ ] Vitest unit tests ensure that components produce expected outputs on different inputs.
|
||||
- [ ] Cypress integration tests ensure that login app pages work as expected on good and bad user inputs, ZITADEL responses or IDP redirects. The ZITADEL API is mocked, IDP redirects are simulated.
|
||||
- [ ] Playwright acceptances tests ensure that the happy paths of common user journeys work as expected. The ZITADEL API is not mocked but IDP redirects are simulated.
|
||||
- [ ] No debug or dead code
|
||||
- [ ] My code has no repetitions
|
39
apps/login/.github/workflows/close_pr.yml
vendored
Normal file
39
apps/login/.github/workflows/close_pr.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Auto-close PRs and guide to correct repo
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
auto-close:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_id == '622995060' && github.ref_name != 'mirror-zitadel-repo'
|
||||
steps:
|
||||
- name: Comment and close PR
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const message = `
|
||||
👋 **Thanks for your contribution @${{ github.event.pull_request.user.login }}!**
|
||||
|
||||
This repository \`${{ github.repository }}\` is a read-only mirror of the git subtree at [\`zitadel/zitadel/login\`](https://github.com/zitadel/zitadel).
|
||||
Therefore, we close this pull request automatically.
|
||||
|
||||
Your changes are not lost. Submitting them to the main repository is easy:
|
||||
1. [Fork zitadel/zitadel](https://github.com/zitadel/zitadel/fork)
|
||||
2. Clone your Zitadel fork \`git clone https://github.com/<your-owner>/zitadel.git\`
|
||||
3. Change directory to your Zitadel forks root.
|
||||
4. Pull your changes into the Zitadel fork by running \`make login_pull LOGIN_REMOTE_URL=https://github.com/<your-owner>/typescript.git LOGIN_REMOTE_BRANCH=<your-typescript-fork-branch>\`.
|
||||
5. Push your changes and [open a pull request to zitadel/zitadel](https://github.com/zitadel/zitadel/compare)
|
||||
`.trim();
|
||||
await github.rest.issues.createComment({
|
||||
...context.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: message
|
||||
});
|
||||
await github.rest.pulls.update({
|
||||
...context.repo,
|
||||
pull_number: context.issue.number,
|
||||
state: "closed"
|
||||
});
|
41
apps/login/.github/workflows/issues.yml
vendored
Normal file
41
apps/login/.github/workflows/issues.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Add new issues to product management project
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
|
||||
jobs:
|
||||
add-to-project:
|
||||
name: Add issue and community pr to project
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_id == '622995060'
|
||||
steps:
|
||||
- name: add issue
|
||||
uses: actions/add-to-project@v1.0.2
|
||||
if: ${{ github.event_name == 'issues' }}
|
||||
with:
|
||||
# You can target a repository in a different organization
|
||||
# to the issue
|
||||
project-url: https://github.com/orgs/zitadel/projects/2
|
||||
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
|
||||
- uses: tspascoal/get-user-teams-membership@v3
|
||||
id: checkUserMember
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
with:
|
||||
username: ${{ github.actor }}
|
||||
GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_PAT }}
|
||||
- name: add pr
|
||||
uses: actions/add-to-project@v1.0.2
|
||||
if: ${{ github.event_name == 'pull_request_target' && github.actor != 'dependabot[bot]' && !contains(steps.checkUserMember.outputs.teams, 'engineers')}}
|
||||
with:
|
||||
# You can target a repository in a different organization
|
||||
# to the issue
|
||||
project-url: https://github.com/orgs/zitadel/projects/2
|
||||
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
|
||||
- uses: actions-ecosystem/action-add-labels@v1.1.3
|
||||
if: ${{ github.event_name == 'pull_request_target' && github.actor != 'dependabot[bot]' && !contains(steps.checkUserMember.outputs.teams, 'staff')}}
|
||||
with:
|
||||
github_token: ${{ secrets.ADD_TO_PROJECT_PAT }}
|
||||
labels: |
|
||||
os-contribution
|
32
apps/login/.github/workflows/release.yml
vendored
Normal file
32
apps/login/.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_id != '622995060'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Create Release Pull Request
|
||||
uses: changesets/action@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
67
apps/login/.github/workflows/test.yml
vendored
Normal file
67
apps/login/.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
name: Quality
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ignore-run-cache:
|
||||
description: 'Whether to ignore the run cache'
|
||||
required: false
|
||||
default: true
|
||||
ref-tag:
|
||||
description: 'overwrite the DOCKER_METADATA_OUTPUT_VERSION environment variable used by the make file'
|
||||
required: false
|
||||
default: ''
|
||||
jobs:
|
||||
quality:
|
||||
name: Ensure Quality
|
||||
if: github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'pull_request' && github.repository_id != '622995060')
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read # We only need read access to the repository contents
|
||||
actions: write # We need write access to the actions cache
|
||||
env:
|
||||
CACHE_DIR: /tmp/login-run-caches
|
||||
# Only run this job on workflow_dispatch or pushes to forks
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/zitadel/login
|
||||
tags: |
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
- name: Set up Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
# Only with correctly restored build cache layers, the run caches work as expected.
|
||||
# To restore docker build layer caches, extend the docker-bake.hcl to use the cache-from and cache-to options.
|
||||
# https://docs.docker.com/build/ci/github-actions/cache/
|
||||
# Alternatively, you can use a self-hosted runner or a third-party builder that restores build layer caches out-of-the-box, like https://depot.dev/
|
||||
- name: Restore Run Caches
|
||||
uses: actions/cache/restore@v4
|
||||
id: run-caches-restore
|
||||
with:
|
||||
path: ${{ env.CACHE_DIR }}
|
||||
key: ${{ runner.os }}-login-run-caches-${{github.ref_name}}-${{ github.sha }}-${{github.run_attempt}}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-login-run-caches-${{github.ref_name}}-${{ github.sha }}-
|
||||
${{ runner.os }}-login-run-caches-${{github.ref_name}}-
|
||||
${{ runner.os }}-login-run-caches-
|
||||
- run: make login_quality
|
||||
env:
|
||||
IGNORE_RUN_CACHE: ${{ github.event.inputs.ignore-run-cache == 'true' }}
|
||||
DOCKER_METADATA_OUTPUT_VERSION: ${{ github.event.inputs.ref-tag || env.DOCKER_METADATA_OUTPUT_VERSION || steps.meta.outputs.version }}
|
||||
- name: Save Run Caches
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: ${{ env.CACHE_DIR }}
|
||||
key: ${{ steps.run-caches-restore.outputs.cache-primary-key }}
|
||||
if: always()
|
16
apps/login/.gitignore
vendored
Normal file
16
apps/login/.gitignore
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
custom-config.js
|
||||
.env*.local
|
||||
standalone
|
||||
tsconfig.tsbuildinfo
|
||||
|
||||
.DS_Store
|
||||
node_modules
|
||||
.turbo
|
||||
*.log
|
||||
.next
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.env
|
||||
.vscode
|
||||
/blob-report/
|
5
apps/login/.prettierignore
Normal file
5
apps/login/.prettierignore
Normal file
@@ -0,0 +1,5 @@
|
||||
*
|
||||
!constants
|
||||
!src
|
||||
!locales
|
||||
!scripts/healthcheck.js
|
6
apps/login/.prettierrc
Normal file
6
apps/login/.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"printWidth": 125,
|
||||
"trailingComma": "all",
|
||||
"plugins": ["prettier-plugin-organize-imports"],
|
||||
"filepath": ""
|
||||
}
|
128
apps/login/CODE_OF_CONDUCT.md
Normal file
128
apps/login/CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
legal@zitadel.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
218
apps/login/CONTRIBUTING.md
Normal file
218
apps/login/CONTRIBUTING.md
Normal file
@@ -0,0 +1,218 @@
|
||||
# Contributing
|
||||
|
||||
:attention: In this CONTRIBUTING.md you read about contributing to this very repository.
|
||||
If you want to develop your own login UI, please refer [to the README.md](./README.md).
|
||||
|
||||
## Introduction
|
||||
|
||||
Thank you for your interest about how to contribute!
|
||||
|
||||
:attention: If you notice a possible **security vulnerability**, please don't hesitate to disclose any concern by contacting [security@zitadel.com](mailto:security@zitadel.com).
|
||||
You don't have to be perfectly sure about the nature of the vulnerability.
|
||||
We will give them a high priority and figure them out.
|
||||
|
||||
We also appreciate all your other ideas, thoughts and feedback and will take care of them as soon as possible.
|
||||
We love to discuss in an open space using [GitHub issues](https://github.com/zitadel/typescript/issues),
|
||||
[GitHub discussions in the core repo](https://github.com/zitadel/zitadel/discussions)
|
||||
or in our [chat on Discord](https://zitadel.com/chat).
|
||||
For private discussions,
|
||||
you have [more contact options on our Website](https://zitadel.com/contact).
|
||||
|
||||
## Pull Requests
|
||||
|
||||
The repository zitadel/typescript is a read-only mirror of the git subtree at zitadel/zitadel/login.
|
||||
To submit changes, please open a Pull Request [in the zitadel/zitadel repository](https://github.com/zitadel/zitadel/compare).
|
||||
|
||||
If you already made changes based on the zitadel/typescript repository, these changes are not lost.
|
||||
Submitting them to the main repository is easy:
|
||||
|
||||
1. [Fork zitadel/zitadel](https://github.com/zitadel/zitadel/fork)
|
||||
1. Clone your Zitadel fork git clone https://github.com/<your-owner>/zitadel.git
|
||||
1. Change directory to your Zitadel forks root.
|
||||
1. Pull your changes into the Zitadel fork by running make login_pull LOGIN_REMOTE_URL=https://github.com/<your-owner>/typescript.git LOGIN_REMOTE_BRANCH=<your-typescript-fork-branch>.
|
||||
1. Push your changes and [open a pull request to zitadel/zitadel](https://github.com/zitadel/zitadel/compare)
|
||||
|
||||
Please consider the following guidelines when creating a pull request.
|
||||
|
||||
- The latest changes are always in `main`, so please make your pull request against that branch.
|
||||
- pull requests should be raised for any change
|
||||
- Pull requests need approval of a Zitadel core engineer @zitadel/engineers before merging
|
||||
- We use ESLint/Prettier for linting/formatting, so please run `pnpm lint:fix` before committing to make resolving conflicts easier (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)
|
||||
- If you add new functionality, please provide the corresponding documentation as well and make it part of the pull request
|
||||
|
||||
### Setting up local environment
|
||||
|
||||
```sh
|
||||
# Install dependencies. Developing requires Node.js v20
|
||||
pnpm install
|
||||
|
||||
# Generate gRPC stubs
|
||||
pnpm generate
|
||||
|
||||
# Start a local development server for the login and manually configure apps/login/.env.local
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
The application is now available at `http://localhost:3000`
|
||||
|
||||
Configure apps/login/.env.local to target the Zitadel instance of your choice.
|
||||
The login app live-reloads on changes, so you can start developing right away.
|
||||
|
||||
### <a name="latest"></a>Developing Against A Local Latest Zitadel Release
|
||||
|
||||
The following command uses Docker to run a local Zitadel instance and the login application in live-reloading dev mode.
|
||||
Additionally, it runs a Traefik reverse proxy that exposes the login with a self-signed certificate at https://127.0.0.1.sslip.io
|
||||
127.0.0.1.sslip.io is a special domain that resolves to your localhost, so it's safe to allow your browser to proceed with loading the page.
|
||||
|
||||
```sh
|
||||
# Install dependencies. Developing requires Node.js v20
|
||||
pnpm install
|
||||
|
||||
# Generate gRPC stubs
|
||||
pnpm generate
|
||||
|
||||
# Start a local development server and have apps/login/.env.test.local configured for you to target the local Zitadel instance.
|
||||
pnpm dev:local
|
||||
```
|
||||
|
||||
Log in at https://127.0.0.1.sslip.io/ui/v2/login/loginname and use the following credentials:
|
||||
**Loginname**: *zitadel-admin@zitadel.127.0.0.1.sslip.io*
|
||||
**Password**: _Password1!_.
|
||||
|
||||
The login app live-reloads on changes, so you can start developing right away.
|
||||
|
||||
### <a name="local"></a>Developing Against A Locally Compiled Zitadel
|
||||
|
||||
To develop against a locally compiled version of Zitadel, you need to build the Zitadel docker image first.
|
||||
Clone the [Zitadel repository](https://github.com/zitadel/zitadel.git) and run the following command from its root:
|
||||
|
||||
```sh
|
||||
# This compiles a Zitadel binary if it does not exist at ./zitadel already and copies it into a Docker image.
|
||||
# If you want to recompile the binary, run `make compile` first
|
||||
make login_dev
|
||||
```
|
||||
|
||||
Open another terminal session at zitadel/zitadel/login and run the following commands to start the dev server.
|
||||
|
||||
```bash
|
||||
# Install dependencies. Developing requires Node.js v20
|
||||
pnpm install
|
||||
|
||||
# Start a local development server and have apps/login/.env.test.local configured for you to target the local Zitadel instance.
|
||||
NODE_ENV=test pnpm dev
|
||||
```
|
||||
|
||||
Log in at https://127.0.0.1.sslip.io/ui/v2/login/loginname and use the following credentials:
|
||||
**Loginname**: *zitadel-admin@zitadel.127.0.0.1.sslip.io*
|
||||
**Password**: _Password1!_.
|
||||
|
||||
The login app live-reloads on changes, so you can start developing right away.
|
||||
|
||||
### Quality Assurance
|
||||
|
||||
Use `make` commands to test the quality of your code against a production build without installing any dependencies besides Docker.
|
||||
Using `make` commands, you can reproduce and debug the CI pipelines locally.
|
||||
|
||||
```sh
|
||||
# Reproduce the whole CI pipeline in docker
|
||||
make login_quality
|
||||
# Show other options with make
|
||||
make help
|
||||
```
|
||||
|
||||
Use `pnpm` commands to run the tests in dev mode with live reloading and debugging capabilities.
|
||||
|
||||
#### Linting and formatting
|
||||
|
||||
Check the formatting and linting of the code in docker
|
||||
|
||||
```sh
|
||||
make login_lint
|
||||
```
|
||||
|
||||
Check the linting of the code using pnpm
|
||||
|
||||
```sh
|
||||
pnpm lint
|
||||
pnpm format
|
||||
```
|
||||
|
||||
Fix the linting of your code
|
||||
|
||||
```sh
|
||||
pnpm lint:fix
|
||||
pnpm format:fix
|
||||
```
|
||||
|
||||
#### Running Unit Tests
|
||||
|
||||
Run the tests in docker
|
||||
|
||||
```sh
|
||||
make login_test_unit
|
||||
```
|
||||
|
||||
Run unit tests with live-reloading
|
||||
|
||||
```sh
|
||||
pnpm test:unit
|
||||
```
|
||||
|
||||
#### Running Integration Tests
|
||||
|
||||
Run the test in docker
|
||||
|
||||
```sh
|
||||
make login_test_integration
|
||||
```
|
||||
|
||||
Alternatively, run a live-reloading development server with an interactive Cypress test suite.
|
||||
First, set up your local test environment.
|
||||
|
||||
```sh
|
||||
# Install dependencies. Developing requires Node.js v20
|
||||
pnpm install
|
||||
|
||||
# Generate gRPC stubs
|
||||
pnpm generate
|
||||
|
||||
# Start a local development server and use apps/login/.env.test to use the locally mocked Zitadel API.
|
||||
pnpm test:integration:setup
|
||||
```
|
||||
|
||||
Now, in another terminal session, open the interactive Cypress integration test suite.
|
||||
|
||||
```sh
|
||||
pnpm test:integration open
|
||||
```
|
||||
|
||||
Show more options with Cypress
|
||||
|
||||
```sh
|
||||
pnpm test:integration help
|
||||
```
|
||||
|
||||
#### Running Acceptance Tests
|
||||
|
||||
To run the tests in docker against the latest release of Zitadel, use the following command:
|
||||
|
||||
:warning: The acceptance tests are not reliable at the moment :construction:
|
||||
|
||||
```sh
|
||||
make login_test_acceptance
|
||||
```
|
||||
|
||||
Alternatively, run can use a live-reloading development server with an interactive Playwright test suite.
|
||||
Set up your local environment by running the commands either for [developing against a local latest Zitadel release](latest) or for [developing against a locally compiled Zitadel](compiled).
|
||||
|
||||
Now, in another terminal session, open the interactive Playwright acceptance test suite.
|
||||
|
||||
```sh
|
||||
pnpm test:acceptance open
|
||||
```
|
||||
|
||||
Show more options with Playwright
|
||||
|
||||
```sh
|
||||
pnpm test:acceptance help
|
||||
```
|
36
apps/login/Dockerfile
Normal file
36
apps/login/Dockerfile
Normal file
@@ -0,0 +1,36 @@
|
||||
FROM node:20-alpine AS base
|
||||
|
||||
FROM base AS build
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable && COREPACK_ENABLE_DOWNLOAD_PROMPT=0 corepack prepare pnpm@9.1.2 --activate && \
|
||||
apk update && apk add --no-cache && \
|
||||
rm -rf /var/cache/apk/*
|
||||
WORKDIR /app
|
||||
COPY pnpm-lock.yaml ./
|
||||
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm fetch --frozen-lockfile
|
||||
COPY package.json ./
|
||||
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile --prod
|
||||
COPY . .
|
||||
RUN pnpm build:login:standalone
|
||||
|
||||
FROM scratch AS build-out
|
||||
COPY --from=build /app/.next/standalone /
|
||||
COPY --from=build /app/.next/static /.next/static
|
||||
COPY --from=build /app/public /public
|
||||
|
||||
FROM base AS login-standalone
|
||||
WORKDIR /runtime
|
||||
RUN addgroup --system --gid 1001 nodejs && \
|
||||
adduser --system --uid 1001 nextjs
|
||||
# If /.env-file/.env is mounted into the container, its variables are made available to the server before it starts up.
|
||||
RUN mkdir -p /.env-file && touch /.env-file/.env && chown -R nextjs:nodejs /.env-file
|
||||
COPY ./scripts/ ./
|
||||
COPY --chown=nextjs:nodejs --from=build-out / ./
|
||||
USER nextjs
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
ENV PORT=3000
|
||||
# TODO: Check healthy, not ready
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD ["/bin/sh", "-c", "node ./healthcheck.js http://localhost:${PORT}/ui/v2/login/healthy"]
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
21
apps/login/LICENSE
Normal file
21
apps/login/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 ZITADEL
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
10
apps/login/acceptance/.eslintrc.cjs
Normal file
10
apps/login/acceptance/.eslintrc.cjs
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
// Use basic ESLint config since the login app has its own detailed config
|
||||
extends: ["eslint:recommended"],
|
||||
settings: {
|
||||
next: {
|
||||
rootDir: ["apps/*/"],
|
||||
},
|
||||
},
|
||||
};
|
1
apps/login/acceptance/.gitignore
vendored
Normal file
1
apps/login/acceptance/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
go-command
|
11
apps/login/acceptance/go-command.Dockerfile
Normal file
11
apps/login/acceptance/go-command.Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
ARG LOGIN_TEST_ACCEPTANCE_GOLANG_TAG="golang:1.24-alpine"
|
||||
|
||||
FROM ${LOGIN_TEST_ACCEPTANCE_GOLANG_TAG}
|
||||
RUN apk add curl jq
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN go build -o /go-command .
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s \
|
||||
CMD curl -f http://localhost:${PORT}/healthy || exit 1
|
||||
ENTRYPOINT [ "/go-command" ]
|
28
apps/login/acceptance/idp/oidc/go.mod
Normal file
28
apps/login/acceptance/idp/oidc/go.mod
Normal file
@@ -0,0 +1,28 @@
|
||||
module github.com/zitadel/typescript/acceptance/idp/oidc
|
||||
|
||||
go 1.24.1
|
||||
|
||||
require github.com/zitadel/oidc/v3 v3.37.0
|
||||
|
||||
require (
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.2 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/muhlemmer/gu v0.3.1 // indirect
|
||||
github.com/muhlemmer/httpforwarded v0.1.0 // indirect
|
||||
github.com/rs/cors v1.11.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/zitadel/logging v0.6.2 // indirect
|
||||
github.com/zitadel/schema v1.3.1 // indirect
|
||||
go.opentelemetry.io/otel v1.29.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.29.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.29.0 // indirect
|
||||
golang.org/x/crypto v0.35.0 // indirect
|
||||
golang.org/x/oauth2 v0.28.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
)
|
71
apps/login/acceptance/idp/oidc/go.sum
Normal file
71
apps/login/acceptance/idp/oidc/go.sum
Normal file
@@ -0,0 +1,71 @@
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM=
|
||||
github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM=
|
||||
github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY=
|
||||
github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/zitadel/logging v0.6.2 h1:MW2kDDR0ieQynPZ0KIZPrh9ote2WkxfBif5QoARDQcU=
|
||||
github.com/zitadel/logging v0.6.2/go.mod h1:z6VWLWUkJpnNVDSLzrPSQSQyttysKZ6bCRongw0ROK4=
|
||||
github.com/zitadel/oidc/v3 v3.37.0 h1:nYATWlnP7f18XiAbw6upUruBaqfB1kUrXrSTf1EYGO8=
|
||||
github.com/zitadel/oidc/v3 v3.37.0/go.mod h1:/xDan4OUQhguJ4Ur73OOJrtugvR164OMnidXP9xfVNw=
|
||||
github.com/zitadel/schema v1.3.1 h1:QT3kwiRIRXXLVAs6gCK/u044WmUVh6IlbLXUsn6yRQU=
|
||||
github.com/zitadel/schema v1.3.1/go.mod h1:071u7D2LQacy1HAN+YnMd/mx1qVE2isb0Mjeqg46xnU=
|
||||
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
|
||||
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
|
||||
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
|
||||
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
|
||||
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
|
||||
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
186
apps/login/acceptance/idp/oidc/main.go
Normal file
186
apps/login/acceptance/idp/oidc/main.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v3/example/server/exampleop"
|
||||
"github.com/zitadel/oidc/v3/example/server/storage"
|
||||
)
|
||||
|
||||
func main() {
|
||||
apiURL := os.Getenv("API_URL")
|
||||
pat := readPAT(os.Getenv("PAT_FILE"))
|
||||
domain := os.Getenv("API_DOMAIN")
|
||||
schema := os.Getenv("SCHEMA")
|
||||
host := os.Getenv("HOST")
|
||||
port := os.Getenv("PORT")
|
||||
|
||||
logger := slog.New(
|
||||
slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||
AddSource: true,
|
||||
Level: slog.LevelDebug,
|
||||
}),
|
||||
)
|
||||
|
||||
issuer := fmt.Sprintf("%s://%s:%s/", schema, host, port)
|
||||
redirectURI := fmt.Sprintf("%s/idps/callback", apiURL)
|
||||
|
||||
clientID := "web"
|
||||
clientSecret := "secret"
|
||||
storage.RegisterClients(
|
||||
storage.WebClient(clientID, clientSecret, redirectURI),
|
||||
)
|
||||
|
||||
storage := storage.NewStorage(storage.NewUserStore(issuer))
|
||||
router := exampleop.SetupServer(issuer, storage, logger, false)
|
||||
|
||||
server := &http.Server{
|
||||
Addr: ":" + port,
|
||||
Handler: router,
|
||||
}
|
||||
go func() {
|
||||
if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalf("HTTP server error: %v", err)
|
||||
}
|
||||
log.Println("Stopped serving new connections.")
|
||||
}()
|
||||
|
||||
createZitadelResources(apiURL, pat, domain, issuer, clientID, clientSecret)
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigChan
|
||||
|
||||
shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer shutdownRelease()
|
||||
|
||||
if err := server.Shutdown(shutdownCtx); err != nil {
|
||||
log.Fatalf("HTTP shutdown error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func readPAT(path string) string {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pat, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return strings.Trim(string(pat), "\n")
|
||||
}
|
||||
|
||||
func createZitadelResources(apiURL, pat, domain, issuer, clientID, clientSecret string) error {
|
||||
idpID, err := CreateIDP(apiURL, pat, domain, issuer, clientID, clientSecret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ActivateIDP(apiURL, pat, domain, idpID)
|
||||
}
|
||||
|
||||
type createIDP struct {
|
||||
Name string `json:"name"`
|
||||
Issuer string `json:"issuer"`
|
||||
ClientId string `json:"clientId"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
Scopes []string `json:"scopes"`
|
||||
ProviderOptions providerOptions `json:"providerOptions"`
|
||||
IsIdTokenMapping bool `json:"isIdTokenMapping"`
|
||||
UsePkce bool `json:"usePkce"`
|
||||
}
|
||||
|
||||
type providerOptions struct {
|
||||
IsLinkingAllowed bool `json:"isLinkingAllowed"`
|
||||
IsCreationAllowed bool `json:"isCreationAllowed"`
|
||||
IsAutoCreation bool `json:"isAutoCreation"`
|
||||
IsAutoUpdate bool `json:"isAutoUpdate"`
|
||||
AutoLinking string `json:"autoLinking"`
|
||||
}
|
||||
|
||||
type idp struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func CreateIDP(apiURL, pat, domain string, issuer, clientID, clientSecret string) (string, error) {
|
||||
createIDP := &createIDP{
|
||||
Name: "OIDC",
|
||||
Issuer: issuer,
|
||||
ClientId: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
Scopes: []string{"openid", "profile", "email"},
|
||||
ProviderOptions: providerOptions{
|
||||
IsLinkingAllowed: true,
|
||||
IsCreationAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
AutoLinking: "AUTO_LINKING_OPTION_USERNAME",
|
||||
},
|
||||
IsIdTokenMapping: false,
|
||||
UsePkce: false,
|
||||
}
|
||||
|
||||
resp, err := doRequestWithHeaders(apiURL+"/admin/v1/idps/generic_oidc", pat, domain, createIDP)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
idp := new(idp)
|
||||
if err := json.Unmarshal(data, idp); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return idp.ID, nil
|
||||
}
|
||||
|
||||
type activateIDP struct {
|
||||
IdpId string `json:"idpId"`
|
||||
}
|
||||
|
||||
func ActivateIDP(apiURL, pat, domain string, idpID string) error {
|
||||
activateIDP := &activateIDP{
|
||||
IdpId: idpID,
|
||||
}
|
||||
_, err := doRequestWithHeaders(apiURL+"/admin/v1/policies/login/idps", pat, domain, activateIDP)
|
||||
return err
|
||||
}
|
||||
|
||||
func doRequestWithHeaders(apiURL, pat, domain string, body any) (*http.Response, error) {
|
||||
data, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(data)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values := http.Header{}
|
||||
values.Add("Authorization", "Bearer "+pat)
|
||||
values.Add("x-forwarded-host", domain)
|
||||
values.Add("Content-Type", "application/json")
|
||||
req.Header = values
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
16
apps/login/acceptance/idp/saml/go.mod
Normal file
16
apps/login/acceptance/idp/saml/go.mod
Normal file
@@ -0,0 +1,16 @@
|
||||
module github.com/zitadel/typescript/acceptance/idp/saml
|
||||
|
||||
go 1.24.1
|
||||
|
||||
require (
|
||||
github.com/crewjam/saml v0.4.14
|
||||
github.com/mattermost/xml-roundtrip-validator v0.1.0
|
||||
github.com/zenazn/goji v1.0.1
|
||||
golang.org/x/crypto v0.36.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beevik/etree v1.1.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||
github.com/russellhaering/goxmldsig v1.3.0 // indirect
|
||||
)
|
49
apps/login/acceptance/idp/saml/go.sum
Normal file
49
apps/login/acceptance/idp/saml/go.sum
Normal file
@@ -0,0 +1,49 @@
|
||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c=
|
||||
github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
|
||||
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
|
||||
github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/russellhaering/goxmldsig v1.3.0 h1:DllIWUgMy0cRUMfGiASiYEa35nsieyD3cigIwLonTPM=
|
||||
github.com/russellhaering/goxmldsig v1.3.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8=
|
||||
github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
328
apps/login/acceptance/idp/saml/main.go
Normal file
328
apps/login/acceptance/idp/saml/main.go
Normal file
@@ -0,0 +1,328 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/crewjam/saml"
|
||||
"github.com/crewjam/saml/logger"
|
||||
"github.com/crewjam/saml/samlidp"
|
||||
xrv "github.com/mattermost/xml-roundtrip-validator"
|
||||
"github.com/zenazn/goji"
|
||||
"github.com/zenazn/goji/bind"
|
||||
"github.com/zenazn/goji/web"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var key = func() crypto.PrivateKey {
|
||||
b, _ := pem.Decode([]byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA0OhbMuizgtbFOfwbK7aURuXhZx6VRuAs3nNibiuifwCGz6u9
|
||||
yy7bOR0P+zqN0YkjxaokqFgra7rXKCdeABmoLqCC0U+cGmLNwPOOA0PaD5q5xKhQ
|
||||
4Me3rt/R9C4Ca6k3/OnkxnKwnogcsmdgs2l8liT3qVHP04Oc7Uymq2v09bGb6nPu
|
||||
fOrkXS9F6mSClxHG/q59AGOWsXK1xzIRV1eu8W2SNdyeFVU1JHiQe444xLoPul5t
|
||||
InWasKayFsPlJfWNc8EoU8COjNhfo/GovFTHVjh9oUR/gwEFVwifIHihRE0Hazn2
|
||||
EQSLaOr2LM0TsRsQroFjmwSGgI+X2bfbMTqWOQIDAQABAoIBAFWZwDTeESBdrLcT
|
||||
zHZe++cJLxE4AObn2LrWANEv5AeySYsyzjRBYObIN9IzrgTb8uJ900N/zVr5VkxH
|
||||
xUa5PKbOcowd2NMfBTw5EEnaNbILLm+coHdanrNzVu59I9TFpAFoPavrNt/e2hNo
|
||||
NMGPSdOkFi81LLl4xoadz/WR6O/7N2famM+0u7C2uBe+TrVwHyuqboYoidJDhO8M
|
||||
w4WlY9QgAUhkPyzZqrl+VfF1aDTGVf4LJgaVevfFCas8Ws6DQX5q4QdIoV6/0vXi
|
||||
B1M+aTnWjHuiIzjBMWhcYW2+I5zfwNWRXaxdlrYXRukGSdnyO+DH/FhHePJgmlkj
|
||||
NInADDkCgYEA6MEQFOFSCc/ELXYWgStsrtIlJUcsLdLBsy1ocyQa2lkVUw58TouW
|
||||
RciE6TjW9rp31pfQUnO2l6zOUC6LT9Jvlb9PSsyW+rvjtKB5PjJI6W0hjX41wEO6
|
||||
fshFELMJd9W+Ezao2AsP2hZJ8McCF8no9e00+G4xTAyxHsNI2AFTCQcCgYEA5cWZ
|
||||
JwNb4t7YeEajPt9xuYNUOQpjvQn1aGOV7KcwTx5ELP/Hzi723BxHs7GSdrLkkDmi
|
||||
Gpb+mfL4wxCt0fK0i8GFQsRn5eusyq9hLqP/bmjpHoXe/1uajFbE1fZQR+2LX05N
|
||||
3ATlKaH2hdfCJedFa4wf43+cl6Yhp6ZA0Yet1r8CgYEAwiu1j8W9G+RRA5/8/DtO
|
||||
yrUTOfsbFws4fpLGDTA0mq0whf6Soy/96C90+d9qLaC3srUpnG9eB0CpSOjbXXbv
|
||||
kdxseLkexwOR3bD2FHX8r4dUM2bzznZyEaxfOaQypN8SV5ME3l60Fbr8ajqLO288
|
||||
wlTmGM5Mn+YCqOg/T7wjGmcCgYBpzNfdl/VafOROVbBbhgXWtzsz3K3aYNiIjbp+
|
||||
MunStIwN8GUvcn6nEbqOaoiXcX4/TtpuxfJMLw4OvAJdtxUdeSmEee2heCijV6g3
|
||||
ErrOOy6EqH3rNWHvlxChuP50cFQJuYOueO6QggyCyruSOnDDuc0BM0SGq6+5g5s7
|
||||
H++S/wKBgQDIkqBtFr9UEf8d6JpkxS0RXDlhSMjkXmkQeKGFzdoJcYVFIwq8jTNB
|
||||
nJrVIGs3GcBkqGic+i7rTO1YPkquv4dUuiIn+vKZVoO6b54f+oPBXd4S0BnuEqFE
|
||||
rdKNuCZhiaE2XD9L/O9KP1fh5bfEcKwazQ23EvpJHBMm8BGC+/YZNw==
|
||||
-----END RSA PRIVATE KEY-----`))
|
||||
k, _ := x509.ParsePKCS1PrivateKey(b.Bytes)
|
||||
return k
|
||||
}()
|
||||
|
||||
var cert = func() *x509.Certificate {
|
||||
b, _ := pem.Decode([]byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV
|
||||
BAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5
|
||||
NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB
|
||||
BQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8A
|
||||
hs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+a
|
||||
ucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWx
|
||||
m+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6
|
||||
D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURN
|
||||
B2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0O
|
||||
BBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56
|
||||
zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5
|
||||
pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uv
|
||||
NONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEf
|
||||
y/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL
|
||||
/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsb
|
||||
GFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTL
|
||||
UzreO96WzlBBMtY=
|
||||
-----END CERTIFICATE-----`))
|
||||
c, _ := x509.ParseCertificate(b.Bytes)
|
||||
return c
|
||||
}()
|
||||
|
||||
// Example from https://github.com/crewjam/saml/blob/main/example/idp/idp.go
|
||||
func main() {
|
||||
apiURL := os.Getenv("API_URL")
|
||||
pat := readPAT(os.Getenv("PAT_FILE"))
|
||||
domain := os.Getenv("API_DOMAIN")
|
||||
schema := os.Getenv("SCHEMA")
|
||||
host := os.Getenv("HOST")
|
||||
port := os.Getenv("PORT")
|
||||
|
||||
baseURL, err := url.Parse(schema + "://" + host + ":" + port)
|
||||
if err != nil {
|
||||
|
||||
panic(err)
|
||||
}
|
||||
|
||||
idpServer, err := samlidp.New(samlidp.Options{
|
||||
URL: *baseURL,
|
||||
Logger: logger.DefaultLogger,
|
||||
Key: key,
|
||||
Certificate: cert,
|
||||
Store: &samlidp.MemoryStore{},
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
panic(err)
|
||||
}
|
||||
|
||||
metadata, err := xml.MarshalIndent(idpServer.IDP.Metadata(), "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
idpID, err := createZitadelResources(apiURL, pat, domain, metadata)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
lis := bind.Socket(":" + baseURL.Port())
|
||||
goji.Handle("/*", idpServer)
|
||||
|
||||
go func() {
|
||||
goji.ServeListener(lis)
|
||||
}()
|
||||
|
||||
addService(idpServer, apiURL+"/idps/"+idpID+"/saml/metadata")
|
||||
addUsers(idpServer)
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigChan
|
||||
|
||||
if err := lis.Close(); err != nil {
|
||||
log.Fatalf("HTTP shutdown error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func readPAT(path string) string {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pat, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return strings.Trim(string(pat), "\n")
|
||||
}
|
||||
|
||||
func addService(idpServer *samlidp.Server, spURLStr string) {
|
||||
metadataResp, err := http.Get(spURLStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer metadataResp.Body.Close()
|
||||
|
||||
idpServer.HandlePutService(
|
||||
web.C{URLParams: map[string]string{"id": spURLStr}},
|
||||
httptest.NewRecorder(),
|
||||
httptest.NewRequest(http.MethodPost, spURLStr, metadataResp.Body),
|
||||
)
|
||||
}
|
||||
|
||||
func getSPMetadata(r io.Reader) (spMetadata *saml.EntityDescriptor, err error) {
|
||||
var data []byte
|
||||
if data, err = io.ReadAll(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spMetadata = &saml.EntityDescriptor{}
|
||||
if err := xrv.Validate(bytes.NewBuffer(data)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := xml.Unmarshal(data, &spMetadata); err != nil {
|
||||
if err.Error() == "expected element type <EntityDescriptor> but have <EntitiesDescriptor>" {
|
||||
entities := &saml.EntitiesDescriptor{}
|
||||
if err := xml.Unmarshal(data, &entities); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, e := range entities.EntityDescriptors {
|
||||
if len(e.SPSSODescriptors) > 0 {
|
||||
return &e, nil
|
||||
}
|
||||
}
|
||||
|
||||
// there were no SPSSODescriptors in the response
|
||||
return nil, errors.New("metadata contained no service provider metadata")
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return spMetadata, nil
|
||||
}
|
||||
|
||||
func addUsers(idpServer *samlidp.Server) {
|
||||
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("hunter2"), bcrypt.DefaultCost)
|
||||
err := idpServer.Store.Put("/users/alice", samlidp.User{Name: "alice",
|
||||
HashedPassword: hashedPassword,
|
||||
Groups: []string{"Administrators", "Users"},
|
||||
Email: "alice@example.com",
|
||||
CommonName: "Alice Smith",
|
||||
Surname: "Smith",
|
||||
GivenName: "Alice",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = idpServer.Store.Put("/users/bob", samlidp.User{
|
||||
Name: "bob",
|
||||
HashedPassword: hashedPassword,
|
||||
Groups: []string{"Users"},
|
||||
Email: "bob@example.com",
|
||||
CommonName: "Bob Smith",
|
||||
Surname: "Smith",
|
||||
GivenName: "Bob",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func createZitadelResources(apiURL, pat, domain string, metadata []byte) (string, error) {
|
||||
idpID, err := CreateIDP(apiURL, pat, domain, metadata)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return idpID, ActivateIDP(apiURL, pat, domain, idpID)
|
||||
}
|
||||
|
||||
type createIDP struct {
|
||||
Name string `json:"name"`
|
||||
MetadataXml string `json:"metadataXml"`
|
||||
Binding string `json:"binding"`
|
||||
WithSignedRequest bool `json:"withSignedRequest"`
|
||||
ProviderOptions providerOptions `json:"providerOptions"`
|
||||
NameIdFormat string `json:"nameIdFormat"`
|
||||
}
|
||||
type providerOptions struct {
|
||||
IsLinkingAllowed bool `json:"isLinkingAllowed"`
|
||||
IsCreationAllowed bool `json:"isCreationAllowed"`
|
||||
IsAutoCreation bool `json:"isAutoCreation"`
|
||||
IsAutoUpdate bool `json:"isAutoUpdate"`
|
||||
AutoLinking string `json:"autoLinking"`
|
||||
}
|
||||
|
||||
type idp struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func CreateIDP(apiURL, pat, domain string, idpMetadata []byte) (string, error) {
|
||||
encoded := make([]byte, base64.URLEncoding.EncodedLen(len(idpMetadata)))
|
||||
base64.URLEncoding.Encode(encoded, idpMetadata)
|
||||
|
||||
createIDP := &createIDP{
|
||||
Name: "CREWJAM",
|
||||
MetadataXml: string(encoded),
|
||||
Binding: "SAML_BINDING_REDIRECT",
|
||||
WithSignedRequest: false,
|
||||
ProviderOptions: providerOptions{
|
||||
IsLinkingAllowed: true,
|
||||
IsCreationAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
AutoLinking: "AUTO_LINKING_OPTION_USERNAME",
|
||||
},
|
||||
NameIdFormat: "SAML_NAME_ID_FORMAT_PERSISTENT",
|
||||
}
|
||||
|
||||
resp, err := doRequestWithHeaders(apiURL+"/admin/v1/idps/saml", pat, domain, createIDP)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
idp := new(idp)
|
||||
if err := json.Unmarshal(data, idp); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return idp.ID, nil
|
||||
}
|
||||
|
||||
type activateIDP struct {
|
||||
IdpId string `json:"idpId"`
|
||||
}
|
||||
|
||||
func ActivateIDP(apiURL, pat, domain string, idpID string) error {
|
||||
activateIDP := &activateIDP{
|
||||
IdpId: idpID,
|
||||
}
|
||||
_, err := doRequestWithHeaders(apiURL+"/admin/v1/policies/login/idps", pat, domain, activateIDP)
|
||||
return err
|
||||
}
|
||||
|
||||
func doRequestWithHeaders(apiURL, pat, domain string, body any) (*http.Response, error) {
|
||||
data, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(data)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values := http.Header{}
|
||||
values.Add("Authorization", "Bearer "+pat)
|
||||
values.Add("x-forwarded-host", domain)
|
||||
values.Add("Content-Type", "application/json")
|
||||
req.Header = values
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
26
apps/login/acceptance/oidcrp/go.mod
Normal file
26
apps/login/acceptance/oidcrp/go.mod
Normal file
@@ -0,0 +1,26 @@
|
||||
module github.com/zitadel/typescript/acceptance/oidc
|
||||
|
||||
go 1.24.1
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/zitadel/logging v0.6.1
|
||||
github.com/zitadel/oidc/v3 v3.36.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/muhlemmer/gu v0.3.1 // indirect
|
||||
github.com/zitadel/schema v1.3.0 // indirect
|
||||
go.opentelemetry.io/otel v1.29.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.29.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.29.0 // indirect
|
||||
golang.org/x/crypto v0.35.0 // indirect
|
||||
golang.org/x/oauth2 v0.28.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
)
|
67
apps/login/acceptance/oidcrp/go.sum
Normal file
67
apps/login/acceptance/oidcrp/go.sum
Normal file
@@ -0,0 +1,67 @@
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/jeremija/gosubmit v0.2.8 h1:mmSITBz9JxVtu8eqbN+zmmwX7Ij2RidQxhcwRVI4wqA=
|
||||
github.com/jeremija/gosubmit v0.2.8/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI=
|
||||
github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM=
|
||||
github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM=
|
||||
github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY=
|
||||
github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/zitadel/logging v0.6.1 h1:Vyzk1rl9Kq9RCevcpX6ujUaTYFX43aa4LkvV1TvUk+Y=
|
||||
github.com/zitadel/logging v0.6.1/go.mod h1:Y4CyAXHpl3Mig6JOszcV5Rqqsojj+3n7y2F591Mp/ow=
|
||||
github.com/zitadel/oidc/v3 v3.36.1 h1:1AT1NqKKEqAwx4GmKJZ9fYkWH2WIn/VKMfQ46nBtRf0=
|
||||
github.com/zitadel/oidc/v3 v3.36.1/go.mod h1:dApGZLvWZTHRuxmcbQlW5d2XVjVYR3vGOdq536igmTs=
|
||||
github.com/zitadel/schema v1.3.0 h1:kQ9W9tvIwZICCKWcMvCEweXET1OcOyGEuFbHs4o5kg0=
|
||||
github.com/zitadel/schema v1.3.0/go.mod h1:NptN6mkBDFvERUCvZHlvWmmME+gmZ44xzwRXwhzsbtc=
|
||||
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
|
||||
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
|
||||
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
|
||||
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
|
||||
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
|
||||
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
322
apps/login/acceptance/oidcrp/main.go
Normal file
322
apps/login/acceptance/oidcrp/main.go
Normal file
@@ -0,0 +1,322 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
var (
|
||||
callbackPath = "/auth/callback"
|
||||
key = []byte("test1234test1234")
|
||||
)
|
||||
|
||||
func main() {
|
||||
apiURL := os.Getenv("API_URL")
|
||||
pat := readPAT(os.Getenv("PAT_FILE"))
|
||||
domain := os.Getenv("API_DOMAIN")
|
||||
loginURL := os.Getenv("LOGIN_URL")
|
||||
issuer := os.Getenv("ISSUER")
|
||||
port := os.Getenv("PORT")
|
||||
scopeList := strings.Split(os.Getenv("SCOPES"), " ")
|
||||
|
||||
redirectURI := fmt.Sprintf("%s%s", issuer, callbackPath)
|
||||
cookieHandler := httphelper.NewCookieHandler(key, key, httphelper.WithUnsecure())
|
||||
|
||||
clientID, clientSecret, err := createZitadelResources(apiURL, pat, domain, redirectURI, loginURL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
logger := slog.New(
|
||||
slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||
AddSource: true,
|
||||
Level: slog.LevelDebug,
|
||||
}),
|
||||
)
|
||||
client := &http.Client{
|
||||
Timeout: time.Minute,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
// enable outgoing request logging
|
||||
logging.EnableHTTPClient(client,
|
||||
logging.WithClientGroup("client"),
|
||||
)
|
||||
|
||||
options := []rp.Option{
|
||||
rp.WithCookieHandler(cookieHandler),
|
||||
rp.WithVerifierOpts(rp.WithIssuedAtOffset(5 * time.Second)),
|
||||
rp.WithHTTPClient(client),
|
||||
rp.WithLogger(logger),
|
||||
rp.WithSigningAlgsFromDiscovery(),
|
||||
rp.WithCustomDiscoveryUrl(issuer + "/.well-known/openid-configuration"),
|
||||
}
|
||||
if clientSecret == "" {
|
||||
options = append(options, rp.WithPKCE(cookieHandler))
|
||||
}
|
||||
|
||||
// One can add a logger to the context,
|
||||
// pre-defining log attributes as required.
|
||||
ctx := logging.ToContext(context.TODO(), logger)
|
||||
provider, err := rp.NewRelyingPartyOIDC(ctx, issuer, clientID, clientSecret, redirectURI, scopeList, options...)
|
||||
if err != nil {
|
||||
logrus.Fatalf("error creating provider %s", err.Error())
|
||||
}
|
||||
|
||||
// generate some state (representing the state of the user in your application,
|
||||
// e.g. the page where he was before sending him to login
|
||||
state := func() string {
|
||||
return uuid.New().String()
|
||||
}
|
||||
|
||||
urlOptions := []rp.URLParamOpt{
|
||||
rp.WithPromptURLParam("Welcome back!"),
|
||||
}
|
||||
|
||||
// register the AuthURLHandler at your preferred path.
|
||||
// the AuthURLHandler creates the auth request and redirects the user to the auth server.
|
||||
// including state handling with secure cookie and the possibility to use PKCE.
|
||||
// Prompts can optionally be set to inform the server of
|
||||
// any messages that need to be prompted back to the user.
|
||||
http.Handle("/login", rp.AuthURLHandler(
|
||||
state,
|
||||
provider,
|
||||
urlOptions...,
|
||||
))
|
||||
|
||||
// for demonstration purposes the returned userinfo response is written as JSON object onto response
|
||||
marshalUserinfo := func(w http.ResponseWriter, r *http.Request, tokens *oidc.Tokens[*oidc.IDTokenClaims], state string, rp rp.RelyingParty, info *oidc.UserInfo) {
|
||||
fmt.Println("access token", tokens.AccessToken)
|
||||
fmt.Println("refresh token", tokens.RefreshToken)
|
||||
fmt.Println("id token", tokens.IDToken)
|
||||
|
||||
data, err := json.Marshal(info)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("content-type", "application/json")
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
// register the CodeExchangeHandler at the callbackPath
|
||||
// the CodeExchangeHandler handles the auth response, creates the token request and calls the callback function
|
||||
// with the returned tokens from the token endpoint
|
||||
// in this example the callback function itself is wrapped by the UserinfoCallback which
|
||||
// will call the Userinfo endpoint, check the sub and pass the info into the callback function
|
||||
http.Handle(callbackPath, rp.CodeExchangeHandler(rp.UserinfoCallback(marshalUserinfo), provider))
|
||||
|
||||
// if you would use the callback without calling the userinfo endpoint, simply switch the callback handler for:
|
||||
//
|
||||
// http.Handle(callbackPath, rp.CodeExchangeHandler(marshalToken, provider))
|
||||
|
||||
// simple counter for request IDs
|
||||
var counter atomic.Int64
|
||||
// enable incomming request logging
|
||||
mw := logging.Middleware(
|
||||
logging.WithLogger(logger),
|
||||
logging.WithGroup("server"),
|
||||
logging.WithIDFunc(func() slog.Attr {
|
||||
return slog.Int64("id", counter.Add(1))
|
||||
}),
|
||||
)
|
||||
|
||||
http.Handle("/healthy", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return }))
|
||||
fmt.Println("/healthy returns 200 OK")
|
||||
|
||||
server := &http.Server{
|
||||
Addr: ":" + port,
|
||||
Handler: mw(http.DefaultServeMux),
|
||||
}
|
||||
go func() {
|
||||
if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalf("HTTP server error: %v", err)
|
||||
}
|
||||
log.Println("Stopped serving new connections.")
|
||||
}()
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigChan
|
||||
|
||||
shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer shutdownRelease()
|
||||
|
||||
if err := server.Shutdown(shutdownCtx); err != nil {
|
||||
log.Fatalf("HTTP shutdown error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func readPAT(path string) string {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pat, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return strings.Trim(string(pat), "\n")
|
||||
}
|
||||
|
||||
func createZitadelResources(apiURL, pat, domain, redirectURI, loginURL string) (string, string, error) {
|
||||
projectID, err := CreateProject(apiURL, pat, domain)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return CreateApp(apiURL, pat, domain, projectID, redirectURI, loginURL)
|
||||
}
|
||||
|
||||
type project struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
type createProject struct {
|
||||
Name string `json:"name"`
|
||||
ProjectRoleAssertion bool `json:"projectRoleAssertion"`
|
||||
ProjectRoleCheck bool `json:"projectRoleCheck"`
|
||||
HasProjectCheck bool `json:"hasProjectCheck"`
|
||||
PrivateLabelingSetting string `json:"privateLabelingSetting"`
|
||||
}
|
||||
|
||||
func CreateProject(apiURL, pat, domain string) (string, error) {
|
||||
createProject := &createProject{
|
||||
Name: "OIDC",
|
||||
ProjectRoleAssertion: false,
|
||||
ProjectRoleCheck: false,
|
||||
HasProjectCheck: false,
|
||||
PrivateLabelingSetting: "PRIVATE_LABELING_SETTING_UNSPECIFIED",
|
||||
}
|
||||
resp, err := doRequestWithHeaders(apiURL+"/management/v1/projects", pat, domain, createProject)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
p := new(project)
|
||||
if err := json.Unmarshal(data, p); err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Printf("projectID: %+v\n", p.ID)
|
||||
return p.ID, nil
|
||||
}
|
||||
|
||||
type createApp struct {
|
||||
Name string `json:"name"`
|
||||
RedirectUris []string `json:"redirectUris"`
|
||||
ResponseTypes []string `json:"responseTypes"`
|
||||
GrantTypes []string `json:"grantTypes"`
|
||||
AppType string `json:"appType"`
|
||||
AuthMethodType string `json:"authMethodType"`
|
||||
PostLogoutRedirectUris []string `json:"postLogoutRedirectUris"`
|
||||
Version string `json:"version"`
|
||||
DevMode bool `json:"devMode"`
|
||||
AccessTokenType string `json:"accessTokenType"`
|
||||
AccessTokenRoleAssertion bool `json:"accessTokenRoleAssertion"`
|
||||
IdTokenRoleAssertion bool `json:"idTokenRoleAssertion"`
|
||||
IdTokenUserinfoAssertion bool `json:"idTokenUserinfoAssertion"`
|
||||
ClockSkew string `json:"clockSkew"`
|
||||
AdditionalOrigins []string `json:"additionalOrigins"`
|
||||
SkipNativeAppSuccessPage bool `json:"skipNativeAppSuccessPage"`
|
||||
BackChannelLogoutUri []string `json:"backChannelLogoutUri"`
|
||||
LoginVersion version `json:"loginVersion"`
|
||||
}
|
||||
|
||||
type version struct {
|
||||
LoginV2 loginV2 `json:"loginV2"`
|
||||
}
|
||||
type loginV2 struct {
|
||||
BaseUri string `json:"baseUri"`
|
||||
}
|
||||
|
||||
type app struct {
|
||||
ClientID string `json:"clientId"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
}
|
||||
|
||||
func CreateApp(apiURL, pat, domain, projectID string, redirectURI, loginURL string) (string, string, error) {
|
||||
createApp := &createApp{
|
||||
Name: "OIDC",
|
||||
RedirectUris: []string{redirectURI},
|
||||
ResponseTypes: []string{"OIDC_RESPONSE_TYPE_CODE"},
|
||||
GrantTypes: []string{"OIDC_GRANT_TYPE_AUTHORIZATION_CODE"},
|
||||
AppType: "OIDC_APP_TYPE_WEB",
|
||||
AuthMethodType: "OIDC_AUTH_METHOD_TYPE_BASIC",
|
||||
Version: "OIDC_VERSION_1_0",
|
||||
DevMode: true,
|
||||
AccessTokenType: "OIDC_TOKEN_TYPE_BEARER",
|
||||
AccessTokenRoleAssertion: true,
|
||||
IdTokenRoleAssertion: true,
|
||||
IdTokenUserinfoAssertion: true,
|
||||
ClockSkew: "1s",
|
||||
SkipNativeAppSuccessPage: true,
|
||||
LoginVersion: version{
|
||||
LoginV2: loginV2{
|
||||
BaseUri: loginURL,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := doRequestWithHeaders(apiURL+"/management/v1/projects/"+projectID+"/apps/oidc", pat, domain, createApp)
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
a := new(app)
|
||||
if err := json.Unmarshal(data, a); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return a.ClientID, a.ClientSecret, err
|
||||
}
|
||||
|
||||
func doRequestWithHeaders(apiURL, pat, domain string, body any) (*http.Response, error) {
|
||||
data, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(data)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values := http.Header{}
|
||||
values.Add("Authorization", "Bearer "+pat)
|
||||
values.Add("x-forwarded-host", domain)
|
||||
values.Add("Content-Type", "application/json")
|
||||
req.Header = values
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
20
apps/login/acceptance/package.json
Normal file
20
apps/login/acceptance/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "login-test-acceptance",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test:acceptance": "dotenv -e ../login/.env.test.local playwright",
|
||||
"test:acceptance:setup": "cd ../.. && make login_test_acceptance_setup_env && NODE_ENV=test turbo run test:acceptance:setup:dev",
|
||||
"test:acceptance:setup:dev": "cd ../.. && make login_test_acceptance_setup_dev",
|
||||
"clean": "rm -rf .turbo node_modules"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^9.7.0",
|
||||
"@otplib/core": "^12.0.0",
|
||||
"@otplib/plugin-crypto": "^12.0.0",
|
||||
"@otplib/plugin-thirty-two": "^12.0.0",
|
||||
"@playwright/test": "^1.52.0",
|
||||
"dotenv-cli": "^8.0.0",
|
||||
"gaxios": "^7.1.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
3
apps/login/acceptance/pat/.gitignore
vendored
Normal file
3
apps/login/acceptance/pat/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*
|
||||
!.gitignore
|
||||
!.gitkeep
|
3
apps/login/acceptance/playwright-report/.gitignore
vendored
Normal file
3
apps/login/acceptance/playwright-report/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*
|
||||
!.gitignore
|
||||
!.gitkeep
|
0
build/zitadel/generate-grpc.sh → apps/login/acceptance/playwright-report/.gitkeep
Executable file → Normal file
0
build/zitadel/generate-grpc.sh → apps/login/acceptance/playwright-report/.gitkeep
Executable file → Normal file
78
apps/login/acceptance/playwright.config.ts
Normal file
78
apps/login/acceptance/playwright.config.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
import dotenv from "dotenv";
|
||||
import path from "path";
|
||||
|
||||
dotenv.config({ path: path.resolve(__dirname, "../login/.env.test.local") });
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: "./tests",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
expect: {
|
||||
timeout: 10_000, // 10 seconds
|
||||
},
|
||||
timeout: 300 * 1000, // 5 minutes
|
||||
globalTimeout: 30 * 60_000, // 30 minutes
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: [
|
||||
["line"],
|
||||
["html", { open: process.env.CI ? "never" : "on-failure", host: "0.0.0.0", outputFolder: "./playwright-report/html" }],
|
||||
],
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: process.env.LOGIN_BASE_URL || "http://127.0.0.1:3000",
|
||||
trace: "retain-on-failure",
|
||||
headless: true,
|
||||
screenshot: "only-on-failure",
|
||||
video: "retain-on-failure",
|
||||
ignoreHTTPSErrors: true,
|
||||
},
|
||||
outputDir: "test-results/results",
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
/*
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] },
|
||||
},
|
||||
TODO: webkit fails. Is this a bug?
|
||||
{
|
||||
name: 'webkit',
|
||||
use: { ...devices['Desktop Safari'] },
|
||||
},
|
||||
*/
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
});
|
18
apps/login/acceptance/samlsp/go.mod
Normal file
18
apps/login/acceptance/samlsp/go.mod
Normal file
@@ -0,0 +1,18 @@
|
||||
module github.com/zitadel/typescript/acceptance/saml
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require github.com/crewjam/saml v0.4.14
|
||||
|
||||
require (
|
||||
github.com/beevik/etree v1.5.0 // indirect
|
||||
github.com/crewjam/httperr v0.2.0 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/russellhaering/goxmldsig v1.5.0 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
)
|
38
apps/login/acceptance/samlsp/go.sum
Normal file
38
apps/login/acceptance/samlsp/go.sum
Normal file
@@ -0,0 +1,38 @@
|
||||
github.com/beevik/etree v1.5.0 h1:iaQZFSDS+3kYZiGoc9uKeOkUY3nYMXOKLl6KIJxiJWs=
|
||||
github.com/beevik/etree v1.5.0/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs=
|
||||
github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo=
|
||||
github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4=
|
||||
github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c=
|
||||
github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||
github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
|
||||
github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russellhaering/goxmldsig v1.5.0 h1:AU2UkkYIUOTyZRbe08XMThaOCelArgvNfYapcmSjBNw=
|
||||
github.com/russellhaering/goxmldsig v1.5.0/go.mod h1:x98CjQNFJcWfMxeOrMnMKg70lvDP6tE0nTaeUnjXDmk=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
271
apps/login/acceptance/samlsp/main.go
Normal file
271
apps/login/acceptance/samlsp/main.go
Normal file
@@ -0,0 +1,271 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/crewjam/saml/samlsp"
|
||||
)
|
||||
|
||||
var keyPair = func() tls.Certificate {
|
||||
cert := []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDITCCAgmgAwIBAgIUKjAUmxsHO44X+/TKBNciPgNl1GEwDQYJKoZIhvcNAQEL
|
||||
BQAwIDEeMBwGA1UEAwwVbXlzZXJ2aWNlLmV4YW1wbGUuY29tMB4XDTI0MTIxOTEz
|
||||
Mzc1MVoXDTI1MTIxOTEzMzc1MVowIDEeMBwGA1UEAwwVbXlzZXJ2aWNlLmV4YW1w
|
||||
bGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0QYuJsayILRI
|
||||
hVT7G1DlitVSXnt1iw3gEXJZfe81Egz06fUbvXF6Yo1LJmwYpqe/rm+hf4FNUb8e
|
||||
2O+LH2FieA9FkVe4P2gKOzw87A/KxvpV8stgNgl4LlqRCokbc1AzeE/NiLr5TcTD
|
||||
RXm3DUcYxXxinprtDu2jftFysaOZmNAukvE/iL6qS3X6ggVEDDM7tY9n5FV2eJ4E
|
||||
p0ImKfypi2aZYROxOK+v5x9ryFRMl4y07lMDvmtcV45uXYmfGNCgG9PNf91Kk/mh
|
||||
JxEQbxycJwFoSi9XWljR8ahPdO11LXG7Dsj/RVbY8k2LdKNstl6Ae3aCpbe9u2Pj
|
||||
vxYs1bVJuQIDAQABo1MwUTAdBgNVHQ4EFgQU+mRVN5HYJWgnpopReaLhf2cMcoYw
|
||||
HwYDVR0jBBgwFoAU+mRVN5HYJWgnpopReaLhf2cMcoYwDwYDVR0TAQH/BAUwAwEB
|
||||
/zANBgkqhkiG9w0BAQsFAAOCAQEABJpHVuc9tGhD04infRVlofvqXIUizTlOrjZX
|
||||
vozW9pIhSWEHX8o+sJP8AMZLnrsdq+bm0HE0HvgYrw7Lb8pd4FpR46TkFHjeukoj
|
||||
izqfgckjIBl2nwPGlynbKA0/U/rTCSxVt7XiAn+lgYUGIpOzNdk06/hRMitrMNB7
|
||||
t2C97NseVC4b1ZgyFrozsefCfUmD8IJF0+XJ4Wzmsh0jRrI8koCtVmPYnKn6vw1b
|
||||
cZprg/97CWHYrsavd406wOB60CMtYl83Q16ucOF1dretDFqJC5kY+aFLvuqfag2+
|
||||
kIaoPV1MnGsxveQyyHdOsEatS5XOv/1OWcmnvePDPxcvb9jCcw==
|
||||
-----END CERTIFICATE-----
|
||||
`)
|
||||
key := []byte(`-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRBi4mxrIgtEiF
|
||||
VPsbUOWK1VJee3WLDeARcll97zUSDPTp9Ru9cXpijUsmbBimp7+ub6F/gU1Rvx7Y
|
||||
74sfYWJ4D0WRV7g/aAo7PDzsD8rG+lXyy2A2CXguWpEKiRtzUDN4T82IuvlNxMNF
|
||||
ebcNRxjFfGKemu0O7aN+0XKxo5mY0C6S8T+IvqpLdfqCBUQMMzu1j2fkVXZ4ngSn
|
||||
QiYp/KmLZplhE7E4r6/nH2vIVEyXjLTuUwO+a1xXjm5diZ8Y0KAb081/3UqT+aEn
|
||||
ERBvHJwnAWhKL1daWNHxqE907XUtcbsOyP9FVtjyTYt0o2y2XoB7doKlt727Y+O/
|
||||
FizVtUm5AgMBAAECggEACak+l5f6Onj+u5vrjc4JyAaXW6ra6loSM9g8Uu3sHukW
|
||||
plwoA7Pzp0u20CAxrP1Gpqw984/hSCCcb0Q2ItWMWLaC/YZni5W2WFnOyo3pzlPa
|
||||
hmH4UNMT+ReCSfF/oW8w69QLcNEMjhfEu0i2iWBygIlA4SoRwC2Db6yEX7nLMwUB
|
||||
6AICid9hfeACNRz/nq5ytdcHdmcB7Ptgb9jLiXr6RZw26g5AsRPHU3LdcyZAOXjP
|
||||
aUHriHuHQFKAVkoEUxslvCB6ePCTCpB0bSAuzQbeGoY8fmvmNSCvJ1vrH5hiSUYp
|
||||
Axtl5iNgFl5o9obb0eBYlY9x3pMSz0twdbCwfR7HAQKBgQDtWhmFm0NaJALoY+tq
|
||||
lIIC0EOMSrcRIlgeXr6+g8womuDOMi5m/Nr5Mqt4mPOdP4HytrQb+a/ZmEm17KHh
|
||||
mQb1vwH8ffirCBHbPNC1vwSNoxDKv9E6OysWlKiOzxPFSVZr3dKl2EMX6qi17n0l
|
||||
LBrGXXaNPgYiHSmwBA5CZvvouQKBgQDhclGJfZfuoubQkUuz8yOA2uxalh/iUmQ/
|
||||
G8ac6/w7dmnL9pXehqCWh06SeC3ZvW7yrf7IIGx4sTJji2FzQ+8Ta6pPELMyBEXr
|
||||
1VirIFrlNVMlMQEbZcbzdzEhchM1RUpZJtl3b4amvH21UcRB69d9klcDRisKoFRm
|
||||
k0P9QLHpAQKBgQDh5J9nphZa4u0ViYtTW1XFIbs3+R/0IbCl7tww67TRbF3KQL4i
|
||||
7EHna88ALumkXf3qJvKRsXgoaqS0jSqgUAjst8ZHLQkOldaQxneIkezedDSWEisp
|
||||
9YgTrJYjnHefiyXB8VL63jE0wPOiewEF8Mzmv6sFz+L8cq7rQ2Di16qmmQKBgQDH
|
||||
bvCwVxkrMpJK2O2GH8U9fOzu6bUE6eviY/jb4mp8U7EdjGJhuuieoM2iBoxQ/SID
|
||||
rmYftYcfcWlo4+juJZ99p5W+YcCTs3IDQPUyVOnzr6uA0Avxp6RKxhsBQj+5tTUj
|
||||
Dpn77P3JzB7MYqvhwPcdD3LH46+5s8FWCFpx02RPAQKBgARbngtggfifatcsMC7n
|
||||
lSv/FVLH7LYQAHdoW/EH5Be7FeeP+eQvGXwh1dgl+u0VZO8FvI8RwFganpBRR2Nc
|
||||
ZSBRIb0fSUlTvIsckSWjpEvUJUomJXyi4PIZAfNvd9/u1uLInQiCDtObwb6hnLTU
|
||||
FHHEZ+dR4eMaJp6PhNm8hu2O
|
||||
-----END PRIVATE KEY-----
|
||||
`)
|
||||
|
||||
kp, err := tls.X509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
kp.Leaf, err = x509.ParseCertificate(kp.Certificate[0])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return kp
|
||||
}()
|
||||
|
||||
func hello(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello, %s!", samlsp.AttributeFromContext(r.Context(), "UserName"))
|
||||
}
|
||||
|
||||
func main() {
|
||||
apiURL := os.Getenv("API_URL")
|
||||
pat := readPAT(os.Getenv("PAT_FILE"))
|
||||
domain := os.Getenv("API_DOMAIN")
|
||||
loginURL := os.Getenv("LOGIN_URL")
|
||||
idpURL := os.Getenv("IDP_URL")
|
||||
host := os.Getenv("HOST")
|
||||
port := os.Getenv("PORT")
|
||||
|
||||
idpMetadataURL, err := url.Parse(idpURL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
idpMetadata, err := samlsp.FetchMetadata(context.Background(), http.DefaultClient,
|
||||
*idpMetadataURL)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to fetch IDP metadata from %s: %w", idpURL, err))
|
||||
}
|
||||
fmt.Printf("idpMetadata: %+v\n", idpMetadata)
|
||||
rootURL, err := url.Parse(host + ":" + port)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
samlSP, err := samlsp.New(samlsp.Options{
|
||||
URL: *rootURL,
|
||||
Key: keyPair.PrivateKey.(*rsa.PrivateKey),
|
||||
Certificate: keyPair.Leaf,
|
||||
IDPMetadata: idpMetadata,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
server := &http.Server{
|
||||
Addr: ":" + port,
|
||||
}
|
||||
app := http.HandlerFunc(hello)
|
||||
http.Handle("/hello", samlSP.RequireAccount(app))
|
||||
http.Handle("/saml/", samlSP)
|
||||
go func() {
|
||||
if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalf("HTTP server error: %v", err)
|
||||
}
|
||||
log.Println("Stopped serving new connections.")
|
||||
}()
|
||||
|
||||
metadata, err := xml.MarshalIndent(samlSP.ServiceProvider.Metadata(), "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := createZitadelResources(apiURL, pat, domain, metadata, loginURL); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
http.Handle("/healthy", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return }))
|
||||
fmt.Println("/healthy returns 200 OK")
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigChan
|
||||
|
||||
shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer shutdownRelease()
|
||||
|
||||
if err := server.Shutdown(shutdownCtx); err != nil {
|
||||
log.Fatalf("HTTP shutdown error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func readPAT(path string) string {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pat, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return strings.Trim(string(pat), "\n")
|
||||
}
|
||||
|
||||
func createZitadelResources(apiURL, pat, domain string, metadata []byte, loginURL string) error {
|
||||
projectID, err := CreateProject(apiURL, pat, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return CreateApp(apiURL, pat, domain, projectID, metadata, loginURL)
|
||||
}
|
||||
|
||||
type project struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
type createProject struct {
|
||||
Name string `json:"name"`
|
||||
ProjectRoleAssertion bool `json:"projectRoleAssertion"`
|
||||
ProjectRoleCheck bool `json:"projectRoleCheck"`
|
||||
HasProjectCheck bool `json:"hasProjectCheck"`
|
||||
PrivateLabelingSetting string `json:"privateLabelingSetting"`
|
||||
}
|
||||
|
||||
func CreateProject(apiURL, pat, domain string) (string, error) {
|
||||
createProject := &createProject{
|
||||
Name: "SAML",
|
||||
ProjectRoleAssertion: false,
|
||||
ProjectRoleCheck: false,
|
||||
HasProjectCheck: false,
|
||||
PrivateLabelingSetting: "PRIVATE_LABELING_SETTING_UNSPECIFIED",
|
||||
}
|
||||
resp, err := doRequestWithHeaders(apiURL+"/management/v1/projects", pat, domain, createProject)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
p := new(project)
|
||||
if err := json.Unmarshal(data, p); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return p.ID, nil
|
||||
}
|
||||
|
||||
type createApp struct {
|
||||
Name string `json:"name"`
|
||||
MetadataXml string `json:"metadataXml"`
|
||||
LoginVersion version `json:"loginVersion"`
|
||||
}
|
||||
type version struct {
|
||||
LoginV2 loginV2 `json:"loginV2"`
|
||||
}
|
||||
type loginV2 struct {
|
||||
BaseUri string `json:"baseUri"`
|
||||
}
|
||||
|
||||
func CreateApp(apiURL, pat, domain, projectID string, spMetadata []byte, loginURL string) error {
|
||||
encoded := make([]byte, base64.URLEncoding.EncodedLen(len(spMetadata)))
|
||||
base64.URLEncoding.Encode(encoded, spMetadata)
|
||||
|
||||
createApp := &createApp{
|
||||
Name: "SAML",
|
||||
MetadataXml: string(encoded),
|
||||
LoginVersion: version{
|
||||
LoginV2: loginV2{
|
||||
BaseUri: loginURL,
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err := doRequestWithHeaders(apiURL+"/management/v1/projects/"+projectID+"/apps/saml", pat, domain, createApp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating saml app with request %+v: %v", *createApp, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func doRequestWithHeaders(apiURL, pat, domain string, body any) (*http.Response, error) {
|
||||
data, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(data)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values := http.Header{}
|
||||
values.Add("Authorization", "Bearer "+pat)
|
||||
values.Add("x-forwarded-host", domain)
|
||||
values.Add("Content-Type", "application/json")
|
||||
req.Header = values
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
3
apps/login/acceptance/setup/go.mod
Normal file
3
apps/login/acceptance/setup/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module github.com/zitadel/typescript/apps/login-test-acceptance/setup
|
||||
|
||||
go 1.23.3
|
0
apps/login/acceptance/setup/go.sum
Normal file
0
apps/login/acceptance/setup/go.sum
Normal file
3
apps/login/acceptance/setup/main.go
Normal file
3
apps/login/acceptance/setup/main.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package main
|
||||
|
||||
func main() {}
|
139
apps/login/acceptance/setup/setup.sh
Executable file
139
apps/login/acceptance/setup/setup.sh
Executable file
@@ -0,0 +1,139 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e pipefail
|
||||
|
||||
PAT_FILE=${PAT_FILE:-./pat/zitadel-admin-sa.pat}
|
||||
LOGIN_BASE_URL=${LOGIN_BASE_URL:-"http://localhost:3000"}
|
||||
ZITADEL_API_PROTOCOL="${ZITADEL_API_PROTOCOL:-http}"
|
||||
ZITADEL_API_DOMAIN="${ZITADEL_API_DOMAIN:-localhost}"
|
||||
ZITADEL_API_PORT="${ZITADEL_API_PORT:-8080}"
|
||||
ZITADEL_API_URL="${ZITADEL_API_URL:-${ZITADEL_API_PROTOCOL}://${ZITADEL_API_DOMAIN}:${ZITADEL_API_PORT}}"
|
||||
ZITADEL_API_INTERNAL_URL="${ZITADEL_API_INTERNAL_URL:-${ZITADEL_API_URL}}"
|
||||
SINK_EMAIL_INTERNAL_URL="${SINK_EMAIL_INTERNAL_URL:-"http://sink:3333/email"}"
|
||||
SINK_SMS_INTERNAL_URL="${SINK_SMS_INTERNAL_URL:-"http://sink:3333/sms"}"
|
||||
SINK_NOTIFICATION_URL="${SINK_NOTIFICATION_URL:-"http://localhost:3333/notification"}"
|
||||
WRITE_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login/.env.test.local}
|
||||
|
||||
if [ -z "${PAT}" ]; then
|
||||
echo "Reading PAT from file ${PAT_FILE}"
|
||||
PAT=$(cat ${PAT_FILE})
|
||||
fi
|
||||
|
||||
#################################################################
|
||||
# ServiceAccount as Login Client
|
||||
#################################################################
|
||||
|
||||
SERVICEACCOUNT_RESPONSE=$(curl -s --request POST \
|
||||
--url "${ZITADEL_API_INTERNAL_URL}/management/v1/users/machine" \
|
||||
--header "Authorization: Bearer ${PAT}" \
|
||||
--header "Host: ${ZITADEL_API_DOMAIN}" \
|
||||
--header "Content-Type: application/json" \
|
||||
-d "{\"userName\": \"login\", \"name\": \"Login v2\", \"description\": \"Serviceaccount for Login v2\", \"accessTokenType\": \"ACCESS_TOKEN_TYPE_BEARER\"}")
|
||||
echo "Received ServiceAccount response: ${SERVICEACCOUNT_RESPONSE}"
|
||||
|
||||
SERVICEACCOUNT_ID=$(echo ${SERVICEACCOUNT_RESPONSE} | jq -r '. | .userId')
|
||||
echo "Received ServiceAccount ID: ${SERVICEACCOUNT_ID}"
|
||||
|
||||
MEMBER_RESPONSE=$(curl -s --request POST \
|
||||
--url "${ZITADEL_API_INTERNAL_URL}/admin/v1/members" \
|
||||
--header "Authorization: Bearer ${PAT}" \
|
||||
--header "Host: ${ZITADEL_API_DOMAIN}" \
|
||||
--header "Content-Type: application/json" \
|
||||
-d "{\"userId\": \"${SERVICEACCOUNT_ID}\", \"roles\": [\"IAM_LOGIN_CLIENT\"]}")
|
||||
echo "Received Member response: ${MEMBER_RESPONSE}"
|
||||
|
||||
SA_PAT_RESPONSE=$(curl -s --request POST \
|
||||
--url "${ZITADEL_API_INTERNAL_URL}/management/v1/users/${SERVICEACCOUNT_ID}/pats" \
|
||||
--header "Authorization: Bearer ${PAT}" \
|
||||
--header "Host: ${ZITADEL_API_DOMAIN}" \
|
||||
--header "Content-Type: application/json" \
|
||||
-d "{\"expirationDate\": \"2519-04-01T08:45:00.000000Z\"}")
|
||||
echo "Received Member response: ${MEMBER_RESPONSE}"
|
||||
|
||||
SA_PAT=$(echo ${SA_PAT_RESPONSE} | jq -r '. | .token')
|
||||
echo "Received ServiceAccount Token: ${SA_PAT}"
|
||||
|
||||
#################################################################
|
||||
# Environment files
|
||||
#################################################################
|
||||
|
||||
echo "Writing environment file ${WRITE_ENVIRONMENT_FILE}."
|
||||
|
||||
echo "ZITADEL_API_URL=${ZITADEL_API_URL}
|
||||
ZITADEL_SERVICE_USER_TOKEN=${SA_PAT}
|
||||
ZITADEL_ADMIN_TOKEN=${PAT}
|
||||
SINK_NOTIFICATION_URL=${SINK_NOTIFICATION_URL}
|
||||
EMAIL_VERIFICATION=true
|
||||
DEBUG=false
|
||||
LOGIN_BASE_URL=${LOGIN_BASE_URL}
|
||||
NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||
ZITADEL_ADMIN_USER=${ZITADEL_ADMIN_USER:-"zitadel-admin@zitadel.localhost"}
|
||||
NEXT_PUBLIC_BASE_PATH=/ui/v2/login
|
||||
" > ${WRITE_ENVIRONMENT_FILE}
|
||||
|
||||
echo "Wrote environment file ${WRITE_ENVIRONMENT_FILE}"
|
||||
cat ${WRITE_ENVIRONMENT_FILE}
|
||||
|
||||
#################################################################
|
||||
# SMS provider with HTTP
|
||||
#################################################################
|
||||
|
||||
SMSHTTP_RESPONSE=$(curl -s --request POST \
|
||||
--url "${ZITADEL_API_INTERNAL_URL}/admin/v1/sms/http" \
|
||||
--header "Authorization: Bearer ${PAT}" \
|
||||
--header "Host: ${ZITADEL_API_DOMAIN}" \
|
||||
--header "Content-Type: application/json" \
|
||||
-d "{\"endpoint\": \"${SINK_SMS_INTERNAL_URL}\", \"description\": \"test\"}")
|
||||
echo "Received SMS HTTP response: ${SMSHTTP_RESPONSE}"
|
||||
|
||||
SMSHTTP_ID=$(echo ${SMSHTTP_RESPONSE} | jq -r '. | .id')
|
||||
echo "Received SMS HTTP ID: ${SMSHTTP_ID}"
|
||||
|
||||
SMS_ACTIVE_RESPONSE=$(curl -s --request POST \
|
||||
--url "${ZITADEL_API_INTERNAL_URL}/admin/v1/sms/${SMSHTTP_ID}/_activate" \
|
||||
--header "Authorization: Bearer ${PAT}" \
|
||||
--header "Host: ${ZITADEL_API_DOMAIN}" \
|
||||
--header "Content-Type: application/json")
|
||||
echo "Received SMS active response: ${SMS_ACTIVE_RESPONSE}"
|
||||
|
||||
#################################################################
|
||||
# Email provider with HTTP
|
||||
#################################################################
|
||||
|
||||
EMAILHTTP_RESPONSE=$(curl -s --request POST \
|
||||
--url "${ZITADEL_API_INTERNAL_URL}/admin/v1/email/http" \
|
||||
--header "Authorization: Bearer ${PAT}" \
|
||||
--header "Host: ${ZITADEL_API_DOMAIN}" \
|
||||
--header "Content-Type: application/json" \
|
||||
-d "{\"endpoint\": \"${SINK_EMAIL_INTERNAL_URL}\", \"description\": \"test\"}")
|
||||
echo "Received Email HTTP response: ${EMAILHTTP_RESPONSE}"
|
||||
|
||||
EMAILHTTP_ID=$(echo ${EMAILHTTP_RESPONSE} | jq -r '. | .id')
|
||||
echo "Received Email HTTP ID: ${EMAILHTTP_ID}"
|
||||
|
||||
EMAIL_ACTIVE_RESPONSE=$(curl -s --request POST \
|
||||
--url "${ZITADEL_API_INTERNAL_URL}/admin/v1/email/${EMAILHTTP_ID}/_activate" \
|
||||
--header "Authorization: Bearer ${PAT}" \
|
||||
--header "Host: ${ZITADEL_API_DOMAIN}" \
|
||||
--header "Content-Type: application/json")
|
||||
echo "Received Email active response: ${EMAIL_ACTIVE_RESPONSE}"
|
||||
|
||||
#################################################################
|
||||
# Wait for projection of default organization in ZITADEL
|
||||
#################################################################
|
||||
|
||||
DEFAULTORG_RESPONSE_RESULTS=0
|
||||
# waiting for default organization
|
||||
until [ ${DEFAULTORG_RESPONSE_RESULTS} -eq 1 ]
|
||||
do
|
||||
DEFAULTORG_RESPONSE=$(curl -s --request POST \
|
||||
--url "${ZITADEL_API_INTERNAL_URL}/v2/organizations/_search" \
|
||||
--header "Authorization: Bearer ${PAT}" \
|
||||
--header "Host: ${ZITADEL_API_DOMAIN}" \
|
||||
--header "Content-Type: application/json" \
|
||||
-d "{\"queries\": [{\"defaultQuery\":{}}]}" )
|
||||
echo "Received default organization response: ${DEFAULTORG_RESPONSE}"
|
||||
DEFAULTORG_RESPONSE_RESULTS=$(echo $DEFAULTORG_RESPONSE | jq -r '.result | length')
|
||||
echo "Received default organization response result: ${DEFAULTORG_RESPONSE_RESULTS}"
|
||||
done
|
||||
|
3
apps/login/acceptance/sink/go.mod
Normal file
3
apps/login/acceptance/sink/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module github.com/zitadel/typescript/acceptance/sink
|
||||
|
||||
go 1.24.0
|
0
apps/login/acceptance/sink/go.sum
Normal file
0
apps/login/acceptance/sink/go.sum
Normal file
111
apps/login/acceptance/sink/main.go
Normal file
111
apps/login/acceptance/sink/main.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type serializableData struct {
|
||||
ContextInfo map[string]interface{} `json:"contextInfo,omitempty"`
|
||||
Args map[string]interface{} `json:"args,omitempty"`
|
||||
}
|
||||
|
||||
type response struct {
|
||||
Recipient string `json:"recipient,omitempty"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
port := flag.String("port", "3333", "used port for the sink")
|
||||
email := flag.String("email", "/email", "path for a sent email")
|
||||
emailKey := flag.String("email-key", "recipientEmailAddress", "value in the sent context info of the email used as key to retrieve the notification")
|
||||
sms := flag.String("sms", "/sms", "path for a sent sms")
|
||||
smsKey := flag.String("sms-key", "recipientPhoneNumber", "value in the sent context info of the sms used as key to retrieve the notification")
|
||||
notification := flag.String("notification", "/notification", "path to receive the notification")
|
||||
flag.Parse()
|
||||
|
||||
messages := make(map[string]serializableData)
|
||||
|
||||
http.HandleFunc(*email, func(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
serializableData := serializableData{}
|
||||
if err := json.Unmarshal(data, &serializableData); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
email, ok := serializableData.ContextInfo[*emailKey].(string)
|
||||
if !ok {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
fmt.Println(email + ": " + string(data))
|
||||
messages[email] = serializableData
|
||||
io.WriteString(w, "Email!\n")
|
||||
})
|
||||
|
||||
http.HandleFunc(*sms, func(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
serializableData := serializableData{}
|
||||
if err := json.Unmarshal(data, &serializableData); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
phone, ok := serializableData.ContextInfo[*smsKey].(string)
|
||||
if !ok {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
fmt.Println(phone + ": " + string(data))
|
||||
messages[phone] = serializableData
|
||||
io.WriteString(w, "SMS!\n")
|
||||
})
|
||||
|
||||
http.HandleFunc(*notification, func(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
response := response{}
|
||||
if err := json.Unmarshal(data, &response); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
msg, ok := messages[response.Recipient]
|
||||
if !ok {
|
||||
http.Error(w, "No messages found for recipient: "+response.Recipient, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
serializableData, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
io.WriteString(w, string(serializableData))
|
||||
})
|
||||
|
||||
fmt.Println("Starting server on", *port)
|
||||
fmt.Println(*email, " for email handling")
|
||||
fmt.Println(*sms, " for sms handling")
|
||||
fmt.Println(*notification, " for retrieving notifications")
|
||||
http.Handle("/healthy", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return }))
|
||||
fmt.Println("/healthy returns 200 OK")
|
||||
err := http.ListenAndServe(":"+*port, nil)
|
||||
if err != nil {
|
||||
panic("Server could not be started: " + err.Error())
|
||||
}
|
||||
}
|
3
apps/login/acceptance/test-results/.gitignore
vendored
Normal file
3
apps/login/acceptance/test-results/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*
|
||||
!.gitignore
|
||||
!.gitkeep
|
0
apps/login/acceptance/test-results/.gitkeep
Normal file
0
apps/login/acceptance/test-results/.gitkeep
Normal file
7
apps/login/acceptance/tests/admin.spec.ts
Normal file
7
apps/login/acceptance/tests/admin.spec.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { test } from "@playwright/test";
|
||||
import { loginScreenExpect, loginWithPassword } from "./login";
|
||||
|
||||
test("admin login", async ({ page }) => {
|
||||
await loginWithPassword(page, process.env["ZITADEL_ADMIN_USER"], "Password1!");
|
||||
await loginScreenExpect(page, "ZITADEL Admin");
|
||||
});
|
12
apps/login/acceptance/tests/code-screen.ts
Normal file
12
apps/login/acceptance/tests/code-screen.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { expect, Page } from "@playwright/test";
|
||||
|
||||
const codeTextInput = "code-text-input";
|
||||
|
||||
export async function codeScreen(page: Page, code: string) {
|
||||
await page.getByTestId(codeTextInput).pressSequentially(code);
|
||||
}
|
||||
|
||||
export async function codeScreenExpect(page: Page, code: string) {
|
||||
await expect(page.getByTestId(codeTextInput)).toHaveValue(code);
|
||||
await expect(page.getByTestId("error").locator("div")).toContainText("Could not verify OTP code");
|
||||
}
|
17
apps/login/acceptance/tests/code.ts
Normal file
17
apps/login/acceptance/tests/code.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Page } from "@playwright/test";
|
||||
import { codeScreen } from "./code-screen";
|
||||
import { getOtpFromSink } from "./sink";
|
||||
|
||||
export async function otpFromSink(page: Page, key: string) {
|
||||
const c = await getOtpFromSink(key);
|
||||
await code(page, c);
|
||||
}
|
||||
|
||||
export async function code(page: Page, code: string) {
|
||||
await codeScreen(page, code);
|
||||
await page.getByTestId("submit-button").click();
|
||||
}
|
||||
|
||||
export async function codeResend(page: Page) {
|
||||
await page.getByTestId("resend-button").click();
|
||||
}
|
12
apps/login/acceptance/tests/email-verify-screen.ts
Normal file
12
apps/login/acceptance/tests/email-verify-screen.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { expect, Page } from "@playwright/test";
|
||||
|
||||
const codeTextInput = "code-text-input";
|
||||
|
||||
export async function emailVerifyScreen(page: Page, code: string) {
|
||||
await page.getByTestId(codeTextInput).pressSequentially(code);
|
||||
}
|
||||
|
||||
export async function emailVerifyScreenExpect(page: Page, code: string) {
|
||||
await expect(page.getByTestId(codeTextInput)).toHaveValue(code);
|
||||
await expect(page.getByTestId("error").locator("div")).toContainText("Could not verify email");
|
||||
}
|
69
apps/login/acceptance/tests/email-verify.spec.ts
Normal file
69
apps/login/acceptance/tests/email-verify.spec.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { test as base } from "@playwright/test";
|
||||
import dotenv from "dotenv";
|
||||
import path from "path";
|
||||
import { emailVerify, emailVerifyResend } from "./email-verify";
|
||||
import { emailVerifyScreenExpect } from "./email-verify-screen";
|
||||
import { loginScreenExpect, loginWithPassword } from "./login";
|
||||
import { getCodeFromSink } from "./sink";
|
||||
import { PasswordUser } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasswordUser }>({
|
||||
user: async ({ page }, use) => {
|
||||
const user = new PasswordUser({
|
||||
email: faker.internet.email(),
|
||||
isEmailVerified: false,
|
||||
firstName: faker.person.firstName(),
|
||||
lastName: faker.person.lastName(),
|
||||
organization: "",
|
||||
phone: faker.phone.number(),
|
||||
isPhoneVerified: false,
|
||||
password: "Password1!",
|
||||
passwordChangeRequired: false,
|
||||
});
|
||||
await user.ensure(page);
|
||||
await use(user);
|
||||
await user.cleanup();
|
||||
},
|
||||
});
|
||||
|
||||
test("user email not verified, verify", async ({ user, page }) => {
|
||||
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||
const c = await getCodeFromSink(user.getUsername());
|
||||
await emailVerify(page, c);
|
||||
// wait for resend of the code
|
||||
await page.waitForTimeout(2000);
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
});
|
||||
|
||||
test("user email not verified, resend, verify", async ({ user, page }) => {
|
||||
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||
// auto-redirect on /verify
|
||||
await emailVerifyResend(page);
|
||||
const c = await getCodeFromSink(user.getUsername());
|
||||
// wait for resend of the code
|
||||
await page.waitForTimeout(2000);
|
||||
await emailVerify(page, c);
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
});
|
||||
|
||||
test("user email not verified, resend, old code", async ({ user, page }) => {
|
||||
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||
const c = await getCodeFromSink(user.getUsername());
|
||||
await emailVerifyResend(page);
|
||||
// wait for resend of the code
|
||||
await page.waitForTimeout(2000);
|
||||
await emailVerify(page, c);
|
||||
await emailVerifyScreenExpect(page, c);
|
||||
});
|
||||
|
||||
test("user email not verified, wrong code", async ({ user, page }) => {
|
||||
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||
// auto-redirect on /verify
|
||||
const code = "wrong";
|
||||
await emailVerify(page, code);
|
||||
await emailVerifyScreenExpect(page, code);
|
||||
});
|
15
apps/login/acceptance/tests/email-verify.ts
Normal file
15
apps/login/acceptance/tests/email-verify.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Page } from "@playwright/test";
|
||||
import { emailVerifyScreen } from "./email-verify-screen";
|
||||
|
||||
export async function startEmailVerify(page: Page, loginname: string) {
|
||||
await page.goto("./verify");
|
||||
}
|
||||
|
||||
export async function emailVerify(page: Page, code: string) {
|
||||
await emailVerifyScreen(page, code);
|
||||
await page.getByTestId("submit-button").click();
|
||||
}
|
||||
|
||||
export async function emailVerifyResend(page: Page) {
|
||||
await page.getByTestId("resend-button").click();
|
||||
}
|
102
apps/login/acceptance/tests/idp-apple.spec.ts
Normal file
102
apps/login/acceptance/tests/idp-apple.spec.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
// Note for all tests, in case Apple doesn't deliver all relevant information per default
|
||||
// We should add an action in the needed cases
|
||||
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with Apple IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given an Apple IDP is configured on the organization
|
||||
// Given the user has an Apple added as auth method
|
||||
// User authenticates with Apple
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with Apple IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given an Apple IDP is configured on the organization
|
||||
// Given the user has an Apple added as auth method
|
||||
// User is redirected to Apple
|
||||
// User authenticates with Apple and gets an error
|
||||
// User is redirect back to login
|
||||
// An error is shown to the user "Something went wrong in Apple Login"
|
||||
});
|
||||
|
||||
test("login with Apple IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Apple is configure on the organization as only authencation method
|
||||
// Given idp Apple is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Apple
|
||||
// User authenticates in Apple
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Apple IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Apple is configure on the organization as only authencation method
|
||||
// Given idp Apple is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Apple
|
||||
// User authenticates in Apple
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Apple IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp Apple is configure on the organization as only authencation method
|
||||
// Given idp Apple is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Apple
|
||||
// User authenticates in Apple
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with Apple IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Apple is configure on the organization as only authencation method
|
||||
// Given idp Apple is configure with account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to Apple
|
||||
// User authenticates in Apple with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Apple IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Apple is configure on the organization as only authencation method
|
||||
// Given idp Apple is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Apple
|
||||
// User authenticates in Apple with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with Apple IDP, no user linked, user link successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Apple is configure on the organization as only authencation method
|
||||
// Given idp Apple is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Apple
|
||||
// User authenticates in Apple with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
99
apps/login/acceptance/tests/idp-generic-jwt.spec.ts
Normal file
99
apps/login/acceptance/tests/idp-generic-jwt.spec.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with Generic JWT IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a Generic JWT IDP is configured on the organization
|
||||
// Given the user has Generic JWT IDP added as auth method
|
||||
// User authenticates with the Generic JWT IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with Generic JWT IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the Generic JWT IDP is configured on the organization
|
||||
// Given the user has Generic JWT IDP added as auth method
|
||||
// User is redirected to the Generic JWT IDP
|
||||
// User authenticates with the Generic JWT IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with Generic JWT IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic JWT is configure on the organization as only authencation method
|
||||
// Given idp Generic JWT is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Generic JWT
|
||||
// User authenticates in Generic JWT
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Generic JWT IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic JWT is configure on the organization as only authencation method
|
||||
// Given idp Generic JWT is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Generic JWT
|
||||
// User authenticates in Generic JWT
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Generic JWT IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp Generic JWT is configure on the organization as only authencation method
|
||||
// Given idp Generic JWT is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Generic JWT
|
||||
// User authenticates in Generic JWT
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with Generic JWT IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic JWT is configure on the organization as only authencation method
|
||||
// Given idp Generic JWT is configure with account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to Generic JWT
|
||||
// User authenticates in Generic JWT with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Generic JWT IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic JWT is configure on the organization as only authencation method
|
||||
// Given idp Generic JWT is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Generic JWT
|
||||
// User authenticates in Generic JWT with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with Generic JWT IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic JWT is configure on the organization as only authencation method
|
||||
// Given idp Generic JWT is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Generic JWT
|
||||
// User authenticates in Generic JWT with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
99
apps/login/acceptance/tests/idp-generic-oauth.spec.ts
Normal file
99
apps/login/acceptance/tests/idp-generic-oauth.spec.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with Generic OAuth IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a Generic OAuth IDP is configured on the organization
|
||||
// Given the user has Generic OAuth IDP added as auth method
|
||||
// User authenticates with the Generic OAuth IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with Generic OAuth IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the Generic OAuth IDP is configured on the organization
|
||||
// Given the user has Generic OAuth IDP added as auth method
|
||||
// User is redirected to the Generic OAuth IDP
|
||||
// User authenticates with the Generic OAuth IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with Generic OAuth IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OAuth is configure on the organization as only authencation method
|
||||
// Given idp Generic OAuth is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Generic OAuth
|
||||
// User authenticates in Generic OAuth
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Generic OAuth IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OAuth is configure on the organization as only authencation method
|
||||
// Given idp Generic OAuth is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Generic OAuth
|
||||
// User authenticates in Generic OAuth
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Generic OAuth IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp Generic OAuth is configure on the organization as only authencation method
|
||||
// Given idp Generic OAuth is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Generic OAuth
|
||||
// User authenticates in Generic OAuth
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with Generic OAuth IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OAuth is configure on the organization as only authencation method
|
||||
// Given idp Generic OAuth is configure with account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to Generic OAuth
|
||||
// User authenticates in Generic OAuth with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Generic OAuth IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OAuth is configure on the organization as only authencation method
|
||||
// Given idp Generic OAuth is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Generic OAuth
|
||||
// User authenticates in Generic OAuth with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with Generic OAuth IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OAuth is configure on the organization as only authencation method
|
||||
// Given idp Generic OAuth is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Generic OAuth
|
||||
// User authenticates in Generic OAuth with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
101
apps/login/acceptance/tests/idp-generic-oidc.spec.ts
Normal file
101
apps/login/acceptance/tests/idp-generic-oidc.spec.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
// Note, we should use a provider such as Google to test this, where we know OIDC standard is properly implemented
|
||||
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with Generic OIDC IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a Generic OIDC IDP is configured on the organization
|
||||
// Given the user has Generic OIDC IDP added as auth method
|
||||
// User authenticates with the Generic OIDC IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with Generic OIDC IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the Generic OIDC IDP is configured on the organization
|
||||
// Given the user has Generic OIDC IDP added as auth method
|
||||
// User is redirected to the Generic OIDC IDP
|
||||
// User authenticates with the Generic OIDC IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with Generic OIDC IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OIDC is configure on the organization as only authencation method
|
||||
// Given idp Generic OIDC is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Generic OIDC
|
||||
// User authenticates in Generic OIDC
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Generic OIDC IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OIDC is configure on the organization as only authencation method
|
||||
// Given idp Generic OIDC is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Generic OIDC
|
||||
// User authenticates in Generic OIDC
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Generic OIDC IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp Generic OIDC is configure on the organization as only authencation method
|
||||
// Given idp Generic OIDC is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Generic OIDC
|
||||
// User authenticates in Generic OIDC
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with Generic OIDC IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OIDC is configure on the organization as only authencation method
|
||||
// Given idp Generic OIDC is configure with account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to Generic OIDC
|
||||
// User authenticates in Generic OIDC with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Generic OIDC IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OIDC is configure on the organization as only authencation method
|
||||
// Given idp Generic OIDC is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Generic OIDC
|
||||
// User authenticates in Generic OIDC with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with Generic OIDC IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OIDC is configure on the organization as only authencation method
|
||||
// Given idp Generic OIDC is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Generic OIDC
|
||||
// User authenticates in Generic OIDC with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
103
apps/login/acceptance/tests/idp-github-enterprise.spec.ts
Normal file
103
apps/login/acceptance/tests/idp-github-enterprise.spec.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with GitHub Enterprise IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a GitHub Enterprise IDP is configured on the organization
|
||||
// Given the user has GitHub Enterprise IDP added as auth method
|
||||
// User authenticates with the GitHub Enterprise IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with GitHub Enterprise IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the GitHub Enterprise IDP is configured on the organization
|
||||
// Given the user has GitHub Enterprise IDP added as auth method
|
||||
// User is redirected to the GitHub Enterprise IDP
|
||||
// User authenticates with the GitHub Enterprise IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with GitHub Enterprise IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub Enterprise is configure on the organization as only authencation method
|
||||
// Given idp GitHub Enterprise is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to GitHub Enterprise
|
||||
// User authenticates in GitHub Enterprise
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with GitHub Enterprise IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub Enterprise is configure on the organization as only authencation method
|
||||
// Given idp GitHub Enterprise is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to GitHub Enterprise
|
||||
// User authenticates in GitHub Enterprise
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with GitHub Enterprise IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp GitHub Enterprise is configure on the organization as only authencation method
|
||||
// Given idp GitHub Enterprise is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to GitHub Enterprise
|
||||
// User authenticates in GitHub Enterprise
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with GitHub Enterprise IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub Enterprise is configure on the organization as only authencation method
|
||||
// Given idp GitHub Enterprise is configure with account linking allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to GitHub Enterprise
|
||||
// User authenticates in GitHub Enterprise with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with GitHub Enterprise IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub Enterprise is configure on the organization as only authencation method
|
||||
// Given idp GitHub Enterprise is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to GitHub Enterprise
|
||||
// User authenticates in GitHub Enterprise with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with GitHub Enterprise IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub Enterprise is configure on the organization as only authencation method
|
||||
// Given idp GitHub Enterprise is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to GitHub Enterprise
|
||||
// User authenticates in GitHub Enterprise with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
103
apps/login/acceptance/tests/idp-github.spec.ts
Normal file
103
apps/login/acceptance/tests/idp-github.spec.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with GitHub IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a GitHub IDP is configured on the organization
|
||||
// Given the user has GitHub IDP added as auth method
|
||||
// User authenticates with the GitHub IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with GitHub IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the GitHub IDP is configured on the organization
|
||||
// Given the user has GitHub IDP added as auth method
|
||||
// User is redirected to the GitHub IDP
|
||||
// User authenticates with the GitHub IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with GitHub IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub is configure on the organization as only authencation method
|
||||
// Given idp GitHub is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to GitHub
|
||||
// User authenticates in GitHub
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with GitHub IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub is configure on the organization as only authencation method
|
||||
// Given idp GitHub is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to GitHub
|
||||
// User authenticates in GitHub
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with GitHub IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp GitHub is configure on the organization as only authencation method
|
||||
// Given idp GitHub is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to GitHub
|
||||
// User authenticates in GitHub
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with GitHub IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub is configure on the organization as only authencation method
|
||||
// Given idp GitHub is configure with account linking allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to GitHub
|
||||
// User authenticates in GitHub with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with GitHub IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub is configure on the organization as only authencation method
|
||||
// Given idp GitHub is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to GitHub
|
||||
// User authenticates in GitHub with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with GitHub IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub is configure on the organization as only authencation method
|
||||
// Given idp GitHub is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to GitHub
|
||||
// User authenticates in GitHub with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
103
apps/login/acceptance/tests/idp-gitlab-self-hosted.spec.ts
Normal file
103
apps/login/acceptance/tests/idp-gitlab-self-hosted.spec.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with GitLab Self-Hosted IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a GitLab Self-Hosted IDP is configured on the organization
|
||||
// Given the user has GitLab Self-Hosted IDP added as auth method
|
||||
// User authenticates with the GitLab Self-Hosted IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with GitLab Self-Hosted IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the GitLab Self-Hosted IDP is configured on the organization
|
||||
// Given the user has GitLab Self-Hosted IDP added as auth method
|
||||
// User is redirected to the GitLab Self-Hosted IDP
|
||||
// User authenticates with the GitLab Self-Hosted IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with Gitlab Self-Hosted IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab Self-Hosted is configure on the organization as only authencation method
|
||||
// Given idp Gitlab Self-Hosted is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Gitlab Self-Hosted
|
||||
// User authenticates in Gitlab Self-Hosted
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Gitlab Self-Hosted IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab Self-Hosted is configure on the organization as only authencation method
|
||||
// Given idp Gitlab Self-Hosted is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Gitlab Self-Hosted
|
||||
// User authenticates in Gitlab Self-Hosted
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Gitlab Self-Hosted IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab Self-Hosted is configure on the organization as only authencation method
|
||||
// Given idp Gitlab Self-Hosted is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Gitlab Self-Hosted
|
||||
// User authenticates in Gitlab Self-Hosted
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with Gitlab Self-Hosted IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab Self-Hosted is configure on the organization as only authencation method
|
||||
// Given idp Gitlab Self-Hosted is configure with account linking allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to Gitlab Self-Hosted
|
||||
// User authenticates in Gitlab Self-Hosted with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Gitlab Self-Hosted IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab Self-Hosted is configure on the organization as only authencation method
|
||||
// Given idp Gitlab Self-Hosted is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Gitlab Self-Hosted
|
||||
// User authenticates in Gitlab Self-Hosted with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with Gitlab Self-Hosted IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab Self-Hosted is configure on the organization as only authencation method
|
||||
// Given idp Gitlab Self-Hosted is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Gitlab Self-Hosted
|
||||
// User authenticates in Gitlab Self-Hosted with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
103
apps/login/acceptance/tests/idp-gitlab.spec.ts
Normal file
103
apps/login/acceptance/tests/idp-gitlab.spec.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with GitLab IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a GitLab IDP is configured on the organization
|
||||
// Given the user has GitLab IDP added as auth method
|
||||
// User authenticates with the GitLab IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with GitLab IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the GitLab IDP is configured on the organization
|
||||
// Given the user has GitLab IDP added as auth method
|
||||
// User is redirected to the GitLab IDP
|
||||
// User authenticates with the GitLab IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with Gitlab IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab is configure on the organization as only authencation method
|
||||
// Given idp Gitlab is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Gitlab
|
||||
// User authenticates in Gitlab
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Gitlab IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab is configure on the organization as only authencation method
|
||||
// Given idp Gitlab is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Gitlab
|
||||
// User authenticates in Gitlab
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Gitlab IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab is configure on the organization as only authencation method
|
||||
// Given idp Gitlab is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Gitlab
|
||||
// User authenticates in Gitlab
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with Gitlab IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab is configure on the organization as only authencation method
|
||||
// Given idp Gitlab is configure with account linking allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to Gitlab
|
||||
// User authenticates in Gitlab with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Gitlab IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab is configure on the organization as only authencation method
|
||||
// Given idp Gitlab is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Gitlab
|
||||
// User authenticates in Gitlab with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with Gitlab IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab is configure on the organization as only authencation method
|
||||
// Given idp Gitlab is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Gitlab
|
||||
// User authenticates in Gitlab with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
99
apps/login/acceptance/tests/idp-google.spec.ts
Normal file
99
apps/login/acceptance/tests/idp-google.spec.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with Google IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a Google IDP is configured on the organization
|
||||
// Given the user has Google IDP added as auth method
|
||||
// User authenticates with the Google IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with Google IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the Google IDP is configured on the organization
|
||||
// Given the user has Google IDP added as auth method
|
||||
// User is redirected to the Google IDP
|
||||
// User authenticates with the Google IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with Google IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Google is configure on the organization as only authencation method
|
||||
// Given idp Google is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Google
|
||||
// User authenticates in Google
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Google IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Google is configure on the organization as only authencation method
|
||||
// Given idp Google is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Google
|
||||
// User authenticates in Google
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Google IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp Google is configure on the organization as only authencation method
|
||||
// Given idp Google is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Google
|
||||
// User authenticates in Google
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with Google IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Google is configure on the organization as only authencation method
|
||||
// Given idp Google is configure with account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to Google
|
||||
// User authenticates in Google with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Google IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Google is configure on the organization as only authencation method
|
||||
// Given idp Google is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Google
|
||||
// User authenticates in Google with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with Google IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Google is configure on the organization as only authencation method
|
||||
// Given idp Google is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Google
|
||||
// User authenticates in Google with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user