mirror of
https://github.com/zitadel/zitadel.git
synced 2025-01-09 10:13:42 +00:00
fix(instance-create): merge back origin/main
This commit is contained in:
commit
39660044ad
84
.github/ISSUE_TEMPLATE/BUG_REPORT.yaml
vendored
Normal file
84
.github/ISSUE_TEMPLATE/BUG_REPORT.yaml
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
name: Bug Report
|
||||
description: Create a report to help us improve ZITADEL
|
||||
title: "[Bug]: "
|
||||
labels: ["type: bug", "state: triage"]
|
||||
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)
|
||||
- type: dropdown
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: How do you use ZITADEL?
|
||||
options:
|
||||
- ZITADEL Cloud
|
||||
- Self-hosted
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of 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. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
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: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: Which version of ZITADEL are you using.
|
||||
- type: textarea
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: Please complete information about your operating-system, device, browser, etc.
|
||||
placeholder: |
|
||||
Device: [e.g. Windows, Mac, iPhone6]
|
||||
OS: [e.g. iOS8.1, Windows]
|
||||
Browser [e.g. chrome, stock browser, safari]
|
||||
Version [e.g. 22]
|
||||
- type: textarea
|
||||
id: config
|
||||
attributes:
|
||||
label: Relevant Configuration
|
||||
description: Add any relevant configurations that could help as. 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.
|
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,38 +0,0 @@
|
||||
---
|
||||
name: "\U0001F41B Bug report"
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: 'state: triage, type: bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -6,3 +6,5 @@ contact_links:
|
||||
- name: ❓ Questions
|
||||
url: https://github.com/zitadel/zitadel/discussions/categories/q-a
|
||||
about: Ask for help in our Q&A discussions
|
||||
- name: 💬 ZITADEL Chat
|
||||
url: https://zitadel.com/chat
|
||||
|
12
.github/workflows/test-code.yml
vendored
12
.github/workflows/test-code.yml
vendored
@ -8,17 +8,17 @@ on:
|
||||
- '**.md'
|
||||
|
||||
jobs:
|
||||
Test:
|
||||
Build-ZITADEL:
|
||||
runs-on: ubuntu-20.04
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
- name: Source checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
@ -33,6 +33,12 @@ jobs:
|
||||
version: v1.10.3
|
||||
- name: Build and Unit Test
|
||||
run: GOOS="linux" GOARCH="amd64" goreleaser build --id prod --snapshot --single-target --rm-dist --output .artifacts/zitadel/zitadel
|
||||
- name: linting
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: v1.50.1
|
||||
only-new-issues: true
|
||||
skip-pkg-cache: true
|
||||
- name: Publish go coverage
|
||||
uses: codecov/codecov-action@v3.1.0
|
||||
with:
|
||||
|
2
.github/workflows/test-docs.yml
vendored
2
.github/workflows/test-docs.yml
vendored
@ -14,7 +14,7 @@ on:
|
||||
- '**.md'
|
||||
|
||||
jobs:
|
||||
Test:
|
||||
Build-ZITADEL:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- run: 'echo "No tests for docs are implemented, yet"'
|
||||
|
303
.golangci.yaml
Normal file
303
.golangci.yaml
Normal file
@ -0,0 +1,303 @@
|
||||
issues:
|
||||
new: true
|
||||
fix: false
|
||||
|
||||
run:
|
||||
concurrency: 2
|
||||
timeout: 10m
|
||||
go: '1.19'
|
||||
skip-dirs:
|
||||
- .artifacts
|
||||
- .backups
|
||||
- .codecov
|
||||
- .github
|
||||
- .keys
|
||||
- .vscode
|
||||
- build
|
||||
- console
|
||||
- deploy
|
||||
- docs
|
||||
- guides
|
||||
- internal/api/ui/login/static
|
||||
- openapi
|
||||
- proto
|
||||
- tools
|
||||
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]
|
||||
- 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
|
||||
# In addition to fixing imports, goimports also formats your code in the same style as gofmt. [fast: true, auto-fix: true]
|
||||
- goimports
|
||||
# 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
|
||||
#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
|
||||
- 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.
|
@ -1,7 +1,8 @@
|
||||
module.exports = {
|
||||
branches: [
|
||||
{name: 'main'},
|
||||
{name: '1.87.x', range: '1.87.x', channel: '1.87.x'}
|
||||
{name: '1.87.x', range: '1.87.x', channel: '1.87.x'},
|
||||
{name: 'startup-times', prerelease: 'beta'}
|
||||
],
|
||||
plugins: [
|
||||
"@semantic-release/commit-analyzer"
|
||||
|
@ -115,6 +115,8 @@ With [goreleaser](https://opencollective.com/goreleaser), you build a debuggable
|
||||
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.
|
||||
|
||||
The commands in this section are tested against the following software versions:
|
||||
|
||||
- [Docker version 20.10.17](https://docs.docker.com/engine/install/)
|
||||
@ -122,8 +124,6 @@ The commands in this section are tested against the following software versions:
|
||||
- [Go version 1.19](https://go.dev/doc/install)
|
||||
- [Delve 1.9.1](https://github.com/go-delve/delve/tree/v1.9.1/Documentation/installation)
|
||||
|
||||
<!-- TODO: Describe linting (@adlerhurst) -->
|
||||
|
||||
Make some changes to the source code, then run the database locally.
|
||||
|
||||
```bash
|
||||
|
93
README.md
93
README.md
@ -4,6 +4,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://bestpractices.coreinfrastructure.org/projects/6662"><img src="https://bestpractices.coreinfrastructure.org/projects/6662/badge"></a>
|
||||
<a href="https://github.com/zitadel/zitadel/graphs/contributors" alt="Release">
|
||||
<img src="https://badgen.net/github/contributors/zitadel/zitadel" /></a>
|
||||
<a href="https://github.com/semantic-release/semantic-release" alt="semantic-release">
|
||||
@ -27,13 +28,16 @@
|
||||
<img src="./docs/static/logos/oidc-cert.png" /></a>
|
||||
</p>
|
||||
|
||||
You want auth that's quickly set up like Auth0 but open source like Keycloak? Look no further — ZITADEL combines the ease of Auth0 and the versatility of Keycloak.
|
||||
Do you look for a user management that's quickly set up like Auth0 and open source like Keycloak?
|
||||
|
||||
We provide a wide range of out of the box features like secure login, self-service, OpenID Connect, OAuth2.x, SAML2, branding, Passwordless with FIDO2, OTP, U2F, and an unlimited audit trail to improve the life of developers. Especially noteworthy is that ZITADEL supports not only B2C and B2E scenarios but also B2B. This is super useful for people who build B2B Solutions, as ZITADEL can handle all the delegated user and access management.
|
||||
Do you have project that requires a multi-tenant user management with self-service for your customers?
|
||||
|
||||
With ZITADEL you rely on a battle tested, hardened and extensible turnkey solution to solve all of your authentication and authorization needs. With the unique way of how ZITADEL stores data it gives you an unlimited audit trail which provides a peace of mind for even the harshest audit and analytics requirements.
|
||||
Look no further — ZITADEL combines the ease of Auth0 with the versatility of Keycloak.
|
||||
|
||||
<!-- TODO: Insert Video here-->
|
||||
We provide you with a wide range of out of the box features to accelerate your project.
|
||||
Multi-tenancy with branding customization, secure login, self-service, OpenID Connect, OAuth2.x, SAML2, Passwordless with FIDO2 (including Passkeys), OTP, U2F, and an unlimited audit trail is there for you, ready to use.
|
||||
|
||||
With ZITADEL you can rely on a hardened and extensible turnkey solution to solve all of your authentication and authorization needs.
|
||||
|
||||
---
|
||||
|
||||
@ -41,34 +45,43 @@ With ZITADEL you rely on a battle tested, hardened and extensible turnkey soluti
|
||||
|
||||
## Get started
|
||||
|
||||
### ZITADEL Cloud (SaaS)
|
||||
### Deploy ZITADEL (Self-Hosted)
|
||||
|
||||
The easiest way to get started with [ZITADEL Cloud](https://zitadel.cloud).
|
||||
Deploying ZITADEL locally takes less than 3 minutes. So go ahead and give it a try!
|
||||
|
||||
It's free for up to 25'000 authenticated requests. Learn more about the [pay-as-you-go pricing](https://zitadel.com/pricing).
|
||||
* [Linux](https://docs.zitadel.com/docs/guides/deploy/linux)
|
||||
* [MacOS](https://docs.zitadel.com/docs/guides/deploy/macos)
|
||||
* [Docker compose](https://docs.zitadel.com/docs/guides/deploy/compose)
|
||||
* [Knative](https://docs.zitadel.com/docs/guides/deploy/knative)
|
||||
* [Kubernetes](https://docs.zitadel.com/docs/guides/deploy/kubernetes)
|
||||
|
||||
### Install ZITADEL
|
||||
See all guides [here](https://docs.zitadel.com/docs/guides/deploy/overview)
|
||||
|
||||
- [We provide installation guides for multiple platforms here](https://docs.zitadel.com/docs/guides/deploy/overview)
|
||||
> If you are interested to get professional support for your self-hosted ZITADEL [please reach out to us](https://zitadel.com/contact)!
|
||||
|
||||
### Quickstarts - Integrate your app
|
||||
### Setup ZITADEL Cloud (SaaS)
|
||||
|
||||
[Multiple Examples can be found here](https://docs.zitadel.com/docs/examples/introduction)
|
||||
If you want to experience a hands-free ZITADEL, you should use [ZITADEL Cloud](https://zitadel.cloud).
|
||||
|
||||
> If you miss something please feel free to [join the Discussion](https://github.com/zitadel/zitadel/discussions/1717)
|
||||
It is free for up to 25'000 authenticated requests and provides you all the features that make ZITADEL great.
|
||||
Learn more about the [pay-as-you-go pricing](https://zitadel.com/pricing).
|
||||
|
||||
## Why ZITADEL
|
||||
## Why choose ZITADEL
|
||||
|
||||
- [API-first](https://docs.zitadel.com/docs/apis/introduction)
|
||||
- Strong audit trail thanks to [event sourcing](https://docs.zitadel.com/docs/concepts/eventstore/overview)
|
||||
- [Actions](https://docs.zitadel.com/docs/concepts/features/actions) to react on events with custom code
|
||||
- [Branding](https://docs.zitadel.com/docs/guides/manage/customize/branding) for a uniform user experience
|
||||
- [CockroachDB](https://www.cockroachlabs.com/) or a Postgres database is the only dependency
|
||||
We built ZITADEL with a complex multi-tenancy architecture in mind and provide the best solution to handle B2B customers and partners.
|
||||
Yet it offers everything you need for a customer identity (CIAM) use case.
|
||||
|
||||
- [API-first approach](https://docs.zitadel.com/docs/apis/introduction)
|
||||
- Strong audit trail thanks to [event sourcing](https://docs.zitadel.com/docs/concepts/eventstore/overview) as storage pattern
|
||||
- [Actions](https://docs.zitadel.com/docs/concepts/features/actions) to react on events with custom code and extended ZITADEL for you needs
|
||||
- [Branding](https://docs.zitadel.com/docs/guides/manage/customize/branding) for a uniform user experience across multiple organizations
|
||||
- [Self-service](https://docs.zitadel.com/docs/concepts/features/selfservice) for end-users, business customers, and administrators
|
||||
- [CockroachDB](https://www.cockroachlabs.com/) or a [Postgres](https://www.postgresql.org/) database as reliable and widespread storage option
|
||||
|
||||
## Features
|
||||
|
||||
- Single Sign On (SSO)
|
||||
- Passwordless with FIDO2 support
|
||||
- Passwordless with FIDO2 support (Including Passkeys)
|
||||
- Username / Password
|
||||
- Multifactor authentication with OTP, U2F
|
||||
- [Identity Brokering](https://docs.zitadel.com/docs/guides/integrate/identity-brokering)
|
||||
@ -76,25 +89,39 @@ It's free for up to 25'000 authenticated requests. Learn more about the [pay-as-
|
||||
- Personal Access Tokens (PAT)
|
||||
- Role Based Access Control (RBAC)
|
||||
- [Delegate role management to third-parties](https://docs.zitadel.com/docs/guides/manage/console/projects)
|
||||
- Self-registration including verification
|
||||
- User self service
|
||||
- [Service Accounts](https://docs.zitadel.com/docs/guides/integrate/serviceusers)
|
||||
- [Self-registration](https://docs.zitadel.com/docs/concepts/features/selfservice#registration) including verification
|
||||
- [Self-service](https://docs.zitadel.com/docs/concepts/features/selfservice) for end-users, business customers, and administrators
|
||||
- [OpenID Connect certified](https://openid.net/certification/#OPs) => [OIDC Endpoints](https://docs.zitadel.com/docs/apis/openidoauth/endpoints), [OIDC Integration Guides](https://docs.zitadel.com/docs/guides/integrate/auth0-oidc)
|
||||
- [SAML 2.0](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) => [SAML Endpoints](https://docs.zitadel.com/docs/apis/saml/endpoints), [SAML Integration Guides](https://docs.zitadel.com/docs/guides/integrate/auth0-saml)
|
||||
- [Postgres](https://docs.zitadel.com/docs/guides/manage/self-hosted/database#postgres) (version >= 14) or [CockroachDB](https://docs.zitadel.com/docs/guides/manage/self-hosted/database#cockroach) (version >= 22.0)
|
||||
|
||||
Track upcoming features on our [roadmap](https://zitadel.com/roadmap).
|
||||
|
||||
## Client libraries
|
||||
## Integrate your application
|
||||
|
||||
<!-- TODO: check other libraries -->
|
||||
### Quickstarts
|
||||
|
||||
| Language | Client | API | Machine auth (\*) | Auth check (\*\*) | Thanks to the maintainers |
|
||||
Get started with your preferred language with our [Quickstarts](https://docs.zitadel.com/docs/examples/introduction).
|
||||
|
||||
> If you miss something please feel free to [join the Discussion](https://github.com/zitadel/zitadel/discussions/1717)
|
||||
|
||||
### Example applications
|
||||
|
||||
Clone one of our [example applications](https://docs.zitadel.com/docs/examples/introduction#clone-a-sample-project) or deploy them directly to Vercel.
|
||||
|
||||
### OpenID Connect RP Libraries
|
||||
|
||||
Use any of the [Open ID Connect certified RP implementations](https://openid.net/developers/certified/) in your preferred language.
|
||||
As certified OpenID Provider, ZITADEL is compatible with any of the implementations.
|
||||
|
||||
### Client libraries
|
||||
|
||||
| Language / Framework | Client | API | Machine auth (\*) | Auth check (\*\*) | Thanks to the maintainers |
|
||||
|----------|--------|--------------|----------|---------|---------------------------|
|
||||
| .NET | [zitadel-net](https://github.com/smartive/zitadel-net) | GRPC | ✔️ | ✔️ | [smartive 👑](https://github.com/smartive/) |
|
||||
| Dart | [zitadel-dart](https://github.com/smartive/zitadel-dart) | GRPC | ✔️ | ❌ | [smartive 👑](https://github.com/smartive/) |
|
||||
| Elixir | [zitadel_api](https://github.com/jshmrtn/zitadel_api) | GRPC | ✔️ | ✔️ | [jshmrtn 🙏🏻](https://github.com/jshmrtn) |
|
||||
| Go | [zitadel-go](https://github.com/zitadel/zitadel-go) | GRPC | ✔️ | ✔️ | ZITADEL |
|
||||
| Go | [zitadel-go](https://github.com/zitadel/zitadel-go) | GRPC | ✔️ | ✔️ | [ZITADEL](https://github.com/zitadel/) |
|
||||
| Rust | [zitadel-rust](https://crates.io/crates/zitadel) | GRPC | ✔️ | ❌ | [smartive 👑](https://github.com/smartive/) |
|
||||
| JVM | 🚧 [WIP](https://github.com/zitadel/zitadel/discussions/3650) | ❓ | ❓ | | TBD |
|
||||
| Python | 🚧 [WIP](https://github.com/zitadel/zitadel/issues/3675) | ❓ | ❓ | | TBD |
|
||||
@ -117,8 +144,6 @@ Made with [contrib.rocks](https://contrib.rocks).
|
||||
|
||||
## Showcase
|
||||
|
||||
<!-- TODO: Replace Images-->
|
||||
|
||||
### Passwordless Login
|
||||
|
||||
Use our login widget to allow easy and secure access to your applications and enjoy all the benefits of passwordless (FIDO 2 / WebAuthN):
|
||||
@ -134,14 +159,7 @@ Use our login widget to allow easy and secure access to your applications and en
|
||||
|
||||
Use [Console](https://docs.zitadel.com/docs/manuals/introduction) or our [APIs](https://docs.zitadel.com/docs/apis/introduction) to setup organizations, projects and applications.
|
||||
|
||||
Register new applications
|
||||
![OIDC-Client-Register](https://user-images.githubusercontent.com/1366906/118765446-62064b80-b87b-11eb-8b24-4f4c365b8c58.gif)
|
||||
|
||||
Delegate the right to assign roles to another organization
|
||||
![projects_create_org_grant](https://user-images.githubusercontent.com/1366906/118766069-39cb1c80-b87c-11eb-84cf-f5becce4e9b6.gif)
|
||||
|
||||
Customize login and console with your design
|
||||
![private_labeling](https://user-images.githubusercontent.com/1366906/123089110-d148ff80-d426-11eb-9598-32b506f6d4fd.gif)
|
||||
[![Console Showcase](http://img.youtube.com/vi/RPpHktAcCtk/0.jpg)](http://www.youtube.com/watch?v=RPpHktAcCtk "Console Showcase")
|
||||
|
||||
## Security
|
||||
|
||||
@ -151,4 +169,5 @@ See the policy [here](./SECURITY.md)
|
||||
|
||||
See the exact licensing terms [here](./LICENSE)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and limitations under the License.
|
||||
|
@ -10,6 +10,10 @@ var (
|
||||
)
|
||||
|
||||
func Version() string {
|
||||
if version != "" {
|
||||
return version
|
||||
}
|
||||
version = Date().Format(time.RFC3339)
|
||||
return version
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,12 @@ Metrics:
|
||||
# Select type otel (OpenTelemetry) or none (disables collection and endpoint)
|
||||
Type: otel
|
||||
|
||||
Tracing:
|
||||
# Choose one in "otel", "google", "log" and "none"
|
||||
Type: none
|
||||
Fraction: 1
|
||||
MetricPrefix: zitadel
|
||||
|
||||
# Port ZITADEL will listen on
|
||||
Port: 8080
|
||||
# Port ZITADEL is exposed on, it can differ from port e.g. if you proxy the traffic
|
||||
@ -220,8 +226,6 @@ SAML:
|
||||
# Company: ZITADEL
|
||||
# EmailAddress: hi@zitadel.com
|
||||
|
||||
|
||||
|
||||
Login:
|
||||
LanguageCookieName: zitadel.login.lang
|
||||
CSRFCookieName: zitadel.login.csrf
|
||||
@ -312,7 +316,7 @@ Actions:
|
||||
# wildcard sub domains are currently unsupported
|
||||
DenyList:
|
||||
- localhost
|
||||
- '127.0.0.1'
|
||||
- "127.0.0.1"
|
||||
|
||||
DefaultInstance:
|
||||
InstanceName:
|
||||
@ -423,7 +427,7 @@ DefaultInstance:
|
||||
BackgroundColor: "#fafafa"
|
||||
WarnColor: "#cd3d56"
|
||||
FontColor: "#000000"
|
||||
PrimaryColorDark: "#bbbafa"
|
||||
PrimaryColorDark: "#2073c4"
|
||||
BackgroundColorDark: "#111827"
|
||||
WarnColorDark: "#ff3b5b"
|
||||
FontColorDark: "#ffffff"
|
||||
|
25
cmd/setup/05.go
Normal file
25
cmd/setup/05.go
Normal file
@ -0,0 +1,25 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed 05.sql
|
||||
lastFailedStmts string
|
||||
)
|
||||
|
||||
type LastFailed struct {
|
||||
dbClient *sql.DB
|
||||
}
|
||||
|
||||
func (mig *LastFailed) Execute(ctx context.Context) error {
|
||||
_, err := mig.dbClient.ExecContext(ctx, lastFailedStmts)
|
||||
return err
|
||||
}
|
||||
|
||||
func (mig *LastFailed) String() string {
|
||||
return "05_last_failed"
|
||||
}
|
11
cmd/setup/05.sql
Normal file
11
cmd/setup/05.sql
Normal file
@ -0,0 +1,11 @@
|
||||
CREATE INDEX instance_id_idx ON adminapi.current_sequences (instance_id);
|
||||
CREATE INDEX instance_id_idx ON auth.current_sequences (instance_id);
|
||||
CREATE INDEX instance_id_idx ON projections.current_sequences (instance_id);
|
||||
|
||||
CREATE INDEX instance_id_idx ON adminapi.failed_events (instance_id);
|
||||
CREATE INDEX instance_id_idx ON auth.failed_events (instance_id);
|
||||
CREATE INDEX instance_id_idx ON projections.failed_events (instance_id);
|
||||
|
||||
ALTER TABLE adminapi.failed_events ADD COLUMN last_failed TIMESTAMPTZ;
|
||||
ALTER TABLE auth.failed_events ADD COLUMN last_failed TIMESTAMPTZ;
|
||||
ALTER TABLE projections.failed_events ADD COLUMN last_failed TIMESTAMPTZ;
|
@ -15,6 +15,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/id"
|
||||
"github.com/zitadel/zitadel/internal/query/projection"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@ -28,6 +29,7 @@ type Config struct {
|
||||
EncryptionKeys *encryptionKeyConfig
|
||||
DefaultInstance command.InstanceSetup
|
||||
Machine *id.Config
|
||||
Projections projection.Config
|
||||
}
|
||||
|
||||
func MustNewConfig(v *viper.Viper) *Config {
|
||||
@ -56,6 +58,7 @@ type Steps struct {
|
||||
s2AssetsTable *AssetTable
|
||||
FirstInstance *FirstInstance
|
||||
s4EventstoreIndexes *EventstoreIndexes
|
||||
s5LastFailed *LastFailed
|
||||
}
|
||||
|
||||
type encryptionKeyConfig struct {
|
||||
|
31
cmd/setup/projections.go
Normal file
31
cmd/setup/projections.go
Normal file
@ -0,0 +1,31 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/query/projection"
|
||||
)
|
||||
|
||||
type projectionTables struct {
|
||||
es *eventstore.Eventstore
|
||||
currentVersion string
|
||||
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func (mig *projectionTables) SetLastExecution(lastRun map[string]interface{}) {
|
||||
mig.currentVersion, _ = lastRun["version"].(string)
|
||||
}
|
||||
|
||||
func (mig *projectionTables) Check() bool {
|
||||
return mig.currentVersion != mig.Version
|
||||
}
|
||||
|
||||
func (mig *projectionTables) Execute(ctx context.Context) error {
|
||||
return projection.Init(ctx)
|
||||
}
|
||||
|
||||
func (mig *projectionTables) String() string {
|
||||
return "projection_tables"
|
||||
}
|
@ -8,11 +8,13 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/cmd/build"
|
||||
"github.com/zitadel/zitadel/cmd/key"
|
||||
"github.com/zitadel/zitadel/cmd/tls"
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/migration"
|
||||
"github.com/zitadel/zitadel/internal/query/projection"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -54,6 +56,7 @@ func Flags(cmd *cobra.Command) {
|
||||
}
|
||||
|
||||
func Setup(config *Config, steps *Steps, masterKey string) {
|
||||
ctx := context.Background()
|
||||
logging.Info("setup started")
|
||||
|
||||
dbClient, err := database.Connect(config.Database, false)
|
||||
@ -79,6 +82,10 @@ func Setup(config *Config, steps *Steps, masterKey string) {
|
||||
steps.FirstInstance.externalPort = config.ExternalPort
|
||||
|
||||
steps.s4EventstoreIndexes = &EventstoreIndexes{dbClient: dbClient, dbType: config.Database.Type()}
|
||||
steps.s5LastFailed = &LastFailed{dbClient: dbClient}
|
||||
|
||||
err = projection.Create(ctx, dbClient, eventstoreClient, config.Projections, nil, nil)
|
||||
logging.OnError(err).Fatal("unable to start projections")
|
||||
|
||||
repeatableSteps := []migration.RepeatableMigration{
|
||||
&externalConfigChange{
|
||||
@ -87,9 +94,12 @@ func Setup(config *Config, steps *Steps, masterKey string) {
|
||||
ExternalPort: config.ExternalPort,
|
||||
ExternalSecure: config.ExternalSecure,
|
||||
},
|
||||
&projectionTables{
|
||||
es: eventstoreClient,
|
||||
Version: build.Version(),
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
err = migration.Migrate(ctx, eventstoreClient, steps.s1ProjectionTable)
|
||||
logging.OnError(err).Fatal("unable to migrate step 1")
|
||||
err = migration.Migrate(ctx, eventstoreClient, steps.s2AssetsTable)
|
||||
@ -98,6 +108,8 @@ func Setup(config *Config, steps *Steps, masterKey string) {
|
||||
logging.OnError(err).Fatal("unable to migrate step 3")
|
||||
err = migration.Migrate(ctx, eventstoreClient, steps.s4EventstoreIndexes)
|
||||
logging.OnError(err).Fatal("unable to migrate step 4")
|
||||
err = migration.Migrate(ctx, eventstoreClient, steps.s5LastFailed)
|
||||
logging.OnError(err).Fatal("unable to migrate step 5")
|
||||
|
||||
for _, repeatableStep := range repeatableSteps {
|
||||
err = migration.Migrate(ctx, eventstoreClient, repeatableStep)
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/cmd/admin"
|
||||
"github.com/zitadel/zitadel/cmd/build"
|
||||
"github.com/zitadel/zitadel/cmd/initialise"
|
||||
"github.com/zitadel/zitadel/cmd/key"
|
||||
"github.com/zitadel/zitadel/cmd/setup"
|
||||
@ -33,6 +34,7 @@ func New(out io.Writer, in io.Reader, args []string) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return errors.New("no additional command provided")
|
||||
},
|
||||
Version: build.Version(),
|
||||
}
|
||||
|
||||
viper.AutomaticEnv()
|
||||
@ -55,6 +57,8 @@ func New(out io.Writer, in io.Reader, args []string) *cobra.Command {
|
||||
key.New(),
|
||||
)
|
||||
|
||||
cmd.InitDefaultVersionFlag()
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,7 @@
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "5mb",
|
||||
"maximumWarning": "6mb",
|
||||
"maximumError": "6mb"
|
||||
},
|
||||
{
|
||||
|
7210
console/package-lock.json
generated
7210
console/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -12,18 +12,18 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^14.2.4",
|
||||
"@angular/animations": "^14.2.9",
|
||||
"@angular/cdk": "^14.2.3",
|
||||
"@angular/common": "^14.2.4",
|
||||
"@angular/compiler": "^14.2.4",
|
||||
"@angular/core": "^14.2.4",
|
||||
"@angular/forms": "^14.2.4",
|
||||
"@angular/common": "^14.2.9",
|
||||
"@angular/compiler": "^14.2.9",
|
||||
"@angular/core": "^14.2.9",
|
||||
"@angular/forms": "^14.2.9",
|
||||
"@angular/material": "^14.2.3",
|
||||
"@angular/material-moment-adapter": "^14.2.3",
|
||||
"@angular/platform-browser": "^14.2.4",
|
||||
"@angular/platform-browser-dynamic": "^14.2.4",
|
||||
"@angular/router": "^14.2.4",
|
||||
"@angular/service-worker": "^14.2.4",
|
||||
"@angular/platform-browser": "^14.2.9",
|
||||
"@angular/platform-browser-dynamic": "^14.2.9",
|
||||
"@angular/router": "^14.2.9",
|
||||
"@angular/service-worker": "^14.2.9",
|
||||
"@ctrl/ngx-codemirror": "^5.1.1",
|
||||
"@grpc/grpc-js": "^1.7.1",
|
||||
"@ngx-translate/core": "^14.0.0",
|
||||
@ -36,7 +36,7 @@
|
||||
"cors": "^2.8.5",
|
||||
"file-saver": "^2.0.5",
|
||||
"google-proto-files": "^3.0.2",
|
||||
"google-protobuf": "^3.19.4",
|
||||
"google-protobuf": "^3.21.2",
|
||||
"grpc-web": "^1.4.1",
|
||||
"libphonenumber-js": "^1.10.6",
|
||||
"material-design-icons-iconfont": "^6.1.1",
|
||||
@ -46,28 +46,28 @@
|
||||
"ngx-quicklink": "^0.3.0",
|
||||
"rxjs": "~7.5.7",
|
||||
"tinycolor2": "^1.4.2",
|
||||
"tslib": "^2.2.0",
|
||||
"tslib": "^2.4.1",
|
||||
"uuid": "^9.0.0",
|
||||
"zone.js": "~0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^14.2.4",
|
||||
"@angular-devkit/build-angular": "^14.2.8",
|
||||
"@angular-eslint/builder": "^14.1.2",
|
||||
"@angular-eslint/eslint-plugin": "^14.1.2",
|
||||
"@angular-eslint/eslint-plugin-template": "^14.1.2",
|
||||
"@angular-eslint/schematics": "^14.1.2",
|
||||
"@angular-eslint/template-parser": "^14.1.2",
|
||||
"@angular/cli": "^14.2.4",
|
||||
"@angular/compiler-cli": "^14.2.4",
|
||||
"@angular/language-service": "^14.2.4",
|
||||
"@angular/cli": "^14.2.8",
|
||||
"@angular/compiler-cli": "^14.2.9",
|
||||
"@angular/language-service": "^14.2.9",
|
||||
"@types/jasmine": "~4.3.0",
|
||||
"@types/jasminewd2": "~2.0.10",
|
||||
"@types/jsonwebtoken": "^8.5.5",
|
||||
"@types/node": "^18.8.1",
|
||||
"@typescript-eslint/eslint-plugin": "5.39.0",
|
||||
"@typescript-eslint/parser": "5.39.0",
|
||||
"@typescript-eslint/parser": "5.42.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.42.0",
|
||||
"codelyzer": "^6.0.0",
|
||||
"eslint": "^8.24.0",
|
||||
"eslint": "^8.26.0",
|
||||
"jasmine-core": "~4.4.0",
|
||||
"jasmine-spec-reporter": "~7.0.0",
|
||||
"karma": "~6.4.1",
|
||||
@ -79,4 +79,4 @@
|
||||
"protractor": "~7.0.0",
|
||||
"typescript": "^4.8.4"
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,9 @@
|
||||
$warn: map-get($theme, warn);
|
||||
$background: map-get($theme, background);
|
||||
$accent: map-get($theme, accent);
|
||||
$primary-color: mat.get-color-from-palette($primary, 500);
|
||||
|
||||
$lighter-primary-color: mat.get-color-from-palette($primary, 300);
|
||||
$darker-primary-color: mat.get-color-from-palette($primary, 700);
|
||||
|
||||
$warn-color: mat.get-color-from-palette($warn, 500);
|
||||
$accent-color: mat.get-color-from-palette($accent, 500);
|
||||
@ -11,6 +13,7 @@
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
$back: map-get($background, background);
|
||||
$secondary-text: map-get($foreground, secondary-text);
|
||||
$link-hover-color: if($is-dark-theme, $lighter-primary-color, $darker-primary-color);
|
||||
|
||||
.footer-wrapper {
|
||||
padding: 2rem;
|
||||
@ -46,7 +49,7 @@
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $primary-color;
|
||||
color: $link-hover-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -114,7 +117,7 @@
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $primary-color;
|
||||
color: $link-hover-color;
|
||||
|
||||
i {
|
||||
visibility: visible;
|
||||
|
@ -284,6 +284,7 @@ export class IdpTableComponent implements OnInit {
|
||||
if (this.isDefault) {
|
||||
return this.addLoginPolicy()
|
||||
.then(() => {
|
||||
this.loginPolicy.isDefault = false;
|
||||
return (this.service as ManagementService).addIDPToLoginPolicy(idp.id, idp.owner).then(() => {
|
||||
this.toast.showInfo('IDP.TOAST.ADDED', true);
|
||||
|
||||
@ -339,6 +340,7 @@ export class IdpTableComponent implements OnInit {
|
||||
if (this.isDefault) {
|
||||
return this.addLoginPolicy()
|
||||
.then(() => {
|
||||
this.loginPolicy.isDefault = false;
|
||||
return (this.service as ManagementService)
|
||||
.removeIDPFromLoginPolicy(idp.id)
|
||||
.then(() => {
|
||||
|
@ -1,6 +1,11 @@
|
||||
<button class="nav-toggle" [ngClass]="{active}" (click)="clicked.emit()">
|
||||
<div class="c_label">
|
||||
<span> {{ label }} </span>
|
||||
<small *ngIf="count" class="count">({{ count }})</small>
|
||||
<div *ngIf="count && count < 100" class="count">
|
||||
<span>{{ count }}</span>
|
||||
</div>
|
||||
<div *ngIf="count && count >= 100" class="count">
|
||||
<span>99+</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
@ -31,6 +31,8 @@
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-family: 'Lato', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
box-sizing: border-box;
|
||||
height: 27px;
|
||||
|
||||
.c_label {
|
||||
display: flex;
|
||||
@ -38,8 +40,19 @@
|
||||
text-align: center;
|
||||
|
||||
.count {
|
||||
display: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 17px;
|
||||
width: 17px;
|
||||
border-radius: 50%;
|
||||
padding: 2px;
|
||||
margin: -2px -9px -2px 8px;
|
||||
margin-left: 6px;
|
||||
background-color: if($is-dark-theme, #ffffff20, #00000020);
|
||||
line-height: 18px;
|
||||
font-size: 11px;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,7 +76,7 @@
|
||||
|
||||
.c_label {
|
||||
.count {
|
||||
display: inline-block;
|
||||
background-color: if($is-dark-theme, #00000020, #ffffff20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,14 +121,14 @@
|
||||
>
|
||||
<div class="c_label">
|
||||
<span> {{ 'MENU.PROJECT' | translate }} </span>
|
||||
<small
|
||||
*ngIf="(mgmtService?.ownedProjectsCount | async) && (mgmtService?.grantedProjectsCount | async)"
|
||||
class="count"
|
||||
>({{
|
||||
((mgmtService.ownedProjectsCount | async) ?? 0) +
|
||||
((mgmtService.grantedProjectsCount | async) ?? 0)
|
||||
}})</small
|
||||
>
|
||||
<ng-container *ngIf="projectcounter | async as count">
|
||||
<div *ngIf="count < 100" class="count">
|
||||
<span>{{ count }}</span>
|
||||
</div>
|
||||
<div *ngIf="count >= 100" class="count">
|
||||
<span>99+</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</a>
|
||||
</ng-template>
|
||||
|
@ -64,6 +64,8 @@
|
||||
margin: 0.25rem 2px;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: 27px;
|
||||
|
||||
.c_label {
|
||||
display: flex;
|
||||
@ -72,7 +74,18 @@
|
||||
|
||||
.count {
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 17px;
|
||||
width: 17px;
|
||||
border-radius: 50%;
|
||||
padding: 2px;
|
||||
margin: -2px -9px -2px 8px;
|
||||
margin-left: 6px;
|
||||
background-color: if($is-dark-theme, #ffffff20, #00000020);
|
||||
line-height: 18px;
|
||||
font-size: 11px;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,6 +110,7 @@
|
||||
.c_label {
|
||||
.count {
|
||||
display: inline-block;
|
||||
background-color: if($is-dark-theme, #00000020, #ffffff20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { HttpClient } from '@angular/common/http';
|
||||
import { Component, ElementRef, Input, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { UntypedFormControl } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { BehaviorSubject, map, Observable, Subject, take } from 'rxjs';
|
||||
import { BehaviorSubject, combineLatest, forkJoin, map, merge, Observable, Subject, switchMap, take } from 'rxjs';
|
||||
import { Org } from 'src/app/proto/generated/zitadel/org_pb';
|
||||
import { User } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
import { AuthenticationService } from 'src/app/services/authentication.service';
|
||||
@ -132,4 +132,15 @@ export class NavComponent implements OnDestroy {
|
||||
public openHelp() {
|
||||
this.shortcutService.openOverviewDialog();
|
||||
}
|
||||
|
||||
public get projectcounter(): Observable<number> {
|
||||
return combineLatest({
|
||||
owned: this.mgmtService.ownedProjectsCount,
|
||||
granted: this.mgmtService.grantedProjectsCount,
|
||||
}).pipe(
|
||||
map(({ owned, granted }) => {
|
||||
return (owned ?? 0) + (granted ?? 0);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
|
||||
import { BehaviorSubject, catchError, finalize, from, map, Observable, of, Subject, switchMap, take, takeUntil } from 'rxjs';
|
||||
import { Org, OrgFieldName, OrgQuery, OrgState } from 'src/app/proto/generated/zitadel/org_pb';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { ThemeService } from 'src/app/services/theme.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
import { PaginatorComponent } from '../paginator/paginator.component';
|
||||
@ -95,7 +94,7 @@ export class OrgTableComponent {
|
||||
|
||||
public refresh(): void {
|
||||
this.requestOrgs$.next({
|
||||
limit: this.paginator.length,
|
||||
limit: this.paginator.pageSize,
|
||||
offset: this.paginator.pageSize * this.paginator.pageIndex,
|
||||
queries: this.searchQueries,
|
||||
});
|
||||
|
@ -34,16 +34,18 @@ export class GeneralSettingsComponent implements OnInit {
|
||||
|
||||
public savePolicy(): void {
|
||||
const prom = this.updateData();
|
||||
this.loading = true;
|
||||
if (prom) {
|
||||
prom
|
||||
.then(() => {
|
||||
this.toast.showInfo('POLICY.LOGIN_POLICY.SAVED', true);
|
||||
this.loading = true;
|
||||
this.loading = false;
|
||||
setTimeout(() => {
|
||||
this.fetchData();
|
||||
}, 2000);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.loading = false;
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
@ -45,11 +45,11 @@ export class LoginPolicyComponent implements OnInit {
|
||||
public InfoSectionType: any = InfoSectionType;
|
||||
public PasswordlessType: any = PasswordlessType;
|
||||
public lifetimeForm: UntypedFormGroup = this.fb.group({
|
||||
passwordCheckLifetime: [{ disabled: true, value: 240 }, [Validators.required]],
|
||||
externalLoginCheckLifetime: [{ disabled: true, value: 12 }, [Validators.required]],
|
||||
mfaInitSkipLifetime: [{ disabled: true, value: 720 }, [Validators.required]],
|
||||
secondFactorCheckLifetime: [{ disabled: true, value: 12 }, [Validators.required]],
|
||||
multiFactorCheckLifetime: [{ disabled: true, value: 12 }, [Validators.required]],
|
||||
passwordCheckLifetime: [{ disabled: true }, [Validators.required]],
|
||||
externalLoginCheckLifetime: [{ disabled: true }, [Validators.required]],
|
||||
mfaInitSkipLifetime: [{ disabled: true }, [Validators.required]],
|
||||
secondFactorCheckLifetime: [{ disabled: true }, [Validators.required]],
|
||||
multiFactorCheckLifetime: [{ disabled: true }, [Validators.required]],
|
||||
});
|
||||
constructor(
|
||||
private toast: ToastService,
|
||||
@ -67,29 +67,29 @@ export class LoginPolicyComponent implements OnInit {
|
||||
this.loading = false;
|
||||
|
||||
this.passwordCheckLifetime?.setValue(
|
||||
this.loginData.passwordCheckLifetime?.seconds ? this.loginData.passwordCheckLifetime?.seconds / 60 / 60 : 240,
|
||||
this.loginData.passwordCheckLifetime?.seconds ? this.loginData.passwordCheckLifetime?.seconds / 60 / 60 : 0,
|
||||
);
|
||||
|
||||
this.externalLoginCheckLifetime?.setValue(
|
||||
this.loginData.externalLoginCheckLifetime?.seconds
|
||||
? this.loginData.externalLoginCheckLifetime?.seconds / 60 / 60
|
||||
: 12,
|
||||
: 0,
|
||||
);
|
||||
|
||||
this.mfaInitSkipLifetime?.setValue(
|
||||
this.loginData.mfaInitSkipLifetime?.seconds ? this.loginData.mfaInitSkipLifetime?.seconds / 60 / 60 : 720,
|
||||
this.loginData.mfaInitSkipLifetime?.seconds ? this.loginData.mfaInitSkipLifetime?.seconds / 60 / 60 : 0,
|
||||
);
|
||||
|
||||
this.secondFactorCheckLifetime?.setValue(
|
||||
this.loginData.secondFactorCheckLifetime?.seconds
|
||||
? this.loginData.secondFactorCheckLifetime?.seconds / 60 / 60
|
||||
: 12,
|
||||
: 0,
|
||||
);
|
||||
|
||||
this.multiFactorCheckLifetime?.setValue(
|
||||
this.loginData.multiFactorCheckLifetime?.seconds
|
||||
? this.loginData.multiFactorCheckLifetime?.seconds / 60 / 60
|
||||
: 12,
|
||||
: 0,
|
||||
);
|
||||
}
|
||||
})
|
||||
@ -158,19 +158,19 @@ export class LoginPolicyComponent implements OnInit {
|
||||
mgmtreq.setDisableLoginWithEmail(this.loginData.disableLoginWithEmail);
|
||||
mgmtreq.setDisableLoginWithPhone(this.loginData.disableLoginWithPhone);
|
||||
|
||||
const pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 240) * 60 * 60);
|
||||
const pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 0) * 60 * 60);
|
||||
mgmtreq.setPasswordCheckLifetime(pcl);
|
||||
|
||||
const elcl = new Duration().setSeconds((this.externalLoginCheckLifetime?.value ?? 12) * 60 * 60);
|
||||
const elcl = new Duration().setSeconds((this.externalLoginCheckLifetime?.value ?? 0) * 60 * 60);
|
||||
mgmtreq.setExternalLoginCheckLifetime(elcl);
|
||||
|
||||
const misl = new Duration().setSeconds((this.mfaInitSkipLifetime?.value ?? 720) * 60 * 60);
|
||||
const misl = new Duration().setSeconds((this.mfaInitSkipLifetime?.value ?? 0) * 60 * 60);
|
||||
mgmtreq.setMfaInitSkipLifetime(misl);
|
||||
|
||||
const sfcl = new Duration().setSeconds((this.secondFactorCheckLifetime?.value ?? 12) * 60 * 60);
|
||||
const sfcl = new Duration().setSeconds((this.secondFactorCheckLifetime?.value ?? 0) * 60 * 60);
|
||||
mgmtreq.setSecondFactorCheckLifetime(sfcl);
|
||||
|
||||
const mficl = new Duration().setSeconds((this.multiFactorCheckLifetime?.value ?? 12) * 60 * 60);
|
||||
const mficl = new Duration().setSeconds((this.multiFactorCheckLifetime?.value ?? 0) * 60 * 60);
|
||||
mgmtreq.setMultiFactorCheckLifetime(mficl);
|
||||
|
||||
mgmtreq.setAllowDomainDiscovery(this.loginData.allowDomainDiscovery);
|
||||
@ -189,19 +189,19 @@ export class LoginPolicyComponent implements OnInit {
|
||||
mgmtreq.setDisableLoginWithEmail(this.loginData.disableLoginWithEmail);
|
||||
mgmtreq.setDisableLoginWithPhone(this.loginData.disableLoginWithPhone);
|
||||
|
||||
const pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 240) * 60 * 60);
|
||||
const pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 0) * 60 * 60);
|
||||
mgmtreq.setPasswordCheckLifetime(pcl);
|
||||
|
||||
const elcl = new Duration().setSeconds((this.externalLoginCheckLifetime?.value ?? 12) * 60 * 60);
|
||||
const elcl = new Duration().setSeconds((this.externalLoginCheckLifetime?.value ?? 0) * 60 * 60);
|
||||
mgmtreq.setExternalLoginCheckLifetime(elcl);
|
||||
|
||||
const misl = new Duration().setSeconds((this.mfaInitSkipLifetime?.value ?? 720) * 60 * 60);
|
||||
const misl = new Duration().setSeconds((this.mfaInitSkipLifetime?.value ?? 0) * 60 * 60);
|
||||
mgmtreq.setMfaInitSkipLifetime(misl);
|
||||
|
||||
const sfcl = new Duration().setSeconds((this.secondFactorCheckLifetime?.value ?? 12) * 60 * 60);
|
||||
const sfcl = new Duration().setSeconds((this.secondFactorCheckLifetime?.value ?? 0) * 60 * 60);
|
||||
mgmtreq.setSecondFactorCheckLifetime(sfcl);
|
||||
|
||||
const mficl = new Duration().setSeconds((this.multiFactorCheckLifetime?.value ?? 12) * 60 * 60);
|
||||
const mficl = new Duration().setSeconds((this.multiFactorCheckLifetime?.value ?? 0) * 60 * 60);
|
||||
mgmtreq.setMultiFactorCheckLifetime(mficl);
|
||||
|
||||
mgmtreq.setAllowDomainDiscovery(this.loginData.allowDomainDiscovery);
|
||||
@ -221,19 +221,19 @@ export class LoginPolicyComponent implements OnInit {
|
||||
adminreq.setDisableLoginWithEmail(this.loginData.disableLoginWithEmail);
|
||||
adminreq.setDisableLoginWithPhone(this.loginData.disableLoginWithPhone);
|
||||
|
||||
const admin_pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 240) * 60 * 60);
|
||||
const admin_pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 0) * 60 * 60);
|
||||
adminreq.setPasswordCheckLifetime(admin_pcl);
|
||||
|
||||
const admin_elcl = new Duration().setSeconds((this.externalLoginCheckLifetime?.value ?? 12) * 60 * 60);
|
||||
const admin_elcl = new Duration().setSeconds((this.externalLoginCheckLifetime?.value ?? 0) * 60 * 60);
|
||||
adminreq.setExternalLoginCheckLifetime(admin_elcl);
|
||||
|
||||
const admin_misl = new Duration().setSeconds((this.mfaInitSkipLifetime?.value ?? 720) * 60 * 60);
|
||||
const admin_misl = new Duration().setSeconds((this.mfaInitSkipLifetime?.value ?? 0) * 60 * 60);
|
||||
adminreq.setMfaInitSkipLifetime(admin_misl);
|
||||
|
||||
const admin_sfcl = new Duration().setSeconds((this.secondFactorCheckLifetime?.value ?? 12) * 60 * 60);
|
||||
const admin_sfcl = new Duration().setSeconds((this.secondFactorCheckLifetime?.value ?? 0) * 60 * 60);
|
||||
adminreq.setSecondFactorCheckLifetime(admin_sfcl);
|
||||
|
||||
const admin_mficl = new Duration().setSeconds((this.multiFactorCheckLifetime?.value ?? 12) * 60 * 60);
|
||||
const admin_mficl = new Duration().setSeconds((this.multiFactorCheckLifetime?.value ?? 0) * 60 * 60);
|
||||
adminreq.setMultiFactorCheckLifetime(admin_mficl);
|
||||
adminreq.setAllowDomainDiscovery(this.loginData.allowDomainDiscovery);
|
||||
adminreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames);
|
||||
|
@ -98,7 +98,7 @@ export function mapRequestValues(map: Partial<Map>, req: Req): Req {
|
||||
r6.setOtpOption(map.initMfaPromptText?.otpOption ?? '');
|
||||
r6.setSkipButtonText(map.initMfaPromptText?.skipButtonText ?? '');
|
||||
r6.setTitle(map.initMfaPromptText?.title ?? '');
|
||||
r6.setU2fOption(map.initMfaPromptText?.otpOption ?? '');
|
||||
r6.setU2fOption(map.initMfaPromptText?.u2fOption ?? '');
|
||||
req.setInitMfaPromptText(r6);
|
||||
|
||||
const r7 = new InitMFAU2FScreenText();
|
||||
@ -333,7 +333,7 @@ export function mapRequestValues(map: Partial<Map>, req: Req): Req {
|
||||
r31.setDescriptionClose(map.passwordlessRegistrationDoneText?.descriptionClose ?? '');
|
||||
r31.setNextButtonText(map.passwordlessRegistrationDoneText?.nextButtonText ?? '');
|
||||
r31.setTitle(map.passwordlessRegistrationDoneText?.title ?? '');
|
||||
r31.setNextButtonText(map.passwordlessRegistrationDoneText?.cancelButtonText ?? '');
|
||||
r31.setCancelButtonText(map.passwordlessRegistrationDoneText?.cancelButtonText ?? '');
|
||||
req.setPasswordlessRegistrationDoneText(r31);
|
||||
|
||||
const r32 = new PasswordlessRegistrationScreenText();
|
||||
|
@ -20,7 +20,15 @@ import { AdminService } from 'src/app/services/admin.service';
|
||||
import { AssetEndpoint, AssetService, AssetType } from 'src/app/services/asset.service';
|
||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
import { StorageKey, StorageLocation, StorageService } from 'src/app/services/storage.service';
|
||||
import { ThemeService } from 'src/app/services/theme.service';
|
||||
import {
|
||||
BACKGROUND,
|
||||
DARK_BACKGROUND,
|
||||
DARK_PRIMARY,
|
||||
DARK_WARN,
|
||||
PRIMARY,
|
||||
ThemeService,
|
||||
WARN,
|
||||
} from 'src/app/services/theme.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
import { InfoSectionType } from '../../info-section/info-section.component';
|
||||
@ -637,14 +645,14 @@ export class PrivateLabelingPolicyComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private applyToConsole(labelpolicy: LabelPolicy.AsObject): void {
|
||||
const darkPrimary = labelpolicy?.primaryColorDark || '#bbbafa';
|
||||
const lightPrimary = labelpolicy?.primaryColor || '#5469d4';
|
||||
const darkPrimary = labelpolicy?.primaryColorDark || DARK_PRIMARY;
|
||||
const lightPrimary = labelpolicy?.primaryColor || PRIMARY;
|
||||
|
||||
const darkWarn = labelpolicy?.warnColorDark || '#ff3b5b';
|
||||
const lightWarn = labelpolicy?.warnColor || '#cd3d56';
|
||||
const darkWarn = labelpolicy?.warnColorDark || DARK_WARN;
|
||||
const lightWarn = labelpolicy?.warnColor || WARN;
|
||||
|
||||
const darkBackground = labelpolicy?.backgroundColorDark || '#111827';
|
||||
const lightBackground = labelpolicy?.backgroundColor || '#fafafa';
|
||||
const darkBackground = labelpolicy?.backgroundColorDark || DARK_BACKGROUND;
|
||||
const lightBackground = labelpolicy?.backgroundColor || BACKGROUND;
|
||||
|
||||
this.themeService.savePrimaryColor(darkPrimary, true);
|
||||
this.themeService.savePrimaryColor(lightPrimary, false);
|
||||
|
@ -41,8 +41,8 @@
|
||||
color="primary"
|
||||
[disabled]="disabled"
|
||||
(click)="$event.stopPropagation()"
|
||||
(change)="$event ? selection.toggle(row) : null"
|
||||
[checked]="selection.isSelected(row)"
|
||||
(change)="$event ? selection.toggle(row.key) : null"
|
||||
[checked]="selection.isSelected(row.key)"
|
||||
>
|
||||
</mat-checkbox>
|
||||
</td>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { SelectionModel } from '@angular/cdk/collections';
|
||||
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatTable } from '@angular/material/table';
|
||||
import { Router } from '@angular/router';
|
||||
@ -27,8 +27,8 @@ export class ProjectRolesTableComponent implements OnInit {
|
||||
@ViewChild(PaginatorComponent) public paginator?: PaginatorComponent;
|
||||
@ViewChild(MatTable) public table?: MatTable<Role.AsObject>;
|
||||
public dataSource: ProjectRolesDataSource = new ProjectRolesDataSource(this.mgmtService);
|
||||
public selection: SelectionModel<Role.AsObject> = new SelectionModel<Role.AsObject>(true, []);
|
||||
@Output() public changedSelection: EventEmitter<Array<Role.AsObject>> = new EventEmitter();
|
||||
public selection: SelectionModel<string> = new SelectionModel<string>(true, []);
|
||||
@Output() public changedSelection: EventEmitter<Array<string>> = new EventEmitter();
|
||||
@Input() public displayedColumns: string[] = ['key', 'displayname', 'group', 'creationDate', 'changeDate', 'actions'];
|
||||
|
||||
constructor(
|
||||
@ -47,7 +47,7 @@ export class ProjectRolesTableComponent implements OnInit {
|
||||
|
||||
this.dataSource.rolesSubject.subscribe((roles) => {
|
||||
const selectedRoles: Role.AsObject[] = roles.filter((role) => this.selectedKeys.includes(role.key));
|
||||
this.selection.select(...selectedRoles);
|
||||
this.selection.select(...selectedRoles.map((r) => r.key));
|
||||
});
|
||||
|
||||
this.selection.changed.subscribe(() => {
|
||||
@ -57,7 +57,7 @@ export class ProjectRolesTableComponent implements OnInit {
|
||||
|
||||
public selectAllOfGroup(group: string): void {
|
||||
const groupRoles: Role.AsObject[] = this.dataSource.rolesSubject.getValue().filter((role) => role.group === group);
|
||||
this.selection.select(...groupRoles);
|
||||
this.selection.select(...groupRoles.map((r) => r.key));
|
||||
}
|
||||
|
||||
private loadRolesPage(): void {
|
||||
@ -65,20 +65,19 @@ export class ProjectRolesTableComponent implements OnInit {
|
||||
}
|
||||
|
||||
public changePage(): void {
|
||||
this.selection.clear();
|
||||
this.loadRolesPage();
|
||||
}
|
||||
|
||||
public isAllSelected(): boolean {
|
||||
const numSelected = this.selection.selected.length;
|
||||
const numRows = this.dataSource.rolesSubject.value.length;
|
||||
const numRows = this.dataSource.totalResult;
|
||||
return numSelected === numRows;
|
||||
}
|
||||
|
||||
public masterToggle(): void {
|
||||
this.isAllSelected()
|
||||
? this.selection.clear()
|
||||
: this.dataSource.rolesSubject.value.forEach((row: Role.AsObject) => this.selection.select(row));
|
||||
: this.dataSource.rolesSubject.value.forEach((row: Role.AsObject) => this.selection.select(row.key));
|
||||
}
|
||||
|
||||
public deleteRole(role: Role.AsObject): void {
|
||||
|
@ -3,9 +3,16 @@
|
||||
@mixin search-user-autocomplete-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$primary-color: mat.get-color-from-palette($primary, 500);
|
||||
$lighter-primary-color: mat.get-color-from-palette($primary, 300);
|
||||
$darker-primary-color: mat.get-color-from-palette($primary, 700);
|
||||
|
||||
$background: map-get($theme, background);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$secondary-text: map-get($foreground, secondary-text);
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
|
||||
$link-hover-color: if($is-dark-theme, mat.get-color-from-palette($primary, 200), $primary-color);
|
||||
$link-color: if($is-dark-theme, $lighter-primary-color, $primary-color);
|
||||
|
||||
.user-autocomplete-found {
|
||||
margin: 0.5rem 0;
|
||||
@ -67,11 +74,12 @@
|
||||
margin-top: 0.5rem;
|
||||
|
||||
a {
|
||||
color: $primary-color;
|
||||
color: $link-color;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: mat.get-color-from-palette($primary, 400);
|
||||
color: $link-hover-color;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import { from, of, Subject } from 'rxjs';
|
||||
import { debounceTime, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||
import { ListUsersResponse } from 'src/app/proto/generated/zitadel/management_pb';
|
||||
import { TextQueryMethod } from 'src/app/proto/generated/zitadel/object_pb';
|
||||
import { SearchQuery, User, UserNameQuery } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
import { LoginNameQuery, SearchQuery, User } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
@ -78,11 +78,11 @@ export class SearchUserAutocompleteComponent implements OnInit, AfterContentChec
|
||||
switchMap((value) => {
|
||||
const query = new SearchQuery();
|
||||
|
||||
const unQuery = new UserNameQuery();
|
||||
unQuery.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
|
||||
unQuery.setUserName(value);
|
||||
const lnQuery = new LoginNameQuery();
|
||||
lnQuery.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
|
||||
lnQuery.setLoginName(value);
|
||||
|
||||
query.setUserNameQuery(unQuery);
|
||||
query.setLoginNameQuery(lnQuery);
|
||||
|
||||
if (this.target === UserTarget.SELF) {
|
||||
return from(this.userService.listUsers(10, 0, [query]));
|
||||
|
@ -1,16 +1,18 @@
|
||||
<span class="title" mat-dialog-title>{{ data.i18nTitle | translate }}</span>
|
||||
<div mat-dialog-content>
|
||||
<cnsl-project-roles-table
|
||||
class="role-table"
|
||||
*ngIf="projectId"
|
||||
[displayedColumns]="['select', 'key', 'displayname', 'group']"
|
||||
(changedSelection)="selectRoles($event)"
|
||||
[projectId]="projectId"
|
||||
[grantId]="grantId"
|
||||
[selectedKeys]="selectedRoleKeysList"
|
||||
[showSelectionActionButton]="false"
|
||||
>
|
||||
</cnsl-project-roles-table>
|
||||
<div class="dialog-table-wrapper">
|
||||
<cnsl-project-roles-table
|
||||
class="role-table"
|
||||
*ngIf="projectId"
|
||||
[displayedColumns]="['select', 'key', 'displayname', 'group']"
|
||||
(changedSelection)="selectRoles($event)"
|
||||
[projectId]="projectId"
|
||||
[grantId]="grantId"
|
||||
[selectedKeys]="selectedRoleKeysList"
|
||||
[showSelectionActionButton]="false"
|
||||
>
|
||||
</cnsl-project-roles-table>
|
||||
</div>
|
||||
</div>
|
||||
<div mat-dialog-actions class="action">
|
||||
<button mat-stroked-button (click)="closeDialog()">
|
||||
|
@ -3,10 +3,15 @@
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.role-table {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
.dialog-table-wrapper {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
|
||||
.role-table {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
|
@ -12,7 +12,7 @@ export class UserGrantRoleDialogComponent {
|
||||
public grantId: string = '';
|
||||
public selectedRoleKeysList: string[] = [];
|
||||
|
||||
public selectedRoles: Role.AsObject[] = [];
|
||||
public selectedRoleKeys: string[] = [];
|
||||
|
||||
constructor(public dialogRef: MatDialogRef<UserGrantRoleDialogComponent>, @Inject(MAT_DIALOG_DATA) public data: any) {
|
||||
this.projectId = data.projectId;
|
||||
@ -20,8 +20,8 @@ export class UserGrantRoleDialogComponent {
|
||||
this.selectedRoleKeysList = data.selectedRoleKeysList;
|
||||
}
|
||||
|
||||
public selectRoles(selected: any): void {
|
||||
this.selectedRoles = selected;
|
||||
public selectRoles(selected: string[]): void {
|
||||
this.selectedRoleKeys = selected;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
@ -29,6 +29,6 @@ export class UserGrantRoleDialogComponent {
|
||||
}
|
||||
|
||||
public closeDialogWithSuccess(): void {
|
||||
this.dialogRef.close({ roles: this.selectedRoles.map((r) => r.key) });
|
||||
this.dialogRef.close({ roles: this.selectedRoleKeys });
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,9 @@
|
||||
<cnsl-info-section *ngIf="data.warnSectionKey" [type]="InfoSectionType.WARN">{{
|
||||
data.warnSectionKey | translate
|
||||
}}</cnsl-info-section>
|
||||
|
||||
<p *ngIf="data.hintKey" class="desc cnsl-secondary-text">{{ data.hintKey | translate: { value: data.confirmation } }}</p>
|
||||
|
||||
<cnsl-form-field *ngIf="data.confirmation && data.confirmationKey" class="formfield">
|
||||
<cnsl-label>{{ data.confirmationKey | translate: { value: data.confirmation } }}</cnsl-label>
|
||||
<input cnslInput [(ngModel)]="confirm" data-e2e="confirm-dialog-input" />
|
||||
|
@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { InfoSectionType } from 'src/app/modules/info-section/info-section.component';
|
||||
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
|
||||
import { Domain } from 'src/app/proto/generated/zitadel/org_pb';
|
||||
import { Domain, DomainValidationType } from 'src/app/proto/generated/zitadel/org_pb';
|
||||
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
@ -62,13 +62,16 @@ export class DomainsComponent implements OnInit {
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((resp) => {
|
||||
if (resp) {
|
||||
dialogRef.afterClosed().subscribe((domainName) => {
|
||||
if (domainName) {
|
||||
this.mgmtService
|
||||
.addOrgDomain(resp)
|
||||
.addOrgDomain(domainName)
|
||||
.then(() => {
|
||||
this.toast.showInfo('ORG.TOAST.DOMAINADDED', true);
|
||||
|
||||
this.verifyDomain({
|
||||
domainName: domainName,
|
||||
validationType: DomainValidationType.DOMAIN_VALIDATION_TYPE_UNSPECIFIED,
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.loadDomains();
|
||||
}, 1000);
|
||||
@ -91,8 +94,8 @@ export class DomainsComponent implements OnInit {
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((resp) => {
|
||||
if (resp) {
|
||||
dialogRef.afterClosed().subscribe((del) => {
|
||||
if (del) {
|
||||
this.mgmtService
|
||||
.removeOrgDomain(domain)
|
||||
.then(() => {
|
||||
@ -109,7 +112,7 @@ export class DomainsComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
public verifyDomain(domain: Domain.AsObject): void {
|
||||
public verifyDomain(domain: Partial<Domain.AsObject>): void {
|
||||
const dialogRef = this.dialog.open(DomainVerificationComponent, {
|
||||
data: {
|
||||
domain: domain,
|
||||
|
@ -29,6 +29,13 @@
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="lastFailed">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.FAILEDEVENTS.LASTFAILED' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let event">
|
||||
<span>{{ event?.lastFailed | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="errorMessage">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.FAILEDEVENTS.ERRORMESSAGE' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let event">
|
||||
|
@ -22,6 +22,7 @@ export class FailedEventsComponent implements AfterViewInit {
|
||||
'database',
|
||||
'failedSequence',
|
||||
'failureCount',
|
||||
'lastFailed',
|
||||
'errorMessage',
|
||||
'actions',
|
||||
];
|
||||
|
@ -73,7 +73,6 @@ export class OrgCreateComponent {
|
||||
private _location: Location,
|
||||
private fb: UntypedFormBuilder,
|
||||
private mgmtService: ManagementService,
|
||||
private authService: GrpcAuthService,
|
||||
breadcrumbService: BreadcrumbService,
|
||||
) {
|
||||
const instanceBread = new Breadcrumb({
|
||||
@ -83,16 +82,6 @@ export class OrgCreateComponent {
|
||||
});
|
||||
|
||||
breadcrumbService.setBreadcrumb([instanceBread]);
|
||||
|
||||
this.authService
|
||||
.isAllowed(['iam.write'])
|
||||
.pipe(take(1))
|
||||
.subscribe((allowed) => {
|
||||
if (allowed) {
|
||||
this.forSelf = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.initForm();
|
||||
|
||||
this.adminService.getSupportedLanguages().then((supportedResp) => {
|
||||
|
@ -89,8 +89,8 @@ export class ProjectGrantCreateComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
public selectRoles(roles: Role.AsObject[]): void {
|
||||
this.rolesKeyList = roles.map((role) => role.key);
|
||||
public selectRoles(roles: string[]): void {
|
||||
this.rolesKeyList = roles;
|
||||
}
|
||||
|
||||
public next(): void {
|
||||
|
@ -208,8 +208,8 @@ export class UserGrantCreateComponent implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
public selectRoles(roles: Role.AsObject[]): void {
|
||||
this.rolesList = roles.map((role) => role.key);
|
||||
public selectRoles(roleKeys: string[]): void {
|
||||
this.rolesList = roleKeys;
|
||||
}
|
||||
|
||||
public next(): void {
|
||||
|
@ -30,7 +30,7 @@
|
||||
required
|
||||
[ngStyle]="{ 'padding-right': suffixPadding ? suffixPadding : '10px' }"
|
||||
/>
|
||||
<span #suffix *ngIf="envSuffixLabel" cnslSuffix>{{ envSuffixLabel }}</span>
|
||||
<span #suffix *ngIf="envSuffix" cnslSuffix>{{ envSuffix }}</span>
|
||||
|
||||
<span cnslError *ngIf="userName?.invalid && userName?.errors?.required">
|
||||
{{ 'USER.VALIDATION.REQUIRED' | translate }}
|
||||
|
@ -46,8 +46,6 @@ export class UserCreateComponent implements OnDestroy {
|
||||
public languages: string[] = ['de', 'en', 'it', 'fr'];
|
||||
public userForm!: UntypedFormGroup;
|
||||
public pwdForm!: UntypedFormGroup;
|
||||
|
||||
public envSuffixLabel: string = '';
|
||||
private destroyed$: Subject<void> = new Subject();
|
||||
|
||||
public userLoginMustBeDomain: boolean = false;
|
||||
@ -84,14 +82,12 @@ export class UserCreateComponent implements OnDestroy {
|
||||
}
|
||||
this.initForm();
|
||||
this.loading = false;
|
||||
this.envSuffixLabel = this.envSuffix();
|
||||
this.changeDetRef.detectChanges();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
this.initForm();
|
||||
this.loading = false;
|
||||
this.envSuffixLabel = this.envSuffix();
|
||||
this.changeDetRef.detectChanges();
|
||||
});
|
||||
|
||||
@ -249,7 +245,8 @@ export class UserCreateComponent implements OnDestroy {
|
||||
public get confirmPassword(): AbstractControl | null {
|
||||
return this.pwdForm.get('confirmPassword');
|
||||
}
|
||||
private envSuffix(): string {
|
||||
|
||||
public get envSuffix(): string {
|
||||
if (this.userLoginMustBeDomain && this.primaryDomain?.domainName) {
|
||||
return `@${this.primaryDomain.domainName}`;
|
||||
} else {
|
||||
|
@ -12,7 +12,9 @@
|
||||
</cnsl-top-view>
|
||||
|
||||
<div *ngIf="loading" class="max-width-container">
|
||||
<mat-progress-spinner diameter="25" color="primary" mode="indeterminate"></mat-progress-spinner>
|
||||
<div class="user-spinner-wrapper">
|
||||
<mat-progress-spinner diameter="25" color="primary" mode="indeterminate"></mat-progress-spinner>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-width-container">
|
||||
@ -57,6 +59,7 @@
|
||||
<cnsl-contact
|
||||
*ngIf="user.human"
|
||||
[human]="user.human"
|
||||
[username]="user.preferredLoginName"
|
||||
[state]="user.state"
|
||||
[canWrite]="true"
|
||||
(editType)="openEditDialog($event)"
|
||||
@ -97,7 +100,12 @@
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<a matTooltip="{{ 'USER.PASSWORD.SET' | translate }}" [routerLink]="['password']" mat-icon-button>
|
||||
<a
|
||||
matTooltip="{{ 'USER.PASSWORD.SET' | translate }}"
|
||||
[routerLink]="['password']"
|
||||
[queryParams]="{ username: user.preferredLoginName }"
|
||||
mat-icon-button
|
||||
>
|
||||
<i class="las la-pen"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -88,6 +88,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.user-spinner-wrapper {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
.icon {
|
||||
font-size: 1.2rem;
|
||||
|
@ -15,6 +15,7 @@ export class ContactComponent {
|
||||
@Input() disablePhoneCode: boolean = false;
|
||||
@Input() canWrite: boolean | null = false;
|
||||
@Input() human?: Human.AsObject;
|
||||
@Input() username: string = '';
|
||||
@Input() state!: UserState;
|
||||
@Output() editType: EventEmitter<EditDialogType> = new EventEmitter<EditDialogType>();
|
||||
@Output() resendEmailVerification: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
@ -1,12 +1,17 @@
|
||||
<cnsl-detail-layout [hasBackButton]="true" title="{{ 'USER.PASSWORD.TITLE' | translate }}">
|
||||
<p class="password-info cnsl-secondary-text" sub>{{ 'USER.PASSWORD.DESCRIPTION' | translate }}</p>
|
||||
<ng-container *ngIf="userId; else authUser">
|
||||
<form
|
||||
*ngIf="passwordForm"
|
||||
autocomplete="new-password"
|
||||
[formGroup]="passwordForm"
|
||||
(ngSubmit)="setInitialPassword(userId)"
|
||||
>
|
||||
<form *ngIf="passwordForm" [formGroup]="passwordForm" (ngSubmit)="setInitialPassword(userId)">
|
||||
<input
|
||||
*ngIf="username"
|
||||
class="hiddeninput"
|
||||
type="hidden"
|
||||
autocomplete="username"
|
||||
name="username"
|
||||
type="text"
|
||||
[value]="username"
|
||||
/>
|
||||
|
||||
<div class="password-content">
|
||||
<div class="side-by-side">
|
||||
<cnsl-form-field class="formfield">
|
||||
@ -39,6 +44,16 @@
|
||||
|
||||
<ng-template #authUser>
|
||||
<form *ngIf="passwordForm" [formGroup]="passwordForm" (ngSubmit)="setPassword()">
|
||||
<input
|
||||
*ngIf="username"
|
||||
class="hiddeninput"
|
||||
type="hidden"
|
||||
autocomplete="username"
|
||||
name="username"
|
||||
type="text"
|
||||
[value]="username"
|
||||
/>
|
||||
|
||||
<div class="password-content">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PASSWORD.OLD' | translate }}</cnsl-label>
|
||||
|
@ -3,35 +3,40 @@
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.password-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
max-width: 40rem;
|
||||
form {
|
||||
.hiddeninput {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.side-by-side {
|
||||
.password-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
margin: 0 -0.5rem;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
max-width: 40rem;
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
flex-direction: column;
|
||||
.side-by-side {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
margin: 0 -0.5rem;
|
||||
width: 100%;
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.formfield {
|
||||
flex: 1;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.formfield {
|
||||
flex: 1;
|
||||
margin: 0 0.5rem;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.formfield {
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.validation {
|
||||
&.between {
|
||||
margin: 0 0.5rem;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Subscription, take } from 'rxjs';
|
||||
import { Subject, Subscription, take, takeUntil } from 'rxjs';
|
||||
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from 'src/app/pages/validators';
|
||||
import { PasswordComplexityPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
|
||||
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||
@ -31,11 +31,13 @@ function passwordConfirmValidator(c: AbstractControl): any {
|
||||
})
|
||||
export class PasswordComponent implements OnDestroy {
|
||||
userId: string = '';
|
||||
public username: string = '';
|
||||
|
||||
public policy!: PasswordComplexityPolicy.AsObject;
|
||||
public passwordForm!: UntypedFormGroup;
|
||||
|
||||
private formSub: Subscription = new Subscription();
|
||||
private destroy$: Subject<void> = new Subject();
|
||||
|
||||
constructor(
|
||||
activatedRoute: ActivatedRoute,
|
||||
@ -45,11 +47,14 @@ export class PasswordComponent implements OnDestroy {
|
||||
private toast: ToastService,
|
||||
private breadcrumbService: BreadcrumbService,
|
||||
) {
|
||||
activatedRoute.params.subscribe((data) => {
|
||||
activatedRoute.queryParams.pipe(takeUntil(this.destroy$)).subscribe((data) => {
|
||||
const { username } = data;
|
||||
this.username = username;
|
||||
});
|
||||
activatedRoute.params.pipe(takeUntil(this.destroy$)).subscribe((data) => {
|
||||
const { id } = data;
|
||||
if (id) {
|
||||
this.userId = id;
|
||||
|
||||
breadcrumbService.setBreadcrumb([
|
||||
new Breadcrumb({
|
||||
type: BreadcrumbType.ORG,
|
||||
@ -59,6 +64,7 @@ export class PasswordComponent implements OnDestroy {
|
||||
} else {
|
||||
this.authService.user.pipe(take(1)).subscribe((user) => {
|
||||
if (user) {
|
||||
this.username = user.preferredLoginName;
|
||||
this.breadcrumbService.setBreadcrumb([
|
||||
new Breadcrumb({
|
||||
type: BreadcrumbType.AUTHUSER,
|
||||
@ -102,6 +108,8 @@ export class PasswordComponent implements OnDestroy {
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
this.formSub.unsubscribe();
|
||||
}
|
||||
|
||||
|
@ -103,6 +103,7 @@
|
||||
<cnsl-contact
|
||||
[disablePhoneCode]="true"
|
||||
[state]="user.state"
|
||||
[username]="user.preferredLoginName"
|
||||
[canWrite]="['user.write:' + user.id, 'user.write$'] | hasRole | async"
|
||||
*ngIf="user?.human"
|
||||
[human]="user.human"
|
||||
@ -186,6 +187,7 @@
|
||||
matTooltip="{{ 'USER.PASSWORD.SET' | translate }}"
|
||||
[disabled]="(['user.write:' + user.id, 'user.write$'] | hasRole | async) === false"
|
||||
[routerLink]="['password']"
|
||||
[queryParams]="{ username: user.preferredLoginName }"
|
||||
mat-icon-button
|
||||
>
|
||||
<i class="las la-pen"></i>
|
||||
|
@ -256,8 +256,10 @@ export class UserTableComponent implements OnInit {
|
||||
confirmKey: 'ACTIONS.DELETE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'USER.DIALOG.DELETE_SELF_TITLE',
|
||||
descriptionKey: 'USER.DIALOG.DELETE_SELF_DESCRIPTION',
|
||||
confirmationKey: 'USER.DIALOG.TYPEUSERNAME',
|
||||
warnSectionKey: 'USER.DIALOG.DELETE_SELF_DESCRIPTION',
|
||||
hintKey: 'USER.DIALOG.TYPEUSERNAME',
|
||||
hintParam: 'USER.DIALOG.DELETE_DESCRIPTION',
|
||||
confirmationKey: 'USER.DIALOG.USERNAME',
|
||||
confirmation: user.preferredLoginName,
|
||||
};
|
||||
|
||||
@ -265,8 +267,10 @@ export class UserTableComponent implements OnInit {
|
||||
confirmKey: 'ACTIONS.DELETE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'USER.DIALOG.DELETE_TITLE',
|
||||
descriptionKey: 'USER.DIALOG.DELETE_DESCRIPTION',
|
||||
confirmationKey: 'USER.DIALOG.TYPEUSERNAME',
|
||||
warnSectionKey: 'USER.DIALOG.DELETE_DESCRIPTION',
|
||||
hintKey: 'USER.DIALOG.TYPEUSERNAME',
|
||||
hintParam: 'USER.DIALOG.DELETE_DESCRIPTION',
|
||||
confirmationKey: 'USER.DIALOG.USERNAME',
|
||||
confirmation: user.preferredLoginName,
|
||||
};
|
||||
|
||||
|
@ -13,17 +13,17 @@ export interface Color {
|
||||
contrastColor: string;
|
||||
}
|
||||
|
||||
const DARK_PRIMARY = '#bbbafa';
|
||||
const PRIMARY = '#5469d4';
|
||||
export const DARK_PRIMARY = '#2073c4';
|
||||
export const PRIMARY = '#5469d4';
|
||||
|
||||
const DARK_WARN = '#ff3b5b';
|
||||
const WARN = '#cd3d56';
|
||||
export const DARK_WARN = '#ff3b5b';
|
||||
export const WARN = '#cd3d56';
|
||||
|
||||
const DARK_BACKGROUND = '#111827';
|
||||
const BACKGROUND = '#fafafa';
|
||||
export const DARK_BACKGROUND = '#111827';
|
||||
export const BACKGROUND = '#fafafa';
|
||||
|
||||
const DARK_TEXT = '#ffffff';
|
||||
const TEXT = '#000000';
|
||||
export const DARK_TEXT = '#ffffff';
|
||||
export const TEXT = '#000000';
|
||||
|
||||
@Injectable()
|
||||
export class ThemeService {
|
||||
|
@ -256,6 +256,7 @@
|
||||
"DELETE_SELF_DESCRIPTION": "Sie sind im Begriff Ihren eigenen Benutzer endgültig zu löschen. Dadurch werden Sie ausgeloggt und Ihr Account wird gelöscht. Diese Aktion kann nicht rückgängig gemacht werden!",
|
||||
"DELETE_AUTH_DESCRIPTION": "Sie sind im Begriff Ihren Account endgültig zu löschen. Wollen Sie dies wirklich tun?",
|
||||
"TYPEUSERNAME": "Wiederholen Sie '{{value}}', um den Benutzer zu löschen.",
|
||||
"USERNAME": "Loginname",
|
||||
"DELETE_BTN": "Endgültig löschen"
|
||||
},
|
||||
"SENDEMAILDIALOG": {
|
||||
@ -742,6 +743,7 @@
|
||||
"DATABASE": "Datenbank",
|
||||
"FAILEDSEQUENCE": "betroffene Sequenz",
|
||||
"FAILURECOUNT": "Fehleranzahl",
|
||||
"LASTFAILED": "Zuletzt gescheitert um",
|
||||
"ERRORMESSAGE": "Meldung",
|
||||
"ACTIONS": "Aktionen",
|
||||
"DELETE": "Entfernen",
|
||||
|
@ -256,6 +256,7 @@
|
||||
"DELETE_SELF_DESCRIPTION": "You are about to permanently delete your personal account. This will log you out and delete your user. This action cannot be undone!",
|
||||
"DELETE_AUTH_DESCRIPTION": "You are about to permanently delete your personal account. Are you sure?",
|
||||
"TYPEUSERNAME": "Type '{{value}}', to confirm and delete the user.",
|
||||
"USERNAME": "Loginname",
|
||||
"DELETE_BTN": "Delete permanently"
|
||||
},
|
||||
"SENDEMAILDIALOG": {
|
||||
@ -742,6 +743,7 @@
|
||||
"DATABASE": "Database",
|
||||
"FAILEDSEQUENCE": "Failed Sequence",
|
||||
"FAILURECOUNT": "Failure Count",
|
||||
"LASTFAILED": "Last failed at",
|
||||
"ERRORMESSAGE": "Error Message",
|
||||
"ACTIONS": "Actions",
|
||||
"DELETE": "Remove",
|
||||
|
@ -256,6 +256,7 @@
|
||||
"DELETE_SELF_DESCRIPTION": "Vous êtes sur le point de supprimer définitivement votre compte personnel. Cela vous déconnectera et supprimera votre utilisateur. Cette action ne peut être annulée !",
|
||||
"DELETE_AUTH_DESCRIPTION": "Vous êtes sur le point de supprimer définitivement votre compte personnel. En êtes-vous sûr ?",
|
||||
"TYPEUSERNAME": "Tapez '{{value}}', pour confirmer et supprimer l'utilisateur.",
|
||||
"USERNAME": "Identifiant",
|
||||
"DELETE_BTN": "Supprimer définitivement"
|
||||
},
|
||||
"SENDEMAILDIALOG": {
|
||||
@ -742,6 +743,7 @@
|
||||
"DATABASE": "Base de données",
|
||||
"FAILEDSEQUENCE": "Séquence échouée",
|
||||
"FAILURECOUNT": "Nombre d'échecs",
|
||||
"LASTFAILED": "Dernier échec à",
|
||||
"ERRORMESSAGE": "Message d'erreur",
|
||||
"ACTIONS": "Actions",
|
||||
"DELETE": "Supprimer",
|
||||
|
@ -256,6 +256,7 @@
|
||||
"DELETE_SELF_DESCRIPTION": "Stai per eliminare definitivamente il tuo account. Questo ti disconnetterà ed eliminerà il tuo utente. Questa azione non può essere annullata!",
|
||||
"DELETE_AUTH_DESCRIPTION": "Stai per eleminare il tuo account personale in modo permanente. Vuoi continuare?",
|
||||
"TYPEUSERNAME": "Ripeti '{{value}}' per confermare ed eliminare l'utente.",
|
||||
"USERNAME": "Nome utente",
|
||||
"DELETE_BTN": "Elimina"
|
||||
},
|
||||
"SENDEMAILDIALOG": {
|
||||
@ -742,6 +743,7 @@
|
||||
"DATABASE": "Database",
|
||||
"FAILEDSEQUENCE": "Sequenza fallita",
|
||||
"FAILURECOUNT": "Conteggio dei fallimenti",
|
||||
"LASTFAILED": "L'ultimo fallimento a",
|
||||
"ERRORMESSAGE": "Messaggio di errore",
|
||||
"ACTIONS": "Azioni",
|
||||
"DELETE": "Rimuovi",
|
||||
@ -868,7 +870,7 @@
|
||||
"GENERAL": "Generale",
|
||||
"LOGIN": "Comportamento login e sicurezza",
|
||||
"LOCKOUT": "Meccanismi di bloccaggio",
|
||||
"COMPLEXITY": "complessità della password",
|
||||
"COMPLEXITY": "Complessità della password",
|
||||
"NOTIFICATIONS": "Notifiche",
|
||||
"NOTIFICATIONS_DESC": "Impostazioni SMTP e SMS",
|
||||
"MESSAGETEXTS": "Testi di notifica",
|
||||
|
@ -256,6 +256,7 @@
|
||||
"DELETE_SELF_DESCRIPTION": "您即将永久删除您的个人帐户。这将使您注销并删除您的用户。此操作无法撤消!",
|
||||
"DELETE_AUTH_DESCRIPTION": "您即将永久删除您的个人帐户。你确定吗?",
|
||||
"TYPEUSERNAME": "输入 '{{value}}',确认并删除该用户。",
|
||||
"USERNAME": "登录名",
|
||||
"DELETE_BTN": "永久删除"
|
||||
},
|
||||
"SENDEMAILDIALOG": {
|
||||
@ -742,6 +743,7 @@
|
||||
"DATABASE": "数据库",
|
||||
"FAILEDSEQUENCE": "失败的序列",
|
||||
"FAILURECOUNT": "失败计数",
|
||||
"LASTFAILED": "最后一次失败是在",
|
||||
"ERRORMESSAGE": "错误消息",
|
||||
"ACTIONS": "操作",
|
||||
"DELETE": "删除",
|
||||
|
@ -20,7 +20,14 @@ yarn install
|
||||
yarn start
|
||||
```
|
||||
|
||||
This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server.
|
||||
As an alternative you can use this docker compose command:
|
||||
|
||||
```console
|
||||
docker compose up
|
||||
```
|
||||
|
||||
These commands start a local development server.
|
||||
Most changes are reflected live without having to restart the server.
|
||||
|
||||
## Build
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
|
||||
presets: [require.resolve("@docusaurus/core/lib/babel/preset")],
|
||||
};
|
||||
|
11
docs/docker-compose.yml
Normal file
11
docs/docker-compose.yml
Normal file
@ -0,0 +1,11 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
docusaurus:
|
||||
image: node:lts-alpine3.15
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- ./:/app
|
||||
ports:
|
||||
- 3000:3000
|
||||
command: sh -c "yarn install && yarn start --host 0.0.0.0"
|
@ -104,3 +104,4 @@ ZITADEL supports only the external authentication flow at the moment.
|
||||
|
||||
- [Actions concept](../concepts/features/actions)
|
||||
- [Actions guide](../guides/manage/customize/behavior)
|
||||
- [Actions Marketplace: Find example actions to use in ZITADEL](https://github.com/zitadel/actions)
|
||||
|
@ -2020,6 +2020,7 @@ This is an empty request
|
||||
| failed_sequence | uint64 | - | |
|
||||
| failure_count | uint64 | - | |
|
||||
| error_message | string | - | |
|
||||
| last_failed | google.protobuf.Timestamp | - | |
|
||||
|
||||
|
||||
|
||||
@ -4633,7 +4634,7 @@ this is en empty request
|
||||
| database | string | - | |
|
||||
| view_name | string | - | |
|
||||
| processed_sequence | uint64 | - | |
|
||||
| event_timestamp | google.protobuf.Timestamp | The timestamp the event occured | |
|
||||
| event_timestamp | google.protobuf.Timestamp | The timestamp the event occurred | |
|
||||
| last_successful_spooler_run | google.protobuf.Timestamp | - | |
|
||||
|
||||
|
||||
|
@ -5408,6 +5408,7 @@ This is an empty response
|
||||
| password_change_required | bool | - | |
|
||||
| request_passwordless_registration | bool | - | |
|
||||
| otp_code | string | - | |
|
||||
| idps | repeated ImportHumanUserRequest.IDP | - | |
|
||||
|
||||
|
||||
|
||||
@ -5436,6 +5437,19 @@ This is an empty response
|
||||
|
||||
|
||||
|
||||
### ImportHumanUserRequest.IDP
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| config_id | string | internal id of the IDP in ZITADEL | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| external_user_id | string | id of the user on the IDP | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| display_name | string | (display) name of the user on the IDP | string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### ImportHumanUserRequest.Phone
|
||||
|
||||
|
||||
|
@ -506,6 +506,7 @@ This is an empty response
|
||||
| failed_sequence | uint64 | - | |
|
||||
| failure_count | uint64 | - | |
|
||||
| error_message | string | - | |
|
||||
| last_failed | google.protobuf.Timestamp | - | |
|
||||
|
||||
|
||||
|
||||
@ -687,6 +688,7 @@ This is an empty request
|
||||
| database | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| view_name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| failed_sequence | uint64 | - | |
|
||||
| instance_id | string | - | |
|
||||
|
||||
|
||||
|
||||
|
@ -113,6 +113,18 @@ title: zitadel/user.proto
|
||||
|
||||
|
||||
|
||||
### LoginNameQuery
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| login_name | string | - | string.max_len: 200<br /> |
|
||||
| method | zitadel.v1.TextQueryMethod | - | enum.defined_only: true<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### Machine
|
||||
|
||||
|
||||
@ -288,6 +300,7 @@ this query is always equals
|
||||
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.email_query | EmailQuery | - | |
|
||||
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.state_query | StateQuery | - | |
|
||||
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.type_query | TypeQuery | - | |
|
||||
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.login_name_query | LoginNameQuery | - | |
|
||||
|
||||
|
||||
|
||||
|
@ -35,4 +35,5 @@ Within the JavaScript code, you can read and manipulate the state.
|
||||
## Further reading
|
||||
|
||||
- [Assign users a role after they register using an external identity provider](../../guides/manage/customize/behavior)
|
||||
- [Actions reference](../../apis/actions)
|
||||
- [Actions reference](../../apis/actions)
|
||||
- [Actions Marketplace: Find example actions to use in ZITADEL](https://github.com/zitadel/actions)
|
@ -7,7 +7,7 @@ It shows how to add user login to your application and fetch some data from the
|
||||
|
||||
At the end of the guide, your application has login functionality and has access to the current user's profile.
|
||||
|
||||
> This documentation refers to our [example](https://github.com/zitadel/zitadel-examples/tree/main/angular) in GitHub. Note that we've written ZITADEL Console in Angular, so you can also use that as a reference.
|
||||
> This documentation refers to our [example](https://github.com/zitadel/zitadel-angular) in GitHub. Note that we've written ZITADEL Console in Angular, so you can also use that as a reference.
|
||||
|
||||
## Setup Application and Get Keys
|
||||
|
||||
@ -22,7 +22,7 @@ We recommend you use [Authorization Code](../../apis/openidoauth/grant-types#aut
|
||||
|
||||
With the Redirect URIs field, you tell ZITADEL where it is allowed to redirect users to after authentication. For development, you can set dev mode to `true` to enable insecure HTTP and redirect to a `localhost` URI.
|
||||
|
||||
> If you are following along with the [example](https://github.com/zitadel/zitadel-examples/tree/main/angular), set dev mode to `true` and the Redirect URIs to <http://localhost:4200/auth/callback>.
|
||||
> If you are following along with the [example](https://github.com/zitadel/zitadel-angular), set dev mode to `true` and the Redirect URIs to <http://localhost:4200/auth/callback>.
|
||||
|
||||
If you want to redirect the users back to a route on your application after they have logged out, add an optional redirect in the Post Logout URIs field.
|
||||
|
||||
@ -48,34 +48,8 @@ npm install angular-oauth2-oidc
|
||||
|
||||
Add _OAuthModule_ to your Angular imports in _AppModule_ and provide the _AuthConfig_ in the providers' section. Also, ensure you import the _HTTPClientModule_.
|
||||
|
||||
```ts
|
||||
...
|
||||
import { AuthConfig, OAuthModule } from 'angular-oauth2-oidc';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
|
||||
const authConfig: AuthConfig = {
|
||||
scope: 'openid profile email',
|
||||
responseType: 'code',
|
||||
oidc: true,
|
||||
clientId: 'YOUR-CLIENT-ID', // replace with your appid
|
||||
issuer: 'https:/[your-domain]-[random-string].zitadel.cloud', // replace with your instance
|
||||
redirectUri: 'http://localhost:4200/auth/callback',
|
||||
postLogoutRedirectUri: 'http://localhost:4200/signedout', // optional
|
||||
requireHttps: false // required for running locally
|
||||
};
|
||||
|
||||
@NgModule({
|
||||
...
|
||||
imports: [
|
||||
OAuthModule.forRoot(),
|
||||
HttpClientModule,
|
||||
...
|
||||
providers: [
|
||||
{
|
||||
provide: AuthConfig,
|
||||
useValue: authConfig
|
||||
}
|
||||
...
|
||||
```ts reference
|
||||
https://github.com/zitadel/zitadel-angular/blob/main/src/app/app.module.ts
|
||||
```
|
||||
|
||||
Set _openid_, _profile_ and _email_ as scope, _code_ as responseType, and oidc to _true_. Then create an authentication service to provide the functions to authenticate your user.
|
||||
@ -88,103 +62,25 @@ ng g service services/authentication
|
||||
|
||||
Copy the following code to your service. This code provides a function `authenticate()` which redirects the user to ZITADEL. After successful login, ZITADEL redirects the user back to the redirect URI configured in _AuthModule_ and ZITADEL Console. Make sure both correspond, otherwise ZITADEL throws an error.
|
||||
|
||||
```ts
|
||||
import { Injectable } from "@angular/core";
|
||||
import { AuthConfig, OAuthService } from "angular-oauth2-oidc";
|
||||
import { BehaviorSubject, from, Observable } from "rxjs";
|
||||
|
||||
import { StatehandlerService } from "./statehandler.service";
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
export class AuthenticationService {
|
||||
private _authenticated: boolean = false;
|
||||
private readonly _authenticationChanged: BehaviorSubject<boolean> =
|
||||
new BehaviorSubject(this.authenticated);
|
||||
|
||||
constructor(
|
||||
private oauthService: OAuthService,
|
||||
private authConfig: AuthConfig,
|
||||
private statehandler: StatehandlerService
|
||||
) {}
|
||||
|
||||
public get authenticated(): boolean {
|
||||
return this._authenticated;
|
||||
}
|
||||
|
||||
public get authenticationChanged(): Observable<boolean> {
|
||||
return this._authenticationChanged;
|
||||
}
|
||||
|
||||
public getOIDCUser(): Observable<any> {
|
||||
return from(this.oauthService.loadUserProfile());
|
||||
}
|
||||
|
||||
public async authenticate(setState: boolean = true): Promise<boolean> {
|
||||
this.oauthService.configure(this.authConfig);
|
||||
|
||||
this.oauthService.strictDiscoveryDocumentValidation = false;
|
||||
await this.oauthService.loadDiscoveryDocumentAndTryLogin();
|
||||
|
||||
this._authenticated = this.oauthService.hasValidAccessToken();
|
||||
|
||||
if (!this.oauthService.hasValidIdToken() || !this.authenticated) {
|
||||
const newState = setState
|
||||
? await this.statehandler.createState().toPromise()
|
||||
: undefined;
|
||||
this.oauthService.initCodeFlow(newState);
|
||||
}
|
||||
this._authenticationChanged.next(this.authenticated);
|
||||
|
||||
return this.authenticated;
|
||||
}
|
||||
|
||||
public signout(): void {
|
||||
this.oauthService.logOut();
|
||||
this._authenticated = false;
|
||||
this._authenticationChanged.next(false);
|
||||
}
|
||||
}
|
||||
```ts reference
|
||||
https://github.com/zitadel/zitadel-angular/blob/main/src/app/services/authentication.service.ts
|
||||
```
|
||||
|
||||
Our example includes a _StatehandlerService_ to redirect the user back to the route where he initially came from.
|
||||
If you don't need such behavior, you can omit the following line from the `authenticate()` method above.
|
||||
|
||||
```ts
|
||||
...
|
||||
const newState = setState ? await this.statehandler.createState().toPromise() : undefined;
|
||||
...
|
||||
```ts reference
|
||||
https://github.com/zitadel/zitadel-angular/blob/main/src/app/services/authentication.service.ts#L45
|
||||
```
|
||||
|
||||
If you decide to use the _StatehandlerService_, provide it in the `app.module`. Make sure it gets initialized first using Angular’s `APP_INITIALIZER`. You find the service implementation in the [example](https://github.com/zitadel/zitadel-examples/tree/main/angular).
|
||||
If you decide to use the _StatehandlerService_, provide it in the `app.module`. Make sure it gets initialized first using Angular’s `APP_INITIALIZER`. You find the service implementation in the [example](https://github.com/zitadel/zitadel-angular).
|
||||
|
||||
```ts
|
||||
```ts reference
|
||||
https://github.com/zitadel/zitadel-angular/blob/main/src/app/app.module.ts#L26-L30
|
||||
```
|
||||
|
||||
const stateHandlerFn = (stateHandler: StatehandlerService) => {
|
||||
return () => {
|
||||
return stateHandler.initStateHandler();
|
||||
};
|
||||
};
|
||||
|
||||
...
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: stateHandlerFn,
|
||||
multi: true,
|
||||
deps: [StatehandlerService],
|
||||
},
|
||||
{
|
||||
provide: StatehandlerProcessorService,
|
||||
useClass: StatehandlerProcessorServiceImpl,
|
||||
},
|
||||
{
|
||||
provide: StatehandlerService,
|
||||
useClass: StatehandlerServiceImpl,
|
||||
},
|
||||
]
|
||||
...
|
||||
```ts reference
|
||||
https://github.com/zitadel/zitadel-angular/blob/main/src/app/app.module.ts#L55-L78
|
||||
```
|
||||
|
||||
### Add Login to Your Application
|
||||
@ -213,77 +109,28 @@ ng g guard guards/auth
|
||||
|
||||
This code shows the _AuthGuard_ used in ZITADEL Console.
|
||||
|
||||
```ts
|
||||
import { Injectable } from "@angular/core";
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
CanActivate,
|
||||
RouterStateSnapshot,
|
||||
UrlTree,
|
||||
} from "@angular/router";
|
||||
import { Observable } from "rxjs";
|
||||
import { AuthenticationService } from "../services/authentication.service";
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
export class AuthGuard implements CanActivate {
|
||||
constructor(private auth: AuthenticationService) {}
|
||||
|
||||
canActivate(
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot
|
||||
):
|
||||
| Observable<boolean | UrlTree>
|
||||
| Promise<boolean | UrlTree>
|
||||
| boolean
|
||||
| UrlTree {
|
||||
if (!this.auth.authenticated) {
|
||||
return this.auth.authenticate();
|
||||
}
|
||||
return this.auth.authenticated;
|
||||
}
|
||||
}
|
||||
```ts reference
|
||||
https://github.com/zitadel/zitadel-angular/blob/main/src/app/guards/auth.guard.ts
|
||||
```
|
||||
|
||||
Add the guard to your _RouterModule_ similar to this:
|
||||
|
||||
```ts
|
||||
...
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => import('./pages/home/home.module').then(m => m.HomeModule),
|
||||
canActivate: [AuthGuard],
|
||||
},
|
||||
...
|
||||
```ts reference
|
||||
https://github.com/zitadel/zitadel-angular/blob/main/src/app/app-routing.module.ts#L9-L31
|
||||
```
|
||||
|
||||
> Note: Make sure you redirect the user from your callback URL to a guarded page, so `authenticate()` is called again and the access token is stored.
|
||||
|
||||
```ts
|
||||
...
|
||||
{
|
||||
path: 'auth/callback',
|
||||
redirectTo: 'user',
|
||||
},
|
||||
....
|
||||
```ts reference
|
||||
https://github.com/zitadel/zitadel-angular/blob/main/src/app/app-routing.module.ts#L19-L21
|
||||
```
|
||||
|
||||
### Add Logout to Your Application
|
||||
|
||||
Call `auth.signout()` for logging the current user out. Note that you can also configure a logout redirect URI if you want your users to be redirected after logout.
|
||||
|
||||
```ts
|
||||
import { AuthenticationService } from "src/app/services/authentication.service";
|
||||
|
||||
export class SomeComponentWithLogout {
|
||||
constructor(private authService: AuthenticationService) {}
|
||||
|
||||
public signout(): Promise<void> {
|
||||
return this.authService.signout();
|
||||
}
|
||||
}
|
||||
```ts reference
|
||||
https://github.com/zitadel/zitadel-angular/blob/main/src/app/components/user/user.component.ts#L20-L22
|
||||
```
|
||||
|
||||
### Show User Information
|
||||
@ -291,29 +138,21 @@ export class SomeComponentWithLogout {
|
||||
To fetch user data, ZITADEL's user info endpoint has to be called. This data contains sensitive information and artifacts related to the current user's identity and the scopes you defined in your _AuthConfig_.
|
||||
Our _AuthenticationService_ already includes a method called _getOIDCUser()_. You can call it wherever you need this information.
|
||||
|
||||
```ts
|
||||
import { AuthenticationService } from 'src/app/services/authentication.service';
|
||||
|
||||
public user$: Observable<any>;
|
||||
|
||||
constructor(private auth: AuthenticationService) {
|
||||
this.user$ = this.auth.getOIDCUser();
|
||||
}
|
||||
```ts reference
|
||||
https://github.com/zitadel/zitadel-angular/blob/main/src/app/components/user/user.component.ts
|
||||
```
|
||||
|
||||
And in your HTML file:
|
||||
|
||||
```html
|
||||
<div *ngIf="user$ | async as user">
|
||||
<p>{{user | json}}</p>
|
||||
</div>
|
||||
```html reference
|
||||
https://github.com/zitadel/zitadel-angular/blob/main/src/app/components/user/user.component.html
|
||||
```
|
||||
|
||||
## Completion
|
||||
|
||||
You have successfully integrated your Angular application with ZITADEL!
|
||||
|
||||
If you get stuck, consider checking out our [example](https://github.com/zitadel/zitadel-examples/tree/main/angular) application. It includes all the mentioned functionality of this quickstart. You can simply start by cloning the repository and replacing the _AuthConfig_ in the _AppModule_ by your own configuration. If you run into issues, contact us or raise an issue on [GitHub](https://github.com/zitadel/zitadel).
|
||||
If you get stuck, consider checking out our [example](https://github.com/zitadel/zitadel-angular) application. It includes all the mentioned functionality of this quickstart. You can simply start by cloning the repository and replacing the _AuthConfig_ in the _AppModule_ by your own configuration. If you run into issues, contact us or raise an issue on [GitHub](https://github.com/zitadel/zitadel).
|
||||
|
||||
![App in console](/img/angular/app-screen.png)
|
||||
|
||||
|
@ -2,61 +2,58 @@
|
||||
title: Flutter
|
||||
---
|
||||
|
||||
This guide demonstrates how you integrate **ZITADEL** as an idendity provider to a Flutter app.
|
||||
This guide demonstrates how you integrate **ZITADEL** into a Flutter app. It refers to our example on [GitHub](https://github.com/zitadel/zitadel_flutter)
|
||||
|
||||
At the end of the guide you have a mobile application on Android and iOS with the ability
|
||||
to authenticate users via ZITADEL.
|
||||
At the end of the guide you have a mobile application for **Android**, **iOS** and **Web** with the ability to authenticate users via ZITADEL.
|
||||
|
||||
If you need any other information about Flutter, head over to the [documentation](https://flutter.dev/).
|
||||
|
||||
## Prerequisites
|
||||
## Setup Application
|
||||
|
||||
Before we can start building our application, we have to do a few configuration steps in ZITADEL Console.
|
||||
You will need to provide some information about your app. We recommend creating a new app to start from scratch. Navigate to your Project, then add a new application at the top of the page.
|
||||
Select **Native** application type and continue.
|
||||
|
||||
![Create app in console](/img/flutter/nativeapp.png)
|
||||
|
||||
### Redirect URIs
|
||||
|
||||
With the Redirect URIs field, you tell ZITADEL where it is allowed to redirect users to after authentication.
|
||||
As our application will also support web, we have to make sure to set redirects for http and https, as well as a **custom-scheme** for our native Android and IOS Setup.
|
||||
|
||||
For our local web development, add a redirectURI for `http://localhost:4444/auth.html` with your custom port. For Android and IOS, add your **custom scheme**. In our case it is `com.example.zitadelflutter`.
|
||||
|
||||
:::caution Use Custom Redirect URI!
|
||||
|
||||
Your custom scheme has to be compliant with the OAuth 2.0
|
||||
authentication for mobile devices ([RFC 8252 specification](https://tools.ietf.org/html/rfc8252)).
|
||||
Otherwise your app might get rejected.
|
||||
|
||||
:::
|
||||
|
||||
For development, you need to set dev mode to `true` to enable insecure HTTP and redirect to a `localhost` URI.
|
||||
|
||||
If you want to redirect the users back to a route on your application after they have logged out, add an optional redirect in the Post Logout URIs field.
|
||||
|
||||
Continue and create the application.
|
||||
|
||||
After creation, go to **token settings** and check the refresh token checkbox. This allows us to request a refresh_token via `offline_access` scope.
|
||||
Make sure to save the application.
|
||||
|
||||
### Client ID
|
||||
|
||||
After successful app creation, a pop-up will appear, showing the app's client ID. Copy the client ID, as you will need it to configure your Flutter application.
|
||||
|
||||
## Flutter Prerequisites
|
||||
|
||||
To move further in this quickstart, you'll need the following things prepared:
|
||||
|
||||
- Have Flutter (and Dart) installed ([how-to](https://flutter.dev/docs/get-started/install))
|
||||
- Have an IDE set up for developing Flutter ([how-to](https://flutter.dev/docs/get-started/editor))
|
||||
- Create a basic Flutter app ([how-to](https://flutter.dev/docs/get-started/codelab))
|
||||
- Create a "Native" application in a ZITADEL project
|
||||
- Create a "Native" application in ZITADEL
|
||||
|
||||
## Flutter with ZITADEL
|
||||
|
||||
In your native application on ZITADEL, you need to add a callback (redirect) uri
|
||||
which matches the selected custom url scheme. As an example, if you intend to
|
||||
use `ch.myexample.app://sign-me-in` as redirect URI on ZITADEL and in your app,
|
||||
you need to register the `ch.myexample.app://` custom url scheme within Android and iOS.
|
||||
|
||||
:::caution Use Custom Redirect URI!
|
||||
|
||||
You'll need the custom redirect url to be compliant with the OAuth 2.0
|
||||
authentication for mobile devices ([RFC 8252 specification](https://tools.ietf.org/html/rfc8252)).
|
||||
Otherwise your app might get rejected.
|
||||
|
||||
:::
|
||||
|
||||
### Hello World
|
||||
|
||||
After you created the basic Flutter app, the app will show the following screen:
|
||||
|
||||
<div style={{'text-align': 'center', 'margin-bottom': '1rem'}}>
|
||||
<img src="/img/flutter/hello-world.png" alt="Flutter Hello World" height="500px" />
|
||||
</div>
|
||||
|
||||
You may want to change the Flutter SDK version in `pubspec.yaml` from
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
sdk: ">=2.7.0 <3.0.0"
|
||||
```
|
||||
|
||||
to
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
```
|
||||
|
||||
With this, you'll enable "nullable by default" mode in Flutter, as well as new language features.
|
||||
For this quickstart, the minimal Flutter SDK version is set to the default: `sdk: ">=2.7.0 <3.0.0"`.
|
||||
After you created the starter Flutter app, the app will show a simple, templated Flutter app.
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
@ -69,345 +66,112 @@ Basically, there are two major points in this specification:
|
||||
2. It does not allow third party apps to open the browser for the login process,
|
||||
the app must open the login page within the embedded browser view
|
||||
|
||||
Install the [`appauth`](https://appauth.io/) package and a secure storage (to store the auth / refresh tokens):
|
||||
First install [http](https://pub.dev/packages/http) a library for making HTTP calls,
|
||||
then [`flutter_web_auth_2`](https://pub.dev/packages/flutter_web_auth_2) and a secure storage to store the auth / refresh tokens [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage).
|
||||
|
||||
To install run:
|
||||
|
||||
```bash
|
||||
flutter pub add http
|
||||
flutter pub add flutter_appauth
|
||||
flutter pub add flutter_web_auth_2
|
||||
flutter pub add flutter_secure_storage
|
||||
```
|
||||
|
||||
#### Important on Android
|
||||
#### Setup for Android
|
||||
|
||||
To use this app auth method on Android 11, you'll need to add a `query` to the `AndroidManifest.xml`.
|
||||
Go to `<projectRoot>/android/app/src/main/AndroidManifest.xml` and add to the `<manifest>` root:
|
||||
Navigate to your `AndroidManifest.xml` at `<projectRoot>/android/app/src/main/AndroidManifest.xml` and add the following activity with your custom scheme.
|
||||
|
||||
```xml title="<projectRoot>/android/app/src/main/AndroidManifest.xml"
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.APP_BROWSER" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
</queries>
|
||||
```xml reference
|
||||
https://github.com/zitadel/zitadel_flutter/blob/main/android/app/src/main/AndroidManifest.xml#L29-L38
|
||||
```
|
||||
|
||||
This allows the app to query for internal browser activities.
|
||||
|
||||
Furthermore, for `secure_storage`, you need to set the minimum SDK version to 18
|
||||
in `<projectRoot>/android/app/src/build.gradle`. Then, add the manifest placeholder
|
||||
for your redirect url (the custom url scheme). In the end, the `defaultConfig`
|
||||
section of the `build.gradle` file should look like this:
|
||||
|
||||
```groovy title="<projectRoot>/android/app/src/build.gradle"
|
||||
defaultConfig {
|
||||
applicationId "<<YOUR APPLICATION ID, for example ch.myexample.my_fancy_app>>"
|
||||
minSdkVersion 18
|
||||
targetSdkVersion 30
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
manifestPlaceholders = [
|
||||
'appAuthRedirectScheme': '<<YOUR CUSTOM URL SCHEME, for example ch.myexample.app>>'
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Important on iOS
|
||||
|
||||
In a similar way to Android, you need to register the custom url scheme within iOS to
|
||||
be able to use custom redirect schemes. In the `Info.plist` file of the Runner
|
||||
project, you can add the `CFBundleTypeRole` and the `CFBundleUrlSchemes`.
|
||||
|
||||
```xml title="<projectRoot>/ios/Runner/Info.plist"
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>YOUR CUSTOM URL SCHEME, for example ch.myexample.app</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
```
|
||||
in `<projectRoot>/android/app/src/build.gradle`.
|
||||
|
||||
### Add Authentication
|
||||
|
||||
:::note
|
||||
|
||||
The auth redirect scheme "`ch.myexample.app`" does register all auth urls with the given
|
||||
scheme for the app. So an url pointing to `ch.myexample.app://signin` and another one
|
||||
for `ch.myexample.app://logout` will work with the same registration.
|
||||
|
||||
:::
|
||||
|
||||
To reduce the commented default code, we will modify the `main.dart` file.
|
||||
|
||||
First, the `MyApp` class: it remains a stateless widget:
|
||||
|
||||
```dart
|
||||
class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter ZITADEL',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: MyHomePage(title: 'Flutter ZITADEL Quickstart'),
|
||||
);
|
||||
}
|
||||
}
|
||||
```dart reference
|
||||
https://github.com/zitadel/zitadel_flutter/blob/main/lib/main.dart#L14-L28
|
||||
```
|
||||
|
||||
Second, the `MyHomePage` class will remain a stateful widget with
|
||||
its title, we don't change any code here.
|
||||
|
||||
```dart
|
||||
class MyHomePage extends StatefulWidget {
|
||||
MyHomePage({Key key, this.title}) : super(key: key);
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
_MyHomePageState createState() => _MyHomePageState();
|
||||
}
|
||||
```dart reference
|
||||
https://github.com/zitadel/zitadel_flutter/blob/main/lib/main.dart#L30-L37
|
||||
```
|
||||
|
||||
What we'll change now, is the `_MyHomePageState` class to enable
|
||||
authentication via ZITADEL and remove the counter button of the hello
|
||||
world application. We'll show the username of the authenticated user.
|
||||
authentication via ZITADEL and remove the counter button of the starter application. We'll show the username of the authenticated user.
|
||||
|
||||
We define the needed elements for our state:
|
||||
|
||||
```dart
|
||||
final _appAuth = FlutterAppAuth();
|
||||
final _secureStorage = const FlutterSecureStorage();
|
||||
|
||||
var _busy = false;
|
||||
var _authenticated = false;
|
||||
var _username = '';
|
||||
final storage = const FlutterSecureStorage();
|
||||
```
|
||||
|
||||
Then the builder method, which does show the login button if you're not
|
||||
authenticated, a loading bar if the login process is going on and
|
||||
your name if you are authenticated:
|
||||
|
||||
```dart
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.title),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (!_authenticated && !_busy)
|
||||
Text(
|
||||
'You are not authenticated.',
|
||||
),
|
||||
if (!_authenticated && !_busy)
|
||||
ElevatedButton.icon(
|
||||
icon: Icon(Icons.fingerprint),
|
||||
label: Text('login'),
|
||||
onPressed: _authenticate),
|
||||
if (_busy)
|
||||
Stack(
|
||||
children: [
|
||||
Center(child: Text("Busy, logging in.")),
|
||||
Opacity(
|
||||
opacity: 0.5,
|
||||
child: Container(
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
Center(child: CircularProgressIndicator()),
|
||||
],
|
||||
),
|
||||
if (_authenticated && !_busy)
|
||||
Text(
|
||||
'Hello $_username!',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
```dart reference
|
||||
https://github.com/zitadel/zitadel_flutter/blob/main/lib/main.dart#L119-L159
|
||||
```
|
||||
|
||||
And finally the `_authenticate` method which calls the authorization endpoint,
|
||||
then fetches the user info and stores the tokens into the secure storage.
|
||||
|
||||
```dart
|
||||
Future<void> _authenticate() async {
|
||||
setState(() {
|
||||
_busy = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final result = await _appAuth.authorizeAndExchangeCode(
|
||||
AuthorizationTokenRequest(
|
||||
'<<CLIENT_ID>>', // Client ID of the native application
|
||||
'<<CALLBACK_URL>>', // The registered url from zitadel (e.g. ch.myexample.app://signin)
|
||||
issuer: '<<ISSUER>>', // most of the cases: https:/[your-domain]-[random-string].zitadel.cloud
|
||||
scopes: [
|
||||
'openid',
|
||||
'profile',
|
||||
'email',
|
||||
'offline_access',
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final userInfoResponse = await get(
|
||||
Uri.parse('https://[your-instance].zitadel.cloud/oidc/v1/userinfo'),
|
||||
headers: {
|
||||
HttpHeaders.authorizationHeader: 'Bearer ${result.accessToken}',
|
||||
HttpHeaders.acceptHeader: 'application/json; charset=UTF-8'
|
||||
},
|
||||
);
|
||||
final userJson = jsonDecode(utf8.decode(userInfoResponse.bodyBytes));
|
||||
|
||||
await _secureStorage.write(
|
||||
key: 'auth_access_token', value: result?.accessToken);
|
||||
await _secureStorage.write(
|
||||
key: 'refresh_token', value: result?.refreshToken);
|
||||
await _secureStorage.write(key: 'id_token', value: result?.idToken);
|
||||
|
||||
setState(() {
|
||||
_busy = false;
|
||||
_authenticated = true;
|
||||
_username = '${userJson['given_name']} ${userJson['family_name']}';
|
||||
});
|
||||
} catch (e, s) {
|
||||
print('error on authorizeAndExchangeCode token: $e - stack: $s');
|
||||
setState(() {
|
||||
_busy = false;
|
||||
_authenticated = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
```dart reference
|
||||
https://github.com/zitadel/zitadel_flutter/blob/main/lib/main.dart#L45-L117
|
||||
```
|
||||
|
||||
Now, you should be able to login with a valid ZITADEL user.
|
||||
Note that we have to use our http redirect URL for web applications or otherwise use our custom scheme for Android and iOS devices.
|
||||
To setup other platforms, read the documentation of the [Flutter Web Auth](https://pub.dev/packages/flutter_web_auth_2).
|
||||
|
||||
#### Result
|
||||
To ensure our application catches the callback URL, you have to create a `auth.html` file in the `/web`
|
||||
folder with the following content:
|
||||
|
||||
In the end, our state class looks like:
|
||||
|
||||
```dart
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
final _appAuth = FlutterAppAuth();
|
||||
final _secureStorage = const FlutterSecureStorage();
|
||||
|
||||
var _busy = false;
|
||||
var _authenticated = false;
|
||||
var _username = '';
|
||||
|
||||
Future<void> _authenticate() async {
|
||||
setState(() {
|
||||
_busy = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final result = await _appAuth.authorizeAndExchangeCode(
|
||||
AuthorizationTokenRequest(
|
||||
'CLIENT_ID',
|
||||
'CALLBACK_URL',
|
||||
issuer: 'ISSUER',
|
||||
scopes: [
|
||||
'openid',
|
||||
'profile',
|
||||
'email',
|
||||
'offline_access',
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final userInfoResponse = await get(
|
||||
Uri.parse('https:/[your-domain]-[random-string].zitadel.cloud/oidc/v1/userinfo'), // replace with your instance
|
||||
headers: {
|
||||
HttpHeaders.authorizationHeader: 'Bearer ${result.accessToken}',
|
||||
HttpHeaders.acceptHeader: 'application/json; charset=UTF-8'
|
||||
},
|
||||
);
|
||||
final userJson = jsonDecode(utf8.decode(userInfoResponse.bodyBytes));
|
||||
|
||||
await _secureStorage.write(
|
||||
key: 'auth_access_token', value: result?.accessToken);
|
||||
await _secureStorage.write(
|
||||
key: 'refresh_token', value: result?.refreshToken);
|
||||
await _secureStorage.write(key: 'id_token', value: result?.idToken);
|
||||
|
||||
setState(() {
|
||||
_busy = false;
|
||||
_authenticated = true;
|
||||
_username = '${userJson['given_name']} ${userJson['family_name']}';
|
||||
});
|
||||
} catch (e, s) {
|
||||
print('error on authorizeAndExchangeCode token: $e - stack: $s');
|
||||
setState(() {
|
||||
_busy = false;
|
||||
_authenticated = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.title),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (!_authenticated && !_busy)
|
||||
Text(
|
||||
'You are not authenticated.',
|
||||
),
|
||||
if (!_authenticated && !_busy)
|
||||
ElevatedButton.icon(
|
||||
icon: Icon(Icons.fingerprint),
|
||||
label: Text('login'),
|
||||
onPressed: _authenticate),
|
||||
if (_busy)
|
||||
Stack(
|
||||
children: [
|
||||
Center(child: Text("Busy, logging in.")),
|
||||
Opacity(
|
||||
opacity: 0.5,
|
||||
child: Container(
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
Center(child: CircularProgressIndicator()),
|
||||
],
|
||||
),
|
||||
if (_authenticated && !_busy)
|
||||
Text(
|
||||
'Hello $_username!',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```html reference
|
||||
https://github.com/zitadel/zitadel_flutter/blob/main/web/auth.html
|
||||
```
|
||||
|
||||
If you run this application, you can authenticate with a valid ZITADEL user.
|
||||
Now, you can run your application for iOS and Android devices with
|
||||
|
||||
<div style={{display: 'grid', 'grid-column-gap': '1rem', 'grid-template-columns': '1fr 1fr'}}>
|
||||
```bash
|
||||
flutter run
|
||||
```
|
||||
|
||||
or by directly selecting your device
|
||||
|
||||
```bash
|
||||
flutter run -d iphone
|
||||
```
|
||||
|
||||
For Web make sure you run the application on your fixed port such that it matches your redirect URI in your ZITADEL application. We used 4444 as port before so the command would look like this:
|
||||
|
||||
```bash
|
||||
flutter run -d chrome --web-port=4444
|
||||
```
|
||||
|
||||
Our Android and iOS Application opens ZITADEL's login within a custom tab, on Web a new tab is opened.
|
||||
|
||||
### Result
|
||||
|
||||
If everything works out correctly, your applications should look like this:
|
||||
|
||||
<div style={{display: 'grid', 'grid-column-gap': '1rem', 'grid-template-columns': '1fr 1fr', 'max-width': '500px', 'margin': '0 auto'}}>
|
||||
<img src="/img/flutter/not-authed.png" alt="Unauthenticated" height="500px" />
|
||||
<img src="/img/flutter/authed.png" alt="Flutter Authenticated" height="500px" />
|
||||
</div>
|
||||
|
||||
<div style={{display: 'grid', 'grid-column-gap': '1rem', 'grid-template-columns': '1fr 1fr', 'max-width': '800px', 'margin': '0 auto'}}>
|
||||
<img src="/img/flutter/web-not-authed.png" alt="Unauthenticated" height="500px" />
|
||||
<img src="/img/flutter/web-authed.png" alt="Flutter Authenticated" height="500px" />
|
||||
</div>
|
||||
|
@ -16,7 +16,7 @@ npx create-next-app --typescript
|
||||
yarn create next-app --typescript
|
||||
```
|
||||
|
||||
# Install Authentication library
|
||||
### Install Authentication library
|
||||
|
||||
To keep the template as easy as possible we use [next-auth](https://next-auth.js.org/) as our main authentication library. To install, run:
|
||||
|
||||
@ -34,25 +34,42 @@ npm run dev
|
||||
|
||||
then open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
# Configuration
|
||||
## Setup Application and Get Keys
|
||||
|
||||
Before we can start building our application, we have to do a few configuration steps in ZITADEL Console.
|
||||
You will need to provide some information about your app.
|
||||
Navigate to your Project, then add a new application at the top of the page.
|
||||
Select Web application type and continue.
|
||||
We recommend you use [Authorization Code](../../apis/openidoauth/grant-types#authorization-code) in combination with [Proof Key for Code Exchange (PKCE)](../../apis/openidoauth/grant-types#proof-key-for-code-exchange) for all web applications.
|
||||
|
||||
![Create app in console](/img/nextjs/app-create.png)
|
||||
|
||||
### Redirect URIs
|
||||
|
||||
With the Redirect URIs field, you tell ZITADEL where it is allowed to redirect users to after authentication. For development, you can set dev mode to `true` to enable insecure HTTP and redirect to a `localhost` URI.
|
||||
|
||||
> If you are following along with the [example](https://github.com/zitadel/zitadel-examples/tree/main/angular), set dev mode to `true` and the Redirect URIs to <http://localhost:300/api/auth/callback/zitadel>.
|
||||
|
||||
If you want to redirect the users back to a route on your application after they have logged out, add an optional redirect in the Post Logout URIs field.
|
||||
|
||||
Continue and create the application.
|
||||
|
||||
### Client ID
|
||||
|
||||
After successful app creation, a pop-up will appear, showing the app's client ID. Copy the client ID, as you will need it to configure your NextJS app.
|
||||
|
||||
## NextJS Setup
|
||||
|
||||
Now that you have your web application configured on the ZITADEL side, you can go ahead and integrate your NextJS app.
|
||||
|
||||
### Configuration
|
||||
|
||||
NextAuth.js exposes a REST API which is used by your client.
|
||||
To setup your configuration, create a file called [...nextauth].tsx in `pages/api/auth`.
|
||||
You can directly import the ZITADEL provider from [next-auth](https://next-auth.js.org/providers/zitadel).
|
||||
|
||||
```ts
|
||||
import NextAuth from "next-auth";
|
||||
import ZitadelProvider from "next-auth/providers/zitadel";
|
||||
|
||||
export default NextAuth({
|
||||
providers: [
|
||||
ZitadelProvider({
|
||||
issuer: process.env.ZITADEL_ISSUER,
|
||||
clientId: process.env.ZITADEL_CLIENT_ID,
|
||||
clientSecret: process.env.ZITADEL_CLIENT_SECRET,
|
||||
}),
|
||||
],
|
||||
});
|
||||
```ts reference
|
||||
https://github.com/zitadel/zitadel-nextjs/blob/main/pages/api/auth/%5B...nextauth%5D.tsx
|
||||
```
|
||||
|
||||
You can overwrite the default callbacks, just append them to the ZITADEL provider.
|
||||
@ -89,81 +106,41 @@ Hit Create, then in the detail view of your application make sure to enable dev
|
||||
|
||||
Now go to Token settings and check the checkbox for **User Info inside ID Token** to get your users name directly on authentication.
|
||||
|
||||
## Environment
|
||||
### Environment
|
||||
|
||||
Create a file `.env` in the root of the project and add the following keys to it.
|
||||
You can find your Issuer Url on the application detail page in console.
|
||||
|
||||
```
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
ZITADEL_ISSUER=[yourIssuerUrl]
|
||||
ZITADEL_CLIENT_ID=[yourClientId]
|
||||
ZITADEL_CLIENT_SECRET=[randomvalue]
|
||||
NEXTAUTH_SECRET=[randomvalue]
|
||||
```env reference
|
||||
https://github.com/zitadel/zitadel-nextjs/blob/main/.env
|
||||
```
|
||||
|
||||
next-auth requires a secret for all providers, so just define a random value here.
|
||||
|
||||
# User interface
|
||||
### User interface
|
||||
|
||||
Now we can start editing the homepage by modifying `pages/index.tsx`.
|
||||
Add this snippet your file. This code gets your auth session from next-auth, renders a Logout button if your authenticated or shows a Signup button if your not.
|
||||
Note that signIn method requires the id of the provider we provided earlier, and provides a possibilty to add a callback url, Auth Next will redirect you to the specified route if logged in successfully.
|
||||
Now we can start editing the homepage by modifying `pages/index.tsx`. On the homepage, your authenticated user or a Signin button is shown.
|
||||
|
||||
```ts
|
||||
import { signIn, signOut, useSession } from 'next-auth/client';
|
||||
Add the following component to render the UI elements:
|
||||
|
||||
export default function Page() {
|
||||
const { data: session } = useSession();
|
||||
...
|
||||
{!session && <>
|
||||
Not signed in <br />
|
||||
<button onClick={() => signIn('zitadel', { callbackUrl: 'http://localhost:3000/profile' })}>
|
||||
Sign in
|
||||
</button>
|
||||
</>}
|
||||
{session && <>
|
||||
Signed in as {session.user.email} <br />
|
||||
<button onClick={() => signOut()}>Sign out</button>
|
||||
</>}
|
||||
...
|
||||
}
|
||||
```ts reference
|
||||
|
||||
https://github.com/zitadel/zitadel-nextjs/blob/main/components/profile.tsx#L4-L38
|
||||
```
|
||||
|
||||
Note that the signIn method requires the id of our provider which is in our case `zitadel`.
|
||||
|
||||
### Session state
|
||||
|
||||
To allow session state to be shared between pages - which improves performance, reduces network traffic and avoids component state changes while rendering - you can use the NextAuth.js Provider in `/pages/_app.tsx`.
|
||||
Take a loot at the template `_app.tsx`.
|
||||
|
||||
```ts
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
return (
|
||||
<SessionProvider session={pageProps.session}>
|
||||
<Component {...pageProps} />
|
||||
</SessionProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyApp;
|
||||
```ts reference
|
||||
https://github.com/zitadel/zitadel-nextjs/blob/main/pages/_app.tsx
|
||||
```
|
||||
|
||||
Last thing: create a `profile.tsx` in /pages which renders the callback page.
|
||||
|
||||
```ts
|
||||
import Link from "next/link";
|
||||
|
||||
import styles from "../styles/Home.module.css";
|
||||
|
||||
export default function Profile() {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<h1>Login successful</h1>
|
||||
<Link href="/">
|
||||
<button>Back to Home</button>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```ts reference
|
||||
https://github.com/zitadel/zitadel-nextjs/blob/main/pages/profile.tsx
|
||||
```
|
||||
|
@ -18,7 +18,7 @@ services:
|
||||
image: 'ghcr.io/zitadel/zitadel:stable'
|
||||
command: 'start-from-init --config /example-zitadel-config.yaml --config /example-zitadel-secrets.yaml --steps /example-zitadel-init-steps.yaml --masterkey "${ZITADEL_MASTERKEY}" --tlsMode external'
|
||||
depends_on:
|
||||
chown:
|
||||
certs:
|
||||
condition: 'service_completed_successfully'
|
||||
|
||||
volumes:
|
||||
@ -27,10 +27,10 @@ services:
|
||||
- './example-zitadel-init-steps.yaml:/example-zitadel-init-steps.yaml:ro'
|
||||
- 'zitadel-certs:/crdb-certs:ro'
|
||||
|
||||
chown:
|
||||
certs:
|
||||
image: 'cockroachdb/cockroach:v22.1.0'
|
||||
entrypoint: [ '/bin/bash', '-c' ]
|
||||
command: [ 'cp /certs/ca.crt /zitadel-certs/ && cp /certs/client.root.crt /zitadel-certs/ && cp /certs/client.root.key /zitadel-certs/ && chown 1000:1000 /zitadel-certs/* && chmod 0400 /zitadel-certs/*' ]
|
||||
command: [ 'cp /certs/* /zitadel-certs/ && cockroach cert create-client --overwrite --certs-dir /zitadel-certs/ --ca-key /zitadel-certs/ca.key zitadel_user && chown 1000:1000 /zitadel-certs/*' ]
|
||||
volumes:
|
||||
- 'certs:/certs:ro'
|
||||
- 'zitadel-certs:/zitadel-certs:rw'
|
||||
|
@ -6,7 +6,5 @@ Database:
|
||||
User:
|
||||
# If the user doesn't exist already, it is created
|
||||
Username: 'zitadel_user'
|
||||
Password: 'Secret_DB_User_Password'
|
||||
Admin:
|
||||
Username: 'root'
|
||||
Password: ''
|
||||
|
@ -3,8 +3,6 @@ title: Overview
|
||||
---
|
||||
|
||||
Choose your platform and run ZITADEL with the most minimal configuration possible.
|
||||
For an easy self-hosted production setup, we recommend running ZITADEL on [Kubernetes](https://kubernetes.io/docs/home/), using our official [Helm](https://helm.sh/docs/) chart.
|
||||
By default, it runs a highly available ZITADEL instance along with a secure and highly available [CockroachDB](https://www.cockroachlabs.com/docs/stable/) instance.
|
||||
|
||||
- [Linux](./linux)
|
||||
- [MacOS](./macos)
|
||||
@ -12,12 +10,10 @@ By default, it runs a highly available ZITADEL instance along with a secure and
|
||||
- [Knative](./knative)
|
||||
- [Kubernetes](./kubernetes)
|
||||
|
||||
## Prerequisits
|
||||
## Prerequisites
|
||||
|
||||
- ZITADEL does not need many resources, 1 CPU and 512MB memory are more than enough. (With more CPU, the password hashing might be faster)
|
||||
- For test environments, ZITADEL does not need many resources, 1 CPU and 512MB memory are more than enough. (With more CPU, the password hashing might be faster)
|
||||
- A cockroachDB or Postgresql (currently in beta) as only needed storage
|
||||
- If you want to front ZITADEL with a reverse proxy, web application firewall or content delivery network, make sure to support [HTTP/2](../manage/self-hosted/http2)
|
||||
|
||||
|
||||
## Releases
|
||||
|
||||
@ -25,4 +21,13 @@ The easiest way to use ZITADEL is to run one of our container releases
|
||||
|
||||
- ZITADEL does provide latest and stable [container images](https://github.com/zitadel/zitadel/pkgs/container/zitadel)
|
||||
- **stable** is the current **production** release of ZITADEL.
|
||||
- **latest** is the **last created** release from our pipelines that gets updated on a high frequency.
|
||||
- **latest** is the **last created** release from our pipelines that gets updated in a high frequency.
|
||||
|
||||
# Production Setup
|
||||
|
||||
As soon as you successfully created your first test environment using one of the deployment guides in this section,
|
||||
you might want to configure ZITADEL for production and embed it into your system landscape.
|
||||
To do so, jump straight to the [production setup guide](../manage/self-hosted/production).
|
||||
|
||||
To achieving high availability, we recommend to use a [Kubernetes](https://kubernetes.io/docs/home/) Cluster.
|
||||
We have an official [Helm chart](https://artifacthub.io/packages/helm/zitadel/zitadel) for easy deployment and maintenance.
|
||||
|
@ -23,7 +23,7 @@ Browse to the [App registration menus create dialog](https://portal.azure.com/#v
|
||||
![Create an Application](/img/guides/azure_app_register.png)
|
||||
|
||||
:::info
|
||||
Mare sure to select `web` as application type in the `Redirect URI (optional)` section.
|
||||
Make sure to select `web` as application type in the `Redirect URI (optional)` section.
|
||||
You can leave the second field empty since we will change this in the next step.
|
||||
:::
|
||||
|
||||
@ -79,9 +79,49 @@ Once you created the IdP you need to activate it, to make it usable for your use
|
||||
|
||||
![Active AzureAD](/img/guides/azure_zitadel_active.png)
|
||||
|
||||
#### Disable 2-Factor prompt
|
||||
|
||||
If a user has no 2-factor configured, ZITADEL does ask on a regularly basis, if the user likes to add a new 2-factor for more security.
|
||||
If you don't want your users to get this prompt when using Azure, you have to disable this feature.
|
||||
|
||||
1. Go to the login behaviour settings of your instance or organization, depending if you like to disable it for all or just a specific organization respectively
|
||||
2. Set "Multi-factor init lifetimes" to 0
|
||||
|
||||
![img.png](../../../static/img/guides/login_lifetimes.png)
|
||||
|
||||
#### Create user with verified email
|
||||
|
||||
Azure AD does not send the "email verified claim" in its token.
|
||||
Due to that the user will get an email verification mail to verify his email address.
|
||||
|
||||
To create the user with a verified email address you must add an action.
|
||||
|
||||
1. Go to the actions of your organization
|
||||
2. Create a new action with the following code to set the email to verified automatically
|
||||
3. Make sure the action name matches the function in the action itself e.g: "setEmailVerified"
|
||||
|
||||
```js reference
|
||||
https://github.com/zitadel/actions/blob/main/examples/verify_email.js
|
||||
```
|
||||
|
||||
![img.png](../../../static/img/guides/action_email_verify.png)
|
||||
|
||||
3. Add the action "email verify" to the flow "external authentication" and to the trigger "pre creation"
|
||||
|
||||
![img.png](../../../static/img/guides/action_pre_creation_email_verify.png)
|
||||
|
||||
#### Automatically redirect to Azure AD
|
||||
|
||||
If you like to get automatically redirected to your Azure AD login instead of showing the ZITADEL login with the Username/Password and a button "Login with AzureAD" you have to do the following steps:
|
||||
|
||||
1. Go to the login behaviour settings of your instance or organization
|
||||
2. Disable login with username and password
|
||||
3. Make sure you have only configured AzureAD as external identity provider
|
||||
4. If you did all your settings on the organization level make sure to send the organization scope in your authorization request: [scope](../../apis/openidoauth/scopes#reserved-scopes)
|
||||
|
||||
### Test the setup
|
||||
|
||||
To test the setup use a incognito mode and browse to your login page.
|
||||
To test the setup use incognito mode and browse to your login page.
|
||||
If you succeeded you should see a new button which should redirect you to your AzureAD Tenant.
|
||||
|
||||
![AzureAD Button](/img/guides/azure_zitadel_button.png)
|
||||
|
@ -109,6 +109,17 @@ Secondfactors:
|
||||
- OTP (One Time Password), Authenticator Apps like Google/Microsoft Authenticator, Authy, etc.
|
||||
- U2F (Universal Second Factor), e.g FaceID, WindowsHello, Fingerprint, Hardwaretokens like Yubikey
|
||||
|
||||
### Login Lifetimes
|
||||
|
||||
Configure the different lifetimes checks for the login process:
|
||||
|
||||
- **Password Check Lifetime** specifies after which period a user has to reenter his password during the login process
|
||||
- **External Login Check Lifetime** specifies after which period a user will be redirected to the IDP during the login process
|
||||
- **Multifactor Init Lifetime** specifies after which period a user will be prompted to setup a 2-Factor / Multi Factor during the login process (value 0 will deactivate the prompt)
|
||||
- **Second Factor Check Lifetime** specifies after which period a user has to revalidate the 2-Factor during the login process
|
||||
- **External Login Check Lifetime** specifies after which period a user has to revalidate the Multi Factor during the login process
|
||||
|
||||
|
||||
## Identity Providers
|
||||
|
||||
You can configure all kinds of external identity providers for identity brokering, which support OIDC (OpenID Connect).
|
||||
|
@ -29,13 +29,9 @@ Before you start, make sure you have everything set up correctly.
|
||||
1. Paste this snippet into the multiline textfield.
|
||||
1. Replace the snippets placeholders and select **Save**.
|
||||
|
||||
```js
|
||||
function addGrant(ctx, api) {
|
||||
api.userGrants.push({
|
||||
ProjectID: '<the projects resource ID you copied above>',
|
||||
Roles: ['<the role key you copied above>']
|
||||
});
|
||||
}
|
||||
|
||||
```js reference
|
||||
https://github.com/zitadel/actions/blob/main/examples/add_user_grant.js
|
||||
```
|
||||
|
||||
## Run the action when a user registers
|
||||
|
@ -1,53 +1,55 @@
|
||||
version: '3.8'
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
|
||||
zitadel:
|
||||
restart: 'always'
|
||||
restart: "always"
|
||||
networks:
|
||||
- 'zitadel'
|
||||
image: 'ghcr.io/zitadel/zitadel:stable'
|
||||
- "zitadel"
|
||||
image: "ghcr.io/zitadel/zitadel:stable"
|
||||
command: 'start-from-init --config /example-zitadel-config.yaml --config /example-zitadel-secrets.yaml --steps /example-zitadel-init-steps.yaml --masterkey "${ZITADEL_MASTERKEY}" --tlsMode disabled'
|
||||
depends_on:
|
||||
chown:
|
||||
condition: 'service_completed_successfully'
|
||||
certs:
|
||||
condition: "service_completed_successfully"
|
||||
ports:
|
||||
- '8080:8080'
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- './example-zitadel-config.yaml:/example-zitadel-config.yaml:ro'
|
||||
- './example-zitadel-secrets.yaml:/example-zitadel-secrets.yaml:ro'
|
||||
- './example-zitadel-init-steps.yaml:/example-zitadel-init-steps.yaml:ro'
|
||||
- 'zitadel-certs:/crdb-certs:ro'
|
||||
- "./example-zitadel-config.yaml:/example-zitadel-config.yaml:ro"
|
||||
- "./example-zitadel-secrets.yaml:/example-zitadel-secrets.yaml:ro"
|
||||
- "./example-zitadel-init-steps.yaml:/example-zitadel-init-steps.yaml:ro"
|
||||
- "zitadel-certs:/crdb-certs:ro"
|
||||
|
||||
chown:
|
||||
image: 'cockroachdb/cockroach:v22.1.0'
|
||||
entrypoint: [ '/bin/bash', '-c' ]
|
||||
command: [ 'cp /certs/ca.crt /zitadel-certs/ && cp /certs/client.root.crt /zitadel-certs/ && cp /certs/client.root.key /zitadel-certs/ && chown 1000:1000 /zitadel-certs/* && chmod 0400 /zitadel-certs/*' ]
|
||||
certs:
|
||||
image: "cockroachdb/cockroach:v22.1.0"
|
||||
entrypoint: ["/bin/bash", "-c"]
|
||||
command:
|
||||
[
|
||||
"cp /certs/* /zitadel-certs/ && cockroach cert create-client --overwrite --certs-dir /zitadel-certs/ --ca-key /zitadel-certs/ca.key zitadel_user && chown 1000:1000 /zitadel-certs/*",
|
||||
]
|
||||
volumes:
|
||||
- 'certs:/certs:ro'
|
||||
- 'zitadel-certs:/zitadel-certs:rw'
|
||||
- "certs:/certs:ro"
|
||||
- "zitadel-certs:/zitadel-certs:rw"
|
||||
depends_on:
|
||||
my-cockroach-db:
|
||||
condition: 'service_healthy'
|
||||
condition: "service_healthy"
|
||||
|
||||
my-cockroach-db:
|
||||
restart: 'always'
|
||||
restart: "always"
|
||||
networks:
|
||||
- 'zitadel'
|
||||
image: 'cockroachdb/cockroach:v22.1.0'
|
||||
command: 'start-single-node --advertise-addr my-cockroach-db'
|
||||
- "zitadel"
|
||||
image: "cockroachdb/cockroach:v22.1.0"
|
||||
command: "start-single-node --advertise-addr my-cockroach-db"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health?ready=1"]
|
||||
interval: '10s'
|
||||
timeout: '30s'
|
||||
interval: "10s"
|
||||
timeout: "30s"
|
||||
retries: 5
|
||||
start_period: '20s'
|
||||
start_period: "20s"
|
||||
ports:
|
||||
- '9090:8080'
|
||||
- '26257:26257'
|
||||
- "9090:8080"
|
||||
- "26257:26257"
|
||||
volumes:
|
||||
- 'certs:/cockroach/certs:rw'
|
||||
- 'data:/cockroach/cockroach-data:rw'
|
||||
- "certs:/cockroach/certs:rw"
|
||||
- "data:/cockroach/cockroach-data:rw"
|
||||
|
||||
networks:
|
||||
zitadel:
|
||||
|
@ -6,7 +6,5 @@ Database:
|
||||
User:
|
||||
# If the user doesn't exist already, it is created
|
||||
Username: 'zitadel_user'
|
||||
Password: 'Secret_DB_User_Password'
|
||||
Admin:
|
||||
Username: 'root'
|
||||
Password: ''
|
||||
|
@ -1,8 +1,9 @@
|
||||
## Cockroach
|
||||
|
||||
The default database of ZITADEL is [CockroachDB](https://www.cockroachlabs.com). The SQL database provides a bunch of features like horizontal scalability, data reginality and many more.
|
||||
The default database of ZITADEL is [CockroachDB](https://www.cockroachlabs.com). The SQL database provides a bunch of features like horizontal scalability, data regionality and many more.
|
||||
|
||||
The default configuration of the database looks like this:
|
||||
|
||||
```yaml
|
||||
Database:
|
||||
cockroach:
|
||||
@ -29,4 +30,4 @@ Database:
|
||||
RootCert: ""
|
||||
Cert: ""
|
||||
Key: ""
|
||||
```
|
||||
```
|
||||
|
@ -2,14 +2,14 @@
|
||||
title: HTTP/2 Support
|
||||
---
|
||||
|
||||
ZITADEL follows a strict API first approach and makes heavy use of the modern API framework called [gRPC](https://grpc.io/).
|
||||
ZITADEL follows a strict API first approach and makes heavy use of the modern API framework called [gRPC](https://grpc.io/).
|
||||
Besides gRPC all APIs are also available in an openapi Rest fashion as well as in gRPC-web for compatibilty towards browser integrations.
|
||||
|
||||
To make us of gRPC it is vital to allow your clients to communicate with ZITADEL with [HTTP/2](https://en.wikipedia.org/wiki/HTTP/2).
|
||||
|
||||
Sometimes you need to configure explicitly that you want to use HTTP/2 if you run ZITADEL behind a proxy and below you should find examples for different vendors and projects.
|
||||
Sometimes you need to configure explicitly that you want to use HTTP/2 if you run ZITADEL behind a reverse proxy and below you should find examples for different vendors and projects.
|
||||
|
||||
Furthermore it is important to notice that by default HTTP/2 is always encrypted, but if you want to run ZITADEL without TLS from your proxy or service mesh this is possible through [h2c](https://httpd.apache.org/docs/2.4/howto/http2.html).
|
||||
Furthermore it is important to notice that by default HTTP/2 is always encrypted, but if you want to run ZITADEL without TLS from your reverse proxy or service mesh this is possible through [h2c](https://httpd.apache.org/docs/2.4/howto/http2.html).
|
||||
Oftentimes when you run ZITADEL inside a service mesh, or a servelerss offering (e.g. Google Cloud Run, Knative, ...) you will need h2c.
|
||||
|
||||
You can read more about ZITADEL's [TLSs modes here](/docs/guides/manage/self-hosted/tls_modes).
|
||||
|
101
docs/docs/guides/manage/self-hosted/production.md
Normal file
101
docs/docs/guides/manage/self-hosted/production.md
Normal file
@ -0,0 +1,101 @@
|
||||
---
|
||||
title: Production Checklist
|
||||
---
|
||||
|
||||
As soon as you successfully deployed ZITADEL as a proof of concept using one of our [deployment guides](/docs/guides/deploy/overview),
|
||||
you are ready to configure ZITADEL for production usage.
|
||||
|
||||
## High Availability
|
||||
|
||||
We recommend running ZITADEL highly available using an orchestrator that schedules ZITADEL on multiple servers, like [Kubernetes](/docs/guides/deploy/kubernetes). For keeping startup times fast when scaling ZITADEL, you should also consider using separate jobs with `zitadel init` and `zitadel setup`, so your workload containers just have to execute `zitadel start`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Read [on the configure page](/docs/guides/manage/self-hosted/configure) about the available options you have to configure ZITADEL.
|
||||
|
||||
## Networking
|
||||
|
||||
- To make ZITADEL available at the domain of your choice, [you need to configure the ExternalDomain property](/docs/guides/manage/self-hosted/custom-domain).
|
||||
- To enable and restrict access to **HTTPS**, head over to [the description of your TLS options](/docs/guides/manage/self-hosted/tls_modes).
|
||||
- If you want to front ZITADEL with a reverse proxy, web application firewall or content delivery network, make sure to support **[HTTP/2](/docs/guides/manage/self-hosted/http2)**.
|
||||
- You can also refer to some **[example reverse proxy configurations](/docs/guides/manage/self-hosted/reverseproxy/reverse_proxy)**.
|
||||
|
||||
## Monitoring
|
||||
|
||||
By default, [**metrics**](docs/apis/observability/metrics) are exposed at /debug/metrics in OpenTelemetry (otel) format.
|
||||
|
||||
Also, you can enable **tracing** in the ZITADEL configuration.
|
||||
|
||||
```yaml
|
||||
Tracing:
|
||||
# Choose one in "otel", "google", "log" and "none"
|
||||
Type: google
|
||||
Fraction: 1
|
||||
MetricPrefix: zitadel
|
||||
```
|
||||
|
||||
## Database
|
||||
|
||||
Depending on your environment, you maybe would want to tweak some settings about how ZITADEL interacts with the database in the database section of your ZITADEL configuration. Read more about your [database configuration options](/docs/guides/manage/self-hosted/database).
|
||||
|
||||
```yaml
|
||||
Database:
|
||||
cockroach:
|
||||
Host: localhost
|
||||
Port: 26257
|
||||
Database: zitadel
|
||||
//highlight-start
|
||||
MaxOpenConns: 20
|
||||
MaxConnLifetime: 30m
|
||||
MaxConnIdleTime: 30m
|
||||
//highlight-end
|
||||
Options: ""
|
||||
```
|
||||
|
||||
You also might want to configure how [projections](/docs/concepts/eventstore/implementation#projections) are computed. These are the default values:
|
||||
|
||||
```yaml
|
||||
Projections:
|
||||
RequeueEvery: 60s
|
||||
RetryFailedAfter: 1s
|
||||
MaxFailureCount: 5
|
||||
ConcurrentInstances: 1
|
||||
BulkLimit: 200
|
||||
MaxIterators: 1
|
||||
Customizations:
|
||||
projects:
|
||||
BulkLimit: 2000
|
||||
```
|
||||
|
||||
## Data Initialization
|
||||
|
||||
- You can configure instance defaults in the DefaultInstance section.
|
||||
If you plan to eventually create [multiple virtual instances](/docs/concepts/structure/instance#multiple-virtual-instances), these defaults take effect.
|
||||
Also, these configurations apply to the first instance, that ZITADEL automatically creates for you.
|
||||
Especially the following properties are of special interest for your production setup.
|
||||
|
||||
```yaml
|
||||
DefaultInstance:
|
||||
OIDCSettings:
|
||||
AccessTokenLifetime: 12h
|
||||
IdTokenLifetime: 12h
|
||||
RefreshTokenIdleExpiration: 720h #30d
|
||||
RefreshTokenExpiration: 2160h #90d
|
||||
# this configuration sets the default email configuration
|
||||
SMTPConfiguration:
|
||||
# configuration of the host
|
||||
SMTP:
|
||||
#for example smtp.mailtrap.io:2525
|
||||
Host:
|
||||
User:
|
||||
Password:
|
||||
TLS:
|
||||
# if the host of the sender is different from ExternalDomain set DefaultInstance.DomainPolicy.SMTPSenderAddressMatchesInstanceDomain to false
|
||||
From:
|
||||
FromName:
|
||||
```
|
||||
|
||||
- If you don't want to use the DefaultInstance configuration for the first instance that ZITADEL automatically creates for you during the [setup phase](/docs/guides/manage/self-hosted/configure#database-initialization), you can provide a FirstInstance YAML section using the --steps argument.
|
||||
- Learn how to configure ZITADEL via the [Console user interface](/docs/guides/manage/console/overview).
|
||||
- Probably, you also want to [apply your custom branding](/docs/guides/manage/customize/branding), [hook into certain events](/docs/guides/manage/customize/behavior), [customize texts](/docs/guides/manage/customize/texts) or [add metadata to your users](/docs/guides/manage/customize/user-metadata).
|
||||
- If you want to automatically create ZITADEL resources, you can use the [ZITADEL Terraform Provider](/docs/guides/manage/terraform/basics).
|
@ -1,4 +0,0 @@
|
||||
## More information
|
||||
|
||||
- [You can read here about the TLS Modes](./tls_modes)
|
||||
- [And here about how ZITADEL makes use of HTTP/2](./http2)
|
@ -1,53 +0,0 @@
|
||||
---
|
||||
title: Proxy Configuration
|
||||
---
|
||||
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Zcloud from './_zitadel_cloud.mdx'
|
||||
import Nginx from './_nginx.mdx'
|
||||
import Traefik from './_traefik.mdx'
|
||||
import Caddy from './_caddy.mdx'
|
||||
import Cftunnel from './_cloudflare_tunnel.mdx'
|
||||
import Cloudflare from './_cloudflare.mdx'
|
||||
import More from './_more.mdx'
|
||||
|
||||
# Proxy Configuration
|
||||
|
||||
<Tabs
|
||||
groupId="proxy-vendor"
|
||||
default="zcloud"
|
||||
values={[
|
||||
{'label': 'ZITADEL Cloud', 'value': 'zcloud'},
|
||||
{'label': 'NGINX', 'value': 'nginx'},
|
||||
{'label': 'Traefik', 'value': 'traefik'},
|
||||
{'label': 'Caddy', 'value': 'caddy'},
|
||||
{'label': 'Cloudflare Tunnel', 'value': 'cftunnel'},
|
||||
{'label': 'Cloudflare', 'value': 'cf'}
|
||||
]}
|
||||
>
|
||||
<TabItem value="zcloud">
|
||||
<Zcloud/>
|
||||
<More/>
|
||||
</TabItem>
|
||||
<TabItem value="nginx">
|
||||
<Nginx/>
|
||||
<More/>
|
||||
</TabItem>
|
||||
<TabItem value="traefik">
|
||||
<Traefik/>
|
||||
<More/>
|
||||
</TabItem>
|
||||
<TabItem value="caddy">
|
||||
<Caddy/>
|
||||
<More/>
|
||||
</TabItem>
|
||||
<TabItem value="cftunnel">
|
||||
<Cftunnel/>
|
||||
<More/>
|
||||
</TabItem>
|
||||
<TabItem value="cf">
|
||||
<Cloudflare/>
|
||||
<More/>
|
||||
</TabItem>
|
||||
</Tabs>
|
@ -1,6 +1,6 @@
|
||||
## TLS mode external
|
||||
|
||||
```bash
|
||||
```
|
||||
https://localhost {
|
||||
reverse_proxy h2c://localhost:8080
|
||||
tls internal #only non production
|
||||
@ -9,7 +9,7 @@ https://localhost {
|
||||
|
||||
## TLS mode enabled
|
||||
|
||||
```bash
|
||||
```
|
||||
https://localhost {
|
||||
reverse_proxy https://localhost:8080
|
||||
tls internal #only non production
|
||||
@ -18,7 +18,7 @@ https://localhost {
|
||||
|
||||
## TLS mode disabled
|
||||
|
||||
```bash
|
||||
```
|
||||
http://localhost {
|
||||
reverse_proxy h2c://localhost:8080
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
- [Make sure HTTP/2 is enabled](https://support.cloudflare.com/hc/en-us/articles/200168076-Understanding-Cloudflare-HTTP-2-and-HTTP-3-Support)
|
||||
- [Verify that gRPC is enabled](https://support.cloudflare.com/hc/en-us/articles/360050483011-Understanding-Cloudflare-gRPC-support)
|
||||
- [Verify that traffic is proxied through cloudflare](https://developers.cloudflare.com/dns/manage-dns-records/reference/proxied-dns-records/)
|
||||
- [Configure ZITADEL to use the TLS Mode enabled](./tls_modes#enabled)
|
||||
- [Configure ZITADEL to use the TLS Mode enabled](/docs/guides/manage/self-hosted/tls_modes#enabled)
|
||||
|
||||
:::info
|
||||
[Cloudflare does only support gRPC with TLS!](https://support.cloudflare.com/hc/en-us/articles/360050483011-Understanding-Cloudflare-gRPC-support)
|
||||
@ -14,5 +14,5 @@
|
||||
If something is not working please check the cloudflare WAF rules for potential violations.
|
||||
These two rules are known to be triggered:
|
||||
|
||||
- 100001 Anomaly:Header:User-Agent - Missing Cloudflare Specials
|
||||
- 100004 Anomaly:Header:User-Agent, Anomaly:Header:Referer - Missing or empty
|
||||
- 100001 Anomaly:Header:User-Agent - Missing Cloudflare Specials
|
||||
- 100004 Anomaly:Header:User-Agent, Anomaly:Header:Referer - Missing or empty
|
166
docs/docs/guides/manage/self-hosted/reverseproxy/_httpd.mdx
Normal file
166
docs/docs/guides/manage/self-hosted/reverseproxy/_httpd.mdx
Normal file
@ -0,0 +1,166 @@
|
||||
## TLS mode external
|
||||
|
||||
```
|
||||
LoadModule mpm_event_module modules/mod_mpm_event.so
|
||||
LoadModule authn_file_module modules/mod_authn_file.so
|
||||
LoadModule authn_core_module modules/mod_authn_core.so
|
||||
LoadModule authz_host_module modules/mod_authz_host.so
|
||||
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
|
||||
LoadModule authz_user_module modules/mod_authz_user.so
|
||||
LoadModule authz_core_module modules/mod_authz_core.so
|
||||
LoadModule access_compat_module modules/mod_access_compat.so
|
||||
LoadModule auth_basic_module modules/mod_auth_basic.so
|
||||
LoadModule reqtimeout_module modules/mod_reqtimeout.so
|
||||
LoadModule filter_module modules/mod_filter.so
|
||||
LoadModule mime_module modules/mod_mime.so
|
||||
LoadModule log_config_module modules/mod_log_config.so
|
||||
LoadModule env_module modules/mod_env.so
|
||||
LoadModule headers_module modules/mod_headers.so
|
||||
LoadModule setenvif_module modules/mod_setenvif.so
|
||||
LoadModule version_module modules/mod_version.so
|
||||
LoadModule proxy_module modules/mod_proxy.so
|
||||
LoadModule proxy_http_module modules/mod_proxy_http.so
|
||||
LoadModule ssl_module modules/mod_ssl.so
|
||||
LoadModule proxy_http2_module modules/mod_proxy_http2.so
|
||||
LoadModule unixd_module modules/mod_unixd.so
|
||||
LoadModule status_module modules/mod_status.so
|
||||
LoadModule autoindex_module modules/mod_autoindex.so
|
||||
LoadModule dir_module modules/mod_dir.so
|
||||
LoadModule alias_module modules/mod_alias.so
|
||||
LoadModule rewrite_module modules/mod_rewrite.so
|
||||
|
||||
ServerRoot "/usr/local/apache2"
|
||||
LogLevel warn
|
||||
ErrorLog /proc/self/fd/2
|
||||
CustomLog /proc/self/fd/1 "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
|
||||
|
||||
ServerName my.domain
|
||||
Listen 80
|
||||
Listen 443
|
||||
|
||||
SSLRandomSeed startup builtin
|
||||
SSLRandomSeed connect builtin
|
||||
|
||||
<VirtualHost *:80>
|
||||
ServerName my.domain
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTPS} off
|
||||
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:443>
|
||||
ServerName my.domain
|
||||
ProxyPreserveHost On
|
||||
SSLCertificateFile /certs/server.crt
|
||||
SSLCertificateKeyFile /certs/server.key
|
||||
ProxyPass / h2c://localhost:8080/
|
||||
ProxyPassReverse / h2c://localhost:8080/
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
## TLS mode enabled
|
||||
|
||||
```
|
||||
LoadModule mpm_event_module modules/mod_mpm_event.so
|
||||
LoadModule authn_file_module modules/mod_authn_file.so
|
||||
LoadModule authn_core_module modules/mod_authn_core.so
|
||||
LoadModule authz_host_module modules/mod_authz_host.so
|
||||
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
|
||||
LoadModule authz_user_module modules/mod_authz_user.so
|
||||
LoadModule authz_core_module modules/mod_authz_core.so
|
||||
LoadModule access_compat_module modules/mod_access_compat.so
|
||||
LoadModule auth_basic_module modules/mod_auth_basic.so
|
||||
LoadModule reqtimeout_module modules/mod_reqtimeout.so
|
||||
LoadModule filter_module modules/mod_filter.so
|
||||
LoadModule mime_module modules/mod_mime.so
|
||||
LoadModule log_config_module modules/mod_log_config.so
|
||||
LoadModule env_module modules/mod_env.so
|
||||
LoadModule headers_module modules/mod_headers.so
|
||||
LoadModule setenvif_module modules/mod_setenvif.so
|
||||
LoadModule version_module modules/mod_version.so
|
||||
LoadModule proxy_module modules/mod_proxy.so
|
||||
LoadModule proxy_http_module modules/mod_proxy_http.so
|
||||
LoadModule ssl_module modules/mod_ssl.so
|
||||
LoadModule proxy_http2_module modules/mod_proxy_http2.so
|
||||
LoadModule unixd_module modules/mod_unixd.so
|
||||
LoadModule status_module modules/mod_status.so
|
||||
LoadModule autoindex_module modules/mod_autoindex.so
|
||||
LoadModule dir_module modules/mod_dir.so
|
||||
LoadModule alias_module modules/mod_alias.so
|
||||
LoadModule rewrite_module modules/mod_rewrite.so
|
||||
LoadModule http2_module modules/mod_http2.so
|
||||
|
||||
ServerRoot "/usr/local/apache2"
|
||||
LogLevel debug
|
||||
ErrorLog /proc/self/fd/2
|
||||
CustomLog /proc/self/fd/1 "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
|
||||
|
||||
ServerName my.domain
|
||||
Listen 80
|
||||
Listen 443
|
||||
|
||||
SSLRandomSeed startup builtin
|
||||
SSLRandomSeed connect builtin
|
||||
|
||||
<VirtualHost *:80>
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTPS} off
|
||||
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:443>
|
||||
ProxyPreserveHost On
|
||||
SSLEngine on
|
||||
SSLProxyEngine on
|
||||
SSLCertificateFile /certs/server.crt
|
||||
SSLCertificateKeyFile /certs/server.key
|
||||
ProxyPass / h2://localhost:8080/
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
## TLS mode disabled
|
||||
|
||||
```
|
||||
LoadModule mpm_event_module modules/mod_mpm_event.so
|
||||
LoadModule authn_file_module modules/mod_authn_file.so
|
||||
LoadModule authn_core_module modules/mod_authn_core.so
|
||||
LoadModule authz_host_module modules/mod_authz_host.so
|
||||
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
|
||||
LoadModule authz_user_module modules/mod_authz_user.so
|
||||
LoadModule authz_core_module modules/mod_authz_core.so
|
||||
LoadModule access_compat_module modules/mod_access_compat.so
|
||||
LoadModule auth_basic_module modules/mod_auth_basic.so
|
||||
LoadModule reqtimeout_module modules/mod_reqtimeout.so
|
||||
LoadModule filter_module modules/mod_filter.so
|
||||
LoadModule mime_module modules/mod_mime.so
|
||||
LoadModule log_config_module modules/mod_log_config.so
|
||||
LoadModule env_module modules/mod_env.so
|
||||
LoadModule headers_module modules/mod_headers.so
|
||||
LoadModule setenvif_module modules/mod_setenvif.so
|
||||
LoadModule version_module modules/mod_version.so
|
||||
LoadModule proxy_module modules/mod_proxy.so
|
||||
LoadModule proxy_http_module modules/mod_proxy_http.so
|
||||
LoadModule ssl_module modules/mod_ssl.so
|
||||
LoadModule proxy_http2_module modules/mod_proxy_http2.so
|
||||
LoadModule unixd_module modules/mod_unixd.so
|
||||
LoadModule status_module modules/mod_status.so
|
||||
LoadModule autoindex_module modules/mod_autoindex.so
|
||||
LoadModule dir_module modules/mod_dir.so
|
||||
LoadModule alias_module modules/mod_alias.so
|
||||
LoadModule rewrite_module modules/mod_rewrite.so
|
||||
|
||||
ServerRoot "/usr/local/apache2"
|
||||
LogLevel warn
|
||||
ErrorLog /proc/self/fd/2
|
||||
CustomLog /proc/self/fd/1 "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
|
||||
|
||||
ServerName my.domain
|
||||
Listen 80
|
||||
|
||||
<VirtualHost *:80>
|
||||
ServerName my.domain
|
||||
ProxyPreserveHost On
|
||||
ProxyPass / h2c://localhost:8080/
|
||||
ProxyPassReverse / h2c://localhost:8080/
|
||||
</VirtualHost>
|
||||
```
|
@ -0,0 +1,4 @@
|
||||
## More information
|
||||
|
||||
- [You can read here about the TLS Modes](/docs/guides/manage/self-hosted/tls_modes)
|
||||
- [And here about how ZITADEL makes use of HTTP/2](/docs/guides/manage/self-hosted/http2)
|
@ -1,6 +1,6 @@
|
||||
## TLS mode external
|
||||
|
||||
```bash
|
||||
```
|
||||
worker_processes 1;
|
||||
events {
|
||||
worker_connections 1024;
|
||||
@ -12,7 +12,7 @@ http {
|
||||
|
||||
ssl_certificate ssl/certificate.pem;
|
||||
ssl_certificate_key ssl/key.pem;
|
||||
|
||||
|
||||
location / {
|
||||
grpc_pass grpc://localhost:8080;
|
||||
grpc_set_header Host $host;
|
||||
@ -33,7 +33,7 @@ with
|
||||
|
||||
## TLS mode enabled
|
||||
|
||||
```bash
|
||||
```
|
||||
worker_processes 1;
|
||||
events {
|
||||
worker_connections 1024;
|
||||
@ -45,7 +45,7 @@ http {
|
||||
|
||||
ssl_certificate ssl/certificate.pem;
|
||||
ssl_certificate_key ssl/key.pem;
|
||||
|
||||
|
||||
location / {
|
||||
grpc_pass grpcs://localhost:8080;
|
||||
grpc_set_header Host $host;
|
||||
@ -66,7 +66,7 @@ with
|
||||
|
||||
## TLS mode disabled
|
||||
|
||||
```bash
|
||||
```
|
||||
worker_processes 1;
|
||||
events {
|
||||
worker_connections 1024;
|
||||
@ -75,7 +75,7 @@ events {
|
||||
http {
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
|
||||
location / {
|
||||
grpc_pass grpc://localhost:8080;
|
||||
grpc_set_header Host $host;
|
@ -0,0 +1,59 @@
|
||||
---
|
||||
title: Reverse Proxy Configuration
|
||||
---
|
||||
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Zcloud from "./_zitadel_cloud.mdx";
|
||||
import Nginx from "./_nginx.mdx";
|
||||
import Traefik from "./_traefik.mdx";
|
||||
import Caddy from "./_caddy.mdx";
|
||||
import Httpd from "./_httpd.mdx";
|
||||
import Cftunnel from "./_cloudflare_tunnel.mdx";
|
||||
import Cloudflare from "./_cloudflare.mdx";
|
||||
import More from "./_more.mdx";
|
||||
|
||||
# Proxy Configuration
|
||||
|
||||
<Tabs
|
||||
groupId="proxy-vendor"
|
||||
default="zcloud"
|
||||
values={[
|
||||
{ label: "ZITADEL Cloud", value: "zcloud" },
|
||||
{ label: "NGINX", value: "nginx" },
|
||||
{ label: "Traefik", value: "traefik" },
|
||||
{ label: "Caddy", value: "caddy" },
|
||||
{ label: "Apache httpd", value: "httpd" },
|
||||
{ label: "Cloudflare Tunnel", value: "cftunnel" },
|
||||
{ label: "Cloudflare", value: "cf" },
|
||||
]}
|
||||
>
|
||||
<TabItem value="zcloud">
|
||||
<Zcloud />
|
||||
<More />
|
||||
</TabItem>
|
||||
<TabItem value="nginx">
|
||||
<Nginx />
|
||||
<More />
|
||||
</TabItem>
|
||||
<TabItem value="traefik">
|
||||
<Traefik />
|
||||
<More />
|
||||
</TabItem>
|
||||
<TabItem value="caddy">
|
||||
<Caddy />
|
||||
<More />
|
||||
</TabItem>
|
||||
<TabItem value="httpd">
|
||||
<Httpd />
|
||||
<More />
|
||||
</TabItem>
|
||||
<TabItem value="cftunnel">
|
||||
<Cftunnel />
|
||||
<More />
|
||||
</TabItem>
|
||||
<TabItem value="cf">
|
||||
<Cloudflare />
|
||||
<More />
|
||||
</TabItem>
|
||||
</Tabs>
|
48
docs/docs/guides/solution-scenarios/configurations.mdx
Normal file
48
docs/docs/guides/solution-scenarios/configurations.mdx
Normal file
@ -0,0 +1,48 @@
|
||||
---
|
||||
title: How to configure ZITADEL for your scenario
|
||||
---
|
||||
|
||||
Each customer does have different needs and use-cases. In ZITADEL you are able to configure your settings depending on your needs.
|
||||
In this section we show you the different use-cases we have already experienced, that could interest you.
|
||||
|
||||
|
||||
## Automatically redirect users if the organization has only one identity provider
|
||||
|
||||
You have different customers (organizations) in your ZITADEL instance and they have different needs on how to authenticate their users. One of your customers does only allow login with an external identity provider like Google, Azure AD, and so on.
|
||||
If a user of this organization wants to login, you don't want them to enter their username in the ZITADEL Login UI, they should be redirected directly to the identity provider without their interaction.
|
||||
|
||||
### Settings
|
||||
|
||||
1. Go to the "Identity Providers" Settings of the organization
|
||||
2. Configure the needed identity provider: Read this [guide](../integrate/identity-brokering.md) if you don't know how
|
||||
3. Go to the "Login Behavior and Security" settings of the organization
|
||||
4. Disable "Username Password Allowed" and enable "External IDP allowed" in the Advanced Section
|
||||
|
||||
Now your application can send either the organizations id (`urn:zitadel:iam:org:id:{id}`) or organizations primary domain (`urn:zitadel:iam:org:domain:primary:{domainname}`) scope on your authorization request to identify on which organization the users should be logged in.
|
||||
|
||||
More about the [scopes](../../apis/openidoauth/scopes#reserved-scopes)
|
||||
|
||||
## Custom Application Domain per Organization
|
||||
|
||||
If you have an application that runs a dedicated domain for each customer you need to instruct ZITADEL to allow redirection for each domain specifically to safeguard against phishing attacks.
|
||||
|
||||
Example:
|
||||
MyApplication: customer-a.app.com
|
||||
ZITADEL Login: login.app.com
|
||||
|
||||
In the OIDC Authorization request you always have to send the redirect URI to where you like to be redirected after login.
|
||||
To handle this scenario it is possible to register multiple URIs on each application in ZITADEL, the only criteria is that the requested URI has to match one of the registered URIs.
|
||||
|
||||
Read more about [applications](../manage/console/applications) and the [redirect urls](../manage/console/applications#redirect-uris)
|
||||
|
||||
### Trigger organization in ZITADEL login
|
||||
|
||||
It is possible to trigger the organization directly with the authorization request to ZITADEL.
|
||||
|
||||
This will have the following impacts:
|
||||
- Trigger organization login behaviour settings
|
||||
- Trigger organization branding
|
||||
- Only allow users from selected organization to login
|
||||
|
||||
To request the organization send either the the organization id (`urn:zitadel:iam:org:id:{id}`) or organization primary domain (`urn:zitadel:iam:org:domain:primary:{domainname}`) scope on your authentication request from your application.
|
||||
More about the [scopes](../../apis/openidoauth/scopes#reserved-scopes)
|
@ -14,7 +14,6 @@ In this session your second level support and operations team will gain an under
|
||||
- Event types
|
||||
- Database schemas and compute models
|
||||
- Accessing database
|
||||
- Validation of tokens
|
||||
- Observability (Logs, Errors, Metrics, Tracing)
|
||||
- Operations best practices (Deployment, Backup, Networking etc.)
|
||||
- Q&A
|
||||
@ -32,5 +31,6 @@ In this hands-on training your employees will get a complete overview of the sys
|
||||
- Walk-though all features
|
||||
- Users / Manuals
|
||||
- APIs
|
||||
- Validation of tokens
|
||||
- Client integration best-practices
|
||||
- Q&A
|
||||
|
@ -18,15 +18,19 @@ module.exports = {
|
||||
"data-api": "/proxy/api/event",
|
||||
},
|
||||
],
|
||||
customFields: {
|
||||
description: "Documentation for ZITADEL - The best of Auth0 and Keycloak combined. Built for the serverless era.",
|
||||
},
|
||||
themeConfig: {
|
||||
metadata: [{name: 'keywords', content: 'zitadel, documentation, jwt, saml, oauth2, authentication, serverless, login, auth, authorization, sso, openid-connect, oidc, mfa, 2fa, passkeys, fido2, docker'}],
|
||||
zoom: {
|
||||
selector: '.markdown :not(em) > img',
|
||||
selector: ".markdown :not(em) > img",
|
||||
background: {
|
||||
light: 'rgb(243, 244, 246)',
|
||||
dark: 'rgb(55, 59, 82)'
|
||||
light: "rgb(243, 244, 246)",
|
||||
dark: "rgb(55, 59, 82)",
|
||||
},
|
||||
// options you can specify via https://github.com/francoischalifour/medium-zoom#usage
|
||||
config: {}
|
||||
config: {},
|
||||
},
|
||||
navbar: {
|
||||
// title: 'ZITADEL',
|
||||
@ -119,7 +123,6 @@ module.exports = {
|
||||
{
|
||||
title: "Legal",
|
||||
items: [
|
||||
|
||||
{
|
||||
label: "Terms and Conditions",
|
||||
href: "/docs/legal/terms-of-service",
|
||||
@ -152,22 +155,26 @@ module.exports = {
|
||||
{
|
||||
label: "Docs v1 (deprecated)",
|
||||
href: "https://docs-v1.zitadel.com/",
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
],
|
||||
copyright: `Copyright © ${new Date().getFullYear()} ZITADEL Docs - Built with Docusaurus.`,
|
||||
},
|
||||
algolia: {
|
||||
appId: "8H6ZKXENLO",
|
||||
apiKey: "124fe1c102a184bc6fc70c75dc84f96f",
|
||||
indexName: 'zitadel',
|
||||
selector: 'div#'
|
||||
},
|
||||
indexName: "zitadel",
|
||||
selector: "div#",
|
||||
},
|
||||
prism: {
|
||||
additionalLanguages: ["csharp", "dart", "groovy", "regex"],
|
||||
},
|
||||
colorMode: {
|
||||
defaultMode: "dark",
|
||||
disableSwitch: false,
|
||||
respectPrefersColorScheme: true,
|
||||
},
|
||||
},
|
||||
presets: [
|
||||
[
|
||||
@ -187,4 +194,5 @@ module.exports = {
|
||||
],
|
||||
],
|
||||
plugins: [require.resolve("docusaurus-plugin-image-zoom")],
|
||||
themes: ["@saucelabs/theme-github-codeblock"],
|
||||
};
|
||||
|
@ -53,6 +53,7 @@
|
||||
"@leichtgewicht/ip-codec": "2.0.4",
|
||||
"@mdx-js/mdx": "1.6.22",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"@saucelabs/theme-github-codeblock": "^0.1.1",
|
||||
"@slorber/static-site-generator-webpack-plugin": "4.0.4",
|
||||
"@svgr/core": "6.2.1",
|
||||
"@svgr/hast-util-to-babel-ast": "6.2.1",
|
||||
@ -159,6 +160,7 @@
|
||||
"stylehacks": "5.1.0",
|
||||
"terser-webpack-plugin": "5.3.1",
|
||||
"type-fest": "2.12.2",
|
||||
"url": "^0.11.0",
|
||||
"wait-on": "6.0.1",
|
||||
"webpack-bundle-analyzer": "4.5.0",
|
||||
"webpack-dev-middleware": "5.3.1",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user