mirror of
https://github.com/zitadel/zitadel.git
synced 2025-03-03 23:35:12 +00:00
Merge branch 'main' into next-rc
# Conflicts: # internal/eventstore/handler/v2/handler.go # internal/eventstore/handler/v2/statement.go
This commit is contained in:
commit
4f8a154a83
@ -3,6 +3,34 @@
|
||||
Dear community!
|
||||
We're excited to announce bi-weekly office hours.
|
||||
|
||||
## #5 Q&A
|
||||
|
||||
Dear community,
|
||||
|
||||
This week's office hour is dedicated for you to drop by and ask any questions you may have about ZITADEL. We are happy to discuss anything, from Actions to Zero downtime deployments.
|
||||
|
||||
Join us on the stage or ask your questions in the chat next Wednesday in the office hours channel on Discord. We're looking forward to have a nice chat with you.
|
||||
|
||||
**What to expect:**
|
||||
|
||||
* **Q&A Session**: Ask your questions and feel free to join the discussion to help others getting their questions answered
|
||||
|
||||
**Details:**
|
||||
|
||||
* **Target Audience:** Developers and IT Ops personnel using ZITADEL
|
||||
* **Topic:** Q\&A session
|
||||
* **When**: Wednesday 25th of September 6 pm UTC
|
||||
* **Duration**: about 1 hour
|
||||
* **Platform:** Zitadel Discord Server (Join us here: https://discord.gg/zitadel-927474939156643850?event=1286221582838272000 )
|
||||
|
||||
**In the meantime:**
|
||||
|
||||
If you have questions upfront, feel free to already post them in the chat of the [office hours channel](https://zitadel.com/office-hours) on our Discord server :gigi:
|
||||
|
||||
We look forward to seeing you there\!
|
||||
|
||||
**P.S.** Spread the word\! Share this announcement with your fellow ZITADEL users who might be interested 📢
|
||||
|
||||
## #4 Login UI deepdive
|
||||
|
||||
Dear community,
|
||||
|
2
Makefile
2
Makefile
@ -135,7 +135,7 @@ core_integration_server_start: core_integration_setup
|
||||
|
||||
.PHONY: core_integration_test_packages
|
||||
core_integration_test_packages:
|
||||
go test -count 1 -tags integration -timeout 30m $$(go list -tags integration ./... | grep "integration_test")
|
||||
go test -race -count 1 -tags integration -timeout 30m $$(go list -tags integration ./... | grep "integration_test")
|
||||
|
||||
.PHONY: core_integration_server_stop
|
||||
core_integration_server_stop:
|
||||
|
@ -183,6 +183,37 @@ Database:
|
||||
Cert: # ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_CERT
|
||||
Key: # ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_KEY
|
||||
|
||||
# Caches are EXPERIMENTAL. The following config may have breaking changes in the future.
|
||||
# If no config is provided, caching is disabled by default.
|
||||
# Caches:
|
||||
# Connectors are reused by caches.
|
||||
# Connectors:
|
||||
# Memory connector works with local server memory.
|
||||
# It is the simplest (and probably fastest) cache implementation.
|
||||
# Unsuitable for deployments with multiple containers,
|
||||
# as each container's cache may hold a different state of the same object.
|
||||
# Memory:
|
||||
# Enabled: true
|
||||
# AutoPrune removes invalidated or expired object from the cache.
|
||||
# AutoPrune:
|
||||
# Interval: 15m
|
||||
# TimeOut: 30s
|
||||
|
||||
# Instance caches auth middleware instances, gettable by domain or ID.
|
||||
# Instance:
|
||||
# Connector must be enabled above.
|
||||
# When connector is empty, this cache will be disabled.
|
||||
# Connector: "memory"
|
||||
# MaxAge: 1h
|
||||
# LastUsage: 10m
|
||||
#
|
||||
# Log enables cache-specific logging. Default to error log to stdout when omitted.
|
||||
# Log:
|
||||
# Level: debug
|
||||
# AddSource: true
|
||||
# Formatter:
|
||||
# Format: text
|
||||
|
||||
Machine:
|
||||
# Cloud-hosted VMs need to specify their metadata endpoint so that the machine can be uniquely identified.
|
||||
Identification:
|
||||
@ -231,7 +262,7 @@ Projections:
|
||||
# The maximum duration a transaction remains open
|
||||
# before it spots left folding additional events
|
||||
# and updates the table.
|
||||
TransactionDuration: 500ms # ZITADEL_PROJECTIONS_TRANSACTIONDURATION
|
||||
TransactionDuration: 1m # ZITADEL_PROJECTIONS_TRANSACTIONDURATION
|
||||
# Time interval between scheduled projections
|
||||
RequeueEvery: 60s # ZITADEL_PROJECTIONS_REQUEUEEVERY
|
||||
# Time between retried database statements resulting from projected events
|
||||
@ -246,10 +277,7 @@ Projections:
|
||||
HandleActiveInstances: 0s # ZITADEL_PROJECTIONS_HANDLEACTIVEINSTANCES
|
||||
# In the Customizations section, all settings from above can be overwritten for each specific projection
|
||||
Customizations:
|
||||
Projects:
|
||||
TransactionDuration: 2s
|
||||
custom_texts:
|
||||
TransactionDuration: 2s
|
||||
BulkLimit: 400
|
||||
project_grant_fields:
|
||||
TransactionDuration: 0s
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
|
||||
"github.com/zitadel/zitadel/internal/authz"
|
||||
authz_es "github.com/zitadel/zitadel/internal/authz/repository/eventsourcing/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/cache"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
crypto_db "github.com/zitadel/zitadel/internal/crypto/database"
|
||||
@ -71,6 +72,7 @@ type ProjectionsConfig struct {
|
||||
EncryptionKeys *encryption.EncryptionKeyConfig
|
||||
SystemAPIUsers map[string]*internal_authz.SystemAPIUser
|
||||
Eventstore *eventstore.Config
|
||||
Caches *cache.CachesConfig
|
||||
|
||||
Admin admin_es.Config
|
||||
Auth auth_es.Config
|
||||
@ -132,6 +134,7 @@ func projections(
|
||||
esV4.Querier,
|
||||
client,
|
||||
client,
|
||||
config.Caches,
|
||||
config.Projections,
|
||||
config.SystemDefaults,
|
||||
keys.IDPConfig,
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
internal_authz "github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/oidc"
|
||||
"github.com/zitadel/zitadel/internal/api/ui/login"
|
||||
"github.com/zitadel/zitadel/internal/cache"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/config/hook"
|
||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
@ -30,6 +31,7 @@ import (
|
||||
type Config struct {
|
||||
ForMirror bool
|
||||
Database database.Config
|
||||
Caches *cache.CachesConfig
|
||||
SystemDefaults systemdefaults.SystemDefaults
|
||||
InternalAuthZ internal_authz.Config
|
||||
ExternalDomain string
|
||||
|
@ -309,6 +309,7 @@ func initProjections(
|
||||
eventstoreV4.Querier,
|
||||
queryDBClient,
|
||||
projectionDBClient,
|
||||
config.Caches,
|
||||
config.Projections,
|
||||
config.SystemDefaults,
|
||||
keys.IDPConfig,
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/ui/console"
|
||||
"github.com/zitadel/zitadel/internal/api/ui/login"
|
||||
auth_es "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing"
|
||||
"github.com/zitadel/zitadel/internal/cache"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/config/hook"
|
||||
"github.com/zitadel/zitadel/internal/config/network"
|
||||
@ -48,6 +49,7 @@ type Config struct {
|
||||
HTTP1HostHeader string
|
||||
WebAuthNName string
|
||||
Database database.Config
|
||||
Caches *cache.CachesConfig
|
||||
Tracing tracing.Config
|
||||
Metrics metrics.Config
|
||||
Profiler profiler.Config
|
||||
|
@ -184,6 +184,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
|
||||
eventstoreV4.Querier,
|
||||
queryDBClient,
|
||||
projectionDBClient,
|
||||
config.Caches,
|
||||
config.Projections,
|
||||
config.SystemDefaults,
|
||||
keys.IDPConfig,
|
||||
@ -454,7 +455,7 @@ func startAPIs(
|
||||
if err := apis.RegisterService(ctx, userschema_v3_alpha.CreateServer(config.SystemDefaults, commands, queries)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, user_v3_alpha.CreateServer(commands, keys.User)); err != nil {
|
||||
if err := apis.RegisterService(ctx, user_v3_alpha.CreateServer(commands)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, webkey.CreateServer(commands, queries)); err != nil {
|
||||
|
@ -28,7 +28,7 @@
|
||||
"@fortawesome/angular-fontawesome": "^0.13.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.4.2",
|
||||
"@grpc/grpc-js": "^1.11.1",
|
||||
"@grpc/grpc-js": "^1.11.2",
|
||||
"@netlify/framework-info": "^9.8.13",
|
||||
"@ngx-translate/core": "^15.0.0",
|
||||
"angular-oauth2-oidc": "^15.0.1",
|
||||
@ -42,14 +42,14 @@
|
||||
"google-protobuf": "^3.21.2",
|
||||
"grpc-web": "^1.4.1",
|
||||
"i18n-iso-countries": "^7.7.0",
|
||||
"libphonenumber-js": "^1.11.4",
|
||||
"libphonenumber-js": "^1.11.8",
|
||||
"material-design-icons-iconfont": "^6.1.1",
|
||||
"moment": "^2.29.4",
|
||||
"ngx-color": "^9.0.0",
|
||||
"opentype.js": "^1.3.4",
|
||||
"rxjs": "~7.8.0",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"tslib": "^2.6.2",
|
||||
"tslib": "^2.7.0",
|
||||
"uuid": "^10.0.0",
|
||||
"zone.js": "~0.13.3"
|
||||
},
|
||||
@ -60,16 +60,16 @@
|
||||
"@angular-eslint/eslint-plugin-template": "18.0.0",
|
||||
"@angular-eslint/schematics": "16.2.0",
|
||||
"@angular-eslint/template-parser": "18.3.0",
|
||||
"@angular/cli": "^16.2.14",
|
||||
"@angular/cli": "^16.2.15",
|
||||
"@angular/compiler-cli": "^16.2.5",
|
||||
"@angular/language-service": "^18.2.2",
|
||||
"@bufbuild/buf": "^1.39.0",
|
||||
"@angular/language-service": "^18.2.4",
|
||||
"@bufbuild/buf": "^1.41.0",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/google-protobuf": "^3.15.3",
|
||||
"@types/jasmine": "~5.1.4",
|
||||
"@types/jasminewd2": "~2.0.13",
|
||||
"@types/jsonwebtoken": "^9.0.6",
|
||||
"@types/node": "^22.5.2",
|
||||
"@types/node": "^22.5.5",
|
||||
"@types/opentype.js": "^1.3.8",
|
||||
"@types/qrcode": "^1.5.2",
|
||||
"@types/uuid": "^10.0.0",
|
||||
@ -77,7 +77,7 @@
|
||||
"@typescript-eslint/parser": "^5.60.1",
|
||||
"codelyzer": "^6.0.2",
|
||||
"eslint": "^8.50.0",
|
||||
"jasmine-core": "~5.2.0",
|
||||
"jasmine-core": "~5.3.0",
|
||||
"jasmine-spec-reporter": "~7.0.0",
|
||||
"karma": "^6.4.2",
|
||||
"karma-chrome-launcher": "^3.2.0",
|
||||
|
@ -26,6 +26,14 @@
|
||||
"@angular-devkit/core" "16.2.14"
|
||||
rxjs "7.8.1"
|
||||
|
||||
"@angular-devkit/architect@0.1602.15":
|
||||
version "0.1602.15"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1602.15.tgz#b70f2456677f6859d4dac4ad80c6b13d00108797"
|
||||
integrity sha512-+yPlUG5c8l7Z/A6dyeV7NQjj4WDWnWWQt+8eW/KInwVwoYiM32ntTJ0M4uU/aDdHuwKQnMLly28AcSWPWKYf2Q==
|
||||
dependencies:
|
||||
"@angular-devkit/core" "16.2.15"
|
||||
rxjs "7.8.1"
|
||||
|
||||
"@angular-devkit/build-angular@^16.2.2":
|
||||
version "16.2.14"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-16.2.14.tgz#0c4e41aa3f67e52b474b2fabeb027aebf6e76566"
|
||||
@ -118,12 +126,24 @@
|
||||
rxjs "7.8.1"
|
||||
source-map "0.7.4"
|
||||
|
||||
"@angular-devkit/schematics@16.2.14":
|
||||
version "16.2.14"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-16.2.14.tgz#819c2ef8bb298e383cb312d9d1411f5970f0328f"
|
||||
integrity sha512-B6LQKInCT8w5zx5Pbroext5eFFRTCJdTwHN8GhcVS8IeKCnkeqVTQLjB4lBUg7LEm8Y7UHXwzrVxmk+f+MBXhw==
|
||||
"@angular-devkit/core@16.2.15":
|
||||
version "16.2.15"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-16.2.15.tgz#44ef98cda82ef82435a2a41507f8c24720d372df"
|
||||
integrity sha512-68BgPWpcjNKz++uvLFG8IZaOH3ti2BWQVqaE3yTIYaMoNt0y0A0X2MUVd7EGbAGUk2JdloWJv5LTPVZMzCuK4w==
|
||||
dependencies:
|
||||
"@angular-devkit/core" "16.2.14"
|
||||
ajv "8.12.0"
|
||||
ajv-formats "2.1.1"
|
||||
jsonc-parser "3.2.0"
|
||||
picomatch "2.3.1"
|
||||
rxjs "7.8.1"
|
||||
source-map "0.7.4"
|
||||
|
||||
"@angular-devkit/schematics@16.2.15":
|
||||
version "16.2.15"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-16.2.15.tgz#cedcb48fdd240db0a779674cf52455a78a4098bb"
|
||||
integrity sha512-C/j2EwapdBMf1HWDuH89bA9B2e511iEYImkyZ+vCSXRwGiWUaZCrhl18bvztpErTrdOLM3mCwNXWEAMXI4zUXA==
|
||||
dependencies:
|
||||
"@angular-devkit/core" "16.2.15"
|
||||
jsonc-parser "3.2.0"
|
||||
magic-string "0.30.1"
|
||||
ora "5.4.1"
|
||||
@ -242,15 +262,15 @@
|
||||
optionalDependencies:
|
||||
parse5 "^7.1.2"
|
||||
|
||||
"@angular/cli@^16.2.14":
|
||||
version "16.2.14"
|
||||
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-16.2.14.tgz#ab58910ae354ee31b89a7479efd5978fd1a3042e"
|
||||
integrity sha512-0y71jtitigVolm4Rim1b8xPQ+B22cGp4Spef2Wunpqj67UowN6tsZaVuWBEQh4u5xauX8LAHKqsvy37ZPWCc4A==
|
||||
"@angular/cli@^16.2.15":
|
||||
version "16.2.15"
|
||||
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-16.2.15.tgz#951d84ef9a7113242b10fe89be1adfa3a94dd6aa"
|
||||
integrity sha512-nNUmt0ZRj2xHH8tGXSJUiusP5rmakAz0f6cc6T4p03OyeShOKdvs9+/F4hzzsM79/ylZofBlFfwYVCBTbOtMqw==
|
||||
dependencies:
|
||||
"@angular-devkit/architect" "0.1602.14"
|
||||
"@angular-devkit/core" "16.2.14"
|
||||
"@angular-devkit/schematics" "16.2.14"
|
||||
"@schematics/angular" "16.2.14"
|
||||
"@angular-devkit/architect" "0.1602.15"
|
||||
"@angular-devkit/core" "16.2.15"
|
||||
"@angular-devkit/schematics" "16.2.15"
|
||||
"@schematics/angular" "16.2.15"
|
||||
"@yarnpkg/lockfile" "1.1.0"
|
||||
ansi-colors "4.1.3"
|
||||
ini "4.1.1"
|
||||
@ -318,10 +338,10 @@
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@angular/language-service@^18.2.2":
|
||||
version "18.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-18.2.2.tgz#8a6b3f224871cb4b1dd5d76a43a1c3884d14aa62"
|
||||
integrity sha512-aROQNQeLf+o+F5OVvE/9BUe/Tpv8pjzmrZlogBbic5cb4IqSNhR4RjxbgIyXBO/6bhLCZwqfmMqRbW2J2xqMkg==
|
||||
"@angular/language-service@^18.2.4":
|
||||
version "18.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-18.2.4.tgz#c449a75bc405bf519fc90f7a9269a98e2a1f7758"
|
||||
integrity sha512-Keg6n8u8xHLhRDTmx4hUqh1AtVFUt8hDxPMYSUu64czjOT5Dnh8XsgKagu563NEjxbDaCzttPuO+y3DlcaDZoQ==
|
||||
|
||||
"@angular/material-moment-adapter@^16.2.4":
|
||||
version "16.2.14"
|
||||
@ -1462,47 +1482,47 @@
|
||||
"@babel/helper-validator-identifier" "^7.24.7"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@bufbuild/buf-darwin-arm64@1.39.0":
|
||||
version "1.39.0"
|
||||
resolved "https://registry.yarnpkg.com/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.39.0.tgz#0ab8453dc7fc7694e5bd39c69d934edc51b81c81"
|
||||
integrity sha512-Ptl0uAGssLxQTzoZhGwv1FFTbzUfcstIpEwMhN+XrwiuqsSxOg9eq/n3yXoci5VJsHokjDUHnWkR3y+j5P/5KA==
|
||||
"@bufbuild/buf-darwin-arm64@1.41.0":
|
||||
version "1.41.0"
|
||||
resolved "https://registry.yarnpkg.com/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.41.0.tgz#a6aee96452f5a624eb7e5b0336833fdd7a3a7911"
|
||||
integrity sha512-+G5DwpIgnm0AkqgxORxoYXVT0RGDcw8P4SXFXcovgvDBkk9rPvEI1dbPF83n3SUxzcu2A2OxC7DxlXszWIh2Gw==
|
||||
|
||||
"@bufbuild/buf-darwin-x64@1.39.0":
|
||||
version "1.39.0"
|
||||
resolved "https://registry.yarnpkg.com/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.39.0.tgz#9c9a211c8039b8cb89b45bf44f338edf82d5e506"
|
||||
integrity sha512-XNCuy9sjQwVJ4NIZqxaTIyzUtlyquSkp/Uuoh5W5thJ3nzZ5RSgvXKF5iXHhZmesrfRGApktwoCx5Am8runsfQ==
|
||||
"@bufbuild/buf-darwin-x64@1.41.0":
|
||||
version "1.41.0"
|
||||
resolved "https://registry.yarnpkg.com/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.41.0.tgz#aac6a6b86f6d1f30c86f70e918d212e067e5257f"
|
||||
integrity sha512-qjkJ/LAWqNk3HX65n+JTt18WtKrhrrAhIu3Dpfbe0eujsxafFZKoPzlWJYybxvsaF9CdEyMMm/OalBPpoosMOA==
|
||||
|
||||
"@bufbuild/buf-linux-aarch64@1.39.0":
|
||||
version "1.39.0"
|
||||
resolved "https://registry.yarnpkg.com/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.39.0.tgz#9778732efbdbbfe02ec821017cc2392ce4a0153f"
|
||||
integrity sha512-Am+hrw94awp/eY027ROXwRQBuwAzOpQ/4zI4dgmgsyhzeWZ8w1LWC8z2SSr8T2cqd0cm52KxtoWMW+B3b2qzbw==
|
||||
"@bufbuild/buf-linux-aarch64@1.41.0":
|
||||
version "1.41.0"
|
||||
resolved "https://registry.yarnpkg.com/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.41.0.tgz#8ac97e7a19cf0c0957ca1b3e690d8c039b0b3468"
|
||||
integrity sha512-5E+MLAF4QHPwAjwVVRRP3Is2U3zpIpQQR7S3di9HlKACbgvefJEBrUfRqQZvHrMuuynQRqjFuZD16Sfvxn9rCQ==
|
||||
|
||||
"@bufbuild/buf-linux-x64@1.39.0":
|
||||
version "1.39.0"
|
||||
resolved "https://registry.yarnpkg.com/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.39.0.tgz#d7ca62c4f506c60011f5a97ca2e8683aa26693b0"
|
||||
integrity sha512-JXVkHoMrTvmpseqdoQPJJ6MRV7/vlloYtvXHHACEzVytYjljOYCNoVET/E5gLBco/edeXFMNc40cCi1KgL3rSw==
|
||||
"@bufbuild/buf-linux-x64@1.41.0":
|
||||
version "1.41.0"
|
||||
resolved "https://registry.yarnpkg.com/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.41.0.tgz#8a272846929215affccb9c271f02948e10f8d4a9"
|
||||
integrity sha512-W4T+uqmdtypzzatv6OXjUzGacZiNzGECogr+qDkJF38MSZd3jHXhTEN2KhRckl3i9rRAnfHBwG68BjCTxxBCOQ==
|
||||
|
||||
"@bufbuild/buf-win32-arm64@1.39.0":
|
||||
version "1.39.0"
|
||||
resolved "https://registry.yarnpkg.com/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.39.0.tgz#efdaf1eca30445f04124c6d829a46a676e6b1dc3"
|
||||
integrity sha512-akdGW02mo04wbLfjNMBQqxC4mPQ/L/vTU8/o79I67GSxyFYt7bKifvYIYhAA39C2gibHyB7ZLmoeRPbaU8wbYA==
|
||||
"@bufbuild/buf-win32-arm64@1.41.0":
|
||||
version "1.41.0"
|
||||
resolved "https://registry.yarnpkg.com/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.41.0.tgz#e26b67b2da15e284326c3d3c38255b443e201e0b"
|
||||
integrity sha512-OsRVoTZHJZYGIphAwaRqcCeYR9Sk5VEMjpCJiFt/dkHxx2acKH4u/7O+633gcCxQL8EnsU2l8AfdbW7sQaOvlg==
|
||||
|
||||
"@bufbuild/buf-win32-x64@1.39.0":
|
||||
version "1.39.0"
|
||||
resolved "https://registry.yarnpkg.com/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.39.0.tgz#09f2b0290818d826847689d6149f8fb0def4ac4b"
|
||||
integrity sha512-jos08UMg9iUZsGjPrNpLXP+FNk6q6GizO+bjee/GcI0kSijIzXYMg14goQr0TKlvqs/+IRAM5vZIokQBYlAENQ==
|
||||
"@bufbuild/buf-win32-x64@1.41.0":
|
||||
version "1.41.0"
|
||||
resolved "https://registry.yarnpkg.com/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.41.0.tgz#b2ff4e9cdb9f73baaad216d35c91c733c4c4b661"
|
||||
integrity sha512-2KJLp7Py0GsfRjDxwBzS17RMpaYFGCvzkwY5CtxfPMw8cg6cE7E36r+vcjHh5dBOj/CumaiXLTwxhCSBtp0V1g==
|
||||
|
||||
"@bufbuild/buf@^1.39.0":
|
||||
version "1.39.0"
|
||||
resolved "https://registry.yarnpkg.com/@bufbuild/buf/-/buf-1.39.0.tgz#65884f55d072b93122959c92b389c1d7d8ab510b"
|
||||
integrity sha512-lm7xb9pc7X04rRjCQ69o9byAAZ7/dsUQGoH+iJ9uBSXQWiwQ1Ts8gneBnuUVsAH2vdW73NFBpmNQGE9XtFauVQ==
|
||||
"@bufbuild/buf@^1.41.0":
|
||||
version "1.41.0"
|
||||
resolved "https://registry.yarnpkg.com/@bufbuild/buf/-/buf-1.41.0.tgz#76077338696009c2f34e7ca1c76baf89a04079f5"
|
||||
integrity sha512-6pN2fqMrPqnIkrC1q9KpXpu7fv3Rul0ZPhT4MSYYj+8VcyR3kbLVk6K+CzzPvYhr4itfotnI3ZVGQ/X/vupECg==
|
||||
optionalDependencies:
|
||||
"@bufbuild/buf-darwin-arm64" "1.39.0"
|
||||
"@bufbuild/buf-darwin-x64" "1.39.0"
|
||||
"@bufbuild/buf-linux-aarch64" "1.39.0"
|
||||
"@bufbuild/buf-linux-x64" "1.39.0"
|
||||
"@bufbuild/buf-win32-arm64" "1.39.0"
|
||||
"@bufbuild/buf-win32-x64" "1.39.0"
|
||||
"@bufbuild/buf-darwin-arm64" "1.41.0"
|
||||
"@bufbuild/buf-darwin-x64" "1.41.0"
|
||||
"@bufbuild/buf-linux-aarch64" "1.41.0"
|
||||
"@bufbuild/buf-linux-x64" "1.41.0"
|
||||
"@bufbuild/buf-win32-arm64" "1.41.0"
|
||||
"@bufbuild/buf-win32-x64" "1.41.0"
|
||||
|
||||
"@colors/colors@1.5.0":
|
||||
version "1.5.0"
|
||||
@ -1810,10 +1830,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
|
||||
integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==
|
||||
|
||||
"@grpc/grpc-js@^1.11.1":
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.11.1.tgz#a92f33e98f1959feffcd1b25a33b113d2c977b70"
|
||||
integrity sha512-gyt/WayZrVPH2w/UTLansS7F9Nwld472JxxaETamrM8HNlsa+jSLNyKAZmhxI2Me4c3mQHFiS1wWHDY1g1Kthw==
|
||||
"@grpc/grpc-js@^1.11.2":
|
||||
version "1.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.11.2.tgz#541a00303e533b5efe9d84ed61b84cdf9dd93ee7"
|
||||
integrity sha512-DWp92gDD7/Qkj7r8kus6/HCINeo3yPZWZ3paKgDgsbKbSpoxKg1yvN8xe2Q8uE3zOsPe3bX8FQX2+XValq2yTw==
|
||||
dependencies:
|
||||
"@grpc/proto-loader" "^0.7.13"
|
||||
"@js-sdsl/ordered-map" "^4.4.2"
|
||||
@ -2884,13 +2904,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
|
||||
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
|
||||
|
||||
"@schematics/angular@16.2.14":
|
||||
version "16.2.14"
|
||||
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-16.2.14.tgz#3aac7e05b6e3919195275cf06ac403d7a3567876"
|
||||
integrity sha512-YqIv727l9Qze8/OL6H9mBHc2jVXzAGRNBYnxYWqWhLbfvuVbbldo6NNIIjgv6lrl2LJSdPAAMNOD5m/f6210ug==
|
||||
"@schematics/angular@16.2.15":
|
||||
version "16.2.15"
|
||||
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-16.2.15.tgz#f3b810842959808f0d65ce816f4f0c1a7463c176"
|
||||
integrity sha512-T7wEGYxidpLAkis+hO5nsVfnWsy6sXf1T9GS8uztC8IYYsnqB9jTVfjVyfhASugZasdmx7+jWv3oCGy6Z5ZehA==
|
||||
dependencies:
|
||||
"@angular-devkit/core" "16.2.14"
|
||||
"@angular-devkit/schematics" "16.2.14"
|
||||
"@angular-devkit/core" "16.2.15"
|
||||
"@angular-devkit/schematics" "16.2.15"
|
||||
jsonc-parser "3.2.0"
|
||||
|
||||
"@sigstore/bundle@^1.1.0":
|
||||
@ -3098,10 +3118,10 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node@*", "@types/node@>=10.0.0", "@types/node@>=13.7.0", "@types/node@^22.5.2":
|
||||
version "22.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.2.tgz#e42344429702e69e28c839a7e16a8262a8086793"
|
||||
integrity sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==
|
||||
"@types/node@*", "@types/node@>=10.0.0", "@types/node@>=13.7.0", "@types/node@^22.5.5":
|
||||
version "22.5.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.5.tgz#52f939dd0f65fc552a4ad0b392f3c466cc5d7a44"
|
||||
integrity sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==
|
||||
dependencies:
|
||||
undici-types "~6.19.2"
|
||||
|
||||
@ -3978,10 +3998,10 @@ blocking-proxy@^1.0.0:
|
||||
dependencies:
|
||||
minimist "^1.2.0"
|
||||
|
||||
body-parser@1.20.2, body-parser@^1.19.0:
|
||||
version "1.20.2"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
|
||||
integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
|
||||
body-parser@1.20.3, body-parser@^1.19.0:
|
||||
version "1.20.3"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6"
|
||||
integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==
|
||||
dependencies:
|
||||
bytes "3.1.2"
|
||||
content-type "~1.0.5"
|
||||
@ -3991,7 +4011,7 @@ body-parser@1.20.2, body-parser@^1.19.0:
|
||||
http-errors "2.0.0"
|
||||
iconv-lite "0.4.24"
|
||||
on-finished "2.4.1"
|
||||
qs "6.11.0"
|
||||
qs "6.13.0"
|
||||
raw-body "2.5.2"
|
||||
type-is "~1.6.18"
|
||||
unpipe "1.0.0"
|
||||
@ -4902,6 +4922,11 @@ encodeurl@~1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
|
||||
|
||||
encodeurl@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
|
||||
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
|
||||
|
||||
encoding@^0.1.13:
|
||||
version "0.1.13"
|
||||
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
|
||||
@ -5276,36 +5301,36 @@ exponential-backoff@^3.1.1:
|
||||
integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==
|
||||
|
||||
express@^4.17.3:
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
|
||||
integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==
|
||||
version "4.20.0"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.20.0.tgz#f1d08e591fcec770c07be4767af8eb9bcfd67c48"
|
||||
integrity sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==
|
||||
dependencies:
|
||||
accepts "~1.3.8"
|
||||
array-flatten "1.1.1"
|
||||
body-parser "1.20.2"
|
||||
body-parser "1.20.3"
|
||||
content-disposition "0.5.4"
|
||||
content-type "~1.0.4"
|
||||
cookie "0.6.0"
|
||||
cookie-signature "1.0.6"
|
||||
debug "2.6.9"
|
||||
depd "2.0.0"
|
||||
encodeurl "~1.0.2"
|
||||
encodeurl "~2.0.0"
|
||||
escape-html "~1.0.3"
|
||||
etag "~1.8.1"
|
||||
finalhandler "1.2.0"
|
||||
fresh "0.5.2"
|
||||
http-errors "2.0.0"
|
||||
merge-descriptors "1.0.1"
|
||||
merge-descriptors "1.0.3"
|
||||
methods "~1.1.2"
|
||||
on-finished "2.4.1"
|
||||
parseurl "~1.3.3"
|
||||
path-to-regexp "0.1.7"
|
||||
path-to-regexp "0.1.10"
|
||||
proxy-addr "~2.0.7"
|
||||
qs "6.11.0"
|
||||
range-parser "~1.2.1"
|
||||
safe-buffer "5.2.1"
|
||||
send "0.18.0"
|
||||
serve-static "1.15.0"
|
||||
send "0.19.0"
|
||||
serve-static "1.16.0"
|
||||
setprototypeof "1.2.0"
|
||||
statuses "2.0.1"
|
||||
type-is "~1.6.18"
|
||||
@ -6482,10 +6507,10 @@ jasmine-core@~2.8.0:
|
||||
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e"
|
||||
integrity sha512-SNkOkS+/jMZvLhuSx1fjhcNWUC/KG6oVyFUGkSBEr9n1axSNduWU8GlI7suaHXr4yxjet6KjrUZxUTE5WzzWwQ==
|
||||
|
||||
jasmine-core@~5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-5.2.0.tgz#7d0aa4c26cb3dbaed201d0505489baf1e48faeca"
|
||||
integrity sha512-tSAtdrvWybZkQmmaIoDgnvHG8ORUNw5kEVlO5CvrXj02Jjr9TZrmjFq7FUiOUzJiOP2wLGYT6PgrQgQF4R1xiw==
|
||||
jasmine-core@~5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-5.3.0.tgz#ed784e5a10af43988d8408bad80b9f08e240a3f8"
|
||||
integrity sha512-zsOmeBKESky4toybvWEikRiZ0jHoBEu79wNArLfMdSnlLMZx3Xcp6CSm2sUcYyoJC+Uyj8LBJap/MUbVSfJ27g==
|
||||
|
||||
jasmine-spec-reporter@~7.0.0:
|
||||
version "7.0.0"
|
||||
@ -6810,10 +6835,10 @@ levn@^0.4.1:
|
||||
prelude-ls "^1.2.1"
|
||||
type-check "~0.4.0"
|
||||
|
||||
libphonenumber-js@^1.11.4:
|
||||
version "1.11.5"
|
||||
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.5.tgz#50a441da5ff9ed9a322d796a14f1e9cbc0fdabdf"
|
||||
integrity sha512-TwHR5BZxGRODtAfz03szucAkjT5OArXr+94SMtAM2pYXIlQNVMrxvb6uSCbnaJJV6QXEyICk7+l6QPgn72WHhg==
|
||||
libphonenumber-js@^1.11.8:
|
||||
version "1.11.8"
|
||||
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.8.tgz#697fdd36500a97bc672d7927d867edf34b4bd2a7"
|
||||
integrity sha512-0fv/YKpJBAgXKy0kaS3fnqoUVN8901vUYAKIGD/MWZaDfhJt1nZjPL3ZzdZBt/G8G8Hw2J1xOIrXWdNHFHPAvg==
|
||||
|
||||
license-webpack-plugin@4.0.2:
|
||||
version "4.0.2"
|
||||
@ -7034,10 +7059,10 @@ memfs@^3.4.12, memfs@^3.4.3:
|
||||
dependencies:
|
||||
fs-monkey "^1.0.4"
|
||||
|
||||
merge-descriptors@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
||||
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
|
||||
merge-descriptors@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
|
||||
integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
|
||||
|
||||
merge-stream@^2.0.0:
|
||||
version "2.0.0"
|
||||
@ -7855,10 +7880,10 @@ path-scurry@^1.11.1:
|
||||
lru-cache "^10.2.0"
|
||||
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
|
||||
path-to-regexp@0.1.7:
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
|
||||
path-to-regexp@0.1.10:
|
||||
version "0.1.10"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b"
|
||||
integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==
|
||||
|
||||
path-type@^4.0.0:
|
||||
version "4.0.0"
|
||||
@ -8145,6 +8170,13 @@ qs@6.11.0:
|
||||
dependencies:
|
||||
side-channel "^1.0.4"
|
||||
|
||||
qs@6.13.0:
|
||||
version "6.13.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
|
||||
integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
|
||||
dependencies:
|
||||
side-channel "^1.0.6"
|
||||
|
||||
qs@~6.5.2:
|
||||
version "6.5.3"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
|
||||
@ -8618,6 +8650,25 @@ send@0.18.0:
|
||||
range-parser "~1.2.1"
|
||||
statuses "2.0.1"
|
||||
|
||||
send@0.19.0:
|
||||
version "0.19.0"
|
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8"
|
||||
integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
|
||||
dependencies:
|
||||
debug "2.6.9"
|
||||
depd "2.0.0"
|
||||
destroy "1.2.0"
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
etag "~1.8.1"
|
||||
fresh "0.5.2"
|
||||
http-errors "2.0.0"
|
||||
mime "1.6.0"
|
||||
ms "2.1.3"
|
||||
on-finished "2.4.1"
|
||||
range-parser "~1.2.1"
|
||||
statuses "2.0.1"
|
||||
|
||||
serialize-javascript@^6.0.0, serialize-javascript@^6.0.1:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2"
|
||||
@ -8638,10 +8689,10 @@ serve-index@^1.9.1:
|
||||
mime-types "~2.1.17"
|
||||
parseurl "~1.3.2"
|
||||
|
||||
serve-static@1.15.0:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
|
||||
integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
|
||||
serve-static@1.16.0:
|
||||
version "1.16.0"
|
||||
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.0.tgz#2bf4ed49f8af311b519c46f272bf6ac3baf38a92"
|
||||
integrity sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==
|
||||
dependencies:
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
@ -8704,7 +8755,7 @@ shell-quote@^1.8.1:
|
||||
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680"
|
||||
integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==
|
||||
|
||||
side-channel@^1.0.4:
|
||||
side-channel@^1.0.4, side-channel@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
|
||||
integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==
|
||||
@ -8956,7 +9007,16 @@ streamroller@^3.1.5:
|
||||
debug "^4.3.4"
|
||||
fs-extra "^8.1.0"
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@ -8993,7 +9053,7 @@ string_decoder@~1.1.1:
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@ -9007,6 +9067,13 @@ strip-ansi@^3.0.0:
|
||||
dependencies:
|
||||
ansi-regex "^2.0.0"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.0.1:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||
@ -9269,10 +9336,10 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.4.1, tslib@^2.6.2:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
|
||||
integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==
|
||||
tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.4.1, tslib@^2.7.0:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
|
||||
integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
|
||||
|
||||
tsutils@^3.21.0:
|
||||
version "3.21.0"
|
||||
@ -9781,7 +9848,7 @@ word-wrap@^1.2.5:
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
|
||||
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
@ -9799,6 +9866,15 @@ wrap-ansi@^6.2.0:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
|
@ -1 +0,0 @@
|
||||
You have to install Pylon as described in [their documentation](https://pylon.cronit.io/docs/installation/).
|
@ -6,7 +6,6 @@ sidebar_label: Pylon
|
||||
import AppJWT from "../imports/_app_jwt.mdx";
|
||||
import ServiceuserJWT from "../imports/_serviceuser_jwt.mdx";
|
||||
import ServiceuserRole from "../imports/_serviceuser_role.mdx";
|
||||
import SetupPylon from "../imports/_setup_pylon.mdx";
|
||||
|
||||
This integration guide demonstrates the recommended way to incorporate ZITADEL into your [Pylon](https://pylon.cronit.io) service.
|
||||
It explains how to check the token validity in the API and how to check for permissions.
|
||||
@ -43,26 +42,27 @@ And the following from the Serviceuser:
|
||||
|
||||
## Setup new Pylon service
|
||||
|
||||
### Setup Pylon
|
||||
Pylon allows you to create a new service using the `npm create pylon` command. This command creates a new Pylon project with a basic project structure and configuration.
|
||||
During the setup process, you can choose your preferred runtime, such as Bun, Node.js, or Cloudflare Workers.
|
||||
|
||||
<SetupPylon />
|
||||
**This guide uses the Bun runtime.**
|
||||
|
||||
### Creating a new project
|
||||
|
||||
To create a new Pylon project, run the following command:
|
||||
|
||||
```bash
|
||||
pylon new my-pylon-project
|
||||
npm create pylon my-pylon@latest
|
||||
```
|
||||
|
||||
This will create a new directory called `my-pylon-project` with a basic Pylon project structure.
|
||||
This will create a new directory called `my-pylon` with a basic Pylon project structure.
|
||||
|
||||
### Project structure
|
||||
|
||||
Pylon projects are structured as follows:
|
||||
|
||||
```
|
||||
my-pylon-project/
|
||||
my-pylon/
|
||||
├── .pylon/
|
||||
├── src/
|
||||
│ ├── index.ts
|
||||
@ -81,16 +81,18 @@ my-pylon-project/
|
||||
Here's an example of a basic Pylon service:
|
||||
|
||||
```ts
|
||||
import { defineService } from "@getcronit/pylon";
|
||||
import { app } from "@getcronit/pylon";
|
||||
|
||||
export default defineService({
|
||||
export const graphql = {
|
||||
Query: {
|
||||
sum: (a: number, b: number) => a + b,
|
||||
},
|
||||
Mutation: {
|
||||
divide: (a: number, b: number) => a / b,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default app;
|
||||
```
|
||||
|
||||
## Secure the API
|
||||
@ -113,6 +115,8 @@ AUTH_PROJECT_ID='250719519163548112'
|
||||
|
||||
2. Copy the `.json`-key-file that you downloaded from the ZITADEL Console into the root folder of your project and rename it to `key.json`.
|
||||
|
||||
3. (Optional) For added convenience in production environments, you can include the content of the .json key file as `AUTH_KEY` in the .env file or as an environment variable.
|
||||
|
||||
### Auth
|
||||
|
||||
Pylon provides a auth module and a decorator to check the validity of the token and the permissions.
|
||||
@ -140,8 +144,7 @@ The following code demonstrates how to create a Pylon service with the required
|
||||
|
||||
```ts
|
||||
import {
|
||||
defineService,
|
||||
PylonAPI,
|
||||
app,
|
||||
auth,
|
||||
requireAuth,
|
||||
getContext,
|
||||
@ -208,7 +211,7 @@ class User {
|
||||
}
|
||||
}
|
||||
|
||||
export default defineService({
|
||||
export const graphql = {
|
||||
Query: {
|
||||
me: User.me,
|
||||
info: () => "Public Data",
|
||||
@ -216,43 +219,43 @@ export default defineService({
|
||||
Mutation: {
|
||||
createUser: User.create,
|
||||
},
|
||||
};
|
||||
|
||||
// Initialize the authentication middleware
|
||||
app.use("*", auth.initialize());
|
||||
|
||||
// Automatically try to create a user for each request for demonstration purposes
|
||||
app.use(async (_, next) => {
|
||||
try {
|
||||
await User.create();
|
||||
} catch {
|
||||
// Ignore errors
|
||||
// Fail silently if the user already exists
|
||||
}
|
||||
|
||||
await next();
|
||||
});
|
||||
|
||||
export const configureApp: PylonAPI["configureApp"] = (app) => {
|
||||
// Initialize the authentication middleware
|
||||
app.use("*", auth.initialize());
|
||||
app.get("/api/info", (c) => {
|
||||
return new Response("Public Data");
|
||||
});
|
||||
|
||||
// Automatically try to create a user for each request for demonstration purposes
|
||||
app.use(async (_, next) => {
|
||||
try {
|
||||
await User.create();
|
||||
} catch {
|
||||
// Ignore errors
|
||||
// Fail silently if the user already exists
|
||||
}
|
||||
// The `auth.require()` middleware is optional here, as the `User.me` method already checks for it.
|
||||
app.get("/api/me", auth.require(), async (c) => {
|
||||
const user = await User.me();
|
||||
|
||||
await next();
|
||||
});
|
||||
return c.json(user);
|
||||
});
|
||||
|
||||
app.get("/api/info", (c) => {
|
||||
return new Response("Public Data");
|
||||
});
|
||||
// A role check for `read:messages` is not required here, as the `user.messages` method already checks for it.
|
||||
app.get("/api/me/messages", auth.require(), async (c) => {
|
||||
const user = await User.me();
|
||||
|
||||
// The `auth.require()` middleware is optional here, as the `User.me` method already checks for it.
|
||||
app.get("/api/me", auth.require(), async (c) => {
|
||||
const user = await User.me();
|
||||
// This will throw an error if the user does not have the `read:messages` role
|
||||
return c.json(await user.messages());
|
||||
});
|
||||
|
||||
return c.json(user);
|
||||
});
|
||||
|
||||
// A role check for `read:messages` is not required here, as the `user.messages` method already checks for it.
|
||||
app.get("/api/me/messages", auth.require(), async (c) => {
|
||||
const user = await User.me();
|
||||
|
||||
// This will throw an error if the user does not have the `read:messages` role
|
||||
return c.json(await user.messages());
|
||||
});
|
||||
};
|
||||
export default app;
|
||||
```
|
||||
|
||||
### Call the API
|
||||
@ -273,7 +276,7 @@ export TOKEN='MtjHodGy4zxKylDOhg6kW90WeEQs2q...'
|
||||
Now you have to start the Pylon service:
|
||||
|
||||
```bash
|
||||
bun run develop
|
||||
bun run dev
|
||||
```
|
||||
|
||||
With the access token, you can then do the following calls:
|
||||
|
166
docs/docs/guides/manage/customize/notification-providers.mdx
Normal file
166
docs/docs/guides/manage/customize/notification-providers.mdx
Normal file
@ -0,0 +1,166 @@
|
||||
---
|
||||
title: SMS, SMTP and HTTP Provider for Notifications
|
||||
sidebar_label: Notification Providers
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
ZITADEL can send messages to users via different notification providers, such as SMS, SMTP, or Webhook (HTTP Provider).
|
||||
While you can add multiple providers to different channels, messages will only be delivered via the actived provider.
|
||||
Message and notification texts can be [customized](./texts) for an instance or for each organization.
|
||||
|
||||
## SMS providers
|
||||
|
||||
ZITADEL integrates with Twilio as SMS provider.
|
||||
|
||||
## SMTP providers
|
||||
|
||||
Integration with most SMTP providers is possible through a generic SMTP provider template that allows you to configure custom SMTP providers.
|
||||
Additionally, integration templates are available for:
|
||||
|
||||
- Amazon SES
|
||||
- Mailgun
|
||||
- Mailjet
|
||||
- Postmark
|
||||
- Sendgrid
|
||||
|
||||
:::info Default Provider ZITADEL Cloud
|
||||
A default SMTP provider is configured for ZITADEL Cloud customers.
|
||||
This provider meant for development and testing purposes and you must replace this provider with your custom SMTP provider for production use cases to guarantee security and reliability of your service.
|
||||
:::
|
||||
|
||||
## Webhook / HTTP provider
|
||||
|
||||
Webhook (HTTP Provider) allows you to fully customize the messages and use integrate with any provider or custom solution to deliver the messages to users.
|
||||
A provider with HTTP type will send the messages and the data to a pre-defined webhook as JSON.
|
||||
|
||||
### Configuring a HTTP provider
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="sms" label="SMS" default>
|
||||
|
||||
First [add a new SMS Provider of type HTTP](/apis/resources/admin/admin-service-add-sms-provider-http) to create a new HTTP provider that can be used to send SMS messages:
|
||||
|
||||
```bash
|
||||
curl -L 'https://$CUSTOM-DOMAIN/admin/v1/sms/http' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Authorization: Bearer <TOKEN>' \
|
||||
-d '{
|
||||
"endpoint": "http://relay.example.com/provider",
|
||||
"description": "provider description"
|
||||
}'
|
||||
```
|
||||
|
||||
Where `endpoint` defines the Webhook endpoint to which the data should be sent to.
|
||||
The result will contain an ID of the provider that we need in the next step.
|
||||
|
||||
You can configure multiple SMS providers at the same time.
|
||||
To use the HTTP provider you need to [activate the SMS provider](/apis/resources/admin/admin-service-activate-sms-provider):
|
||||
|
||||
```bash
|
||||
curl -L 'https://$CUSTOM-DOMAIN/admin/v1/sms/:id/_activate' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Authorization: Bearer <TOKEN>' \
|
||||
-d '{}'
|
||||
```
|
||||
|
||||
The `id` is the provider's ID from the previous step.
|
||||
|
||||
See full API reference for [SMS Providers](/apis/resources/admin/sms-provider) for more details.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="email" label="Email">
|
||||
|
||||
First [add a new Email Provider of type HTTP](/apis/resources/admin/admin-service-add-email-provider-http) to create a new HTTP provider that can be used to send SMS messages:
|
||||
|
||||
```bash
|
||||
curl -L 'https://$CUSTOM-DOMAIN/admin/v1/email/http' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Authorization: Bearer <TOKEN>' \
|
||||
-d '{
|
||||
"endpoint": "http://relay.example.com/provider",
|
||||
"description": "provider description"
|
||||
}'
|
||||
```
|
||||
|
||||
Where `endpoint` defines the Webhook endpoint to which the data should be sent to.
|
||||
The result will contain an ID of the provider that we need in the next step.
|
||||
|
||||
You can configure multiple Email providers at the same time.
|
||||
To use the HTTP provider you need to [activate the Email provider](/apis/resources/admin/admin-service-activate-email-provider):
|
||||
|
||||
```bash
|
||||
curl -L 'https://$CUSTOM-DOMAIN/admin/v1/email/:id/_activate' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Authorization: Bearer <TOKEN>' \
|
||||
-d '{}'
|
||||
```
|
||||
|
||||
The `id` is the provider's ID from the previous step.
|
||||
|
||||
See full API reference for [Email Providers](/apis/resources/admin/admin-service-list-email-providers) for more details.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### HTTP provider payload
|
||||
|
||||
In case of the Twilio and Email providers, the messages will be sent as before, in case of the HTTP providers the content of the messages is the same but as a HTTP call.
|
||||
|
||||
Here an example of the body of an payload sent via Email HTTP provider:
|
||||
|
||||
```json
|
||||
{
|
||||
"contextInfo": {
|
||||
"eventType": "user.human.initialization.code.added",
|
||||
"provider": {
|
||||
"id": "285181292935381355",
|
||||
"description": "test"
|
||||
},
|
||||
"recipientEmailAddress": "example@zitadel.com"
|
||||
},
|
||||
"templateData": {
|
||||
"title": "Zitadel - Initialize User",
|
||||
"preHeader": "Initialize User",
|
||||
"subject": "Initialize User",
|
||||
"greeting": "Hello GivenName FamilyName,",
|
||||
"text": "This user was created in Zitadel. Use the username Username to login. Please click the button below to finish the initialization process. (Code 0M53RF) If you didn't ask for this mail, please ignore it.",
|
||||
"url": "http://example.zitadel.com/ui/login/user/init?authRequestID=\u0026code=0M53RF\u0026loginname=Username\u0026orgID=275353657317327214\u0026passwordset=false\u0026userID=285181014567813483",
|
||||
"buttonText": "Finish initialization",
|
||||
"primaryColor": "#5469d4",
|
||||
"backgroundColor": "#fafafa",
|
||||
"fontColor": "#000000",
|
||||
"fontFamily": "-apple-system, BlinkMacSystemFont, Segoe UI, Lato, Arial, Helvetica, sans-serif",
|
||||
"footerText": "InitCode.Footer"
|
||||
},
|
||||
"args": {
|
||||
"changeDate": "2024-09-16T10:58:50.73237+02:00",
|
||||
"code": "0M53RF",
|
||||
"creationDate": "2024-09-16T10:58:50.73237+02:00",
|
||||
"displayName": "GivenName FamilyName",
|
||||
"firstName": "GivenName",
|
||||
"lastEmail": "example@zitadel.com",
|
||||
"lastName": "FamilyName",
|
||||
"lastPhone": "+41791234567",
|
||||
"loginNames": [
|
||||
"Username"
|
||||
],
|
||||
"nickName": "",
|
||||
"preferredLoginName": "Username",
|
||||
"userName": "Username",
|
||||
"verifiedEmail": "example@zitadel.com",
|
||||
"verifiedPhone": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There are 3 elements to this message:
|
||||
|
||||
- `contextInfo`, with information on why this message is sent like the Event, which Email or SMS provider is used and which recipient should receive this message
|
||||
- `templateData`, with all texts and format information which can be used with a template to produce the desired message
|
||||
- `args`, with the information provided to the user which can be used in the message to customize
|
@ -1,74 +0,0 @@
|
||||
---
|
||||
title: SMS, SMTP and HTTP Provider for Notifications
|
||||
---
|
||||
|
||||
All Notifications send as SMS and Email are customizable as that you can define your own providers,
|
||||
which then send the notifications out. These providers can also be defined as an HTTP type,
|
||||
and the text and content, which is used to send the SMS's and Emails will get send to a Webhook as JSON.
|
||||
|
||||
With this everything can be customized or even custom logic can be implemented to use a not yet supported provider by ZITADEL.
|
||||
|
||||
## How it works
|
||||
|
||||
There is a default provider configured in ZITADEL Cloud, both for SMS's and Emails, but this default providers can be changed through the respective API's.
|
||||
|
||||
This API's are provided on an instance level:
|
||||
- [SMS Providers](/apis/resources/admin/sms-provider)
|
||||
- [Email Providers](/apis/resources/admin/email-provider)
|
||||
|
||||
To use a non-default provider just add, and then activate. There can only be 1 provider be activated at the same time.
|
||||
|
||||
## Resulting messages
|
||||
|
||||
In case of the Twilio and SMTP providers, the messages will be sent as before, in case of the HTTP providers the content of the messages is the same but as a HTTP call.
|
||||
|
||||
Here an example of the body of an Email sent via HTTP provider:
|
||||
```json
|
||||
{
|
||||
"contextInfo": {
|
||||
"eventType": "user.human.initialization.code.added",
|
||||
"provider": {
|
||||
"id": "285181292935381355",
|
||||
"description": "test"
|
||||
},
|
||||
"recipientEmailAddress": "example@zitadel.com"
|
||||
},
|
||||
"templateData": {
|
||||
"title": "Zitadel - Initialize User",
|
||||
"preHeader": "Initialize User",
|
||||
"subject": "Initialize User",
|
||||
"greeting": "Hello GivenName FamilyName,",
|
||||
"text": "This user was created in Zitadel. Use the username Username to login. Please click the button below to finish the initialization process. (Code 0M53RF) If you didn't ask for this mail, please ignore it.",
|
||||
"url": "http://example.zitadel.com/ui/login/user/init?authRequestID=\u0026code=0M53RF\u0026loginname=Username\u0026orgID=275353657317327214\u0026passwordset=false\u0026userID=285181014567813483",
|
||||
"buttonText": "Finish initialization",
|
||||
"primaryColor": "#5469d4",
|
||||
"backgroundColor": "#fafafa",
|
||||
"fontColor": "#000000",
|
||||
"fontFamily": "-apple-system, BlinkMacSystemFont, Segoe UI, Lato, Arial, Helvetica, sans-serif",
|
||||
"footerText": "InitCode.Footer"
|
||||
},
|
||||
"args": {
|
||||
"changeDate": "2024-09-16T10:58:50.73237+02:00",
|
||||
"code": "0M53RF",
|
||||
"creationDate": "2024-09-16T10:58:50.73237+02:00",
|
||||
"displayName": "GivenName FamilyName",
|
||||
"firstName": "GivenName",
|
||||
"lastEmail": "example@zitadel.com",
|
||||
"lastName": "FamilyName",
|
||||
"lastPhone": "+41791234567",
|
||||
"loginNames": [
|
||||
"Username"
|
||||
],
|
||||
"nickName": "",
|
||||
"preferredLoginName": "Username",
|
||||
"userName": "Username",
|
||||
"verifiedEmail": "example@zitadel.com",
|
||||
"verifiedPhone": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There are 3 elements to this message:
|
||||
- contextInfo, with information on why this message is sent like the Event, which Email or SMS provider is used and which recipient should receive this message
|
||||
- templateData, with all texts and format information which can be used with a template to produce the desired message
|
||||
- args, with the information provided to the user which can be used in the message to customize
|
@ -1,6 +1,7 @@
|
||||
---
|
||||
title: Introduction
|
||||
title: Examples and SDKs for ZITADEL
|
||||
sidebar_label: Introduction
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
You can integrate ZITADEL quickly into your application and be up and running within minutes.
|
||||
@ -10,7 +11,7 @@ The SDKs and integration depend on the framework and language you are using.
|
||||
|
||||
import { Frameworks } from "../../src/components/frameworks";
|
||||
|
||||
### Resources
|
||||
## Resources
|
||||
|
||||
<Frameworks />
|
||||
|
||||
@ -20,7 +21,7 @@ To further streamline your setup, simply visit the console in ZITADEL where you
|
||||
|
||||
To begin configuring login for any of these samples, start [here](https://zitadel.com/signin).
|
||||
|
||||
### OIDC Libraries
|
||||
## OIDC Libraries
|
||||
|
||||
OIDC is a standard for authentication and most languages and frameworks do provide a OIDC library which can be easily integrated to your application.
|
||||
If we do not provide an specific example, SDK or guide, we strongly recommend using existing authentication libraries for your
|
||||
@ -34,7 +35,7 @@ You might want to check out the following links to find a good library:
|
||||
- [OpenID General References](https://openid.net/developers/libraries/)
|
||||
- [OpenID certified developer tools](https://openid.net/certified-open-id-developer-tools/)
|
||||
|
||||
### Other example applications
|
||||
## Other example applications
|
||||
|
||||
- [B2B customer portal](https://github.com/zitadel/zitadel-nextjs-b2b): Showcase the use of personal access tokens in a B2B environment. Uses NextJS Framework.
|
||||
- [Frontend with backend API](https://github.com/zitadel/example-quote-generator-app): A simple web application using a React front-end and a Python back-end API, both secured using ZITADEL
|
||||
@ -43,7 +44,7 @@ You might want to check out the following links to find a good library:
|
||||
|
||||
Search for the "example" tag in our repository to [explore all examples](https://github.com/search?q=topic%3Aexamples+org%3Azitadel&type=repositories).
|
||||
|
||||
### Missing SDK
|
||||
## Missing SDK
|
||||
|
||||
Is your language/framework missing? Fear not, you can generate your gRPC API Client with ease.
|
||||
|
||||
@ -54,7 +55,7 @@ Is your language/framework missing? Fear not, you can generate your gRPC API Cli
|
||||
Let us make an example with Ruby. Any other supported language by buf will work as well. Consult
|
||||
the [buf plugin registry](https://buf.build/plugins) for more ideas.
|
||||
|
||||
#### Example with Ruby
|
||||
### Example with Ruby
|
||||
|
||||
With gRPC we usually need to generate the client stub and the messages/types. This is why we need two plugins.
|
||||
The plugin `grpc/ruby` generates the client stub and the plugin `protocolbuffers/ruby` takes care of the messages/types.
|
||||
|
60
docs/docs/support/advisory/a10012.md
Normal file
60
docs/docs/support/advisory/a10012.md
Normal file
@ -0,0 +1,60 @@
|
||||
---
|
||||
title: Technical Advisory 10012
|
||||
---
|
||||
|
||||
## Date and Version
|
||||
|
||||
Version: 2.63.0
|
||||
|
||||
Date: 2024-09-26
|
||||
|
||||
## Description
|
||||
|
||||
In version 2.63.0 we've increased the transaction duration for projections.
|
||||
|
||||
ZITADEL has an event driven architecture. After events are pushed to the eventstore,
|
||||
they are reduced into projections in bulk batches. Projections allow for efficient lookup of data through normalized SQL tables.
|
||||
|
||||
We've investigated multiple reports of outdated projections.
|
||||
For example created users missing in get requests, or missing data after a ZITADEL upgrade[^1].
|
||||
The conclusion is that the transaction in which we perform a bulk of queries can timeout.
|
||||
The old setting defined a transaction duration of 500ms for a bulk of 200 events.
|
||||
A single event may create multiple statements in a single projection.
|
||||
A timeout may occur even if the actual bulk size is less than 200,
|
||||
which then results in more back-pressure on a busy system, leading to more timeouts and effectively dead-locking a projection.
|
||||
|
||||
Increasing or disabling the projection transaction duration solved dead-locks in all reported cases.
|
||||
We've decided to increase the transaction duration to 1 minute.
|
||||
Due to the high value it is functionally similar to disabling,
|
||||
however it still provides a safety net for transaction that do freeze,
|
||||
perhaps due to connection issues with the database.
|
||||
|
||||
[^1]: Changes written to the eventstore are the main source of truth. When a projection is out of date, some request may serve incomplete or no data. The data itself is however not lost.
|
||||
|
||||
## Statement
|
||||
|
||||
A summary of bug reports can be found in the following issue: [Missing data due to outdated projections](https://github.com/zitadel/zitadel/issues/8517).
|
||||
This change was submitted in the following PR:
|
||||
[fix(projection): increase transaction duration](https://github.com/zitadel/zitadel/pull/8632), which will be released in Version [2.63.0](https://github.com/zitadel/zitadel/releases/tag/v2.63.0)
|
||||
|
||||
## Mitigation
|
||||
|
||||
If you have a custom configuration for projections, this update will not apply to your system or some projections. When encountering projection dead-lock consider increasing the timeout to the new default value.
|
||||
|
||||
Note that entries under `Customizations` overwrite the global settings for a single projection.
|
||||
|
||||
```yaml
|
||||
Projections:
|
||||
TransactionDuration: 1m # ZITADEL_PROJECTIONS_TRANSACTIONDURATION
|
||||
BulkLimit: 200 # ZITADEL_PROJECTIONS_BULKLIMIT
|
||||
Customizations:
|
||||
custom_texts:
|
||||
BulkLimit: 400
|
||||
project_grant_fields:
|
||||
TransactionDuration: 0s
|
||||
BulkLimit: 2000
|
||||
```
|
||||
|
||||
## Impact
|
||||
|
||||
Once this update has been released and deployed, transactions are allowed to run longer. No other functional impact is expected.
|
@ -187,7 +187,7 @@ module.exports = {
|
||||
selector: "div#",
|
||||
},
|
||||
prism: {
|
||||
additionalLanguages: ["csharp", "dart", "groovy", "regex", "java", "php", "python", "protobuf"],
|
||||
additionalLanguages: ["csharp", "dart", "groovy", "regex", "java", "php", "python", "protobuf", "json", "bash"],
|
||||
},
|
||||
colorMode: {
|
||||
defaultMode: "dark",
|
||||
|
@ -54,18 +54,10 @@ module.exports = {
|
||||
label: "Examples & SDKs",
|
||||
link: {type: "doc", id: "sdk-examples/introduction"},
|
||||
items: [
|
||||
"sdk-examples/introduction",
|
||||
"sdk-examples/angular",
|
||||
"sdk-examples/flutter",
|
||||
"sdk-examples/go",
|
||||
"sdk-examples/java",
|
||||
"sdk-examples/nestjs",
|
||||
"sdk-examples/nextjs",
|
||||
"sdk-examples/python-flask",
|
||||
"sdk-examples/python-django",
|
||||
"sdk-examples/react",
|
||||
"sdk-examples/symfony",
|
||||
"sdk-examples/vue",
|
||||
{
|
||||
type: "autogenerated",
|
||||
dirName: "sdk-examples"
|
||||
},
|
||||
{
|
||||
type: "link",
|
||||
label: "Dart",
|
||||
@ -154,11 +146,10 @@ module.exports = {
|
||||
type: "category",
|
||||
label: "Customize",
|
||||
items: [
|
||||
"guides/manage/customize/branding",
|
||||
"guides/manage/customize/texts",
|
||||
"guides/manage/customize/behavior",
|
||||
"guides/manage/customize/restrictions",
|
||||
"guides/manage/customize/notifications",
|
||||
{
|
||||
type: "autogenerated",
|
||||
dirName: "guides/manage/customize",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -8,7 +8,7 @@ export function Tile({ title, imageSource, imageSourceLight, link, external }) {
|
||||
className={styles.tile}
|
||||
target={external ? "_blank" : "_self"}
|
||||
>
|
||||
<h4>{title}</h4>
|
||||
<h3>{title}</h3>
|
||||
<img
|
||||
className={imageSourceLight ? "hideonlight" : ""}
|
||||
src={imageSource}
|
||||
|
@ -7976,9 +7976,9 @@ micromark@^4.0.0:
|
||||
micromark-util-types "^2.0.0"
|
||||
|
||||
micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5:
|
||||
version "4.0.7"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5"
|
||||
integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
|
||||
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
|
||||
dependencies:
|
||||
braces "^3.0.3"
|
||||
picomatch "^2.3.1"
|
||||
|
@ -123,7 +123,9 @@ func (s *Server) ImportData(ctx context.Context, req *admin_pb.ImportDataRequest
|
||||
return nil, ctxTimeout.Err()
|
||||
case result := <-ch:
|
||||
logging.OnError(result.err).Errorf("error while importing: %v", result.err)
|
||||
logging.Infof("Import done: %s", result.count.getProgress())
|
||||
if result.count != nil {
|
||||
logging.Infof("Import done: %s", result.count.getProgress())
|
||||
}
|
||||
return result.ret, result.err
|
||||
}
|
||||
} else {
|
||||
|
@ -250,16 +250,26 @@ func TestServer_ExecutionTarget(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer close()
|
||||
}
|
||||
|
||||
got, err := instance.Client.ActionV3Alpha.GetTarget(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
retryDuration := 5 * time.Second
|
||||
if ctxDeadline, ok := isolatedIAMOwnerCTX.Deadline(); ok {
|
||||
retryDuration = time.Until(ctxDeadline)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertResourceDetails(t, tt.want.GetTarget().GetDetails(), got.GetTarget().GetDetails())
|
||||
assert.Equal(t, tt.want.GetTarget().GetConfig(), got.GetTarget().GetConfig())
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
got, err := instance.Client.ActionV3Alpha.GetTarget(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(ttt, err, "Error: "+err.Error())
|
||||
} else {
|
||||
assert.NoError(ttt, err)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
integration.AssertResourceDetails(t, tt.want.GetTarget().GetDetails(), got.GetTarget().GetDetails())
|
||||
assert.Equal(t, tt.want.GetTarget().GetConfig(), got.GetTarget().GetConfig())
|
||||
}, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result")
|
||||
|
||||
if tt.clean != nil {
|
||||
tt.clean(tt.ctx)
|
||||
}
|
||||
|
@ -75,3 +75,18 @@ func SearchQueryPbToQuery(defaults systemdefaults.SystemDefaults, query *resourc
|
||||
}
|
||||
return offset, limit, asc, nil
|
||||
}
|
||||
|
||||
func ResourceOwnerFromOrganization(organization *object.Organization) string {
|
||||
if organization == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if organization.GetOrgId() != "" {
|
||||
return organization.GetOrgId()
|
||||
}
|
||||
if organization.GetOrgDomain() != "" {
|
||||
// TODO get org from domain
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
83
internal/api/grpc/resources/user/v3alpha/email.go
Normal file
83
internal/api/grpc/resources/user/v3alpha/email.go
Normal file
@ -0,0 +1,83 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
)
|
||||
|
||||
func (s *Server) SetContactEmail(ctx context.Context, req *user.SetContactEmailRequest) (_ *user.SetContactEmailResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schemauser := setContactEmailRequestToChangeSchemaUserEmail(req)
|
||||
details, err := s.command.ChangeSchemaUserEmail(ctx, schemauser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.SetContactEmailResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
VerificationCode: schemauser.ReturnCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setContactEmailRequestToChangeSchemaUserEmail(req *user.SetContactEmailRequest) *command.ChangeSchemaUserEmail {
|
||||
return &command.ChangeSchemaUserEmail{
|
||||
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
|
||||
ID: req.GetId(),
|
||||
Email: setEmailToEmail(req.Email),
|
||||
}
|
||||
}
|
||||
|
||||
func setEmailToEmail(setEmail *user.SetEmail) *command.Email {
|
||||
if setEmail == nil {
|
||||
return nil
|
||||
}
|
||||
return &command.Email{
|
||||
Address: domain.EmailAddress(setEmail.Address),
|
||||
ReturnCode: setEmail.GetReturnCode() != nil,
|
||||
Verified: setEmail.GetIsVerified(),
|
||||
URLTemplate: setEmail.GetSendCode().GetUrlTemplate(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) VerifyContactEmail(ctx context.Context, req *user.VerifyContactEmailRequest) (_ *user.VerifyContactEmailResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.VerifySchemaUserEmail(ctx, organizationToUpdateResourceOwner(req.Organization), req.GetId(), req.GetVerificationCode())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.VerifyContactEmailResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ResendContactEmailCode(ctx context.Context, req *user.ResendContactEmailCodeRequest) (_ *user.ResendContactEmailCodeResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schemauser := resendContactEmailCodeRequestToResendSchemaUserEmailCode(req)
|
||||
details, err := s.command.ResendSchemaUserEmailCode(ctx, schemauser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.ResendContactEmailCodeResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
VerificationCode: schemauser.PlainCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func resendContactEmailCodeRequestToResendSchemaUserEmailCode(req *user.ResendContactEmailCodeRequest) *command.ResendSchemaUserEmailCode {
|
||||
return &command.ResendSchemaUserEmailCode{
|
||||
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
|
||||
ID: req.GetId(),
|
||||
URLTemplate: req.GetSendCode().GetUrlTemplate(),
|
||||
ReturnCode: req.GetReturnCode() != nil,
|
||||
}
|
||||
}
|
@ -0,0 +1,772 @@
|
||||
//go:build integration
|
||||
|
||||
package user_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
)
|
||||
|
||||
func TestServer_SetContactEmail(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
schema := []byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
|
||||
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
|
||||
|
||||
type res struct {
|
||||
want *resource_object.Details
|
||||
returnCode bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(req *user.SetContactEmailRequest) error
|
||||
req *user.SetContactEmailRequest
|
||||
res res
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "email patch, no context",
|
||||
ctx: context.Background(),
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email patch, no permission",
|
||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email patch, not found",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Id: "notexisting",
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email patch, not found, org",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: "not existing",
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email patch, empty",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
data := "{\"name\": \"user\"}"
|
||||
schemaID := schemaResp.GetDetails().GetId()
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
email := gofakeit.Email()
|
||||
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, email)
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email patch, no change",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
data := "{\"name\": \"user\"}"
|
||||
schemaID := schemaResp.GetDetails().GetId()
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
email := gofakeit.Email()
|
||||
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, email)
|
||||
req.Email.Address = email
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email patch, no org, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email patch, return, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
Verification: &user.SetEmail_ReturnCode{},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
returnCode: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email patch, return, invalid template",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
Verification: &user.SetEmail_SendCode{SendCode: &user.SendEmailVerificationCode{UrlTemplate: gu.Ptr("{{")}},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email patch, verified, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
Verification: &user.SetEmail_IsVerified{IsVerified: true},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email patch, template, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
Verification: &user.SetEmail_SendCode{SendCode: &user.SendEmailVerificationCode{UrlTemplate: gu.Ptr("https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}")}},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email patch, sent, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
Verification: &user.SetEmail_SendCode{},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.dep != nil {
|
||||
err := tt.dep(tt.req)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.SetContactEmail(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.res.want, got.Details)
|
||||
if tt.res.returnCode {
|
||||
assert.NotNil(t, got.VerificationCode)
|
||||
} else {
|
||||
assert.Nil(t, got.VerificationCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_VerifyContactEmail(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
schema := []byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
|
||||
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
|
||||
|
||||
type res struct {
|
||||
want *resource_object.Details
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(req *user.VerifyContactEmailRequest) error
|
||||
req *user.VerifyContactEmailRequest
|
||||
res res
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "email verify, no context",
|
||||
ctx: context.Background(),
|
||||
dep: func(req *user.VerifyContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email verify, no permission",
|
||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
||||
dep: func(req *user.VerifyContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email verify, not found",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactEmailRequest) error {
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Id: "notexisting",
|
||||
VerificationCode: "unimportant",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email verify, not found, org",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: "not existing",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email verify, wrong code",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactEmailRequest) error {
|
||||
data := "{\"name\": \"user\"}"
|
||||
schemaID := schemaResp.GetDetails().GetId()
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
VerificationCode: "wrong",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email verify, no org, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactEmailRequest{},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email verify, return, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.dep != nil {
|
||||
err := tt.dep(tt.req)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.VerifyContactEmail(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.res.want, got.Details)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ResendContactEmailCode(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
schema := []byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
|
||||
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
|
||||
|
||||
type res struct {
|
||||
want *resource_object.Details
|
||||
returnCode bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(req *user.ResendContactEmailCodeRequest) error
|
||||
req *user.ResendContactEmailCodeRequest
|
||||
res res
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "email resend, no context",
|
||||
ctx: context.Background(),
|
||||
dep: func(req *user.ResendContactEmailCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactEmailCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email resend, no permission",
|
||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
||||
dep: func(req *user.ResendContactEmailCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactEmailCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email resend, not found",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactEmailCodeRequest) error {
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactEmailCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Id: "notexisting",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email resend, not found, org",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactEmailCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactEmailCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: "not existing",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email resend, no code",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactEmailCodeRequest) error {
|
||||
data := "{\"name\": \"user\"}"
|
||||
schemaID := schemaResp.GetDetails().GetId()
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactEmailCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email resend, no org, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactEmailCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactEmailCodeRequest{},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email resend, return, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactEmailCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactEmailCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Verification: &user.ResendContactEmailCodeRequest_ReturnCode{},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
returnCode: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email resend, sent, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactEmailCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactEmailCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Verification: &user.ResendContactEmailCodeRequest_SendCode{},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.dep != nil {
|
||||
err := tt.dep(tt.req)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.ResendContactEmailCode(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.res.want, got.Details)
|
||||
if tt.res.returnCode {
|
||||
assert.NotNil(t, got.VerificationCode)
|
||||
} else {
|
||||
assert.Nil(t, got.VerificationCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,701 @@
|
||||
//go:build integration
|
||||
|
||||
package user_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
)
|
||||
|
||||
func TestServer_SetContactPhone(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
schema := []byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
|
||||
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
|
||||
|
||||
type res struct {
|
||||
want *resource_object.Details
|
||||
returnCode bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(req *user.SetContactPhoneRequest) error
|
||||
req *user.SetContactPhoneRequest
|
||||
res res
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "phone patch, no context",
|
||||
ctx: context.Background(),
|
||||
dep: func(req *user.SetContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Phone: &user.SetPhone{
|
||||
Number: gofakeit.Phone(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone patch, no permission",
|
||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
||||
dep: func(req *user.SetContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Phone: &user.SetPhone{
|
||||
Number: gofakeit.Phone(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone patch, not found",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactPhoneRequest) error {
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Id: "notexisting",
|
||||
Phone: &user.SetPhone{
|
||||
Number: gofakeit.Phone(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone patch, not found, org",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: "not existing",
|
||||
},
|
||||
},
|
||||
Phone: &user.SetPhone{
|
||||
Number: gofakeit.Phone(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone patch, no change",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactPhoneRequest) error {
|
||||
data := "{\"name\": \"user\"}"
|
||||
schemaID := schemaResp.GetDetails().GetId()
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
number := gofakeit.Phone()
|
||||
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, number)
|
||||
req.Phone.Number = number
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Phone: &user.SetPhone{},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "phone patch, no org, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactPhoneRequest{
|
||||
Phone: &user.SetPhone{
|
||||
Number: gofakeit.Phone(),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "phone patch, return, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Phone: &user.SetPhone{
|
||||
Number: gofakeit.Phone(),
|
||||
Verification: &user.SetPhone_ReturnCode{},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
returnCode: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "phone patch, verified, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Phone: &user.SetPhone{
|
||||
Number: gofakeit.Phone(),
|
||||
Verification: &user.SetPhone_IsVerified{IsVerified: true},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "phone patch, sent, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Phone: &user.SetPhone{
|
||||
Number: gofakeit.Phone(),
|
||||
Verification: &user.SetPhone_SendCode{},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.dep != nil {
|
||||
err := tt.dep(tt.req)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.SetContactPhone(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.res.want, got.Details)
|
||||
if tt.res.returnCode {
|
||||
assert.NotNil(t, got.VerificationCode)
|
||||
} else {
|
||||
assert.Nil(t, got.VerificationCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_VerifyContactPhone(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
schema := []byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
|
||||
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
|
||||
|
||||
type res struct {
|
||||
want *resource_object.Details
|
||||
returnCodePhone bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(req *user.VerifyContactPhoneRequest) error
|
||||
req *user.VerifyContactPhoneRequest
|
||||
res res
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "phone verify, no context",
|
||||
ctx: context.Background(),
|
||||
dep: func(req *user.VerifyContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone verify, no permission",
|
||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
||||
dep: func(req *user.VerifyContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone verify, not found",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactPhoneRequest) error {
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Id: "notexisting",
|
||||
VerificationCode: "unimportant",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone verify, not found, org",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: "not existing",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone verify, wrong code",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactPhoneRequest) error {
|
||||
data := "{\"name\": \"user\"}"
|
||||
schemaID := schemaResp.GetDetails().GetId()
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
VerificationCode: "wrong",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone verify, no org, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactPhoneRequest{},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "phone verify, return, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
returnCodePhone: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.dep != nil {
|
||||
err := tt.dep(tt.req)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.VerifyContactPhone(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.res.want, got.Details)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ResendContactPhoneCode(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
schema := []byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
|
||||
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
|
||||
|
||||
type res struct {
|
||||
want *resource_object.Details
|
||||
returnCode bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(req *user.ResendContactPhoneCodeRequest) error
|
||||
req *user.ResendContactPhoneCodeRequest
|
||||
res res
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "phone resend, no context",
|
||||
ctx: context.Background(),
|
||||
dep: func(req *user.ResendContactPhoneCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactPhoneCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone resend, no permission",
|
||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
||||
dep: func(req *user.ResendContactPhoneCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactPhoneCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone resend, not found",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactPhoneCodeRequest) error {
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactPhoneCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Id: "notexisting",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone resend, not found, org",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactPhoneCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactPhoneCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: "not existing",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone resend, no code",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactPhoneCodeRequest) error {
|
||||
data := "{\"name\": \"user\"}"
|
||||
schemaID := schemaResp.GetDetails().GetId()
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactPhoneCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone resend, no org, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactPhoneCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactPhoneCodeRequest{},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "phone resend, return, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactPhoneCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactPhoneCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Verification: &user.ResendContactPhoneCodeRequest_ReturnCode{},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
returnCode: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "phone resend, sent, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactPhoneCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactPhoneCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Verification: &user.ResendContactPhoneCodeRequest_SendCode{},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.dep != nil {
|
||||
err := tt.dep(tt.req)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.ResendContactPhoneCode(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.res.want, got.Details)
|
||||
if tt.res.returnCode {
|
||||
assert.NotNil(t, got.VerificationCode)
|
||||
} else {
|
||||
assert.Nil(t, got.VerificationCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -18,49 +18,41 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
IAMOwnerCTX, SystemCTX context.Context
|
||||
UserCTX context.Context
|
||||
Instance *integration.Instance
|
||||
Client user.ZITADELUsersClient
|
||||
CTX context.Context
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
Instance = integration.NewInstance(ctx)
|
||||
|
||||
IAMOwnerCTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
|
||||
SystemCTX = integration.WithSystemAuthorization(ctx)
|
||||
UserCTX = Instance.WithAuthorization(ctx, integration.UserTypeLogin)
|
||||
Client = Instance.Client.UserV3Alpha
|
||||
CTX = ctx
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
||||
|
||||
func ensureFeatureEnabled(t *testing.T, iamOwnerCTX context.Context) {
|
||||
f, err := Instance.Client.FeatureV2.GetInstanceFeatures(iamOwnerCTX, &feature.GetInstanceFeaturesRequest{
|
||||
func ensureFeatureEnabled(t *testing.T, instance *integration.Instance) {
|
||||
ctx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
if f.UserSchema.GetEnabled() {
|
||||
return
|
||||
}
|
||||
_, err = Instance.Client.FeatureV2.SetInstanceFeatures(iamOwnerCTX, &feature.SetInstanceFeaturesRequest{
|
||||
_, err = instance.Client.FeatureV2.SetInstanceFeatures(ctx, &feature.SetInstanceFeaturesRequest{
|
||||
UserSchema: gu.Ptr(true),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
retryDuration := time.Minute
|
||||
if ctxDeadline, ok := iamOwnerCTX.Deadline(); ok {
|
||||
if ctxDeadline, ok := ctx.Deadline(); ok {
|
||||
retryDuration = time.Until(ctxDeadline)
|
||||
}
|
||||
require.EventuallyWithT(t,
|
||||
func(ttt *assert.CollectT) {
|
||||
f, err := Instance.Client.FeatureV2.GetInstanceFeatures(iamOwnerCTX, &feature.GetInstanceFeaturesRequest{
|
||||
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
require.NoError(ttt, err)
|
||||
assert.NoError(ttt, err)
|
||||
if f.UserSchema.GetEnabled() {
|
||||
return
|
||||
}
|
||||
@ -68,4 +60,13 @@ func ensureFeatureEnabled(t *testing.T, iamOwnerCTX context.Context) {
|
||||
retryDuration,
|
||||
time.Second,
|
||||
"timed out waiting for ensuring instance feature")
|
||||
|
||||
require.EventuallyWithT(t,
|
||||
func(ttt *assert.CollectT) {
|
||||
_, err := instance.Client.UserV3Alpha.SearchUsers(ctx, &user.SearchUsersRequest{})
|
||||
assert.NoError(ttt, err)
|
||||
},
|
||||
retryDuration,
|
||||
time.Second,
|
||||
"timed out waiting for ensuring instance feature call")
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
81
internal/api/grpc/resources/user/v3alpha/phone.go
Normal file
81
internal/api/grpc/resources/user/v3alpha/phone.go
Normal file
@ -0,0 +1,81 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
)
|
||||
|
||||
func (s *Server) SetContactPhone(ctx context.Context, req *user.SetContactPhoneRequest) (_ *user.SetContactPhoneResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schemauser := setContactPhoneRequestToChangeSchemaUserPhone(req)
|
||||
details, err := s.command.ChangeSchemaUserPhone(ctx, schemauser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.SetContactPhoneResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
VerificationCode: schemauser.ReturnCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setContactPhoneRequestToChangeSchemaUserPhone(req *user.SetContactPhoneRequest) *command.ChangeSchemaUserPhone {
|
||||
return &command.ChangeSchemaUserPhone{
|
||||
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
|
||||
ID: req.GetId(),
|
||||
Phone: setPhoneToPhone(req.Phone),
|
||||
}
|
||||
}
|
||||
|
||||
func setPhoneToPhone(setPhone *user.SetPhone) *command.Phone {
|
||||
if setPhone == nil {
|
||||
return nil
|
||||
}
|
||||
return &command.Phone{
|
||||
Number: domain.PhoneNumber(setPhone.Number),
|
||||
ReturnCode: setPhone.GetReturnCode() != nil,
|
||||
Verified: setPhone.GetIsVerified(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) VerifyContactPhone(ctx context.Context, req *user.VerifyContactPhoneRequest) (_ *user.VerifyContactPhoneResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.VerifySchemaUserPhone(ctx, organizationToUpdateResourceOwner(req.Organization), req.GetId(), req.GetVerificationCode())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.VerifyContactPhoneResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ResendContactPhoneCode(ctx context.Context, req *user.ResendContactPhoneCodeRequest) (_ *user.ResendContactPhoneCodeResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schemauser := resendContactPhoneCodeRequestToResendSchemaUserPhoneCode(req)
|
||||
details, err := s.command.ResendSchemaUserPhoneCode(ctx, schemauser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.ResendContactPhoneCodeResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
VerificationCode: schemauser.PlainCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func resendContactPhoneCodeRequestToResendSchemaUserPhoneCode(req *user.ResendContactPhoneCodeRequest) *command.ResendSchemaUserPhoneCode {
|
||||
return &command.ResendSchemaUserPhoneCode{
|
||||
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
|
||||
ID: req.GetId(),
|
||||
ReturnCode: req.GetReturnCode() != nil,
|
||||
}
|
||||
}
|
14
internal/api/grpc/resources/user/v3alpha/query.go
Normal file
14
internal/api/grpc/resources/user/v3alpha/query.go
Normal file
@ -0,0 +1,14 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
)
|
||||
|
||||
func (s *Server) SearchUsers(ctx context.Context, _ *user.SearchUsersRequest) (_ *user.SearchUsersResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.SearchUsersResponse{}, nil
|
||||
}
|
@ -6,7 +6,6 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
)
|
||||
|
||||
@ -14,19 +13,16 @@ var _ user.ZITADELUsersServer = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
user.UnimplementedZITADELUsersServer
|
||||
command *command.Commands
|
||||
userCodeAlg crypto.EncryptionAlgorithm
|
||||
command *command.Commands
|
||||
}
|
||||
|
||||
type Config struct{}
|
||||
|
||||
func CreateServer(
|
||||
command *command.Commands,
|
||||
userCodeAlg crypto.EncryptionAlgorithm,
|
||||
) *Server {
|
||||
return &Server{
|
||||
command: command,
|
||||
userCodeAlg: userCodeAlg,
|
||||
command: command,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,6 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
@ -21,14 +19,14 @@ func (s *Server) CreateUser(ctx context.Context, req *user.CreateUserRequest) (_
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.command.CreateSchemaUser(ctx, schemauser, s.userCodeAlg); err != nil {
|
||||
details, err := s.command.CreateSchemaUser(ctx, schemauser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.CreateUserResponse{
|
||||
Details: resource_object.DomainToDetailsPb(schemauser.Details, object.OwnerType_OWNER_TYPE_ORG, schemauser.ResourceOwner),
|
||||
EmailCode: gu.Ptr(schemauser.ReturnCodeEmail),
|
||||
PhoneCode: gu.Ptr(schemauser.ReturnCodePhone),
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
EmailCode: schemauser.ReturnCodeEmail,
|
||||
PhoneCode: schemauser.ReturnCodePhone,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -37,19 +35,37 @@ func createUserRequestToCreateSchemaUser(ctx context.Context, req *user.CreateUs
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &command.CreateSchemaUser{
|
||||
ResourceOwner: authz.GetCtxData(ctx).OrgID,
|
||||
ResourceOwner: organizationToCreateResourceOwner(ctx, req.Organization),
|
||||
SchemaID: req.GetUser().GetSchemaId(),
|
||||
ID: req.GetUser().GetUserId(),
|
||||
Data: data,
|
||||
Email: setEmailToEmail(req.GetUser().GetContact().GetEmail()),
|
||||
Phone: setPhoneToPhone(req.GetUser().GetContact().GetPhone()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func organizationToCreateResourceOwner(ctx context.Context, org *object.Organization) string {
|
||||
resourceOwner := authz.GetCtxData(ctx).OrgID
|
||||
if resourceOwnerReq := resource_object.ResourceOwnerFromOrganization(org); resourceOwnerReq != "" {
|
||||
return resourceOwnerReq
|
||||
}
|
||||
return resourceOwner
|
||||
}
|
||||
|
||||
func organizationToUpdateResourceOwner(org *object.Organization) string {
|
||||
if resourceOwnerReq := resource_object.ResourceOwnerFromOrganization(org); resourceOwnerReq != "" {
|
||||
return resourceOwnerReq
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *Server) DeleteUser(ctx context.Context, req *user.DeleteUserRequest) (_ *user.DeleteUserResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.DeleteSchemaUser(ctx, req.GetUserId())
|
||||
details, err := s.command.DeleteSchemaUser(ctx, organizationToUpdateResourceOwner(req.Organization), req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -64,3 +80,119 @@ func checkUserSchemaEnabled(ctx context.Context) error {
|
||||
}
|
||||
return zerrors.ThrowPreconditionFailed(nil, "TODO", "Errors.UserSchema.NotEnabled")
|
||||
}
|
||||
|
||||
func (s *Server) PatchUser(ctx context.Context, req *user.PatchUserRequest) (_ *user.PatchUserResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schemauser, err := patchUserRequestToChangeSchemaUser(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
details, err := s.command.ChangeSchemaUser(ctx, schemauser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.PatchUserResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
EmailCode: schemauser.ReturnCodeEmail,
|
||||
PhoneCode: schemauser.ReturnCodePhone,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func patchUserRequestToChangeSchemaUser(req *user.PatchUserRequest) (_ *command.ChangeSchemaUser, err error) {
|
||||
schemaUser, err := setSchemaUserToSchemaUser(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
email, phone := setContactToContact(req.GetUser().GetContact())
|
||||
return &command.ChangeSchemaUser{
|
||||
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
|
||||
ID: req.GetId(),
|
||||
SchemaUser: schemaUser,
|
||||
Email: email,
|
||||
Phone: phone,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setSchemaUserToSchemaUser(req *user.PatchUserRequest) (_ *command.SchemaUser, err error) {
|
||||
if req.GetUser() == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var data []byte
|
||||
if req.GetUser().Data != nil {
|
||||
data, err = req.GetUser().GetData().MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &command.SchemaUser{
|
||||
SchemaID: req.GetUser().GetSchemaId(),
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setContactToContact(contact *user.SetContact) (*command.Email, *command.Phone) {
|
||||
if contact == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return setEmailToEmail(contact.GetEmail()), setPhoneToPhone(contact.GetPhone())
|
||||
}
|
||||
|
||||
func (s *Server) DeactivateUser(ctx context.Context, req *user.DeactivateUserRequest) (_ *user.DeactivateUserResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
details, err := s.command.DeactivateSchemaUser(ctx, organizationToUpdateResourceOwner(req.Organization), req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.DeactivateUserResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ActivateUser(ctx context.Context, req *user.ActivateUserRequest) (_ *user.ActivateUserResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
details, err := s.command.ActivateSchemaUser(ctx, organizationToUpdateResourceOwner(req.Organization), req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.ActivateUserResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) LockUser(ctx context.Context, req *user.LockUserRequest) (_ *user.LockUserResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
details, err := s.command.LockSchemaUser(ctx, organizationToUpdateResourceOwner(req.Organization), req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.LockUserResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UnlockUser(ctx context.Context, req *user.UnlockUserRequest) (_ *user.UnlockUserResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
details, err := s.command.UnlockSchemaUser(ctx, organizationToUpdateResourceOwner(req.Organization), req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.UnlockUserResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
}, nil
|
||||
}
|
||||
|
@ -20,7 +20,10 @@ import (
|
||||
)
|
||||
|
||||
func TestServer_ListUserSchemas(t *testing.T) {
|
||||
ensureFeatureEnabled(t, IAMOwnerCTX)
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
userSchema := new(structpb.Struct)
|
||||
err := userSchema.UnmarshalJSON([]byte(`{
|
||||
@ -43,7 +46,7 @@ func TestServer_ListUserSchemas(t *testing.T) {
|
||||
{
|
||||
name: "missing permission",
|
||||
args: args{
|
||||
ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
req: &schema.SearchUserSchemasRequest{},
|
||||
},
|
||||
wantErr: true,
|
||||
@ -51,7 +54,7 @@ func TestServer_ListUserSchemas(t *testing.T) {
|
||||
{
|
||||
name: "not found, error",
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.SearchUserSchemasRequest{
|
||||
Filters: []*schema.SearchFilter{
|
||||
{
|
||||
@ -75,11 +78,11 @@ func TestServer_ListUserSchemas(t *testing.T) {
|
||||
{
|
||||
name: "single (id), ok",
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.SearchUserSchemasRequest{},
|
||||
prepare: func(request *schema.SearchUserSchemasRequest, resp *schema.SearchUserSchemasResponse) error {
|
||||
schemaType := gofakeit.Name()
|
||||
createResp := Instance.CreateUserSchemaEmptyWithType(IAMOwnerCTX, schemaType)
|
||||
createResp := instance.CreateUserSchemaEmptyWithType(isolatedIAMOwnerCTX, schemaType)
|
||||
request.Filters = []*schema.SearchFilter{
|
||||
{
|
||||
Filter: &schema.SearchFilter_IdFilter{
|
||||
@ -121,14 +124,14 @@ func TestServer_ListUserSchemas(t *testing.T) {
|
||||
{
|
||||
name: "multiple (type), ok",
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.SearchUserSchemasRequest{},
|
||||
prepare: func(request *schema.SearchUserSchemasRequest, resp *schema.SearchUserSchemasResponse) error {
|
||||
schemaType := gofakeit.Name()
|
||||
schemaType1 := schemaType + "_1"
|
||||
schemaType2 := schemaType + "_2"
|
||||
createResp := Instance.CreateUserSchemaEmptyWithType(IAMOwnerCTX, schemaType1)
|
||||
createResp2 := Instance.CreateUserSchemaEmptyWithType(IAMOwnerCTX, schemaType2)
|
||||
createResp := instance.CreateUserSchemaEmptyWithType(isolatedIAMOwnerCTX, schemaType1)
|
||||
createResp2 := instance.CreateUserSchemaEmptyWithType(isolatedIAMOwnerCTX, schemaType2)
|
||||
|
||||
request.SortingColumn = gu.Ptr(schema.FieldName_FIELD_NAME_TYPE)
|
||||
request.Query = &object.SearchQuery{Desc: false}
|
||||
@ -186,12 +189,12 @@ func TestServer_ListUserSchemas(t *testing.T) {
|
||||
}
|
||||
|
||||
retryDuration := 20 * time.Second
|
||||
if ctxDeadline, ok := IAMOwnerCTX.Deadline(); ok {
|
||||
if ctxDeadline, ok := isolatedIAMOwnerCTX.Deadline(); ok {
|
||||
retryDuration = time.Until(ctxDeadline)
|
||||
}
|
||||
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
got, err := Client.SearchUserSchemas(tt.args.ctx, tt.args.req)
|
||||
got, err := instance.Client.UserSchemaV3.SearchUserSchemas(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(ttt, err)
|
||||
return
|
||||
@ -215,7 +218,10 @@ func TestServer_ListUserSchemas(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_GetUserSchema(t *testing.T) {
|
||||
ensureFeatureEnabled(t, IAMOwnerCTX)
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
userSchema := new(structpb.Struct)
|
||||
err := userSchema.UnmarshalJSON([]byte(`{
|
||||
@ -238,11 +244,11 @@ func TestServer_GetUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "missing permission",
|
||||
args: args{
|
||||
ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
req: &schema.GetUserSchemaRequest{},
|
||||
prepare: func(request *schema.GetUserSchemaRequest, resp *schema.GetUserSchemaResponse) error {
|
||||
schemaType := gofakeit.Name()
|
||||
createResp := Instance.CreateUserSchemaEmptyWithType(IAMOwnerCTX, schemaType)
|
||||
createResp := instance.CreateUserSchemaEmptyWithType(isolatedIAMOwnerCTX, schemaType)
|
||||
request.Id = createResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
@ -252,7 +258,7 @@ func TestServer_GetUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "not existing, error",
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.GetUserSchemaRequest{
|
||||
Id: "notexisting",
|
||||
},
|
||||
@ -262,11 +268,11 @@ func TestServer_GetUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "get, ok",
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.GetUserSchemaRequest{},
|
||||
prepare: func(request *schema.GetUserSchemaRequest, resp *schema.GetUserSchemaResponse) error {
|
||||
schemaType := gofakeit.Name()
|
||||
createResp := Instance.CreateUserSchemaEmptyWithType(IAMOwnerCTX, schemaType)
|
||||
createResp := instance.CreateUserSchemaEmptyWithType(isolatedIAMOwnerCTX, schemaType)
|
||||
request.Id = createResp.GetDetails().GetId()
|
||||
|
||||
resp.UserSchema.Config.Type = schemaType
|
||||
@ -295,12 +301,12 @@ func TestServer_GetUserSchema(t *testing.T) {
|
||||
}
|
||||
|
||||
retryDuration := 5 * time.Second
|
||||
if ctxDeadline, ok := IAMOwnerCTX.Deadline(); ok {
|
||||
if ctxDeadline, ok := isolatedIAMOwnerCTX.Deadline(); ok {
|
||||
retryDuration = time.Until(ctxDeadline)
|
||||
}
|
||||
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
got, err := Client.GetUserSchema(tt.args.ctx, tt.args.req)
|
||||
got, err := instance.Client.UserSchemaV3.GetUserSchema(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err, "Error: "+err.Error())
|
||||
} else {
|
||||
|
@ -18,48 +18,41 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
IAMOwnerCTX, SystemCTX context.Context
|
||||
Instance *integration.Instance
|
||||
Client schema.ZITADELUserSchemasClient
|
||||
CTX context.Context
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
Instance = integration.NewInstance(ctx)
|
||||
|
||||
IAMOwnerCTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
|
||||
SystemCTX = integration.WithSystemAuthorization(ctx)
|
||||
Client = Instance.Client.UserSchemaV3
|
||||
|
||||
CTX = ctx
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
||||
|
||||
func ensureFeatureEnabled(t *testing.T, iamOwnerCTX context.Context) {
|
||||
f, err := Instance.Client.FeatureV2.GetInstanceFeatures(iamOwnerCTX, &feature.GetInstanceFeaturesRequest{
|
||||
func ensureFeatureEnabled(t *testing.T, instance *integration.Instance) {
|
||||
ctx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
if f.UserSchema.GetEnabled() {
|
||||
return
|
||||
}
|
||||
_, err = Instance.Client.FeatureV2.SetInstanceFeatures(iamOwnerCTX, &feature.SetInstanceFeaturesRequest{
|
||||
_, err = instance.Client.FeatureV2.SetInstanceFeatures(ctx, &feature.SetInstanceFeaturesRequest{
|
||||
UserSchema: gu.Ptr(true),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
retryDuration := time.Minute
|
||||
if ctxDeadline, ok := iamOwnerCTX.Deadline(); ok {
|
||||
if ctxDeadline, ok := ctx.Deadline(); ok {
|
||||
retryDuration = time.Until(ctxDeadline)
|
||||
}
|
||||
require.EventuallyWithT(t,
|
||||
func(ttt *assert.CollectT) {
|
||||
f, err := Instance.Client.FeatureV2.GetInstanceFeatures(iamOwnerCTX, &feature.GetInstanceFeaturesRequest{
|
||||
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
require.NoError(ttt, err)
|
||||
assert.NoError(ttt, err)
|
||||
if f.UserSchema.GetEnabled() {
|
||||
return
|
||||
}
|
||||
@ -67,4 +60,13 @@ func ensureFeatureEnabled(t *testing.T, iamOwnerCTX context.Context) {
|
||||
retryDuration,
|
||||
time.Second,
|
||||
"timed out waiting for ensuring instance feature")
|
||||
|
||||
require.EventuallyWithT(t,
|
||||
func(ttt *assert.CollectT) {
|
||||
_, err := instance.Client.UserSchemaV3.SearchUserSchemas(ctx, &schema.SearchUserSchemasRequest{})
|
||||
assert.NoError(ttt, err)
|
||||
},
|
||||
retryDuration,
|
||||
time.Second,
|
||||
"timed out waiting for ensuring instance feature call")
|
||||
}
|
||||
|
@ -19,7 +19,10 @@ import (
|
||||
)
|
||||
|
||||
func TestServer_CreateUserSchema(t *testing.T) {
|
||||
ensureFeatureEnabled(t, IAMOwnerCTX)
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -30,7 +33,7 @@ func TestServer_CreateUserSchema(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "missing permission, error",
|
||||
ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
UserSchema: &schema.UserSchema{
|
||||
Type: gofakeit.Name(),
|
||||
@ -40,7 +43,7 @@ func TestServer_CreateUserSchema(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty type",
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
UserSchema: &schema.UserSchema{
|
||||
Type: "",
|
||||
@ -50,7 +53,7 @@ func TestServer_CreateUserSchema(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty schema, error",
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
UserSchema: &schema.UserSchema{
|
||||
Type: gofakeit.Name(),
|
||||
@ -60,7 +63,7 @@ func TestServer_CreateUserSchema(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "invalid schema, error",
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
UserSchema: &schema.UserSchema{
|
||||
Type: gofakeit.Name(),
|
||||
@ -91,7 +94,7 @@ func TestServer_CreateUserSchema(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "no authenticators, ok",
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
UserSchema: &schema.UserSchema{
|
||||
Type: gofakeit.Name(),
|
||||
@ -123,14 +126,14 @@ func TestServer_CreateUserSchema(t *testing.T) {
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Instance.ID(),
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid authenticator, error",
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
UserSchema: &schema.UserSchema{
|
||||
Type: gofakeit.Name(),
|
||||
@ -164,7 +167,7 @@ func TestServer_CreateUserSchema(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "with authenticator, ok",
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
UserSchema: &schema.UserSchema{
|
||||
Type: gofakeit.Name(),
|
||||
@ -199,14 +202,14 @@ func TestServer_CreateUserSchema(t *testing.T) {
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Instance.ID(),
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with invalid permission, error",
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
UserSchema: &schema.UserSchema{
|
||||
Type: gofakeit.Name(),
|
||||
@ -241,7 +244,7 @@ func TestServer_CreateUserSchema(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "with valid permission, ok",
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
UserSchema: &schema.UserSchema{
|
||||
Type: gofakeit.Name(),
|
||||
@ -280,7 +283,7 @@ func TestServer_CreateUserSchema(t *testing.T) {
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Instance.ID(),
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -288,7 +291,7 @@ func TestServer_CreateUserSchema(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Client.CreateUserSchema(tt.ctx, tt.req)
|
||||
got, err := instance.Client.UserSchemaV3.CreateUserSchema(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
@ -301,7 +304,10 @@ func TestServer_CreateUserSchema(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
ensureFeatureEnabled(t, IAMOwnerCTX)
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@ -317,12 +323,12 @@ func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "missing permission, error",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
req: &schema.PatchUserSchemaRequest{
|
||||
UserSchema: &schema.PatchUserSchema{
|
||||
Type: gu.Ptr(gofakeit.Name()),
|
||||
@ -337,7 +343,7 @@ func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{},
|
||||
},
|
||||
wantErr: true,
|
||||
@ -349,7 +355,7 @@ func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{},
|
||||
},
|
||||
wantErr: true,
|
||||
@ -357,12 +363,12 @@ func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "empty type, error",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{
|
||||
UserSchema: &schema.PatchUserSchema{
|
||||
Type: gu.Ptr(""),
|
||||
@ -374,12 +380,12 @@ func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "update type, ok",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{
|
||||
UserSchema: &schema.PatchUserSchema{
|
||||
Type: gu.Ptr(gofakeit.Name()),
|
||||
@ -391,7 +397,7 @@ func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Instance.ID(),
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -399,12 +405,12 @@ func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "empty schema, ok",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{
|
||||
UserSchema: &schema.PatchUserSchema{
|
||||
DataType: &schema.PatchUserSchema_Schema{},
|
||||
@ -416,7 +422,7 @@ func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Instance.ID(),
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -424,12 +430,12 @@ func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "invalid schema, error",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{
|
||||
UserSchema: &schema.PatchUserSchema{
|
||||
DataType: &schema.PatchUserSchema_Schema{
|
||||
@ -462,12 +468,12 @@ func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "update schema, ok",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{
|
||||
UserSchema: &schema.PatchUserSchema{
|
||||
DataType: &schema.PatchUserSchema_Schema{
|
||||
@ -500,7 +506,7 @@ func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Instance.ID(),
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -508,12 +514,12 @@ func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "invalid authenticator, error",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{
|
||||
UserSchema: &schema.PatchUserSchema{
|
||||
PossibleAuthenticators: []schema.AuthenticatorType{
|
||||
@ -527,12 +533,12 @@ func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "update authenticator, ok",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{
|
||||
UserSchema: &schema.PatchUserSchema{
|
||||
PossibleAuthenticators: []schema.AuthenticatorType{
|
||||
@ -546,7 +552,7 @@ func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Instance.ID(),
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -554,8 +560,8 @@ func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "inactive, error",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
_, err := Client.DeactivateUserSchema(IAMOwnerCTX, &schema.DeactivateUserSchemaRequest{
|
||||
schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId()
|
||||
_, err := instance.Client.UserSchemaV3.DeactivateUserSchema(isolatedIAMOwnerCTX, &schema.DeactivateUserSchemaRequest{
|
||||
Id: schemaID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@ -563,7 +569,7 @@ func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{
|
||||
UserSchema: &schema.PatchUserSchema{
|
||||
Type: gu.Ptr(gofakeit.Name()),
|
||||
@ -578,7 +584,7 @@ func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
err := tt.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.PatchUserSchema(tt.args.ctx, tt.args.req)
|
||||
got, err := instance.Client.UserSchemaV3.PatchUserSchema(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
@ -590,7 +596,10 @@ func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_DeactivateUserSchema(t *testing.T) {
|
||||
ensureFeatureEnabled(t, IAMOwnerCTX)
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@ -606,7 +615,7 @@ func TestServer_DeactivateUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "not existing, error",
|
||||
args: args{
|
||||
IAMOwnerCTX,
|
||||
isolatedIAMOwnerCTX,
|
||||
&schema.DeactivateUserSchemaRequest{
|
||||
Id: "notexisting",
|
||||
},
|
||||
@ -617,10 +626,10 @@ func TestServer_DeactivateUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "active, ok",
|
||||
args: args{
|
||||
IAMOwnerCTX,
|
||||
isolatedIAMOwnerCTX,
|
||||
&schema.DeactivateUserSchemaRequest{},
|
||||
func(request *schema.DeactivateUserSchemaRequest) error {
|
||||
schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
@ -630,7 +639,7 @@ func TestServer_DeactivateUserSchema(t *testing.T) {
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Instance.ID(),
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -638,12 +647,12 @@ func TestServer_DeactivateUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "inactive, error",
|
||||
args: args{
|
||||
IAMOwnerCTX,
|
||||
isolatedIAMOwnerCTX,
|
||||
&schema.DeactivateUserSchemaRequest{},
|
||||
func(request *schema.DeactivateUserSchemaRequest) error {
|
||||
schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
_, err := Client.DeactivateUserSchema(IAMOwnerCTX, &schema.DeactivateUserSchemaRequest{
|
||||
_, err := instance.Client.UserSchemaV3.DeactivateUserSchema(isolatedIAMOwnerCTX, &schema.DeactivateUserSchemaRequest{
|
||||
Id: schemaID,
|
||||
})
|
||||
return err
|
||||
@ -657,7 +666,7 @@ func TestServer_DeactivateUserSchema(t *testing.T) {
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.DeactivateUserSchema(tt.args.ctx, tt.args.req)
|
||||
got, err := instance.Client.UserSchemaV3.DeactivateUserSchema(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
@ -669,7 +678,10 @@ func TestServer_DeactivateUserSchema(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_ReactivateUserSchema(t *testing.T) {
|
||||
ensureFeatureEnabled(t, IAMOwnerCTX)
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@ -685,7 +697,7 @@ func TestServer_ReactivateUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "not existing, error",
|
||||
args: args{
|
||||
IAMOwnerCTX,
|
||||
isolatedIAMOwnerCTX,
|
||||
&schema.ReactivateUserSchemaRequest{
|
||||
Id: "notexisting",
|
||||
},
|
||||
@ -696,10 +708,10 @@ func TestServer_ReactivateUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "active, error",
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.ReactivateUserSchemaRequest{},
|
||||
prepare: func(request *schema.ReactivateUserSchemaRequest) error {
|
||||
schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
@ -709,12 +721,12 @@ func TestServer_ReactivateUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "inactive, ok",
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.ReactivateUserSchemaRequest{},
|
||||
prepare: func(request *schema.ReactivateUserSchemaRequest) error {
|
||||
schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
_, err := Client.DeactivateUserSchema(IAMOwnerCTX, &schema.DeactivateUserSchemaRequest{
|
||||
_, err := instance.Client.UserSchemaV3.DeactivateUserSchema(isolatedIAMOwnerCTX, &schema.DeactivateUserSchemaRequest{
|
||||
Id: schemaID,
|
||||
})
|
||||
return err
|
||||
@ -725,7 +737,7 @@ func TestServer_ReactivateUserSchema(t *testing.T) {
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Instance.ID(),
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -736,7 +748,7 @@ func TestServer_ReactivateUserSchema(t *testing.T) {
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.ReactivateUserSchema(tt.args.ctx, tt.args.req)
|
||||
got, err := instance.Client.UserSchemaV3.ReactivateUserSchema(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
@ -748,7 +760,10 @@ func TestServer_ReactivateUserSchema(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_DeleteUserSchema(t *testing.T) {
|
||||
ensureFeatureEnabled(t, IAMOwnerCTX)
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@ -764,7 +779,7 @@ func TestServer_DeleteUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "not existing, error",
|
||||
args: args{
|
||||
IAMOwnerCTX,
|
||||
isolatedIAMOwnerCTX,
|
||||
&schema.DeleteUserSchemaRequest{
|
||||
Id: "notexisting",
|
||||
},
|
||||
@ -775,10 +790,10 @@ func TestServer_DeleteUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "delete, ok",
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.DeleteUserSchemaRequest{},
|
||||
prepare: func(request *schema.DeleteUserSchemaRequest) error {
|
||||
schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
@ -788,7 +803,7 @@ func TestServer_DeleteUserSchema(t *testing.T) {
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Instance.ID(),
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -796,12 +811,12 @@ func TestServer_DeleteUserSchema(t *testing.T) {
|
||||
{
|
||||
name: "deleted, error",
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &schema.DeleteUserSchemaRequest{},
|
||||
prepare: func(request *schema.DeleteUserSchemaRequest) error {
|
||||
schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
_, err := Client.DeleteUserSchema(IAMOwnerCTX, &schema.DeleteUserSchemaRequest{
|
||||
_, err := instance.Client.UserSchemaV3.DeleteUserSchema(isolatedIAMOwnerCTX, &schema.DeleteUserSchemaRequest{
|
||||
Id: schemaID,
|
||||
})
|
||||
return err
|
||||
@ -815,7 +830,7 @@ func TestServer_DeleteUserSchema(t *testing.T) {
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.DeleteUserSchema(tt.args.ctx, tt.args.req)
|
||||
got, err := instance.Client.UserSchemaV3.DeleteUserSchema(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
@ -31,11 +31,11 @@ func TestServer_Limits_AuditLogRetention(t *testing.T) {
|
||||
farPast := timestamppb.New(beforeTime.Add(-10 * time.Hour).UTC())
|
||||
zeroCounts := &eventCounts{}
|
||||
seededCount := requireEventually(t, iamOwnerCtx, isoInstance.Client, userID, projectID, appID, projectGrantID, func(c assert.TestingT, counts *eventCounts) {
|
||||
counts.assertAll(t, c, "seeded events are > 0", assert.Greater, zeroCounts)
|
||||
counts.assertAll(c, "seeded events are > 0", assert.Greater, zeroCounts)
|
||||
}, "wait for seeded event assertions to pass")
|
||||
produceEvents(iamOwnerCtx, t, isoInstance.Client, userID, appID, projectID, projectGrantID)
|
||||
addedCount := requireEventually(t, iamOwnerCtx, isoInstance.Client, userID, projectID, appID, projectGrantID, func(c assert.TestingT, counts *eventCounts) {
|
||||
counts.assertAll(t, c, "added events are > seeded events", assert.Greater, seededCount)
|
||||
counts.assertAll(c, "added events are > seeded events", assert.Greater, seededCount)
|
||||
}, "wait for added event assertions to pass")
|
||||
_, err := integration.SystemClient().SetLimits(CTX, &system.SetLimitsRequest{
|
||||
InstanceId: isoInstance.ID(),
|
||||
@ -44,8 +44,8 @@ func TestServer_Limits_AuditLogRetention(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
var limitedCounts *eventCounts
|
||||
requireEventually(t, iamOwnerCtx, isoInstance.Client, userID, projectID, appID, projectGrantID, func(c assert.TestingT, counts *eventCounts) {
|
||||
counts.assertAll(t, c, "limited events < added events", assert.Less, addedCount)
|
||||
counts.assertAll(t, c, "limited events > 0", assert.Greater, zeroCounts)
|
||||
counts.assertAll(c, "limited events < added events", assert.Less, addedCount)
|
||||
counts.assertAll(c, "limited events > 0", assert.Greater, zeroCounts)
|
||||
limitedCounts = counts
|
||||
}, "wait for limited event assertions to pass")
|
||||
listedEvents, err := isoInstance.Client.Admin.ListEvents(iamOwnerCtx, &admin.ListEventsRequest{CreationDateFilter: &admin.ListEventsRequest_From{
|
||||
@ -63,7 +63,7 @@ func TestServer_Limits_AuditLogRetention(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireEventually(t, iamOwnerCtx, isoInstance.Client, userID, projectID, appID, projectGrantID, func(c assert.TestingT, counts *eventCounts) {
|
||||
counts.assertAll(t, c, "with reset limit, added events are > seeded events", assert.Greater, seededCount)
|
||||
counts.assertAll(c, "with reset limit, added events are > seeded events", assert.Greater, seededCount)
|
||||
}, "wait for reset event assertions to pass")
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ func requireEventually(
|
||||
) (counts *eventCounts) {
|
||||
countTimeout := 30 * time.Second
|
||||
assertTimeout := countTimeout + time.Second
|
||||
countCtx, cancel := context.WithTimeout(ctx, countTimeout)
|
||||
countCtx, cancel := context.WithTimeout(ctx, time.Minute)
|
||||
defer cancel()
|
||||
require.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
counts = countEvents(countCtx, c, cc, userID, projectID, appID, projectGrantID)
|
||||
@ -168,63 +168,77 @@ type eventCounts struct {
|
||||
all, myUser, aUser, grant, project, app, org int
|
||||
}
|
||||
|
||||
func (e *eventCounts) assertAll(t *testing.T, c assert.TestingT, name string, compare assert.ComparisonAssertionFunc, than *eventCounts) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
compare(c, e.all, than.all, "ListEvents")
|
||||
compare(c, e.myUser, than.myUser, "ListMyUserChanges")
|
||||
compare(c, e.aUser, than.aUser, "ListUserChanges")
|
||||
compare(c, e.grant, than.grant, "ListProjectGrantChanges")
|
||||
compare(c, e.project, than.project, "ListProjectChanges")
|
||||
compare(c, e.app, than.app, "ListAppChanges")
|
||||
compare(c, e.org, than.org, "ListOrgChanges")
|
||||
})
|
||||
func (e *eventCounts) assertAll(c assert.TestingT, name string, compare assert.ComparisonAssertionFunc, than *eventCounts) {
|
||||
compare(c, e.all, than.all, name+"ListEvents")
|
||||
compare(c, e.myUser, than.myUser, name+"ListMyUserChanges")
|
||||
compare(c, e.aUser, than.aUser, name+"ListUserChanges")
|
||||
compare(c, e.grant, than.grant, name+"ListProjectGrantChanges")
|
||||
compare(c, e.project, than.project, name+"ListProjectChanges")
|
||||
compare(c, e.app, than.app, name+"ListAppChanges")
|
||||
compare(c, e.org, than.org, name+"ListOrgChanges")
|
||||
}
|
||||
|
||||
func countEvents(ctx context.Context, t assert.TestingT, cc *integration.Client, userID, projectID, appID, grantID string) *eventCounts {
|
||||
counts := new(eventCounts)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(7)
|
||||
|
||||
var mutex sync.Mutex
|
||||
assertResultLocked := func(err error, f func(counts *eventCounts)) {
|
||||
mutex.Lock()
|
||||
assert.NoError(t, err)
|
||||
f(counts)
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
result, err := cc.Admin.ListEvents(ctx, &admin.ListEventsRequest{})
|
||||
assert.NoError(t, err)
|
||||
counts.all = len(result.GetEvents())
|
||||
assertResultLocked(err, func(counts *eventCounts) {
|
||||
counts.all = len(result.GetEvents())
|
||||
})
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
result, err := cc.Auth.ListMyUserChanges(ctx, &auth.ListMyUserChangesRequest{})
|
||||
assert.NoError(t, err)
|
||||
counts.myUser = len(result.GetResult())
|
||||
assertResultLocked(err, func(counts *eventCounts) {
|
||||
counts.myUser = len(result.GetResult())
|
||||
})
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
result, err := cc.Mgmt.ListUserChanges(ctx, &management.ListUserChangesRequest{UserId: userID})
|
||||
assert.NoError(t, err)
|
||||
counts.aUser = len(result.GetResult())
|
||||
assertResultLocked(err, func(counts *eventCounts) {
|
||||
counts.aUser = len(result.GetResult())
|
||||
})
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
result, err := cc.Mgmt.ListAppChanges(ctx, &management.ListAppChangesRequest{ProjectId: projectID, AppId: appID})
|
||||
assert.NoError(t, err)
|
||||
counts.app = len(result.GetResult())
|
||||
assertResultLocked(err, func(counts *eventCounts) {
|
||||
counts.app = len(result.GetResult())
|
||||
})
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
result, err := cc.Mgmt.ListOrgChanges(ctx, &management.ListOrgChangesRequest{})
|
||||
assert.NoError(t, err)
|
||||
counts.org = len(result.GetResult())
|
||||
assertResultLocked(err, func(counts *eventCounts) {
|
||||
counts.org = len(result.GetResult())
|
||||
})
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
result, err := cc.Mgmt.ListProjectChanges(ctx, &management.ListProjectChangesRequest{ProjectId: projectID})
|
||||
assert.NoError(t, err)
|
||||
counts.project = len(result.GetResult())
|
||||
assertResultLocked(err, func(counts *eventCounts) {
|
||||
counts.project = len(result.GetResult())
|
||||
})
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
result, err := cc.Mgmt.ListProjectGrantChanges(ctx, &management.ListProjectGrantChangesRequest{ProjectId: projectID, GrantId: grantID})
|
||||
assert.NoError(t, err)
|
||||
counts.grant = len(result.GetResult())
|
||||
assertResultLocked(err, func(counts *eventCounts) {
|
||||
counts.grant = len(result.GetResult())
|
||||
})
|
||||
}()
|
||||
wg.Wait()
|
||||
return counts
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/zitadel/oidc/v3/pkg/client/profile"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
oidc_api "github.com/zitadel/zitadel/internal/api/oidc"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@ -98,13 +99,19 @@ func TestServer_JWTProfile(t *testing.T) {
|
||||
tokenSource, err := profile.NewJWTProfileTokenSourceFromKeyFileData(CTX, Instance.OIDCIssuer(), tt.keyData, tt.scope)
|
||||
require.NoError(t, err)
|
||||
|
||||
tokens, err := tokenSource.TokenCtx(CTX)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, tokens)
|
||||
var tokens *oauth2.Token
|
||||
require.EventuallyWithT(
|
||||
t, func(collect *assert.CollectT) {
|
||||
tokens, err = tokenSource.TokenCtx(CTX)
|
||||
if tt.wantErr {
|
||||
assert.Error(collect, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(collect, err)
|
||||
assert.NotNil(collect, tokens)
|
||||
},
|
||||
time.Minute, time.Second,
|
||||
)
|
||||
|
||||
provider, err := rp.NewRelyingPartyOIDC(CTX, Instance.OIDCIssuer(), "", "", redirectURI, tt.scope)
|
||||
require.NoError(t, err)
|
||||
|
106
internal/cache/cache.go
vendored
Normal file
106
internal/cache/cache.go
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
// Package cache provides abstraction of cache implementations that can be used by zitadel.
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
)
|
||||
|
||||
// Cache stores objects with a value of type `V`.
|
||||
// Objects may be referred to by one or more indices.
|
||||
// Implementations may encode the value for storage.
|
||||
// This means non-exported fields may be lost and objects
|
||||
// with function values may fail to encode.
|
||||
// See https://pkg.go.dev/encoding/json#Marshal for example.
|
||||
//
|
||||
// `I` is the type by which indices are identified,
|
||||
// typically an enum for type-safe access.
|
||||
// Indices are defined when calling the constructor of an implementation of this interface.
|
||||
// It is illegal to refer to an idex not defined during construction.
|
||||
//
|
||||
// `K` is the type used as key in each index.
|
||||
// Due to the limitations in type constraints, all indices use the same key type.
|
||||
//
|
||||
// Implementations are free to use stricter type constraints or fixed typing.
|
||||
type Cache[I, K comparable, V Entry[I, K]] interface {
|
||||
// Get an object through specified index.
|
||||
// An [IndexUnknownError] may be returned if the index is unknown.
|
||||
// [ErrCacheMiss] is returned if the key was not found in the index,
|
||||
// or the object is not valid.
|
||||
Get(ctx context.Context, index I, key K) (V, bool)
|
||||
|
||||
// Set an object.
|
||||
// Keys are created on each index based in the [Entry.Keys] method.
|
||||
// If any key maps to an existing object, the object is invalidated,
|
||||
// regardless if the object has other keys defined in the new entry.
|
||||
// This to prevent ghost objects when an entry reduces the amount of keys
|
||||
// for a given index.
|
||||
Set(ctx context.Context, value V)
|
||||
|
||||
// Invalidate an object through specified index.
|
||||
// Implementations may choose to instantly delete the object,
|
||||
// defer until prune or a separate cleanup routine.
|
||||
// Invalidated object are no longer returned from Get.
|
||||
// It is safe to call Invalidate multiple times or on non-existing entries.
|
||||
Invalidate(ctx context.Context, index I, key ...K) error
|
||||
|
||||
// Delete one or more keys from a specific index.
|
||||
// An [IndexUnknownError] may be returned if the index is unknown.
|
||||
// The referred object is not invalidated and may still be accessible though
|
||||
// other indices and keys.
|
||||
// It is safe to call Delete multiple times or on non-existing entries
|
||||
Delete(ctx context.Context, index I, key ...K) error
|
||||
|
||||
// Truncate deletes all cached objects.
|
||||
Truncate(ctx context.Context) error
|
||||
|
||||
// Close the cache. Subsequent calls to the cache are not allowed.
|
||||
Close(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Entry contains a value of type `V` to be cached.
|
||||
//
|
||||
// `I` is the type by which indices are identified,
|
||||
// typically an enum for type-safe access.
|
||||
//
|
||||
// `K` is the type used as key in an index.
|
||||
// Due to the limitations in type constraints, all indices use the same key type.
|
||||
type Entry[I, K comparable] interface {
|
||||
// Keys returns which keys map to the object in a specified index.
|
||||
// May return nil if the index in unknown or when there are no keys.
|
||||
Keys(index I) (key []K)
|
||||
}
|
||||
|
||||
type CachesConfig struct {
|
||||
Connectors struct {
|
||||
Memory MemoryConnectorConfig
|
||||
// SQL database.Config
|
||||
// Redis redis.Config?
|
||||
}
|
||||
Instance *CacheConfig
|
||||
}
|
||||
|
||||
type CacheConfig struct {
|
||||
Connector string
|
||||
|
||||
// Age since an object was added to the cache,
|
||||
// after which the object is considered invalid.
|
||||
// 0 disables max age checks.
|
||||
MaxAge time.Duration
|
||||
|
||||
// Age since last use (Get) of an object,
|
||||
// after which the object is considered invalid.
|
||||
// 0 disables last use age checks.
|
||||
LastUseAge time.Duration
|
||||
|
||||
// Log allows logging of the specific cache.
|
||||
// By default only errors are logged to stdout.
|
||||
Log *logging.Config
|
||||
}
|
||||
|
||||
type MemoryConnectorConfig struct {
|
||||
Enabled bool
|
||||
AutoPrune AutoPruneConfig
|
||||
}
|
29
internal/cache/error.go
vendored
Normal file
29
internal/cache/error.go
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type IndexUnknownError[I comparable] struct {
|
||||
index I
|
||||
}
|
||||
|
||||
func NewIndexUnknownErr[I comparable](index I) error {
|
||||
return IndexUnknownError[I]{index}
|
||||
}
|
||||
|
||||
func (i IndexUnknownError[I]) Error() string {
|
||||
return fmt.Sprintf("index %v unknown", i.index)
|
||||
}
|
||||
|
||||
func (a IndexUnknownError[I]) Is(err error) bool {
|
||||
if b, ok := err.(IndexUnknownError[I]); ok {
|
||||
return a.index == b.index
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
ErrCacheMiss = errors.New("cache miss")
|
||||
)
|
204
internal/cache/gomap/gomap.go
vendored
Normal file
204
internal/cache/gomap/gomap.go
vendored
Normal file
@ -0,0 +1,204 @@
|
||||
package gomap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/cache"
|
||||
)
|
||||
|
||||
type mapCache[I, K comparable, V cache.Entry[I, K]] struct {
|
||||
config *cache.CacheConfig
|
||||
indexMap map[I]*index[K, V]
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// NewCache returns an in-memory Cache implementation based on the builtin go map type.
|
||||
// Object values are stored as-is and there is no encoding or decoding involved.
|
||||
func NewCache[I, K comparable, V cache.Entry[I, K]](background context.Context, indices []I, config cache.CacheConfig) cache.PrunerCache[I, K, V] {
|
||||
m := &mapCache[I, K, V]{
|
||||
config: &config,
|
||||
indexMap: make(map[I]*index[K, V], len(indices)),
|
||||
logger: slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||
AddSource: true,
|
||||
Level: slog.LevelError,
|
||||
})),
|
||||
}
|
||||
if config.Log != nil {
|
||||
m.logger = config.Log.Slog()
|
||||
}
|
||||
m.logger.InfoContext(background, "map cache logging enabled")
|
||||
|
||||
for _, name := range indices {
|
||||
m.indexMap[name] = &index[K, V]{
|
||||
config: m.config,
|
||||
entries: make(map[K]*entry[V]),
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (c *mapCache[I, K, V]) Get(ctx context.Context, index I, key K) (value V, ok bool) {
|
||||
i, ok := c.indexMap[index]
|
||||
if !ok {
|
||||
c.logger.ErrorContext(ctx, "map cache get", "err", cache.NewIndexUnknownErr(index), "index", index, "key", key)
|
||||
return value, false
|
||||
}
|
||||
entry, err := i.Get(key)
|
||||
if err == nil {
|
||||
c.logger.DebugContext(ctx, "map cache get", "index", index, "key", key)
|
||||
return entry.value, true
|
||||
}
|
||||
if errors.Is(err, cache.ErrCacheMiss) {
|
||||
c.logger.InfoContext(ctx, "map cache get", "err", err, "index", index, "key", key)
|
||||
return value, false
|
||||
}
|
||||
c.logger.ErrorContext(ctx, "map cache get", "err", cache.NewIndexUnknownErr(index), "index", index, "key", key)
|
||||
return value, false
|
||||
}
|
||||
|
||||
func (c *mapCache[I, K, V]) Set(ctx context.Context, value V) {
|
||||
now := time.Now()
|
||||
entry := &entry[V]{
|
||||
value: value,
|
||||
created: now,
|
||||
}
|
||||
entry.lastUse.Store(now.UnixMicro())
|
||||
|
||||
for name, i := range c.indexMap {
|
||||
keys := value.Keys(name)
|
||||
i.Set(keys, entry)
|
||||
c.logger.DebugContext(ctx, "map cache set", "index", name, "keys", keys)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *mapCache[I, K, V]) Invalidate(ctx context.Context, index I, keys ...K) error {
|
||||
i, ok := c.indexMap[index]
|
||||
if !ok {
|
||||
return cache.NewIndexUnknownErr(index)
|
||||
}
|
||||
i.Invalidate(keys)
|
||||
c.logger.DebugContext(ctx, "map cache invalidate", "index", index, "keys", keys)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mapCache[I, K, V]) Delete(ctx context.Context, index I, keys ...K) error {
|
||||
i, ok := c.indexMap[index]
|
||||
if !ok {
|
||||
return cache.NewIndexUnknownErr(index)
|
||||
}
|
||||
i.Delete(keys)
|
||||
c.logger.DebugContext(ctx, "map cache delete", "index", index, "keys", keys)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mapCache[I, K, V]) Prune(ctx context.Context) error {
|
||||
for name, index := range c.indexMap {
|
||||
index.Prune()
|
||||
c.logger.DebugContext(ctx, "map cache prune", "index", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mapCache[I, K, V]) Truncate(ctx context.Context) error {
|
||||
for name, index := range c.indexMap {
|
||||
index.Truncate()
|
||||
c.logger.DebugContext(ctx, "map cache clear", "index", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mapCache[I, K, V]) Close(ctx context.Context) error {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
type index[K comparable, V any] struct {
|
||||
mutex sync.RWMutex
|
||||
config *cache.CacheConfig
|
||||
entries map[K]*entry[V]
|
||||
}
|
||||
|
||||
func (i *index[K, V]) Get(key K) (*entry[V], error) {
|
||||
i.mutex.RLock()
|
||||
entry, ok := i.entries[key]
|
||||
i.mutex.RUnlock()
|
||||
if ok && entry.isValid(i.config) {
|
||||
return entry, nil
|
||||
}
|
||||
return nil, cache.ErrCacheMiss
|
||||
}
|
||||
|
||||
func (c *index[K, V]) Set(keys []K, entry *entry[V]) {
|
||||
c.mutex.Lock()
|
||||
for _, key := range keys {
|
||||
c.entries[key] = entry
|
||||
}
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (i *index[K, V]) Invalidate(keys []K) {
|
||||
i.mutex.RLock()
|
||||
for _, key := range keys {
|
||||
if entry, ok := i.entries[key]; ok {
|
||||
entry.invalid.Store(true)
|
||||
}
|
||||
}
|
||||
i.mutex.RUnlock()
|
||||
}
|
||||
|
||||
func (c *index[K, V]) Delete(keys []K) {
|
||||
c.mutex.Lock()
|
||||
for _, key := range keys {
|
||||
delete(c.entries, key)
|
||||
}
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (c *index[K, V]) Prune() {
|
||||
c.mutex.Lock()
|
||||
maps.DeleteFunc(c.entries, func(_ K, entry *entry[V]) bool {
|
||||
return !entry.isValid(c.config)
|
||||
})
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (c *index[K, V]) Truncate() {
|
||||
c.mutex.Lock()
|
||||
c.entries = make(map[K]*entry[V])
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
|
||||
type entry[V any] struct {
|
||||
value V
|
||||
created time.Time
|
||||
invalid atomic.Bool
|
||||
lastUse atomic.Int64 // UnixMicro time
|
||||
}
|
||||
|
||||
func (e *entry[V]) isValid(c *cache.CacheConfig) bool {
|
||||
if e.invalid.Load() {
|
||||
return false
|
||||
}
|
||||
now := time.Now()
|
||||
if c.MaxAge > 0 {
|
||||
if e.created.Add(c.MaxAge).Before(now) {
|
||||
e.invalid.Store(true)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if c.LastUseAge > 0 {
|
||||
lastUse := e.lastUse.Load()
|
||||
if time.UnixMicro(lastUse).Add(c.LastUseAge).Before(now) {
|
||||
e.invalid.Store(true)
|
||||
return false
|
||||
}
|
||||
e.lastUse.CompareAndSwap(lastUse, now.UnixMicro())
|
||||
}
|
||||
return true
|
||||
}
|
334
internal/cache/gomap/gomap_test.go
vendored
Normal file
334
internal/cache/gomap/gomap_test.go
vendored
Normal file
@ -0,0 +1,334 @@
|
||||
package gomap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/cache"
|
||||
)
|
||||
|
||||
type testIndex int
|
||||
|
||||
const (
|
||||
testIndexID testIndex = iota
|
||||
testIndexName
|
||||
)
|
||||
|
||||
var testIndices = []testIndex{
|
||||
testIndexID,
|
||||
testIndexName,
|
||||
}
|
||||
|
||||
type testObject struct {
|
||||
id string
|
||||
names []string
|
||||
}
|
||||
|
||||
func (o *testObject) Keys(index testIndex) []string {
|
||||
switch index {
|
||||
case testIndexID:
|
||||
return []string{o.id}
|
||||
case testIndexName:
|
||||
return o.names
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func Test_mapCache_Get(t *testing.T) {
|
||||
c := NewCache[testIndex, string, *testObject](context.Background(), testIndices, cache.CacheConfig{
|
||||
MaxAge: time.Second,
|
||||
LastUseAge: time.Second / 4,
|
||||
Log: &logging.Config{
|
||||
Level: "debug",
|
||||
AddSource: true,
|
||||
},
|
||||
})
|
||||
defer c.Close(context.Background())
|
||||
obj := &testObject{
|
||||
id: "id",
|
||||
names: []string{"foo", "bar"},
|
||||
}
|
||||
c.Set(context.Background(), obj)
|
||||
|
||||
type args struct {
|
||||
index testIndex
|
||||
key string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *testObject
|
||||
wantOk bool
|
||||
}{
|
||||
{
|
||||
name: "ok",
|
||||
args: args{
|
||||
index: testIndexID,
|
||||
key: "id",
|
||||
},
|
||||
want: obj,
|
||||
wantOk: true,
|
||||
},
|
||||
{
|
||||
name: "miss",
|
||||
args: args{
|
||||
index: testIndexID,
|
||||
key: "spanac",
|
||||
},
|
||||
want: nil,
|
||||
wantOk: false,
|
||||
},
|
||||
{
|
||||
name: "unknown index",
|
||||
args: args{
|
||||
index: 99,
|
||||
key: "id",
|
||||
},
|
||||
want: nil,
|
||||
wantOk: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, ok := c.Get(context.Background(), tt.args.index, tt.args.key)
|
||||
assert.Equal(t, tt.want, got)
|
||||
assert.Equal(t, tt.wantOk, ok)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_mapCache_Invalidate(t *testing.T) {
|
||||
c := NewCache[testIndex, string, *testObject](context.Background(), testIndices, cache.CacheConfig{
|
||||
MaxAge: time.Second,
|
||||
LastUseAge: time.Second / 4,
|
||||
Log: &logging.Config{
|
||||
Level: "debug",
|
||||
AddSource: true,
|
||||
},
|
||||
})
|
||||
defer c.Close(context.Background())
|
||||
obj := &testObject{
|
||||
id: "id",
|
||||
names: []string{"foo", "bar"},
|
||||
}
|
||||
c.Set(context.Background(), obj)
|
||||
err := c.Invalidate(context.Background(), testIndexName, "bar")
|
||||
require.NoError(t, err)
|
||||
got, ok := c.Get(context.Background(), testIndexID, "id")
|
||||
assert.Nil(t, got)
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func Test_mapCache_Delete(t *testing.T) {
|
||||
c := NewCache[testIndex, string, *testObject](context.Background(), testIndices, cache.CacheConfig{
|
||||
MaxAge: time.Second,
|
||||
LastUseAge: time.Second / 4,
|
||||
Log: &logging.Config{
|
||||
Level: "debug",
|
||||
AddSource: true,
|
||||
},
|
||||
})
|
||||
defer c.Close(context.Background())
|
||||
obj := &testObject{
|
||||
id: "id",
|
||||
names: []string{"foo", "bar"},
|
||||
}
|
||||
c.Set(context.Background(), obj)
|
||||
err := c.Delete(context.Background(), testIndexName, "bar")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Shouldn't find object by deleted name
|
||||
got, ok := c.Get(context.Background(), testIndexName, "bar")
|
||||
assert.Nil(t, got)
|
||||
assert.False(t, ok)
|
||||
|
||||
// Should find object by other name
|
||||
got, ok = c.Get(context.Background(), testIndexName, "foo")
|
||||
assert.Equal(t, obj, got)
|
||||
assert.True(t, ok)
|
||||
|
||||
// Should find object by id
|
||||
got, ok = c.Get(context.Background(), testIndexID, "id")
|
||||
assert.Equal(t, obj, got)
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func Test_mapCache_Prune(t *testing.T) {
|
||||
c := NewCache[testIndex, string, *testObject](context.Background(), testIndices, cache.CacheConfig{
|
||||
MaxAge: time.Second,
|
||||
LastUseAge: time.Second / 4,
|
||||
Log: &logging.Config{
|
||||
Level: "debug",
|
||||
AddSource: true,
|
||||
},
|
||||
})
|
||||
defer c.Close(context.Background())
|
||||
|
||||
objects := []*testObject{
|
||||
{
|
||||
id: "id1",
|
||||
names: []string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
id: "id2",
|
||||
names: []string{"hello"},
|
||||
},
|
||||
}
|
||||
for _, obj := range objects {
|
||||
c.Set(context.Background(), obj)
|
||||
}
|
||||
// invalidate one entry
|
||||
err := c.Invalidate(context.Background(), testIndexName, "bar")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = c.(cache.Pruner).Prune(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Other object should still be found
|
||||
got, ok := c.Get(context.Background(), testIndexID, "id2")
|
||||
assert.Equal(t, objects[1], got)
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func Test_mapCache_Truncate(t *testing.T) {
|
||||
c := NewCache[testIndex, string, *testObject](context.Background(), testIndices, cache.CacheConfig{
|
||||
MaxAge: time.Second,
|
||||
LastUseAge: time.Second / 4,
|
||||
Log: &logging.Config{
|
||||
Level: "debug",
|
||||
AddSource: true,
|
||||
},
|
||||
})
|
||||
defer c.Close(context.Background())
|
||||
objects := []*testObject{
|
||||
{
|
||||
id: "id1",
|
||||
names: []string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
id: "id2",
|
||||
names: []string{"hello"},
|
||||
},
|
||||
}
|
||||
for _, obj := range objects {
|
||||
c.Set(context.Background(), obj)
|
||||
}
|
||||
|
||||
err := c.Truncate(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
mc := c.(*mapCache[testIndex, string, *testObject])
|
||||
for _, index := range mc.indexMap {
|
||||
index.mutex.RLock()
|
||||
assert.Len(t, index.entries, 0)
|
||||
index.mutex.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
func Test_entry_isValid(t *testing.T) {
|
||||
type fields struct {
|
||||
created time.Time
|
||||
invalid bool
|
||||
lastUse time.Time
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
config *cache.CacheConfig
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "invalid",
|
||||
fields: fields{
|
||||
created: time.Now(),
|
||||
invalid: true,
|
||||
lastUse: time.Now(),
|
||||
},
|
||||
config: &cache.CacheConfig{
|
||||
MaxAge: time.Minute,
|
||||
LastUseAge: time.Second,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "max age exceeded",
|
||||
fields: fields{
|
||||
created: time.Now().Add(-(time.Minute + time.Second)),
|
||||
invalid: false,
|
||||
lastUse: time.Now(),
|
||||
},
|
||||
config: &cache.CacheConfig{
|
||||
MaxAge: time.Minute,
|
||||
LastUseAge: time.Second,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "max age disabled",
|
||||
fields: fields{
|
||||
created: time.Now().Add(-(time.Minute + time.Second)),
|
||||
invalid: false,
|
||||
lastUse: time.Now(),
|
||||
},
|
||||
config: &cache.CacheConfig{
|
||||
LastUseAge: time.Second,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "last use age exceeded",
|
||||
fields: fields{
|
||||
created: time.Now().Add(-(time.Minute / 2)),
|
||||
invalid: false,
|
||||
lastUse: time.Now().Add(-(time.Second * 2)),
|
||||
},
|
||||
config: &cache.CacheConfig{
|
||||
MaxAge: time.Minute,
|
||||
LastUseAge: time.Second,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "last use age disabled",
|
||||
fields: fields{
|
||||
created: time.Now().Add(-(time.Minute / 2)),
|
||||
invalid: false,
|
||||
lastUse: time.Now().Add(-(time.Second * 2)),
|
||||
},
|
||||
config: &cache.CacheConfig{
|
||||
MaxAge: time.Minute,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
fields: fields{
|
||||
created: time.Now(),
|
||||
invalid: false,
|
||||
lastUse: time.Now(),
|
||||
},
|
||||
config: &cache.CacheConfig{
|
||||
MaxAge: time.Minute,
|
||||
LastUseAge: time.Second,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
e := &entry[any]{
|
||||
created: tt.fields.created,
|
||||
}
|
||||
e.invalid.Store(tt.fields.invalid)
|
||||
e.lastUse.Store(tt.fields.lastUse.UnixMicro())
|
||||
got := e.isValid(tt.config)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
22
internal/cache/noop/noop.go
vendored
Normal file
22
internal/cache/noop/noop.go
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
package noop
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/cache"
|
||||
)
|
||||
|
||||
type noop[I, K comparable, V cache.Entry[I, K]] struct{}
|
||||
|
||||
// NewCache returns a cache that does nothing
|
||||
func NewCache[I, K comparable, V cache.Entry[I, K]]() cache.Cache[I, K, V] {
|
||||
return noop[I, K, V]{}
|
||||
}
|
||||
|
||||
func (noop[I, K, V]) Set(context.Context, V) {}
|
||||
func (noop[I, K, V]) Get(context.Context, I, K) (value V, ok bool) { return }
|
||||
func (noop[I, K, V]) Invalidate(context.Context, I, ...K) (err error) { return }
|
||||
func (noop[I, K, V]) Delete(context.Context, I, ...K) (err error) { return }
|
||||
func (noop[I, K, V]) Prune(context.Context) (err error) { return }
|
||||
func (noop[I, K, V]) Truncate(context.Context) (err error) { return }
|
||||
func (noop[I, K, V]) Close(context.Context) (err error) { return }
|
76
internal/cache/pruner.go
vendored
Normal file
76
internal/cache/pruner.go
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/jonboulle/clockwork"
|
||||
"github.com/zitadel/logging"
|
||||
)
|
||||
|
||||
// Pruner is an optional [Cache] interface.
|
||||
type Pruner interface {
|
||||
// Prune deletes all invalidated or expired objects.
|
||||
Prune(ctx context.Context) error
|
||||
}
|
||||
|
||||
type PrunerCache[I, K comparable, V Entry[I, K]] interface {
|
||||
Cache[I, K, V]
|
||||
Pruner
|
||||
}
|
||||
|
||||
type AutoPruneConfig struct {
|
||||
// Interval at which the cache is automatically pruned.
|
||||
// 0 or lower disables automatic pruning.
|
||||
Interval time.Duration
|
||||
|
||||
// Timeout for an automatic prune.
|
||||
// It is recommended to keep the value shorter than AutoPruneInterval
|
||||
// 0 or lower disables automatic pruning.
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func (c AutoPruneConfig) StartAutoPrune(background context.Context, pruner Pruner, name string) (close func()) {
|
||||
return c.startAutoPrune(background, pruner, name, clockwork.NewRealClock())
|
||||
}
|
||||
|
||||
func (c *AutoPruneConfig) startAutoPrune(background context.Context, pruner Pruner, name string, clock clockwork.Clock) (close func()) {
|
||||
if c.Interval <= 0 {
|
||||
return func() {}
|
||||
}
|
||||
background, cancel := context.WithCancel(background)
|
||||
// randomize the first interval
|
||||
timer := clock.NewTimer(time.Duration(rand.Int63n(int64(c.Interval))))
|
||||
go c.pruneTimer(background, pruner, name, timer)
|
||||
return cancel
|
||||
}
|
||||
|
||||
func (c *AutoPruneConfig) pruneTimer(background context.Context, pruner Pruner, name string, timer clockwork.Timer) {
|
||||
defer func() {
|
||||
if !timer.Stop() {
|
||||
<-timer.Chan()
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-background.Done():
|
||||
return
|
||||
case <-timer.Chan():
|
||||
timer.Reset(c.Interval)
|
||||
err := c.doPrune(background, pruner)
|
||||
logging.OnError(err).WithField("name", name).Error("cache auto prune")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *AutoPruneConfig) doPrune(background context.Context, pruner Pruner) error {
|
||||
ctx, cancel := context.WithCancel(background)
|
||||
defer cancel()
|
||||
if c.Timeout > 0 {
|
||||
ctx, cancel = context.WithTimeout(background, c.Timeout)
|
||||
defer cancel()
|
||||
}
|
||||
return pruner.Prune(ctx)
|
||||
}
|
43
internal/cache/pruner_test.go
vendored
Normal file
43
internal/cache/pruner_test.go
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jonboulle/clockwork"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type testPruner struct {
|
||||
called chan struct{}
|
||||
}
|
||||
|
||||
func (p *testPruner) Prune(context.Context) error {
|
||||
p.called <- struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestAutoPruneConfig_startAutoPrune(t *testing.T) {
|
||||
c := AutoPruneConfig{
|
||||
Interval: time.Second,
|
||||
Timeout: time.Millisecond,
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
pruner := testPruner{
|
||||
called: make(chan struct{}),
|
||||
}
|
||||
clock := clockwork.NewFakeClock()
|
||||
close := c.startAutoPrune(ctx, &pruner, "foo", clock)
|
||||
defer close()
|
||||
clock.Advance(time.Second)
|
||||
|
||||
select {
|
||||
case _, ok := <-pruner.called:
|
||||
assert.True(t, ok)
|
||||
case <-ctx.Done():
|
||||
t.Fatal(ctx.Err())
|
||||
}
|
||||
}
|
@ -189,6 +189,9 @@ type AppendReducer interface {
|
||||
}
|
||||
|
||||
func (c *Commands) pushAppendAndReduce(ctx context.Context, object AppendReducer, cmds ...eventstore.Command) error {
|
||||
if len(cmds) == 0 {
|
||||
return nil
|
||||
}
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -196,6 +199,20 @@ func (c *Commands) pushAppendAndReduce(ctx context.Context, object AppendReducer
|
||||
return AppendAndReduce(object, events...)
|
||||
}
|
||||
|
||||
type AppendReducerDetails interface {
|
||||
AppendEvents(...eventstore.Event)
|
||||
// TODO: Why is it allowed to return an error here?
|
||||
Reduce() error
|
||||
GetWriteModel() *eventstore.WriteModel
|
||||
}
|
||||
|
||||
func (c *Commands) pushAppendAndReduceDetails(ctx context.Context, object AppendReducerDetails, cmds ...eventstore.Command) (*domain.ObjectDetails, error) {
|
||||
if err := c.pushAppendAndReduce(ctx, object, cmds...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(object.GetWriteModel()), nil
|
||||
}
|
||||
|
||||
func AppendAndReduce(object AppendReducer, events ...eventstore.Event) error {
|
||||
object.AppendEvents(events...)
|
||||
return object.Reduce()
|
||||
|
@ -186,7 +186,7 @@ func validateUserSchema(userSchema json.RawMessage) error {
|
||||
}
|
||||
|
||||
func (c *Commands) getSchemaWriteModelByID(ctx context.Context, resourceOwner, id string) (*UserSchemaWriteModel, error) {
|
||||
writeModel := NewUserSchemaWriteModel(resourceOwner, id, "")
|
||||
writeModel := NewUserSchemaWriteModel(resourceOwner, id)
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -19,16 +19,15 @@ type UserSchemaWriteModel struct {
|
||||
Schema json.RawMessage
|
||||
PossibleAuthenticators []domain.AuthenticatorType
|
||||
State domain.UserSchemaState
|
||||
Revision uint64
|
||||
SchemaRevision uint64
|
||||
}
|
||||
|
||||
func NewUserSchemaWriteModel(resourceOwner, schemaID, ty string) *UserSchemaWriteModel {
|
||||
func NewUserSchemaWriteModel(resourceOwner, schemaID string) *UserSchemaWriteModel {
|
||||
return &UserSchemaWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: schemaID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
SchemaType: ty,
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,13 +39,13 @@ func (wm *UserSchemaWriteModel) Reduce() error {
|
||||
wm.Schema = e.Schema
|
||||
wm.PossibleAuthenticators = e.PossibleAuthenticators
|
||||
wm.State = domain.UserSchemaStateActive
|
||||
wm.Revision = 1
|
||||
wm.SchemaRevision = 1
|
||||
case *schema.UpdatedEvent:
|
||||
if e.SchemaType != nil {
|
||||
wm.SchemaType = *e.SchemaType
|
||||
}
|
||||
if e.SchemaRevision != nil {
|
||||
wm.Revision = *e.SchemaRevision
|
||||
wm.SchemaRevision = *e.SchemaRevision
|
||||
}
|
||||
if len(e.Schema) > 0 {
|
||||
wm.Schema = e.Schema
|
||||
@ -79,10 +78,6 @@ func (wm *UserSchemaWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
schema.DeletedType,
|
||||
)
|
||||
|
||||
if wm.SchemaType != "" {
|
||||
query = query.EventData(map[string]interface{}{"schemaType": wm.SchemaType})
|
||||
}
|
||||
|
||||
return query.Builder()
|
||||
}
|
||||
func (wm *UserSchemaWriteModel) NewUpdatedEvent(
|
||||
@ -99,7 +94,7 @@ func (wm *UserSchemaWriteModel) NewUpdatedEvent(
|
||||
if !bytes.Equal(wm.Schema, userSchema) {
|
||||
changes = append(changes, schema.ChangeSchema(userSchema))
|
||||
// change revision if the content of the schema changed
|
||||
changes = append(changes, schema.IncreaseRevision(wm.Revision))
|
||||
changes = append(changes, schema.IncreaseRevision(wm.SchemaRevision))
|
||||
}
|
||||
if len(possibleAuthenticators) > 0 && slices.Compare(wm.PossibleAuthenticators, possibleAuthenticators) != 0 {
|
||||
changes = append(changes, schema.ChangePossibleAuthenticators(possibleAuthenticators))
|
||||
|
@ -6,28 +6,24 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
domain_schema "github.com/zitadel/zitadel/internal/domain/schema"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/user/schemauser"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type CreateSchemaUser struct {
|
||||
Details *domain.ObjectDetails
|
||||
ResourceOwner string
|
||||
|
||||
SchemaID string
|
||||
schemaRevision uint64
|
||||
|
||||
ID string
|
||||
Data json.RawMessage
|
||||
ResourceOwner string
|
||||
ID string
|
||||
Data json.RawMessage
|
||||
|
||||
Email *Email
|
||||
ReturnCodeEmail string
|
||||
ReturnCodeEmail *string
|
||||
Phone *Phone
|
||||
ReturnCodePhone string
|
||||
ReturnCodePhone *string
|
||||
}
|
||||
|
||||
func (s *CreateSchemaUser) Valid(ctx context.Context, c *Commands) (err error) {
|
||||
@ -45,7 +41,7 @@ func (s *CreateSchemaUser) Valid(ctx context.Context, c *Commands) (err error) {
|
||||
if !schemaWriteModel.Exists() {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-N9QOuN4F7o", "Errors.UserSchema.NotExists")
|
||||
}
|
||||
s.schemaRevision = schemaWriteModel.Revision
|
||||
s.schemaRevision = schemaWriteModel.SchemaRevision
|
||||
|
||||
if s.ID == "" {
|
||||
s.ID, err = c.idGenerator.Next()
|
||||
@ -99,120 +95,263 @@ func (c *Commands) getSchemaRoleForWrite(ctx context.Context, resourceOwner, use
|
||||
return domain_schema.RoleOwner, nil
|
||||
}
|
||||
|
||||
func (c *Commands) CreateSchemaUser(ctx context.Context, user *CreateSchemaUser, alg crypto.EncryptionAlgorithm) (err error) {
|
||||
func (c *Commands) CreateSchemaUser(ctx context.Context, user *CreateSchemaUser) (*domain.ObjectDetails, error) {
|
||||
if err := user.Valid(ctx, c); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
writeModel, err := c.getSchemaUserExists(ctx, user.ResourceOwner, user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if writeModel.Exists() {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-Nn8CRVlkeZ", "Errors.User.AlreadyExists")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userAgg := UserV3AggregateFromWriteModel(&writeModel.WriteModel)
|
||||
events := []eventstore.Command{
|
||||
schemauser.NewCreatedEvent(ctx,
|
||||
userAgg,
|
||||
user.SchemaID, user.schemaRevision, user.Data,
|
||||
),
|
||||
events, codeEmail, codePhone, err := writeModel.NewCreated(ctx,
|
||||
user.SchemaID,
|
||||
user.schemaRevision,
|
||||
user.Data,
|
||||
user.Email,
|
||||
user.Phone,
|
||||
func(ctx context.Context) (*EncryptedCode, error) {
|
||||
return c.newEmailCode(ctx, c.eventstore.Filter, c.userEncryption) //nolint:staticcheck
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user.Email != nil {
|
||||
events, user.ReturnCodeEmail, err = c.updateSchemaUserEmail(ctx, events, userAgg, user.Email, alg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if codeEmail != "" {
|
||||
user.ReturnCodeEmail = &codeEmail
|
||||
}
|
||||
if user.Phone != nil {
|
||||
events, user.ReturnCodePhone, err = c.updateSchemaUserPhone(ctx, events, userAgg, user.Phone, alg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if codePhone != "" {
|
||||
user.ReturnCodePhone = &codePhone
|
||||
}
|
||||
|
||||
if err := c.pushAppendAndReduce(ctx, writeModel, events...); err != nil {
|
||||
return err
|
||||
}
|
||||
user.Details = writeModelToObjectDetails(&writeModel.WriteModel)
|
||||
return nil
|
||||
return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
|
||||
}
|
||||
|
||||
func (c *Commands) DeleteSchemaUser(ctx context.Context, id string) (*domain.ObjectDetails, error) {
|
||||
func (c *Commands) DeleteSchemaUser(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) {
|
||||
if id == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Vs4wJCME7T", "Errors.IDMissing")
|
||||
}
|
||||
writeModel, err := c.getSchemaUserExists(ctx, "", id)
|
||||
writeModel, err := c.getSchemaUserExists(ctx, resourceOwner, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, err := writeModel.NewDelete(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
|
||||
}
|
||||
|
||||
type ChangeSchemaUser struct {
|
||||
schemaWriteModel *UserSchemaWriteModel
|
||||
|
||||
ResourceOwner string
|
||||
ID string
|
||||
|
||||
SchemaUser *SchemaUser
|
||||
|
||||
Email *Email
|
||||
ReturnCodeEmail *string
|
||||
Phone *Phone
|
||||
ReturnCodePhone *string
|
||||
}
|
||||
|
||||
type SchemaUser struct {
|
||||
SchemaID string
|
||||
Data json.RawMessage
|
||||
}
|
||||
|
||||
func (s *ChangeSchemaUser) Valid() (err error) {
|
||||
if s.ID == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-gEJR1QOGHb", "Errors.IDMissing")
|
||||
}
|
||||
if s.Email != nil && s.Email.Address != "" {
|
||||
if err := s.Email.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.Phone != nil && s.Phone.Number != "" {
|
||||
if s.Phone.Number, err = s.Phone.Number.Normalize(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeSchemaUser(ctx context.Context, user *ChangeSchemaUser) (*domain.ObjectDetails, error) {
|
||||
if err := user.Valid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
writeModel, err := c.getSchemaUserWriteModelByID(ctx, user.ResourceOwner, user.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !writeModel.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound")
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Nn8CRVlkeZ", "Errors.User.NotFound")
|
||||
}
|
||||
if err := c.checkPermissionDeleteUser(ctx, writeModel.ResourceOwner, writeModel.AggregateID); err != nil {
|
||||
|
||||
schemaID := writeModel.SchemaID
|
||||
if user.SchemaUser != nil && user.SchemaUser.SchemaID != "" {
|
||||
schemaID = user.SchemaUser.SchemaID
|
||||
}
|
||||
|
||||
var schemaWM *UserSchemaWriteModel
|
||||
if user.SchemaUser != nil {
|
||||
schemaWriteModel, err := c.getSchemaWriteModelByID(ctx, "", schemaID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !schemaWriteModel.Exists() {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-VLDTtxT3If", "Errors.UserSchema.NotExists")
|
||||
}
|
||||
schemaWM = schemaWriteModel
|
||||
}
|
||||
|
||||
events, codeEmail, codePhone, err := writeModel.NewUpdate(ctx,
|
||||
schemaWM,
|
||||
user.SchemaUser,
|
||||
user.Email,
|
||||
user.Phone,
|
||||
func(ctx context.Context) (*EncryptedCode, error) {
|
||||
return c.newEmailCode(ctx, c.eventstore.Filter, c.userEncryption) //nolint:staticcheck
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if codeEmail != "" {
|
||||
user.ReturnCodeEmail = &codeEmail
|
||||
}
|
||||
if codePhone != "" {
|
||||
user.ReturnCodePhone = &codePhone
|
||||
}
|
||||
return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionUpdateUserState(ctx context.Context, resourceOwner, userID string) error {
|
||||
return c.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, userID)
|
||||
}
|
||||
|
||||
func (c *Commands) LockSchemaUser(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) {
|
||||
if id == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Eu8I2VAfjF", "Errors.IDMissing")
|
||||
}
|
||||
writeModel, err := c.getSchemaUserExists(ctx, resourceOwner, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !writeModel.Exists() || writeModel.Locked {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-G4LOrnjY7q", "Errors.User.NotFound")
|
||||
}
|
||||
if err := c.checkPermissionUpdateUserState(ctx, writeModel.ResourceOwner, writeModel.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.pushAppendAndReduce(ctx, writeModel,
|
||||
schemauser.NewDeletedEvent(ctx, UserV3AggregateFromWriteModel(&writeModel.WriteModel)),
|
||||
schemauser.NewLockedEvent(ctx, UserV3AggregateFromWriteModel(&writeModel.WriteModel)),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) updateSchemaUserEmail(ctx context.Context, events []eventstore.Command, agg *eventstore.Aggregate, email *Email, alg crypto.EncryptionAlgorithm) (_ []eventstore.Command, plainCode string, err error) {
|
||||
|
||||
events = append(events, schemauser.NewEmailUpdatedEvent(ctx,
|
||||
agg,
|
||||
email.Address,
|
||||
))
|
||||
if email.Verified {
|
||||
events = append(events, schemauser.NewEmailVerifiedEvent(ctx, agg))
|
||||
} else {
|
||||
cryptoCode, err := c.newEmailCode(ctx, c.eventstore.Filter, alg) //nolint:staticcheck
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if email.ReturnCode {
|
||||
plainCode = cryptoCode.Plain
|
||||
}
|
||||
events = append(events, schemauser.NewEmailCodeAddedEvent(ctx, agg,
|
||||
cryptoCode.Crypted,
|
||||
cryptoCode.Expiry,
|
||||
email.URLTemplate,
|
||||
email.ReturnCode,
|
||||
))
|
||||
func (c *Commands) UnlockSchemaUser(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) {
|
||||
if id == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-krXtYscQZh", "Errors.IDMissing")
|
||||
}
|
||||
return events, plainCode, nil
|
||||
writeModel, err := c.getSchemaUserExists(ctx, resourceOwner, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !writeModel.Exists() || !writeModel.Locked {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-gpBv46Lh9m", "Errors.User.NotFound")
|
||||
}
|
||||
if err := c.checkPermissionUpdateUserState(ctx, writeModel.ResourceOwner, writeModel.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.pushAppendAndReduce(ctx, writeModel,
|
||||
schemauser.NewUnlockedEvent(ctx, UserV3AggregateFromWriteModel(&writeModel.WriteModel)),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) updateSchemaUserPhone(ctx context.Context, events []eventstore.Command, agg *eventstore.Aggregate, phone *Phone, alg crypto.EncryptionAlgorithm) (_ []eventstore.Command, plainCode string, err error) {
|
||||
events = append(events, schemauser.NewPhoneChangedEvent(ctx,
|
||||
agg,
|
||||
phone.Number,
|
||||
))
|
||||
if phone.Verified {
|
||||
events = append(events, schemauser.NewPhoneVerifiedEvent(ctx, agg))
|
||||
} else {
|
||||
cryptoCode, err := c.newPhoneCode(ctx, c.eventstore.Filter, alg) //nolint:staticcheck
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if phone.ReturnCode {
|
||||
plainCode = cryptoCode.Plain
|
||||
}
|
||||
events = append(events, schemauser.NewPhoneCodeAddedEvent(ctx, agg,
|
||||
cryptoCode.Crypted,
|
||||
cryptoCode.Expiry,
|
||||
phone.ReturnCode,
|
||||
))
|
||||
func (c *Commands) DeactivateSchemaUser(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) {
|
||||
if id == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-pjJhge86ZV", "Errors.IDMissing")
|
||||
}
|
||||
return events, plainCode, nil
|
||||
writeModel, err := c.getSchemaUserExists(ctx, resourceOwner, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if writeModel.State != domain.UserStateActive {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-Ob6lR5iFTe", "Errors.User.NotFound")
|
||||
}
|
||||
if err := c.checkPermissionUpdateUserState(ctx, writeModel.ResourceOwner, writeModel.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.pushAppendAndReduce(ctx, writeModel,
|
||||
schemauser.NewDeactivatedEvent(ctx, UserV3AggregateFromWriteModel(&writeModel.WriteModel)),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) ActivateSchemaUser(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) {
|
||||
if id == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-17XupGvxBJ", "Errors.IDMissing")
|
||||
}
|
||||
writeModel, err := c.getSchemaUserExists(ctx, resourceOwner, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if writeModel.State != domain.UserStateInactive {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-rQjbBr4J3j", "Errors.User.NotFound")
|
||||
}
|
||||
if err := c.checkPermissionUpdateUserState(ctx, writeModel.ResourceOwner, writeModel.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.pushAppendAndReduce(ctx, writeModel,
|
||||
schemauser.NewActivatedEvent(ctx, UserV3AggregateFromWriteModel(&writeModel.WriteModel)),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) getSchemaUserExists(ctx context.Context, resourceOwner, id string) (*UserV3WriteModel, error) {
|
||||
writeModel := NewExistsUserV3WriteModel(resourceOwner, id)
|
||||
writeModel := NewExistsUserV3WriteModel(resourceOwner, id, c.checkPermission)
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) getSchemaUserWriteModelByID(ctx context.Context, resourceOwner, id string) (*UserV3WriteModel, error) {
|
||||
writeModel := NewUserV3WriteModel(resourceOwner, id, c.checkPermission)
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) getSchemaUserEmailWriteModelByID(ctx context.Context, resourceOwner, id string) (*UserV3WriteModel, error) {
|
||||
writeModel := NewUserV3EmailWriteModel(resourceOwner, id, c.checkPermission)
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) getSchemaUserPhoneWriteModelByID(ctx context.Context, resourceOwner, id string) (*UserV3WriteModel, error) {
|
||||
writeModel := NewUserV3PhoneWriteModel(resourceOwner, id, c.checkPermission)
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
115
internal/command/user_v3_email.go
Normal file
115
internal/command/user_v3_email.go
Normal file
@ -0,0 +1,115 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type ChangeSchemaUserEmail struct {
|
||||
ResourceOwner string
|
||||
ID string
|
||||
|
||||
Email *Email
|
||||
ReturnCode *string
|
||||
}
|
||||
|
||||
func (s *ChangeSchemaUserEmail) Valid() (err error) {
|
||||
if s.ID == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-0oj2PquNGA", "Errors.IDMissing")
|
||||
}
|
||||
if s.Email != nil && s.Email.Address != "" {
|
||||
if err := s.Email.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if s.Email != nil && s.Email.URLTemplate != "" {
|
||||
if err := domain.RenderConfirmURLTemplate(io.Discard, s.Email.URLTemplate, s.ID, "code", "orgID"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeSchemaUserEmail(ctx context.Context, user *ChangeSchemaUserEmail) (_ *domain.ObjectDetails, err error) {
|
||||
if err := user.Valid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
writeModel, err := c.getSchemaUserEmailWriteModelByID(ctx, user.ResourceOwner, user.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, plainCode, err := writeModel.NewEmailUpdate(ctx,
|
||||
user.Email,
|
||||
func(ctx context.Context) (*EncryptedCode, error) {
|
||||
return c.newEmailCode(ctx, c.eventstore.Filter, c.userEncryption) //nolint:staticcheck
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if plainCode != "" {
|
||||
user.ReturnCode = &plainCode
|
||||
}
|
||||
return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
|
||||
}
|
||||
|
||||
func (c *Commands) VerifySchemaUserEmail(ctx context.Context, resourceOwner, id, code string) (*domain.ObjectDetails, error) {
|
||||
if id == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-y3n4Sdu8j5", "Errors.IDMissing")
|
||||
}
|
||||
writeModel, err := c.getSchemaUserEmailWriteModelByID(ctx, resourceOwner, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, err := writeModel.NewEmailVerify(ctx,
|
||||
func(creationDate time.Time, expiry time.Duration, cryptoCode *crypto.CryptoValue) error {
|
||||
return crypto.VerifyCode(creationDate, expiry, cryptoCode, code, c.userEncryption)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
|
||||
}
|
||||
|
||||
type ResendSchemaUserEmailCode struct {
|
||||
ResourceOwner string
|
||||
ID string
|
||||
|
||||
URLTemplate string
|
||||
ReturnCode bool
|
||||
PlainCode *string
|
||||
}
|
||||
|
||||
func (c *Commands) ResendSchemaUserEmailCode(ctx context.Context, user *ResendSchemaUserEmailCode) (*domain.ObjectDetails, error) {
|
||||
if user.ID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-KvPc5o9GeJ", "Errors.IDMissing")
|
||||
}
|
||||
writeModel, err := c.getSchemaUserEmailWriteModelByID(ctx, user.ResourceOwner, user.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, plainCode, err := writeModel.NewResendEmailCode(ctx,
|
||||
func(ctx context.Context) (*EncryptedCode, error) {
|
||||
return c.newEmailCode(ctx, c.eventstore.Filter, c.userEncryption) //nolint:staticcheck
|
||||
},
|
||||
user.URLTemplate,
|
||||
user.ReturnCode,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if plainCode != "" {
|
||||
user.PlainCode = &plainCode
|
||||
}
|
||||
return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
|
||||
}
|
1076
internal/command/user_v3_email_test.go
Normal file
1076
internal/command/user_v3_email_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -4,10 +4,15 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
domain_schema "github.com/zitadel/zitadel/internal/domain/schema"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/user/schemauser"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type UserV3WriteModel struct {
|
||||
@ -23,36 +28,77 @@ type UserV3WriteModel struct {
|
||||
Email string
|
||||
IsEmailVerified bool
|
||||
EmailVerifiedFailedCount int
|
||||
EmailCode *VerifyCode
|
||||
|
||||
Phone string
|
||||
IsPhoneVerified bool
|
||||
PhoneVerifiedFailedCount int
|
||||
PhoneCode *VerifyCode
|
||||
|
||||
Data json.RawMessage
|
||||
|
||||
State domain.UserState
|
||||
Locked bool
|
||||
State domain.UserState
|
||||
|
||||
checkPermission domain.PermissionCheck
|
||||
writePermissionCheck bool
|
||||
}
|
||||
|
||||
func NewExistsUserV3WriteModel(resourceOwner, userID string) *UserV3WriteModel {
|
||||
func (wm *UserV3WriteModel) GetWriteModel() *eventstore.WriteModel {
|
||||
return &wm.WriteModel
|
||||
}
|
||||
|
||||
type VerifyCode struct {
|
||||
Code *crypto.CryptoValue
|
||||
CreationDate time.Time
|
||||
Expiry time.Duration
|
||||
}
|
||||
|
||||
func NewExistsUserV3WriteModel(resourceOwner, userID string, checkPermission domain.PermissionCheck) *UserV3WriteModel {
|
||||
return &UserV3WriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
PhoneWM: false,
|
||||
EmailWM: false,
|
||||
DataWM: false,
|
||||
PhoneWM: false,
|
||||
EmailWM: false,
|
||||
DataWM: false,
|
||||
checkPermission: checkPermission,
|
||||
}
|
||||
}
|
||||
|
||||
func NewUserV3WriteModel(resourceOwner, userID string) *UserV3WriteModel {
|
||||
func NewUserV3WriteModel(resourceOwner, userID string, checkPermission domain.PermissionCheck) *UserV3WriteModel {
|
||||
return &UserV3WriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
PhoneWM: true,
|
||||
EmailWM: true,
|
||||
DataWM: true,
|
||||
PhoneWM: true,
|
||||
EmailWM: true,
|
||||
DataWM: true,
|
||||
checkPermission: checkPermission,
|
||||
}
|
||||
}
|
||||
|
||||
func NewUserV3EmailWriteModel(resourceOwner, userID string, checkPermission domain.PermissionCheck) *UserV3WriteModel {
|
||||
return &UserV3WriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
EmailWM: true,
|
||||
checkPermission: checkPermission,
|
||||
}
|
||||
}
|
||||
|
||||
func NewUserV3PhoneWriteModel(resourceOwner, userID string, checkPermission domain.PermissionCheck) *UserV3WriteModel {
|
||||
return &UserV3WriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
PhoneWM: true,
|
||||
checkPermission: checkPermission,
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,8 +107,9 @@ func (wm *UserV3WriteModel) Reduce() error {
|
||||
switch e := event.(type) {
|
||||
case *schemauser.CreatedEvent:
|
||||
wm.SchemaID = e.SchemaID
|
||||
wm.SchemaRevision = 1
|
||||
wm.SchemaRevision = e.SchemaRevision
|
||||
wm.Data = e.Data
|
||||
wm.Locked = false
|
||||
|
||||
wm.State = domain.UserStateActive
|
||||
case *schemauser.UpdatedEvent:
|
||||
@ -79,46 +126,75 @@ func (wm *UserV3WriteModel) Reduce() error {
|
||||
wm.State = domain.UserStateDeleted
|
||||
case *schemauser.EmailUpdatedEvent:
|
||||
wm.Email = string(e.EmailAddress)
|
||||
wm.IsEmailVerified = false
|
||||
wm.EmailVerifiedFailedCount = 0
|
||||
wm.EmailCode = nil
|
||||
case *schemauser.EmailCodeAddedEvent:
|
||||
wm.IsEmailVerified = false
|
||||
wm.EmailVerifiedFailedCount = 0
|
||||
wm.EmailCode = &VerifyCode{
|
||||
Code: e.Code,
|
||||
CreationDate: e.CreationDate(),
|
||||
Expiry: e.Expiry,
|
||||
}
|
||||
case *schemauser.EmailVerifiedEvent:
|
||||
wm.IsEmailVerified = true
|
||||
wm.EmailVerifiedFailedCount = 0
|
||||
wm.EmailCode = nil
|
||||
case *schemauser.EmailVerificationFailedEvent:
|
||||
wm.EmailVerifiedFailedCount += 1
|
||||
case *schemauser.PhoneChangedEvent:
|
||||
case *schemauser.PhoneUpdatedEvent:
|
||||
wm.Phone = string(e.PhoneNumber)
|
||||
wm.IsPhoneVerified = false
|
||||
wm.PhoneVerifiedFailedCount = 0
|
||||
wm.EmailCode = nil
|
||||
case *schemauser.PhoneCodeAddedEvent:
|
||||
wm.IsPhoneVerified = false
|
||||
wm.PhoneVerifiedFailedCount = 0
|
||||
wm.PhoneCode = &VerifyCode{
|
||||
Code: e.Code,
|
||||
CreationDate: e.CreationDate(),
|
||||
Expiry: e.Expiry,
|
||||
}
|
||||
case *schemauser.PhoneVerifiedEvent:
|
||||
wm.PhoneVerifiedFailedCount = 0
|
||||
wm.IsPhoneVerified = true
|
||||
wm.PhoneCode = nil
|
||||
case *schemauser.PhoneVerificationFailedEvent:
|
||||
wm.PhoneVerifiedFailedCount += 1
|
||||
case *schemauser.LockedEvent:
|
||||
wm.Locked = true
|
||||
case *schemauser.UnlockedEvent:
|
||||
wm.Locked = false
|
||||
case *schemauser.DeactivatedEvent:
|
||||
wm.State = domain.UserStateInactive
|
||||
case *schemauser.ActivatedEvent:
|
||||
wm.State = domain.UserStateActive
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(schemauser.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(
|
||||
schemauser.CreatedType,
|
||||
schemauser.DeletedType,
|
||||
)
|
||||
builder := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent)
|
||||
if wm.ResourceOwner != "" {
|
||||
builder = builder.ResourceOwner(wm.ResourceOwner)
|
||||
}
|
||||
eventtypes := []eventstore.EventType{
|
||||
schemauser.CreatedType,
|
||||
schemauser.DeletedType,
|
||||
schemauser.ActivatedType,
|
||||
schemauser.DeactivatedType,
|
||||
schemauser.LockedType,
|
||||
schemauser.UnlockedType,
|
||||
}
|
||||
if wm.DataWM {
|
||||
query = query.EventTypes(
|
||||
eventtypes = append(eventtypes,
|
||||
schemauser.UpdatedType,
|
||||
)
|
||||
}
|
||||
if wm.EmailWM {
|
||||
query = query.EventTypes(
|
||||
eventtypes = append(eventtypes,
|
||||
schemauser.EmailUpdatedType,
|
||||
schemauser.EmailVerifiedType,
|
||||
schemauser.EmailCodeAddedType,
|
||||
@ -126,37 +202,199 @@ func (wm *UserV3WriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
)
|
||||
}
|
||||
if wm.PhoneWM {
|
||||
query = query.EventTypes(
|
||||
eventtypes = append(eventtypes,
|
||||
schemauser.PhoneUpdatedType,
|
||||
schemauser.PhoneVerifiedType,
|
||||
schemauser.PhoneCodeAddedType,
|
||||
schemauser.PhoneVerificationFailedType,
|
||||
)
|
||||
}
|
||||
return query.Builder()
|
||||
return builder.AddQuery().
|
||||
AggregateTypes(schemauser.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(eventtypes...).Builder()
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) NewUpdatedEvent(
|
||||
func (wm *UserV3WriteModel) NewCreated(
|
||||
ctx context.Context,
|
||||
agg *eventstore.Aggregate,
|
||||
schemaID *string,
|
||||
schemaRevision *uint64,
|
||||
schemaID string,
|
||||
schemaRevision uint64,
|
||||
data json.RawMessage,
|
||||
) *schemauser.UpdatedEvent {
|
||||
email *Email,
|
||||
phone *Phone,
|
||||
code func(context.Context) (*EncryptedCode, error),
|
||||
) (_ []eventstore.Command, codeEmail string, codePhone string, err error) {
|
||||
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
if wm.Exists() {
|
||||
return nil, "", "", zerrors.ThrowPreconditionFailed(nil, "COMMAND-Nn8CRVlkeZ", "Errors.User.AlreadyExists")
|
||||
}
|
||||
events := []eventstore.Command{
|
||||
schemauser.NewCreatedEvent(ctx,
|
||||
UserV3AggregateFromWriteModel(&wm.WriteModel),
|
||||
schemaID, schemaRevision, data,
|
||||
),
|
||||
}
|
||||
if email != nil {
|
||||
emailEvents, plainCodeEmail, err := wm.NewEmailCreate(ctx,
|
||||
email,
|
||||
code,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
if plainCodeEmail != "" {
|
||||
codeEmail = plainCodeEmail
|
||||
}
|
||||
events = append(events, emailEvents...)
|
||||
}
|
||||
|
||||
if phone != nil {
|
||||
phoneEvents, plainCodePhone, err := wm.NewPhoneCreate(ctx,
|
||||
phone,
|
||||
code,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
if plainCodePhone != "" {
|
||||
codePhone = plainCodePhone
|
||||
}
|
||||
events = append(events, phoneEvents...)
|
||||
}
|
||||
|
||||
return events, codeEmail, codePhone, nil
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) getSchemaRoleForWrite(ctx context.Context, resourceOwner, userID string) (domain_schema.Role, error) {
|
||||
if userID == authz.GetCtxData(ctx).UserID {
|
||||
return domain_schema.RoleSelf, nil
|
||||
}
|
||||
if err := wm.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, userID); err != nil {
|
||||
return domain_schema.RoleUnspecified, err
|
||||
}
|
||||
return domain_schema.RoleOwner, nil
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) validateData(ctx context.Context, data []byte, schemaWM *UserSchemaWriteModel) (string, uint64, error) {
|
||||
// get role for permission check in schema through extension
|
||||
role, err := wm.getSchemaRoleForWrite(ctx, wm.ResourceOwner, wm.AggregateID)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
schema, err := domain_schema.NewSchema(role, bytes.NewReader(schemaWM.Schema))
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
// if data not changed but a new schema or revision should be used
|
||||
if data == nil {
|
||||
data = wm.Data
|
||||
}
|
||||
var v interface{}
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
return "", 0, zerrors.ThrowInvalidArgument(nil, "COMMAND-7o3ZGxtXUz", "Errors.User.Invalid")
|
||||
}
|
||||
|
||||
if err := schema.Validate(v); err != nil {
|
||||
return "", 0, zerrors.ThrowPreconditionFailed(nil, "COMMAND-SlKXqLSeL6", "Errors.UserSchema.Data.Invalid")
|
||||
}
|
||||
return schemaWM.AggregateID, schemaWM.SchemaRevision, nil
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) NewUpdate(
|
||||
ctx context.Context,
|
||||
schemaWM *UserSchemaWriteModel,
|
||||
user *SchemaUser,
|
||||
email *Email,
|
||||
phone *Phone,
|
||||
code func(context.Context) (*EncryptedCode, error),
|
||||
) (_ []eventstore.Command, codeEmail string, codePhone string, err error) {
|
||||
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
if !wm.Exists() {
|
||||
return nil, "", "", zerrors.ThrowPreconditionFailed(nil, "COMMAND-Nn8CRVlkeZ", "Errors.User.NotFound")
|
||||
}
|
||||
events := make([]eventstore.Command, 0)
|
||||
if user != nil {
|
||||
schemaID, schemaRevision, err := wm.validateData(ctx, user.Data, schemaWM)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
userEvents := wm.newUpdatedEvents(ctx,
|
||||
schemaID,
|
||||
schemaRevision,
|
||||
user.Data,
|
||||
)
|
||||
events = append(events, userEvents...)
|
||||
}
|
||||
if email != nil {
|
||||
emailEvents, plainCodeEmail, err := wm.NewEmailUpdate(ctx,
|
||||
email,
|
||||
code,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
if plainCodeEmail != "" {
|
||||
codeEmail = plainCodeEmail
|
||||
}
|
||||
events = append(events, emailEvents...)
|
||||
}
|
||||
|
||||
if phone != nil {
|
||||
phoneEvents, plainCodePhone, err := wm.NewPhoneCreate(ctx,
|
||||
phone,
|
||||
code,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
if plainCodePhone != "" {
|
||||
codePhone = plainCodePhone
|
||||
}
|
||||
events = append(events, phoneEvents...)
|
||||
}
|
||||
|
||||
return events, codeEmail, codePhone, nil
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) newUpdatedEvents(
|
||||
ctx context.Context,
|
||||
schemaID string,
|
||||
schemaRevision uint64,
|
||||
data json.RawMessage,
|
||||
) []eventstore.Command {
|
||||
changes := make([]schemauser.Changes, 0)
|
||||
if schemaID != nil && wm.SchemaID != *schemaID {
|
||||
changes = append(changes, schemauser.ChangeSchemaID(wm.SchemaID, *schemaID))
|
||||
if wm.SchemaID != schemaID {
|
||||
changes = append(changes, schemauser.ChangeSchemaID(schemaID))
|
||||
}
|
||||
if schemaRevision != nil && wm.SchemaRevision != *schemaRevision {
|
||||
changes = append(changes, schemauser.ChangeSchemaRevision(wm.SchemaRevision, *schemaRevision))
|
||||
if wm.SchemaRevision != schemaRevision {
|
||||
changes = append(changes, schemauser.ChangeSchemaRevision(schemaRevision))
|
||||
}
|
||||
if !bytes.Equal(wm.Data, data) {
|
||||
if data != nil && !bytes.Equal(wm.Data, data) {
|
||||
changes = append(changes, schemauser.ChangeData(data))
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
return nil
|
||||
}
|
||||
return schemauser.NewUpdatedEvent(ctx, agg, changes)
|
||||
return []eventstore.Command{schemauser.NewUpdatedEvent(ctx, UserV3AggregateFromWriteModel(&wm.WriteModel), changes)}
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) NewDelete(
|
||||
ctx context.Context,
|
||||
) (_ []eventstore.Command, err error) {
|
||||
if !wm.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound")
|
||||
}
|
||||
if err := wm.checkPermissionDelete(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []eventstore.Command{schemauser.NewDeletedEvent(ctx, UserV3AggregateFromWriteModel(&wm.WriteModel))}, nil
|
||||
|
||||
}
|
||||
|
||||
func UserV3AggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
|
||||
@ -172,3 +410,271 @@ func UserV3AggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggreg
|
||||
func (wm *UserV3WriteModel) Exists() bool {
|
||||
return wm.State != domain.UserStateDeleted && wm.State != domain.UserStateUnspecified
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) checkPermissionWrite(
|
||||
ctx context.Context,
|
||||
resourceOwner string,
|
||||
userID string,
|
||||
) error {
|
||||
if wm.writePermissionCheck {
|
||||
return nil
|
||||
}
|
||||
if userID != "" && userID == authz.GetCtxData(ctx).UserID {
|
||||
return nil
|
||||
}
|
||||
if err := wm.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
wm.writePermissionCheck = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) checkPermissionDelete(
|
||||
ctx context.Context,
|
||||
resourceOwner string,
|
||||
userID string,
|
||||
) error {
|
||||
if userID != "" && userID == authz.GetCtxData(ctx).UserID {
|
||||
return nil
|
||||
}
|
||||
return wm.checkPermission(ctx, domain.PermissionUserDelete, resourceOwner, userID)
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) NewEmailCreate(
|
||||
ctx context.Context,
|
||||
email *Email,
|
||||
code func(context.Context) (*EncryptedCode, error),
|
||||
) (_ []eventstore.Command, plainCode string, err error) {
|
||||
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if email == nil || wm.Email == string(email.Address) {
|
||||
return nil, "", nil
|
||||
}
|
||||
events := []eventstore.Command{
|
||||
schemauser.NewEmailUpdatedEvent(ctx,
|
||||
UserV3AggregateFromWriteModel(&wm.WriteModel),
|
||||
email.Address,
|
||||
),
|
||||
}
|
||||
if email.Verified {
|
||||
events = append(events, wm.newEmailVerifiedEvent(ctx))
|
||||
} else {
|
||||
codeEvent, code, err := wm.newEmailCodeAddedEvent(ctx, code, email.URLTemplate, email.ReturnCode)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
events = append(events, codeEvent)
|
||||
if code != "" {
|
||||
plainCode = code
|
||||
}
|
||||
}
|
||||
return events, plainCode, nil
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) NewEmailUpdate(
|
||||
ctx context.Context,
|
||||
email *Email,
|
||||
code func(context.Context) (*EncryptedCode, error),
|
||||
) (_ []eventstore.Command, plainCode string, err error) {
|
||||
if !wm.EmailWM {
|
||||
return nil, "", nil
|
||||
}
|
||||
if !wm.Exists() {
|
||||
return nil, "", zerrors.ThrowNotFound(nil, "COMMAND-nJ0TQFuRmP", "Errors.User.NotFound")
|
||||
}
|
||||
return wm.NewEmailCreate(ctx, email, code)
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) NewEmailVerify(
|
||||
ctx context.Context,
|
||||
verify func(creationDate time.Time, expiry time.Duration, cryptoCode *crypto.CryptoValue) error,
|
||||
) ([]eventstore.Command, error) {
|
||||
if !wm.EmailWM {
|
||||
return nil, nil
|
||||
}
|
||||
if !wm.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-qbGyMPvjvj", "Errors.User.NotFound")
|
||||
}
|
||||
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if wm.EmailCode == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if err := verify(wm.EmailCode.CreationDate, wm.EmailCode.Expiry, wm.EmailCode.Code); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []eventstore.Command{wm.newEmailVerifiedEvent(ctx)}, nil
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) newEmailVerifiedEvent(
|
||||
ctx context.Context,
|
||||
) *schemauser.EmailVerifiedEvent {
|
||||
return schemauser.NewEmailVerifiedEvent(ctx, UserV3AggregateFromWriteModel(&wm.WriteModel))
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) NewResendEmailCode(
|
||||
ctx context.Context,
|
||||
code func(context.Context) (*EncryptedCode, error),
|
||||
urlTemplate string,
|
||||
isReturnCode bool,
|
||||
) (_ []eventstore.Command, plainCode string, err error) {
|
||||
if !wm.EmailWM {
|
||||
return nil, "", nil
|
||||
}
|
||||
if !wm.Exists() {
|
||||
return nil, "", zerrors.ThrowNotFound(nil, "COMMAND-EajeF6ypOV", "Errors.User.NotFound")
|
||||
}
|
||||
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if wm.EmailCode == nil {
|
||||
return nil, "", zerrors.ThrowPreconditionFailed(err, "COMMAND-QRkNTBwF8q", "Errors.User.Code.Empty")
|
||||
}
|
||||
event, plainCode, err := wm.newEmailCodeAddedEvent(ctx, code, urlTemplate, isReturnCode)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return []eventstore.Command{event}, plainCode, nil
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) newEmailCodeAddedEvent(
|
||||
ctx context.Context,
|
||||
code func(context.Context) (*EncryptedCode, error),
|
||||
urlTemplate string,
|
||||
isReturnCode bool,
|
||||
) (_ *schemauser.EmailCodeAddedEvent, plainCode string, err error) {
|
||||
cryptoCode, err := code(ctx)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if isReturnCode {
|
||||
plainCode = cryptoCode.Plain
|
||||
}
|
||||
return schemauser.NewEmailCodeAddedEvent(ctx,
|
||||
UserV3AggregateFromWriteModel(&wm.WriteModel),
|
||||
cryptoCode.Crypted,
|
||||
cryptoCode.Expiry,
|
||||
urlTemplate,
|
||||
isReturnCode,
|
||||
), plainCode, nil
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) NewPhoneCreate(
|
||||
ctx context.Context,
|
||||
phone *Phone,
|
||||
code func(context.Context) (*EncryptedCode, error),
|
||||
) (_ []eventstore.Command, plainCode string, err error) {
|
||||
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if phone == nil || wm.Phone == string(phone.Number) {
|
||||
return nil, "", nil
|
||||
}
|
||||
events := []eventstore.Command{
|
||||
schemauser.NewPhoneUpdatedEvent(ctx,
|
||||
UserV3AggregateFromWriteModel(&wm.WriteModel),
|
||||
phone.Number,
|
||||
),
|
||||
}
|
||||
if phone.Verified {
|
||||
events = append(events, wm.newPhoneVerifiedEvent(ctx))
|
||||
} else {
|
||||
codeEvent, code, err := wm.newPhoneCodeAddedEvent(ctx, code, phone.ReturnCode)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
events = append(events, codeEvent)
|
||||
if code != "" {
|
||||
plainCode = code
|
||||
}
|
||||
}
|
||||
return events, plainCode, nil
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) NewPhoneUpdate(
|
||||
ctx context.Context,
|
||||
phone *Phone,
|
||||
code func(context.Context) (*EncryptedCode, error),
|
||||
) (_ []eventstore.Command, plainCode string, err error) {
|
||||
if !wm.PhoneWM {
|
||||
return nil, "", nil
|
||||
}
|
||||
if !wm.Exists() {
|
||||
return nil, "", zerrors.ThrowNotFound(nil, "COMMAND-b33QAVgel6", "Errors.User.NotFound")
|
||||
}
|
||||
return wm.NewPhoneCreate(ctx, phone, code)
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) NewPhoneVerify(
|
||||
ctx context.Context,
|
||||
verify func(creationDate time.Time, expiry time.Duration, cryptoCode *crypto.CryptoValue) error,
|
||||
) ([]eventstore.Command, error) {
|
||||
if !wm.PhoneWM {
|
||||
return nil, nil
|
||||
}
|
||||
if !wm.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-bx2OLtgGNS", "Errors.User.NotFound")
|
||||
}
|
||||
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if wm.PhoneCode == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if err := verify(wm.PhoneCode.CreationDate, wm.PhoneCode.Expiry, wm.PhoneCode.Code); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []eventstore.Command{wm.newPhoneVerifiedEvent(ctx)}, nil
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) newPhoneVerifiedEvent(
|
||||
ctx context.Context,
|
||||
) *schemauser.PhoneVerifiedEvent {
|
||||
return schemauser.NewPhoneVerifiedEvent(ctx, UserV3AggregateFromWriteModel(&wm.WriteModel))
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) NewResendPhoneCode(
|
||||
ctx context.Context,
|
||||
code func(context.Context) (*EncryptedCode, error),
|
||||
isReturnCode bool,
|
||||
) (_ []eventstore.Command, plainCode string, err error) {
|
||||
if !wm.PhoneWM {
|
||||
return nil, "", nil
|
||||
}
|
||||
if !wm.Exists() {
|
||||
return nil, "", zerrors.ThrowNotFound(nil, "COMMAND-z8Bu9vuL9s", "Errors.User.NotFound")
|
||||
}
|
||||
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if wm.PhoneCode == nil {
|
||||
return nil, "", zerrors.ThrowPreconditionFailed(err, "COMMAND-fEsHdqECzb", "Errors.User.Code.Empty")
|
||||
}
|
||||
event, plainCode, err := wm.newPhoneCodeAddedEvent(ctx, code, isReturnCode)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return []eventstore.Command{event}, plainCode, nil
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) newPhoneCodeAddedEvent(
|
||||
ctx context.Context,
|
||||
code func(context.Context) (*EncryptedCode, error),
|
||||
isReturnCode bool,
|
||||
) (_ *schemauser.PhoneCodeAddedEvent, plainCode string, err error) {
|
||||
cryptoCode, err := code(ctx)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if isReturnCode {
|
||||
plainCode = cryptoCode.Plain
|
||||
}
|
||||
return schemauser.NewPhoneCodeAddedEvent(ctx,
|
||||
UserV3AggregateFromWriteModel(&wm.WriteModel),
|
||||
cryptoCode.Crypted,
|
||||
cryptoCode.Expiry,
|
||||
isReturnCode,
|
||||
), plainCode, nil
|
||||
}
|
||||
|
107
internal/command/user_v3_phone.go
Normal file
107
internal/command/user_v3_phone.go
Normal file
@ -0,0 +1,107 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type ChangeSchemaUserPhone struct {
|
||||
ResourceOwner string
|
||||
ID string
|
||||
|
||||
Phone *Phone
|
||||
ReturnCode *string
|
||||
}
|
||||
|
||||
func (s *ChangeSchemaUserPhone) Valid() (err error) {
|
||||
if s.ID == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-DkQ9aurv5u", "Errors.IDMissing")
|
||||
}
|
||||
if s.Phone != nil && s.Phone.Number != "" {
|
||||
if s.Phone.Number, err = s.Phone.Number.Normalize(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeSchemaUserPhone(ctx context.Context, user *ChangeSchemaUserPhone) (_ *domain.ObjectDetails, err error) {
|
||||
if err := user.Valid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
writeModel, err := c.getSchemaUserPhoneWriteModelByID(ctx, user.ResourceOwner, user.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, plainCode, err := writeModel.NewPhoneUpdate(ctx,
|
||||
user.Phone,
|
||||
func(ctx context.Context) (*EncryptedCode, error) {
|
||||
return c.newPhoneCode(ctx, c.eventstore.Filter, c.userEncryption) //nolint:staticcheck
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if plainCode != "" {
|
||||
user.ReturnCode = &plainCode
|
||||
}
|
||||
return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
|
||||
}
|
||||
|
||||
func (c *Commands) VerifySchemaUserPhone(ctx context.Context, resourceOwner, id, code string) (*domain.ObjectDetails, error) {
|
||||
if id == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-R4LKY44Ke3", "Errors.IDMissing")
|
||||
}
|
||||
writeModel, err := c.getSchemaUserPhoneWriteModelByID(ctx, resourceOwner, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, err := writeModel.NewPhoneVerify(ctx,
|
||||
func(creationDate time.Time, expiry time.Duration, cryptoCode *crypto.CryptoValue) error {
|
||||
return crypto.VerifyCode(creationDate, expiry, cryptoCode, code, c.userEncryption)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
|
||||
}
|
||||
|
||||
type ResendSchemaUserPhoneCode struct {
|
||||
ResourceOwner string
|
||||
ID string
|
||||
|
||||
ReturnCode bool
|
||||
PlainCode *string
|
||||
}
|
||||
|
||||
func (c *Commands) ResendSchemaUserPhoneCode(ctx context.Context, user *ResendSchemaUserPhoneCode) (*domain.ObjectDetails, error) {
|
||||
if user.ID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-zmxIFR2nMo", "Errors.IDMissing")
|
||||
}
|
||||
writeModel, err := c.getSchemaUserPhoneWriteModelByID(ctx, user.ResourceOwner, user.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, plainCode, err := writeModel.NewResendPhoneCode(ctx,
|
||||
func(ctx context.Context) (*EncryptedCode, error) {
|
||||
return c.newPhoneCode(ctx, c.eventstore.Filter, c.userEncryption) //nolint:staticcheck
|
||||
},
|
||||
user.ReturnCode,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if plainCode != "" {
|
||||
user.PlainCode = &plainCode
|
||||
}
|
||||
return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
|
||||
}
|
1040
internal/command/user_v3_phone_test.go
Normal file
1040
internal/command/user_v3_phone_test.go
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -39,9 +39,9 @@ func failureFromEvent(event eventstore.Event, err error) *failure {
|
||||
func failureFromStatement(statement *Statement, err error) *failure {
|
||||
return &failure{
|
||||
sequence: statement.Sequence,
|
||||
instance: statement.InstanceID,
|
||||
aggregateID: statement.AggregateID,
|
||||
aggregateType: statement.AggregateType,
|
||||
instance: statement.Aggregate.InstanceID,
|
||||
aggregateID: statement.Aggregate.ID,
|
||||
aggregateType: statement.Aggregate.Type,
|
||||
eventDate: statement.CreationDate,
|
||||
err: err,
|
||||
}
|
||||
|
@ -62,6 +62,7 @@ type Handler struct {
|
||||
triggeredInstancesSync sync.Map
|
||||
|
||||
triggerWithoutEvents Reduce
|
||||
cacheInvalidations []func(ctx context.Context, aggregates []*eventstore.Aggregate)
|
||||
}
|
||||
|
||||
var _ migration.Migration = (*Handler)(nil)
|
||||
@ -418,6 +419,12 @@ func (h *Handler) Trigger(ctx context.Context, opts ...TriggerOpt) (_ context.Co
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterCacheInvalidation registers a function to be called when a cache needs to be invalidated.
|
||||
// In order to avoid race conditions, this method must be called before [Handler.Start] is called.
|
||||
func (h *Handler) RegisterCacheInvalidation(invalidate func(ctx context.Context, aggregates []*eventstore.Aggregate)) {
|
||||
h.cacheInvalidations = append(h.cacheInvalidations, invalidate)
|
||||
}
|
||||
|
||||
// lockInstance tries to lock the instance.
|
||||
// If the instance is already locked from another process no cancel function is returned
|
||||
// the instance can be skipped then
|
||||
@ -486,10 +493,6 @@ func (h *Handler) processEvents(ctx context.Context, config *triggerConfig) (add
|
||||
h.log().OnError(rollbackErr).Debug("unable to rollback tx")
|
||||
return
|
||||
}
|
||||
commitErr := tx.Commit()
|
||||
if err == nil {
|
||||
err = commitErr
|
||||
}
|
||||
}()
|
||||
|
||||
currentState, err := h.currentState(ctx, tx, config)
|
||||
@ -509,6 +512,17 @@ func (h *Handler) processEvents(ctx context.Context, config *triggerConfig) (add
|
||||
if err != nil {
|
||||
return additionalIteration, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
commitErr := tx.Commit()
|
||||
if err == nil {
|
||||
err = commitErr
|
||||
}
|
||||
if err == nil && currentState.aggregateID != "" && len(statements) > 0 {
|
||||
h.invalidateCaches(ctx, aggregatesFromStatements(statements))
|
||||
}
|
||||
}()
|
||||
|
||||
if len(statements) == 0 {
|
||||
err = h.setState(tx, currentState)
|
||||
return additionalIteration, err
|
||||
@ -522,8 +536,8 @@ func (h *Handler) processEvents(ctx context.Context, config *triggerConfig) (add
|
||||
|
||||
currentState.position = statements[lastProcessedIndex].Position
|
||||
currentState.offset = statements[lastProcessedIndex].offset
|
||||
currentState.aggregateID = statements[lastProcessedIndex].AggregateID
|
||||
currentState.aggregateType = statements[lastProcessedIndex].AggregateType
|
||||
currentState.aggregateID = statements[lastProcessedIndex].Aggregate.ID
|
||||
currentState.aggregateType = statements[lastProcessedIndex].Aggregate.Type
|
||||
currentState.sequence = statements[lastProcessedIndex].Sequence
|
||||
currentState.eventTimestamp = statements[lastProcessedIndex].CreationDate
|
||||
err = h.setState(tx, currentState)
|
||||
@ -556,8 +570,8 @@ func (h *Handler) generateStatements(ctx context.Context, tx *sql.Tx, currentSta
|
||||
if idx+1 == len(statements) {
|
||||
currentState.position = statements[len(statements)-1].Position
|
||||
currentState.offset = statements[len(statements)-1].offset
|
||||
currentState.aggregateID = statements[len(statements)-1].AggregateID
|
||||
currentState.aggregateType = statements[len(statements)-1].AggregateType
|
||||
currentState.aggregateID = statements[len(statements)-1].Aggregate.ID
|
||||
currentState.aggregateType = statements[len(statements)-1].Aggregate.Type
|
||||
currentState.sequence = statements[len(statements)-1].Sequence
|
||||
currentState.eventTimestamp = statements[len(statements)-1].CreationDate
|
||||
|
||||
@ -577,8 +591,8 @@ func (h *Handler) generateStatements(ctx context.Context, tx *sql.Tx, currentSta
|
||||
func skipPreviouslyReducedStatements(statements []*Statement, currentState *state) int {
|
||||
for i, statement := range statements {
|
||||
if statement.Position == currentState.position &&
|
||||
statement.AggregateID == currentState.aggregateID &&
|
||||
statement.AggregateType == currentState.aggregateType &&
|
||||
statement.Aggregate.ID == currentState.aggregateID &&
|
||||
statement.Aggregate.Type == currentState.aggregateType &&
|
||||
statement.Sequence == currentState.sequence {
|
||||
return i
|
||||
}
|
||||
@ -667,3 +681,34 @@ func (h *Handler) eventQuery(currentState *state) *eventstore.SearchQueryBuilder
|
||||
func (h *Handler) ProjectionName() string {
|
||||
return h.projection.Name()
|
||||
}
|
||||
|
||||
func (h *Handler) invalidateCaches(ctx context.Context, aggregates []*eventstore.Aggregate) {
|
||||
if len(h.cacheInvalidations) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(h.cacheInvalidations))
|
||||
|
||||
for _, invalidate := range h.cacheInvalidations {
|
||||
go func(invalidate func(context.Context, []*eventstore.Aggregate)) {
|
||||
defer wg.Done()
|
||||
invalidate(ctx, aggregates)
|
||||
}(invalidate)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// aggregatesFromStatements returns the unique aggregates from statements.
|
||||
// Duplicate aggregates are omitted.
|
||||
func aggregatesFromStatements(statements []*Statement) []*eventstore.Aggregate {
|
||||
aggregates := make([]*eventstore.Aggregate, 0, len(statements))
|
||||
for _, statement := range statements {
|
||||
if !slices.ContainsFunc(aggregates, func(aggregate *eventstore.Aggregate) bool {
|
||||
return *statement.Aggregate == *aggregate
|
||||
}) {
|
||||
aggregates = append(aggregates, statement.Aggregate)
|
||||
}
|
||||
}
|
||||
return aggregates
|
||||
}
|
||||
|
@ -80,12 +80,10 @@ func (h *Handler) reduce(event eventstore.Event) (*Statement, error) {
|
||||
}
|
||||
|
||||
type Statement struct {
|
||||
AggregateType eventstore.AggregateType
|
||||
AggregateID string
|
||||
Sequence uint64
|
||||
Position float64
|
||||
CreationDate time.Time
|
||||
InstanceID string
|
||||
Aggregate *eventstore.Aggregate
|
||||
Sequence uint64
|
||||
Position float64
|
||||
CreationDate time.Time
|
||||
|
||||
offset uint32
|
||||
|
||||
@ -108,13 +106,11 @@ var (
|
||||
|
||||
func NewStatement(event eventstore.Event, e Exec) *Statement {
|
||||
return &Statement{
|
||||
AggregateType: event.Aggregate().Type,
|
||||
Sequence: event.Sequence(),
|
||||
Position: event.Position(),
|
||||
AggregateID: event.Aggregate().ID,
|
||||
CreationDate: event.CreatedAt(),
|
||||
InstanceID: event.Aggregate().InstanceID,
|
||||
Execute: e,
|
||||
Aggregate: event.Aggregate(),
|
||||
Sequence: event.Sequence(),
|
||||
Position: event.Position(),
|
||||
CreationDate: event.CreatedAt(),
|
||||
Execute: e,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ import (
|
||||
object_v3alpha "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2"
|
||||
oidc_pb_v2beta "github.com/zitadel/zitadel/pkg/grpc/oidc/v2beta"
|
||||
org "github.com/zitadel/zitadel/pkg/grpc/org/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/org/v2"
|
||||
org_v2beta "github.com/zitadel/zitadel/pkg/grpc/org/v2beta"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
user_v3alpha "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
@ -776,6 +776,32 @@ func (i *Instance) CreateSchemaUser(ctx context.Context, orgID string, schemaID
|
||||
return user
|
||||
}
|
||||
|
||||
func (i *Instance) UpdateSchemaUserEmail(ctx context.Context, orgID string, userID string, email string) *user_v3alpha.SetContactEmailResponse {
|
||||
user, err := i.Client.UserV3Alpha.SetContactEmail(ctx, &user_v3alpha.SetContactEmailRequest{
|
||||
Organization: &object_v3alpha.Organization{Property: &object_v3alpha.Organization_OrgId{OrgId: orgID}},
|
||||
Id: userID,
|
||||
Email: &user_v3alpha.SetEmail{
|
||||
Address: email,
|
||||
Verification: &user_v3alpha.SetEmail_ReturnCode{},
|
||||
},
|
||||
})
|
||||
logging.OnError(err).Fatal("create user")
|
||||
return user
|
||||
}
|
||||
|
||||
func (i *Instance) UpdateSchemaUserPhone(ctx context.Context, orgID string, userID string, phone string) *user_v3alpha.SetContactPhoneResponse {
|
||||
user, err := i.Client.UserV3Alpha.SetContactPhone(ctx, &user_v3alpha.SetContactPhoneRequest{
|
||||
Organization: &object_v3alpha.Organization{Property: &object_v3alpha.Organization_OrgId{OrgId: orgID}},
|
||||
Id: userID,
|
||||
Phone: &user_v3alpha.SetPhone{
|
||||
Number: phone,
|
||||
Verification: &user_v3alpha.SetPhone_ReturnCode{},
|
||||
},
|
||||
})
|
||||
logging.OnError(err).Fatal("create user")
|
||||
return user
|
||||
}
|
||||
|
||||
func (i *Instance) CreateInviteCode(ctx context.Context, userID string) *user_v2.CreateInviteCodeResponse {
|
||||
user, err := i.Client.UserV2.CreateInviteCode(ctx, &user_v2.CreateInviteCodeRequest{
|
||||
UserId: userID,
|
||||
@ -784,3 +810,55 @@ func (i *Instance) CreateInviteCode(ctx context.Context, userID string) *user_v2
|
||||
logging.OnError(err).Fatal("create invite code")
|
||||
return user
|
||||
}
|
||||
|
||||
func (i *Instance) LockSchemaUser(ctx context.Context, orgID string, userID string) *user_v3alpha.LockUserResponse {
|
||||
var org *object_v3alpha.Organization
|
||||
if orgID != "" {
|
||||
org = &object_v3alpha.Organization{Property: &object_v3alpha.Organization_OrgId{OrgId: orgID}}
|
||||
}
|
||||
user, err := i.Client.UserV3Alpha.LockUser(ctx, &user_v3alpha.LockUserRequest{
|
||||
Organization: org,
|
||||
Id: userID,
|
||||
})
|
||||
logging.OnError(err).Fatal("lock user")
|
||||
return user
|
||||
}
|
||||
|
||||
func (i *Instance) UnlockSchemaUser(ctx context.Context, orgID string, userID string) *user_v3alpha.UnlockUserResponse {
|
||||
var org *object_v3alpha.Organization
|
||||
if orgID != "" {
|
||||
org = &object_v3alpha.Organization{Property: &object_v3alpha.Organization_OrgId{OrgId: orgID}}
|
||||
}
|
||||
user, err := i.Client.UserV3Alpha.UnlockUser(ctx, &user_v3alpha.UnlockUserRequest{
|
||||
Organization: org,
|
||||
Id: userID,
|
||||
})
|
||||
logging.OnError(err).Fatal("unlock user")
|
||||
return user
|
||||
}
|
||||
|
||||
func (i *Instance) DeactivateSchemaUser(ctx context.Context, orgID string, userID string) *user_v3alpha.DeactivateUserResponse {
|
||||
var org *object_v3alpha.Organization
|
||||
if orgID != "" {
|
||||
org = &object_v3alpha.Organization{Property: &object_v3alpha.Organization_OrgId{OrgId: orgID}}
|
||||
}
|
||||
user, err := i.Client.UserV3Alpha.DeactivateUser(ctx, &user_v3alpha.DeactivateUserRequest{
|
||||
Organization: org,
|
||||
Id: userID,
|
||||
})
|
||||
logging.OnError(err).Fatal("deactivate user")
|
||||
return user
|
||||
}
|
||||
|
||||
func (i *Instance) ActivateSchemaUser(ctx context.Context, orgID string, userID string) *user_v3alpha.ActivateUserResponse {
|
||||
var org *object_v3alpha.Organization
|
||||
if orgID != "" {
|
||||
org = &object_v3alpha.Organization{Property: &object_v3alpha.Organization_OrgId{OrgId: orgID}}
|
||||
}
|
||||
user, err := i.Client.UserV3Alpha.ActivateUser(ctx, &user_v3alpha.ActivateUserRequest{
|
||||
Organization: org,
|
||||
Id: userID,
|
||||
})
|
||||
logging.OnError(err).Fatal("reactivate user")
|
||||
return user
|
||||
}
|
||||
|
@ -6,6 +6,23 @@ ExternalSecure: false
|
||||
TLS:
|
||||
Enabled: false
|
||||
|
||||
Caches:
|
||||
Connectors:
|
||||
Memory:
|
||||
Enabled: true
|
||||
AutoPrune:
|
||||
Interval: 30s
|
||||
TimeOut: 1s
|
||||
Instance:
|
||||
Connector: "memory"
|
||||
MaxAge: 1m
|
||||
LastUsage: 30s
|
||||
Log:
|
||||
Level: info
|
||||
AddSource: true
|
||||
Formatter:
|
||||
Format: text
|
||||
|
||||
Quotas:
|
||||
Access:
|
||||
Enabled: true
|
||||
@ -33,7 +50,6 @@ LogStore:
|
||||
Projections:
|
||||
HandleActiveInstances: 30m
|
||||
RequeueEvery: 5s
|
||||
TransactionDuration: 1m
|
||||
Customizations:
|
||||
NotificationsQuotas:
|
||||
RequeueEvery: 1s
|
||||
|
95
internal/query/cache.go
Normal file
95
internal/query/cache.go
Normal file
@ -0,0 +1,95 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/cache"
|
||||
"github.com/zitadel/zitadel/internal/cache/gomap"
|
||||
"github.com/zitadel/zitadel/internal/cache/noop"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
type Caches struct {
|
||||
connectors *cacheConnectors
|
||||
instance cache.Cache[instanceIndex, string, *authzInstance]
|
||||
}
|
||||
|
||||
func startCaches(background context.Context, conf *cache.CachesConfig) (_ *Caches, err error) {
|
||||
caches := &Caches{
|
||||
instance: noop.NewCache[instanceIndex, string, *authzInstance](),
|
||||
}
|
||||
if conf == nil {
|
||||
return caches, nil
|
||||
}
|
||||
caches.connectors, err = startCacheConnectors(background, conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caches.instance, err = startCache[instanceIndex, string, *authzInstance](background, instanceIndexValues(), "authz_instance", conf.Instance, caches.connectors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caches.registerInstanceInvalidation()
|
||||
|
||||
return caches, nil
|
||||
}
|
||||
|
||||
type cacheConnectors struct {
|
||||
memory *cache.AutoPruneConfig
|
||||
// pool *pgxpool.Pool
|
||||
}
|
||||
|
||||
func startCacheConnectors(_ context.Context, conf *cache.CachesConfig) (*cacheConnectors, error) {
|
||||
connectors := new(cacheConnectors)
|
||||
if conf.Connectors.Memory.Enabled {
|
||||
connectors.memory = &conf.Connectors.Memory.AutoPrune
|
||||
}
|
||||
|
||||
return connectors, nil
|
||||
}
|
||||
|
||||
func startCache[I, K comparable, V cache.Entry[I, K]](background context.Context, indices []I, name string, conf *cache.CacheConfig, connectors *cacheConnectors) (cache.Cache[I, K, V], error) {
|
||||
if conf == nil || conf.Connector == "" {
|
||||
return noop.NewCache[I, K, V](), nil
|
||||
}
|
||||
if strings.EqualFold(conf.Connector, "memory") && connectors.memory != nil {
|
||||
c := gomap.NewCache[I, K, V](background, indices, *conf)
|
||||
connectors.memory.StartAutoPrune(background, c, name)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
/* TODO
|
||||
if strings.EqualFold(conf.Connector, "sql") && connectors.pool != nil {
|
||||
return ...
|
||||
}
|
||||
*/
|
||||
|
||||
return nil, fmt.Errorf("cache connector %q not enabled", conf.Connector)
|
||||
}
|
||||
|
||||
type invalidator[I comparable] interface {
|
||||
Invalidate(ctx context.Context, index I, key ...string) error
|
||||
}
|
||||
|
||||
func cacheInvalidationFunc[I comparable](cache invalidator[I], index I, getID func(*eventstore.Aggregate) string) func(context.Context, []*eventstore.Aggregate) {
|
||||
return func(ctx context.Context, aggregates []*eventstore.Aggregate) {
|
||||
ids := make([]string, len(aggregates))
|
||||
for i, aggregate := range aggregates {
|
||||
ids[i] = getID(aggregate)
|
||||
}
|
||||
err := cache.Invalidate(ctx, index, ids...)
|
||||
logging.OnError(err).Warn("cache invalidation failed")
|
||||
}
|
||||
}
|
||||
|
||||
func getAggregateID(aggregate *eventstore.Aggregate) string {
|
||||
return aggregate.ID
|
||||
}
|
||||
|
||||
func getResourceOwner(aggregate *eventstore.Aggregate) string {
|
||||
return aggregate.ResourceOwner
|
||||
}
|
@ -7,6 +7,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -17,6 +18,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/call"
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
||||
"github.com/zitadel/zitadel/internal/feature"
|
||||
"github.com/zitadel/zitadel/internal/query/projection"
|
||||
@ -206,22 +208,35 @@ func (q *Queries) InstanceByHost(ctx context.Context, instanceHost, publicHost s
|
||||
|
||||
instanceDomain := strings.Split(instanceHost, ":")[0] // remove possible port
|
||||
publicDomain := strings.Split(publicHost, ":")[0] // remove possible port
|
||||
instance, scan := scanAuthzInstance()
|
||||
// in case public domain is the same as the instance domain, we do not need to check it
|
||||
// and can empty it for the check
|
||||
if instanceDomain == publicDomain {
|
||||
publicDomain = ""
|
||||
|
||||
instance, ok := q.caches.instance.Get(ctx, instanceIndexByHost, instanceDomain)
|
||||
if ok {
|
||||
return instance, instance.checkDomain(instanceDomain, publicDomain)
|
||||
}
|
||||
err = q.client.QueryRowContext(ctx, scan, instanceByDomainQuery, instanceDomain, publicDomain)
|
||||
return instance, err
|
||||
instance, scan := scanAuthzInstance()
|
||||
if err = q.client.QueryRowContext(ctx, scan, instanceByDomainQuery, instanceDomain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.caches.instance.Set(ctx, instance)
|
||||
|
||||
return instance, instance.checkDomain(instanceDomain, publicDomain)
|
||||
}
|
||||
|
||||
func (q *Queries) InstanceByID(ctx context.Context, id string) (_ authz.Instance, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
instance, ok := q.caches.instance.Get(ctx, instanceIndexByID, id)
|
||||
if ok {
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
instance, scan := scanAuthzInstance()
|
||||
err = q.client.QueryRowContext(ctx, scan, instanceByIDQuery, id)
|
||||
logging.OnError(err).WithField("instance_id", id).Warn("instance by ID")
|
||||
if err == nil {
|
||||
q.caches.instance.Set(ctx, instance)
|
||||
}
|
||||
return instance, err
|
||||
}
|
||||
|
||||
@ -431,6 +446,8 @@ type authzInstance struct {
|
||||
block *bool
|
||||
auditLogRetention *time.Duration
|
||||
features feature.Features
|
||||
externalDomains database.TextArray[string]
|
||||
trustedDomains database.TextArray[string]
|
||||
}
|
||||
|
||||
type csp struct {
|
||||
@ -485,6 +502,31 @@ func (i *authzInstance) Features() feature.Features {
|
||||
return i.features
|
||||
}
|
||||
|
||||
var errPublicDomain = "public domain %q not trusted"
|
||||
|
||||
func (i *authzInstance) checkDomain(instanceDomain, publicDomain string) error {
|
||||
// in case public domain is empty, or the same as the instance domain, we do not need to check it
|
||||
if publicDomain == "" || instanceDomain == publicDomain {
|
||||
return nil
|
||||
}
|
||||
if !slices.Contains(i.trustedDomains, publicDomain) {
|
||||
return zerrors.ThrowNotFound(fmt.Errorf(errPublicDomain, publicDomain), "QUERY-IuGh1", "Errors.IAM.NotFound")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Keys implements [cache.Entry]
|
||||
func (i *authzInstance) Keys(index instanceIndex) []string {
|
||||
switch index {
|
||||
case instanceIndexByID:
|
||||
return []string{i.id}
|
||||
case instanceIndexByHost:
|
||||
return i.externalDomains
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func scanAuthzInstance() (*authzInstance, func(row *sql.Row) error) {
|
||||
instance := &authzInstance{}
|
||||
return instance, func(row *sql.Row) error {
|
||||
@ -509,6 +551,8 @@ func scanAuthzInstance() (*authzInstance, func(row *sql.Row) error) {
|
||||
&auditLogRetention,
|
||||
&block,
|
||||
&features,
|
||||
&instance.externalDomains,
|
||||
&instance.trustedDomains,
|
||||
)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return zerrors.ThrowNotFound(nil, "QUERY-1kIjX", "Errors.IAM.NotFound")
|
||||
@ -534,3 +578,30 @@ func scanAuthzInstance() (*authzInstance, func(row *sql.Row) error) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Caches) registerInstanceInvalidation() {
|
||||
invalidate := cacheInvalidationFunc(c.instance, instanceIndexByID, getAggregateID)
|
||||
projection.InstanceProjection.RegisterCacheInvalidation(invalidate)
|
||||
projection.InstanceDomainProjection.RegisterCacheInvalidation(invalidate)
|
||||
projection.InstanceFeatureProjection.RegisterCacheInvalidation(invalidate)
|
||||
projection.InstanceTrustedDomainProjection.RegisterCacheInvalidation(invalidate)
|
||||
projection.SecurityPolicyProjection.RegisterCacheInvalidation(invalidate)
|
||||
|
||||
// limits uses own aggregate ID, invalidate using resource owner.
|
||||
invalidate = cacheInvalidationFunc(c.instance, instanceIndexByID, getResourceOwner)
|
||||
projection.LimitsProjection.RegisterCacheInvalidation(invalidate)
|
||||
|
||||
// System feature update should invalidate all instances, so Truncate the cache.
|
||||
projection.SystemFeatureProjection.RegisterCacheInvalidation(func(ctx context.Context, _ []*eventstore.Aggregate) {
|
||||
err := c.instance.Truncate(ctx)
|
||||
logging.OnError(err).Warn("cache truncate failed")
|
||||
})
|
||||
}
|
||||
|
||||
type instanceIndex int16
|
||||
|
||||
//go:generate enumer -type instanceIndex
|
||||
const (
|
||||
instanceIndexByID instanceIndex = iota
|
||||
instanceIndexByHost
|
||||
)
|
||||
|
@ -14,6 +14,16 @@ with domain as (
|
||||
cross join projections.system_features s
|
||||
full outer join instance_features i using (instance_id, key)
|
||||
group by instance_id
|
||||
), external_domains as (
|
||||
select ed.instance_id, array_agg(ed.domain) as domains
|
||||
from domain d
|
||||
join projections.instance_domains ed on d.instance_id = ed.instance_id
|
||||
group by ed.instance_id
|
||||
), trusted_domains as (
|
||||
select td.instance_id, array_agg(td.domain) as domains
|
||||
from domain d
|
||||
join projections.instance_trusted_domains td on d.instance_id = td.instance_id
|
||||
group by td.instance_id
|
||||
)
|
||||
select
|
||||
i.id,
|
||||
@ -27,11 +37,13 @@ select
|
||||
s.enable_impersonation,
|
||||
l.audit_log_retention,
|
||||
l.block,
|
||||
f.features
|
||||
f.features,
|
||||
ed.domains as external_domains,
|
||||
td.domains as trusted_domains
|
||||
from domain d
|
||||
join projections.instances i on i.id = d.instance_id
|
||||
left join projections.instance_trusted_domains td on i.id = td.instance_id
|
||||
left join projections.security_policies2 s on i.id = s.instance_id
|
||||
left join projections.limits l on i.id = l.instance_id
|
||||
left join features f on i.id = f.instance_id
|
||||
where case when $2 = '' then true else td.domain = $2 end;
|
||||
left join external_domains ed on i.id = ed.instance_id
|
||||
left join trusted_domains td on i.id = td.instance_id;
|
||||
|
@ -7,6 +7,16 @@ with features as (
|
||||
cross join projections.system_features s
|
||||
full outer join projections.instance_features2 i using (key, instance_id)
|
||||
group by instance_id
|
||||
), external_domains as (
|
||||
select instance_id, array_agg(domain) as domains
|
||||
from projections.instance_domains
|
||||
where instance_id = $1
|
||||
group by instance_id
|
||||
), trusted_domains as (
|
||||
select instance_id, array_agg(domain) as domains
|
||||
from projections.instance_trusted_domains
|
||||
where instance_id = $1
|
||||
group by instance_id
|
||||
)
|
||||
select
|
||||
i.id,
|
||||
@ -20,9 +30,13 @@ select
|
||||
s.enable_impersonation,
|
||||
l.audit_log_retention,
|
||||
l.block,
|
||||
f.features
|
||||
f.features,
|
||||
ed.domains as external_domains,
|
||||
td.domains as trusted_domains
|
||||
from projections.instances i
|
||||
left join projections.security_policies2 s on i.id = s.instance_id
|
||||
left join projections.limits l on i.id = l.instance_id
|
||||
left join features f on i.id = f.instance_id
|
||||
left join external_domains ed on i.id = ed.instance_id
|
||||
left join trusted_domains td on i.id = td.instance_id
|
||||
where i.id = $1;
|
||||
|
78
internal/query/instanceindex_enumer.go
Normal file
78
internal/query/instanceindex_enumer.go
Normal file
@ -0,0 +1,78 @@
|
||||
// Code generated by "enumer -type instanceIndex"; DO NOT EDIT.
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const _instanceIndexName = "instanceIndexByIDinstanceIndexByHost"
|
||||
|
||||
var _instanceIndexIndex = [...]uint8{0, 17, 36}
|
||||
|
||||
const _instanceIndexLowerName = "instanceindexbyidinstanceindexbyhost"
|
||||
|
||||
func (i instanceIndex) String() string {
|
||||
if i < 0 || i >= instanceIndex(len(_instanceIndexIndex)-1) {
|
||||
return fmt.Sprintf("instanceIndex(%d)", i)
|
||||
}
|
||||
return _instanceIndexName[_instanceIndexIndex[i]:_instanceIndexIndex[i+1]]
|
||||
}
|
||||
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
func _instanceIndexNoOp() {
|
||||
var x [1]struct{}
|
||||
_ = x[instanceIndexByID-(0)]
|
||||
_ = x[instanceIndexByHost-(1)]
|
||||
}
|
||||
|
||||
var _instanceIndexValues = []instanceIndex{instanceIndexByID, instanceIndexByHost}
|
||||
|
||||
var _instanceIndexNameToValueMap = map[string]instanceIndex{
|
||||
_instanceIndexName[0:17]: instanceIndexByID,
|
||||
_instanceIndexLowerName[0:17]: instanceIndexByID,
|
||||
_instanceIndexName[17:36]: instanceIndexByHost,
|
||||
_instanceIndexLowerName[17:36]: instanceIndexByHost,
|
||||
}
|
||||
|
||||
var _instanceIndexNames = []string{
|
||||
_instanceIndexName[0:17],
|
||||
_instanceIndexName[17:36],
|
||||
}
|
||||
|
||||
// instanceIndexString retrieves an enum value from the enum constants string name.
|
||||
// Throws an error if the param is not part of the enum.
|
||||
func instanceIndexString(s string) (instanceIndex, error) {
|
||||
if val, ok := _instanceIndexNameToValueMap[s]; ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
if val, ok := _instanceIndexNameToValueMap[strings.ToLower(s)]; ok {
|
||||
return val, nil
|
||||
}
|
||||
return 0, fmt.Errorf("%s does not belong to instanceIndex values", s)
|
||||
}
|
||||
|
||||
// instanceIndexValues returns all values of the enum
|
||||
func instanceIndexValues() []instanceIndex {
|
||||
return _instanceIndexValues
|
||||
}
|
||||
|
||||
// instanceIndexStrings returns a slice of all String values of the enum
|
||||
func instanceIndexStrings() []string {
|
||||
strs := make([]string, len(_instanceIndexNames))
|
||||
copy(strs, _instanceIndexNames)
|
||||
return strs
|
||||
}
|
||||
|
||||
// IsAinstanceIndex returns "true" if the value is listed in the enum definition. "false" otherwise
|
||||
func (i instanceIndex) IsAinstanceIndex() bool {
|
||||
for _, v := range _instanceIndexValues {
|
||||
if i == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -74,8 +74,8 @@ func assertReduce(t *testing.T, stmt *handler.Statement, err error, projection s
|
||||
if want.err != nil && want.err(err) {
|
||||
return
|
||||
}
|
||||
if stmt.AggregateType != want.aggregateType {
|
||||
t.Errorf("wrong aggregate type: want: %q got: %q", want.aggregateType, stmt.AggregateType)
|
||||
if stmt.Aggregate.Type != want.aggregateType {
|
||||
t.Errorf("wrong aggregate type: want: %q got: %q", want.aggregateType, stmt.Aggregate.Type)
|
||||
}
|
||||
|
||||
if stmt.Sequence != want.sequence {
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/cache"
|
||||
sd "github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
@ -26,6 +27,7 @@ type Queries struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventStoreV4 es_v4.Querier
|
||||
client *database.DB
|
||||
caches *Caches
|
||||
|
||||
keyEncryptionAlgorithm crypto.EncryptionAlgorithm
|
||||
idpConfigEncryption crypto.EncryptionAlgorithm
|
||||
@ -47,6 +49,7 @@ func StartQueries(
|
||||
es *eventstore.Eventstore,
|
||||
esV4 es_v4.Querier,
|
||||
querySqlClient, projectionSqlClient *database.DB,
|
||||
caches *cache.CachesConfig,
|
||||
projections projection.Config,
|
||||
defaults sd.SystemDefaults,
|
||||
idpConfigEncryption, otpEncryption, keyEncryptionAlgorithm, certEncryptionAlgorithm crypto.EncryptionAlgorithm,
|
||||
@ -86,6 +89,10 @@ func StartQueries(
|
||||
if startProjections {
|
||||
projection.Start(ctx)
|
||||
}
|
||||
repo.caches, err = startCaches(ctx, caches)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repo, nil
|
||||
}
|
||||
|
@ -5,8 +5,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
AggregateType = "user"
|
||||
AggregateVersion = "v3"
|
||||
AggregateType = "schemauser"
|
||||
AggregateVersion = "v1"
|
||||
)
|
||||
|
||||
type Aggregate struct {
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -21,11 +20,15 @@ const (
|
||||
)
|
||||
|
||||
type EmailUpdatedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
|
||||
EmailAddress domain.EmailAddress `json:"email,omitempty"`
|
||||
}
|
||||
|
||||
func (e *EmailUpdatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func (e *EmailUpdatedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
@ -36,7 +39,7 @@ func (e *EmailUpdatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
|
||||
func NewEmailUpdatedEvent(ctx context.Context, aggregate *eventstore.Aggregate, emailAddress domain.EmailAddress) *EmailUpdatedEvent {
|
||||
return &EmailUpdatedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
EmailUpdatedType,
|
||||
@ -45,24 +48,16 @@ func NewEmailUpdatedEvent(ctx context.Context, aggregate *eventstore.Aggregate,
|
||||
}
|
||||
}
|
||||
|
||||
func EmailUpdatedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||
emailChangedEvent := &EmailUpdatedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
err := event.Unmarshal(emailChangedEvent)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "USER-4M0sd", "unable to unmarshal human password changed")
|
||||
}
|
||||
|
||||
return emailChangedEvent, nil
|
||||
}
|
||||
|
||||
type EmailVerifiedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
|
||||
IsEmailVerified bool `json:"-"`
|
||||
}
|
||||
|
||||
func (e *EmailVerifiedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func (e *EmailVerifiedEvent) Payload() interface{} {
|
||||
return nil
|
||||
}
|
||||
@ -73,7 +68,7 @@ func (e *EmailVerifiedEvent) UniqueConstraints() []*eventstore.UniqueConstraint
|
||||
|
||||
func NewEmailVerifiedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *EmailVerifiedEvent {
|
||||
return &EmailVerifiedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
EmailVerifiedType,
|
||||
@ -81,18 +76,13 @@ func NewEmailVerifiedEvent(ctx context.Context, aggregate *eventstore.Aggregate)
|
||||
}
|
||||
}
|
||||
|
||||
func HumanVerifiedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||
emailVerified := &EmailVerifiedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
IsEmailVerified: true,
|
||||
}
|
||||
return emailVerified, nil
|
||||
}
|
||||
|
||||
type EmailVerificationFailedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func (e *EmailVerificationFailedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
func (e *EmailVerificationFailedEvent) Payload() interface{} {
|
||||
return nil
|
||||
}
|
||||
@ -101,9 +91,9 @@ func (e *EmailVerificationFailedEvent) UniqueConstraints() []*eventstore.UniqueC
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewHumanEmailVerificationFailedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *EmailVerificationFailedEvent {
|
||||
func NewEmailVerificationFailedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *EmailVerificationFailedEvent {
|
||||
return &EmailVerificationFailedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
EmailVerificationFailedType,
|
||||
@ -111,14 +101,8 @@ func NewHumanEmailVerificationFailedEvent(ctx context.Context, aggregate *events
|
||||
}
|
||||
}
|
||||
|
||||
func EmailVerificationFailedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||
return &EmailVerificationFailedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type EmailCodeAddedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
|
||||
Code *crypto.CryptoValue `json:"code,omitempty"`
|
||||
Expiry time.Duration `json:"expiry,omitempty"`
|
||||
@ -127,6 +111,10 @@ type EmailCodeAddedEvent struct {
|
||||
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
|
||||
}
|
||||
|
||||
func (e *EmailCodeAddedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func (e *EmailCodeAddedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
@ -148,7 +136,7 @@ func NewEmailCodeAddedEvent(
|
||||
codeReturned bool,
|
||||
) *EmailCodeAddedEvent {
|
||||
return &EmailCodeAddedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
EmailCodeAddedType,
|
||||
@ -161,22 +149,13 @@ func NewEmailCodeAddedEvent(
|
||||
}
|
||||
}
|
||||
|
||||
func EmailCodeAddedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||
codeAdded := &EmailCodeAddedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
err := event.Unmarshal(codeAdded)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "USER-3M0sd", "unable to unmarshal human email code added")
|
||||
}
|
||||
|
||||
return codeAdded, nil
|
||||
}
|
||||
|
||||
type EmailCodeSentEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func (e *EmailCodeSentEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
func (e *EmailCodeSentEvent) Payload() interface{} {
|
||||
return nil
|
||||
}
|
||||
@ -185,18 +164,12 @@ func (e *EmailCodeSentEvent) UniqueConstraints() []*eventstore.UniqueConstraint
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewHumanEmailCodeSentEvent(ctx context.Context, aggregate *eventstore.Aggregate) *EmailCodeSentEvent {
|
||||
func NewEmailCodeSentEvent(ctx context.Context, aggregate *eventstore.Aggregate) *EmailCodeSentEvent {
|
||||
return &EmailCodeSentEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
EmailCodeSentType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func EmailCodeSentEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||
return &EmailCodeSentEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}, nil
|
||||
}
|
||||
|
@ -6,4 +6,18 @@ func init() {
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, CreatedType, eventstore.GenericEventMapper[CreatedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, UpdatedType, eventstore.GenericEventMapper[UpdatedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, DeletedType, eventstore.GenericEventMapper[DeletedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, LockedType, eventstore.GenericEventMapper[LockedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, UnlockedType, eventstore.GenericEventMapper[UnlockedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, ActivatedType, eventstore.GenericEventMapper[ActivatedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, DeactivatedType, eventstore.GenericEventMapper[DeactivatedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, EmailUpdatedType, eventstore.GenericEventMapper[EmailUpdatedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, EmailCodeAddedType, eventstore.GenericEventMapper[EmailCodeAddedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, EmailCodeSentType, eventstore.GenericEventMapper[EmailCodeSentEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, EmailVerifiedType, eventstore.GenericEventMapper[EmailVerifiedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, EmailVerificationFailedType, eventstore.GenericEventMapper[EmailVerificationFailedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, PhoneUpdatedType, eventstore.GenericEventMapper[PhoneUpdatedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, PhoneCodeAddedType, eventstore.GenericEventMapper[PhoneCodeAddedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, PhoneCodeSentType, eventstore.GenericEventMapper[PhoneCodeSentEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, PhoneVerifiedType, eventstore.GenericEventMapper[PhoneVerifiedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, PhoneVerificationFailedType, eventstore.GenericEventMapper[PhoneVerificationFailedEvent])
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -20,23 +19,27 @@ const (
|
||||
PhoneCodeSentType = phoneEventPrefix + "code.sent"
|
||||
)
|
||||
|
||||
type PhoneChangedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
type PhoneUpdatedEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
|
||||
PhoneNumber domain.PhoneNumber `json:"phone,omitempty"`
|
||||
}
|
||||
|
||||
func (e *PhoneChangedEvent) Payload() interface{} {
|
||||
func (e *PhoneUpdatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func (e *PhoneUpdatedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *PhoneChangedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
func (e *PhoneUpdatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPhoneChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, phone domain.PhoneNumber) *PhoneChangedEvent {
|
||||
return &PhoneChangedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
func NewPhoneUpdatedEvent(ctx context.Context, aggregate *eventstore.Aggregate, phone domain.PhoneNumber) *PhoneUpdatedEvent {
|
||||
return &PhoneUpdatedEvent{
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
PhoneUpdatedType,
|
||||
@ -45,24 +48,15 @@ func NewPhoneChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate,
|
||||
}
|
||||
}
|
||||
|
||||
func PhoneChangedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||
phoneChangedEvent := &PhoneChangedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
err := event.Unmarshal(phoneChangedEvent)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "USER-5M0pd", "unable to unmarshal phone changed")
|
||||
}
|
||||
|
||||
return phoneChangedEvent, nil
|
||||
}
|
||||
|
||||
type PhoneVerifiedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
|
||||
IsPhoneVerified bool `json:"-"`
|
||||
}
|
||||
|
||||
func (e *PhoneVerifiedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
func (e *PhoneVerifiedEvent) Payload() interface{} {
|
||||
return nil
|
||||
}
|
||||
@ -73,7 +67,7 @@ func (e *PhoneVerifiedEvent) UniqueConstraints() []*eventstore.UniqueConstraint
|
||||
|
||||
func NewPhoneVerifiedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *PhoneVerifiedEvent {
|
||||
return &PhoneVerifiedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
PhoneVerifiedType,
|
||||
@ -81,15 +75,12 @@ func NewPhoneVerifiedEvent(ctx context.Context, aggregate *eventstore.Aggregate)
|
||||
}
|
||||
}
|
||||
|
||||
func PhoneVerifiedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||
return &PhoneVerifiedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
IsPhoneVerified: true,
|
||||
}, nil
|
||||
type PhoneVerificationFailedEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
type PhoneVerificationFailedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
func (e *PhoneVerificationFailedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func (e *PhoneVerificationFailedEvent) Payload() interface{} {
|
||||
@ -102,7 +93,7 @@ func (e *PhoneVerificationFailedEvent) UniqueConstraints() []*eventstore.UniqueC
|
||||
|
||||
func NewPhoneVerificationFailedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *PhoneVerificationFailedEvent {
|
||||
return &PhoneVerificationFailedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
PhoneVerificationFailedType,
|
||||
@ -110,14 +101,8 @@ func NewPhoneVerificationFailedEvent(ctx context.Context, aggregate *eventstore.
|
||||
}
|
||||
}
|
||||
|
||||
func PhoneVerificationFailedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||
return &PhoneVerificationFailedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type PhoneCodeAddedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
|
||||
Code *crypto.CryptoValue `json:"code,omitempty"`
|
||||
Expiry time.Duration `json:"expiry,omitempty"`
|
||||
@ -137,6 +122,10 @@ func (e *PhoneCodeAddedEvent) TriggerOrigin() string {
|
||||
return e.TriggeredAtOrigin
|
||||
}
|
||||
|
||||
func (e *PhoneCodeAddedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func NewPhoneCodeAddedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
@ -145,7 +134,7 @@ func NewPhoneCodeAddedEvent(
|
||||
codeReturned bool,
|
||||
) *PhoneCodeAddedEvent {
|
||||
return &PhoneCodeAddedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
PhoneCodeAddedType,
|
||||
@ -157,20 +146,8 @@ func NewPhoneCodeAddedEvent(
|
||||
}
|
||||
}
|
||||
|
||||
func PhoneCodeAddedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||
codeAdded := &PhoneCodeAddedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
err := event.Unmarshal(codeAdded)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "USER-6Ms9d", "unable to unmarshal phone code added")
|
||||
}
|
||||
|
||||
return codeAdded, nil
|
||||
}
|
||||
|
||||
type PhoneCodeSentEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func (e *PhoneCodeSentEvent) Payload() interface{} {
|
||||
@ -181,18 +158,16 @@ func (e *PhoneCodeSentEvent) UniqueConstraints() []*eventstore.UniqueConstraint
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *PhoneCodeSentEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func NewPhoneCodeSentEvent(ctx context.Context, aggregate *eventstore.Aggregate) *PhoneCodeSentEvent {
|
||||
return &PhoneCodeSentEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
PhoneCodeSentType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func PhoneCodeSentEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||
return &PhoneCodeSentEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}, nil
|
||||
}
|
||||
|
@ -8,10 +8,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
eventPrefix = "user."
|
||||
CreatedType = eventPrefix + "created"
|
||||
UpdatedType = eventPrefix + "updated"
|
||||
DeletedType = eventPrefix + "deleted"
|
||||
eventPrefix = "schemauser."
|
||||
CreatedType = eventPrefix + "created"
|
||||
UpdatedType = eventPrefix + "updated"
|
||||
DeletedType = eventPrefix + "deleted"
|
||||
LockedType = eventPrefix + "locked"
|
||||
UnlockedType = eventPrefix + "unlocked"
|
||||
DeactivatedType = eventPrefix + "deactivated"
|
||||
ActivatedType = eventPrefix + "activated"
|
||||
)
|
||||
|
||||
type CreatedEvent struct {
|
||||
@ -60,8 +64,6 @@ type UpdatedEvent struct {
|
||||
SchemaID *string `json:"schemaID,omitempty"`
|
||||
SchemaRevision *uint64 `json:"schemaRevision,omitempty"`
|
||||
Data json.RawMessage `json:"schema,omitempty"`
|
||||
oldSchemaID string
|
||||
oldRevision uint64
|
||||
}
|
||||
|
||||
func (e *UpdatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
@ -95,16 +97,14 @@ func NewUpdatedEvent(
|
||||
|
||||
type Changes func(event *UpdatedEvent)
|
||||
|
||||
func ChangeSchemaID(oldSchemaID, schemaID string) func(event *UpdatedEvent) {
|
||||
func ChangeSchemaID(schemaID string) func(event *UpdatedEvent) {
|
||||
return func(e *UpdatedEvent) {
|
||||
e.SchemaID = &schemaID
|
||||
e.oldSchemaID = oldSchemaID
|
||||
}
|
||||
}
|
||||
func ChangeSchemaRevision(oldSchemaRevision, schemaRevision uint64) func(event *UpdatedEvent) {
|
||||
func ChangeSchemaRevision(schemaRevision uint64) func(event *UpdatedEvent) {
|
||||
return func(e *UpdatedEvent) {
|
||||
e.SchemaRevision = &schemaRevision
|
||||
e.oldRevision = oldSchemaRevision
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,3 +142,119 @@ func NewDeletedEvent(
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
type LockedEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func (e *LockedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func (e *LockedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *LockedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewLockedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
) *LockedEvent {
|
||||
return &LockedEvent{
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
LockedType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
type UnlockedEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func (e *UnlockedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func (e *UnlockedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *UnlockedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewUnlockedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
) *UnlockedEvent {
|
||||
return &UnlockedEvent{
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
UnlockedType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
type DeactivatedEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func (e *DeactivatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func (e *DeactivatedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *DeactivatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewDeactivatedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
) *DeactivatedEvent {
|
||||
return &DeactivatedEvent{
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
DeactivatedType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
type ActivatedEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func (e *ActivatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func (e *ActivatedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *ActivatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewActivatedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
) *ActivatedEvent {
|
||||
return &ActivatedEvent{
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
ActivatedType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ service ZITADELUsers {
|
||||
// Returns the user identified by the requested ID.
|
||||
rpc GetUser (GetUserRequest) returns (GetUserResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/resources/v3alpha/users/{user_id}"
|
||||
get: "/resources/v3alpha/users/{id}"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
@ -208,7 +208,7 @@ service ZITADELUsers {
|
||||
// Patch an existing user with data based on a user schema.
|
||||
rpc PatchUser (PatchUserRequest) returns (PatchUserResponse) {
|
||||
option (google.api.http) = {
|
||||
patch: "/resources/v3alpha/users/{user_id}"
|
||||
patch: "/resources/v3alpha/users/{id}"
|
||||
body: "user"
|
||||
};
|
||||
|
||||
@ -238,7 +238,7 @@ service ZITADELUsers {
|
||||
// The endpoint returns an error if the user is already in the state 'deactivated'.
|
||||
rpc DeactivateUser (DeactivateUserRequest) returns (DeactivateUserResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/_deactivate"
|
||||
post: "/resources/v3alpha/users/{id}/_deactivate"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
@ -257,15 +257,15 @@ service ZITADELUsers {
|
||||
};
|
||||
}
|
||||
|
||||
// Reactivate a user
|
||||
// Activate a user
|
||||
//
|
||||
// Reactivate a previously deactivated user and change the state to 'active'.
|
||||
// Activate a previously deactivated user and change the state to 'active'.
|
||||
// The user will be able to log in again.
|
||||
//
|
||||
// The endpoint returns an error if the user is not in the state 'deactivated'.
|
||||
rpc ReactivateUser (ReactivateUserRequest) returns (ReactivateUserResponse) {
|
||||
rpc ActivateUser (ActivateUserRequest) returns (ActivateUserResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/_reactivate"
|
||||
post: "/resources/v3alpha/users/{id}/_activate"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
@ -278,7 +278,7 @@ service ZITADELUsers {
|
||||
responses: {
|
||||
key: "200";
|
||||
value: {
|
||||
description: "User successfully reactivated";
|
||||
description: "User successfully activated";
|
||||
};
|
||||
};
|
||||
};
|
||||
@ -294,7 +294,7 @@ service ZITADELUsers {
|
||||
// The endpoint returns an error if the user is already in the state 'locked'.
|
||||
rpc LockUser (LockUserRequest) returns (LockUserResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/_lock"
|
||||
post: "/resources/v3alpha/users/{id}/_lock"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
@ -321,7 +321,7 @@ service ZITADELUsers {
|
||||
// The endpoint returns an error if the user is not in the state 'locked'.
|
||||
rpc UnlockUser (UnlockUserRequest) returns (UnlockUserResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/_unlock"
|
||||
post: "/resources/v3alpha/users/{id}/_unlock"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
@ -346,7 +346,7 @@ service ZITADELUsers {
|
||||
// The user will be able to log in anymore.
|
||||
rpc DeleteUser (DeleteUserRequest) returns (DeleteUserResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/resources/v3alpha/users/{user_id}"
|
||||
delete: "/resources/v3alpha/users/{id}"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
@ -372,7 +372,7 @@ service ZITADELUsers {
|
||||
// which can be either returned or will be sent to the user by email.
|
||||
rpc SetContactEmail (SetContactEmailRequest) returns (SetContactEmailResponse) {
|
||||
option (google.api.http) = {
|
||||
put: "/resources/v3alpha/users/{user_id}/email"
|
||||
put: "/resources/v3alpha/users/{id}/email"
|
||||
body: "email"
|
||||
};
|
||||
|
||||
@ -397,7 +397,7 @@ service ZITADELUsers {
|
||||
// Verify the contact email with the provided code.
|
||||
rpc VerifyContactEmail (VerifyContactEmailRequest) returns (VerifyContactEmailResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/email/_verify"
|
||||
post: "/resources/v3alpha/users/{id}/email/_verify"
|
||||
body: "verification_code"
|
||||
};
|
||||
|
||||
@ -422,7 +422,7 @@ service ZITADELUsers {
|
||||
// Resend the email with the verification code for the contact email address.
|
||||
rpc ResendContactEmailCode (ResendContactEmailCodeRequest) returns (ResendContactEmailCodeResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/email/_resend"
|
||||
post: "/resources/v3alpha/users/{id}/email/_resend"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
@ -449,7 +449,7 @@ service ZITADELUsers {
|
||||
// which can be either returned or will be sent to the user by SMS.
|
||||
rpc SetContactPhone (SetContactPhoneRequest) returns (SetContactPhoneResponse) {
|
||||
option (google.api.http) = {
|
||||
put: "/resources/v3alpha/users/{user_id}/phone"
|
||||
put: "/resources/v3alpha/users/{id}/phone"
|
||||
body: "phone"
|
||||
};
|
||||
|
||||
@ -474,7 +474,7 @@ service ZITADELUsers {
|
||||
// Verify the contact phone with the provided code.
|
||||
rpc VerifyContactPhone (VerifyContactPhoneRequest) returns (VerifyContactPhoneResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/phone/_verify"
|
||||
post: "/resources/v3alpha/users/{id}/phone/_verify"
|
||||
body: "verification_code"
|
||||
};
|
||||
|
||||
@ -499,7 +499,7 @@ service ZITADELUsers {
|
||||
// Resend the phone with the verification code for the contact phone number.
|
||||
rpc ResendContactPhoneCode (ResendContactPhoneCodeRequest) returns (ResendContactPhoneCodeResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/phone/_resend"
|
||||
post: "/resources/v3alpha/users/{id}/phone/_resend"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
@ -524,7 +524,7 @@ service ZITADELUsers {
|
||||
// Add a new unique username to a user. The username will be used to identify the user on authentication.
|
||||
rpc AddUsername (AddUsernameRequest) returns (AddUsernameResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/username"
|
||||
post: "/resources/v3alpha/users/{id}/username"
|
||||
body: "username"
|
||||
};
|
||||
|
||||
@ -549,7 +549,7 @@ service ZITADELUsers {
|
||||
// Remove an existing username of a user, so it cannot be used for authentication anymore.
|
||||
rpc RemoveUsername (RemoveUsernameRequest) returns (RemoveUsernameResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/resources/v3alpha/users/{user_id}/username/{username_id}"
|
||||
delete: "/resources/v3alpha/users/{id}/username/{username_id}"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
@ -573,7 +573,7 @@ service ZITADELUsers {
|
||||
// Add, update or reset a user's password with either a verification code or the current password.
|
||||
rpc SetPassword (SetPasswordRequest) returns (SetPasswordResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/password"
|
||||
post: "/resources/v3alpha/users/{id}/password"
|
||||
body: "new_password"
|
||||
};
|
||||
|
||||
@ -598,7 +598,7 @@ service ZITADELUsers {
|
||||
// Request a code to be able to set a new password.
|
||||
rpc RequestPasswordReset (RequestPasswordResetRequest) returns (RequestPasswordResetResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/password/_reset"
|
||||
post: "/resources/v3alpha/users/{id}/password/_reset"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
@ -625,7 +625,7 @@ service ZITADELUsers {
|
||||
// which are used to verify the device.
|
||||
rpc StartWebAuthNRegistration (StartWebAuthNRegistrationRequest) returns (StartWebAuthNRegistrationResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/webauthn"
|
||||
post: "/resources/v3alpha/users/{id}/webauthn"
|
||||
body: "registration"
|
||||
};
|
||||
|
||||
@ -650,7 +650,7 @@ service ZITADELUsers {
|
||||
// Verify the WebAuthN registration started by StartWebAuthNRegistration with the public key credential.
|
||||
rpc VerifyWebAuthNRegistration (VerifyWebAuthNRegistrationRequest) returns (VerifyWebAuthNRegistrationResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/webauthn/{web_auth_n_id}/_verify"
|
||||
post: "/resources/v3alpha/users/{id}/webauthn/{web_auth_n_id}/_verify"
|
||||
body: "verify"
|
||||
};
|
||||
|
||||
@ -675,7 +675,7 @@ service ZITADELUsers {
|
||||
// The code will allow the user to start a new WebAuthN registration.
|
||||
rpc CreateWebAuthNRegistrationLink (CreateWebAuthNRegistrationLinkRequest) returns (CreateWebAuthNRegistrationLinkResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/webauthn/registration_link"
|
||||
post: "/resources/v3alpha/users/{id}/webauthn/registration_link"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
@ -699,7 +699,7 @@ service ZITADELUsers {
|
||||
// Remove an existing WebAuthN authenticator from a user, so it cannot be used for authentication anymore.
|
||||
rpc RemoveWebAuthNAuthenticator (RemoveWebAuthNAuthenticatorRequest) returns (RemoveWebAuthNAuthenticatorResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/resources/v3alpha/users/{user_id}/webauthn/{web_auth_n_id}"
|
||||
delete: "/resources/v3alpha/users/{id}/webauthn/{web_auth_n_id}"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
@ -723,7 +723,7 @@ service ZITADELUsers {
|
||||
// As a response a secret is returned, which is used to initialize a TOTP app or device.
|
||||
rpc StartTOTPRegistration (StartTOTPRegistrationRequest) returns (StartTOTPRegistrationResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/totp"
|
||||
post: "/resources/v3alpha/users/{id}/totp"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
@ -746,7 +746,7 @@ service ZITADELUsers {
|
||||
// Verify the time-based one-time-password (TOTP) registration with the generated code.
|
||||
rpc VerifyTOTPRegistration (VerifyTOTPRegistrationRequest) returns (VerifyTOTPRegistrationResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/totp/{totp_id}/_verify"
|
||||
post: "/resources/v3alpha/users/{id}/totp/{totp_id}/_verify"
|
||||
body: "code"
|
||||
};
|
||||
|
||||
@ -770,7 +770,7 @@ service ZITADELUsers {
|
||||
// Remove an existing time-based one-time-password (TOTP) authenticator from a user, so it cannot be used for authentication anymore.
|
||||
rpc RemoveTOTPAuthenticator (RemoveTOTPAuthenticatorRequest) returns (RemoveTOTPAuthenticatorResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/resources/v3alpha/users/{user_id}/totp/{totp_id}"
|
||||
delete: "/resources/v3alpha/users/{id}/totp/{totp_id}"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
@ -795,7 +795,7 @@ service ZITADELUsers {
|
||||
// which can be either returned or will be sent to the user by SMS.
|
||||
rpc AddOTPSMSAuthenticator (AddOTPSMSAuthenticatorRequest) returns (AddOTPSMSAuthenticatorResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/otp_sms"
|
||||
post: "/resources/v3alpha/users/{id}/otp_sms"
|
||||
body: "phone"
|
||||
};
|
||||
|
||||
@ -819,7 +819,7 @@ service ZITADELUsers {
|
||||
// Verify the OTP SMS registration with the provided code.
|
||||
rpc VerifyOTPSMSRegistration (VerifyOTPSMSRegistrationRequest) returns (VerifyOTPSMSRegistrationResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/otp_sms/{otp_sms_id}/_verify"
|
||||
post: "/resources/v3alpha/users/{id}/otp_sms/{otp_sms_id}/_verify"
|
||||
body: "code"
|
||||
};
|
||||
|
||||
@ -844,7 +844,7 @@ service ZITADELUsers {
|
||||
// Remove an existing one-time-password (OTP) SMS authenticator from a user, so it cannot be used for authentication anymore.
|
||||
rpc RemoveOTPSMSAuthenticator (RemoveOTPSMSAuthenticatorRequest) returns (RemoveOTPSMSAuthenticatorResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/resources/v3alpha/users/{user_id}/otp_sms/{otp_sms_id}"
|
||||
delete: "/resources/v3alpha/users/{id}/otp_sms/{otp_sms_id}"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
@ -869,7 +869,7 @@ service ZITADELUsers {
|
||||
// which can be either returned or will be sent to the user by email.
|
||||
rpc AddOTPEmailAuthenticator (AddOTPEmailAuthenticatorRequest) returns (AddOTPEmailAuthenticatorResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/otp_email"
|
||||
post: "/resources/v3alpha/users/{id}/otp_email"
|
||||
body: "email"
|
||||
};
|
||||
|
||||
@ -893,7 +893,7 @@ service ZITADELUsers {
|
||||
// Verify the OTP Email registration with the provided code.
|
||||
rpc VerifyOTPEmailRegistration (VerifyOTPEmailRegistrationRequest) returns (VerifyOTPEmailRegistrationResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/otp_email/{otp_email_id}/_verify"
|
||||
post: "/resources/v3alpha/users/{id}/otp_email/{otp_email_id}/_verify"
|
||||
body: "code"
|
||||
};
|
||||
|
||||
@ -918,7 +918,7 @@ service ZITADELUsers {
|
||||
// Remove an existing one-time-password (OTP) Email authenticator from a user, so it cannot be used for authentication anymore.
|
||||
rpc RemoveOTPEmailAuthenticator (RemoveOTPEmailAuthenticatorRequest) returns (RemoveOTPEmailAuthenticatorResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/resources/v3alpha/users/{user_id}/otp_email/{otp_email_id}"
|
||||
delete: "/resources/v3alpha/users/{id}/otp_email/{otp_email_id}"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
@ -991,7 +991,7 @@ service ZITADELUsers {
|
||||
// This will allow the user to authenticate with the provided IDP.
|
||||
rpc AddIDPAuthenticator (AddIDPAuthenticatorRequest) returns (AddIDPAuthenticatorResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{user_id}/idps"
|
||||
post: "/resources/v3alpha/users/{id}/idps"
|
||||
body: "authenticator"
|
||||
};
|
||||
|
||||
@ -1016,7 +1016,7 @@ service ZITADELUsers {
|
||||
// Remove an existing identity provider (IDP) authenticator from a user, so it cannot be used for authentication anymore.
|
||||
rpc RemoveIDPAuthenticator (RemoveIDPAuthenticatorRequest) returns (RemoveIDPAuthenticatorResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/resources/v3alpha/users/{user_id}/idps/{idp_id}"
|
||||
delete: "/resources/v3alpha/users/{id}/idps/{idp_id}"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
@ -1069,7 +1069,7 @@ message GetUserRequest {
|
||||
}
|
||||
];
|
||||
// unique identifier of the user.
|
||||
string user_id = 2 [
|
||||
string id = 2 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1123,7 +1123,7 @@ message PatchUserRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"69629012906488334\"";
|
||||
}
|
||||
@ -1156,7 +1156,7 @@ message DeactivateUserRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1172,7 +1172,7 @@ message DeactivateUserResponse {
|
||||
}
|
||||
|
||||
|
||||
message ReactivateUserRequest {
|
||||
message ActivateUserRequest {
|
||||
optional zitadel.object.v3alpha.Instance instance = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
default: "\"domain from HOST or :authority header\""
|
||||
@ -1181,7 +1181,7 @@ message ReactivateUserRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1192,7 +1192,7 @@ message ReactivateUserRequest {
|
||||
];
|
||||
}
|
||||
|
||||
message ReactivateUserResponse {
|
||||
message ActivateUserResponse {
|
||||
zitadel.resources.object.v3alpha.Details details = 1;
|
||||
}
|
||||
|
||||
@ -1205,7 +1205,7 @@ message LockUserRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1229,7 +1229,7 @@ message UnlockUserRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1253,7 +1253,7 @@ message DeleteUserRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1277,7 +1277,7 @@ message SetContactEmailRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1309,7 +1309,7 @@ message VerifyContactEmailRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1343,7 +1343,7 @@ message ResendContactEmailCodeRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1376,7 +1376,7 @@ message SetContactPhoneRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1392,7 +1392,7 @@ message SetContactPhoneRequest {
|
||||
message SetContactPhoneResponse {
|
||||
zitadel.resources.object.v3alpha.Details details = 1;
|
||||
// The phone verification code will be set if a contact phone was set with a return_code verification option.
|
||||
optional string email_code = 3 [
|
||||
optional string verification_code = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"SKJd342k\"";
|
||||
}
|
||||
@ -1408,7 +1408,7 @@ message VerifyContactPhoneRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1442,7 +1442,7 @@ message ResendContactPhoneCodeRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1475,7 +1475,7 @@ message AddUsernameRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1507,7 +1507,7 @@ message RemoveUsernameRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1541,7 +1541,7 @@ message SetPasswordRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1567,7 +1567,7 @@ message RequestPasswordResetRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1606,7 +1606,7 @@ message StartWebAuthNRegistrationRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1645,7 +1645,7 @@ message VerifyWebAuthNRegistrationRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1680,7 +1680,7 @@ message CreateWebAuthNRegistrationLinkRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1713,7 +1713,7 @@ message RemoveWebAuthNAuthenticatorRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1747,7 +1747,7 @@ message StartTOTPRegistrationRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1789,7 +1789,7 @@ message VerifyTOTPRegistrationRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1833,7 +1833,7 @@ message RemoveTOTPAuthenticatorRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1867,7 +1867,7 @@ message AddOTPSMSAuthenticatorRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1906,7 +1906,7 @@ message VerifyOTPSMSRegistrationRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1950,7 +1950,7 @@ message RemoveOTPSMSAuthenticatorRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -1984,7 +1984,7 @@ message AddOTPEmailAuthenticatorRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -2022,7 +2022,7 @@ message VerifyOTPEmailRegistrationRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -2066,7 +2066,7 @@ message RemoveOTPEmailAuthenticatorRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -2170,7 +2170,7 @@ message GetIdentityProviderIntentResponse {
|
||||
// and detailed / profile information.
|
||||
IDPInformation idp_information = 2;
|
||||
// If the user was already federated and linked to a ZITADEL user, it's id will be returned.
|
||||
optional string user_id = 3 [
|
||||
optional string id = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"163840776835432345\"";
|
||||
}
|
||||
@ -2186,7 +2186,7 @@ message AddIDPAuthenticatorRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -2211,7 +2211,7 @@ message RemoveIDPAuthenticatorRequest {
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string user_id = 3 [
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
|
@ -385,6 +385,7 @@ service UserService {
|
||||
rpc UpdateHumanUser(UpdateHumanUserRequest) returns (UpdateHumanUserResponse) {
|
||||
option (google.api.http) = {
|
||||
put: "/v2/users/human/{user_id}"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user