diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ac7d909589..181ec838fe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -77,7 +77,7 @@ jobs: go_version: "1.22" node_version: "18" buf_version: "latest" - go_lint_version: "v1.55.2" + go_lint_version: "v1.62.2" core_cache_key: ${{ needs.core.outputs.cache_key }} core_cache_path: ${{ needs.core.outputs.cache_path }} diff --git a/.github/workflows/release-channels.yml b/.github/workflows/release-channels.yml deleted file mode 100644 index bc5a184bf2..0000000000 --- a/.github/workflows/release-channels.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: ZITADEL Release tags - -on: - push: - branches: - - "main" - paths: - - 'release-channels.yaml' - workflow_dispatch: - -permissions: - contents: write - packages: write - -jobs: - Build: - runs-on: ubuntu-20.04 - env: - DOCKER_BUILDKIT: 1 - steps: - - name: Source checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: get stable tag - run: echo STABLE_RELEASE=$(yq eval '.stable' release-channels.yaml) >> $GITHUB_ENV - - name: checkout stable tag - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ env.STABLE_RELEASE }} - - name: GitHub Container Registry Login - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Google Artifact Registry Login - uses: docker/login-action@v3 - with: - registry: europe-docker.pkg.dev - username: _json_key_base64 - password: ${{ secrets.GCR_JSON_KEY_BASE64 }} - - name: copy release to stable - run: | - skopeo --version - skopeo copy --all docker://ghcr.io/zitadel/zitadel:$STABLE_RELEASE docker://ghcr.io/zitadel/zitadel:stable diff --git a/ADOPTERS.md b/ADOPTERS.md new file mode 100644 index 0000000000..bc7e8d69f5 --- /dev/null +++ b/ADOPTERS.md @@ -0,0 +1,22 @@ +## Adopters + +Sharing experiences and learning from other users is essential. We are frequently asked who is using a particular feature of Zitadel so people can get in touch with other users to share experiences and best practices. People also often want to know if a specific product or platform has integrated Zitadel. While the Zitadel Discord Community allows users to get in touch, it can be challenging to find this information quickly. + +The following is a directory of adopters to help identify users of individual features. The users themselves directly maintain the list. + +### Adding yourself as a user + +If you are using Zitadel, please consider adding yourself as a user with a quick description of your use case by opening a pull request to this file and adding a section describing your usage of Zitadel. + +| Organization/Individual | Contact Information | Description of Usage | +| ----------------------- | -------------------------------------------------------------------- | ----------------------------------------------- | +| Zitadel | [@fforootd](https://github.com/fforootd) (and many more) | Zitadel Cloud makes heavy use of of Zitadel ;-) | +| Rawkode Academy | [@RawkodeAcademy](https://github.com/RawkodeAcademy) | Rawkode Academy Platform & Zulip use Zitadel for all user and M2M authentication | +| devOS: Sanity Edition | [@devOS-Sanity-Edition](https://github.com/devOS-Sanity-Edition) | Uses SSO Auth for every piece of our internal and external infrastructure | +| CNAP.tech | [@cnap-tech](https://github.com/cnap-tech) | Using Zitadel for authentication and authorization in cloud-native applications | +| Minekube | [@minekube](https://github.com/minekube) | Leveraging Zitadel for secure user authentication in gaming infrastructure | +| OpenAIP | [@openaip](https://github.com/openAIP) | Using Zitadel Cloud for everything related to user authentication. | +| Smat.io | [@smatio](https://github.com/smatio) - [@lukasver](https://github.com/lukasver) | Zitadel for authentication in cloud applications while offering B2B portfolio management solutions for professional investors | +| Organization Name | contact@example.com | Description of how they use Zitadel | +| Individual Name | contact@example.com | Description of how they use Zitadel | + diff --git a/MEETING_SCHEDULE.md b/MEETING_SCHEDULE.md index 695a19855f..40e528d489 100644 --- a/MEETING_SCHEDULE.md +++ b/MEETING_SCHEDULE.md @@ -3,6 +3,60 @@ Dear community! We're excited to announce bi-weekly office hours. +## #7 Feature demo - Back-channel Logout + +Our dev team has been hard at work developing new features for you to explore. What's been cooking? ๐Ÿง‘๐Ÿพโ€๐Ÿณ Back-channel logout! We're inviting to you join our swiss army knife dev Livio on Wednesday, December 11th, 2024 at 11:00 AM (EST) as he walks you through back-channel logout on the ZITADEL platform & answers your questions! + +๐Ÿฆ’ **What to expect** + +A demo session - You'll get the chance to learn more about an upcoming feature through a comprehensive walkthrough led by Livio. +Brief Q&A - Post-demo, there will be space to share your questions & feedback on the back-channel logout feature. + +๐Ÿ—’๏ธ **Details** + +Topic: Feature demo - Back-channel logout +Date & time: Wednesday, December 11th, 2024 at 11:00 AM (EST) +Duration: ~1 hour +Platform: ZITADELโ€™s Discord stage channel + +**Register for this event here** โžก๏ธ https://discord.gg/bnuAe2RX?event=1308899625041924127 + +๐Ÿ—“๏ธ **Add this to your calendar** โžก๏ธ [Google Calendar](https://calendar.google.com/calendar/u/0/r/eventedit?dates=20241211T110000/20241211T110000&details=Our+dev+team+has+been+hard+at+work+developing+new+features+for+you+to+explore.+What%27s+been+cooking?+%F0%9F%A7%91%F0%9F%8F%BD%E2%80%8D%F0%9F%8D%B3+Back-channel+logout!+We%27re+inviting+to+you+join+our+swiss+army+knife+dev+Livio+on+Wednesday,+December+11,+2024+at+11:00+AM+as+he+walks+you+through+implementing+back-channel+logout+on+the+ZITADEL+platform+%26+answers+your+questions!&location=Discord:+ZITADEL+server,+office+hours&text=Feature+Demo+-+Back-Channel+Logout) + +If you have any questions prior to the live session, be sure to share them in the (office hours stage chat)[https://discord.com/channels/927474939156643850/1243281463554605058] + +Looking forward to seeing you there! Share this with other ZITADEL users & people who might be interested in ZITADEL! Itโ€™s appreciated ๐Ÿซถ + + +## #6 Q&A + +Hey folks! + +Weโ€™re inviting you to our next open office hours session! C: From leveraging ZITADEL actions to exploring your use cases, join our hosts Silvan & Stefan on Wednesday, November 20, 2024 at 11:00 AM (EST) as they answer your questions about ZITADEL! + +๐Ÿฆ’ **What to expect** + +An open Q&A session - Share your questions and support others with their inquiries. +A space to share your thoughts / feedback on the ZITADEL platform + +๐Ÿ—’๏ธ **Details** + +Target audience: All ZITADEL platform users & community members +Topic: Q&A Session +Date & time: Wednesday, November 20, 2024 at 11:00 AM (EST) +Duration: ~1 hour +Platform: ZITADELโ€™s Discord stage channel + +Register for this event here โžก๏ธ https://discord.gg/bnuAe2RX?event=1307010383713927230 + +๐Ÿ—“๏ธ **Add this to your calendar** โžก๏ธ [Google Calendar](https://calendar.google.com/calendar/u/0/r/eventedit?dates=20241120T110000/20241120T110000&details=We%E2%80%99re+inviting+you+to+our+next+open+office+hours+session!+C:+From+leveraging+ZITADEL+actions+to+exploring+your+use+cases,+join+our+hosts+Silvan+%26+Stefan+as+they+answer+your+questions+about+ZITADEL!+%0A%0A**What+to+expect**%0A%0A-+An+open+Q%26A+session+-+Share+your+questions+and+support+others+with+their+inquiries.+%0A-+A+space+to+share+your+thoughts+/+feedback+on+the+ZITADEL+platform+++%0A%0A**Details**+%0A%0A**Target+audience:**+All+ZITADEL+platform+users+%26+community+members%0A**Topic**:+Q%26A+Session+%0A**Date+%26+time**:+Wednesday,+November+20,+2024+11:00+AM%0A**Duration**:+~1+hour+%0A**Platform**:+ZITADEL%E2%80%99s+Discord+stage+channel&location=Discord:+ZITADEL+server,+office+hours&text=Open+Office+Hours) + + +If you have any questions prior to the live session, be sure to share them in the office hours stage chat + +Looking forward to seeing you there! Share this with other ZITADEL users & people who might be interested in ZITADEL! Itโ€™s appreciated ๐Ÿซถ + + ## #5 Q&A Dear community, diff --git a/README.md b/README.md index 27c66dbf81..f2f4d9a772 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,6 @@ GitHub Workflow Status (with event) - - Dynamic YAML Badge @@ -89,6 +87,10 @@ Available data regions are: ZITADEL Cloud comes with a free tier, providing you with all the same features as the open-source version. Learn more about the [pay-as-you-go pricing](https://zitadel.com/pricing). +## Adopters + +We are grateful to the organizations and individuals who are using ZITADEL. If you are using ZITADEL, please consider adding your name to our [Adopters list](./ADOPTERS.md) by submitting a pull request. + ### Example applications Clone one of our [example applications](https://zitadel.com/docs/sdk-examples/introduction) or deploy them directly to Vercel. diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index 7e5a153ea8..08973cee64 100644 --- a/cmd/defaults.yaml +++ b/cmd/defaults.yaml @@ -68,7 +68,7 @@ Port: 8080 # ZITADEL_PORT # It can differ from Port e.g. if a reverse proxy forwards the traffic to ZITADEL # Read more about external access: https://zitadel.com/docs/self-hosting/manage/custom-domain ExternalPort: 8080 # ZITADEL_EXTERNALPORT -# ExternalPort is the domain on which end users access ZITADEL. +# ExternalDomain is the domain on which end users access ZITADEL. # Read more about external access: https://zitadel.com/docs/self-hosting/manage/custom-domain ExternalDomain: localhost # ZITADEL_EXTERNALDOMAIN # ExternalSecure specifies if ZITADEL is exposed externally using HTTPS or HTTP. @@ -291,6 +291,19 @@ Caches: DisableIndentity: false # Add suffix to client name. Default is empty. IdentitySuffix: "" + # Implementation of [Circuit Breaker Pattern](https://learn.microsoft.com/en-us/previous-versions/msp-n-p/dn589784(v=pandp.10)?redirectedfrom=MSDN) + CircuitBreaker: + # Interval when the counters are reset to 0. + # 0 interval never resets the counters until the CB is opened. + Interval: 0 + # Amount of consecutive failures permitted + MaxConsecutiveFailures: 5 + # The ratio of failed requests out of total requests + MaxFailureRatio: 0.1 + # Timeout after opening of the CB, until the state is set to half-open. + Timeout: 60s + # The allowed amount of requests that are allowed to pass when the CB is half-open. + MaxRetryRequests: 1 # Instance caches auth middleware instances, gettable by domain or ID. Instance: @@ -315,6 +328,16 @@ Caches: AddSource: true Formatter: Format: text + # Organization cache, gettable by primary domain or ID. + Organization: + Connector: "" + MaxAge: 1h + LastUsage: 10m + Log: + Level: error + AddSource: true + Formatter: + Format: text Machine: # Cloud-hosted VMs need to specify their metadata endpoint so that the machine can be uniquely identified. @@ -377,6 +400,9 @@ Projections: # from HandleActiveInstances duration in the past until the projection's current time # If set to 0 (default), every instance is always considered active HandleActiveInstances: 0s # ZITADEL_PROJECTIONS_HANDLEACTIVEINSTANCES + # Maximum amount of instances cached as active + # If set to 0, every instance is always considered active + MaxActiveInstances: 0 # ZITADEL_PROJECTIONS_MAXACTIVEINSTANCES # In the Customizations section, all settings from above can be overwritten for each specific projection Customizations: custom_texts: @@ -400,11 +426,6 @@ Projections: TransactionDuration: 2s # ZITADEL_PROJECTIONS_CUSTOMIZATIONS_LOCKOUT_POLICY_TRANSACTIONDURATION # The NotificationsQuotas projection is used for calling quota webhooks NotificationsQuotas: - # In case of failed deliveries, ZITADEL retries to send the data points to the configured endpoints, but only for active instances. - # An instance is active, as long as there are projected events on the instance, that are not older than the HandleActiveInstances duration. - # Delivery guarantee requirements are higher for quota webhooks - # If set to 0 (default), every instance is always considered active - HandleActiveInstances: 0s # ZITADEL_PROJECTIONS_CUSTOMIZATIONS_NOTIFICATIONSQUOTAS_HANDLEACTIVEINSTANCES # As quota notification projections don't result in database statements, retries don't have an effect MaxFailureCount: 10 # ZITADEL_PROJECTIONS_CUSTOMIZATIONS_NOTIFICATIONSQUOTAS_MAXFAILURECOUNT # Quota notifications are not so time critical. Setting RequeueEvery every five minutes doesn't annoy the db too much. @@ -415,16 +436,50 @@ Projections: BulkLimit: 50 # The Telemetry projection is used for calling telemetry webhooks Telemetry: - # In case of failed deliveries, ZITADEL retries to send the data points to the configured endpoints, but only for active instances. - # An instance is active, as long as there are projected events on the instance, that are not older than the HandleActiveInstances duration. - # Telemetry delivery guarantee requirements are a bit higher than normal data projections, as they are not interactively retryable. - # If set to 0 (default), every instance is always considered active - HandleActiveInstances: 0s # ZITADEL_PROJECTIONS_CUSTOMIZATIONS_TELEMETRY_HANDLEACTIVEINSTANCES # As sending telemetry data doesn't result in database statements, retries don't have any effects MaxFailureCount: 0 # ZITADEL_PROJECTIONS_CUSTOMIZATIONS_TELEMETRY_MAXFAILURECOUNT # Telemetry data synchronization is not time critical. Setting RequeueEvery to 55 minutes doesn't annoy the database too much. RequeueEvery: 3300s # ZITADEL_PROJECTIONS_CUSTOMIZATIONS_TELEMETRY_REQUEUEEVERY +Notifications: + # Notifications can be processed by either a sequential mode (legacy) or a new parallel mode. + # The parallel mode is currently only recommended for Postgres databases. + # For CockroachDB, the sequential mode is recommended, see: https://github.com/zitadel/zitadel/issues/9002 + # If legacy mode is enabled, the worker config below is ignored. + LegacyEnabled: true # ZITADEL_NOTIFICATIONS_LEGACYENABLED + # The amount of workers processing the notification request events. + # If set to 0, no notification request events will be handled. This can be useful when running in + # multi binary / pod setup and allowing only certain executables to process the events. + Workers: 1 # ZITADEL_NOTIFIACATIONS_WORKERS + # The amount of events a single worker will process in a run. + BulkLimit: 10 # ZITADEL_NOTIFIACATIONS_BULKLIMIT + # Time interval between scheduled notifications for request events + RequeueEvery: 5s # ZITADEL_NOTIFIACATIONS_REQUEUEEVERY + # The amount of workers processing the notification retry events. + # If set to 0, no notification retry events will be handled. This can be useful when running in + # multi binary / pod setup and allowing only certain executables to process the events. + RetryWorkers: 1 # ZITADEL_NOTIFIACATIONS_RETRYWORKERS + # Time interval between scheduled notifications for retry events + RetryRequeueEvery: 5s # ZITADEL_NOTIFIACATIONS_RETRYREQUEUEEVERY + # Only instances are projected, for which at least a projection-relevant event exists within the timeframe + # from HandleActiveInstances duration in the past until the projection's current time + # If set to 0 (default), every instance is always considered active + HandleActiveInstances: 0s # ZITADEL_NOTIFIACATIONS_HANDLEACTIVEINSTANCES + # The maximum duration a transaction remains open + # before it spots left folding additional events + # and updates the table. + TransactionDuration: 10s # ZITADEL_NOTIFIACATIONS_TRANSACTIONDURATION + # Automatically cancel the notification after the amount of failed attempts + MaxAttempts: 3 # ZITADEL_NOTIFIACATIONS_MAXATTEMPTS + # Automatically cancel the notification if it cannot be handled within a specific time + MaxTtl: 5m # ZITADEL_NOTIFIACATIONS_MAXTTL + # Failed attempts are retried after a confogired delay (with exponential backoff). + # Set a minimum and maximum delay and a factor for the backoff + MinRetryDelay: 5s # ZITADEL_NOTIFIACATIONS_MINRETRYDELAY + MaxRetryDelay: 1m # ZITADEL_NOTIFIACATIONS_MAXRETRYDELAY + # Any factor below 1 will be set to 1 + RetryDelayFactor: 1.5 # ZITADEL_NOTIFIACATIONS_RETRYDELAYFACTOR + Auth: # See Projections.BulkLimit SearchLimit: 1000 # ZITADEL_AUTH_SEARCHLIMIT @@ -435,10 +490,6 @@ Auth: BulkLimit: 100 #ZITADEL_AUTH_SPOOLER_BULKLIMIT # See Projections.MaxFailureCount FailureCountUntilSkip: 5 #ZITADEL_AUTH_SPOOLER_FAILURECOUNTUNTILSKIP - # Only instance are projected, for which at least a projection relevant event exists withing the timeframe - # from HandleActiveInstances duration in the past until the projections current time - # If set to 0 (default), every instance is always considered active - HandleActiveInstances: 0s #ZITADEL_AUTH_SPOOLER_HANDLEACTIVEINSTANCES # Defines the amount of auth requests stored in the LRU caches. # There are two caches implemented one for id and one for code AmountOfCachedAuthRequests: 0 #ZITADEL_AUTH_AMOUNTOFCACHEDAUTHREQUESTS @@ -453,10 +504,6 @@ Admin: BulkLimit: 200 # See Projections.MaxFailureCount FailureCountUntilSkip: 5 - # Only instance are projected, for which at least a projection relevant event exists withing the timeframe - # from HandleActiveInstances duration in the past until the projections current time - # If set to 0 (default), every instance is always considered active - HandleActiveInstances: 0s UserAgentCookie: Name: zitadel.useragent # ZITADEL_USERAGENTCOOKIE_NAME @@ -576,6 +623,9 @@ EncryptionKeys: User: EncryptionKeyID: "userKey" # ZITADEL_ENCRYPTIONKEYS_USER_ENCRYPTIONKEYID DecryptionKeyIDs: # ZITADEL_ENCRYPTIONKEYS_USER_DECRYPTIONKEYIDS (comma separated list) + Target: + EncryptionKeyID: "targetKey" # ZITADEL_ENCRYPTIONKEYS_TARGET_ENCRYPTIONKEYID + DecryptionKeyIDs: # ZITADEL_ENCRYPTIONKEYS_TARGET_DECRYPTIONKEYIDS (comma separated list) CSRFCookieKeyID: "csrfCookieKey" # ZITADEL_ENCRYPTIONKEYS_CSRFCOOKIEKEYID UserAgentCookieKeyID: "userAgentCookieKey" # ZITADEL_ENCRYPTIONKEYS_USERAGENTCOOKIEKEYID @@ -853,6 +903,12 @@ DefaultInstance: IncludeUpperLetters: true # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_INITIALIZEUSERCODE_INCLUDEUPPERLETTERS IncludeDigits: true # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_INITIALIZEUSERCODE_INCLUDEDIGITS IncludeSymbols: false # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_INITIALIZEUSERCODE_INCLUDESYMBOLS + SigningKey: + Length: 36 # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_SIGNINGKEY_LENGTH + IncludeLowerLetters: true # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_SIGNINGKEY_INCLUDELOWERLETTERS + IncludeUpperLetters: true # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_SIGNINGKEY_INCLUDEUPPERLETTERS + IncludeDigits: true # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_SIGNINGKEY_INCLUDEDIGITS + IncludeSymbols: false # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_SIGNINGKEY_INCLUDESYMBOLS PasswordComplexityPolicy: MinLength: 8 # ZITADEL_DEFAULTINSTANCE_PASSWORDCOMPLEXITYPOLICY_MINLENGTH HasLowercase: true # ZITADEL_DEFAULTINSTANCE_PASSWORDCOMPLEXITYPOLICY_HASLOWERCASE diff --git a/cmd/encryption/encryption_keys.go b/cmd/encryption/encryption_keys.go index b4772e7957..9a26d572c0 100644 --- a/cmd/encryption/encryption_keys.go +++ b/cmd/encryption/encryption_keys.go @@ -17,6 +17,7 @@ var ( "smsKey", "smtpKey", "userKey", + "targetKey", "csrfCookieKey", "userAgentCookieKey", } @@ -31,6 +32,7 @@ type EncryptionKeyConfig struct { SMS *crypto.KeyConfig SMTP *crypto.KeyConfig User *crypto.KeyConfig + Target *crypto.KeyConfig CSRFCookieKeyID string UserAgentCookieKeyID string } @@ -44,6 +46,7 @@ type EncryptionKeys struct { SMS crypto.EncryptionAlgorithm SMTP crypto.EncryptionAlgorithm User crypto.EncryptionAlgorithm + Target crypto.EncryptionAlgorithm CSRFCookieKey []byte UserAgentCookieKey []byte OIDCKey []byte @@ -91,6 +94,10 @@ func EnsureEncryptionKeys(ctx context.Context, keyConfig *EncryptionKeyConfig, k if err != nil { return nil, err } + keys.Target, err = crypto.NewAESCrypto(keyConfig.Target, keyStorage) + if err != nil { + return nil, err + } key, err = crypto.LoadKey(keyConfig.CSRFCookieKeyID, keyStorage) if err != nil { return nil, err diff --git a/cmd/initialise/helper.go b/cmd/initialise/helper.go index ac212e8e6d..94d5aef7eb 100644 --- a/cmd/initialise/helper.go +++ b/cmd/initialise/helper.go @@ -1,6 +1,7 @@ package initialise import ( + "context" "errors" "github.com/jackc/pgx/v5/pgconn" @@ -8,8 +9,8 @@ import ( "github.com/zitadel/zitadel/internal/database" ) -func exec(db *database.DB, stmt string, possibleErrCodes []string, args ...interface{}) error { - _, err := db.Exec(stmt, args...) +func exec(ctx context.Context, db database.ContextExecuter, stmt string, possibleErrCodes []string, args ...interface{}) error { + _, err := db.ExecContext(ctx, stmt, args...) pgErr := new(pgconn.PgError) if errors.As(err, &pgErr) { for _, possibleCode := range possibleErrCodes { diff --git a/cmd/initialise/init.go b/cmd/initialise/init.go index 917e6a2d93..fba5098fa2 100644 --- a/cmd/initialise/init.go +++ b/cmd/initialise/init.go @@ -59,7 +59,7 @@ The user provided by flags needs privileges to } func InitAll(ctx context.Context, config *Config) { - err := initialise(config.Database, + err := initialise(ctx, config.Database, VerifyUser(config.Database.Username(), config.Database.Password()), VerifyDatabase(config.Database.DatabaseName()), VerifyGrant(config.Database.DatabaseName(), config.Database.Username()), @@ -71,7 +71,7 @@ func InitAll(ctx context.Context, config *Config) { logging.OnError(err).Fatal("unable to initialize ZITADEL") } -func initialise(config database.Config, steps ...func(*database.DB) error) error { +func initialise(ctx context.Context, config database.Config, steps ...func(context.Context, *database.DB) error) error { logging.Info("initialization started") err := ReadStmts(config.Type()) @@ -85,12 +85,12 @@ func initialise(config database.Config, steps ...func(*database.DB) error) error } defer db.Close() - return Init(db, steps...) + return Init(ctx, db, steps...) } -func Init(db *database.DB, steps ...func(*database.DB) error) error { +func Init(ctx context.Context, db *database.DB, steps ...func(context.Context, *database.DB) error) error { for _, step := range steps { - if err := step(db); err != nil { + if err := step(ctx, db); err != nil { return err } } diff --git a/cmd/initialise/sql/cockroach/02_database.sql b/cmd/initialise/sql/cockroach/02_database.sql index a0e3c3350f..6103b95b31 100644 --- a/cmd/initialise/sql/cockroach/02_database.sql +++ b/cmd/initialise/sql/cockroach/02_database.sql @@ -1,2 +1,2 @@ -- replace %[1]s with the name of the database -CREATE DATABASE IF NOT EXISTS "%[1]s" \ No newline at end of file +CREATE DATABASE IF NOT EXISTS "%[1]s"; diff --git a/cmd/initialise/sql/cockroach/08_events_table.sql b/cmd/initialise/sql/cockroach/08_events_table.sql index 00eba689f3..ebaf18ce2a 100644 --- a/cmd/initialise/sql/cockroach/08_events_table.sql +++ b/cmd/initialise/sql/cockroach/08_events_table.sql @@ -19,3 +19,98 @@ CREATE TABLE IF NOT EXISTS eventstore.events2 ( , INDEX es_wm (aggregate_id, instance_id, aggregate_type, event_type) , INDEX es_projection (instance_id, aggregate_type, event_type, "position" DESC) ); + +-- represents an event to be created. +CREATE TYPE IF NOT EXISTS eventstore.command AS ( + instance_id TEXT + , aggregate_type TEXT + , aggregate_id TEXT + , command_type TEXT + , revision INT2 + , payload JSONB + , creator TEXT + , owner TEXT +); + +CREATE OR REPLACE FUNCTION eventstore.commands_to_events(commands eventstore.command[]) RETURNS SETOF eventstore.events2 VOLATILE AS $$ +SELECT + ("c").instance_id + , ("c").aggregate_type + , ("c").aggregate_id + , ("c").command_type AS event_type + , cs.sequence + ROW_NUMBER() OVER (PARTITION BY ("c").instance_id, ("c").aggregate_type, ("c").aggregate_id ORDER BY ("c").in_tx_order) AS sequence + , ("c").revision + , hlc_to_timestamp(cluster_logical_timestamp()) AS created_at + , ("c").payload + , ("c").creator + , cs.owner + , cluster_logical_timestamp() AS position + , ("c").in_tx_order +FROM ( + SELECT + ("c").instance_id + , ("c").aggregate_type + , ("c").aggregate_id + , ("c").command_type + , ("c").revision + , ("c").payload + , ("c").creator + , ("c").owner + , ROW_NUMBER() OVER () AS in_tx_order + FROM + UNNEST(commands) AS "c" +) AS "c" +JOIN ( + SELECT + cmds.instance_id + , cmds.aggregate_type + , cmds.aggregate_id + , CASE WHEN (e.owner IS NOT NULL OR e.owner <> '') THEN e.owner ELSE command_owners.owner END AS owner + , COALESCE(MAX(e.sequence), 0) AS sequence + FROM ( + SELECT DISTINCT + ("cmds").instance_id + , ("cmds").aggregate_type + , ("cmds").aggregate_id + , ("cmds").owner + FROM UNNEST(commands) AS "cmds" + ) AS cmds + LEFT JOIN eventstore.events2 AS e + ON cmds.instance_id = e.instance_id + AND cmds.aggregate_type = e.aggregate_type + AND cmds.aggregate_id = e.aggregate_id + JOIN ( + SELECT + DISTINCT ON ( + ("c").instance_id + , ("c").aggregate_type + , ("c").aggregate_id + ) + ("c").instance_id + , ("c").aggregate_type + , ("c").aggregate_id + , ("c").owner + FROM + UNNEST(commands) AS "c" + ) AS command_owners ON + cmds.instance_id = command_owners.instance_id + AND cmds.aggregate_type = command_owners.aggregate_type + AND cmds.aggregate_id = command_owners.aggregate_id + GROUP BY + cmds.instance_id + , cmds.aggregate_type + , cmds.aggregate_id + , 4 -- owner +) AS cs + ON ("c").instance_id = cs.instance_id + AND ("c").aggregate_type = cs.aggregate_type + AND ("c").aggregate_id = cs.aggregate_id +ORDER BY + in_tx_order +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION eventstore.push(commands eventstore.command[]) RETURNS SETOF eventstore.events2 AS $$ + INSERT INTO eventstore.events2 + SELECT * FROM eventstore.commands_to_events(commands) + RETURNING * +$$ LANGUAGE SQL; \ No newline at end of file diff --git a/cmd/initialise/sql/postgres/08_events_table.sql b/cmd/initialise/sql/postgres/08_events_table.sql index d978df5d9f..c4b7351e65 100644 --- a/cmd/initialise/sql/postgres/08_events_table.sql +++ b/cmd/initialise/sql/postgres/08_events_table.sql @@ -19,4 +19,103 @@ CREATE TABLE IF NOT EXISTS eventstore.events2 ( CREATE INDEX IF NOT EXISTS es_active_instances ON eventstore.events2 (created_at DESC, instance_id); CREATE INDEX IF NOT EXISTS es_wm ON eventstore.events2 (aggregate_id, instance_id, aggregate_type, event_type); -CREATE INDEX IF NOT EXISTS es_projection ON eventstore.events2 (instance_id, aggregate_type, event_type, "position"); \ No newline at end of file +CREATE INDEX IF NOT EXISTS es_projection ON eventstore.events2 (instance_id, aggregate_type, event_type, "position"); + +-- represents an event to be created. +DO $$ BEGIN + CREATE TYPE eventstore.command AS ( + instance_id TEXT + , aggregate_type TEXT + , aggregate_id TEXT + , command_type TEXT + , revision INT2 + , payload JSONB + , creator TEXT + , owner TEXT + ); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + +CREATE OR REPLACE FUNCTION eventstore.commands_to_events(commands eventstore.command[]) RETURNS SETOF eventstore.events2 VOLATILE AS $$ +SELECT + c.instance_id + , c.aggregate_type + , c.aggregate_id + , c.command_type AS event_type + , cs.sequence + ROW_NUMBER() OVER (PARTITION BY c.instance_id, c.aggregate_type, c.aggregate_id ORDER BY c.in_tx_order) AS sequence + , c.revision + , NOW() AS created_at + , c.payload + , c.creator + , cs.owner + , EXTRACT(EPOCH FROM NOW()) AS position + , c.in_tx_order +FROM ( + SELECT + c.instance_id + , c.aggregate_type + , c.aggregate_id + , c.command_type + , c.revision + , c.payload + , c.creator + , c.owner + , ROW_NUMBER() OVER () AS in_tx_order + FROM + UNNEST(commands) AS c +) AS c +JOIN ( + SELECT + cmds.instance_id + , cmds.aggregate_type + , cmds.aggregate_id + , CASE WHEN (e.owner IS NOT NULL OR e.owner <> '') THEN e.owner ELSE command_owners.owner END AS owner + , COALESCE(MAX(e.sequence), 0) AS sequence + FROM ( + SELECT DISTINCT + instance_id + , aggregate_type + , aggregate_id + , owner + FROM UNNEST(commands) + ) AS cmds + LEFT JOIN eventstore.events2 AS e + ON cmds.instance_id = e.instance_id + AND cmds.aggregate_type = e.aggregate_type + AND cmds.aggregate_id = e.aggregate_id + JOIN ( + SELECT + DISTINCT ON ( + instance_id + , aggregate_type + , aggregate_id + ) + instance_id + , aggregate_type + , aggregate_id + , owner + FROM + UNNEST(commands) + ) AS command_owners ON + cmds.instance_id = command_owners.instance_id + AND cmds.aggregate_type = command_owners.aggregate_type + AND cmds.aggregate_id = command_owners.aggregate_id + GROUP BY + cmds.instance_id + , cmds.aggregate_type + , cmds.aggregate_id + , 4 -- owner +) AS cs + ON c.instance_id = cs.instance_id + AND c.aggregate_type = cs.aggregate_type + AND c.aggregate_id = cs.aggregate_id +ORDER BY + in_tx_order; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION eventstore.push(commands eventstore.command[]) RETURNS SETOF eventstore.events2 VOLATILE AS $$ +INSERT INTO eventstore.events2 +SELECT * FROM eventstore.commands_to_events(commands) +RETURNING * +$$ LANGUAGE SQL; diff --git a/cmd/initialise/verify_database.go b/cmd/initialise/verify_database.go index be3ec19bab..6e04e489f5 100644 --- a/cmd/initialise/verify_database.go +++ b/cmd/initialise/verify_database.go @@ -1,6 +1,7 @@ package initialise import ( + "context" _ "embed" "fmt" @@ -28,16 +29,16 @@ The user provided by flags needs privileges to Run: func(cmd *cobra.Command, args []string) { config := MustNewConfig(viper.GetViper()) - err := initialise(config.Database, VerifyDatabase(config.Database.DatabaseName())) + err := initialise(cmd.Context(), config.Database, VerifyDatabase(config.Database.DatabaseName())) logging.OnError(err).Fatal("unable to initialize the database") }, } } -func VerifyDatabase(databaseName string) func(*database.DB) error { - return func(db *database.DB) error { +func VerifyDatabase(databaseName string) func(context.Context, *database.DB) error { + return func(ctx context.Context, db *database.DB) error { logging.WithFields("database", databaseName).Info("verify database") - return exec(db, fmt.Sprintf(databaseStmt, databaseName), []string{dbAlreadyExistsCode}) + return exec(ctx, db, fmt.Sprintf(databaseStmt, databaseName), []string{dbAlreadyExistsCode}) } } diff --git a/cmd/initialise/verify_database_test.go b/cmd/initialise/verify_database_test.go index ebdf0473b6..d7da97847f 100644 --- a/cmd/initialise/verify_database_test.go +++ b/cmd/initialise/verify_database_test.go @@ -1,6 +1,7 @@ package initialise import ( + "context" "database/sql" "errors" "testing" @@ -55,7 +56,7 @@ func Test_verifyDB(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := VerifyDatabase(tt.args.database)(tt.args.db.db); !errors.Is(err, tt.targetErr) { + if err := VerifyDatabase(tt.args.database)(context.Background(), tt.args.db.db); !errors.Is(err, tt.targetErr) { t.Errorf("verifyDB() error = %v, want: %v", err, tt.targetErr) } if err := tt.args.db.mock.ExpectationsWereMet(); err != nil { diff --git a/cmd/initialise/verify_grant.go b/cmd/initialise/verify_grant.go index ed8ecb7256..a14a495bff 100644 --- a/cmd/initialise/verify_grant.go +++ b/cmd/initialise/verify_grant.go @@ -1,6 +1,7 @@ package initialise import ( + "context" _ "embed" "fmt" @@ -23,16 +24,16 @@ Prerequisites: Run: func(cmd *cobra.Command, args []string) { config := MustNewConfig(viper.GetViper()) - err := initialise(config.Database, VerifyGrant(config.Database.DatabaseName(), config.Database.Username())) + err := initialise(cmd.Context(), config.Database, VerifyGrant(config.Database.DatabaseName(), config.Database.Username())) logging.OnError(err).Fatal("unable to set grant") }, } } -func VerifyGrant(databaseName, username string) func(*database.DB) error { - return func(db *database.DB) error { +func VerifyGrant(databaseName, username string) func(context.Context, *database.DB) error { + return func(ctx context.Context, db *database.DB) error { logging.WithFields("user", username, "database", databaseName).Info("verify grant") - return exec(db, fmt.Sprintf(grantStmt, databaseName, username), nil) + return exec(ctx, db, fmt.Sprintf(grantStmt, databaseName, username), nil) } } diff --git a/cmd/initialise/verify_grant_test.go b/cmd/initialise/verify_grant_test.go index a6bfa818ad..3cab9099f7 100644 --- a/cmd/initialise/verify_grant_test.go +++ b/cmd/initialise/verify_grant_test.go @@ -1,6 +1,7 @@ package initialise import ( + "context" "database/sql" "errors" "testing" @@ -53,7 +54,7 @@ func Test_verifyGrant(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := VerifyGrant(tt.args.database, tt.args.username)(tt.args.db.db); !errors.Is(err, tt.targetErr) { + if err := VerifyGrant(tt.args.database, tt.args.username)(context.Background(), tt.args.db.db); !errors.Is(err, tt.targetErr) { t.Errorf("VerifyGrant() error = %v, want: %v", err, tt.targetErr) } if err := tt.args.db.mock.ExpectationsWereMet(); err != nil { diff --git a/cmd/initialise/verify_settings.go b/cmd/initialise/verify_settings.go index 75e811663f..6f4ba7c074 100644 --- a/cmd/initialise/verify_settings.go +++ b/cmd/initialise/verify_settings.go @@ -1,6 +1,7 @@ package initialise import ( + "context" _ "embed" "fmt" @@ -26,19 +27,19 @@ Cockroach Run: func(cmd *cobra.Command, args []string) { config := MustNewConfig(viper.GetViper()) - err := initialise(config.Database, VerifySettings(config.Database.DatabaseName(), config.Database.Username())) + err := initialise(cmd.Context(), config.Database, VerifySettings(config.Database.DatabaseName(), config.Database.Username())) logging.OnError(err).Fatal("unable to set settings") }, } } -func VerifySettings(databaseName, username string) func(*database.DB) error { - return func(db *database.DB) error { +func VerifySettings(databaseName, username string) func(context.Context, *database.DB) error { + return func(ctx context.Context, db *database.DB) error { if db.Type() == "postgres" { return nil } logging.WithFields("user", username, "database", databaseName).Info("verify settings") - return exec(db, fmt.Sprintf(settingsStmt, databaseName, username), nil) + return exec(ctx, db, fmt.Sprintf(settingsStmt, databaseName, username), nil) } } diff --git a/cmd/initialise/verify_user.go b/cmd/initialise/verify_user.go index 86e6c25ba0..43bdb91420 100644 --- a/cmd/initialise/verify_user.go +++ b/cmd/initialise/verify_user.go @@ -1,6 +1,7 @@ package initialise import ( + "context" _ "embed" "fmt" @@ -28,20 +29,20 @@ The user provided by flags needs privileges to Run: func(cmd *cobra.Command, args []string) { config := MustNewConfig(viper.GetViper()) - err := initialise(config.Database, VerifyUser(config.Database.Username(), config.Database.Password())) + err := initialise(cmd.Context(), config.Database, VerifyUser(config.Database.Username(), config.Database.Password())) logging.OnError(err).Fatal("unable to init user") }, } } -func VerifyUser(username, password string) func(*database.DB) error { - return func(db *database.DB) error { +func VerifyUser(username, password string) func(context.Context, *database.DB) error { + return func(ctx context.Context, db *database.DB) error { logging.WithFields("username", username).Info("verify user") if password != "" { createUserStmt += " WITH PASSWORD '" + password + "'" } - return exec(db, fmt.Sprintf(createUserStmt, username), []string{roleAlreadyExistsCode}) + return exec(ctx, db, fmt.Sprintf(createUserStmt, username), []string{roleAlreadyExistsCode}) } } diff --git a/cmd/initialise/verify_user_test.go b/cmd/initialise/verify_user_test.go index da7afc1765..53b35e67db 100644 --- a/cmd/initialise/verify_user_test.go +++ b/cmd/initialise/verify_user_test.go @@ -1,6 +1,7 @@ package initialise import ( + "context" "database/sql" "errors" "testing" @@ -70,7 +71,7 @@ func Test_verifyUser(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := VerifyUser(tt.args.username, tt.args.password)(tt.args.db.db); !errors.Is(err, tt.targetErr) { + if err := VerifyUser(tt.args.username, tt.args.password)(context.Background(), tt.args.db.db); !errors.Is(err, tt.targetErr) { t.Errorf("VerifyGrant() error = %v, want: %v", err, tt.targetErr) } if err := tt.args.db.mock.ExpectationsWereMet(); err != nil { diff --git a/cmd/initialise/verify_zitadel.go b/cmd/initialise/verify_zitadel.go index 7c16fdaadf..a5ce1fd57c 100644 --- a/cmd/initialise/verify_zitadel.go +++ b/cmd/initialise/verify_zitadel.go @@ -2,6 +2,7 @@ package initialise import ( "context" + "database/sql" _ "embed" "fmt" @@ -11,6 +12,7 @@ import ( "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/database/dialect" + es_v3 "github.com/zitadel/zitadel/internal/eventstore/v3" ) func newZitadel() *cobra.Command { @@ -36,38 +38,44 @@ func VerifyZitadel(ctx context.Context, db *database.DB, config database.Config) return err } + conn, err := db.Conn(ctx) + if err != nil { + return err + } + defer conn.Close() + logging.WithFields().Info("verify system") - if err := exec(db, fmt.Sprintf(createSystemStmt, config.Username()), nil); err != nil { + if err := exec(ctx, conn, fmt.Sprintf(createSystemStmt, config.Username()), nil); err != nil { return err } logging.WithFields().Info("verify encryption keys") - if err := createEncryptionKeys(ctx, db); err != nil { + if err := createEncryptionKeys(ctx, conn); err != nil { return err } logging.WithFields().Info("verify projections") - if err := exec(db, fmt.Sprintf(createProjectionsStmt, config.Username()), nil); err != nil { + if err := exec(ctx, conn, fmt.Sprintf(createProjectionsStmt, config.Username()), nil); err != nil { return err } logging.WithFields().Info("verify eventstore") - if err := exec(db, fmt.Sprintf(createEventstoreStmt, config.Username()), nil); err != nil { + if err := exec(ctx, conn, fmt.Sprintf(createEventstoreStmt, config.Username()), nil); err != nil { return err } logging.WithFields().Info("verify events tables") - if err := createEvents(ctx, db); err != nil { + if err := createEvents(ctx, conn); err != nil { return err } logging.WithFields().Info("verify system sequence") - if err := exec(db, createSystemSequenceStmt, nil); err != nil { + if err := exec(ctx, conn, createSystemSequenceStmt, nil); err != nil { return err } logging.WithFields().Info("verify unique constraints") - if err := exec(db, createUniqueConstraints, nil); err != nil { + if err := exec(ctx, conn, createUniqueConstraints, nil); err != nil { return err } @@ -89,7 +97,7 @@ func verifyZitadel(ctx context.Context, config database.Config) error { return db.Close() } -func createEncryptionKeys(ctx context.Context, db *database.DB) error { +func createEncryptionKeys(ctx context.Context, db database.Beginner) error { tx, err := db.BeginTx(ctx, nil) if err != nil { return err @@ -103,8 +111,8 @@ func createEncryptionKeys(ctx context.Context, db *database.DB) error { return tx.Commit() } -func createEvents(ctx context.Context, db *database.DB) (err error) { - tx, err := db.BeginTx(ctx, nil) +func createEvents(ctx context.Context, conn *sql.Conn) (err error) { + tx, err := conn.BeginTx(ctx, nil) if err != nil { return err } @@ -127,5 +135,8 @@ func createEvents(ctx context.Context, db *database.DB) (err error) { return row.Err() } _, err = tx.Exec(createEventsStmt) - return err + if err != nil { + return err + } + return es_v3.CheckExecutionPlan(ctx, conn) } diff --git a/cmd/initialise/verify_zitadel_test.go b/cmd/initialise/verify_zitadel_test.go index 64df01bdb1..194911a179 100644 --- a/cmd/initialise/verify_zitadel_test.go +++ b/cmd/initialise/verify_zitadel_test.go @@ -108,7 +108,12 @@ func Test_verifyEvents(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := createEvents(context.Background(), tt.args.db.db); !errors.Is(err, tt.targetErr) { + conn, err := tt.args.db.db.Conn(context.Background()) + if err != nil { + t.Error(err) + return + } + if err := createEvents(context.Background(), conn); !errors.Is(err, tt.targetErr) { t.Errorf("createEvents() error = %v, want: %v", err, tt.targetErr) } if err := tt.args.db.mock.ExpectationsWereMet(); err != nil { diff --git a/cmd/mirror/projections.go b/cmd/mirror/projections.go index cffc4921ca..ae903d90c5 100644 --- a/cmd/mirror/projections.go +++ b/cmd/mirror/projections.go @@ -69,6 +69,7 @@ func projectionsCmd() *cobra.Command { type ProjectionsConfig struct { Destination database.Config Projections projection.Config + Notifications handlers.WorkerConfig EncryptionKeys *encryption.EncryptionKeyConfig SystemAPIUsers map[string]*internal_authz.SystemAPIUser Eventstore *eventstore.Config @@ -144,6 +145,7 @@ func projections( keys.OTP, keys.OIDC, keys.SAML, + keys.Target, config.InternalAuthZ.RolePermissionMappings, sessionTokenVerifier, func(q *query.Queries) domain.PermissionCheck { @@ -182,6 +184,7 @@ func projections( keys.DomainVerification, keys.OIDC, keys.SAML, + keys.Target, &http.Client{}, func(ctx context.Context, permission, orgID, resourceID string) (err error) { return internal_authz.CheckPermission(ctx, authZRepo, config.InternalAuthZ.RolePermissionMappings, permission, orgID, resourceID) @@ -205,6 +208,7 @@ func projections( config.Projections.Customizations["notificationsquotas"], config.Projections.Customizations["backchannel"], config.Projections.Customizations["telemetry"], + config.Notifications, *config.Telemetry, config.ExternalDomain, config.ExternalPort, @@ -219,6 +223,7 @@ func projections( keys.SMS, keys.OIDC, config.OIDC.DefaultBackChannelLogoutLifetime, + client, ) config.Auth.Spooler.Client = client diff --git a/cmd/setup/03.go b/cmd/setup/03.go index 4d4231ea9c..588ac71610 100644 --- a/cmd/setup/03.go +++ b/cmd/setup/03.go @@ -86,6 +86,7 @@ func (mig *FirstInstance) Execute(ctx context.Context, _ eventstore.Event) error nil, nil, nil, + nil, 0, 0, 0, diff --git a/cmd/setup/29.go b/cmd/setup/29.go index 1d3bfe992d..8df1047ec9 100644 --- a/cmd/setup/29.go +++ b/cmd/setup/29.go @@ -16,8 +16,6 @@ type FillFieldsForProjectGrant struct { func (mig *FillFieldsForProjectGrant) Execute(ctx context.Context, _ eventstore.Event) error { instances, err := mig.eventstore.InstanceIDs( ctx, - 0, - true, eventstore.NewSearchQueryBuilder(eventstore.ColumnsInstanceIDs). OrderDesc(). AddQuery(). diff --git a/cmd/setup/30.go b/cmd/setup/30.go index f2b6d466f1..c2037a7f23 100644 --- a/cmd/setup/30.go +++ b/cmd/setup/30.go @@ -16,8 +16,6 @@ type FillFieldsForOrgDomainVerified struct { func (mig *FillFieldsForOrgDomainVerified) Execute(ctx context.Context, _ eventstore.Event) error { instances, err := mig.eventstore.InstanceIDs( ctx, - 0, - true, eventstore.NewSearchQueryBuilder(eventstore.ColumnsInstanceIDs). OrderDesc(). AddQuery(). diff --git a/cmd/setup/39.go b/cmd/setup/39.go new file mode 100644 index 0000000000..01b0ddfcbf --- /dev/null +++ b/cmd/setup/39.go @@ -0,0 +1,27 @@ +package setup + +import ( + "context" + _ "embed" + + "github.com/zitadel/zitadel/internal/database" + "github.com/zitadel/zitadel/internal/eventstore" +) + +var ( + //go:embed 39.sql + deleteStaleOrgFields string +) + +type DeleteStaleOrgFields struct { + dbClient *database.DB +} + +func (mig *DeleteStaleOrgFields) Execute(ctx context.Context, _ eventstore.Event) error { + _, err := mig.dbClient.ExecContext(ctx, deleteStaleOrgFields) + return err +} + +func (mig *DeleteStaleOrgFields) String() string { + return "39_delete_stale_org_fields" +} diff --git a/cmd/setup/39.sql b/cmd/setup/39.sql new file mode 100644 index 0000000000..2abb701c9f --- /dev/null +++ b/cmd/setup/39.sql @@ -0,0 +1,8 @@ +DELETE FROM eventstore.fields +WHERE aggregate_type = 'org' +AND aggregate_id IN ( + SELECT aggregate_id + FROM eventstore.events2 + WHERE aggregate_type = 'org' + AND event_type = 'org.removed' +); diff --git a/cmd/setup/40.go b/cmd/setup/40.go new file mode 100644 index 0000000000..a0d1afcf54 --- /dev/null +++ b/cmd/setup/40.go @@ -0,0 +1,52 @@ +package setup + +import ( + "context" + "embed" + "fmt" + + "github.com/zitadel/logging" + + "github.com/zitadel/zitadel/internal/database" + "github.com/zitadel/zitadel/internal/eventstore" +) + +var ( + //go:embed 40/cockroach/*.sql + //go:embed 40/postgres/*.sql + initPushFunc embed.FS +) + +type InitPushFunc struct { + dbClient *database.DB +} + +func (mig *InitPushFunc) Execute(ctx context.Context, _ eventstore.Event) (err error) { + statements, err := readStatements(initPushFunc, "40", mig.dbClient.Type()) + if err != nil { + return err + } + conn, err := mig.dbClient.Conn(ctx) + if err != nil { + return err + } + defer func() { + closeErr := conn.Close() + logging.OnError(closeErr).Debug("failed to release connection") + // Force the pool to reopen connections to apply the new types + mig.dbClient.Pool.Reset() + }() + + for _, stmt := range statements { + logging.WithFields("file", stmt.file, "migration", mig.String()).Info("execute statement") + if _, err := conn.ExecContext(ctx, stmt.query); err != nil { + return fmt.Errorf("%s %s: %w", mig.String(), stmt.file, err) + } + } + + return nil +} + +func (mig *InitPushFunc) String() string { + return "40_init_push_func" +} diff --git a/cmd/setup/40/cockroach/40_init_push_func.sql b/cmd/setup/40/cockroach/40_init_push_func.sql new file mode 100644 index 0000000000..c2e2e92b07 --- /dev/null +++ b/cmd/setup/40/cockroach/40_init_push_func.sql @@ -0,0 +1,107 @@ +-- represents an event to be created. +CREATE TYPE IF NOT EXISTS eventstore.command AS ( + instance_id TEXT + , aggregate_type TEXT + , aggregate_id TEXT + , command_type TEXT + , revision INT2 + , payload JSONB + , creator TEXT + , owner TEXT +); + +/* +select * from eventstore.commands_to_events( +ARRAY[ + ROW('', 'system', 'SYSTEM', 'ct1', 1, '{"key": "value"}', 'c1', 'SYSTEM') + , ROW('', 'system', 'SYSTEM', 'ct2', 1, '{"key": "value"}', 'c1', 'SYSTEM') + , ROW('289525561255060732', 'org', '289575074711790844', 'ct3', 1, '{"key": "value"}', 'c1', '289575074711790844') + , ROW('289525561255060732', 'user', '289575075164906748', 'ct3', 1, '{"key": "value"}', 'c1', '289575074711790844') + , ROW('289525561255060732', 'oidc_session', 'V2_289575178579535100', 'ct3', 1, '{"key": "value"}', 'c1', '289575074711790844') + , ROW('', 'system', 'SYSTEM', 'ct3', 1, '{"key": "value"}', 'c1', 'SYSTEM') +]::eventstore.command[] +); +*/ + +CREATE OR REPLACE FUNCTION eventstore.commands_to_events(commands eventstore.command[]) RETURNS SETOF eventstore.events2 VOLATILE AS $$ +SELECT + ("c").instance_id + , ("c").aggregate_type + , ("c").aggregate_id + , ("c").command_type AS event_type + , cs.sequence + ROW_NUMBER() OVER (PARTITION BY ("c").instance_id, ("c").aggregate_type, ("c").aggregate_id ORDER BY ("c").in_tx_order) AS sequence + , ("c").revision + , hlc_to_timestamp(cluster_logical_timestamp()) AS created_at + , ("c").payload + , ("c").creator + , cs.owner + , cluster_logical_timestamp() AS position + , ("c").in_tx_order +FROM ( + SELECT + ("c").instance_id + , ("c").aggregate_type + , ("c").aggregate_id + , ("c").command_type + , ("c").revision + , ("c").payload + , ("c").creator + , ("c").owner + , ROW_NUMBER() OVER () AS in_tx_order + FROM + UNNEST(commands) AS "c" +) AS "c" +JOIN ( + SELECT + cmds.instance_id + , cmds.aggregate_type + , cmds.aggregate_id + , CASE WHEN (e.owner <> '') THEN e.owner ELSE command_owners.owner END AS owner + , COALESCE(MAX(e.sequence), 0) AS sequence + FROM ( + SELECT DISTINCT + ("cmds").instance_id + , ("cmds").aggregate_type + , ("cmds").aggregate_id + , ("cmds").owner + FROM UNNEST(commands) AS "cmds" + ) AS cmds + LEFT JOIN eventstore.events2 AS e + ON cmds.instance_id = e.instance_id + AND cmds.aggregate_type = e.aggregate_type + AND cmds.aggregate_id = e.aggregate_id + JOIN ( + SELECT + DISTINCT ON ( + ("c").instance_id + , ("c").aggregate_type + , ("c").aggregate_id + ) + ("c").instance_id + , ("c").aggregate_type + , ("c").aggregate_id + , ("c").owner + FROM + UNNEST(commands) AS "c" + ) AS command_owners ON + cmds.instance_id = command_owners.instance_id + AND cmds.aggregate_type = command_owners.aggregate_type + AND cmds.aggregate_id = command_owners.aggregate_id + GROUP BY + cmds.instance_id + , cmds.aggregate_type + , cmds.aggregate_id + , 4 -- owner +) AS cs + ON ("c").instance_id = cs.instance_id + AND ("c").aggregate_type = cs.aggregate_type + AND ("c").aggregate_id = cs.aggregate_id +ORDER BY + in_tx_order +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION eventstore.push(commands eventstore.command[]) RETURNS SETOF eventstore.events2 AS $$ + INSERT INTO eventstore.events2 + SELECT * FROM eventstore.commands_to_events(commands) + RETURNING * +$$ LANGUAGE SQL; \ No newline at end of file diff --git a/cmd/setup/40/postgres/01_type.sql b/cmd/setup/40/postgres/01_type.sql new file mode 100644 index 0000000000..ace6d8fe1a --- /dev/null +++ b/cmd/setup/40/postgres/01_type.sql @@ -0,0 +1,15 @@ +-- represents an event to be created. +DO $$ BEGIN + CREATE TYPE eventstore.command AS ( + instance_id TEXT + , aggregate_type TEXT + , aggregate_id TEXT + , command_type TEXT + , revision INT2 + , payload JSONB + , creator TEXT + , owner TEXT + ); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/cmd/setup/40/postgres/02_func.sql b/cmd/setup/40/postgres/02_func.sql new file mode 100644 index 0000000000..5f84f3908c --- /dev/null +++ b/cmd/setup/40/postgres/02_func.sql @@ -0,0 +1,82 @@ +CREATE OR REPLACE FUNCTION eventstore.commands_to_events(commands eventstore.command[]) RETURNS SETOF eventstore.events2 VOLATILE AS $$ +SELECT + c.instance_id + , c.aggregate_type + , c.aggregate_id + , c.command_type AS event_type + , cs.sequence + ROW_NUMBER() OVER (PARTITION BY c.instance_id, c.aggregate_type, c.aggregate_id ORDER BY c.in_tx_order) AS sequence + , c.revision + , NOW() AS created_at + , c.payload + , c.creator + , cs.owner + , EXTRACT(EPOCH FROM NOW()) AS position + , c.in_tx_order +FROM ( + SELECT + c.instance_id + , c.aggregate_type + , c.aggregate_id + , c.command_type + , c.revision + , c.payload + , c.creator + , c.owner + , ROW_NUMBER() OVER () AS in_tx_order + FROM + UNNEST(commands) AS c +) AS c +JOIN ( + SELECT + cmds.instance_id + , cmds.aggregate_type + , cmds.aggregate_id + , CASE WHEN (e.owner IS NOT NULL OR e.owner <> '') THEN e.owner ELSE command_owners.owner END AS owner + , COALESCE(MAX(e.sequence), 0) AS sequence + FROM ( + SELECT DISTINCT + instance_id + , aggregate_type + , aggregate_id + , owner + FROM UNNEST(commands) + ) AS cmds + LEFT JOIN eventstore.events2 AS e + ON cmds.instance_id = e.instance_id + AND cmds.aggregate_type = e.aggregate_type + AND cmds.aggregate_id = e.aggregate_id + JOIN ( + SELECT + DISTINCT ON ( + instance_id + , aggregate_type + , aggregate_id + ) + instance_id + , aggregate_type + , aggregate_id + , owner + FROM + UNNEST(commands) + ) AS command_owners ON + cmds.instance_id = command_owners.instance_id + AND cmds.aggregate_type = command_owners.aggregate_type + AND cmds.aggregate_id = command_owners.aggregate_id + GROUP BY + cmds.instance_id + , cmds.aggregate_type + , cmds.aggregate_id + , 4 -- owner +) AS cs + ON c.instance_id = cs.instance_id + AND c.aggregate_type = cs.aggregate_type + AND c.aggregate_id = cs.aggregate_id +ORDER BY + in_tx_order; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION eventstore.push(commands eventstore.command[]) RETURNS SETOF eventstore.events2 VOLATILE AS $$ +INSERT INTO eventstore.events2 +SELECT * FROM eventstore.commands_to_events(commands) +RETURNING * +$$ LANGUAGE SQL; diff --git a/cmd/setup/41.go b/cmd/setup/41.go new file mode 100644 index 0000000000..57c446e2d1 --- /dev/null +++ b/cmd/setup/41.go @@ -0,0 +1,40 @@ +package setup + +import ( + "context" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/query/projection" + "github.com/zitadel/zitadel/internal/repository/instance" +) + +type FillFieldsForInstanceDomains struct { + eventstore *eventstore.Eventstore +} + +func (mig *FillFieldsForInstanceDomains) Execute(ctx context.Context, _ eventstore.Event) error { + instances, err := mig.eventstore.InstanceIDs( + ctx, + eventstore.NewSearchQueryBuilder(eventstore.ColumnsInstanceIDs). + OrderDesc(). + AddQuery(). + AggregateTypes("instance"). + EventTypes(instance.InstanceAddedEventType). + Builder(), + ) + if err != nil { + return err + } + for _, instance := range instances { + ctx := authz.WithInstanceID(ctx, instance) + if err := projection.InstanceDomainFields.Trigger(ctx); err != nil { + return err + } + } + return nil +} + +func (mig *FillFieldsForInstanceDomains) String() string { + return "41_fill_fields_for_instance_domains" +} diff --git a/cmd/setup/config.go b/cmd/setup/config.go index f688a2a3a4..6c0a355b94 100644 --- a/cmd/setup/config.go +++ b/cmd/setup/config.go @@ -42,6 +42,7 @@ type Config struct { DefaultInstance command.InstanceSetup Machine *id.Config Projections projection.Config + Notifications handlers.WorkerConfig Eventstore *eventstore.Config InitProjections InitProjections @@ -125,6 +126,9 @@ type Steps struct { s36FillV2Milestones *FillV3Milestones s37Apps7OIDConfigsBackChannelLogoutURI *Apps7OIDConfigsBackChannelLogoutURI s38BackChannelLogoutNotificationStart *BackChannelLogoutNotificationStart + s40InitPushFunc *InitPushFunc + s39DeleteStaleOrgFields *DeleteStaleOrgFields + s41FillFieldsForInstanceDomains *FillFieldsForInstanceDomains } func MustNewSteps(v *viper.Viper) *Steps { diff --git a/cmd/setup/config_change.go b/cmd/setup/config_change.go index f38508af2c..fb3ae08d52 100644 --- a/cmd/setup/config_change.go +++ b/cmd/setup/config_change.go @@ -53,6 +53,7 @@ func (mig *externalConfigChange) Execute(ctx context.Context, _ eventstore.Event nil, nil, nil, + nil, 0, 0, 0, diff --git a/cmd/setup/setup.go b/cmd/setup/setup.go index aeed1523b1..b21ba31bce 100644 --- a/cmd/setup/setup.go +++ b/cmd/setup/setup.go @@ -169,6 +169,9 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string) steps.s36FillV2Milestones = &FillV3Milestones{dbClient: queryDBClient, eventstore: eventstoreClient} steps.s37Apps7OIDConfigsBackChannelLogoutURI = &Apps7OIDConfigsBackChannelLogoutURI{dbClient: esPusherDBClient} steps.s38BackChannelLogoutNotificationStart = &BackChannelLogoutNotificationStart{dbClient: esPusherDBClient, esClient: eventstoreClient} + steps.s39DeleteStaleOrgFields = &DeleteStaleOrgFields{dbClient: esPusherDBClient} + steps.s40InitPushFunc = &InitPushFunc{dbClient: esPusherDBClient} + steps.s41FillFieldsForInstanceDomains = &FillFieldsForInstanceDomains{eventstore: eventstoreClient} err = projection.Create(ctx, projectionDBClient, eventstoreClient, config.Projections, nil, nil, nil) logging.OnError(err).Fatal("unable to start projections") @@ -189,6 +192,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string) for _, step := range []migration.Migration{ steps.s14NewEventsTable, + steps.s40InitPushFunc, steps.s1ProjectionTable, steps.s2AssetsTable, steps.s28AddFieldTable, @@ -215,6 +219,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string) steps.s35AddPositionToIndexEsWm, steps.s36FillV2Milestones, steps.s38BackChannelLogoutNotificationStart, + steps.s41FillFieldsForInstanceDomains, } { mustExecuteMigration(ctx, eventstoreClient, step, "migration failed") } @@ -232,6 +237,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string) steps.s32AddAuthSessionID, steps.s33SMSConfigs3TwilioAddVerifyServiceSid, steps.s37Apps7OIDConfigsBackChannelLogoutURI, + steps.s39DeleteStaleOrgFields, } { mustExecuteMigration(ctx, eventstoreClient, step, "migration failed") } @@ -364,6 +370,7 @@ func initProjections( keys.OTP, keys.OIDC, keys.SAML, + keys.Target, config.InternalAuthZ.RolePermissionMappings, sessionTokenVerifier, func(q *query.Queries) domain.PermissionCheck { @@ -420,6 +427,7 @@ func initProjections( keys.DomainVerification, keys.OIDC, keys.SAML, + keys.Target, &http.Client{}, permissionCheck, sessionTokenVerifier, @@ -435,6 +443,7 @@ func initProjections( config.Projections.Customizations["notificationsquotas"], config.Projections.Customizations["backchannel"], config.Projections.Customizations["telemetry"], + config.Notifications, *config.Telemetry, config.ExternalDomain, config.ExternalPort, @@ -449,6 +458,7 @@ func initProjections( keys.SMS, keys.OIDC, config.OIDC.DefaultBackChannelLogoutLifetime, + queryDBClient, ) for _, p := range notify_handler.Projections() { err := migration.Migrate(ctx, eventstoreClient, p) diff --git a/cmd/start/config.go b/cmd/start/config.go index 26c4b84b50..6182342592 100644 --- a/cmd/start/config.go +++ b/cmd/start/config.go @@ -54,6 +54,7 @@ type Config struct { Metrics metrics.Config Profiler profiler.Config Projections projection.Config + Notifications handlers.WorkerConfig Auth auth_es.Config Admin admin_es.Config UserAgentCookie *middleware.UserAgentCookieConfig diff --git a/cmd/start/start.go b/cmd/start/start.go index e816b5bb52..61e9c35e34 100644 --- a/cmd/start/start.go +++ b/cmd/start/start.go @@ -196,6 +196,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server keys.OTP, keys.OIDC, keys.SAML, + keys.Target, config.InternalAuthZ.RolePermissionMappings, sessionTokenVerifier, func(q *query.Queries) domain.PermissionCheck { @@ -245,6 +246,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server keys.DomainVerification, keys.OIDC, keys.SAML, + keys.Target, &http.Client{}, permissionCheck, sessionTokenVerifier, @@ -277,6 +279,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server config.Projections.Customizations["notificationsquotas"], config.Projections.Customizations["backchannel"], config.Projections.Customizations["telemetry"], + config.Notifications, *config.Telemetry, config.ExternalDomain, config.ExternalPort, @@ -291,6 +294,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server keys.SMS, keys.OIDC, config.OIDC.DefaultBackChannelLogoutLifetime, + queryDBClient, ) notification.Start(ctx) @@ -401,6 +405,7 @@ func startAPIs( config.Auth.Spooler.Client = dbClient config.Auth.Spooler.Eventstore = eventstore + config.Auth.Spooler.ActiveInstancer = queries authRepo, err := auth_es.Start(ctx, config.Auth, config.SystemDefaults, commands, queries, dbClient, eventstore, keys.OIDC, keys.User) if err != nil { return nil, fmt.Errorf("error starting auth repo: %w", err) @@ -408,7 +413,8 @@ func startAPIs( config.Admin.Spooler.Client = dbClient config.Admin.Spooler.Eventstore = eventstore - err = admin_es.Start(ctx, config.Admin, store, dbClient) + config.Admin.Spooler.ActiveInstancer = queries + err = admin_es.Start(ctx, config.Admin, store, dbClient, queries) if err != nil { return nil, fmt.Errorf("error starting admin repo: %w", err) } diff --git a/console/src/app/app.module.ts b/console/src/app/app.module.ts index b995c69b88..7fad84a4c6 100644 --- a/console/src/app/app.module.ts +++ b/console/src/app/app.module.ts @@ -17,6 +17,7 @@ import localeRu from '@angular/common/locales/ru'; import localeNl from '@angular/common/locales/nl'; import localeSv from '@angular/common/locales/sv'; import localeHu from '@angular/common/locales/hu'; +import localeKo from '@angular/common/locales/ko'; import { APP_INITIALIZER, NgModule } from '@angular/core'; import { MatNativeDateModule } from '@angular/material/core'; import { MatDialogModule } from '@angular/material/dialog'; @@ -108,6 +109,8 @@ registerLocaleData(localeSv); i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/sv.json')); registerLocaleData(localeHu); i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/hu.json')); +registerLocaleData(localeKo); +i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/ko.json')); export class WebpackTranslateLoader implements TranslateLoader { getTranslation(lang: string): Observable { diff --git a/console/src/app/modules/paginator/paginator.component.html b/console/src/app/modules/paginator/paginator.component.html index 9138038437..e74a3e3881 100644 --- a/console/src/app/modules/paginator/paginator.component.html +++ b/console/src/app/modules/paginator/paginator.component.html @@ -8,12 +8,10 @@

- {{ pageIndex * pageSize }} - {{ pageIndex * pageSize + pageSize }} - + {{ startIndex }} - {{ endIndex }}
- + {{ sizeOption }} diff --git a/console/src/app/modules/paginator/paginator.component.ts b/console/src/app/modules/paginator/paginator.component.ts index b4b406acda..4fd62fb568 100644 --- a/console/src/app/modules/paginator/paginator.component.ts +++ b/console/src/app/modules/paginator/paginator.component.ts @@ -50,6 +50,15 @@ export class PaginatorComponent { return temp <= this.length / this.pageSize; } + get startIndex(): number { + return this.pageIndex * this.pageSize; + } + + get endIndex(): number { + const max = this.startIndex + this.pageSize; + return this.length < max ? this.length : max; + } + public emitChange(): void { this.page.emit({ length: this.length, @@ -58,4 +67,10 @@ export class PaginatorComponent { pageSizeOptions: this.pageSizeOptions, }); } + + public updatePageSize(newSize: number): void { + this.pageSize = newSize; + this.pageIndex = 0; + this.emitChange(); + } } diff --git a/console/src/app/modules/project-role-detail-dialog/project-role-detail-dialog.component.ts b/console/src/app/modules/project-role-detail-dialog/project-role-detail-dialog.component.ts index 24c7002778..f62ccc54a1 100644 --- a/console/src/app/modules/project-role-detail-dialog/project-role-detail-dialog.component.ts +++ b/console/src/app/modules/project-role-detail-dialog/project-role-detail-dialog.component.ts @@ -31,9 +31,9 @@ export class ProjectRoleDetailDialogComponent { } submitForm(): void { - if (this.formGroup.valid && this.key?.value && this.group?.value && this.displayName?.value) { + if (this.formGroup.valid && this.key?.value && this.displayName?.value) { this.mgmtService - .updateProjectRole(this.projectId, this.key.value, this.displayName.value, this.group.value) + .updateProjectRole(this.projectId, this.key.value, this.displayName.value, this.group?.value) .then(() => { this.toast.showInfo('PROJECT.TOAST.ROLECHANGED', true); this.dialogRef.close(true); diff --git a/console/src/app/modules/project-roles-table/project-roles-table.component.html b/console/src/app/modules/project-roles-table/project-roles-table.component.html index 851e6848aa..48ad2851bb 100644 --- a/console/src/app/modules/project-roles-table/project-roles-table.component.html +++ b/console/src/app/modules/project-roles-table/project-roles-table.component.html @@ -35,8 +35,8 @@ [disabled]="disabled" color="primary" (change)="$event ? masterToggle() : null" - [checked]="selection.hasValue() && isAllSelected()" - [indeterminate]="selection.hasValue() && !isAllSelected()" + [checked]="isAnySelected() && isAllSelected()" + [indeterminate]="isAnySelected() && !isAllSelected()" >
@@ -76,7 +76,7 @@ class="role state" [ngClass]="{ 'no-selection': !selectionAllowed }" *ngIf="role.group" - (click)="selectionAllowed ? selectAllOfGroup(role.group) : openDetailDialog(role)" + (click)="selectionAllowed ? groupMasterToggle(role.group) : openDetailDialog(role)" [matTooltip]="selectionAllowed ? ('PROJECT.ROLE.SELECTGROUPTOOLTIP' | translate: role) : null" >{{ role.group }} @@ -135,7 +135,7 @@ #paginator [timestamp]="dataSource.viewTimestamp" [length]="dataSource.totalResult" - [pageSize]="50" + [pageSize]="INITIAL_PAGE_SIZE" (page)="changePage()" [pageSizeOptions]="[25, 50, 100, 250]" > diff --git a/console/src/app/modules/project-roles-table/project-roles-table.component.ts b/console/src/app/modules/project-roles-table/project-roles-table.component.ts index ae8633d15d..2159782411 100644 --- a/console/src/app/modules/project-roles-table/project-roles-table.component.ts +++ b/console/src/app/modules/project-roles-table/project-roles-table.component.ts @@ -18,6 +18,7 @@ import { ProjectRolesDataSource } from './project-roles-table-datasource'; styleUrls: ['./project-roles-table.component.scss'], }) export class ProjectRolesTableComponent implements OnInit { + public INITIAL_PAGE_SIZE: number = 50; @Input() public projectId: string = ''; @Input() public grantId: string = ''; @Input() public disabled: boolean = false; @@ -43,41 +44,58 @@ export class ProjectRolesTableComponent implements OnInit { } public ngOnInit(): void { - this.dataSource.loadRoles(this.projectId, this.grantId, 0, 25, 'asc'); - - this.dataSource.rolesSubject.subscribe((roles) => { - const selectedRoles: Role.AsObject[] = roles.filter((role) => this.selectedKeys.includes(role.key)); - this.selection.select(...selectedRoles.map((r) => r.key)); - }); + this.loadRolesPage(); + this.selection.select(...this.selectedKeys); this.selection.changed.subscribe(() => { this.changedSelection.emit(this.selection.selected); }); } - public selectAllOfGroup(group: string): void { - const groupRoles: Role.AsObject[] = this.dataSource.rolesSubject.getValue().filter((role) => role.group === group); - this.selection.select(...groupRoles.map((r) => r.key)); - } - private loadRolesPage(): void { - this.dataSource.loadRoles(this.projectId, this.grantId, this.paginator?.pageIndex ?? 0, this.paginator?.pageSize ?? 25); + this.dataSource.loadRoles( + this.projectId, + this.grantId, + this.paginator?.pageIndex ?? 0, + this.paginator?.pageSize ?? this.INITIAL_PAGE_SIZE, + ); } public changePage(): void { this.loadRolesPage(); } - public isAllSelected(): boolean { - const numSelected = this.selection.selected.length; - const numRows = this.dataSource.totalResult; - return numSelected === numRows; + private listIsAllSelected(list: string[]): boolean { + return list.findIndex((key) => !this.selection.isSelected(key)) == -1; + } + + private listIsAnySelected(list: string[]): boolean { + return list.findIndex((key) => this.selection.isSelected(key)) != -1; + } + + private listMasterToggle(list: string[]): void { + if (this.listIsAllSelected(list)) this.selection.deselect(...list); + else this.selection.select(...list); + } + + private compilePageKeys(): string[] { + return this.dataSource.rolesSubject.value.map((role) => role.key); } public masterToggle(): void { - this.isAllSelected() - ? this.selection.clear() - : this.dataSource.rolesSubject.value.forEach((row: Role.AsObject) => this.selection.select(row.key)); + this.listMasterToggle(this.compilePageKeys()); + } + + public isAllSelected(): boolean { + return this.listIsAllSelected(this.compilePageKeys()); + } + + public isAnySelected(): boolean { + return this.listIsAnySelected(this.compilePageKeys()); + } + + public groupMasterToggle(group: string): void { + this.listMasterToggle(this.dataSource.rolesSubject.value.filter((role) => role.group == group).map((role) => role.key)); } public deleteRole(role: Role.AsObject): void { @@ -93,45 +111,28 @@ export class ProjectRolesTableComponent implements OnInit { dialogRef.afterClosed().subscribe((resp) => { if (resp) { - const index = this.dataSource.rolesSubject.value.findIndex((iter) => iter.key === role.key); - this.mgmtService.removeProjectRole(this.projectId, role.key).then(() => { this.toast.showInfo('PROJECT.TOAST.ROLEREMOVED', true); - - if (index > -1) { - this.dataSource.rolesSubject.value.splice(index, 1); - this.dataSource.rolesSubject.next(this.dataSource.rolesSubject.value); - } + this.loadRolesPage(); }); } }); } - public removeRole(role: Role.AsObject, index: number): void { - this.mgmtService - .removeProjectRole(this.projectId, role.key) - .then(() => { - this.toast.showInfo('PROJECT.TOAST.ROLEREMOVED', true); - this.dataSource.rolesSubject.value.splice(index, 1); - this.dataSource.rolesSubject.next(this.dataSource.rolesSubject.value); - }) - .catch((error) => { - this.toast.showError(error); - }); - } - public openDetailDialog(role: Role.AsObject): void { - this.dialog.open(ProjectRoleDetailDialogComponent, { + const dialogRef = this.dialog.open(ProjectRoleDetailDialogComponent, { data: { role, projectId: this.projectId, }, width: '400px', }); + + dialogRef.afterClosed().subscribe(() => this.loadRolesPage()); } public refreshPage(): void { - this.dataSource.loadRoles(this.projectId, this.grantId, this.paginator?.pageIndex ?? 0, this.paginator?.pageSize ?? 25); + this.loadRolesPage(); } public get selectionAllowed(): boolean { diff --git a/console/src/app/utils/language.ts b/console/src/app/utils/language.ts index f17893372c..4ef63dcb28 100644 --- a/console/src/app/utils/language.ts +++ b/console/src/app/utils/language.ts @@ -16,6 +16,7 @@ export const supportedLanguages = [ 'nl', 'sv', 'hu', + 'ko', ]; -export const supportedLanguagesRegexp: RegExp = /de|en|es|fr|id|it|ja|pl|zh|bg|pt|mk|cs|ru|nl|sv|hu/; +export const supportedLanguagesRegexp: RegExp = /de|en|es|fr|id|it|ja|pl|zh|bg|pt|mk|cs|ru|nl|sv|hu|ko/; export const fallbackLanguage: string = 'en'; diff --git a/console/src/assets/i18n/bg.json b/console/src/assets/i18n/bg.json index d47d411e07..9402ae5bb2 100644 --- a/console/src/assets/i18n/bg.json +++ b/console/src/assets/i18n/bg.json @@ -1383,7 +1383,8 @@ "nl": "Nederlands", "sv": "Svenska", "id": "Bahasa Indonesia", - "hu": "Magyar" + "hu": "Magyar", + "ko": "ํ•œ๊ตญ์–ด" } }, "SMTP": { @@ -1620,7 +1621,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "KEYS": { "emailVerificationDoneText": "ะŸั€ะพะฒะตั€ะบะฐั‚ะฐ ะฝะฐ ะธะผะตะนะป ะต ะธะทะฒัŠั€ัˆะตะฝะฐ", @@ -1720,8 +1722,8 @@ "ALLOWUSERNAMEPASSWORD_DESC": "ะ ะฐะทั€ะตัˆะตะฝะพ ะต ะบะพะฝะฒะตะฝั†ะธะพะฝะฐะปะฝะพั‚ะพ ะฒะปะธะทะฐะฝะต ั ะฟะพั‚ั€ะตะฑะธั‚ะตะปัะบะพ ะธะผะต ะธ ะฟะฐั€ะพะปะฐ.", "ALLOWEXTERNALIDP_DESC": "ะ’ั…ะพะดัŠั‚ ะต ั€ะฐะทั€ะตัˆะตะฝ ะทะฐ ะพัะฝะพะฒะฝะธั‚ะต ะดะพัั‚ะฐะฒั‡ะธั†ะธ ะฝะฐ ัะฐะผะพะปะธั‡ะฝะพัั‚", "ALLOWREGISTER_DESC": "ะะบะพ ะพะฟั†ะธัั‚ะฐ ะต ะธะทะฑั€ะฐะฝะฐ, ะฒ ะฒั…ะพะดะฐ ัะต ะฟะพัะฒัะฒะฐ ะดะพะฟัŠะปะฝะธั‚ะตะปะฝะฐ ัั‚ัŠะฟะบะฐ ะทะฐ ั€ะตะณะธัั‚ั€ะฐั†ะธั ะฝะฐ ะฟะพั‚ั€ะตะฑะธั‚ะตะป.", - "FORCEMFA": "ะกะธะปะฐ MFA", - "FORCEMFALOCALONLY": "ะŸั€ะธะฝัƒะดะธั‚ะตะปะฝะพ MFA ะทะฐ ะปะพะบะฐะปะฝะธ ะฟะพั‚ั€ะตะฑะธั‚ะตะปะธ", + "FORCEMFA": "ะะฐะปะพะถะธ MFA ะทะฐ ะฒัะธั‡ะบะธ ะฟะพั‚ั€ะตะฑะธั‚ะตะปะธ", + "FORCEMFALOCALONLY": "ะะฐะปะพะถะธ MFA ัะฐะผะพ ะทะฐ ะปะพะบะฐะปะฝะพ ะฐะฒั‚ะตะฝั‚ะธั„ะธั†ะธั€ะฐะฝะธ ะฟะพั‚ั€ะตะฑะธั‚ะตะปะธ", "FORCEMFALOCALONLY_DESC": "ะะบะพ ะต ะธะทะฑั€ะฐะฝะฐ ะพะฟั†ะธัั‚ะฐ, ะปะพะบะฐะปะฝะธั‚ะต ัƒะดะพัั‚ะพะฒะตั€ะตะฝะธ ะฟะพั‚ั€ะตะฑะธั‚ะตะปะธ ั‚ั€ัะฑะฒะฐ ะดะฐ ะบะพะฝั„ะธะณัƒั€ะธั€ะฐั‚ ะฒั‚ะพั€ะธ ั„ะฐะบั‚ะพั€ ะทะฐ ะฒะปะธะทะฐะฝะต.", "HIDEPASSWORDRESET_DESC": "ะะบะพ ะพะฟั†ะธัั‚ะฐ ะต ะธะทะฑั€ะฐะฝะฐ, ะฟะพั‚ั€ะตะฑะธั‚ะตะปัั‚ ะฝะต ะผะพะถะต ะดะฐ ะฝัƒะปะธั€ะฐ ะฟะฐั€ะพะปะฐั‚ะฐ ัะธ ะฒ ะฟั€ะพั†ะตัะฐ ะฝะฐ ะฒะปะธะทะฐะฝะต.", "HIDELOGINNAMESUFFIX": "ะกะบั€ะธะฒะฐะฝะต ะฝะฐ ััƒั„ะธะบัะฐ ะฝะฐ ะธะผะตั‚ะพ ะทะฐ ะฒะปะธะทะฐะฝะต", @@ -2559,7 +2561,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "MEMBER": { "ADD": "ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะผะตะฝะธะดะถัŠั€", diff --git a/console/src/assets/i18n/cs.json b/console/src/assets/i18n/cs.json index f251da7ab5..2f34468cd2 100644 --- a/console/src/assets/i18n/cs.json +++ b/console/src/assets/i18n/cs.json @@ -1384,7 +1384,8 @@ "nl": "Nederlands", "sv": "Svenska", "id": "Bahasa Indonesia", - "hu": "Magyar" + "hu": "Magyar", + "ko": "ํ•œ๊ตญ์–ด" } }, "SMTP": { @@ -1621,7 +1622,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "KEYS": { "emailVerificationDoneText": "Ovฤ›ล™enรญ e-mailu dokonฤeno", @@ -1722,8 +1724,8 @@ "ALLOWUSERNAMEPASSWORD_DESC": "Je povoleno klasickรฉ pล™ihlรกลกenรญ s uลพivatelskรฝm jmรฉnem a heslem.", "ALLOWEXTERNALIDP_DESC": "Pล™ihlรกลกenรญ je povoleno pro nรญลพe uvedenรฉ poskytovatele identity.", "ALLOWREGISTER_DESC": "Pokud je moลพnost vybrรกna, objevรญ se pล™i pล™ihlรกลกenรญ dalลกรญ krok pro registraci uลพivatele.", - "FORCEMFA": "Vynutit MFA", - "FORCEMFALOCALONLY": "Vynutit MFA pouze pro lokรกlnฤ› ovฤ›ล™enรฉ uลพivatele", + "FORCEMFA": "Vynuti MFA pro vลกechny uลพivatele", + "FORCEMFALOCALONLY": "Vynutit MFA pouze pro mรญstnฤ› ovฤ›ล™enรฉ uลพivatele", "FORCEMFALOCALONLY_DESC": "Pokud je moลพnost vybrรกna, lokรกlnฤ› ovฤ›ล™enรญ uลพivatelรฉ musรญ pro pล™ihlรกลกenรญ nastavit druhรฝ faktor.", "HIDEPASSWORDRESET_DESC": "Pokud je moลพnost vybrรกna, uลพivatel nemลฏลพe bฤ›hem pล™ihlaลกovacรญho procesu resetovat svรฉ heslo.", "HIDELOGINNAMESUFFIX": "Skrรฝt pล™รญponu pล™ihlaลกovacรญho jmรฉna", @@ -2572,7 +2574,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "MEMBER": { "ADD": "Pล™idat manaลพera", diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index 3659848bc7..4adf55be3e 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -1384,7 +1384,8 @@ "nl": "Nederlands", "sv": "Svenska", "id": "Bahasa Indonesia", - "hu": "Magyar" + "hu": "Magyar", + "ko": "ํ•œ๊ตญ์–ด" } }, "SMTP": { @@ -1621,7 +1622,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "KEYS": { "emailVerificationDoneText": "Email Verification erfolgreich", @@ -1721,8 +1723,8 @@ "ALLOWUSERNAMEPASSWORD_DESC": "Der konventionelle Login mit Benutzername und Passwort wird erlaubt.", "ALLOWEXTERNALIDP_DESC": "Der Login wird fรผr die darunter liegenden Identitรคtsanbieter erlaubt.", "ALLOWREGISTER_DESC": "Ist die Option gewรคhlt, erscheint im Login ein zusรคtzlicher Schritt zum Registrieren eines Benutzers.", - "FORCEMFA": "MFA erzwingen", - "FORCEMFALOCALONLY": "MFA fรผr lokale Benutzer erzwingen", + "FORCEMFA": "MFA fรผr alle Benutzer erzwingen", + "FORCEMFALOCALONLY": "MFA nur fรผr lokal authentifizierte Benutzer erzwingen", "FORCEMFALOCALONLY_DESC": "Ist die Option gewรคhlt, mรผssen lokal authentifizierte Benutzer einen zweiten Faktor fรผr den Login verwenden.", "HIDEPASSWORDRESET_DESC": "Ist die Option gewรคhlt, ist es nicht mรถglich im Login das Passwort zurรผck zusetzen via Passwort vergessen Link.", "HIDELOGINNAMESUFFIX": "Loginname Suffix ausblenden", @@ -2563,7 +2565,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "MEMBER": { "ADD": "Manager hinzufรผgen", diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index c6fe05499d..4e7d2e13d9 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -1384,7 +1384,8 @@ "nl": "Nederlands", "sv": "Svenska", "id": "Bahasa Indonesia", - "hu": "Magyar" + "hu": "Magyar", + "ko": "ํ•œ๊ตญ์–ด" } }, "SMTP": { @@ -1621,7 +1622,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "KEYS": { "emailVerificationDoneText": "Email verification done", @@ -1721,8 +1723,8 @@ "ALLOWUSERNAMEPASSWORD_DESC": "The conventional login with user name and password is allowed.", "ALLOWEXTERNALIDP_DESC": "The login is allowed for the underlying identity providers", "ALLOWREGISTER_DESC": "If the option is selected, an additional step for registering a user appears in the login.", - "FORCEMFA": "Force MFA", - "FORCEMFALOCALONLY": "Force MFA for local authenticated users", + "FORCEMFA": "Force MFA for all users", + "FORCEMFALOCALONLY": "Force MFA for local authenticated users only", "FORCEMFALOCALONLY_DESC": "If the option is selected, local authenticated users have to configure a second factor for login.", "HIDEPASSWORDRESET_DESC": "If the option is selected, the user can't reset his password in the login process.", "HIDELOGINNAMESUFFIX": "Hide Loginname suffix", @@ -2588,7 +2590,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "MEMBER": { "ADD": "Add a Manager", diff --git a/console/src/assets/i18n/es.json b/console/src/assets/i18n/es.json index 9565c034b7..06532cc849 100644 --- a/console/src/assets/i18n/es.json +++ b/console/src/assets/i18n/es.json @@ -1385,7 +1385,8 @@ "nl": "Nederlands", "sv": "Svenska", "id": "Bahasa Indonesia", - "hu": "Magyar" + "hu": "Magyar", + "ko": "ํ•œ๊ตญ์–ด" } }, "SMTP": { @@ -1622,7 +1623,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "KEYS": { "emailVerificationDoneText": "Verificaciรณn de email realizada", @@ -1722,8 +1724,8 @@ "ALLOWUSERNAMEPASSWORD_DESC": "El inicio de sesiรณn convencional con nombre de usuario y contraseรฑa estรก permitido.", "ALLOWEXTERNALIDP_DESC": "El inicio de sesiรณn estรก permitido para los proveedores de identidad subyacentes", "ALLOWREGISTER_DESC": "Si esta opciรณn es seleccionada, aparece un paso adicional durante el inicio de sesiรณn para registrar un usuario.", - "FORCEMFA": "Forzar MFA", - "FORCEMFALOCALONLY": "Forzar MFA para usuarios locales", + "FORCEMFA": "Forzar MFA para todos los usuarios", + "FORCEMFALOCALONLY": "Forzar MFA solo para usuarios autenticados localmente", "FORCEMFALOCALONLY_DESC": "Si esta opciรณn es seleccionada, los usuarios autenticados localmente tendrรกn que configurar un doble factor para iniciar sesiรณn", "HIDEPASSWORDRESET_DESC": "Si esta opciรณn es seleccionada, el usuario no podrรก restablecer su contraseรฑa en el proceso de inicio de sesiรณn.", "HIDELOGINNAMESUFFIX": "Ocultar sufijo del nombre de inicio de sesiรณn", @@ -2560,7 +2562,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "MEMBER": { "ADD": "Aรฑadir un Mรกnager", diff --git a/console/src/assets/i18n/fr.json b/console/src/assets/i18n/fr.json index 5a41984c7e..2814abdc97 100644 --- a/console/src/assets/i18n/fr.json +++ b/console/src/assets/i18n/fr.json @@ -1384,7 +1384,8 @@ "nl": "Nederlands", "sv": "Svenska", "id": "Bahasa Indonesia", - "hu": "Magyar" + "hu": "Magyar", + "ko": "ํ•œ๊ตญ์–ด" } }, "SMTP": { @@ -1621,7 +1622,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "KEYS": { "emailVerificationDoneText": "Vรฉrification de l'e-mail effectuรฉe", @@ -1721,8 +1723,8 @@ "ALLOWUSERNAMEPASSWORD_DESC": "La connexion classique avec nom d'utilisateur et mot de passe est autorisรฉe.", "ALLOWEXTERNALIDP_DESC": "La connexion est autorisรฉe pour les fournisseurs d'identitรฉ sous-jacents", "ALLOWREGISTER_DESC": "Si l'option est sรฉlectionnรฉe, une รฉtape supplรฉmentaire pour l'enregistrement d'un utilisateur apparaรฎt dans la connexion.", - "FORCEMFA": "Forcer MFA", - "FORCEMFALOCALONLY": "Forcer MFA pour les utilisateurs locaux", + "FORCEMFA": "Forcer MFA pour tous les utilisateurs", + "FORCEMFALOCALONLY": "Forcer MFA uniquement pour les utilisateurs authentifiรฉs localement", "FORCEMFALOCALONLY_DESC": "Si l'option est sรฉlectionnรฉe, les utilisateurs locaux authentifiรฉs doivent configurer un deuxiรจme facteur pour la connexion.", "HIDEPASSWORDRESET_DESC": "Si l'option est sรฉlectionnรฉe, l'utilisateur ne peut pas rรฉinitialiser son mot de passe lors du processus de connexion.", "HIDELOGINNAMESUFFIX": "Masquer le suffixe du nom de connexion", @@ -2564,7 +2566,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "MEMBER": { "ADD": "Ajouter un responsable", diff --git a/console/src/assets/i18n/hu.json b/console/src/assets/i18n/hu.json index 614af5520e..0ffa6b92b6 100644 --- a/console/src/assets/i18n/hu.json +++ b/console/src/assets/i18n/hu.json @@ -1384,7 +1384,8 @@ "nl": "Holland", "sv": "Svรฉd", "id": "Indonรฉz", - "hu": "Magyar" + "hu": "Magyar", + "ko": "ํ•œ๊ตญ์–ด" } }, "SMTP": { @@ -1619,7 +1620,8 @@ "ru": "Orosz", "nl": "Holland", "sv": "Svรฉd", - "id": "Indonรฉz" + "id": "Indonรฉz", + "ko": "ํ•œ๊ตญ์–ด" }, "KEYS": { "emailVerificationDoneText": "E-mail ellenล‘rzรฉs kรฉsz", @@ -1719,8 +1721,8 @@ "ALLOWUSERNAMEPASSWORD_DESC": "A hagyomรกnyos bejelentkezรฉs felhasznรกlรณnรฉvvel รฉs jelszรณval engedรฉlyezett.", "ALLOWEXTERNALIDP_DESC": "A bejelentkezรฉs engedรฉlyezett az alapul szolgรกlรณ identitรกsszolgรกltatรณknรกl", "ALLOWREGISTER_DESC": "Ha ezt az opciรณt vรกlasztod, egy tovรกbbi lรฉpรฉs jelenik meg a bejelentkezรฉs sorรกn a felhasznรกlรณi regisztrรกciรณhoz.", - "FORCEMFA": "MFA kรฉnyszerรญtรฉse", - "FORCEMFALOCALONLY": "Kรฉnyszerรญtsd az MFA-t a helyileg hitelesรญtett felhasznรกlรณkra", + "FORCEMFA": "MFA kikรฉnyszerรญtรฉse minden felhasznรกlรณ szรกmรกra", + "FORCEMFALOCALONLY": "MFA kikรฉnyszerรญtรฉse csak helyi hitelesรญtett felhasznรกlรณk szรกmรกra", "FORCEMFALOCALONLY_DESC": "Ha ezt az opciรณt vรกlasztod, a helyileg hitelesรญtett felhasznรกlรณknak be kell รกllรญtaniuk egy mรกsodik faktor a bejelentkezรฉshez.", "HIDEPASSWORDRESET_DESC": "Ha ezt az opciรณt vรกlasztod, a felhasznรกlรณ nem tudja visszaรกllรญtani a jelszavรกt a bejelentkezรฉsi folyamat sorรกn.", "HIDELOGINNAMESUFFIX": "Bejelentkezรฉsi nรฉv utรณtag elrejtรฉse", diff --git a/console/src/assets/i18n/id.json b/console/src/assets/i18n/id.json index 76fe69b507..c12fbbe555 100644 --- a/console/src/assets/i18n/id.json +++ b/console/src/assets/i18n/id.json @@ -1262,7 +1262,8 @@ "nl": "Nederlands", "sv": "Svenska", "id": "Bahasa Indonesia", - "hu": "Magyar" + "hu": "Magyar", + "ko": "ํ•œ๊ตญ์–ด" } }, "SMTP": { @@ -1486,7 +1487,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "KEYS": { "emailVerificationDoneText": "Verifikasi email selesai", @@ -1585,8 +1587,8 @@ "ALLOWUSERNAMEPASSWORD_DESC": "Login konvensional dengan nama pengguna dan kata sandi diperbolehkan.", "ALLOWEXTERNALIDP_DESC": "Login diperbolehkan untuk penyedia identitas yang mendasarinya", "ALLOWREGISTER_DESC": "Jika opsi ini dipilih, langkah tambahan untuk mendaftarkan pengguna akan muncul di login.", - "FORCEMFA": "Paksa MFA", - "FORCEMFALOCALONLY": "Paksa MFA untuk pengguna lokal yang diautentikasi", + "FORCEMFA": "Memaksa MFA untuk semua pengguna", + "FORCEMFALOCALONLY": "Memaksa MFA hanya untuk pengguna yang diautentikasi lokal", "FORCEMFALOCALONLY_DESC": "Jika opsi ini dipilih, pengguna yang diautentikasi lokal harus mengonfigurasi faktor kedua untuk login.", "HIDEPASSWORDRESET_DESC": "Jika opsi ini dipilih, pengguna tidak dapat mengatur ulang kata sandinya dalam proses login.", "HIDELOGINNAMESUFFIX": "Sembunyikan akhiran Nama Login", @@ -2272,7 +2274,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "MEMBER": { "ADD": "Tambahkan Manajer", diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json index a87cb2fc75..d21396991c 100644 --- a/console/src/assets/i18n/it.json +++ b/console/src/assets/i18n/it.json @@ -1384,7 +1384,8 @@ "nl": "Nederlands", "sv": "Svenska", "id": "Bahasa Indonesia", - "hu": "Magyar" + "hu": "Magyar", + "ko": "ํ•œ๊ตญ์–ด" } }, "SMTP": { @@ -1621,7 +1622,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "KEYS": { "emailVerificationDoneText": "Verifica dell'e-mail terminata con successo.", @@ -1721,8 +1723,8 @@ "ALLOWUSERNAMEPASSWORD_DESC": "Autenticazione classica con nome utente e password รจ permessa.", "ALLOWEXTERNALIDP_DESC": "Il login รจ permesso per gli IDP sottostanti", "ALLOWREGISTER_DESC": "Se l'opzione รจ selezionata, nel login apparirร  un passo aggiuntivo per la registrazione di un utente.", - "FORCEMFA": "Forza MFA", - "FORCEMFALOCALONLY": "Forza MFA per gli utenti locali", + "FORCEMFA": "Forzare MFA per tutti gli utenti", + "FORCEMFALOCALONLY": "Forzare MFA solo per gli utenti autenticati localmente", "FORCEMFALOCALONLY_DESC": "Se l'opzione รจ selezionata, gli utenti locali autenticati devono configurare un secondo fattore per l'accesso.", "HIDEPASSWORDRESET_DESC": "Se l'opzione รจ selezionata, l'utente non puรฒ resettare la sua password nel interfaccia login.", "HIDELOGINNAMESUFFIX": "Nascondi il suffisso del nome utente", @@ -2564,7 +2566,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "MEMBER": { "ADD": "Aggiungi un manager", diff --git a/console/src/assets/i18n/ja.json b/console/src/assets/i18n/ja.json index 4e7e401c7a..936262d132 100644 --- a/console/src/assets/i18n/ja.json +++ b/console/src/assets/i18n/ja.json @@ -1384,7 +1384,8 @@ "nl": "Nederlands", "sv": "Svenska", "id": "Bahasa Indonesia", - "hu": "Magyar" + "hu": "Magyar", + "ko": "ํ•œ๊ตญ์–ด" } }, "SMTP": { @@ -1617,7 +1618,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "KEYS": { "emailVerificationDoneText": "ใƒกใƒผใƒซ่ช่จผใŒๅฎŒไบ†ใ—ใพใ—ใŸ", @@ -1716,8 +1718,8 @@ "ALLOWUSERNAMEPASSWORD_DESC": "ใƒฆใƒผใ‚ถใƒผๅใจใƒ‘ใ‚นใƒฏใƒผใƒ‰ใ‚’ไฝฟ็”จใ—ใŸๅพ“ๆฅใฎใƒญใ‚ฐใ‚คใƒณใ‚’่จฑๅฏใ—ใพใ™ใ€‚", "ALLOWEXTERNALIDP_DESC": "ๅŸบ็คŽใจใชใ‚‹IDใƒ—ใƒญใƒใ‚คใƒ€ใƒผใซใƒญใ‚ฐใ‚คใƒณใ‚’่จฑๅฏใ—ใพใ™ใ€‚", "ALLOWREGISTER_DESC": "ใ“ใฎใ‚ชใƒ—ใ‚ทใƒงใƒณใŒ้ธๆŠžใ•ใ‚Œใฆใ„ใ‚‹ๅ ดๅˆใ€ใƒฆใƒผใ‚ถใƒผใ‚’็™ป้Œฒใ™ใ‚‹ใŸใ‚ใฎ่ฟฝๅŠ ใฎใ‚นใƒ†ใƒƒใƒ—ใŒใƒญใ‚ฐใ‚คใƒณใซ่กจ็คบใ•ใ‚Œใพใ™ใ€‚", - "FORCEMFA": "MFAใ‚’ๅผทๅˆถใ™ใ‚‹", - "FORCEMFALOCALONLY": "ใƒญใƒผใ‚ซใƒซ ใƒฆใƒผใ‚ถใƒผใซ MFA ใ‚’ๅผทๅˆถใ™ใ‚‹", + "FORCEMFA": "ใ™ในใฆใฎใƒฆใƒผใ‚ถใƒผใซ MFA ใ‚’ๅผทๅˆถใ™ใ‚‹", + "FORCEMFALOCALONLY": "ใƒญใƒผใ‚ซใƒซ่ช่จผใƒฆใƒผใ‚ถใƒผใฎใฟใซ MFA ใ‚’ๅผทๅˆถใ™ใ‚‹", "FORCEMFALOCALONLY_DESC": "ใ‚ชใƒ—ใ‚ทใƒงใƒณใŒ้ธๆŠžใ•ใ‚Œใฆใ„ใ‚‹ๅ ดๅˆใ€ใƒญใƒผใ‚ซใƒซ่ช่จผใ•ใ‚ŒใŸใƒฆใƒผใ‚ถใƒผใฏใƒญใ‚ฐใ‚คใƒณใฎ 2 ็•ช็›ฎใฎ่ฆ็ด ใ‚’ๆง‹ๆˆใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚", "HIDEPASSWORDRESET_DESC": "ใ“ใฎใ‚ชใƒ—ใ‚ทใƒงใƒณใŒ้ธๆŠžใ•ใ‚Œใฆใ„ใ‚‹ๅ ดๅˆใ€ใƒฆใƒผใ‚ถใƒผใฏใƒญใ‚ฐใ‚คใƒณ้Ž็จ‹ใงใงใƒ‘ใ‚นใƒฏใƒผใƒ‰ใ‚’ใƒชใ‚ปใƒƒใƒˆใงใใพใ›ใ‚“ใ€‚", "HIDELOGINNAMESUFFIX": "ใƒญใ‚ฐใ‚คใƒณๅใฎๆŽฅๅฐพ่พžใ‚’้ž่กจ็คบใซใ™ใ‚‹", @@ -2554,7 +2556,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "MEMBER": { "ADD": "ใƒžใƒใƒผใ‚ธใƒฃใƒผใ‚’่ฟฝๅŠ ใ™ใ‚‹", diff --git a/console/src/assets/i18n/ko.json b/console/src/assets/i18n/ko.json new file mode 100644 index 0000000000..79fe95324a --- /dev/null +++ b/console/src/assets/i18n/ko.json @@ -0,0 +1,2671 @@ +{ + "APP_NAME": "ZITADEL", + "DESCRIPTIONS": { + "METADATA_TITLE": "๋ฉ”ํƒ€๋ฐ์ดํ„ฐ", + "HOME": { + "TITLE": "ZITADEL ์‹œ์ž‘ํ•˜๊ธฐ", + "NEXT": { + "TITLE": "๋‹ค์Œ ๋‹จ๊ณ„", + "DESCRIPTION": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ณด์•ˆ์„ ์œ„ํ•ด ๋‹ค์Œ ๋‹จ๊ณ„๋ฅผ ์™„๋ฃŒํ•˜์„ธ์š”.", + "CREATE_PROJECT": { + "TITLE": "ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ", + "DESCRIPTION": "ํ”„๋กœ์ ํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์—ญํ• ๊ณผ ๊ถŒํ•œ์„ ์ •์˜ํ•˜์„ธ์š”." + } + }, + "MORE_SHORTCUTS": { + "GET_STARTED": { + "TITLE": "์‹œ์ž‘ํ•˜๊ธฐ", + "DESCRIPTION": "๋น ๋ฅธ ์‹œ์ž‘ ๊ฐ€์ด๋“œ๋ฅผ ๋‹จ๊ณ„๋ณ„๋กœ ๋”ฐ๋ผํ•˜์—ฌ ๋ฐ”๋กœ ์‹œ์ž‘ํ•˜์„ธ์š”." + }, + "DOCS": { + "TITLE": "๋ฌธ์„œ", + "DESCRIPTION": "ZITADEL์˜ ์ง€์‹ ๋ฒ ์ด์Šค๋ฅผ ํƒ์ƒ‰ํ•˜์—ฌ ํ•ต์‹ฌ ๊ฐœ๋…๊ณผ ์•„์ด๋””์–ด์— ๋Œ€ํ•ด ๋ฐฐ์šฐ์„ธ์š”. ZITADEL์˜ ์ž‘๋™ ๋ฐฉ์‹๊ณผ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์„ ํ•™์Šตํ•˜์„ธ์š”." + }, + "EXAMPLES": { + "TITLE": "์˜ˆ์ œ ๋ฐ ์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ ํ‚คํŠธ", + "DESCRIPTION": "์˜ˆ์ œ์™€ SDK๋ฅผ ํ†ตํ•ด ZITADEL์„ ์„ ํ˜ธํ•˜๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด ๋ฐ ๋„๊ตฌ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ™•์ธํ•˜์„ธ์š”." + } + } + }, + "ORG": { + "TITLE": "์กฐ์ง", + "DESCRIPTION": "์กฐ์ง์€ ์‚ฌ์šฉ์ž, ์•ฑ์ด ํฌํ•จ๋œ ํ”„๋กœ์ ํŠธ, ID ์ œ๊ณต์ž, ํšŒ์‚ฌ ๋ธŒ๋žœ๋”ฉ๊ณผ ๊ฐ™์€ ์„ค์ •์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ์กฐ์ง ๊ฐ„์— ์„ค์ •์„ ๊ณต์œ ํ•˜๊ณ  ์‹ถ์œผ์‹ ๊ฐ€์š”? ๊ธฐ๋ณธ ์„ค์ •์„ ๊ตฌ์„ฑํ•˜์„ธ์š”.", + "METADATA": "์กฐ์ง์— ์œ„์น˜๋‚˜ ๋‹ค๋ฅธ ์‹œ์Šคํ…œ์˜ ์‹๋ณ„์ž์™€ ๊ฐ™์€ ๋งž์ถค ์†์„ฑ์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”. ์ด ์ •๋ณด๋ฅผ ์ž‘์—…์— ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "PROJECTS": { + "TITLE": "ํ”„๋กœ์ ํŠธ", + "DESCRIPTION": "ํ”„๋กœ์ ํŠธ๋Š” ํ•˜๋‚˜ ์ด์ƒ์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ˜ธ์ŠคํŒ…ํ•˜์—ฌ ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ํ”„๋กœ์ ํŠธ๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์กฐ์ง์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ํ”„๋กœ์ ํŠธ์— ๋Œ€ํ•œ ์•ก์„ธ์Šค๋ฅผ ๋ถ€์—ฌํ•˜์„ธ์š”.

ํ”„๋กœ์ ํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ ํ”„๋กœ์ ํŠธ ์†Œ์œ ์ž ๋˜๋Š” ๊ด€๋ จ ๊ถŒํ•œ์ด ์žˆ๋Š” ์‚ฌ๋žŒ์—๊ฒŒ ๋ฌธ์˜ํ•˜์—ฌ ์•ก์„ธ์Šค๋ฅผ ์–ป์œผ์„ธ์š”.", + "OWNED": { + "TITLE": "์†Œ์œ ํ•œ ํ”„๋กœ์ ํŠธ", + "DESCRIPTION": "์ด ํ”„๋กœ์ ํŠธ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์†Œ์œ ํ•œ ํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ ์„ค์ •, ๊ถŒํ•œ ๋ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "GRANTED": { + "TITLE": "๋ถ€์—ฌ๋œ ํ”„๋กœ์ ํŠธ", + "DESCRIPTION": "๋‹ค๋ฅธ ์กฐ์ง์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ๋ถ€์—ฌ๋ฐ›์€ ํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค. ๋ถ€์—ฌ๋œ ํ”„๋กœ์ ํŠธ๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค๋ฅธ ์กฐ์ง์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + } + }, + "USERS": { + "TITLE": "์‚ฌ์šฉ์ž", + "DESCRIPTION": "์‚ฌ์šฉ์ž๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๋Š” ์‚ฌ๋žŒ ๋˜๋Š” ์žฅ์น˜์ž…๋‹ˆ๋‹ค.", + "HUMANS": { + "TITLE": "์‚ฌ์šฉ์ž", + "DESCRIPTION": "์‚ฌ์šฉ์ž๋Š” ๋กœ๊ทธ์ธ ํ”„๋กฌํ”„ํŠธ๋กœ ๋ธŒ๋ผ์šฐ์ € ์„ธ์…˜์—์„œ ์ƒํ˜ธ ์ธ์ฆ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.", + "METADATA": "๋ถ€์„œ์™€ ๊ฐ™์€ ์‚ฌ์šฉ์ž ์ •์˜ ์†์„ฑ์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ถ”๊ฐ€ํ•˜์„ธ์š”. ์ด ์ •๋ณด๋ฅผ ์ž‘์—…์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "MACHINES": { + "TITLE": "์„œ๋น„์Šค ์‚ฌ์šฉ์ž", + "DESCRIPTION": "์„œ๋น„์Šค ์‚ฌ์šฉ์ž๋Š” ๊ฐœ์ธ ํ‚ค๋กœ ์„œ๋ช…๋œ JWT bearer ํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋Œ€ํ™”ํ˜• ์ธ์ฆ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ๊ฐœ์ธ ์•ก์„ธ์Šค ํ† ํฐ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "METADATA": "์ธ์ฆ ์‹œ์Šคํ…œ๊ณผ ๊ฐ™์€ ์‚ฌ์šฉ์ž ์ •์˜ ์†์„ฑ์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ถ”๊ฐ€ํ•˜์„ธ์š”. ์ด ์ •๋ณด๋ฅผ ์ž‘์—…์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "SELF": { + "METADATA": "๋ถ€์„œ์™€ ๊ฐ™์€ ์‚ฌ์šฉ์ž ์ •์˜ ์†์„ฑ์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ถ”๊ฐ€ํ•˜์„ธ์š”. ์ด ์ •๋ณด๋ฅผ ์กฐ์ง์˜ ์ž‘์—…์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + } + }, + "AUTHORIZATIONS": { + "TITLE": "๊ถŒํ•œ", + "DESCRIPTION": "๊ถŒํ•œ์€ ์‚ฌ์šฉ์ž์˜ ํ”„๋กœ์ ํŠธ ์ ‘๊ทผ ๊ถŒํ•œ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ํ”„๋กœ์ ํŠธ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ณ , ํ”„๋กœ์ ํŠธ ๋‚ด์˜ ์—ญํ• ์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "ACTIONS": { + "TITLE": "์ž‘์—…", + "DESCRIPTION": "์‚ฌ์šฉ์ž๊ฐ€ ZITADEL์— ์ธ์ฆํ•  ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ์ด๋ฒคํŠธ์— ๋งž์ถฐ ์‚ฌ์šฉ์ž ์ •์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜์„ธ์š”. ํ”„๋กœ์„ธ์Šค๋ฅผ ์ž๋™ํ™”ํ•˜๊ณ , ์‚ฌ์šฉ์ž ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ฐ ํ† ํฐ์„ ๊ฐ•ํ™”ํ•˜๊ฑฐ๋‚˜ ์™ธ๋ถ€ ์‹œ์Šคํ…œ์— ์•Œ๋ฆผ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.", + "SCRIPTS": { + "TITLE": "์Šคํฌ๋ฆฝํŠธ", + "DESCRIPTION": "ํ•˜๋‚˜์˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ๋กœ ์—ฌ๋Ÿฌ ์ž‘์—… ํ”Œ๋กœ์šฐ์—์„œ ์†์‰ฝ๊ฒŒ ํ™œ์šฉํ•˜์„ธ์š”." + }, + "FLOWS": { + "TITLE": "ํ”Œ๋กœ์šฐ", + "DESCRIPTION": "์ธ์ฆ ํ”Œ๋กœ์šฐ๋ฅผ ์„ ํƒํ•˜๊ณ  ์ด ํ”Œ๋กœ์šฐ ๋‚ด์˜ ํŠน์ • ์ด๋ฒคํŠธ์—์„œ ์ž‘์—…์„ ํŠธ๋ฆฌ๊ฑฐํ•˜์„ธ์š”." + } + }, + "SETTINGS": { + "INSTANCE": { + "TITLE": "๊ธฐ๋ณธ ์„ค์ •", + "DESCRIPTION": "๋ชจ๋“  ์กฐ์ง์— ๋Œ€ํ•œ ๊ธฐ๋ณธ ์„ค์ •์ž…๋‹ˆ๋‹ค. ๊ถŒํ•œ์ด ์žˆ์œผ๋ฉด ์ผ๋ถ€ ์„ค์ •์„ ์กฐ์ง ์„ค์ •์—์„œ ์žฌ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "ORG": { + "TITLE": "์กฐ์ง ์„ค์ •", + "DESCRIPTION": "์กฐ์ง์˜ ์„ค์ •์„ ์‚ฌ์šฉ์ž ์ •์˜ํ•˜์„ธ์š”." + }, + "FEATURES": { + "TITLE": "๊ธฐ๋Šฅ ์„ค์ •", + "DESCRIPTION": "์ธ์Šคํ„ด์Šค์˜ ๊ธฐ๋Šฅ์„ ์ž ๊ธˆ ํ•ด์ œํ•˜์„ธ์š”." + }, + "IDPS": { + "TITLE": "ID ์ œ๊ณต์ž", + "DESCRIPTION": "์™ธ๋ถ€ ID ์ œ๊ณต์ž๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํ™œ์„ฑํ™”ํ•˜์„ธ์š”. ์•Œ๋ ค์ง„ ์ œ๊ณต์ž๋ฅผ ์„ ํƒํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ OIDC, OAuth ๋˜๋Š” SAML ํ˜ธํ™˜ ์ œ๊ณต์ž๋ฅผ ๊ตฌ์„ฑํ•˜์„ธ์š”. ๊ธฐ์กด JWT ํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ์—ฐํ•ฉ๋œ ID๋กœ ๊ตฌ์„ฑํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.", + "NEXT": "๋‹ค์Œ ๋‹จ๊ณ„๋Š”?", + "SAML": { + "TITLE": "SAML ID ์ œ๊ณต์ž ๊ตฌ์„ฑ", + "DESCRIPTION": "ZITADEL์ด ๊ตฌ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ SAML ID ์ œ๊ณต์ž์— ๋Œ€ํ•œ ์ผ๋ถ€ ์„ค์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ์ œ๊ณต์ž๋Š” ZITADEL ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ XML ์ „์ฒด๋ฅผ ์—…๋กœ๋“œํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ถ€ ์ œ๊ณต์ž๋Š” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ URL, Assertion Consumer Service (ACS) URL, ๋˜๋Š” Single Logout URL๊ณผ ๊ฐ™์€ ํŠน์ • URL๋งŒ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค." + }, + "CALLBACK": { + "TITLE": "{{ provider }} ID ์ œ๊ณต์ž ๊ตฌ์„ฑ", + "DESCRIPTION": "ZITADEL์„ ๊ตฌ์„ฑํ•˜๊ธฐ ์ „์—, ์ธ์ฆ ํ›„ ZITADEL๋กœ ๋ธŒ๋ผ์šฐ์ € ๋ฆฌ๋””๋ ‰์…˜์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ์ด URL์„ ID ์ œ๊ณต์ž์—๊ฒŒ ์ „๋‹ฌํ•˜์„ธ์š”." + }, + "JWT": { + "TITLE": "JWT๋ฅผ ์—ฐํ•ฉ๋œ ID๋กœ ์‚ฌ์šฉ", + "DESCRIPTION": "JWT ID ์ œ๊ณต์ž๋ฅผ ํ†ตํ•ด ๊ธฐ์กด JWT ํ† ํฐ์„ ์—ฐํ•ฉ๋œ ID๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฏธ JWT ๋ฐœ๊ธ‰์ž๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์ด ๊ธฐ๋Šฅ์ด ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. JWT IdP๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ZITADEL์—์„œ ์‚ฌ์šฉ์ž ์ƒ์„ฑ ๋ฐ ์—…๋ฐ์ดํŠธ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "LDAP": { + "TITLE": "LDAP ID ์ œ๊ณต์ž์— ZITADEL ์—ฐ๊ฒฐ ๊ตฌ์„ฑ", + "DESCRIPTION": "LDAP ์„œ๋ฒ„์˜ ์—ฐ๊ฒฐ ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๊ณ , LDAP ์†์„ฑ์„ ZITADEL ์†์„ฑ์— ๋งคํ•‘ํ•˜์„ธ์š”." + }, + "AUTOFILL": { + "TITLE": "์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ ์ž๋™ ์ž…๋ ฅ", + "DESCRIPTION": "์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด ์ž‘์—…์„ ์‚ฌ์šฉํ•˜์„ธ์š”. ID ์ œ๊ณต์ž์—์„œ ๊ฐ’์„ ๊ฐ€์ ธ์™€ ZITADEL์˜ ๋“ฑ๋ก ์–‘์‹์„ ๋ฏธ๋ฆฌ ์ฑ„์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "ACTIVATE": { + "TITLE": "ID ์ œ๊ณต์ž ํ™œ์„ฑํ™”", + "DESCRIPTION": "ID ์ œ๊ณต์ž๊ฐ€ ์•„์ง ํ™œ์„ฑํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ํ™œ์„ฑํ™”ํ•˜์„ธ์š”." + } + }, + "PW_COMPLEXITY": { + "TITLE": "๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก์„ฑ", + "DESCRIPTION": "์‚ฌ์šฉ์ž๊ฐ€ ๋ณต์žกํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ๊ทœ์น™์„ ์ •์˜ํ•˜์—ฌ ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•˜์„ธ์š”." + }, + "BRANDING": { + "TITLE": "๋ธŒ๋žœ๋”ฉ", + "DESCRIPTION": "๋กœ๊ทธ์ธ ํผ์˜ ์™ธ๊ด€์„ ์‚ฌ์šฉ์ž ์ •์˜ํ•˜์„ธ์š”. ์„ค์ •์„ ์™„๋ฃŒํ•œ ํ›„ ๊ตฌ์„ฑ ์ ์šฉ์„ ๊ธฐ์–ตํ•˜์„ธ์š”." + }, + "PRIVACY_POLICY": { + "TITLE": "์™ธ๋ถ€ ๋งํฌ", + "DESCRIPTION": "์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€์—์„œ ์ปค์Šคํ…€ ์™ธ๋ถ€ ๋ฆฌ์†Œ์Šค๋กœ ์•ˆ๋‚ด๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ๊ฐ€์ž… ์ „์— ์ด์šฉ ์•ฝ๊ด€๊ณผ ๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ๋ฐฉ์นจ์„ ์ˆ˜๋ฝํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ฌธ์„œ ๋งํฌ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜, ์ฝ˜์†”์—์„œ ๋ฌธ์„œ ๋ฒ„ํŠผ์„ ์ˆจ๊ธฐ๋ ค๋ฉด ๋นˆ ๋ฌธ์ž์—ด๋กœ ์„ค์ •ํ•˜์„ธ์š”. ์‚ฌ์šฉ์ž ์ •์˜ ์™ธ๋ถ€ ๋งํฌ์™€ ๊ทธ ๋งํฌ์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ฝ˜์†”์— ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜, ๋ฒ„ํŠผ์„ ์ˆจ๊ธฐ๋ ค๋ฉด ๋นˆ ๊ฐ’์œผ๋กœ ์„ค์ •ํ•˜์„ธ์š”." + }, + "SMTP_PROVIDER": { + "TITLE": "SMTP ์„ค์ •", + "DESCRIPTION": "์‚ฌ์šฉ์ž๊ฐ€ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ๋„๋ฉ”์ธ์„ ๋ฐœ์‹ ์ž ์ฃผ์†Œ๋กœ ์‚ฌ์šฉํ•˜๋Š” SMTP ์„œ๋ฒ„๋ฅผ ๊ตฌ์„ฑํ•˜์„ธ์š”." + }, + "SMS_PROVIDER": { + "TITLE": "SMS ์„ค์ •", + "DESCRIPTION": "ZITADEL์˜ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด Twilio๋ฅผ ์„ค์ •ํ•˜์—ฌ ์‚ฌ์šฉ์ž์—๊ฒŒ SMS ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด์„ธ์š”." + }, + "IAM_EVENTS": { + "TITLE": "์ด๋ฒคํŠธ", + "DESCRIPTION": "์ธ์Šคํ„ด์Šค์—์„œ ์ƒํƒœ ๋ณ€๊ฒฝ์„ ๋ณด์—ฌ์ฃผ๋Š” ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค. ๋””๋ฒ„๊น…์„ ์œ„ํ•ด ์‹œ๊ฐ„ ๋ฒ”์œ„๋ณ„๋กœ ํ•„ํ„ฐ๋งํ•˜๊ฑฐ๋‚˜ ๊ฐ์‚ฌ ๋ชฉ์ ์œผ๋กœ ํŠน์ • ์ง‘๊ณ„๋ฅผ ํ•„ํ„ฐ๋งํ•˜์„ธ์š”." + }, + "IAM_FAILED_EVENTS": { + "TITLE": "์‹คํŒจํ•œ ์ด๋ฒคํŠธ", + "DESCRIPTION": "์ธ์Šคํ„ด์Šค์—์„œ ์‹คํŒจํ•œ ๋ชจ๋“  ์ด๋ฒคํŠธ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ZITADEL์ด ์˜ˆ์ƒ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ, ์ด ๋ชฉ๋ก์„ ๋จผ์ € ํ™•์ธํ•˜์„ธ์š”." + }, + "IAM_VIEWS": { + "TITLE": "๋ทฐ", + "DESCRIPTION": "๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ทฐ์™€ ์ตœ๊ทผ ์ด๋ฒคํŠธ๊ฐ€ ์ฒ˜๋ฆฌ๋œ ์‹œ์ ์„ ๋ณด์—ฌ์ฃผ๋Š” ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ ๋ˆ„๋ฝ๋œ ๊ฒฝ์šฐ, ๋ทฐ๊ฐ€ ์ตœ์‹ ์ธ์ง€ ํ™•์ธํ•˜์„ธ์š”." + }, + "LANGUAGES": { + "TITLE": "์–ธ์–ด", + "DESCRIPTION": "๋กœ๊ทธ์ธ ํผ๊ณผ ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€๊ฐ€ ๋ฒˆ์—ญ๋˜๋Š” ์–ธ์–ด๋ฅผ ์ œํ•œํ•˜์„ธ์š”. ์ผ๋ถ€ ์–ธ์–ด๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด 'ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ์–ธ์–ด' ์„น์…˜์œผ๋กœ ๋“œ๋ž˜๊ทธํ•˜์„ธ์š”. ๊ธฐ๋ณธ ์–ธ์–ด๋กœ ํ—ˆ์šฉ๋œ ์–ธ์–ด๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์˜ ์„ ํ˜ธ ์–ธ์–ด๊ฐ€ ํ—ˆ์šฉ๋˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ ์–ธ์–ด๊ฐ€ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค." + }, + "SECRET_GENERATORS": { + "TITLE": "์‹œํฌ๋ฆฟ ์ƒ์„ฑ๊ธฐ", + "DESCRIPTION": "์‹œํฌ๋ฆฟ์˜ ๋ณต์žก์„ฑ๊ณผ ์ˆ˜๋ช…์„ ์ •์˜ํ•˜์„ธ์š”. ๋†’์€ ๋ณต์žก์„ฑ๊ณผ ๊ธด ์ˆ˜๋ช…์€ ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•˜๊ณ , ๋‚ฎ์€ ๋ณต์žก์„ฑ๊ณผ ์งง์€ ์ˆ˜๋ช…์€ ์•”ํ˜ธ ํ•ด๋… ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค." + }, + "SECURITY": { + "TITLE": "๋ณด์•ˆ ์„ค์ •", + "DESCRIPTION": "๋ณด์•ˆ์— ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ๋Š” ZITADEL ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ•˜์„ธ์š”. ์„ค์ •์„ ๋ณ€๊ฒฝํ•˜๊ธฐ ์ „์— ์ฃผ์˜ ๊นŠ๊ฒŒ ๊ฒ€ํ† ํ•˜์„ธ์š”." + }, + "OIDC": { + "TITLE": "OpenID Connect ์„ค์ •", + "DESCRIPTION": "OIDC ํ† ํฐ ์ˆ˜๋ช…์„ ์„ค์ •ํ•˜์„ธ์š”. ์งง์€ ์ˆ˜๋ช…์€ ์‚ฌ์šฉ์ž์˜ ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•˜๊ณ , ๊ธด ์ˆ˜๋ช…์€ ์‚ฌ์šฉ์ž์˜ ํŽธ์˜๋ฅผ ์ฆ๊ฐ€์‹œํ‚ต๋‹ˆ๋‹ค.", + "LABEL_HOURS": "์ตœ๋Œ€ ์ˆ˜๋ช… (์‹œ๊ฐ„)", + "LABEL_DAYS": "์ตœ๋Œ€ ์ˆ˜๋ช… (์ผ)", + "ACCESS_TOKEN": { + "TITLE": "์•ก์„ธ์Šค ํ† ํฐ", + "DESCRIPTION": "์•ก์„ธ์Šค ํ† ํฐ์€ ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๋‹จ๊ธฐ ํ† ํฐ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ ‘๊ทผํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋ถˆ๋ฒ•์ ์ธ ์ ‘๊ทผ ์œ„ํ—˜์„ ์ค„์ด๊ธฐ ์œ„ํ•ด ์งง์€ ์ˆ˜๋ช…์„ ์‚ฌ์šฉํ•˜์„ธ์š”. ์•ก์„ธ์Šค ํ† ํฐ์€ ๊ฐฑ์‹  ํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž๋™์œผ๋กœ ๊ฐฑ์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "ID_TOKEN": { + "TITLE": "ID ํ† ํฐ", + "DESCRIPTION": "ID ํ† ํฐ์€ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๋Š” JSON ์›น ํ† ํฐ(JWT)์ž…๋‹ˆ๋‹ค. ID ํ† ํฐ ์ˆ˜๋ช…์€ ์•ก์„ธ์Šค ํ† ํฐ ์ˆ˜๋ช…์„ ์ดˆ๊ณผํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค." + }, + "REFRESH_TOKEN": { + "TITLE": "๊ฐฑ์‹  ํ† ํฐ", + "DESCRIPTION": "๊ฐฑ์‹  ํ† ํฐ์€ ์ƒˆ๋กœ์šด ์•ก์„ธ์Šค ํ† ํฐ์„ ์–ป๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ์žฅ๊ธฐ ํ† ํฐ์ž…๋‹ˆ๋‹ค. ๊ฐฑ์‹  ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์ˆ˜๋™์œผ๋กœ ์žฌ์ธ์ฆํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค." + }, + "REFRESH_TOKEN_IDLE": { + "TITLE": "์œ ํœด ๊ฐฑ์‹  ํ† ํฐ", + "DESCRIPTION": "์œ ํœด ๊ฐฑ์‹  ํ† ํฐ ์ˆ˜๋ช…์€ ๊ฐฑ์‹  ํ† ํฐ์ด ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ์ตœ๋Œ€ ๊ธฐ๊ฐ„์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค." + } + }, + "MESSAGE_TEXTS": { + "TITLE": "๋ฉ”์‹œ์ง€ ํ…์ŠคํŠธ", + "DESCRIPTION": "์•Œ๋ฆผ ์ด๋ฉ”์ผ ๋˜๋Š” SMS ๋ฉ”์‹œ์ง€์˜ ํ…์ŠคํŠธ๋ฅผ ์‚ฌ์šฉ์ž ์ •์˜ํ•˜์„ธ์š”. ์–ธ์–ด๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด ์ธ์Šคํ„ด์Šค์˜ ์–ธ์–ด ์„ค์ •์—์„œ ์ œํ•œํ•˜์„ธ์š”.", + "TYPE_DESCRIPTIONS": { + "DC": "์กฐ์ง์˜ ๋„๋ฉ”์ธ์„ ์ฃผ์žฅํ•  ๋•Œ, ํ•ด๋‹น ๋„๋ฉ”์ธ์„ ๋กœ๊ทธ์ธ ์ด๋ฆ„์— ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์‚ฌ์šฉ์ž๋Š” ๋กœ๊ทธ์ธ ์ด๋ฆ„์„ ๋„๋ฉ”์ธ์— ๋งž๊ฒŒ ๋ณ€๊ฒฝํ•˜๋„๋ก ์š”์ฒญ๋ฐ›์Šต๋‹ˆ๋‹ค.", + "INIT": "์‚ฌ์šฉ์ž๊ฐ€ ์ƒ์„ฑ๋˜๋ฉด ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๋งํฌ๊ฐ€ ํฌํ•จ๋œ ์ด๋ฉ”์ผ์„ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.", + "PC": "์‚ฌ์šฉ์ž๊ฐ€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด ์•Œ๋ฆผ ์„ค์ •์„ ํ†ตํ•ด ๋ณ€๊ฒฝ ์‚ฌํ•ญ์— ๋Œ€ํ•œ ์•Œ๋ฆผ์„ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.", + "PL": "์‚ฌ์šฉ์ž๊ฐ€ ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์ธ์ฆ ๋ฐฉ์‹์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์ด๋ฉ”์ผ์˜ ๋งํฌ๋ฅผ ํด๋ฆญํ•˜์—ฌ ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "PR": "์‚ฌ์šฉ์ž๊ฐ€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์žฌ์„ค์ •ํ•  ๋•Œ ์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๋งํฌ๊ฐ€ ํฌํ•จ๋œ ์ด๋ฉ”์ผ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค.", + "VE": "์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ๋ณ€๊ฒฝํ•  ๋•Œ, ์ƒˆ ์ฃผ์†Œ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ๋งํฌ๊ฐ€ ํฌํ•จ๋œ ์ด๋ฉ”์ผ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค.", + "VP": "์‚ฌ์šฉ์ž๊ฐ€ ์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ๋ณ€๊ฒฝํ•  ๋•Œ, ์ƒˆ ๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ๊ฐ€ ํฌํ•จ๋œ SMS๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.", + "VEO": "์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฉ”์ผ์„ ํ†ตํ•ด ์ผํšŒ์„ฑ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ, ์ด๋ฉ”์ผ ์ฃผ์†Œ๋กœ ์ „์†ก๋œ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜์—ฌ ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "VSO": "์‚ฌ์šฉ์ž๊ฐ€ SMS๋ฅผ ํ†ตํ•ด ์ผํšŒ์„ฑ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ, ์ „ํ™”๋ฒˆํ˜ธ๋กœ ์ „์†ก๋œ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜์—ฌ ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "IU": "์‚ฌ์šฉ์ž ์ดˆ๋Œ€ ์ฝ”๋“œ๊ฐ€ ์ƒ์„ฑ๋˜๋ฉด ์ธ์ฆ ๋ฐฉ๋ฒ•์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๋งํฌ๊ฐ€ ํฌํ•จ๋œ ์ด๋ฉ”์ผ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค." + } + }, + "LOGIN_TEXTS": { + "TITLE": "๋กœ๊ทธ์ธ ์ธํ„ฐํŽ˜์ด์Šค ํ…์ŠคํŠธ", + "DESCRIPTION": "๋กœ๊ทธ์ธ ์–‘์‹์˜ ํ…์ŠคํŠธ๋ฅผ ์‚ฌ์šฉ์ž ์ •์˜ํ•˜์„ธ์š”. ํ…์ŠคํŠธ๊ฐ€ ๋น„์–ด ์žˆ์œผ๋ฉด ๊ธฐ๋ณธ ๊ฐ’์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์–ธ์–ด๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด ์ธ์Šคํ„ด์Šค์˜ ์–ธ์–ด ์„ค์ •์—์„œ ์ œํ•œํ•˜์„ธ์š”." + }, + "DOMAINS": { + "TITLE": "๋„๋ฉ”์ธ ์„ค์ •", + "DESCRIPTION": "๋„๋ฉ”์ธ์— ๋Œ€ํ•œ ์ œํ•œ ์‚ฌํ•ญ์„ ์ •์˜ํ•˜๊ณ  ๋กœ๊ทธ์ธ ์ด๋ฆ„ ํŒจํ„ด์„ ๊ตฌ์„ฑํ•˜์„ธ์š”.", + "REQUIRE_VERIFICATION": { + "TITLE": "๋งž์ถค ๋„๋ฉ”์ธ ํ™•์ธ ํ•„์š”", + "DESCRIPTION": "ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ, ์กฐ์ง ๋„๋ฉ”์ธ์€ ๋„๋ฉ”์ธ ๊ฒ€์ƒ‰์ด๋‚˜ ์‚ฌ์šฉ์ž ์ด๋ฆ„ ์ ‘๋ฏธ์‚ฌ๋กœ ์‚ฌ์šฉ๋˜๊ธฐ ์ „์— ํ™•์ธ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค." + }, + "LOGIN_NAME_PATTERN": { + "TITLE": "๋กœ๊ทธ์ธ ์ด๋ฆ„ ํŒจํ„ด", + "DESCRIPTION": "์‚ฌ์šฉ์ž์˜ ๋กœ๊ทธ์ธ ์ด๋ฆ„ ํŒจํ„ด์„ ์ œ์–ดํ•˜์„ธ์š”. ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜๋Š” ์ฆ‰์‹œ ZITADEL์€ ์‚ฌ์šฉ์ž์˜ ์กฐ์ง์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋กœ๊ทธ์ธ ์ด๋ฆ„์€ ๋ชจ๋“  ์กฐ์ง์—์„œ ๊ณ ์œ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ๋„๋ฉ”์ธ์— ๊ณ„์ •์ด ์žˆ๋Š” ์‚ฌ์šฉ์ž์˜ ๊ฒฝ์šฐ, ๋กœ๊ทธ์ธ ์ด๋ฆ„์— ์กฐ์ง ๋„๋ฉ”์ธ์„ ์ ‘๋ฏธ์‚ฌ๋กœ ๋ถ™์—ฌ ๊ณ ์œ ์„ฑ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "DOMAIN_VERIFICATION": { + "TITLE": "๋„๋ฉ”์ธ ํ™•์ธ", + "DESCRIPTION": "์กฐ์ง์ด ์‹ค์ œ๋กœ ์ œ์–ดํ•˜๋Š” ๋„๋ฉ”์ธ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ํ™œ์„ฑํ™”๋˜๋ฉด ์กฐ์ง ๋„๋ฉ”์ธ์€ ๋„๋ฉ”์ธ ํ•˜์ด์žฌํ‚น ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด DNS ๋˜๋Š” HTTP ๊ฒ€์ฆ์„ ํ†ตํ•ด ์ฃผ๊ธฐ์ ์œผ๋กœ ํ™•์ธ๋ฉ๋‹ˆ๋‹ค." + }, + "SMTP_SENDER_ADDRESS": { + "TITLE": "SMTP ๋ฐœ์‹  ์ฃผ์†Œ", + "DESCRIPTION": "์ธ์Šคํ„ด์Šค ๋„๋ฉ”์ธ ์ค‘ ํ•˜๋‚˜์™€ ์ผ์น˜ํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ SMTP ๋ฐœ์‹  ์ฃผ์†Œ๋ฅผ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค." + } + }, + "LOGIN": { + "LIFETIMES": { + "TITLE": "๋กœ๊ทธ์ธ ์ˆ˜๋ช…", + "DESCRIPTION": "๋กœ๊ทธ์ธ ๊ด€๋ จ ์ตœ๋Œ€ ์ˆ˜๋ช…์„ ์ค„์—ฌ ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•˜์„ธ์š”.", + "LABEL": "์ตœ๋Œ€ ์ˆ˜๋ช… (์‹œ๊ฐ„)", + "PW_CHECK": { + "TITLE": "๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ", + "DESCRIPTION": "์ง€์ •๋œ ๊ธฐ๊ฐ„ ํ›„ ์‚ฌ์šฉ์ž๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ์žฌ์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค." + }, + "EXT_LOGIN_CHECK": { + "TITLE": "์™ธ๋ถ€ ๋กœ๊ทธ์ธ ํ™•์ธ", + "DESCRIPTION": "์ง€์ •๋œ ๊ธฐ๊ฐ„ ํ›„ ์‚ฌ์šฉ์ž๋Š” ์™ธ๋ถ€ ID ์ œ๊ณต์ž์—๊ฒŒ ๋ฆฌ๋””๋ ‰์…˜๋ฉ๋‹ˆ๋‹ค." + }, + "MULTI_FACTOR_INIT": { + "TITLE": "๋‹ค์ค‘ ์ธ์ฆ ์ดˆ๊ธฐํ™” ํ™•์ธ", + "DESCRIPTION": "์‚ฌ์šฉ์ž๊ฐ€ ์•„์ง ์„ค์ •ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ, ์ง€์ •๋œ ๊ธฐ๊ฐ„ ํ›„ ๋‘ ๋ฒˆ์งธ ์ธ์ฆ ์š”์†Œ๋‚˜ ๋‹ค์ค‘ ์ธ์ฆ์„ ์„ค์ •ํ•˜๋„๋ก ์•ˆ๋‚ดํ•ฉ๋‹ˆ๋‹ค. ์ˆ˜๋ช…์„ 0์œผ๋กœ ์„ค์ •ํ•˜๋ฉด ์ด ์•ˆ๋‚ด๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค." + }, + "SECOND_FACTOR_CHECK": { + "TITLE": "๋‘ ๋ฒˆ์งธ ์ธ์ฆ ์š”์†Œ ํ™•์ธ", + "DESCRIPTION": "์‚ฌ์šฉ์ž๋Š” ์ง€์ •๋œ ๊ธฐ๊ฐ„ ๋™์•ˆ ๋‘ ๋ฒˆ์งธ ์ธ์ฆ ์š”์†Œ๋ฅผ ์žฌํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค." + }, + "MULTI_FACTOR_CHECK": { + "TITLE": "๋‹ค์ค‘ ์ธ์ฆ ํ™•์ธ", + "DESCRIPTION": "์‚ฌ์šฉ์ž๋Š” ์ง€์ •๋œ ๊ธฐ๊ฐ„ ๋™์•ˆ ๋‹ค์ค‘ ์ธ์ฆ์„ ์žฌํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค." + } + }, + "FORM": { + "TITLE": "๋กœ๊ทธ์ธ ํผ", + "DESCRIPTION": "๋กœ๊ทธ์ธ ํผ์„ ์‚ฌ์šฉ์ž ์ •์˜ํ•˜์„ธ์š”.", + "USERNAME_PASSWORD_ALLOWED": { + "TITLE": "์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ ํ—ˆ์šฉ", + "DESCRIPTION": "์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•˜์„ธ์š”. ๋น„ํ™œ์„ฑํ™”ํ•˜๋ฉด ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์ธ์ฆ ๋˜๋Š” ์™ธ๋ถ€ ID ์ œ๊ณต์ž๋กœ๋งŒ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "USER_REGISTRATION_ALLOWED": { + "TITLE": "์‚ฌ์šฉ์ž ๋“ฑ๋ก ํ—ˆ์šฉ", + "DESCRIPTION": "์ต๋ช… ์‚ฌ์šฉ์ž๊ฐ€ ๊ณ„์ •์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•˜์„ธ์š”." + }, + "ORG_REGISTRATION_ALLOWED": { + "TITLE": "์กฐ์ง ๋“ฑ๋ก ํ—ˆ์šฉ", + "DESCRIPTION": "์ต๋ช… ์‚ฌ์šฉ์ž๊ฐ€ ์กฐ์ง์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•˜์„ธ์š”." + }, + "EXTERNAL_LOGIN_ALLOWED": { + "TITLE": "์™ธ๋ถ€ ๋กœ๊ทธ์ธ ํ—ˆ์šฉ", + "DESCRIPTION": "์‚ฌ์šฉ์ž๊ฐ€ ZITADEL ์‚ฌ์šฉ์ž ๋Œ€์‹  ์™ธ๋ถ€ ID ์ œ๊ณต์ž๋ฅผ ํ†ตํ•ด ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•˜์„ธ์š”." + }, + "HIDE_PASSWORD_RESET": { + "TITLE": "๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ์ˆจ๊ธฐ๊ธฐ", + "DESCRIPTION": "์‚ฌ์šฉ์ž๊ฐ€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์žฌ์„ค์ •ํ•  ์ˆ˜ ์—†๋„๋ก ํ—ˆ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค." + }, + "DOMAIN_DISCOVERY_ALLOWED": { + "TITLE": "๋„๋ฉ”์ธ ๊ฒ€์ƒ‰ ํ—ˆ์šฉ", + "DESCRIPTION": "์˜ˆ๋ฅผ ๋“ค์–ด ์ด๋ฉ”์ผ ์ฃผ์†Œ์™€ ๊ฐ™์€ ๋กœ๊ทธ์ธ ์ด๋ฆ„์˜ ๋„๋ฉ”์ธ์— ๋”ฐ๋ผ ์‚ฌ์šฉ์ž์˜ ์กฐ์ง์„ ์ฐพ์Šต๋‹ˆ๋‹ค." + }, + "IGNORE_UNKNOWN_USERNAMES": { + "TITLE": "์•Œ ์ˆ˜ ์—†๋Š” ์‚ฌ์šฉ์ž ์ด๋ฆ„ ๋ฌด์‹œ", + "DESCRIPTION": "ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ, ๋กœ๊ทธ์ธ ํผ์€ ์‚ฌ์šฉ์ž ์ด๋ฆ„์ด ์•Œ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ์—๋„ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์‚ฌ์šฉ์ž ์ด๋ฆ„ ์ถ”์ธก์„ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค." + }, + "DISABLE_EMAIL_LOGIN": { + "TITLE": "์ด๋ฉ”์ผ ๋กœ๊ทธ์ธ ๋น„ํ™œ์„ฑํ™”", + "DESCRIPTION": "ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž๋Š” ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด ์„ค์ •์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ฉด ๋ชจ๋“  ์กฐ์ง์—์„œ ์‚ฌ์šฉ์ž์˜ ์ด๋ฉ”์ผ ์ฃผ์†Œ๊ฐ€ ๊ณ ์œ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค." + }, + "DISABLE_PHONE_LOGIN": { + "TITLE": "์ „ํ™”๋ฒˆํ˜ธ ๋กœ๊ทธ์ธ ๋น„ํ™œ์„ฑํ™”", + "DESCRIPTION": "ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž๋Š” ์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด ์„ค์ •์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ฉด ๋ชจ๋“  ์กฐ์ง์—์„œ ์‚ฌ์šฉ์ž์˜ ์ „ํ™”๋ฒˆํ˜ธ๊ฐ€ ๊ณ ์œ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค." + } + } + } + } + }, + "PAGINATOR": { + "PREVIOUS": "์ด์ „", + "NEXT": "๋‹ค์Œ", + "COUNT": "๊ฐœ์˜ ๊ฒฐ๊ณผ๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค", + "MORE": "๋”๋ณด๊ธฐ" + }, + "FOOTER": { + "LINKS": { + "CONTACT": "๋ฌธ์˜ํ•˜๊ธฐ", + "TOS": "์ด์šฉ ์•ฝ๊ด€", + "PP": "๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ๋ฐฉ์นจ" + }, + "THEME": { + "DARK": "๋‹คํฌ", + "LIGHT": "๋ผ์ดํŠธ" + } + }, + "HOME": { + "WELCOME": "ZITADEL ์‹œ์ž‘ํ•˜๊ธฐ", + "DISCLAIMER": "ZITADEL์€ ๊ท€ํ•˜์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ€ํ•˜๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.", + "DISCLAIMERLINK": "์ถ”๊ฐ€ ์ •๋ณด", + "DOCUMENTATION": { + "DESCRIPTION": "ZITADEL ์‹œ์ž‘์„ ๋น ๋ฅด๊ฒŒ ์ง„ํ–‰ํ•˜์„ธ์š”." + }, + "GETSTARTED": { + "DESCRIPTION": "ZITADEL ์‹œ์ž‘์„ ๋น ๋ฅด๊ฒŒ ์ง„ํ–‰ํ•˜์„ธ์š”." + }, + "QUICKSTARTS": { + "LABEL": "์ฒซ ๋‹จ๊ณ„", + "DESCRIPTION": "ZITADEL ์‹œ์ž‘์„ ๋น ๋ฅด๊ฒŒ ์ง„ํ–‰ํ•˜์„ธ์š”." + }, + "SHORTCUTS": { + "SHORTCUTS": "๋ฐ”๋กœ ๊ฐ€๊ธฐ", + "SETTINGS": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ฐ”๋กœ ๊ฐ€๊ธฐ", + "PROJECTS": "ํ”„๋กœ์ ํŠธ", + "REORDER": "ํƒ€์ผ์„ ํด๋ฆญ ํ›„ ๋“œ๋ž˜๊ทธํ•˜์—ฌ ์ด๋™ํ•˜์„ธ์š”", + "ADD": "ํƒ€์ผ์„ ํด๋ฆญ ํ›„ ๋“œ๋ž˜๊ทธํ•˜์—ฌ ์ถ”๊ฐ€ํ•˜์„ธ์š”" + } + }, + "ONBOARDING": { + "DESCRIPTION": "๋‹ค์Œ ๋‹จ๊ณ„", + "MOREDESCRIPTION": "๋” ๋งŽ์€ ๋ฐ”๋กœ ๊ฐ€๊ธฐ", + "COMPLETED": "์™„๋ฃŒ๋จ", + "DISMISS": "๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค.", + "CARD": { + "TITLE": "ZITADEL ์„ค์ •", + "DESCRIPTION": "์ด ์ฒดํฌ๋ฆฌ์ŠคํŠธ๋Š” ์ธ์Šคํ„ด์Šค๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๋ฉฐ, ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๋‹จ๊ณ„๋ฅผ ์•ˆ๋‚ดํ•ฉ๋‹ˆ๋‹ค." + }, + "MILESTONES": { + "instance.policy.label.added": { + "title": "๋ธŒ๋žœ๋“œ ์„ค์ •", + "description": "๋กœ๊ทธ์ธ์˜ ์ƒ‰์ƒ๊ณผ ๋ชจ์–‘์„ ์ •์˜ํ•˜๊ณ  ๋กœ๊ณ ์™€ ์•„์ด์ฝ˜์„ ์—…๋กœ๋“œํ•˜์„ธ์š”.", + "action": "๋ธŒ๋žœ๋”ฉ ์„ค์ •" + }, + "instance.smtp.config.added": { + "title": "SMTP ์„ค์ •", + "description": "์ž์‹ ์˜ ๋ฉ”์ผ ์„œ๋ฒ„ ์„ค์ •์„ ๊ตฌ์„ฑํ•˜์„ธ์š”.", + "action": "SMTP ์„ค์ •" + }, + "PROJECT_CREATED": { + "title": "ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ", + "description": "ํ”„๋กœ์ ํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์—ญํ• ๊ณผ ๊ถŒํ•œ์„ ์ •์˜ํ•˜์„ธ์š”.", + "action": "ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ" + }, + "APPLICATION_CREATED": { + "title": "์•ฑ ๋“ฑ๋ก", + "description": "์›น, ๋„ค์ดํ‹ฐ๋ธŒ, API ๋˜๋Š” SAML ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋“ฑ๋กํ•˜๊ณ  ์ธ์ฆ ํ”Œ๋กœ์šฐ๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.", + "action": "์•ฑ ๋“ฑ๋ก" + }, + "AUTHENTICATION_SUCCEEDED_ON_APPLICATION": { + "title": "์•ฑ์— ๋กœ๊ทธ์ธ", + "description": "ZITADEL๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ†ตํ•ฉํ•˜๊ณ  ๊ด€๋ฆฌ์ž ์‚ฌ์šฉ์ž๋กœ ๋กœ๊ทธ์ธํ•˜์—ฌ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.", + "action": "๋กœ๊ทธ์ธ" + }, + "user.human.added": { + "title": "์‚ฌ์šฉ์ž ์ถ”๊ฐ€", + "description": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‚ฌ์šฉ์ž ์ถ”๊ฐ€", + "action": "์‚ฌ์šฉ์ž ์ถ”๊ฐ€" + }, + "user.grant.added": { + "title": "์‚ฌ์šฉ์ž ๊ถŒํ•œ ๋ถ€์—ฌ", + "description": "์‚ฌ์šฉ์ž๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ณ  ์—ญํ• ์„ ์„ค์ •ํ•˜์„ธ์š”.", + "action": "์‚ฌ์šฉ์ž ๊ถŒํ•œ ๋ถ€์—ฌ" + } + } + }, + "MENU": { + "INSTANCE": "๊ธฐ๋ณธ ์„ค์ •", + "DASHBOARD": "ํ™ˆ", + "PERSONAL_INFO": "๊ฐœ์ธ ์ •๋ณด", + "DOCUMENTATION": "๋ฌธ์„œ", + "INSTANCEOVERVIEW": "์ธ์Šคํ„ด์Šค", + "ORGS": "์กฐ์ง", + "VIEWS": "๋ทฐ", + "EVENTS": "์ด๋ฒคํŠธ", + "FAILEDEVENTS": "์‹คํŒจํ•œ ์ด๋ฒคํŠธ", + "ORGANIZATION": "์กฐ์ง", + "PROJECT": "ํ”„๋กœ์ ํŠธ", + "PROJECTOVERVIEW": "๊ฐœ์š”", + "PROJECTGRANTS": "๊ถŒํ•œ ๋ถ€์—ฌ", + "ROLES": "์—ญํ• ", + "GRANTEDPROJECT": "๋ถ€์—ฌ๋œ ํ”„๋กœ์ ํŠธ", + "HUMANUSERS": "์‚ฌ์šฉ์ž", + "MACHINEUSERS": "์„œ๋น„์Šค ์‚ฌ์šฉ์ž", + "LOGOUT": "๋ชจ๋“  ์‚ฌ์šฉ์ž ๋กœ๊ทธ์•„์›ƒ", + "NEWORG": "์ƒˆ ์กฐ์ง", + "IAMADMIN": "IAM ๊ด€๋ฆฌ์ž์ž…๋‹ˆ๋‹ค. ํ™•์žฅ๋œ ๊ถŒํ•œ์ด ๋ถ€์—ฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "SHOWORGS": "๋ชจ๋“  ์กฐ์ง ๋ณด๊ธฐ", + "GRANTS": "๊ถŒํ•œ", + "ACTIONS": "์ž‘์—…", + "PRIVACY": "๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ๋ฐฉ์นจ", + "TOS": "์ด์šฉ ์•ฝ๊ด€", + "OPENSHORTCUTSTOOLTIP": "ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค ํ‘œ์‹œํ•˜๋ ค๋ฉด ? ์ž…๋ ฅ", + "SETTINGS": "์„ค์ •", + "CUSTOMERPORTAL": "๊ณ ๊ฐ ํฌํ„ธ" + }, + "QUICKSTART": { + "TITLE": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ZITADEL ํ†ตํ•ฉ", + "DESCRIPTION": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ZITADEL์„ ํ†ตํ•ฉํ•˜๊ฑฐ๋‚˜ ์ƒ˜ํ”Œ ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ช‡ ๋ถ„ ์•ˆ์— ์‹œ์ž‘ํ•˜์„ธ์š”.", + "BTN_START": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒ์„ฑ", + "BTN_LEARNMORE": "์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ", + "CREATEPROJECTFORAPP": "ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ {{value}}", + "SELECT_FRAMEWORK": "ํ”„๋ ˆ์ž„์›Œํฌ ์„ ํƒ", + "FRAMEWORK": "ํ”„๋ ˆ์ž„์›Œํฌ", + "FRAMEWORK_OTHER": "๊ธฐํƒ€ (OIDC, SAML, API)", + "ALMOSTDONE": "๊ฑฐ์˜ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "REVIEWCONFIGURATION": "๊ตฌ์„ฑ ๊ฒ€ํ† ", + "REVIEWCONFIGURATION_DESCRIPTION": "{{value}} ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋Œ€ํ•œ ๊ธฐ๋ณธ ๊ตฌ์„ฑ์„ ์ƒ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. ์ƒ์„ฑ ํ›„ ์ด ๊ตฌ์„ฑ์„ ํ•„์š”์— ๋งž๊ฒŒ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "REDIRECTS": "๋ฆฌ๋””๋ ‰์…˜ ๊ตฌ์„ฑ", + "DEVMODEWARN": "๊ฐœ๋ฐœ ๋ชจ๋“œ๊ฐ€ ๊ธฐ๋ณธ์œผ๋กœ ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์šด์˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋‚˜์ค‘์— ์„ค์ • ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "GUIDE": "๊ฐ€์ด๋“œ", + "BROWSEEXAMPLES": "์˜ˆ์ œ ๋ฐ SDK ๋‘˜๋Ÿฌ๋ณด๊ธฐ", + "DUPLICATEAPPRENAME": "๊ฐ™์€ ์ด๋ฆ„์˜ ์•ฑ์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์ด๋ฆ„์„ ์„ ํƒํ•˜์„ธ์š”.", + "DIALOG": { + "CHANGE": { + "TITLE": "ํ”„๋ ˆ์ž„์›Œํฌ ๋ณ€๊ฒฝ", + "DESCRIPTION": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋น ๋ฅธ ์„ค์ •์„ ์œ„ํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ”„๋ ˆ์ž„์›Œํฌ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”." + } + } + }, + "ACTIONS": { + "ACTIONS": "์ž‘์—…", + "FILTER": "ํ•„ํ„ฐ", + "RENAME": "์ด๋ฆ„ ๋ณ€๊ฒฝ", + "SET": "์„ค์ •", + "COPY": "ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌ", + "COPIED": "ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "RESET": "์žฌ์„ค์ •", + "RESETDEFAULT": "๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์žฌ์„ค์ •", + "RESETTO": "๋‹ค์Œ์œผ๋กœ ์žฌ์„ค์ •: ", + "RESETCURRENT": "ํ˜„์žฌ ๊ฐ’์œผ๋กœ ์žฌ์„ค์ •", + "SHOW": "ํ‘œ์‹œ", + "HIDE": "์ˆจ๊ธฐ๊ธฐ", + "SAVE": "์ €์žฅ", + "SAVENOW": "์ง€๊ธˆ ์ €์žฅ", + "NEW": "์ƒˆ๋กœ ๋งŒ๋“ค๊ธฐ", + "ADD": "์ถ”๊ฐ€", + "CREATE": "์ƒ์„ฑ", + "CONTINUE": "๊ณ„์†", + "CONTINUEWITH": "{{value}}์œผ๋กœ ๊ณ„์†", + "BACK": "๋’ค๋กœ", + "CLOSE": "๋‹ซ๊ธฐ", + "CLEAR": "๋น„์šฐ๊ธฐ", + "CANCEL": "์ทจ์†Œ", + "INFO": "์ •๋ณด", + "OK": "ํ™•์ธ", + "SELECT": "์„ ํƒ", + "VIEW": "๋ณด๊ธฐ", + "SELECTIONDELETE": "์„ ํƒ ํ•ญ๋ชฉ ์‚ญ์ œ", + "DELETE": "์‚ญ์ œ", + "REMOVE": "์ œ๊ฑฐ", + "VERIFY": "ํ™•์ธ", + "FINISH": "์™„๋ฃŒ", + "FINISHED": "๋‹ซ๊ธฐ", + "CHANGE": "๋ณ€๊ฒฝ", + "REACTIVATE": "์žฌํ™œ์„ฑํ™”", + "ACTIVATE": "ํ™œ์„ฑํ™”", + "DEACTIVATE": "๋น„ํ™œ์„ฑํ™”", + "REFRESH": "์ƒˆ๋กœ ๊ณ ์นจ", + "LOGIN": "๋กœ๊ทธ์ธ", + "EDIT": "ํŽธ์ง‘", + "PIN": "๊ณ ์ • / ๊ณ ์ • ํ•ด์ œ", + "CONFIGURE": "๊ตฌ์„ฑ", + "SEND": "๋ณด๋‚ด๊ธฐ", + "NEWVALUE": "์ƒˆ๋กœ์šด ๊ฐ’", + "RESTORE": "๋ณต์›", + "CONTINUEWITHOUTSAVE": "์ €์žฅํ•˜์ง€ ์•Š๊ณ  ๊ณ„์†", + "OF": "์˜", + "PREVIOUS": "์ด์ „", + "NEXT": "๋‹ค์Œ", + "MORE": "๋” ๋ณด๊ธฐ", + "STEP": "๋‹จ๊ณ„", + "SETUP": "์„ค์ •", + "TEST": "ํ…Œ์ŠคํŠธ", + "UNSAVEDCHANGES": "์ €์žฅ๋˜์ง€ ์•Š์€ ๋ณ€๊ฒฝ ์‚ฌํ•ญ", + "UNSAVED": { + "DIALOG": { + "DESCRIPTION": "์ด ์ƒˆ ์ž‘์—…์„ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? ์ž‘์—…์ด ์†์‹ค๋ฉ๋‹ˆ๋‹ค.", + "CANCEL": "์ทจ์†Œ", + "DISCARD": "์‚ญ์ œ" + } + }, + "TABLE": { + "SHOWUSER": "์‚ฌ์šฉ์ž {{value}} ํ‘œ์‹œ" + }, + "DOWNLOAD": "๋‹ค์šด๋กœ๋“œ", + "APPLY": "์ ์šฉ" + }, + "MEMBERROLES": { + "IAM_OWNER": "์ธ์Šคํ„ด์Šค์™€ ๋ชจ๋“  ์กฐ์ง์— ๋Œ€ํ•œ ์ œ์–ด ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "IAM_OWNER_VIEWER": "์ธ์Šคํ„ด์Šค์™€ ๋ชจ๋“  ์กฐ์ง์„ ๊ฒ€ํ† ํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "IAM_ORG_MANAGER": "์กฐ์ง์„ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "IAM_USER_MANAGER": "์‚ฌ์šฉ์ž๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "IAM_ADMIN_IMPERSONATOR": "๋ชจ๋“  ์กฐ์ง์˜ ๊ด€๋ฆฌ์ž์™€ ์ตœ์ข… ์‚ฌ์šฉ์ž๋ฅผ ๋Œ€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "IAM_END_USER_IMPERSONATOR": "๋ชจ๋“  ์กฐ์ง์˜ ์ตœ์ข… ์‚ฌ์šฉ์ž๋ฅผ ๋Œ€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "ORG_OWNER": "์กฐ์ง์— ๋Œ€ํ•œ ์ „์ฒด ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "ORG_USER_MANAGER": "์กฐ์ง์˜ ์‚ฌ์šฉ์ž๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "ORG_OWNER_VIEWER": "์กฐ์ง ์ „์ฒด๋ฅผ ๊ฒ€ํ† ํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "ORG_USER_PERMISSION_EDITOR": "์‚ฌ์šฉ์ž ๊ถŒํ•œ์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "ORG_PROJECT_PERMISSION_EDITOR": "ํ”„๋กœ์ ํŠธ ๊ถŒํ•œ์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "ORG_PROJECT_CREATOR": "์ž์‹ ์˜ ํ”„๋กœ์ ํŠธ์™€ ํ•˜์œ„ ์„ค์ •์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "ORG_ADMIN_IMPERSONATOR": "์กฐ์ง์˜ ๊ด€๋ฆฌ์ž ๋ฐ ์ตœ์ข… ์‚ฌ์šฉ์ž๋ฅผ ๋Œ€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "ORG_END_USER_IMPERSONATOR": "์กฐ์ง์˜ ์ตœ์ข… ์‚ฌ์šฉ์ž๋ฅผ ๋Œ€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "PROJECT_OWNER": "ํ”„๋กœ์ ํŠธ์— ๋Œ€ํ•œ ์ „์ฒด ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "PROJECT_OWNER_VIEWER": "ํ”„๋กœ์ ํŠธ ์ „์ฒด๋ฅผ ๊ฒ€ํ† ํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "PROJECT_OWNER_GLOBAL": "ํ”„๋กœ์ ํŠธ์— ๋Œ€ํ•œ ์ „์ฒด ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "PROJECT_OWNER_VIEWER_GLOBAL": "ํ”„๋กœ์ ํŠธ ์ „์ฒด๋ฅผ ๊ฒ€ํ† ํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "PROJECT_GRANT_OWNER": "ํ”„๋กœ์ ํŠธ ๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค", + "PROJECT_GRANT_OWNER_VIEWER": "ํ”„๋กœ์ ํŠธ ๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ๊ฒ€ํ† ํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค" + }, + "OVERLAYS": { + "ORGSWITCHER": { + "TEXT": "์ฝ˜์†”์˜ ๋ชจ๋“  ์กฐ์ง ์„ค์ • ๋ฐ ํ…Œ์ด๋ธ”์€ ์„ ํƒ๋œ ์กฐ์ง์„ ๊ธฐ์ค€์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์—ฌ ์กฐ์ง์„ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ ์ƒˆ ์กฐ์ง์„ ์ƒ์„ฑํ•˜์„ธ์š”." + }, + "INSTANCE": { + "TEXT": "๊ธฐ๋ณธ ์„ค์ •์œผ๋กœ ์ด๋™ํ•˜๋ ค๋ฉด ์—ฌ๊ธฐ๋ฅผ ํด๋ฆญํ•˜์„ธ์š”. ์ด ๋ฒ„ํŠผ์€ ํ–ฅ์ƒ๋œ ๊ถŒํ•œ์ด ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "PROFILE": { + "TEXT": "์—ฌ๊ธฐ์—์„œ ์‚ฌ์šฉ์ž ๊ณ„์ •์„ ์ „ํ™˜ํ•˜๊ณ  ์„ธ์…˜ ๋ฐ ํ”„๋กœํ•„์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "NAV": { + "TEXT": "์ด ๋„ค๋น„๊ฒŒ์ด์…˜์€ ์œ„์— ์„ ํƒํ•œ ์กฐ์ง ๋˜๋Š” ์ธ์Šคํ„ด์Šค์— ๋”ฐ๋ผ ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค." + }, + "CONTEXTCHANGED": { + "TEXT": "์กฐ์ง ์ปจํ…์ŠคํŠธ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + }, + "SWITCHEDTOINSTANCE": { + "TEXT": "๋ทฐ๊ฐ€ ์ธ์Šคํ„ด์Šค๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!" + } + }, + "FILTER": { + "TITLE": "ํ•„ํ„ฐ", + "STATE": "์ƒํƒœ", + "DISPLAYNAME": "์‚ฌ์šฉ์ž ํ‘œ์‹œ ์ด๋ฆ„", + "EMAIL": "์ด๋ฉ”์ผ", + "USERNAME": "์‚ฌ์šฉ์ž ์ด๋ฆ„", + "ORGNAME": "์กฐ์ง ์ด๋ฆ„", + "PRIMARYDOMAIN": "๊ธฐ๋ณธ ๋„๋ฉ”์ธ", + "PROJECTNAME": "ํ”„๋กœ์ ํŠธ ์ด๋ฆ„", + "RESOURCEOWNER": "๋ฆฌ์†Œ์Šค ์†Œ์œ ์ž", + "METHODS": { + "5": "ํฌํ•จ", + "7": "๋กœ ๋๋‚จ", + "1": "์™€ ์ผ์น˜" + } + }, + "KEYBOARDSHORTCUTS": { + "TITLE": "ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค", + "UNDERORGCONTEXT": "์กฐ์ง ํŽ˜์ด์ง€ ๋‚ด", + "SIDEWIDE": "์‚ฌ์ดํŠธ ์ „์ฒด ๋‹จ์ถ•ํ‚ค", + "SHORTCUTS": { + "HOME": "ํ™ˆ์œผ๋กœ ์ด๋™ (GH)", + "INSTANCE": "์ธ์Šคํ„ด์Šค๋กœ ์ด๋™ (GI)", + "ORG": "์กฐ์ง์œผ๋กœ ์ด๋™ (GO)", + "ORGSETTINGS": "์กฐ์ง ์„ค์ •์œผ๋กœ ์ด๋™ (GS)", + "ORGSWITCHER": "์กฐ์ง ๋ณ€๊ฒฝ", + "ME": "๋‚ด ํ”„๋กœํ•„๋กœ ์ด๋™", + "PROJECTS": "ํ”„๋กœ์ ํŠธ๋กœ ์ด๋™ (GP)", + "USERS": "์‚ฌ์šฉ์ž๋กœ ์ด๋™ (GU)", + "USERGRANTS": "์ธ์ฆ์œผ๋กœ ์ด๋™ (GA)", + "ACTIONS": "์•ก์…˜ ํ”Œ๋กœ์šฐ๋กœ ์ด๋™ (GF)", + "DOMAINS": "๋„๋ฉ”์ธ์œผ๋กœ ์ด๋™ (GD)" + } + }, + "RESOURCEID": "๋ฆฌ์†Œ์Šค ID", + "NAME": "Name", + "VERSION": "๋ฒ„์ „", + "TABLE": { + "NOROWS": "๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค" + }, + "ERRORS": { + "REQUIRED": "์ด ํ•„๋“œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", + "ATLEASTONE": "ํ•˜๋‚˜ ์ด์ƒ์˜ ๊ฐ’์„ ์ œ๊ณตํ•˜์„ธ์š”.", + "TOKENINVALID": { + "TITLE": "์ธ์ฆ ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "DESCRIPTION": "๋‹ค์‹œ ๋กœ๊ทธ์ธํ•˜๋ ค๋ฉด ์•„๋ž˜ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”." + }, + "EXHAUSTED": { + "TITLE": "์ธ์Šคํ„ด์Šค๊ฐ€ ์ฐจ๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "DESCRIPTION": "ZITADEL ์ธ์Šคํ„ด์Šค ๊ด€๋ฆฌ์ž์—๊ฒŒ ๊ตฌ๋…์„ ์—…๋ฐ์ดํŠธํ•˜๋„๋ก ์š”์ฒญํ•˜์„ธ์š”." + }, + "INVALID_FORMAT": "ํ˜•์‹์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + "NOTANEMAIL": "์ž…๋ ฅ๋œ ๊ฐ’์ด ์ด๋ฉ”์ผ ์ฃผ์†Œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.", + "MINLENGTH": "{{requiredLength}}์ž ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "MAXLENGTH": "{{requiredLength}}์ž ์ดํ•˜์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "UPPERCASEMISSING": "๋Œ€๋ฌธ์ž๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "LOWERCASEMISSING": "์†Œ๋ฌธ์ž๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "SYMBOLERROR": "๊ธฐํ˜ธ๋‚˜ ๊ตฌ๋‘์ ์ด ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "NUMBERERROR": "์ˆซ์ž๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "PWNOTEQUAL": "์ž…๋ ฅ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + "PHONE": "์ „ํ™”๋ฒˆํ˜ธ๋Š” +๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค." + }, + "USER": { + "SETTINGS": { + "TITLE": "์„ค์ •", + "GENERAL": "์ผ๋ฐ˜", + "IDP": "ID ์ œ๊ณต์ž", + "SECURITY": "๋น„๋ฐ€๋ฒˆํ˜ธ ๋ฐ ๋ณด์•ˆ", + "KEYS": "ํ‚ค", + "PAT": "๊ฐœ์ธ ์ ‘๊ทผ ํ† ํฐ", + "USERGRANTS": "๊ถŒํ•œ ๋ถ€์—ฌ", + "MEMBERSHIPS": "๋ฉค๋ฒ„์‹ญ", + "METADATA": "๋ฉ”ํƒ€๋ฐ์ดํ„ฐ" + }, + "TITLE": "๊ฐœ์ธ ์ •๋ณด", + "DESCRIPTION": "์ •๋ณด์™€ ๋ณด์•ˆ ์„ค์ •์„ ๊ด€๋ฆฌํ•˜์„ธ์š”.", + "PAGES": { + "TITLE": "์‚ฌ์šฉ์ž", + "DETAIL": "์„ธ๋ถ€ ์ •๋ณด", + "CREATE": "์ƒ์„ฑ", + "MY": "๋‚ด ์ •๋ณด", + "LOGINNAMES": "๋กœ๊ทธ์ธ ์ด๋ฆ„", + "LOGINMETHODS": "๋กœ๊ทธ์ธ ๋ฐฉ๋ฒ•", + "LOGINNAMESDESC": "๋‹ค์Œ์€ ์‚ฌ์šฉ์ž์˜ ๋กœ๊ทธ์ธ ์ด๋ฆ„์ž…๋‹ˆ๋‹ค:", + "NOUSER": "์—ฐ๊ด€๋œ ์‚ฌ์šฉ์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", + "REACTIVATE": "์žฌํ™œ์„ฑํ™”", + "DEACTIVATE": "๋น„ํ™œ์„ฑํ™”", + "FILTER": "ํ•„ํ„ฐ", + "STATE": "์ƒํƒœ", + "DELETE": "์‚ฌ์šฉ์ž ์‚ญ์ œ", + "UNLOCK": "์‚ฌ์šฉ์ž ์ž ๊ธˆ ํ•ด์ œ", + "GENERATESECRET": "ํด๋ผ์ด์–ธํŠธ ์‹œํฌ๋ฆฟ ์ƒ์„ฑ", + "REMOVESECRET": "ํด๋ผ์ด์–ธํŠธ ์‹œํฌ๋ฆฟ ์‚ญ์ œ", + "LOCKEDDESCRIPTION": "๋กœ๊ทธ์ธ ์‹œ๋„ ํšŸ์ˆ˜๊ฐ€ ์ดˆ๊ณผ๋˜์–ด ์‚ฌ์šฉ์ž๊ฐ€ ์ž ๊ธˆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์ž ๊ธˆ์„ ํ•ด์ œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "DELETEACCOUNT": "๊ณ„์ • ์‚ญ์ œ", + "DELETEACCOUNT_DESC": "์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋ฉด ๋กœ๊ทธ์•„์›ƒ๋˜๋ฉฐ ๊ณ„์ •์— ๋‹ค์‹œ ์ ‘๊ทผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด ์ž‘์—…์€ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ์‹ ์ค‘ํžˆ ์ง„ํ–‰ํ•˜์„ธ์š”.", + "DELETEACCOUNT_BTN": "๊ณ„์ • ์‚ญ์ œ", + "DELETEACCOUNT_SUCCESS": "๊ณ„์ •์ด ์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!" + }, + "DETAILS": { + "DATECREATED": "์ƒ์„ฑ์ผ", + "DATECHANGED": "์ˆ˜์ •์ผ" + }, + "DIALOG": { + "DELETE_TITLE": "์‚ฌ์šฉ์ž ์‚ญ์ œ", + "DELETE_SELF_TITLE": "๊ณ„์ • ์‚ญ์ œ", + "DELETE_DESCRIPTION": "์‚ฌ์šฉ์ž๋ฅผ ์˜๊ตฌ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ •๋ง๋กœ ์ง„ํ–‰ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "DELETE_SELF_DESCRIPTION": "๊ฐœ์ธ ๊ณ„์ •์„ ์˜๊ตฌ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ž‘์—…์€ ์‚ฌ์šฉ์ž๋ฅผ ๋กœ๊ทธ์•„์›ƒํ•˜๊ณ  ๊ณ„์ •์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. ์ด ์ž‘์—…์€ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!", + "DELETE_AUTH_DESCRIPTION": "๊ฐœ์ธ ๊ณ„์ •์„ ์˜๊ตฌ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ •๋ง๋กœ ์ง„ํ–‰ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "TYPEUSERNAME": "'{{value}}'์„(๋ฅผ) ์ž…๋ ฅํ•˜์—ฌ ์‚ฌ์šฉ์ž๋ฅผ ์‚ญ์ œํ•˜์„ธ์š”.", + "USERNAME": "๋กœ๊ทธ์ธ ์ด๋ฆ„", + "DELETE_BTN": "์˜๊ตฌ ์‚ญ์ œ" + }, + "SENDEMAILDIALOG": { + "TITLE": "์ด๋ฉ”์ผ ์•Œ๋ฆผ ๋ณด๋‚ด๊ธฐ", + "DESCRIPTION": "ํ˜„์žฌ ์ด๋ฉ”์ผ ์ฃผ์†Œ๋กœ ์•Œ๋ฆผ์„ ๋ณด๋‚ด๋ ค๋ฉด ์•„๋ž˜ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๊ฑฐ๋‚˜ ํ•„๋“œ์—์„œ ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ๋ณ€๊ฒฝํ•˜์„ธ์š”.", + "NEWEMAIL": "์ƒˆ ์ด๋ฉ”์ผ ์ฃผ์†Œ" + }, + "SECRETDIALOG": { + "CLIENTSECRET": "ํด๋ผ์ด์–ธํŠธ ์‹œํฌ๋ฆฟ", + "CLIENTSECRET_DESCRIPTION": "ํด๋ผ์ด์–ธํŠธ ์‹œํฌ๋ฆฟ์€ ์•ˆ์ „ํ•œ ์žฅ์†Œ์— ๋ณด๊ด€ํ•˜์„ธ์š”. ์ด ๋Œ€ํ™” ์ƒ์ž๋ฅผ ๋‹ซ์œผ๋ฉด ๋‹ค์‹œ ๋ณผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." + }, + "TABLE": { + "DEACTIVATE": "๋น„ํ™œ์„ฑํ™”", + "ACTIVATE": "ํ™œ์„ฑํ™”", + "CHANGEDATE": "๋งˆ์ง€๋ง‰ ์ˆ˜์ •", + "CREATIONDATE": "์ƒ์„ฑ์ผ", + "FILTER": { + "0": "ํ‘œ์‹œ ์ด๋ฆ„์œผ๋กœ ํ•„ํ„ฐ๋ง", + "1": "์‚ฌ์šฉ์ž ์ด๋ฆ„์œผ๋กœ ํ•„ํ„ฐ๋ง", + "2": "ํ‘œ์‹œ ์ด๋ฆ„์œผ๋กœ ํ•„ํ„ฐ๋ง", + "3": "์‚ฌ์šฉ์ž ์ด๋ฆ„์œผ๋กœ ํ•„ํ„ฐ๋ง", + "4": "์ด๋ฉ”์ผ๋กœ ํ•„ํ„ฐ๋ง", + "5": "ํ‘œ์‹œ ์ด๋ฆ„์œผ๋กœ ํ•„ํ„ฐ๋ง", + "10": "์กฐ์ง ์ด๋ฆ„์œผ๋กœ ํ•„ํ„ฐ๋ง", + "12": "ํ”„๋กœ์ ํŠธ ์ด๋ฆ„์œผ๋กœ ํ•„ํ„ฐ๋ง" + }, + "EMPTY": "ํ•ญ๋ชฉ ์—†์Œ" + }, + "PASSWORDLESS": { + "SEND": "๋“ฑ๋ก ๋งํฌ ๋ณด๋‚ด๊ธฐ", + "TABLETYPE": "์œ ํ˜•", + "TABLESTATE": "์ƒํƒœ", + "NAME": "์ด๋ฆ„", + "EMPTY": "์„ค์ •๋œ ์žฅ์น˜ ์—†์Œ", + "TITLE": "๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์ธ์ฆ", + "DESCRIPTION": "ZITADEL์— ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†์ด ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ๋„๋ก WebAuthn ๊ธฐ๋ฐ˜ ์ธ์ฆ ๋ฐฉ๋ฒ•์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”.", + "MANAGE_DESCRIPTION": "์‚ฌ์šฉ์ž์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ฆ ์š”์†Œ๋ฅผ ๊ด€๋ฆฌํ•˜์„ธ์š”.", + "U2F": "๋ฐฉ๋ฒ• ์ถ”๊ฐ€", + "U2F_DIALOG_TITLE": "์ธ์ฆ๊ธฐ ํ™•์ธ", + "U2F_DIALOG_DESCRIPTION": "๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ์— ์‚ฌ์šฉํ•  ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”.", + "U2F_SUCCESS": "๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์ธ์ฆ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "U2F_ERROR": "์„ค์ • ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค!", + "U2F_NAME": "์ธ์ฆ๊ธฐ ์ด๋ฆ„", + "TYPE": { + "0": "๋‹ค์ค‘ ์ธ์ฆ ๋ฏธ์ •์˜", + "1": "์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ (OTP)", + "2": "์ง€๋ฌธ, ๋ณด์•ˆ ํ‚ค, Face ID ๋ฐ ๊ธฐํƒ€" + }, + "STATE": { + "0": "์ƒํƒœ ์—†์Œ", + "1": "์ค€๋น„๋˜์ง€ ์•Š์Œ", + "2": "์ค€๋น„๋จ", + "3": "์‚ญ์ œ๋จ" + }, + "DIALOG": { + "DELETE_TITLE": "๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์ธ์ฆ ๋ฐฉ๋ฒ• ์ œ๊ฑฐ", + "DELETE_DESCRIPTION": "๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์ธ์ฆ ๋ฐฉ๋ฒ•์„ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ง„ํ–‰ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "ADD_TITLE": "๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์ธ์ฆ", + "ADD_DESCRIPTION": "๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์ธ์ฆ ๋ฐฉ๋ฒ•์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์˜ต์…˜ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", + "SEND_DESCRIPTION": "์ด๋ฉ”์ผ ์ฃผ์†Œ๋กœ ๋“ฑ๋ก ๋งํฌ๋ฅผ ๋ณด๋‚ด์„ธ์š”.", + "SEND": "๋“ฑ๋ก ๋งํฌ ๋ณด๋‚ด๊ธฐ", + "SENT": "์ด๋ฉ”์ผ์ด ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐœ์†ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋ฉ”์ผํ•จ์„ ํ™•์ธํ•˜์—ฌ ์„ค์ •์„ ๊ณ„์† ์ง„ํ–‰ํ•˜์„ธ์š”.", + "QRCODE_DESCRIPTION": "๋‹ค๋ฅธ ์žฅ์น˜๋กœ ์Šค์บ”ํ•  QR ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”.", + "QRCODE": "QR ์ฝ”๋“œ ์ƒ์„ฑ", + "QRCODE_SCAN": "์„ค์ •์„ ๊ณ„์†ํ•˜๋ ค๋ฉด ์ด QR ์ฝ”๋“œ๋ฅผ ์Šค์บ”ํ•˜์„ธ์š”.", + "NEW_DESCRIPTION": "์ด ์žฅ์น˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์ธ์ฆ์„ ์„ค์ •ํ•˜์„ธ์š”.", + "NEW": "์ƒˆ๋กœ ์ถ”๊ฐ€" + } + }, + "MFA": { + "TABLETYPE": "์œ ํ˜•", + "TABLESTATE": "์ƒํƒœ", + "NAME": "์ด๋ฆ„", + "EMPTY": "์ถ”๊ฐ€ ์ธ์ฆ ์š”์†Œ ์—†์Œ", + "TITLE": "๋‹ค์ค‘ ์ธ์ฆ", + "DESCRIPTION": "๊ณ„์ •์˜ ๋ณด์•ˆ์„ ์œ„ํ•ด ๋‘ ๋ฒˆ์งธ ์ธ์ฆ ์š”์†Œ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”.", + "MANAGE_DESCRIPTION": "์‚ฌ์šฉ์ž์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ฆ ๋ฐฉ๋ฒ•์„ ๊ด€๋ฆฌํ•˜์„ธ์š”.", + "ADD": "์ธ์ฆ ์š”์†Œ ์ถ”๊ฐ€", + "OTP": "TOTP (์‹œ๊ฐ„ ๊ธฐ๋ฐ˜ ์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ)์šฉ ์ธ์ฆ ์•ฑ", + "OTP_DIALOG_TITLE": "OTP ์ถ”๊ฐ€", + "OTP_DIALOG_DESCRIPTION": "์ธ์ฆ ์•ฑ์œผ๋กœ QR ์ฝ”๋“œ๋ฅผ ์Šค์บ”ํ•˜๊ณ , ์•„๋ž˜์— ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜์—ฌ OTP ๋ฐฉ๋ฒ•์„ ๊ฒ€์ฆํ•˜๊ณ  ํ™œ์„ฑํ™”ํ•˜์„ธ์š”.", + "U2F": "์ง€๋ฌธ, ๋ณด์•ˆ ํ‚ค, Face ID ๋ฐ ๊ธฐํƒ€", + "U2F_DIALOG_TITLE": "์ธ์ฆ ์š”์†Œ ํ™•์ธ", + "U2F_DIALOG_DESCRIPTION": "์‚ฌ์šฉํ•  ๋‹ค์ค‘ ์ธ์ฆ์˜ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”.", + "U2F_SUCCESS": "์ธ์ฆ ์š”์†Œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "U2F_ERROR": "์„ค์ • ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค!", + "U2F_NAME": "์ธ์ฆ๊ธฐ ์ด๋ฆ„", + "OTPSMS": "SMS๋ฅผ ํ†ตํ•œ OTP (์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ)", + "OTPEMAIL": "์ด๋ฉ”์ผ์„ ํ†ตํ•œ OTP (์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ)", + "SETUPOTPSMSDESCRIPTION": "์ด ์ „ํ™”๋ฒˆํ˜ธ๋ฅผ OTP (์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ) ๋‘ ๋ฒˆ์งธ ์ธ์ฆ ์š”์†Œ๋กœ ์„ค์ •ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "OTPSMSSUCCESS": "OTP ์ธ์ฆ ์š”์†Œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "OTPSMSPHONEMUSTBEVERIFIED": "์ด ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "OTPEMAILSUCCESS": "OTP ์ธ์ฆ ์š”์†Œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "TYPE": { + "0": "๋‹ค์ค‘ ์ธ์ฆ ๋ฏธ์ •์˜", + "1": "์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ (OTP)", + "2": "์ง€๋ฌธ, ๋ณด์•ˆ ํ‚ค, Face ID ๋ฐ ๊ธฐํƒ€" + }, + "STATE": { + "0": "์ƒํƒœ ์—†์Œ", + "1": "์ค€๋น„๋˜์ง€ ์•Š์Œ", + "2": "์ค€๋น„๋จ", + "3": "์‚ญ์ œ๋จ" + }, + "DIALOG": { + "MFA_DELETE_TITLE": "๋‘ ๋ฒˆ์งธ ์ธ์ฆ ์š”์†Œ ์ œ๊ฑฐ", + "MFA_DELETE_DESCRIPTION": "๋‘ ๋ฒˆ์งธ ์ธ์ฆ ์š”์†Œ๋ฅผ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ •๋ง๋กœ ์ง„ํ–‰ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "ADD_MFA_TITLE": "๋‘ ๋ฒˆ์งธ ์ธ์ฆ ์š”์†Œ ์ถ”๊ฐ€", + "ADD_MFA_DESCRIPTION": "๋‹ค์Œ ์˜ต์…˜ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”." + } + }, + "EXTERNALIDP": { + "TITLE": "์™ธ๋ถ€ ID ์ œ๊ณต์ž", + "DESC": "", + "IDPCONFIGID": "ID ์ œ๊ณต์ž ๊ตฌ์„ฑ ID", + "IDPNAME": "ID ์ œ๊ณต์ž ์ด๋ฆ„", + "USERDISPLAYNAME": "์™ธ๋ถ€ ์ด๋ฆ„", + "EXTERNALUSERID": "์™ธ๋ถ€ ์‚ฌ์šฉ์ž ID", + "EMPTY": "์™ธ๋ถ€ ID ์ œ๊ณต์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค", + "DIALOG": { + "DELETE_TITLE": "ID ์ œ๊ณต์ž ์ œ๊ฑฐ", + "DELETE_DESCRIPTION": "์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ID ์ œ๊ณต์ž๋ฅผ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๊ณ„์†ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?" + } + }, + "CREATE": { + "TITLE": "์ƒˆ ์‚ฌ์šฉ์ž ์ƒ์„ฑ", + "DESCRIPTION": "ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", + "NAMEANDEMAILSECTION": "์ด๋ฆ„๊ณผ ์ด๋ฉ”์ผ", + "GENDERLANGSECTION": "์„ฑ๋ณ„๊ณผ ์–ธ์–ด", + "PHONESECTION": "์ „ํ™”๋ฒˆํ˜ธ", + "PASSWORDSECTION": "์ดˆ๊ธฐ ๋น„๋ฐ€๋ฒˆํ˜ธ", + "ADDRESSANDPHONESECTION": "์ „ํ™”๋ฒˆํ˜ธ", + "INITMAILDESCRIPTION": "๋‘ ์˜ต์…˜์ด ๋ชจ๋‘ ์„ ํƒ๋œ ๊ฒฝ์šฐ ์ดˆ๊ธฐํ™” ์ด๋ฉ”์ผ์ด ์ „์†ก๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ ์˜ต์…˜๋งŒ ์„ ํƒ๋œ ๊ฒฝ์šฐ ๋ฐ์ดํ„ฐ ์ œ๊ณต/ํ™•์ธ์„ ์œ„ํ•œ ์ด๋ฉ”์ผ์ด ์ „์†ก๋ฉ๋‹ˆ๋‹ค." + }, + "CODEDIALOG": { + "TITLE": "์ „ํ™”๋ฒˆํ˜ธ ํ™•์ธ", + "DESCRIPTION": "์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•˜๋ ค๋ฉด ๋ฌธ์ž ๋ฉ”์‹œ์ง€๋กœ ๋ฐ›์€ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", + "CODE": "์ฝ”๋“œ" + }, + "DATA": { + "STATE": "์ƒํƒœ", + "STATE0": "์•Œ ์ˆ˜ ์—†์Œ", + "STATE1": "ํ™œ์„ฑ", + "STATE2": "๋น„ํ™œ์„ฑ", + "STATE3": "์‚ญ์ œ๋จ", + "STATE4": "์ž ๊น€", + "STATE5": "์ผ์‹œ ์ค‘๋‹จ๋จ", + "STATE6": "์ดˆ๊ธฐ" + }, + "PROFILE": { + "TITLE": "ํ”„๋กœํ•„", + "EMAIL": "์ด๋ฉ”์ผ", + "PHONE": "์ „ํ™”๋ฒˆํ˜ธ", + "PHONE_HINT": "+ ๊ธฐํ˜ธ ๋‹ค์Œ์— ๊ตญ๊ฐ€ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜๊ฑฐ๋‚˜ ๋“œ๋กญ๋‹ค์šด์—์„œ ๊ตญ๊ฐ€๋ฅผ ์„ ํƒํ•œ ํ›„ ์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", + "USERNAME": "์‚ฌ์šฉ์ž ์ด๋ฆ„", + "CHANGEUSERNAME": "์ˆ˜์ •", + "CHANGEUSERNAME_TITLE": "์‚ฌ์šฉ์ž ์ด๋ฆ„ ๋ณ€๊ฒฝ", + "CHANGEUSERNAME_DESC": "์•„๋ž˜ ํ•„๋“œ์— ์ƒˆ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”.", + "FIRSTNAME": "์ด๋ฆ„", + "LASTNAME": "์„ฑ", + "NICKNAME": "๋ณ„๋ช…", + "DISPLAYNAME": "ํ‘œ์‹œ ์ด๋ฆ„", + "PREFERREDLOGINNAME": "์„ ํ˜ธ ๋กœ๊ทธ์ธ ์ด๋ฆ„", + "PREFERRED_LANGUAGE": "์–ธ์–ด", + "GENDER": "์„ฑ๋ณ„", + "PASSWORD": "๋น„๋ฐ€๋ฒˆํ˜ธ", + "AVATAR": { + "UPLOADTITLE": "ํ”„๋กœํ•„ ์‚ฌ์ง„ ์—…๋กœ๋“œ", + "UPLOADBTN": "ํŒŒ์ผ ์„ ํƒ", + "UPLOAD": "์—…๋กœ๋“œ", + "CURRENT": "ํ˜„์žฌ ์‚ฌ์ง„", + "PREVIEW": "๋ฏธ๋ฆฌ๋ณด๊ธฐ", + "DELETESUCCESS": "์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "CROPPERERROR": "ํŒŒ์ผ ์—…๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ํ•„์š” ์‹œ ๋‹ค๋ฅธ ํ˜•์‹๊ณผ ํฌ๊ธฐ๋ฅผ ์‹œ๋„ํ•˜์„ธ์š”." + }, + "COUNTRY": "๊ตญ๊ฐ€" + }, + "MACHINE": { + "TITLE": "์„œ๋น„์Šค ์‚ฌ์šฉ์ž ์„ธ๋ถ€ ์ •๋ณด", + "USERNAME": "์‚ฌ์šฉ์ž ์ด๋ฆ„", + "NAME": "์ด๋ฆ„", + "DESCRIPTION": "์„ค๋ช…", + "KEYSTITLE": "ํ‚ค", + "KEYSDESC": "ํ‚ค๋ฅผ ์ •์˜ํ•˜๊ณ  ๋งŒ๋ฃŒ ๋‚ ์งœ๋ฅผ ์„ ํƒ์ ์œผ๋กœ ์ถ”๊ฐ€ํ•˜์„ธ์š”.", + "TOKENSTITLE": "๊ฐœ์ธ ์ ‘๊ทผ ํ† ํฐ", + "TOKENSDESC": "๊ฐœ์ธ ์ ‘๊ทผ ํ† ํฐ์€ ์ผ๋ฐ˜์ ์ธ OAuth ์ ‘๊ทผ ํ† ํฐ๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.", + "ID": "ํ‚ค ID", + "TYPE": "์œ ํ˜•", + "EXPIRATIONDATE": "๋งŒ๋ฃŒ ๋‚ ์งœ", + "CHOOSEDATEAFTER": "์œ ํšจํ•œ ๋งŒ๋ฃŒ ๋‚ ์งœ๋ฅผ ์„ ํƒํ•˜์„ธ์š”", + "CHOOSEEXPIRY": "๋งŒ๋ฃŒ ๋‚ ์งœ ์„ ํƒ", + "CREATIONDATE": "์ƒ์„ฑ์ผ", + "KEYDETAILS": "ํ‚ค ์„ธ๋ถ€ ์ •๋ณด", + "ACCESSTOKENTYPE": "์ ‘๊ทผ ํ† ํฐ ์œ ํ˜•", + "ACCESSTOKENTYPES": { + "0": "Bearer", + "1": "JWT" + }, + "ADD": { + "TITLE": "ํ‚ค ์ถ”๊ฐ€", + "DESCRIPTION": "ํ‚ค ์œ ํ˜•์„ ์„ ํƒํ•˜๊ณ  ๋งŒ๋ฃŒ ๋‚ ์งœ๋ฅผ ์„ ํƒํ•˜์„ธ์š”." + }, + "ADDED": { + "TITLE": "ํ‚ค๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค", + "DESCRIPTION": "ํ‚ค๋ฅผ ๋‹ค์šด๋กœ๋“œํ•˜์„ธ์š”. ์ด ๋Œ€ํ™” ์ƒ์ž๋ฅผ ๋‹ซ์œผ๋ฉด ๋‹ค์‹œ ๋ณผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!" + }, + "KEYTYPES": { + "1": "JSON" + }, + "DIALOG": { + "DELETE_KEY": { + "TITLE": "ํ‚ค ์‚ญ์ œ", + "DESCRIPTION": "์„ ํƒํ•œ ํ‚ค๋ฅผ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? ์ด ์ž‘์—…์€ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." + } + } + }, + "PASSWORD": { + "TITLE": "๋น„๋ฐ€๋ฒˆํ˜ธ", + "LABEL": "์•ˆ์ „ํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ๊ณ„์ • ๋ณดํ˜ธ์— ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค", + "DESCRIPTION": "์•„๋ž˜ ์ •์ฑ…์— ๋”ฐ๋ผ ์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", + "OLD": "ํ˜„์žฌ ๋น„๋ฐ€๋ฒˆํ˜ธ", + "NEW": "์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ", + "CONFIRM": "์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ", + "NEWINITIAL": "๋น„๋ฐ€๋ฒˆํ˜ธ", + "CONFIRMINITIAL": "๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ", + "RESET": "ํ˜„์žฌ ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ •", + "SET": "์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ ์„ค์ •", + "RESENDNOTIFICATION": "๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ๋งํฌ ๋ณด๋‚ด๊ธฐ", + "REQUIRED": "ํ•„์ˆ˜ ํ•„๋“œ๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "MINLENGTHERROR": "{{value}}์ž ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "MAXLENGTHERROR": "{{value}}์ž ์ดํ•˜์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค." + }, + "ID": "ID", + "EMAIL": "์ด๋ฉ”์ผ", + "PHONE": "์ „ํ™”๋ฒˆํ˜ธ", + "PHONEEMPTY": "์ •์˜๋œ ์ „ํ™”๋ฒˆํ˜ธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค", + "PHONEVERIFIED": "์ „ํ™”๋ฒˆํ˜ธ๊ฐ€ ํ™•์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "EMAILVERIFIED": "์ด๋ฉ”์ผ์ด ํ™•์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค", + "NOTVERIFIED": "ํ™•์ธ๋˜์ง€ ์•Š์Œ", + "PREFERRED_LOGINNAME": "์„ ํ˜ธ ๋กœ๊ทธ์ธ ์ด๋ฆ„", + "ISINITIAL": "์‚ฌ์šฉ์ž๊ฐ€ ์•„์ง ํ™œ์„ฑํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.", + "LOGINMETHODS": { + "TITLE": "์—ฐ๋ฝ์ฒ˜ ์ •๋ณด", + "DESCRIPTION": "์ œ๊ณต๋œ ์ •๋ณด๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ์ด๋ฉ”์ผ ๋“ฑ์˜ ์ค‘์š”ํ•œ ์ •๋ณด๋ฅผ ์ „์†กํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.", + "EMAIL": { + "TITLE": "์ด๋ฉ”์ผ", + "VALID": "ํ™•์ธ๋จ", + "ISVERIFIED": "์ด๋ฉ”์ผ ํ™•์ธ๋จ", + "ISVERIFIEDDESC": "์ด๋ฉ”์ผ์ด ํ™•์ธ๋œ ๊ฒƒ์œผ๋กœ ํ‘œ์‹œ๋˜๋ฉด ์ด๋ฉ”์ผ ํ™•์ธ ์š”์ฒญ์ด ๋ณด๋‚ด์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + "RESEND": "์ด๋ฉ”์ผ ํ™•์ธ ์š”์ฒญ ๋‹ค์‹œ ๋ณด๋‚ด๊ธฐ", + "EDITTITLE": "์ด๋ฉ”์ผ ๋ณ€๊ฒฝ", + "EDITDESC": "์ƒˆ ์ด๋ฉ”์ผ์„ ์•„๋ž˜ ํ•„๋“œ์— ์ž…๋ ฅํ•˜์„ธ์š”." + }, + "PHONE": { + "TITLE": "์ „ํ™”", + "VALID": "ํ™•์ธ๋จ", + "RESEND": "์ธ์ฆ ๋ฌธ์ž ๋‹ค์‹œ ๋ณด๋‚ด๊ธฐ", + "EDITTITLE": "๋ฒˆํ˜ธ ๋ณ€๊ฒฝ", + "EDITVALUE": "์ „ํ™”๋ฒˆํ˜ธ", + "EDITDESC": "์ƒˆ ์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ์•„๋ž˜ ํ•„๋“œ์— ์ž…๋ ฅํ•˜์„ธ์š”.", + "DELETETITLE": "์ „ํ™”๋ฒˆํ˜ธ ์‚ญ์ œ", + "DELETEDESC": "์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "OTPSMSREMOVALWARNING": "์ด ๊ณ„์ •์€ ์ด ์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ๋‘ ๋ฒˆ์งธ ์ธ์ฆ ์š”์†Œ๋กœ ์‚ฌ์šฉ ์ค‘์ž…๋‹ˆ๋‹ค. ์‚ญ์ œ ํ›„์—๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." + }, + "RESENDCODE": "์ฝ”๋“œ ๋‹ค์‹œ ๋ณด๋‚ด๊ธฐ", + "ENTERCODE": "ํ™•์ธ", + "ENTERCODE_DESC": "์ฝ”๋“œ ํ™•์ธ" + }, + "GRANTS": { + "TITLE": "์‚ฌ์šฉ์ž ๊ถŒํ•œ", + "DESCRIPTION": "ํŠน์ • ํ”„๋กœ์ ํŠธ์— ๋Œ€ํ•œ ์ด ์‚ฌ์šฉ์ž์˜ ์ ‘๊ทผ์„ ๋ถ€์—ฌํ•˜์„ธ์š”", + "CREATE": { + "TITLE": "์‚ฌ์šฉ์ž ๊ถŒํ•œ ์ƒ์„ฑ", + "DESCRIPTION": "์กฐ์ง, ํ”„๋กœ์ ํŠธ ๋ฐ ๊ด€๋ จ ํ”„๋กœ์ ํŠธ ์—ญํ• ์„ ๊ฒ€์ƒ‰ํ•˜์„ธ์š”." + }, + "PROJECTNAME": "ํ”„๋กœ์ ํŠธ ์ด๋ฆ„", + "PROJECT-OWNED": "ํ”„๋กœ์ ํŠธ", + "PROJECT-GRANTED": "๋ถ€์—ฌ๋œ ํ”„๋กœ์ ํŠธ", + "FILTER": { + "0": "์‚ฌ์šฉ์ž๋กœ ํ•„ํ„ฐ๋ง", + "1": "๋„๋ฉ”์ธ์œผ๋กœ ํ•„ํ„ฐ๋ง", + "2": "ํ”„๋กœ์ ํŠธ ์ด๋ฆ„์œผ๋กœ ํ•„ํ„ฐ๋ง", + "3": "์—ญํ•  ์ด๋ฆ„์œผ๋กœ ํ•„ํ„ฐ๋ง" + } + }, + "STATE": { + "0": "์•Œ ์ˆ˜ ์—†์Œ", + "1": "ํ™œ์„ฑ", + "2": "๋น„ํ™œ์„ฑ", + "3": "์‚ญ์ œ๋จ", + "4": "์ž ๊น€", + "5": "์ผ์‹œ ์ค‘๋‹จ๋จ", + "6": "์ดˆ๊ธฐ" + }, + "SEARCH": { + "ADDITIONAL": "๋กœ๊ทธ์ธ ์ด๋ฆ„ (ํ˜„์žฌ ์กฐ์ง)", + "ADDITIONAL-EXTERNAL": "๋กœ๊ทธ์ธ ์ด๋ฆ„ (์™ธ๋ถ€ ์กฐ์ง)" + }, + "TARGET": { + "SELF": "๋‹ค๋ฅธ ์กฐ์ง์˜ ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๋ ค๋ฉด", + "EXTERNAL": "์กฐ์ง์˜ ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๋ ค๋ฉด", + "CLICKHERE": "์—ฌ๊ธฐ๋ฅผ ํด๋ฆญํ•˜์„ธ์š”" + }, + "SIGNEDOUT": "๋กœ๊ทธ์•„์›ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•˜๋ ค๋ฉด '๋กœ๊ทธ์ธ' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”.", + "SIGNEDOUT_BTN": "๋กœ๊ทธ์ธ", + "EDITACCOUNT": "๊ณ„์ • ํŽธ์ง‘", + "ADDACCOUNT": "๋‹ค๋ฅธ ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธ", + "RESENDINITIALEMAIL": "ํ™œ์„ฑํ™” ์ด๋ฉ”์ผ ๋‹ค์‹œ ๋ณด๋‚ด๊ธฐ", + "RESENDEMAILNOTIFICATION": "์ด๋ฉ”์ผ ์•Œ๋ฆผ ๋‹ค์‹œ ๋ณด๋‚ด๊ธฐ", + "TOAST": { + "CREATED": "์‚ฌ์šฉ์ž๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "SAVED": "ํ”„๋กœํ•„์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "USERNAMECHANGED": "์‚ฌ์šฉ์ž ์ด๋ฆ„์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "EMAILSAVED": "์ด๋ฉ”์ผ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "INITEMAILSENT": "์ดˆ๊ธฐํ™” ์ด๋ฉ”์ผ์ด ์ „์†ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "PHONESAVED": "์ „ํ™”๋ฒˆํ˜ธ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "PHONEREMOVED": "์ „ํ™”๋ฒˆํ˜ธ๊ฐ€ ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "PHONEVERIFIED": "์ „ํ™”๋ฒˆํ˜ธ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ํ™•์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "PHONEVERIFICATIONSENT": "์ „ํ™”๋ฒˆํ˜ธ ํ™•์ธ ์ฝ”๋“œ๊ฐ€ ์ „์†ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "EMAILVERIFICATIONSENT": "์ด๋ฉ”์ผ ํ™•์ธ ์ฝ”๋“œ๊ฐ€ ์ „์†ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "OTPREMOVED": "OTP๊ฐ€ ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "U2FREMOVED": "์ธ์ฆ ์š”์†Œ๊ฐ€ ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "PASSWORDLESSREMOVED": "๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์ธ์ฆ์ด ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "INITIALPASSWORDSET": "์ดˆ๊ธฐ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "PASSWORDNOTIFICATIONSENT": "๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์•Œ๋ฆผ์ด ์ „์†ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "PASSWORDCHANGED": "๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "REACTIVATED": "์‚ฌ์šฉ์ž๊ฐ€ ์žฌํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "DEACTIVATED": "์‚ฌ์šฉ์ž๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "SELECTEDREACTIVATED": "์„ ํƒ๋œ ์‚ฌ์šฉ์ž๊ฐ€ ์žฌํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "SELECTEDDEACTIVATED": "์„ ํƒ๋œ ์‚ฌ์šฉ์ž๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "SELECTEDKEYSDELETED": "์„ ํƒ๋œ ํ‚ค๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "KEYADDED": "ํ‚ค๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "MACHINEADDED": "์„œ๋น„์Šค ์‚ฌ์šฉ์ž๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "DELETED": "์‚ฌ์šฉ์ž๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "UNLOCKED": "์‚ฌ์šฉ์ž๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ž ๊ธˆ ํ•ด์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "PASSWORDLESSREGISTRATIONSENT": "๋“ฑ๋ก ๋งํฌ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ „์†ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "SECRETGENERATED": "์‹œํฌ๋ฆฟ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "SECRETREMOVED": "์‹œํฌ๋ฆฟ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!" + }, + "MEMBERSHIPS": { + "TITLE": "ZITADEL ๊ด€๋ฆฌ์ž ์—ญํ• ", + "DESCRIPTION": "์ด ์‚ฌ์šฉ์ž์˜ ๋ชจ๋“  ๋ฉค๋ฒ„ ๊ถŒํ•œ์ž…๋‹ˆ๋‹ค. ์กฐ์ง, ํ”„๋กœ์ ํŠธ ๋˜๋Š” IAM ์„ธ๋ถ€ ํŽ˜์ด์ง€์—์„œ ์ˆ˜์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.", + "ORGCONTEXT": "ํ˜„์žฌ ์„ ํƒ๋œ ์กฐ์ง๊ณผ ๊ด€๋ จ๋œ ๋ชจ๋“  ์กฐ์ง๊ณผ ํ”„๋กœ์ ํŠธ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "USERCONTEXT": "์‚ฌ์šฉ ๊ถŒํ•œ์ด ์žˆ๋Š” ๋ชจ๋“  ์กฐ์ง๊ณผ ํ”„๋กœ์ ํŠธ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์กฐ์ง๋„ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.", + "CREATIONDATE": "์ƒ์„ฑ ๋‚ ์งœ", + "CHANGEDATE": "๋งˆ์ง€๋ง‰ ์ˆ˜์ •", + "DISPLAYNAME": "ํ‘œ์‹œ ์ด๋ฆ„", + "REMOVE": "์ œ๊ฑฐ", + "TYPE": "์œ ํ˜•", + "ORGID": "์กฐ์ง ID", + "UPDATED": "๋ฉค๋ฒ„์‹ญ์ด ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "NOPERMISSIONTOEDIT": "์—ญํ• ์„ ํŽธ์ง‘ํ•  ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค!", + "TYPES": { + "UNKNOWN": "์•Œ ์ˆ˜ ์—†์Œ", + "ORG": "์กฐ์ง", + "PROJECT": "ํ”„๋กœ์ ํŠธ", + "GRANTEDPROJECT": "๋ถ€์—ฌ๋œ ํ”„๋กœ์ ํŠธ" + } + }, + "PERSONALACCESSTOKEN": { + "ID": "ID", + "TOKEN": "ํ† ํฐ", + "ADD": { + "TITLE": "์ƒˆ๋กœ์šด ๊ฐœ์ธ ์•ก์„ธ์Šค ํ† ํฐ ์ƒ์„ฑ", + "DESCRIPTION": "ํ† ํฐ์˜ ์‚ฌ์šฉ์ž ์ •์˜ ๋งŒ๋ฃŒ์ผ์„ ์„ค์ •ํ•˜์„ธ์š”.", + "CHOOSEEXPIRY": "๋งŒ๋ฃŒ ๋‚ ์งœ ์„ ํƒ", + "CHOOSEDATEAFTER": "์œ ํšจํ•œ ๋งŒ๋ฃŒ์ผ ์ž…๋ ฅ" + }, + "ADDED": { + "TITLE": "๊ฐœ์ธ ์•ก์„ธ์Šค ํ† ํฐ", + "DESCRIPTION": "๊ฐœ์ธ ์•ก์„ธ์Šค ํ† ํฐ์„ ๋ณต์‚ฌํ•˜์„ธ์š”. ๋‹ค์‹œ ๋ณผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!" + }, + "DELETE": { + "TITLE": "ํ† ํฐ ์‚ญ์ œ", + "DESCRIPTION": "๊ฐœ์ธ ์•ก์„ธ์Šค ํ† ํฐ์„ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ํ™•์‹คํ•ฉ๋‹ˆ๊นŒ?" + }, + "DELETED": "ํ† ํฐ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + } + }, + "METADATA": { + "TITLE": "๋ฉ”ํƒ€๋ฐ์ดํ„ฐ", + "KEY": "ํ‚ค", + "VALUE": "๊ฐ’", + "ADD": "์ƒˆ ํ•ญ๋ชฉ", + "SAVE": "์ €์žฅ", + "EMPTY": "๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์—†์Œ", + "SETSUCCESS": "ํ•ญ๋ชฉ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค", + "REMOVESUCCESS": "ํ•ญ๋ชฉ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค" + }, + "FLOWS": { + "ID": "ID", + "NAME": "์ด๋ฆ„", + "STATE": "์ƒํƒœ", + "STATES": { + "0": "์ƒํƒœ ์—†์Œ", + "1": "๋น„ํ™œ์„ฑ", + "2": "ํ™œ์„ฑ" + }, + "ADDTRIGGER": "ํŠธ๋ฆฌ๊ฑฐ ์ถ”๊ฐ€", + "FLOWCHANGED": "ํ”Œ๋กœ์šฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค", + "FLOWCLEARED": "ํ”Œ๋กœ์šฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ดˆ๊ธฐํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค", + "TIMEOUT": "์‹œ๊ฐ„ ์ดˆ๊ณผ", + "TIMEOUTINSEC": "์ดˆ ๋‹จ์œ„ ์‹œ๊ฐ„ ์ดˆ๊ณผ", + "ALLOWEDTOFAIL": "์‹คํŒจ ํ—ˆ์šฉ", + "ALLOWEDTOFAILWARN": { + "TITLE": "๊ฒฝ๊ณ ", + "DESCRIPTION": "์ด ์„ค์ •์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ฉด ์กฐ์ง ๋‚ด ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์—†๊ฒŒ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์ฝ˜์†”์— ๋‹ค์‹œ ์ ‘๊ทผํ•˜์—ฌ ์ž‘์—…์„ ๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์—†๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋ณ„๋„์˜ ์กฐ์ง์—์„œ ๊ด€๋ฆฌ์ž ๊ณ„์ •์„ ๋งŒ๋“ค๊ฑฐ๋‚˜ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๋˜๋Š” ๊ฐœ๋ฐœ ์กฐ์ง์—์„œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋จผ์ € ํ…Œ์ŠคํŠธํ•  ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค." + }, + "SCRIPT": "์Šคํฌ๋ฆฝํŠธ", + "FLOWTYPE": "ํ”Œ๋กœ์šฐ ์œ ํ˜•", + "TRIGGERTYPE": "ํŠธ๋ฆฌ๊ฑฐ ์œ ํ˜•", + "ACTIONS": "์ž‘์—…", + "ACTIONSMAX": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ž‘์—…์˜ ์ˆ˜๋Š” ํ‹ฐ์–ด์— ๋”ฐ๋ผ ์ œํ•œ๋ฉ๋‹ˆ๋‹ค ({{value}}). ํ•„์š”ํ•˜์ง€ ์•Š์€ ์ž‘์—…์€ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ฑฐ๋‚˜ ํ‹ฐ์–ด ์—…๊ทธ๋ ˆ์ด๋“œ๋ฅผ ๊ณ ๋ คํ•˜์„ธ์š”.", + "DIALOG": { + "ADD": { + "TITLE": "์ž‘์—… ์ƒ์„ฑ" + }, + "UPDATE": { + "TITLE": "์ž‘์—… ์—…๋ฐ์ดํŠธ" + }, + "DELETEACTION": { + "TITLE": "์ž‘์—… ์‚ญ์ œ?", + "DESCRIPTION": "์ž‘์—…์„ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ž‘์—…์€ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ํ™•์‹คํ•ฉ๋‹ˆ๊นŒ?", + "DELETE_SUCCESS": "์ž‘์—…์ด ์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + }, + "CLEAR": { + "TITLE": "ํ”Œ๋กœ์šฐ ์ดˆ๊ธฐํ™”?", + "DESCRIPTION": "ํŠธ๋ฆฌ๊ฑฐ ๋ฐ ์ž‘์—…๊ณผ ํ•จ๊ป˜ ํ”Œ๋กœ์šฐ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ณ€๊ฒฝ ์‚ฌํ•ญ์€ ๋ณต๊ตฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ํ™•์‹คํ•ฉ๋‹ˆ๊นŒ?" + }, + "REMOVEACTIONSLIST": { + "TITLE": "์„ ํƒํ•œ ์ž‘์—… ์‚ญ์ œ?", + "DESCRIPTION": "์„ ํƒํ•œ ์ž‘์—…์„ ํ”Œ๋กœ์šฐ์—์„œ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?" + }, + "ABOUTNAME": "์ž‘์—… ์ด๋ฆ„๊ณผ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํ•จ์ˆ˜ ์ด๋ฆ„์€ ๋™์ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค" + }, + "TOAST": { + "ACTIONSSET": "์ž‘์—…์ด ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค", + "ACTIONREACTIVATED": "์ž‘์—…์ด ์„ฑ๊ณต์ ์œผ๋กœ ์žฌํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค", + "ACTIONDEACTIVATED": "์ž‘์—…์ด ์„ฑ๊ณต์ ์œผ๋กœ ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค" + } + }, + "IAM": { + "POLICIES": { + "TITLE": "์‹œ์Šคํ…œ ์ •์ฑ… ๋ฐ ์•ก์„ธ์Šค ์„ค์ •", + "DESCRIPTION": "์ „์—ญ ์ •์ฑ… ๋ฐ ๊ด€๋ฆฌ ์•ก์„ธ์Šค ์„ค์ •์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค." + }, + "EVENTSTORE": { + "TITLE": "IAM ์Šคํ† ๋ฆฌ์ง€ ๊ด€๋ฆฌ", + "DESCRIPTION": "ZITADEL ๋ทฐ ๋ฐ ์‹คํŒจํ•œ ์ด๋ฒคํŠธ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค." + }, + "MEMBER": { + "TITLE": "๊ด€๋ฆฌ์ž", + "DESCRIPTION": "์ด ๊ด€๋ฆฌ์ž๋“ค์€ ์ธ์Šคํ„ด์Šค ๋‚ด์—์„œ ๋ณ€๊ฒฝํ•  ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "PAGES": { + "STATE": "์ƒํƒœ", + "DOMAINLIST": "์‚ฌ์šฉ์ž ์ •์˜ ๋„๋ฉ”์ธ" + }, + "STATE": { + "0": "๋ฏธ์ง€์ •", + "1": "์ƒ์„ฑ ์ค‘", + "2": "์‹คํ–‰ ์ค‘", + "3": "์ค‘์ง€ ์ค‘", + "4": "์ค‘์ง€๋จ" + }, + "VIEWS": { + "VIEWNAME": "์ด๋ฆ„", + "DATABASE": "๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค", + "SEQUENCE": "์ˆœ์„œ", + "EVENTTIMESTAMP": "ํƒ€์ž„์Šคํƒฌํ”„", + "LASTSPOOL": "์„ฑ๊ณต์  ์Šคํ’€", + "ACTIONS": "์ž‘์—…", + "CLEAR": "์ง€์šฐ๊ธฐ", + "CLEARED": "๋ทฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ง€์›Œ์กŒ์Šต๋‹ˆ๋‹ค!", + "DIALOG": { + "VIEW_CLEAR_TITLE": "๋ทฐ ์ง€์šฐ๊ธฐ", + "VIEW_CLEAR_DESCRIPTION": "๋ทฐ๋ฅผ ์ง€์šฐ๋ ค ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ทฐ๋ฅผ ์ง€์šฐ๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ์ตœ์ข… ์‚ฌ์šฉ์ž์—๊ฒŒ ์‚ฌ์šฉ ๋ถˆ๊ฐ€ํ•œ ์ƒํƒœ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ •๋ง ํ™•์‹คํ•ฉ๋‹ˆ๊นŒ?" + } + }, + "FAILEDEVENTS": { + "VIEWNAME": "์ด๋ฆ„", + "DATABASE": "๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค", + "FAILEDSEQUENCE": "์‹คํŒจํ•œ ์ˆœ์„œ", + "FAILURECOUNT": "์‹คํŒจ ํšŸ์ˆ˜", + "LASTFAILED": "๋งˆ์ง€๋ง‰ ์‹คํŒจ ์‹œ์ ", + "ERRORMESSAGE": "์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€", + "ACTIONS": "์ž‘์—…", + "DELETE": "์‚ญ์ œ", + "DELETESUCCESS": "์‹คํŒจํ•œ ์ด๋ฒคํŠธ๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + }, + "EVENTS": { + "EDITOR": "ํŽธ์ง‘์ž", + "EDITORID": "ํŽธ์ง‘์ž ID", + "AGGREGATE": "์ง‘ํ•ฉ", + "AGGREGATEID": "์ง‘ํ•ฉ ID", + "AGGREGATETYPE": "์ง‘ํ•ฉ ์œ ํ˜•", + "RESOURCEOWNER": "๋ฆฌ์†Œ์Šค ์†Œ์œ ์ž", + "SEQUENCE": "์ˆœ์„œ", + "CREATIONDATE": "์ƒ์„ฑ์ผ", + "TYPE": "์œ ํ˜•", + "PAYLOAD": "ํŽ˜์ด๋กœ๋“œ", + "FILTERS": { + "BTN": "ํ•„ํ„ฐ", + "USER": { + "IDLABEL": "ID", + "CHECKBOX": "ํŽธ์ง‘์ž๋กœ ํ•„ํ„ฐ" + }, + "AGGREGATE": { + "TYPELABEL": "์ง‘ํ•ฉ ์œ ํ˜•", + "IDLABEL": "ID", + "CHECKBOX": "์ง‘ํ•ฉ์œผ๋กœ ํ•„ํ„ฐ" + }, + "TYPE": { + "TYPELABEL": "์œ ํ˜•", + "CHECKBOX": "์œ ํ˜•์œผ๋กœ ํ•„ํ„ฐ" + }, + "RESOURCEOWNER": { + "LABEL": "ID", + "CHECKBOX": "๋ฆฌ์†Œ์Šค ์†Œ์œ ์ž๋กœ ํ•„ํ„ฐ" + }, + "SEQUENCE": { + "LABEL": "์ˆœ์„œ", + "CHECKBOX": "์ˆœ์„œ๋กœ ํ•„ํ„ฐ" + }, + "SORT": "์ •๋ ฌ", + "ASC": "์˜ค๋ฆ„์ฐจ์ˆœ", + "DESC": "๋‚ด๋ฆผ์ฐจ์ˆœ", + "CREATIONDATE": { + "RADIO_FROM": "๋ถ€ํ„ฐ", + "RADIO_RANGE": "๋ฒ”์œ„", + "LABEL_SINCE": "์ดํ›„", + "LABEL_UNTIL": "๊นŒ์ง€" + }, + "OTHER": "๊ธฐํƒ€", + "OTHERS": "๊ธฐํƒ€๋“ค" + }, + "DIALOG": { + "TITLE": "์ด๋ฒคํŠธ ์ƒ์„ธ ์ •๋ณด" + } + }, + "TOAST": { + "MEMBERREMOVED": "๊ด€๋ฆฌ์ž๊ฐ€ ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "MEMBERSADDED": "๊ด€๋ฆฌ์ž๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "MEMBERADDED": "๊ด€๋ฆฌ์ž๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "MEMBERCHANGED": "๊ด€๋ฆฌ์ž๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "ROLEREMOVED": "์—ญํ• ์ด ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "ROLECHANGED": "์—ญํ• ์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "REACTIVATED": "์žฌํ™œ์„ฑํ™”๋จ", + "DEACTIVATED": "๋น„ํ™œ์„ฑํ™”๋จ" + } + }, + "ORG": { + "PAGES": { + "NAME": "์ด๋ฆ„", + "ID": "ID", + "CREATIONDATE": "์ƒ์„ฑ์ผ", + "DATECHANGED": "๋ณ€๊ฒฝ์ผ", + "FILTER": "ํ•„ํ„ฐ", + "FILTERPLACEHOLDER": "์ด๋ฆ„์œผ๋กœ ํ•„ํ„ฐ๋ง", + "LIST": "์กฐ์ง", + "LISTDESCRIPTION": "์กฐ์ง์„ ์„ ํƒํ•˜์„ธ์š”.", + "ACTIVE": "ํ™œ์„ฑ", + "CREATE": "์กฐ์ง ์ƒ์„ฑ", + "DEACTIVATE": "์กฐ์ง ๋น„ํ™œ์„ฑํ™”", + "REACTIVATE": "์กฐ์ง ์žฌํ™œ์„ฑํ™”", + "NOPERMISSION": "์กฐ์ง ์„ค์ •์— ์ ‘๊ทผํ•  ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค.", + "USERSELFACCOUNT": "๊ฐœ์ธ ๊ณ„์ •์„ ์กฐ์ง ์†Œ์œ ์ž๋กœ ์‚ฌ์šฉ", + "ORGDETAIL_TITLE": "์ƒˆ ์กฐ์ง์˜ ์ด๋ฆ„๊ณผ ๋„๋ฉ”์ธ์„ ์ž…๋ ฅํ•˜์„ธ์š”.", + "ORGDETAIL_TITLE_WITHOUT_DOMAIN": "์ƒˆ ์กฐ์ง์˜ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”.", + "ORGDETAILUSER_TITLE": "์กฐ์ง ์†Œ์œ ์ž ์„ค์ •", + "DELETE": "์กฐ์ง ์‚ญ์ œ", + "DEFAULTLABEL": "๊ธฐ๋ณธ", + "SETASDEFAULT": "๊ธฐ๋ณธ ์กฐ์ง์œผ๋กœ ์„ค์ •", + "DEFAULTORGSET": "๊ธฐ๋ณธ ์กฐ์ง์ด ์„ฑ๊ณต์ ์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค", + "RENAME": { + "ACTION": "์ด๋ฆ„ ๋ณ€๊ฒฝ", + "TITLE": "์กฐ์ง ์ด๋ฆ„ ๋ณ€๊ฒฝ", + "DESCRIPTION": "์ƒˆ ์กฐ์ง ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”", + "BTN": "์ด๋ฆ„ ๋ณ€๊ฒฝ" + }, + "ORGDOMAIN": { + "TITLE": "{{value}} ์†Œ์œ ๊ถŒ ํ™•์ธ", + "VERIFICATION": "๋„๋ฉ”์ธ์„ ์ˆ˜๋™์œผ๋กœ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:", + "VERIFICATION_HTML": "- HTTP. ์›น์‚ฌ์ดํŠธ์— ์ž„์‹œ ๊ฒ€์ฆ ํŒŒ์ผ์„ ํ˜ธ์ŠคํŒ…ํ•˜์„ธ์š”", + "VERIFICATION_DNS": "- DNS. TXT ๋ ˆ์ฝ”๋“œ DNS ํ•ญ๋ชฉ์„ ์ƒ์„ฑํ•˜์„ธ์š”", + "VERIFICATION_DNS_DESC": "{{value}}์„ ๊ด€๋ฆฌํ•˜๊ณ  DNS ๊ธฐ๋ก์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, ๋‹ค์Œ ๊ฐ’์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ TXT ๋ ˆ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:", + "VERIFICATION_DNS_HOST_LABEL": "ํ˜ธ์ŠคํŠธ:", + "VERIFICATION_DNS_CHALLENGE_LABEL": "TXT ๋ ˆ์ฝ”๋“œ ๊ฐ’์œผ๋กœ ์ด ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”:", + "VERIFICATION_HTTP_DESC": "์›น์‚ฌ์ดํŠธ ํ˜ธ์ŠคํŒ…์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, ๊ฒ€์ฆ ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ์ œ๊ณต๋œ URL์— ์—…๋กœ๋“œํ•˜์„ธ์š”", + "VERIFICATION_HTTP_URL_LABEL": "์˜ˆ์ƒ URL:", + "VERIFICATION_HTTP_FILE_LABEL": "๊ฒ€์ฆ ํŒŒ์ผ:", + "VERIFICATION_SKIP": "์ง€๊ธˆ์€ ๊ฒ€์ฆ์„ ๊ฑด๋„ˆ๋›ฐ๊ณ  ์กฐ์ง ์ƒ์„ฑ์„ ๊ณ„์†ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋„๋ฉ”์ธ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์ด ๋‹จ๊ณ„๋ฅผ ์™„๋ฃŒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค!", + "VERIFICATION_VALIDATION_DESC": "๊ฒ€์ฆ ์ฝ”๋“œ๋ฅผ ์‚ญ์ œํ•˜์ง€ ๋งˆ์„ธ์š”. ZITADEL์€ ์ฃผ๊ธฐ์ ์œผ๋กœ ๋„๋ฉ”์ธ ์†Œ์œ ๊ถŒ์„ ๋‹ค์‹œ ํ™•์ธํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.", + "VERIFICATION_NEWTOKEN_TITLE": "์ƒˆ ํ† ํฐ ์š”์ฒญ", + "VERIFICATION_VALIDATION_ONGOING": "{{value}} ๋ฐฉ๋ฒ•์ด ๋„๋ฉ”์ธ ๊ฒ€์ฆ์„ ์œ„ํ•ด ์„ ํƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ฒ€์ฆ ๊ฒ€์‚ฌ๋ฅผ ์‹คํ–‰ํ•˜๊ฑฐ๋‚˜ ๊ฒ€์ฆ ํ”„๋กœ์„ธ์Šค๋ฅผ ์žฌ์„ค์ •ํ•˜๋ ค๋ฉด ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”.", + "VERIFICATION_SUCCESSFUL": "๋„๋ฉ”์ธ์ด ์„ฑ๊ณต์ ์œผ๋กœ ๊ฒ€์ฆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "RESETMETHOD": "๊ฒ€์ฆ ๋ฐฉ๋ฒ• ์žฌ์„ค์ •" + }, + "DOWNLOAD_FILE": "ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ", + "SELECTORGTOOLTIP": "์ด ์กฐ์ง์„ ์„ ํƒํ•˜์„ธ์š”.", + "PRIMARYDOMAIN": "๊ธฐ๋ณธ ๋„๋ฉ”์ธ", + "STATE": "์ƒํƒœ", + "USEPASSWORD": "์ดˆ๊ธฐ ๋น„๋ฐ€๋ฒˆํ˜ธ ์„ค์ •", + "USEPASSWORDDESC": "์ดˆ๊ธฐํ™” ์ค‘์— ์‚ฌ์šฉ์ž๊ฐ€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์„ค์ •ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค." + }, + "LIST": { + "TITLE": "์กฐ์ง", + "DESCRIPTION": "์ด ์ธ์Šคํ„ด์Šค์˜ ์กฐ์ง๋“ค" + }, + "DOMAINS": { + "NEW": "๋„๋ฉ”์ธ ์ถ”๊ฐ€", + "TITLE": "๊ฒ€์ฆ๋œ ๋„๋ฉ”์ธ", + "DESCRIPTION": "์กฐ์ง ๋„๋ฉ”์ธ์„ ๊ตฌ์„ฑํ•˜์„ธ์š”. ์ด ๋„๋ฉ”์ธ์€ ๋„๋ฉ”์ธ ๊ฒ€์ƒ‰ ๋ฐ ์‚ฌ์šฉ์ž ์ด๋ฆ„ ์ ‘๋ฏธ์‚ฌ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "SETPRIMARY": "๊ธฐ๋ณธ์œผ๋กœ ์„ค์ •", + "DELETE": { + "TITLE": "๋„๋ฉ”์ธ ์‚ญ์ œ", + "DESCRIPTION": "๋„๋ฉ”์ธ ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค." + }, + "ADD": { + "TITLE": "๋„๋ฉ”์ธ ์ถ”๊ฐ€", + "DESCRIPTION": "์กฐ์ง์— ๋„๋ฉ”์ธ์„ ์ถ”๊ฐ€ํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กœ์„ธ์Šค๊ฐ€ ์„ฑ๊ณตํ•˜๋ฉด, ์ด ๋„๋ฉ”์ธ์€ ๋„๋ฉ”์ธ ๊ฒ€์ƒ‰ ๋ฐ ์‚ฌ์šฉ์ž ์ ‘๋ฏธ์‚ฌ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + } + }, + "STATE": { + "0": "๋ฏธ์ •", + "1": "ํ™œ์„ฑ", + "2": "๋น„ํ™œ์„ฑํ™”๋จ" + }, + "MEMBER": { + "TITLE": "์กฐ์ง ๊ด€๋ฆฌ์ž", + "DESCRIPTION": "์กฐ์ง ์„ค์ •์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” ์‚ฌ์šฉ์ž๋ฅผ ์ •์˜ํ•˜์„ธ์š”." + }, + "TOAST": { + "UPDATED": "์กฐ์ง์ด ์„ฑ๊ณต์ ์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "DEACTIVATED": "์กฐ์ง์ด ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "REACTIVATED": "์กฐ์ง์ด ์žฌํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "DOMAINADDED": "๋„๋ฉ”์ธ์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "DOMAINREMOVED": "๋„๋ฉ”์ธ์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "MEMBERADDED": "๊ด€๋ฆฌ์ž๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "MEMBERREMOVED": "๊ด€๋ฆฌ์ž๊ฐ€ ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "MEMBERCHANGED": "๊ด€๋ฆฌ์ž๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "SETPRIMARY": "๊ธฐ๋ณธ ๋„๋ฉ”์ธ์ด ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "DELETED": "์กฐ์ง์ด ์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค", + "DEFAULTORGNOTFOUND": "๊ธฐ๋ณธ ์กฐ์ง์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค", + "ORG_WAS_DELETED": "์กฐ์ง์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + }, + "DIALOG": { + "DEACTIVATE": { + "TITLE": "์กฐ์ง ๋น„ํ™œ์„ฑํ™”", + "DESCRIPTION": "์กฐ์ง์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ดํ›„์—๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ณ„์† ์ง„ํ–‰ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?" + }, + "REACTIVATE": { + "TITLE": "์กฐ์ง ์žฌํ™œ์„ฑํ™”", + "DESCRIPTION": "์กฐ์ง์„ ์žฌํ™œ์„ฑํ™”ํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ณ„์† ์ง„ํ–‰ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?" + }, + "DELETE": { + "TITLE": "์กฐ์ง ์‚ญ์ œ", + "DESCRIPTION": "์กฐ์ง์„ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ž‘์—…์€ ์กฐ์ง๊ณผ ๊ด€๋ จ๋œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•˜๋Š” ๊ณผ์ •์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ๋กœ์„œ๋Š” ์ด ์ž‘์—…์„ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", + "TYPENAME": "์กฐ์ง์„ ์‚ญ์ œํ•˜๋ ค๋ฉด '{{value}}'์„(๋ฅผ) ์ž…๋ ฅํ•˜์„ธ์š”.", + "ORGNAME": "์ด๋ฆ„", + "BTN": "์‚ญ์ œ" + } + } + }, + "SETTINGS": { + "LIST": { + "ORGS": "์กฐ์ง", + "FEATURESETTINGS": "๊ธฐ๋Šฅ ์„ค์ •", + "LANGUAGES": "์–ธ์–ด", + "LOGIN": "๋กœ๊ทธ์ธ ๋™์ž‘ ๋ฐ ๋ณด์•ˆ", + "LOCKOUT": "์ž ๊ธˆ", + "AGE": "๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ", + "COMPLEXITY": "๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก์„ฑ", + "NOTIFICATIONS": "์•Œ๋ฆผ", + "SMTP_PROVIDER": "SMTP ์ œ๊ณต์ž", + "SMS_PROVIDER": "SMS/์ „ํ™” ์ œ๊ณต์ž", + "NOTIFICATIONS_DESC": "SMTP ๋ฐ SMS ์„ค์ •", + "MESSAGETEXTS": "๋ฉ”์‹œ์ง€ ํ…์ŠคํŠธ", + "IDP": "ID ์ œ๊ณต์ž", + "VERIFIED_DOMAINS": "๊ฒ€์ฆ๋œ ๋„๋ฉ”์ธ", + "DOMAIN": "๋„๋ฉ”์ธ ์„ค์ •", + "LOGINTEXTS": "๋กœ๊ทธ์ธ ์ธํ„ฐํŽ˜์ด์Šค ํ…์ŠคํŠธ", + "BRANDING": "๋ธŒ๋žœ๋”ฉ", + "PRIVACYPOLICY": "์™ธ๋ถ€ ๋งํฌ", + "OIDC": "OIDC ํ† ํฐ ์ˆ˜๋ช… ๋ฐ ๋งŒ๋ฃŒ", + "SECRETS": "์‹œํฌ๋ฆฟ ์ƒ์„ฑ๊ธฐ", + "SECURITY": "๋ณด์•ˆ ์„ค์ •", + "EVENTS": "์ด๋ฒคํŠธ", + "FAILEDEVENTS": "์‹คํŒจํ•œ ์ด๋ฒคํŠธ", + "VIEWS": "๋ทฐ" + }, + "GROUPS": { + "GENERAL": "์ผ๋ฐ˜ ์ •๋ณด", + "NOTIFICATIONS": "์•Œ๋ฆผ", + "LOGIN": "๋กœ๊ทธ์ธ ๋ฐ ์ ‘๊ทผ", + "DOMAIN": "๋„๋ฉ”์ธ", + "TEXTS": "ํ…์ŠคํŠธ ๋ฐ ์–ธ์–ด", + "APPEARANCE": "์™ธํ˜•", + "OTHER": "๊ธฐํƒ€", + "STORAGE": "์ €์žฅ์†Œ" + } + }, + "SETTING": { + "LANGUAGES": { + "DEFAULT": "๊ธฐ๋ณธ ์–ธ์–ด", + "ALLOWED": "ํ—ˆ์šฉํ•˜๋Š” ์–ธ์–ด", + "NOT_ALLOWED": "ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š” ์–ธ์–ด", + "ALLOW_ALL": "๋ชจ๋‘ ํ—ˆ์šฉ", + "DISALLOW_ALL": "๋ชจ๋‘ ๋น„ํ—ˆ์šฉ", + "SETASDEFAULT": "๊ธฐ๋ณธ ์–ธ์–ด๋กœ ์„ค์ •", + "DEFAULT_SAVED": "๊ธฐ๋ณธ ์–ธ์–ด๊ฐ€ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "ALLOWED_SAVED": "ํ—ˆ์šฉํ•˜๋Š” ์–ธ์–ด๊ฐ€ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "OPTIONS": { + "de": "Deutsch", + "en": "English", + "es": "Espaรฑol", + "fr": "Franรงais", + "it": "Italiano", + "ja": "ๆ—ฅๆœฌ่ชž", + "pl": "Polski", + "zh": "็ฎ€ไฝ“ไธญๆ–‡", + "bg": "ะ‘ัŠะปะณะฐั€ัะบะธ", + "pt": "Portuguese", + "mk": "ะœะฐะบะตะดะพะฝัะบะธ", + "cs": "ฤŒeลกtina", + "ru": "ะ ัƒััะบะธะน", + "nl": "Nederlands", + "sv": "Svenska", + "id": "Bahasa Indonesia", + "hu": "Magyar", + "ko": "ํ•œ๊ตญ์–ด" + } + }, + "SMTP": { + "TITLE": "SMTP ์ œ๊ณต์ž", + "DESCRIPTION": "์„ค๋ช…", + "SENDERADDRESS": "๋ฐœ์‹  ์ด๋ฉ”์ผ ์ฃผ์†Œ", + "SENDERNAME": "๋ฐœ์‹ ์ž ์ด๋ฆ„", + "REPLYTOADDRESS": "ํšŒ์‹  ์ฃผ์†Œ", + "HOSTANDPORT": "ํ˜ธ์ŠคํŠธ ๋ฐ ํฌํŠธ", + "USER": "์‚ฌ์šฉ์ž", + "PASSWORD": "๋น„๋ฐ€๋ฒˆํ˜ธ", + "SETPASSWORD": "SMTP ๋น„๋ฐ€๋ฒˆํ˜ธ ์„ค์ •", + "PASSWORDSET": "SMTP ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "TLS": "์ „์†ก ๊ณ„์ธต ๋ณด์•ˆ (TLS)", + "SAVED": "์„ฑ๊ณต์ ์œผ๋กœ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "NOCHANGES": "๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ์—†์Šต๋‹ˆ๋‹ค!", + "REQUIREDWARN": "๋„๋ฉ”์ธ์—์„œ ์•Œ๋ฆผ์„ ๋ณด๋‚ด๋ ค๋ฉด SMTP ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค." + }, + "SMS": { + "PROVIDERS": "์ œ๊ณต์ž", + "PROVIDER": "SMS ์ œ๊ณต์ž", + "ADDPROVIDER": "SMS ์ œ๊ณต์ž ์ถ”๊ฐ€", + "ADDPROVIDERDESCRIPTION": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ œ๊ณต์ž ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•˜๊ณ  ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", + "REMOVEPROVIDER": "์ œ๊ณต์ž ์ œ๊ฑฐ", + "REMOVEPROVIDER_DESC": "์ œ๊ณต์ž ๊ตฌ์„ฑ์„ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๊ณ„์†ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "SMSPROVIDERSTATE": { + "0": "๋ฏธ์ง€์ •", + "1": "ํ™œ์„ฑ", + "2": "๋น„ํ™œ์„ฑ" + }, + "ACTIVATED": "์ œ๊ณต์ž๊ฐ€ ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "DEACTIVATED": "์ œ๊ณต์ž๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "TWILIO": { + "SID": "SID", + "TOKEN": "ํ† ํฐ", + "SENDERNUMBER": "๋ฐœ์‹  ๋ฒˆํ˜ธ", + "VERIFYSERVICESID": "๊ฒ€์ฆ ์„œ๋น„์Šค SID", + "VERIFYSERVICESID_DESCRIPTION": "๊ฒ€์ฆ ์„œ๋น„์Šค SID๋ฅผ ์„ค์ •ํ•˜๋ฉด ์ „ํ™”๋ฒˆํ˜ธ ๊ฒ€์ฆ ๋ฐ OTP SMS์— Twilio ๊ฒ€์ฆ ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "ADDED": "Twilio๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "UPDATED": "Twilio๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "REMOVED": "Twilio๊ฐ€ ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "CHANGETOKEN": "ํ† ํฐ ๋ณ€๊ฒฝ", + "SETTOKEN": "ํ† ํฐ ์„ค์ •", + "TOKENSET": "ํ† ํฐ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + } + }, + "SECRETS": { + "TYPES": "์‹œํฌ๋ฆฟ ์œ ํ˜•", + "TYPE": { + "1": "์ดˆ๊ธฐํ™” ๋ฉ”์ผ", + "2": "์ด๋ฉ”์ผ ๊ฒ€์ฆ", + "3": "์ „ํ™” ๊ฒ€์ฆ", + "4": "๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ •", + "5": "๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์ดˆ๊ธฐํ™”", + "6": "์•ฑ ์‹œํฌ๋ฆฟ", + "7": "์ผํšŒ์„ฑ ๋น„๋ฐ€๋ฒˆํ˜ธ (OTP) - SMS", + "8": "์ผํšŒ์„ฑ ๋น„๋ฐ€๋ฒˆํ˜ธ (OTP) - ์ด๋ฉ”์ผ" + }, + "EXPIRY": "๋งŒ๋ฃŒ ์‹œ๊ฐ„ (๋ถ„)", + "INCLUDEDIGITS": "์ˆซ์ž ํฌํ•จ", + "INCLUDESYMBOLS": "๊ธฐํ˜ธ ํฌํ•จ", + "INCLUDELOWERLETTERS": "์†Œ๋ฌธ์ž ํฌํ•จ", + "INCLUDEUPPERLETTERS": "๋Œ€๋ฌธ์ž ํฌํ•จ", + "LENGTH": "๊ธธ์ด", + "UPDATED": "์„ค์ •์ด ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + }, + "SECURITY": { + "IFRAMETITLE": "iFrame", + "IFRAMEDESCRIPTION": "์ด ์„ค์ •์€ CSP๋ฅผ ํ†ตํ•ด ํ—ˆ์šฉ๋œ ๋„๋ฉ”์ธ์—์„œ ํ”„๋ ˆ์ด๋ฐ์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. iFrame ์‚ฌ์šฉ์„ ํ—ˆ์šฉํ•˜๋ฉด ํด๋ฆญ์žฌํ‚น ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.", + "IFRAMEENABLED": "iFrame ํ—ˆ์šฉ", + "ALLOWEDORIGINS": "ํ—ˆ์šฉ๋œ URL", + "IMPERSONATIONTITLE": "์‹ ์› ๊ฐ€์žฅ", + "IMPERSONATIONENABLED": "์‹ ์› ๊ฐ€์žฅ ํ—ˆ์šฉ", + "IMPERSONATIONDESCRIPTION": "์ด ์„ค์ •์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์‹ ์› ๊ฐ€์žฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์‹ ์› ๊ฐ€์žฅํ•˜๋Š” ๊ณ„์ •์—๋Š” ์ ์ ˆํ•œ `*_IMPERSONATOR` ์—ญํ• ์ด ํ• ๋‹น๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค." + }, + "FEATURES": { + "LOGINDEFAULTORG": "๋กœ๊ทธ์ธ ๊ธฐ๋ณธ ์กฐ์ง", + "LOGINDEFAULTORG_DESCRIPTION": "์กฐ์ง ์ปจํ…์ŠคํŠธ๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋กœ๊ทธ์ธ UI๊ฐ€ ๊ธฐ๋ณธ ์กฐ์ง์˜ ์„ค์ •์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค (์ธ์Šคํ„ด์Šค์—์„œ ์„ค์ •๋˜์ง€ ์•Š์Œ).", + "OIDCLEGACYINTROSPECTION": "OIDC ๋ ˆ๊ฑฐ์‹œ ๋‚ด๋ถ€ ์กฐ์‚ฌ", + "OIDCLEGACYINTROSPECTION_DESCRIPTION": "์ตœ๊ทผ ๋‚ด๋ถ€ ์กฐ์‚ฌ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์„ฑ๋Šฅ์„ ์œ„ํ•ด ๋ฆฌํŒฉํ† ๋งํ–ˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ด ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ ˆ๊ฑฐ์‹œ ๊ตฌํ˜„์œผ๋กœ ๋กค๋ฐฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "OIDCTOKENEXCHANGE": "OIDC ํ† ํฐ ๊ตํ™˜", + "OIDCTOKENEXCHANGE_DESCRIPTION": "OIDC ํ† ํฐ ์—”๋“œํฌ์ธํŠธ์˜ ์‹คํ—˜์  urn:ietf:params:oauth:grant-type:token-exchange ํ—ˆ์šฉ์„ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค. ํ† ํฐ ๊ตํ™˜์„ ํ†ตํ•ด ๋ฒ”์œ„๊ฐ€ ์ข์€ ํ† ํฐ์„ ์š”์ฒญํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๋ฅผ ๊ฐ€์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ธ์Šคํ„ด์Šค์—์„œ ๊ฐ€์žฅ์„ ํ—ˆ์šฉํ•˜๋Š” ๋ณด์•ˆ ์ •์ฑ…์„ ํ™•์ธํ•˜์„ธ์š”.", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS": "OIDC ํŠธ๋ฆฌ๊ฑฐ ๋‚ด๋ถ€ ์กฐ์‚ฌ ํ”„๋กœ์ ์…˜", + "OIDCTRIGGERINTROSPECTIONPROJECTIONS_DESCRIPTION": "๋‚ด๋ถ€ ์กฐ์‚ฌ ์š”์ฒญ ์ค‘ ํ”„๋กœ์ ์…˜ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋‚ด๋ถ€ ์กฐ์‚ฌ ์‘๋‹ต์—์„œ ์ผ๊ด€์„ฑ ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์ž„์‹œ ํ•ด๊ฒฐ์ฑ…์œผ๋กœ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ์œผ๋‚˜ ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ–ฅํ›„ ๋‚ด๋ถ€ ์กฐ์‚ฌ ์š”์ฒญ์— ๋Œ€ํ•œ ํŠธ๋ฆฌ๊ฑฐ ์ œ๊ฑฐ๋ฅผ ๊ณ„ํš ์ค‘์ž…๋‹ˆ๋‹ค.", + "USERSCHEMA": "์‚ฌ์šฉ์ž ์Šคํ‚ค๋งˆ", + "USERSCHEMA_DESCRIPTION": "์‚ฌ์šฉ์ž ์Šคํ‚ค๋งˆ๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž์˜ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ”Œ๋ž˜๊ทธ๊ฐ€ ํ™œ์„ฑํ™”๋˜๋ฉด ์ƒˆ API ๋ฐ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "ACTIONS": "์•ก์…˜", + "ACTIONS_DESCRIPTION": "์•ก์…˜ v2๋Š” ๋ฐ์ดํ„ฐ ์‹คํ–‰ ๋ฐ ๋Œ€์ƒ์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ”Œ๋ž˜๊ทธ๊ฐ€ ํ™œ์„ฑํ™”๋˜๋ฉด ์ƒˆ API ๋ฐ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "OIDCSINGLEV1SESSIONTERMINATION": "OIDC ๋‹จ์ผ V1 ์„ธ์…˜ ์ข…๋ฃŒ", + "OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "ํ”Œ๋ž˜๊ทธ๊ฐ€ ํ™œ์„ฑํ™”๋˜๋ฉด, `sid` ํด๋ ˆ์ž„์ด ์žˆ๋Š” id_token์„ ์‚ฌ์šฉํ•˜์—ฌ end_session ์—”๋“œํฌ์ธํŠธ์—์„œ ๋กœ๊ทธ์ธ UI์˜ ๋‹จ์ผ ์„ธ์…˜์„ ์ข…๋ฃŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ๋™์ผํ•œ ์‚ฌ์šฉ์ž ์—์ด์ „ํŠธ(๋ธŒ๋ผ์šฐ์ €)์—์„œ ๋ชจ๋“  ์„ธ์…˜์ด ๋กœ๊ทธ์ธ UI์—์„œ ์ข…๋ฃŒ๋ฉ๋‹ˆ๋‹ค. Session API๋ฅผ ํ†ตํ•ด ๊ด€๋ฆฌ๋œ ์„ธ์…˜์€ ์ด๋ฏธ ๋‹จ์ผ ์„ธ์…˜ ์ข…๋ฃŒ๋ฅผ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "STATES": { + "INHERITED": "์ƒ์†", + "ENABLED": "ํ™œ์„ฑํ™”๋จ", + "DISABLED": "๋น„ํ™œ์„ฑํ™”๋จ" + }, + "INHERITED_DESCRIPTION": "์‹œ์Šคํ…œ์˜ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๊ฐ’์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.", + "INHERITEDINDICATOR_DESCRIPTION": { + "ENABLED": "\"ํ™œ์„ฑํ™”๋จ\"์€ ์ƒ์†๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "DISABLED": "\"๋น„ํ™œ์„ฑํ™”๋จ\"์€ ์ƒ์†๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + }, + "RESET": "๋ชจ๋‘ ์ƒ์†์œผ๋กœ ์„ค์ •" + }, + "DIALOG": { + "RESET": { + "DEFAULTTITLE": "์„ค์ • ์žฌ์„ค์ •", + "DEFAULTDESCRIPTION": "์„ค์ •์„ ์ธ์Šคํ„ด์Šค์˜ ๊ธฐ๋ณธ ๊ตฌ์„ฑ์œผ๋กœ ์žฌ์„ค์ •ํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๊ณ„์†ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "LOGINPOLICY_DESCRIPTION": "๊ฒฝ๊ณ : ๊ณ„์†ํ•˜๋ฉด ID ์ œ๊ณต์ž ์„ค์ •๋„ ์ธ์Šคํ„ด์Šค ์„ค์ •์œผ๋กœ ์žฌ์„ค์ •๋ฉ๋‹ˆ๋‹ค." + } + } + }, + "POLICY": { + "APPLIEDTO": "์ ์šฉ ๋Œ€์ƒ", + "PWD_COMPLEXITY": { + "TITLE": "๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก์„ฑ", + "DESCRIPTION": "๋ชจ๋“  ์„ค์ •๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ํŠน์ • ํŒจํ„ด์— ๋งž๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค", + "SYMBOLANDNUMBERERROR": "์ˆซ์ž์™€ ๊ธฐํ˜ธ/๊ตฌ๋‘์ ์ด ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "SYMBOLERROR": "๊ธฐํ˜ธ/๊ตฌ๋‘์ ์ด ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "NUMBERERROR": "์ˆซ์ž๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "PATTERNERROR": "๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์š”๊ตฌ๋˜๋Š” ํŒจํ„ด์— ๋งž์ง€ ์•Š์Šต๋‹ˆ๋‹ค." + }, + "NOTIFICATION": { + "TITLE": "์•Œ๋ฆผ", + "DESCRIPTION": "์–ด๋–ค ๋ณ€๊ฒฝ ์‚ฌํ•ญ์— ๋Œ€ํ•ด ์•Œ๋ฆผ์„ ๋ณด๋‚ผ์ง€ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "PASSWORDCHANGE": "๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ" + }, + "PRIVATELABELING": { + "DESCRIPTION": "๋กœ๊ทธ์ธ์„ ๋งž์ถคํ˜• ์Šคํƒ€์ผ๋กœ ์ œ๊ณตํ•˜๊ณ  ๋™์ž‘์„ ์ˆ˜์ •ํ•˜์„ธ์š”.", + "PREVIEW_DESCRIPTION": "์ •์ฑ…์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ™˜๊ฒฝ์— ์ž๋™์œผ๋กœ ๋ฐฐํฌ๋ฉ๋‹ˆ๋‹ค.", + "BTN": "ํŒŒ์ผ ์„ ํƒ", + "ACTIVATEPREVIEW": "๊ตฌ์„ฑ ์ ์šฉ", + "DARK": "๋‹คํฌ ๋ชจ๋“œ", + "LIGHT": "๋ผ์ดํŠธ ๋ชจ๋“œ", + "CHANGEVIEW": "๋ณด๊ธฐ ๋ณ€๊ฒฝ", + "ACTIVATED": "์ •์ฑ… ๋ณ€๊ฒฝ์ด ์ด์ œ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค", + "THEME": "ํ…Œ๋งˆ", + "COLORS": "์ƒ‰์ƒ", + "FONT": "ํฐํŠธ", + "ADVANCEDBEHAVIOR": "๊ณ ๊ธ‰ ๋™์ž‘", + "DROP": "์ด๋ฏธ์ง€๋ฅผ ์—ฌ๊ธฐ๋กœ ๋“œ๋กญํ•˜๊ฑฐ๋‚˜", + "RELEASE": "๋ฆด๋ฆฌ์Šค", + "DROPFONT": "ํฐํŠธ ํŒŒ์ผ์„ ์—ฌ๊ธฐ๋กœ ๋“œ๋กญํ•˜์„ธ์š”", + "RELEASEFONT": "๋ฆด๋ฆฌ์Šค", + "USEOFLOGO": "๋กœ๊ทธ์ธ์ด๋‚˜ ์ด๋ฉ”์ผ์— ๋กœ๊ณ ๊ฐ€ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ž‘์€ UI ์š”์†Œ์—๋Š” ์•„์ด์ฝ˜์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.", + "MAXSIZE": "์ตœ๋Œ€ ํฌ๊ธฐ๋Š” 524kB๋กœ ์ œํ•œ๋ฉ๋‹ˆ๋‹ค", + "EMAILNOSVG": "์ด๋ฉ”์ผ์—์„œ๋Š” SVG ํŒŒ์ผ ํ˜•์‹์ด ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. PNG ๋˜๋Š” ์ง€์›๋˜๋Š” ๋‹ค๋ฅธ ํ˜•์‹์˜ ๋กœ๊ณ ๋ฅผ ์—…๋กœ๋“œํ•˜์„ธ์š”.", + "MAXSIZEEXCEEDED": "์ตœ๋Œ€ ํฌ๊ธฐ์ธ 524kB๋ฅผ ์ดˆ๊ณผํ–ˆ์Šต๋‹ˆ๋‹ค.", + "NOSVGSUPPORTED": "SVG๋Š” ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค!", + "FONTINLOGINONLY": "ํ˜„์žฌ ํฐํŠธ๋Š” ๋กœ๊ทธ์ธ ์ธํ„ฐํŽ˜์ด์Šค์—์„œ๋งŒ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.", + "BACKGROUNDCOLOR": "๋ฐฐ๊ฒฝ ์ƒ‰์ƒ", + "PRIMARYCOLOR": "๊ธฐ๋ณธ ์ƒ‰์ƒ", + "WARNCOLOR": "๊ฒฝ๊ณ  ์ƒ‰์ƒ", + "FONTCOLOR": "ํฐํŠธ ์ƒ‰์ƒ", + "VIEWS": { + "PREVIEW": "๋ฏธ๋ฆฌ๋ณด๊ธฐ", + "CURRENT": "ํ˜„์žฌ ๊ตฌ์„ฑ" + }, + "PREVIEW": { + "TITLE": "๋กœ๊ทธ์ธ", + "SECOND": "ZITADEL ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•˜์„ธ์š”.", + "ERROR": "์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!", + "PRIMARYBUTTON": "๋‹ค์Œ", + "SECONDARYBUTTON": "๋“ฑ๋ก" + }, + "THEMEMODE": { + "THEME_MODE_AUTO": "์ž๋™ ๋ชจ๋“œ", + "THEME_MODE_LIGHT": "๋ผ์ดํŠธ ๋ชจ๋“œ๋งŒ", + "THEME_MODE_DARK": "๋‹คํฌ ๋ชจ๋“œ๋งŒ" + } + }, + "PWD_AGE": { + "TITLE": "๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ", + "DESCRIPTION": "๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์ •์ฑ…์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์ •์ฑ…์€ ๋งŒ๋ฃŒ ํ›„ ๋‹ค์Œ ๋กœ๊ทธ์ธ ์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ์„ ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค. ์ž๋™ ๊ฒฝ๊ณ  ๋ฐ ์•Œ๋ฆผ์€ ์—†์Šต๋‹ˆ๋‹ค." + }, + "PWD_LOCKOUT": { + "TITLE": "์ž ๊ธˆ ์ •์ฑ…", + "DESCRIPTION": "๊ณ„์ •์ด ์ฐจ๋‹จ๋˜๊ธฐ ์ „ ์ตœ๋Œ€ ๋น„๋ฐ€๋ฒˆํ˜ธ ์‹œ๋„ ํšŸ์ˆ˜๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค." + }, + "PRIVATELABELING_POLICY": { + "TITLE": "๋ธŒ๋žœ๋”ฉ", + "BTN": "ํŒŒ์ผ ์„ ํƒ", + "DESCRIPTION": "๋กœ๊ทธ์ธ์˜ ์™ธ๊ด€์„ ๋งž์ถคํ™”ํ•˜์„ธ์š”", + "ACTIVATEPREVIEW": "๊ตฌ์„ฑ ํ™œ์„ฑํ™”" + }, + "LOGIN_POLICY": { + "TITLE": "๋กœ๊ทธ์ธ ์„ค์ •", + "DESCRIPTION": "์‚ฌ์šฉ์ž ์ธ์ฆ ๋ฐฉ์‹์„ ์ •์˜ํ•˜๊ณ  ID ์ œ๊ณต์ž๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค", + "DESCRIPTIONCREATEADMIN": "์‚ฌ์šฉ์ž๋Š” ์•„๋ž˜์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ID ์ œ๊ณต์ž ์ค‘์—์„œ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "DESCRIPTIONCREATEMGMT": "์‚ฌ์šฉ์ž๋Š” ์•„๋ž˜์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ID ์ œ๊ณต์ž ์ค‘์—์„œ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฐธ๊ณ : ์‹œ์Šคํ…œ ์„ค์ • ์ œ๊ณต์ž์™€ ์กฐ์ง๋งŒ์„ ์œ„ํ•œ ์ œ๊ณต์ž๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "LIFETIME_INVALID": "์–‘์‹์— ์ž˜๋ชป๋œ ๊ฐ’์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.", + "SAVED": "์„ฑ๊ณต์ ์œผ๋กœ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "PROVIDER_ADDED": "ID ์ œ๊ณต์ž๊ฐ€ ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + }, + "PRIVACY_POLICY": { + "DESCRIPTION": "๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ๋ฐฉ์นจ ๋ฐ ์ด์šฉ ์•ฝ๊ด€ ๋งํฌ ์„ค์ •", + "TOSLINK": "์ด์šฉ ์•ฝ๊ด€ ๋งํฌ", + "POLICYLINK": "๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ๋ฐฉ์นจ ๋งํฌ", + "HELPLINK": "๋„์›€๋ง ๋งํฌ", + "SUPPORTEMAIL": "์ง€์› ์ด๋ฉ”์ผ", + "DOCSLINK": "๋ฌธ์„œ ๋งํฌ (์ฝ˜์†”)", + "CUSTOMLINK": "์‚ฌ์šฉ์ž ์ •์˜ ๋งํฌ (์ฝ˜์†”)", + "CUSTOMLINKTEXT": "์‚ฌ์šฉ์ž ์ •์˜ ๋งํฌ ํ…์ŠคํŠธ (์ฝ˜์†”)", + "SAVED": "์„ฑ๊ณต์ ์œผ๋กœ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "RESET_TITLE": "๊ธฐ๋ณธ๊ฐ’ ๋ณต์›", + "RESET_DESCRIPTION": "์ด์šฉ ์•ฝ๊ด€ ๋ฐ ๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ๋ฐฉ์นจ์˜ ๊ธฐ๋ณธ ๋งํฌ๋ฅผ ๋ณต์›ํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๊ณ„์†ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?" + }, + "LOGIN_TEXTS": { + "TITLE": "๋กœ๊ทธ์ธ ์ธํ„ฐํŽ˜์ด์Šค ํ…์ŠคํŠธ", + "DESCRIPTION": "๋กœ๊ทธ์ธ ์ธํ„ฐํŽ˜์ด์Šค์˜ ํ…์ŠคํŠธ๋ฅผ ์ •์˜ํ•˜์„ธ์š”. ํ…์ŠคํŠธ๊ฐ€ ๋น„์–ด ์žˆ์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’์ด ํ”Œ๋ ˆ์ด์Šคํ™€๋”๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.", + "DESCRIPTION_SHORT": "๋กœ๊ทธ์ธ ์ธํ„ฐํŽ˜์ด์Šค์˜ ํ…์ŠคํŠธ๋ฅผ ์ •์˜ํ•˜์„ธ์š”.", + "NEWERVERSIONEXISTS": "์ƒˆ ๋ฒ„์ „์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค", + "CURRENTDATE": "ํ˜„์žฌ ๊ตฌ์„ฑ", + "CHANGEDATE": "์ƒˆ ๋ฒ„์ „ ์—…๋ฐ์ดํŠธ", + "KEYNAME": "๋กœ๊ทธ์ธ ํ™”๋ฉด / ์ธํ„ฐํŽ˜์ด์Šค", + "RESET_TITLE": "๊ธฐ๋ณธ๊ฐ’ ๋ณต์›", + "RESET_DESCRIPTION": "๋ชจ๋“  ๊ธฐ๋ณธ๊ฐ’์„ ๋ณต์›ํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ์˜๊ตฌ์ ์œผ๋กœ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค. ๊ณ„์†ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "UNSAVED_TITLE": "์ €์žฅํ•˜์ง€ ์•Š๊ณ  ๊ณ„์†ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "UNSAVED_DESCRIPTION": "์ €์žฅํ•˜์ง€ ์•Š๊ณ  ๋ณ€๊ฒฝํ–ˆ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ ์ €์žฅํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "ACTIVE_LANGUAGE_NOT_ALLOWED": "ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ์–ธ์–ด๋ฅผ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค. ํ…์ŠคํŠธ๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜๋Š” ์žˆ์ง€๋งŒ, ์‚ฌ์šฉ์ž๊ฐ€ ์ด ์–ธ์–ด๋ฅผ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋ ค๋ฉด ์ธ์Šคํ„ด์Šค ์ œํ•œ์„ ๋ณ€๊ฒฝํ•˜์„ธ์š”.", + "LANGUAGES_NOT_ALLOWED": "ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ์–ธ์–ด:", + "LANGUAGE": "์–ธ์–ด", + "LANGUAGES": { + "de": "Deutsch", + "en": "English", + "es": "Espaรฑol", + "fr": "Franรงais", + "it": "Italiano", + "ja": "ๆ—ฅๆœฌ่ชž", + "pl": "Polski", + "zh": "็ฎ€ไฝ“ไธญๆ–‡", + "bg": "ะ‘ัŠะปะณะฐั€ัะบะธ", + "pt": "Portuguese", + "mk": "ะœะฐะบะตะดะพะฝัะบะธ", + "cs": "ฤŒeลกtina", + "ru": "ะ ัƒััะบะธะน", + "nl": "Nederlands", + "sv": "Svenska", + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" + }, + "KEYS": { + "emailVerificationDoneText": "์ด๋ฉ”์ผ ์ธ์ฆ ์™„๋ฃŒ", + "emailVerificationText": "์ด๋ฉ”์ผ ์ธ์ฆ", + "externalUserNotFoundText": "์™ธ๋ถ€ ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค", + "footerText": "ํ‘ธํ„ฐ", + "initMfaDoneText": "MFA ์ดˆ๊ธฐํ™” ์™„๋ฃŒ", + "initMfaOtpText": "MFA ์ดˆ๊ธฐํ™”", + "initMfaPromptText": "MFA ์ดˆ๊ธฐํ™” ํ”„๋กฌํ”„ํŠธ", + "initMfaU2fText": "U2F ์ดˆ๊ธฐํ™”", + "initPasswordDoneText": "๋น„๋ฐ€๋ฒˆํ˜ธ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ", + "initPasswordText": "๋น„๋ฐ€๋ฒˆํ˜ธ ์ดˆ๊ธฐํ™”", + "initializeDoneText": "์‚ฌ์šฉ์ž ์ดˆ๊ธฐํ™” ์™„๋ฃŒ", + "initializeUserText": "์‚ฌ์šฉ์ž ์ดˆ๊ธฐํ™”", + "linkingUserDoneText": "์‚ฌ์šฉ์ž ์—ฐ๊ฒฐ ์™„๋ฃŒ", + "loginText": "๋กœ๊ทธ์ธ", + "logoutText": "๋กœ๊ทธ์•„์›ƒ", + "mfaProvidersText": "MFA ์ œ๊ณต์ž", + "passwordChangeDoneText": "๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์™„๋ฃŒ", + "passwordChangeText": "๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ", + "passwordResetDoneText": "๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ์™„๋ฃŒ", + "passwordText": "๋น„๋ฐ€๋ฒˆํ˜ธ", + "registrationOptionText": "๋“ฑ๋ก ์˜ต์…˜", + "registrationOrgText": "์กฐ์ง ๋“ฑ๋ก", + "registrationUserText": "์‚ฌ์šฉ์ž ๋“ฑ๋ก", + "selectAccountText": "๊ณ„์ • ์„ ํƒ", + "successLoginText": "์„ฑ๊ณต์ ์œผ๋กœ ๋กœ๊ทธ์ธ", + "usernameChangeDoneText": "์‚ฌ์šฉ์ž ์ด๋ฆ„ ๋ณ€๊ฒฝ ์™„๋ฃŒ", + "usernameChangeText": "์‚ฌ์šฉ์ž ์ด๋ฆ„ ๋ณ€๊ฒฝ", + "verifyMfaOtpText": "OTP ํ™•์ธ", + "verifyMfaU2fText": "U2F ํ™•์ธ", + "passwordlessPromptText": "๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ํ”„๋กฌํ”„ํŠธ", + "passwordlessRegistrationDoneText": "๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋“ฑ๋ก ์™„๋ฃŒ", + "passwordlessRegistrationText": "๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋“ฑ๋ก", + "passwordlessText": "๋น„๋ฐ€๋ฒˆํ˜ธ ์—†์Œ", + "externalRegistrationUserOverviewText": "์™ธ๋ถ€ ๋“ฑ๋ก ์‚ฌ์šฉ์ž ๊ฐœ์š”" + } + }, + "MESSAGE_TEXTS": { + "TYPE": "์•Œ๋ฆผ", + "TYPES": { + "INIT": "์ดˆ๊ธฐํ™”", + "VE": "์ด๋ฉ”์ผ ํ™•์ธ", + "VP": "์ „ํ™” ํ™•์ธ", + "VSO": "SMS OTP ํ™•์ธ", + "VEO": "์ด๋ฉ”์ผ OTP ํ™•์ธ", + "PR": "๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ •", + "DC": "๋„๋ฉ”์ธ ํด๋ ˆ์ž„", + "PL": "๋น„๋ฐ€๋ฒˆํ˜ธ ์—†์Œ", + "PC": "๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ", + "IU": "์‚ฌ์šฉ์ž ์ดˆ๋Œ€" + }, + "CHIPS": { + "firstname": "์ด๋ฆ„", + "lastname": "์„ฑ", + "code": "์ฝ”๋“œ", + "preferredLoginName": "์„ ํ˜ธ ๋กœ๊ทธ์ธ ์ด๋ฆ„", + "displayName": "ํ‘œ์‹œ ์ด๋ฆ„", + "nickName": "๋‹‰๋„ค์ž„", + "loginnames": "๋กœ๊ทธ์ธ ์ด๋ฆ„", + "domain": "๋„๋ฉ”์ธ", + "lastEmail": "๋งˆ์ง€๋ง‰ ์ด๋ฉ”์ผ", + "lastPhone": "๋งˆ์ง€๋ง‰ ์ „ํ™”๋ฒˆํ˜ธ", + "verifiedEmail": "ํ™•์ธ๋œ ์ด๋ฉ”์ผ", + "verifiedPhone": "ํ™•์ธ๋œ ์ „ํ™”๋ฒˆํ˜ธ", + "changedate": "๋ณ€๊ฒฝ ๋‚ ์งœ", + "username": "์‚ฌ์šฉ์ž ์ด๋ฆ„", + "tempUsername": "์ž„์‹œ ์‚ฌ์šฉ์ž ์ด๋ฆ„", + "otp": "์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ", + "verifyUrl": "์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ URL", + "expiry": "๋งŒ๋ฃŒ", + "applicationName": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ด๋ฆ„" + }, + "TOAST": { + "UPDATED": "์‚ฌ์šฉ์ž ์ •์˜ ํ…์ŠคํŠธ๊ฐ€ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + } + }, + "DEFAULTLABEL": "ํ˜„์žฌ ์„ค์ •์ด ์ธ์Šคํ„ด์Šค์˜ ํ‘œ์ค€๊ณผ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค.", + "BTN_INSTALL": "์„ค์น˜", + "BTN_EDIT": "์ˆ˜์ •", + "DATA": { + "DESCRIPTION": "์„ค๋ช…", + "MINLENGTH": "์ตœ์†Œ ๊ธธ์ด์—ฌ์•ผ ํ•จ", + "HASNUMBER": "์ˆซ์ž๋ฅผ ํฌํ•จํ•ด์•ผ ํ•จ", + "HASSYMBOL": "๊ธฐํ˜ธ๋ฅผ ํฌํ•จํ•ด์•ผ ํ•จ", + "HASLOWERCASE": "์†Œ๋ฌธ์ž๋ฅผ ํฌํ•จํ•ด์•ผ ํ•จ", + "HASUPPERCASE": "๋Œ€๋ฌธ์ž๋ฅผ ํฌํ•จํ•ด์•ผ ํ•จ", + "SHOWLOCKOUTFAILURES": "์ž ๊ธˆ ์‹คํŒจ ํ‘œ์‹œ", + "MAXPASSWORDATTEMPTS": "๋น„๋ฐ€๋ฒˆํ˜ธ ์ตœ๋Œ€ ์‹œ๋„ ํšŸ์ˆ˜", + "MAXOTPATTEMPTS": "OTP ์ตœ๋Œ€ ์‹œ๋„ ํšŸ์ˆ˜", + "EXPIREWARNDAYS": "๋งŒ๋ฃŒ ๊ฒฝ๊ณ  (์ผ)", + "MAXAGEDAYS": "์ตœ๋Œ€ ์œ ํšจ ๊ธฐ๊ฐ„ (์ผ)", + "USERLOGINMUSTBEDOMAIN": "๋กœ๊ทธ์ธ ์ด๋ฆ„์— ์กฐ์ง ๋„๋ฉ”์ธ ์ถ”๊ฐ€", + "USERLOGINMUSTBEDOMAIN_DESCRIPTION": "์ด ์„ค์ •์„ ํ™œ์„ฑํ™”ํ•˜๋ฉด ๋ชจ๋“  ๋กœ๊ทธ์ธ ์ด๋ฆ„์— ์กฐ์ง ๋„๋ฉ”์ธ์ด ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. ์ด ์„ค์ •์ด ๋น„ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ, ๋ชจ๋“  ์กฐ์ง์—์„œ ์‚ฌ์šฉ์ž ์ด๋ฆ„์ด ๊ณ ์œ ํ•˜๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "VALIDATEORGDOMAINS": "์กฐ์ง ๋„๋ฉ”์ธ ํ™•์ธ ํ•„์š” (DNS ๋˜๋Š” HTTP ๊ฒ€์ฆ)", + "SMTPSENDERADDRESSMATCHESINSTANCEDOMAIN": "SMTP ๋ฐœ์‹  ์ฃผ์†Œ๊ฐ€ ์ธ์Šคํ„ด์Šค ๋„๋ฉ”์ธ๊ณผ ์ผ์น˜ํ•ด์•ผ ํ•จ", + "ALLOWUSERNAMEPASSWORD_DESC": "์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ์˜ ์ผ๋ฐ˜์ ์ธ ๋กœ๊ทธ์ธ์ด ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค.", + "ALLOWEXTERNALIDP_DESC": "๊ธฐ๋ณธ ID ์ œ๊ณต์ž์— ๋Œ€ํ•œ ๋กœ๊ทธ์ธ์ด ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค.", + "ALLOWREGISTER_DESC": "์˜ต์…˜์ด ์„ ํƒ๋˜๋ฉด, ๋กœ๊ทธ์ธ ์ค‘ ์‚ฌ์šฉ์ž ๋“ฑ๋ก์„ ์œ„ํ•œ ์ถ”๊ฐ€ ๋‹จ๊ณ„๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.", + "FORCEMFA": "MFA ๊ฐ•์ œ ์ ์šฉ", + "FORCEMFALOCALONLY": "๋กœ์ปฌ ์ธ์ฆ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•ด MFA ๊ฐ•์ œ ์ ์šฉ", + "FORCEMFALOCALONLY_DESC": "์˜ต์…˜์ด ์„ ํƒ๋˜๋ฉด, ๋กœ์ปฌ ์ธ์ฆ ์‚ฌ์šฉ์ž๋Š” ๋กœ๊ทธ์ธ ์‹œ ๋‘ ๋ฒˆ์งธ ์ธ์ฆ ์š”์†Œ๋ฅผ ๊ตฌ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "HIDEPASSWORDRESET_DESC": "์˜ต์…˜์ด ์„ ํƒ๋˜๋ฉด, ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ ๊ณผ์ •์—์„œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์žฌ์„ค์ •ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", + "HIDELOGINNAMESUFFIX": "๋กœ๊ทธ์ธ ์ด๋ฆ„ ์ ‘๋ฏธ์‚ฌ ์ˆจ๊ธฐ๊ธฐ", + "HIDELOGINNAMESUFFIX_DESC": "๋กœ๊ทธ์ธ ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ๋กœ๊ทธ์ธ ์ด๋ฆ„ ์ ‘๋ฏธ์‚ฌ๋ฅผ ์ˆจ๊น๋‹ˆ๋‹ค.", + "IGNOREUNKNOWNUSERNAMES_DESC": "์˜ต์…˜์ด ์„ ํƒ๋˜๋ฉด, ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ์—๋„ ๋กœ๊ทธ์ธ ๊ณผ์ •์—์„œ ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™”๋ฉด์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ ์˜ค๋ฅ˜๊ฐ€ ์‚ฌ์šฉ์ž ์ด๋ฆ„ ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ์˜ ์˜ค๋ฅ˜๋ฅผ ๊ณต๊ฐœํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + "ALLOWDOMAINDISCOVERY_DESC": "์˜ต์…˜์ด ์„ ํƒ๋˜๋ฉด, ๋กœ๊ทธ์ธ ํ™”๋ฉด์—์„œ ์•Œ ์ˆ˜ ์—†๋Š” ์‚ฌ์šฉ์ž ์ด๋ฆ„์˜ ์ ‘๋ฏธ์‚ฌ (@domain.com)๊ฐ€ ์กฐ์ง ๋„๋ฉ”์ธ๊ณผ ์ผ์น˜ํ•˜๊ณ , ์ผ์น˜ํ•  ๊ฒฝ์šฐ ํ•ด๋‹น ์กฐ์ง์˜ ๋“ฑ๋ก ํ™”๋ฉด์œผ๋กœ ๋ฆฌ๋””๋ ‰์…˜๋ฉ๋‹ˆ๋‹ค.", + "DEFAULTREDIRECTURI": "๊ธฐ๋ณธ ๋ฆฌ๋””๋ ‰์…˜ URI", + "DEFAULTREDIRECTURI_DESC": "์•ฑ ์ปจํ…์ŠคํŠธ ์—†์ด ๋กœ๊ทธ์ธ ์‹œ์ž‘ ์‹œ (์˜ˆ: ์ด๋ฉ”์ผ์—์„œ) ์‚ฌ์šฉ์ž๊ฐ€ ๋ฆฌ๋””๋ ‰์…˜๋  ์œ„์น˜๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.", + "ERRORMSGPOPUP": "ํŒ์—…์— ์˜ค๋ฅ˜ ํ‘œ์‹œ", + "DISABLEWATERMARK": "์›Œํ„ฐ๋งˆํฌ ์ˆจ๊ธฐ๊ธฐ", + "DISABLEWATERMARK_DESC": "๋กœ๊ทธ์ธ ์ธํ„ฐํŽ˜์ด์Šค์—์„œ 'Powered by ZITADEL' ์›Œํ„ฐ๋งˆํฌ๋ฅผ ์ˆจ๊น๋‹ˆ๋‹ค." + }, + "RESET": "์ธ์Šคํ„ด์Šค ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์žฌ์„ค์ •", + "CREATECUSTOM": "์‚ฌ์šฉ์ž ์ •์˜ ์ •์ฑ… ์ƒ์„ฑ", + "TOAST": { + "SET": "์ •์ฑ…์ด ์„ฑ๊ณต์ ์œผ๋กœ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "RESETSUCCESS": "์ •์ฑ…์ด ์„ฑ๊ณต์ ์œผ๋กœ ์žฌ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "UPLOADSUCCESS": "์—…๋กœ๋“œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "DELETESUCCESS": "์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "UPLOADFAILED": "์—…๋กœ๋“œ ์‹คํŒจ!" + } + }, + "ORG_DETAIL": { + "TITLE": "์กฐ์ง", + "DESCRIPTION": "์—ฌ๊ธฐ์—์„œ ์กฐ์ง์˜ ์„ค์ •์„ ํŽธ์ง‘ํ•˜๊ณ  ๋ฉค๋ฒ„๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "DETAIL": { + "TITLE": "์„ธ๋ถ€ ์ •๋ณด", + "NAME": "์ด๋ฆ„", + "DOMAIN": "๋„๋ฉ”์ธ", + "STATE": { + "0": "์ •์˜๋˜์ง€ ์•Š์Œ", + "1": "ํ™œ์„ฑํ™”", + "2": "๋น„ํ™œ์„ฑํ™”" + } + }, + "MEMBER": { + "TITLE": "๋ฉค๋ฒ„", + "USERNAME": "์‚ฌ์šฉ์ž ์ด๋ฆ„", + "DISPLAYNAME": "ํ‘œ์‹œ ์ด๋ฆ„", + "LOGINNAME": "๋กœ๊ทธ์ธ ์ด๋ฆ„", + "EMAIL": "์ด๋ฉ”์ผ", + "ROLES": "์—ญํ• ", + "ADD": "๋ฉค๋ฒ„ ์ถ”๊ฐ€", + "ADDDESCRIPTION": "์ถ”๊ฐ€ํ•  ์‚ฌ์šฉ์ž ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”." + }, + "TABLE": { + "TOTAL": "์ด ํ•ญ๋ชฉ", + "SELECTION": "์„ ํƒํ•œ ์š”์†Œ", + "DEACTIVATE": "์‚ฌ์šฉ์ž ๋น„ํ™œ์„ฑํ™”", + "ACTIVATE": "์‚ฌ์šฉ์ž ํ™œ์„ฑํ™”", + "DELETE": "์‚ฌ์šฉ์ž ์‚ญ์ œ", + "CLEAR": "์„ ํƒ ์ง€์šฐ๊ธฐ" + } + }, + "PROJECT": { + "PAGES": { + "TITLE": "ํ”„๋กœ์ ํŠธ", + "DESCRIPTION": "์—ฌ๊ธฐ์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ •์˜ํ•˜๊ณ , ์—ญํ• ์„ ๊ด€๋ฆฌํ•˜๋ฉฐ ๋‹ค๋ฅธ ์กฐ์ง์ด ํ”„๋กœ์ ํŠธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "DELETE": "ํ”„๋กœ์ ํŠธ ์‚ญ์ œ", + "DETAIL": "์„ธ๋ถ€ ์ •๋ณด", + "CREATE": "ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ", + "CREATE_DESC": "ํ”„๋กœ์ ํŠธ์˜ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”.", + "ROLE": "์—ญํ• ", + "NOITEMS": "ํ”„๋กœ์ ํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค", + "ZITADELPROJECT": "์ด ํ”„๋กœ์ ํŠธ๋Š” ZITADEL ํ”„๋กœ์ ํŠธ์— ์†ํ•ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณ€๊ฒฝ ์‹œ ZITADEL์ด ์˜๋„ํ•œ ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "TYPE": { + "OWNED": "์†Œ์œ ํ•œ ํ”„๋กœ์ ํŠธ", + "OWNED_SINGULAR": "์†Œ์œ ํ•œ ํ”„๋กœ์ ํŠธ", + "GRANTED_SINGULAR": "{{name}}์˜ ํ—ˆ๊ฐ€๋œ ํ”„๋กœ์ ํŠธ" + }, + "PRIVATELABEL": { + "TITLE": "๋ธŒ๋žœ๋”ฉ ์„ค์ •", + "0": { + "TITLE": "์ •์˜๋˜์ง€ ์•Š์Œ", + "DESC": "์‚ฌ์šฉ์ž๊ฐ€ ์‹๋ณ„๋˜๋ฉด ์‹๋ณ„๋œ ์‚ฌ์šฉ์ž์˜ ์กฐ์ง ๋ธŒ๋žœ๋”ฉ์ด ํ‘œ์‹œ๋˜๋ฉฐ, ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์‹œ์Šคํ…œ ๊ธฐ๋ณธ๊ฐ’์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค." + }, + "1": { + "TITLE": "ํ”„๋กœ์ ํŠธ ์„ค์ • ์‚ฌ์šฉ", + "DESC": "ํ”„๋กœ์ ํŠธ๋ฅผ ์†Œ์œ ํ•œ ์กฐ์ง์˜ ๋ธŒ๋žœ๋”ฉ์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค" + }, + "2": { + "TITLE": "์‚ฌ์šฉ์ž ์กฐ์ง ์„ค์ • ์‚ฌ์šฉ", + "DESC": "ํ”„๋กœ์ ํŠธ์˜ ์กฐ์ง ๋ธŒ๋žœ๋”ฉ์ด ํ‘œ์‹œ๋˜์ง€๋งŒ ์‚ฌ์šฉ์ž๊ฐ€ ์‹๋ณ„๋˜๋ฉด ์‹๋ณ„๋œ ์‚ฌ์šฉ์ž์˜ ์กฐ์ง ์„ค์ •์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค." + }, + "DIALOG": { + "TITLE": "๋ธŒ๋žœ๋”ฉ ์„ค์ •", + "DESCRIPTION": "ํ”„๋กœ์ ํŠธ ์‚ฌ์šฉ ์‹œ ๋กœ๊ทธ์ธ ๋™์ž‘์„ ์„ ํƒํ•˜์„ธ์š”." + } + }, + "PINNED": "๊ณ ์ •๋จ", + "ALL": "๋ชจ๋‘", + "CREATEDON": "์ƒ์„ฑ์ผ", + "LASTMODIFIED": "๋งˆ์ง€๋ง‰ ์ˆ˜์ •์ผ", + "ADDNEW": "์ƒˆ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ", + "DIALOG": { + "REACTIVATE": { + "TITLE": "ํ”„๋กœ์ ํŠธ ์žฌํ™œ์„ฑํ™”", + "DESCRIPTION": "ํ”„๋กœ์ ํŠธ๋ฅผ ์ •๋ง๋กœ ์žฌํ™œ์„ฑํ™”ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?" + }, + "DEACTIVATE": { + "TITLE": "ํ”„๋กœ์ ํŠธ ๋น„ํ™œ์„ฑํ™”", + "DESCRIPTION": "ํ”„๋กœ์ ํŠธ๋ฅผ ์ •๋ง๋กœ ๋น„ํ™œ์„ฑํ™”ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?" + }, + "DELETE": { + "TITLE": "ํ”„๋กœ์ ํŠธ ์‚ญ์ œ", + "DESCRIPTION": "ํ”„๋กœ์ ํŠธ๋ฅผ ์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "TYPENAME": "์˜๊ตฌ์ ์œผ๋กœ ์‚ญ์ œํ•˜๋ ค๋ฉด ํ”„๋กœ์ ํŠธ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”." + } + } + }, + "SETTINGS": { + "TITLE": "์„ค์ •", + "DESCRIPTION": "" + }, + "STATE": { + "TITLE": "์ƒํƒœ", + "0": "์ •์˜๋˜์ง€ ์•Š์Œ", + "1": "ํ™œ์„ฑํ™”", + "2": "๋น„ํ™œ์„ฑํ™”" + }, + "TYPE": { + "TITLE": "์œ ํ˜•", + "0": "์•Œ ์ˆ˜ ์—†๋Š” ์œ ํ˜•", + "1": "์†Œ์œ ํ•œ ํ”„๋กœ์ ํŠธ", + "2": "ํ—ˆ๊ฐ€๋œ ํ”„๋กœ์ ํŠธ" + }, + "NAME": "์ด๋ฆ„", + "NAMEDIALOG": { + "TITLE": "ํ”„๋กœ์ ํŠธ ์ด๋ฆ„ ๋ณ€๊ฒฝ", + "DESCRIPTION": "ํ”„๋กœ์ ํŠธ์˜ ์ƒˆ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”", + "NAME": "์ƒˆ ์ด๋ฆ„" + }, + "MEMBER": { + "TITLE": "๊ด€๋ฆฌ์ž", + "TITLEDESC": "๊ด€๋ฆฌ์ž๋Š” ์—ญํ• ์— ๋”ฐ๋ผ ์ด ํ”„๋กœ์ ํŠธ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "DESCRIPTION": "์ด ๊ด€๋ฆฌ์ž๋Š” ํ”„๋กœ์ ํŠธ๋ฅผ ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "USERNAME": "์‚ฌ์šฉ์ž ์ด๋ฆ„", + "DISPLAYNAME": "ํ‘œ์‹œ ์ด๋ฆ„", + "LOGINNAME": "๋กœ๊ทธ์ธ ์ด๋ฆ„", + "EMAIL": "์ด๋ฉ”์ผ", + "ROLES": "์—ญํ• ", + "USERID": "์‚ฌ์šฉ์ž ID" + }, + "GRANT": { + "EMPTY": "ํ—ˆ๊ฐ€๋œ ์กฐ์ง์ด ์—†์Šต๋‹ˆ๋‹ค.", + "TITLE": "ํ”„๋กœ์ ํŠธ ๊ถŒํ•œ", + "DESCRIPTION": "๋‹ค๋ฅธ ์กฐ์ง์ด ํ”„๋กœ์ ํŠธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.", + "EDITTITLE": "์—ญํ•  ์ˆ˜์ •", + "CREATE": { + "TITLE": "์กฐ์ง ๊ถŒํ•œ ์ƒ์„ฑ", + "SEL_USERS": "์•ก์„ธ์Šค๋ฅผ ํ—ˆ๊ฐ€ํ•  ์‚ฌ์šฉ์ž๋ฅผ ์„ ํƒํ•˜์„ธ์š”", + "SEL_PROJECT": "ํ”„๋กœ์ ํŠธ๋ฅผ ๊ฒ€์ƒ‰ํ•˜์„ธ์š”", + "SEL_ROLES": "๊ถŒํ•œ์— ์ถ”๊ฐ€ํ•  ์—ญํ• ์„ ์„ ํƒํ•˜์„ธ์š”", + "SEL_USER": "์‚ฌ์šฉ์ž ์„ ํƒ", + "SEL_ORG": "์กฐ์ง ๊ฒ€์ƒ‰", + "SEL_ORG_DESC": "๊ถŒํ•œ์„ ๋ถ€์—ฌํ•  ์กฐ์ง์„ ๊ฒ€์ƒ‰ํ•˜์„ธ์š”.", + "ORG_DESCRIPTION": "{{name}} ์กฐ์ง์˜ ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.", + "ORG_DESCRIPTION_DESC": "ํ—ค๋”์˜ ์ปจํ…์ŠคํŠธ๋ฅผ ์ „ํ™˜ํ•˜์—ฌ ๋‹ค๋ฅธ ์กฐ์ง์— ๋Œ€ํ•œ ์‚ฌ์šฉ์ž ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "SEL_ORG_FORMFIELD": "์กฐ์ง", + "FOR_ORG": "๊ถŒํ•œ์ด ์ƒ์„ฑ๋œ ๋Œ€์ƒ:" + }, + "DETAIL": { + "TITLE": "ํ”„๋กœ์ ํŠธ ๊ถŒํ•œ", + "DESC": "ํŠน์ • ์กฐ์ง์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์—ญํ• ์„ ์„ ํƒํ•˜๊ณ  ๊ด€๋ฆฌ์ž๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "MEMBERTITLE": "๊ด€๋ฆฌ์ž", + "MEMBERDESC": "๊ถŒํ•œ์ด ๋ถ€์—ฌ๋œ ์กฐ์ง์˜ ๊ด€๋ฆฌ์ž์ž…๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ ๋ฐ์ดํ„ฐ ํŽธ์ง‘ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•  ์‚ฌ์šฉ์ž๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”.", + "PROJECTNAME": "ํ”„๋กœ์ ํŠธ ์ด๋ฆ„", + "GRANTEDORG": "๊ถŒํ•œ ๋ถ€์—ฌ๋œ ์กฐ์ง", + "RESOURCEOWNER": "๋ฆฌ์†Œ์Šค ์†Œ์œ ์ž" + }, + "STATE": "์ƒํƒœ", + "STATES": { + "1": "ํ™œ์„ฑํ™”", + "2": "๋น„ํ™œ์„ฑํ™”" + }, + "ALL": "๋ชจ๋‘", + "SHOWDETAIL": "์„ธ๋ถ€ ์ •๋ณด ๋ณด๊ธฐ", + "USER": "์‚ฌ์šฉ์ž", + "MEMBERS": "๊ด€๋ฆฌ์ž", + "ORG": "์กฐ์ง", + "PROJECTNAME": "ํ”„๋กœ์ ํŠธ ์ด๋ฆ„", + "GRANTEDORG": "๊ถŒํ•œ ๋ถ€์—ฌ๋œ ์กฐ์ง", + "GRANTEDORGDOMAIN": "๋„๋ฉ”์ธ", + "RESOURCEOWNER": "๋ฆฌ์†Œ์Šค ์†Œ์œ ์ž", + "GRANTEDORGNAME": "์กฐ์ง ์ด๋ฆ„", + "GRANTID": "๊ถŒํ•œ ID", + "CREATIONDATE": "์ƒ์„ฑ์ผ", + "CHANGEDATE": "๋งˆ์ง€๋ง‰ ์ˆ˜์ •์ผ", + "DATES": "๋‚ ์งœ", + "ROLENAMESLIST": "์—ญํ• ", + "NOROLES": "์—ญํ•  ์—†์Œ", + "TYPE": "์œ ํ˜•", + "TOAST": { + "PROJECTGRANTUSERGRANTADDED": "ํ”„๋กœ์ ํŠธ ๊ถŒํ•œ์ด ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "PROJECTGRANTADDED": "ํ”„๋กœ์ ํŠธ ๊ถŒํ•œ์ด ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "PROJECTGRANTCHANGED": "ํ”„๋กœ์ ํŠธ ๊ถŒํ•œ์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "PROJECTGRANTMEMBERADDED": "๊ถŒํ•œ ๊ด€๋ฆฌ์ž๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "PROJECTGRANTMEMBERCHANGED": "๊ถŒํ•œ ๊ด€๋ฆฌ์ž๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "PROJECTGRANTMEMBERREMOVED": "๊ถŒํ•œ ๊ด€๋ฆฌ์ž๊ฐ€ ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "PROJECTGRANTUPDATED": "ํ”„๋กœ์ ํŠธ ๊ถŒํ•œ์ด ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + }, + "DIALOG": { + "DELETE_TITLE": "ํ”„๋กœ์ ํŠธ ๊ถŒํ•œ ์‚ญ์ œ", + "DELETE_DESCRIPTION": "ํ”„๋กœ์ ํŠธ ๊ถŒํ•œ์„ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ •๋ง ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?" + }, + "ROLES": "ํ”„๋กœ์ ํŠธ ์—ญํ• " + }, + "APP": { + "TITLE": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜", + "NAME": "์ด๋ฆ„", + "NAMEREQUIRED": "์ด๋ฆ„์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค." + }, + "ROLE": { + "EMPTY": "์•„์ง ์ƒ์„ฑ๋œ ์—ญํ• ์ด ์—†์Šต๋‹ˆ๋‹ค.", + "ADDNEWLINE": "์ถ”๊ฐ€ ์—ญํ•  ์ถ”๊ฐ€", + "KEY": "ํ‚ค", + "TITLE": "์—ญํ• ", + "DESCRIPTION": "ํ”„๋กœ์ ํŠธ ๊ถŒํ•œ์„ ์ƒ์„ฑํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์—ญํ• ์„ ์ •์˜ํ•˜์„ธ์š”.", + "NAME": "์ด๋ฆ„", + "DISPLAY_NAME": "ํ‘œ์‹œ ์ด๋ฆ„", + "GROUP": "๊ทธ๋ฃน", + "ACTIONS": "์ž‘์—…", + "ADDTITLE": "์—ญํ•  ์ƒ์„ฑ", + "ADDDESCRIPTION": "์ƒˆ ์—ญํ• ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", + "EDITTITLE": "์—ญํ•  ์ˆ˜์ •", + "EDITDESCRIPTION": "์—ญํ• ์˜ ์ƒˆ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", + "DELETE": "์—ญํ•  ์‚ญ์ œ", + "CREATIONDATE": "์ƒ์„ฑ์ผ", + "CHANGEDATE": "๋งˆ์ง€๋ง‰ ์ˆ˜์ •์ผ", + "SELECTGROUPTOOLTIP": "{{group}} ๊ทธ๋ฃน์˜ ๋ชจ๋“  ์—ญํ• ์„ ์„ ํƒํ•˜์„ธ์š”.", + "OPTIONS": "์˜ต์…˜", + "ASSERTION": "์ธ์ฆ ์‹œ ์—ญํ•  ๊ฒ€์ฆ", + "ASSERTION_DESCRIPTION": "์—ญํ•  ์ •๋ณด๋Š” Userinfo ์—”๋“œํฌ์ธํŠธ์—์„œ ์ „์†ก๋˜๋ฉฐ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ค์ •์— ๋”ฐ๋ผ ํ† ํฐ ๋ฐ ๊ธฐํƒ€ ํ˜•์‹์œผ๋กœ ์ „์†ก๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "CHECK": "์ธ์ฆ ์‹œ ๊ถŒํ•œ ํ™•์ธ", + "CHECK_DESCRIPTION": "์„ค์ • ์‹œ, ๊ณ„์ •์— ์—ญํ• ์ด ํ• ๋‹น๋œ ์‚ฌ์šฉ์ž๋งŒ ์ธ์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "DIALOG": { + "DELETE_TITLE": "์—ญํ•  ์‚ญ์ œ", + "DELETE_DESCRIPTION": "ํ”„๋กœ์ ํŠธ ์—ญํ• ์„ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ •๋ง ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?" + } + }, + "HAS_PROJECT": "์ธ์ฆ ์‹œ ํ”„๋กœ์ ํŠธ ํ™•์ธ", + "HAS_PROJECT_DESCRIPTION": "์‚ฌ์šฉ์ž์˜ ์กฐ์ง์— ์ด ํ”„๋กœ์ ํŠธ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์—†์œผ๋ฉด ์ธ์ฆํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", + "TABLE": { + "TOTAL": "์ด ํ•ญ๋ชฉ ์ˆ˜:", + "SELECTION": "์„ ํƒํ•œ ์š”์†Œ", + "DEACTIVATE": "ํ”„๋กœ์ ํŠธ ๋น„ํ™œ์„ฑํ™”", + "ACTIVATE": "ํ”„๋กœ์ ํŠธ ํ™œ์„ฑํ™”", + "DELETE": "ํ”„๋กœ์ ํŠธ ์‚ญ์ œ", + "ORGNAME": "์กฐ์ง ์ด๋ฆ„", + "ORGDOMAIN": "์กฐ์ง ๋„๋ฉ”์ธ", + "STATE": "์ƒํƒœ", + "TYPE": "์œ ํ˜•", + "CREATIONDATE": "์ƒ์„ฑ์ผ", + "CHANGEDATE": "๋งˆ์ง€๋ง‰ ์ˆ˜์ •์ผ", + "RESOURCEOWNER": "์†Œ์œ ์ž", + "SHOWTABLE": "ํ‘œ ๋ณด๊ธฐ", + "SHOWGRID": "๊ทธ๋ฆฌ๋“œ ๋ณด๊ธฐ", + "EMPTY": "์ฐพ์€ ํ”„๋กœ์ ํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค" + }, + "TOAST": { + "MEMBERREMOVED": "๊ด€๋ฆฌ์ž๊ฐ€ ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "MEMBERSADDED": "๊ด€๋ฆฌ์ž๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "MEMBERADDED": "๊ด€๋ฆฌ์ž๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "MEMBERCHANGED": "๊ด€๋ฆฌ์ž๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "ROLESCREATED": "์—ญํ• ์ด ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "ROLEREMOVED": "์—ญํ• ์ด ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "ROLECHANGED": "์—ญํ• ์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "REACTIVATED": "์žฌํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "DEACTIVATED": "๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "CREATED": "ํ”„๋กœ์ ํŠธ๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "UPDATED": "ํ”„๋กœ์ ํŠธ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "GRANTUPDATED": "๊ถŒํ•œ์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "DELETED": "ํ”„๋กœ์ ํŠธ๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + } + }, + "ROLES": { + "DIALOG": { + "DELETE_TITLE": "์—ญํ•  ์‚ญ์ œ", + "DELETE_DESCRIPTION": "์—ญํ• ์„ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?" + } + }, + "NEXTSTEPS": { + "TITLE": "๋‹ค์Œ ๋‹จ๊ณ„" + }, + "IDP": { + "LIST": { + "ACTIVETITLE": "ํ™œ์„ฑํ™”๋œ ID ์ œ๊ณต์ž" + }, + "CREATE": { + "TITLE": "์ œ๊ณต์ž ์ถ”๊ฐ€", + "DESCRIPTION": "๋‹ค์Œ ์ œ๊ณต์ž ์ค‘ ํ•˜๋‚˜ ์ด์ƒ์„ ์„ ํƒํ•˜์„ธ์š”.", + "STEPPERTITLE": "์ œ๊ณต์ž ์ƒ์„ฑ", + "OIDC": { + "TITLE": "OIDC ์ œ๊ณต์ž", + "DESCRIPTION": "OIDC ์ œ๊ณต์ž์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”." + }, + "OAUTH": { + "TITLE": "OAuth ์ œ๊ณต์ž", + "DESCRIPTION": "OAuth ์ œ๊ณต์ž์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”." + }, + "JWT": { + "TITLE": "JWT ์ œ๊ณต์ž", + "DESCRIPTION": "JWT ์ œ๊ณต์ž์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”." + }, + "GOOGLE": { + "TITLE": "Google ์ œ๊ณต์ž", + "DESCRIPTION": "Google ID ์ œ๊ณต์ž์˜ ์ž๊ฒฉ ์ฆ๋ช…์„ ์ž…๋ ฅํ•˜์„ธ์š”." + }, + "GITLAB": { + "TITLE": "Gitlab ์ œ๊ณต์ž", + "DESCRIPTION": "Gitlab ID ์ œ๊ณต์ž์˜ ์ž๊ฒฉ ์ฆ๋ช…์„ ์ž…๋ ฅํ•˜์„ธ์š”." + }, + "GITLABSELFHOSTED": { + "TITLE": "์ž์ฒด ํ˜ธ์ŠคํŒ…๋œ Gitlab ์ œ๊ณต์ž", + "DESCRIPTION": "์ž์ฒด ํ˜ธ์ŠคํŒ…๋œ Gitlab ID ์ œ๊ณต์ž์˜ ์ž๊ฒฉ ์ฆ๋ช…์„ ์ž…๋ ฅํ•˜์„ธ์š”." + }, + "GITHUBES": { + "TITLE": "GitHub ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ ์„œ๋ฒ„ ์ œ๊ณต์ž", + "DESCRIPTION": "GitHub ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ ์„œ๋ฒ„ ID ์ œ๊ณต์ž์˜ ์ž๊ฒฉ ์ฆ๋ช…์„ ์ž…๋ ฅํ•˜์„ธ์š”." + }, + "GITHUB": { + "TITLE": "Github ์ œ๊ณต์ž", + "DESCRIPTION": "Github ID ์ œ๊ณต์ž์˜ ์ž๊ฒฉ ์ฆ๋ช…์„ ์ž…๋ ฅํ•˜์„ธ์š”." + }, + "AZUREAD": { + "TITLE": "Microsoft ์ œ๊ณต์ž", + "DESCRIPTION": "Microsoft ID ์ œ๊ณต์ž์˜ ์ž๊ฒฉ ์ฆ๋ช…์„ ์ž…๋ ฅํ•˜์„ธ์š”." + }, + "LDAP": { + "TITLE": "Active Directory / LDAP", + "DESCRIPTION": "LDAP ์ œ๊ณต์ž์˜ ์ž๊ฒฉ ์ฆ๋ช…์„ ์ž…๋ ฅํ•˜์„ธ์š”." + }, + "APPLE": { + "TITLE": "Apple๋กœ ๋กœ๊ทธ์ธ", + "DESCRIPTION": "Apple ์ œ๊ณต์ž์˜ ์ž๊ฒฉ ์ฆ๋ช…์„ ์ž…๋ ฅํ•˜์„ธ์š”." + }, + "SAML": { + "TITLE": "SAML๋กœ ๋กœ๊ทธ์ธ", + "DESCRIPTION": "SAML ์ œ๊ณต์ž์˜ ์ž๊ฒฉ ์ฆ๋ช…์„ ์ž…๋ ฅํ•˜์„ธ์š”." + } + }, + "DETAIL": { + "TITLE": "ID ์ œ๊ณต์ž", + "DESCRIPTION": "์ œ๊ณต์ž ์„ค์ •์„ ์—…๋ฐ์ดํŠธํ•˜์„ธ์š”.", + "DATECREATED": "์ƒ์„ฑ๋จ", + "DATECHANGED": "๋ณ€๊ฒฝ๋จ" + }, + "OPTIONS": { + "ISAUTOCREATION": "์ž๋™ ์ƒ์„ฑ", + "ISAUTOCREATION_DESC": "์„ ํƒ ์‹œ, ๊ณ„์ •์ด ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.", + "ISAUTOUPDATE": "์ž๋™ ์—…๋ฐ์ดํŠธ", + "ISAUTOUPDATE_DESC": "์„ ํƒ ์‹œ, ์žฌ์ธ์ฆ ์‹œ ๊ณ„์ •์ด ์—…๋ฐ์ดํŠธ๋ฉ๋‹ˆ๋‹ค.", + "ISCREATIONALLOWED": "๊ณ„์ • ์ƒ์„ฑ ํ—ˆ์šฉ (์ˆ˜๋™)", + "ISCREATIONALLOWED_DESC": "์™ธ๋ถ€ ๊ณ„์ •์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ณ„์ •์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. ์ž๋™ ์ƒ์„ฑ์ด ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž๊ฐ€ ๊ณ„์ • ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ๋ชปํ•˜๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "ISLINKINGALLOWED": "๊ณ„์ • ์—ฐ๊ฒฐ ํ—ˆ์šฉ (์ˆ˜๋™)", + "ISLINKINGALLOWED_DESC": "ID๋ฅผ ๊ธฐ์กด ๊ณ„์ •์— ์ˆ˜๋™์œผ๋กœ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. ์ž๋™ ์—ฐ๊ฒฐ์ด ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž๊ฐ€ ์ œ์•ˆ๋œ ๊ณ„์ •๋งŒ ์—ฐ๊ฒฐํ•˜๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "AUTOLINKING_DESC": "ID๊ฐ€ ๊ธฐ์กด ๊ณ„์ •์— ์—ฐ๊ฒฐ๋˜๋„๋ก ์š”์ฒญํ• ์ง€ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.", + "AUTOLINKINGTYPE": { + "0": "๋น„ํ™œ์„ฑํ™”๋จ", + "1": "๊ธฐ์กด ์‚ฌ์šฉ์ž ์ด๋ฆ„ ํ™•์ธ", + "2": "๊ธฐ์กด ์ด๋ฉ”์ผ ํ™•์ธ" + } + }, + "OWNERTYPES": { + "0": "์•Œ ์ˆ˜ ์—†์Œ", + "1": "์ธ์Šคํ„ด์Šค", + "2": "์กฐ์ง" + }, + "STATES": { + "1": "ํ™œ์„ฑํ™”", + "2": "๋น„ํ™œ์„ฑํ™”" + }, + "AZUREADTENANTTYPES": { + "3": "ํ…Œ๋„ŒํŠธ ID", + "0": "๊ณตํ†ต", + "1": "์กฐ์ง", + "2": "์†Œ๋น„์ž" + }, + "AZUREADTENANTTYPE": "ํ…Œ๋„ŒํŠธ ์œ ํ˜•", + "AZUREADTENANTID": "ํ…Œ๋„ŒํŠธ ID", + "EMAILVERIFIED": "์ด๋ฉ”์ผ ์ธ์ฆ๋จ", + "NAMEHINT": "์ง€์ •ํ•˜๋ฉด ๋กœ๊ทธ์ธ ์ธํ„ฐํŽ˜์ด์Šค์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.", + "OPTIONAL": "์„ ํƒ ์‚ฌํ•ญ", + "LDAPATTRIBUTES": "LDAP ์†์„ฑ", + "UPDATEBINDPASSWORD": "๋ฐ”์ธ๋“œ ๋น„๋ฐ€๋ฒˆํ˜ธ ์—…๋ฐ์ดํŠธ", + "UPDATECLIENTSECRET": "ํด๋ผ์ด์–ธํŠธ ์‹œํฌ๋ฆฟ ์—…๋ฐ์ดํŠธ", + "ADD": "ID ์ œ๊ณต์ž ์ถ”๊ฐ€", + "TYPE": "์œ ํ˜•", + "OWNER": "์†Œ์œ ์ž", + "ID": "ID", + "NAME": "์ด๋ฆ„", + "AUTHORIZATIONENDPOINT": "์ธ์ฆ ์—”๋“œํฌ์ธํŠธ", + "TOKENENDPOINT": "ํ† ํฐ ์—”๋“œํฌ์ธํŠธ", + "USERENDPOINT": "์‚ฌ์šฉ์ž ์—”๋“œํฌ์ธํŠธ", + "IDATTRIBUTE": "ID ์†์„ฑ", + "AVAILABILITY": "๊ฐ€์šฉ์„ฑ", + "AVAILABLE": "์‚ฌ์šฉ ๊ฐ€๋Šฅ", + "AVAILABLEBUTINACTIVE": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ๋น„ํ™œ์„ฑํ™”๋จ", + "SETAVAILABLE": "์‚ฌ์šฉ ๊ฐ€๋Šฅ์œผ๋กœ ์„ค์ •", + "SETUNAVAILABLE": "์‚ฌ์šฉ ๋ถˆ๊ฐ€๋กœ ์„ค์ •", + "CONFIG": "๊ตฌ์„ฑ", + "STATE": "์ƒํƒœ", + "ISSUER": "๋ฐœ๊ธ‰์ž", + "SCOPESLIST": "์Šค์ฝ”ํ”„ ๋ชฉ๋ก", + "CLIENTID": "ํด๋ผ์ด์–ธํŠธ ID", + "CLIENTSECRET": "ํด๋ผ์ด์–ธํŠธ ์‹œํฌ๋ฆฟ", + "LDAPCONNECTION": "์—ฐ๊ฒฐ", + "LDAPUSERBINDING": "์‚ฌ์šฉ์ž ๋ฐ”์ธ๋”ฉ", + "BASEDN": "๊ธฐ์ค€ DN", + "BINDDN": "๋ฐ”์ธ๋“œ DN", + "BINDPASSWORD": "๋ฐ”์ธ๋“œ ๋น„๋ฐ€๋ฒˆํ˜ธ", + "SERVERS": "์„œ๋ฒ„", + "STARTTLS": "TLS ์‹œ์ž‘", + "TIMEOUT": "ํƒ€์ž„์•„์›ƒ (์ดˆ)", + "USERBASE": "์‚ฌ์šฉ์ž ๋ฒ ์ด์Šค", + "USERFILTERS": "์‚ฌ์šฉ์ž ํ•„ํ„ฐ", + "USEROBJECTCLASSES": "์‚ฌ์šฉ์ž ๊ฐ์ฒด ํด๋ž˜์Šค", + "REQUIRED": "ํ•„์ˆ˜", + "LDAPIDATTRIBUTE": "ID ์†์„ฑ", + "AVATARURLATTRIBUTE": "์•„๋ฐ”ํƒ€ URL ์†์„ฑ", + "DISPLAYNAMEATTRIBUTE": "ํ‘œ์‹œ ์ด๋ฆ„ ์†์„ฑ", + "EMAILATTRIBUTEATTRIBUTE": "์ด๋ฉ”์ผ ์†์„ฑ", + "EMAILVERIFIEDATTRIBUTE": "์ด๋ฉ”์ผ ์ธ์ฆ ์†์„ฑ", + "FIRSTNAMEATTRIBUTE": "์ด๋ฆ„ ์†์„ฑ", + "LASTNAMEATTRIBUTE": "์„ฑ ์†์„ฑ", + "NICKNAMEATTRIBUTE": "๋‹‰๋„ค์ž„ ์†์„ฑ", + "PHONEATTRIBUTE": "์ „ํ™”๋ฒˆํ˜ธ ์†์„ฑ", + "PHONEVERIFIEDATTRIBUTE": "์ „ํ™” ์ธ์ฆ ์†์„ฑ", + "PREFERREDLANGUAGEATTRIBUTE": "์„ ํ˜ธ ์–ธ์–ด ์†์„ฑ", + "PREFERREDUSERNAMEATTRIBUTE": "์„ ํ˜ธ ์‚ฌ์šฉ์ž ์ด๋ฆ„ ์†์„ฑ", + "PROFILEATTRIBUTE": "ํ”„๋กœํ•„ ์†์„ฑ", + "IDPDISPLAYNAMMAPPING": "ID ์ œ๊ณต์ž ํ‘œ์‹œ ์ด๋ฆ„ ๋งคํ•‘", + "USERNAMEMAPPING": "์‚ฌ์šฉ์ž ์ด๋ฆ„ ๋งคํ•‘", + "DATES": "๋‚ ์งœ", + "CREATIONDATE": "์ƒ์„ฑ ์ผ์ž", + "CHANGEDATE": "๋งˆ์ง€๋ง‰ ์ˆ˜์ • ์ผ์ž", + "DEACTIVATE": "๋น„ํ™œ์„ฑํ™”", + "ACTIVATE": "ํ™œ์„ฑํ™”", + "DELETE": "์‚ญ์ œ", + "DELETE_TITLE": "ID ์ œ๊ณต์ž ์‚ญ์ œ", + "DELETE_DESCRIPTION": "ID ์ œ๊ณต์ž๋ฅผ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ณ€๊ฒฝ ์‚ฌํ•ญ์€ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "REMOVE_WARN_TITLE": "ID ์ œ๊ณต์ž ์ œ๊ฑฐ", + "REMOVE_WARN_DESCRIPTION": "ID ์ œ๊ณต์ž๋ฅผ ์ œ๊ฑฐํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ID ์ œ๊ณต์ž๊ฐ€ ์ œ๊ฑฐ๋˜๋ฉฐ, ์ด๋ฏธ ๋“ฑ๋ก๋œ ์‚ฌ์šฉ์ž๋Š” ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ณ„์†ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "DELETE_SELECTION_TITLE": "ID ์ œ๊ณต์ž ์‚ญ์ œ", + "DELETE_SELECTION_DESCRIPTION": "ID ์ œ๊ณต์ž๋ฅผ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ณ€๊ฒฝ ์‚ฌํ•ญ์€ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "EMPTY": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ID ์ œ๊ณต์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", + "OIDC": { + "GENERAL": "์ผ๋ฐ˜ ์ •๋ณด", + "TITLE": "OIDC ๊ตฌ์„ฑ", + "DESCRIPTION": "OIDC ID ์ œ๊ณต์ž์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”." + }, + "JWT": { + "TITLE": "JWT ๊ตฌ์„ฑ", + "DESCRIPTION": "JWT ID ์ œ๊ณต์ž์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", + "HEADERNAME": "ํ—ค๋” ์ด๋ฆ„", + "JWTENDPOINT": "JWT ์—”๋“œํฌ์ธํŠธ", + "JWTKEYSENDPOINT": "JWT ํ‚ค ์—”๋“œํฌ์ธํŠธ" + }, + "APPLE": { + "TEAMID": "ํŒ€ ID", + "KEYID": "ํ‚ค ID", + "PRIVATEKEY": "๊ฐœ์ธ ํ‚ค", + "UPDATEPRIVATEKEY": "๊ฐœ์ธ ํ‚ค ์—…๋ฐ์ดํŠธ", + "UPLOADPRIVATEKEY": "๊ฐœ์ธ ํ‚ค ์—…๋กœ๋“œ", + "KEYMAXSIZEEXCEEDED": "์ตœ๋Œ€ ํฌ๊ธฐ 5kB ์ดˆ๊ณผ" + }, + "SAML": { + "METADATAXML": "๋ฉ”ํƒ€๋ฐ์ดํ„ฐ XML", + "METADATAURL": "๋ฉ”ํƒ€๋ฐ์ดํ„ฐ URL", + "BINDING": "๋ฐ”์ธ๋”ฉ", + "SIGNEDREQUEST": "์„œ๋ช…๋œ ์š”์ฒญ", + "NAMEIDFORMAT": "NameID ํ˜•์‹", + "TRANSIENTMAPPINGATTRIBUTENAME": "์‚ฌ์šฉ์ž ๋งคํ•‘ ์†์„ฑ ์ด๋ฆ„", + "TRANSIENTMAPPINGATTRIBUTENAME_DESC": "`nameid-format`์ด `transient`์ธ ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž ๋งคํ•‘์— ์‚ฌ์šฉํ•  ๋Œ€์ฒด ์†์„ฑ ์ด๋ฆ„, ์˜ˆ: `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress`" + }, + "TOAST": { + "SAVED": "์„ฑ๊ณต์ ์œผ๋กœ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "REACTIVATED": "ID ์ œ๊ณต์ž๊ฐ€ ์žฌํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "DEACTIVATED": "ID ์ œ๊ณต์ž๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "SELECTEDREACTIVATED": "์„ ํƒ๋œ ID ์ œ๊ณต์ž๊ฐ€ ์žฌํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "SELECTEDDEACTIVATED": "์„ ํƒ๋œ ID ์ œ๊ณต์ž๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "SELECTEDKEYSDELETED": "์„ ํƒ๋œ ID ์ œ๊ณต์ž๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "DELETED": "ID ์ œ๊ณต์ž๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "ADDED": "์„ฑ๊ณต์ ์œผ๋กœ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "REMOVED": "์„ฑ๊ณต์ ์œผ๋กœ ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + }, + "ISIDTOKENMAPPING": "ID ํ† ํฐ์—์„œ ๋งคํ•‘", + "ISIDTOKENMAPPING_DESC": "์„ ํƒ ์‹œ, ์‚ฌ์šฉ์ž ์ •๋ณด ์—”๋“œํฌ์ธํŠธ๊ฐ€ ์•„๋‹Œ ID ํ† ํฐ์—์„œ ์ œ๊ณต์ž ์ •๋ณด๋ฅผ ๋งคํ•‘ํ•ฉ๋‹ˆ๋‹ค." + }, + "MFA": { + "LIST": { + "MULTIFACTORTITLE": "๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์ธ์ฆ", + "MULTIFACTORDESCRIPTION": "์—ฌ๊ธฐ์„œ ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์ธ์ฆ์„ ์œ„ํ•œ ๋‹ค์ค‘ ์ธ์ฆ ์š”์†Œ๋ฅผ ์ •์˜ํ•˜์„ธ์š”.", + "SECONDFACTORTITLE": "๋‹ค์ค‘ ์ธ์ฆ", + "SECONDFACTORDESCRIPTION": "๋น„๋ฐ€๋ฒˆํ˜ธ ์ธ์ฆ์„ ๊ฐ•ํ™”ํ•  ์ถ”๊ฐ€ ์ธ์ฆ ์š”์†Œ๋ฅผ ์ •์˜ํ•˜์„ธ์š”." + }, + "CREATE": { + "TITLE": "์ƒˆ๋กœ์šด ์ธ์ฆ ์š”์†Œ", + "DESCRIPTION": "์ƒˆ๋กœ์šด ์ธ์ฆ ์š”์†Œ ์œ ํ˜•์„ ์„ ํƒํ•˜์„ธ์š”." + }, + "DELETE": { + "TITLE": "์ธ์ฆ ์š”์†Œ ์‚ญ์ œ", + "DESCRIPTION": "๋กœ๊ทธ์ธ ์„ค์ •์—์„œ ์ธ์ฆ ์š”์†Œ๋ฅผ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?" + }, + "TOAST": { + "ADDED": "์„ฑ๊ณต์ ์œผ๋กœ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "SAVED": "์„ฑ๊ณต์ ์œผ๋กœ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "DELETED": "์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + }, + "TYPE": "์œ ํ˜•", + "MULTIFACTORTYPES": { + "0": "์•Œ ์ˆ˜ ์—†์Œ", + "1": "์ง€๋ฌธ, ๋ณด์•ˆ ํ‚ค, Face ID ๋ฐ ๊ธฐํƒ€" + }, + "SECONDFACTORTYPES": { + "0": "์•Œ ์ˆ˜ ์—†์Œ", + "1": "์ธ์ฆ ์•ฑ์„ ํ†ตํ•œ ์ผํšŒ์„ฑ ๋น„๋ฐ€๋ฒˆํ˜ธ(TOTP)", + "2": "์ง€๋ฌธ, ๋ณด์•ˆ ํ‚ค, Face ID ๋ฐ ๊ธฐํƒ€", + "3": "์ด๋ฉ”์ผ์„ ํ†ตํ•œ ์ผํšŒ์„ฑ ๋น„๋ฐ€๋ฒˆํ˜ธ(์ด๋ฉ”์ผ OTP)", + "4": "SMS๋ฅผ ํ†ตํ•œ ์ผํšŒ์„ฑ ๋น„๋ฐ€๋ฒˆํ˜ธ(SMS OTP)" + } + }, + "LOGINPOLICY": { + "CREATE": { + "TITLE": "๋กœ๊ทธ์ธ ์„ค์ •", + "DESCRIPTION": "์กฐ์ง ๋‚ด ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ๋ฐฉ๋ฒ•์„ ์ •์˜ํ•˜์„ธ์š”." + }, + "IDPS": "์•„์ด๋ดํ‹ฐํ‹ฐ ์ œ๊ณต์ž", + "ADDIDP": { + "TITLE": "์•„์ด๋ดํ‹ฐํ‹ฐ ์ œ๊ณต์ž ์ถ”๊ฐ€", + "DESCRIPTION": "์ธ์ฆ์„ ์œ„ํ•ด ์‚ฌ์ „ ์ •์˜๋œ ์ œ๊ณต์ž ๋˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ƒ์„ฑํ•œ ์ œ๊ณต์ž๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "SELECTIDPS": "์•„์ด๋ดํ‹ฐํ‹ฐ ์ œ๊ณต์ž" + }, + "PASSWORDLESS": "๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ", + "PASSWORDLESSTYPE": { + "0": "ํ—ˆ์šฉ๋˜์ง€ ์•Š์Œ", + "1": "ํ—ˆ์šฉ๋จ" + } + }, + "SMTP": { + "LIST": { + "TITLE": "SMTP ์ œ๊ณต์ž", + "DESCRIPTION": "์ด๊ฒƒ์€ ZITADEL ์ธ์Šคํ„ด์Šค์— ๋Œ€ํ•œ SMTP ์ œ๊ณต์ž ๋ชฉ๋ก์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆผ์„ ์ „์†กํ•  ์ œ๊ณต์ž๋ฅผ ํ™œ์„ฑํ™”ํ•˜์„ธ์š”.", + "EMPTY": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ SMTP ์ œ๊ณต์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", + "ACTIVATED": "ํ™œ์„ฑํ™”๋จ", + "ACTIVATE": "์ œ๊ณต์ž ํ™œ์„ฑํ™”", + "DEACTIVATE": "์ œ๊ณต์ž ๋น„ํ™œ์„ฑํ™”", + "TEST": "์ œ๊ณต์ž ํ…Œ์ŠคํŠธ", + "TYPE": "์œ ํ˜•", + "DIALOG": { + "ACTIVATED": "SMTP ์„ค์ •์ด ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "ACTIVATE_WARN_TITLE": "SMTP ์„ค์ • ํ™œ์„ฑํ™”", + "ACTIVATE_WARN_DESCRIPTION": "SMTP ์„ค์ •์„ ํ™œ์„ฑํ™”ํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ํ™œ์„ฑํ™”๋œ ์ œ๊ณต์ž๋Š” ๋น„ํ™œ์„ฑํ™”๋˜๋ฉฐ ์ƒˆ ์„ค์ •์ด ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค. ์ง„ํ–‰ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "DEACTIVATE_WARN_TITLE": "SMTP ์„ค์ • ๋น„ํ™œ์„ฑํ™”", + "DEACTIVATE_WARN_DESCRIPTION": "SMTP ์„ค์ •์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ง„ํ–‰ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "DEACTIVATED": "SMTP ์„ค์ •์ด ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "DELETE_TITLE": "SMTP ์„ค์ • ์‚ญ์ œ", + "DELETE_DESCRIPTION": "๊ตฌ์„ฑ์„ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๋ฐœ์‹ ์ž ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์—ฌ ์ด ์ž‘์—…์„ ํ™•์ธํ•˜์„ธ์š”.", + "DELETED": "SMTP ์„ค์ •์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "SENDER": "์ด SMTP ์„ค์ •์„ ์‚ญ์ œํ•˜๋ ค๋ฉด {{value}}์„ ์ž…๋ ฅํ•˜์„ธ์š”.", + "TEST_TITLE": "SMTP ์„ค์ • ํ…Œ์ŠคํŠธ", + "TEST_DESCRIPTION": "์ด ์ œ๊ณต์ž์˜ SMTP ์„ค์ •์„ ํ…Œ์ŠคํŠธํ•  ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ์ง€์ •ํ•˜์„ธ์š”.", + "TEST_EMAIL": "์ด๋ฉ”์ผ ์ฃผ์†Œ", + "TEST_RESULT": "ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ" + } + }, + "CREATE": { + "TITLE": "SMTP ์ œ๊ณต์ž ์ถ”๊ฐ€", + "DESCRIPTION": "๋‹ค์Œ ์ œ๊ณต์ž ์ค‘ ํ•˜๋‚˜ ์ด์ƒ์„ ์„ ํƒํ•˜์„ธ์š”.", + "STEPS": { + "TITLE": "{{ value }} SMTP ์ œ๊ณต์ž ์ถ”๊ฐ€", + "CREATE_DESC_TITLE": "๋‹จ๊ณ„๋ณ„๋กœ {{ value }} SMTP ์„ค์ • ์ž…๋ ฅ", + "CURRENT_DESC_TITLE": "ํ˜„์žฌ SMTP ์„ค์ •์ž…๋‹ˆ๋‹ค.", + "PROVIDER_SETTINGS": "SMTP ์ œ๊ณต์ž ์„ค์ •", + "SENDER_SETTINGS": "๋ฐœ์‹ ์ž ์„ค์ •", + "NEXT_STEPS": "๋‹ค์Œ ๋‹จ๊ณ„", + "ACTIVATE": { + "TITLE": "SMTP ์ œ๊ณต์ž ํ™œ์„ฑํ™”", + "DESCRIPTION": "SMTP ์ œ๊ณต์ž๋ฅผ ํ™œ์„ฑํ™”ํ•˜์ง€ ์•Š์œผ๋ฉด ZITADEL์ด ์•Œ๋ฆผ์„ ์ „์†กํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด ์ œ๊ณต์ž๋ฅผ ํ™œ์„ฑํ™”ํ•˜๋ฉด ํ˜„์žฌ ํ™œ์„ฑํ™”๋œ ๋‹ค๋ฅธ ์ œ๊ณต์ž๋Š” ๋น„ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค." + }, + "DEACTIVATE": { + "TITLE": "SMTP ์ œ๊ณต์ž ๋น„ํ™œ์„ฑํ™”", + "DESCRIPTION": "SMTP ์ œ๊ณต์ž๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ฉด ๋‹ค์‹œ ํ™œ์„ฑํ™”ํ•  ๋•Œ๊นŒ์ง€ ZITADEL์ด ์ด๋ฅผ ํ†ตํ•ด ์•Œ๋ฆผ์„ ์ „์†กํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." + }, + "SAVE_SETTINGS": "์„ค์ • ์ €์žฅ", + "TEST": { + "TITLE": "์„ค์ • ํ…Œ์ŠคํŠธ", + "DESCRIPTION": "SMTP ์ œ๊ณต์ž ์„ค์ •์„ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์ €์žฅํ•˜๊ธฐ ์ „์— ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.", + "RESULT": "์ด๋ฉ”์ผ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ „์†ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + } + } + }, + "DETAIL": { + "TITLE": "SMTP ์ œ๊ณต์ž ์„ค์ •" + }, + "EMPTY": "์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ SMTP ์ œ๊ณต์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", + "STEPS": { + "SENDGRID": {} + } + }, + "APP": { + "LIST": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜", + "COMPLIANCE": "OIDC ์ค€์ˆ˜", + "URLS": "URL", + "CONFIGURATION": "๊ตฌ์„ฑ", + "TOKEN": "ํ† ํฐ ์„ค์ •", + "PAGES": { + "TITLE": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜", + "ID": "ID", + "DESCRIPTION": "์—ฌ๊ธฐ์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฐ์ดํ„ฐ์™€ ๊ตฌ์„ฑ์„ ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "CREATE": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒ์„ฑ", + "CREATE_SELECT_PROJECT": "๋จผ์ € ํ”„๋กœ์ ํŠธ๋ฅผ ์„ ํƒํ•˜์„ธ์š”", + "CREATE_NEW_PROJECT": "๋˜๋Š” ์ƒˆ ํ”„๋กœ์ ํŠธ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”", + "CREATE_DESC_TITLE": "๋‹จ๊ณ„๋ณ„๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ธ๋ถ€์‚ฌํ•ญ ์ž…๋ ฅ", + "CREATE_DESC_SUB": "๊ถŒ์žฅ ๊ตฌ์„ฑ ์„ค์ •์ด ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.", + "STATE": "์ƒํƒœ", + "DATECREATED": "์ƒ์„ฑ๋จ", + "DATECHANGED": "๋ณ€๊ฒฝ๋จ", + "URLS": "URL", + "DELETE": "์•ฑ ์‚ญ์ œ", + "JUMPTOPROJECT": "์—ญํ• , ๊ถŒํ•œ ๋“ฑ์„ ๊ตฌ์„ฑํ•˜๋ ค๋ฉด ํ”„๋กœ์ ํŠธ๋กœ ์ด๋™ํ•˜์„ธ์š”.", + "DETAIL": { + "TITLE": "์„ธ๋ถ€์‚ฌํ•ญ", + "STATE": { + "0": "์ •์˜๋˜์ง€ ์•Š์Œ", + "1": "ํ™œ์„ฑ", + "2": "๋น„ํ™œ์„ฑ" + } + }, + "DIALOG": { + "CONFIG": { + "TITLE": "OIDC ๊ตฌ์„ฑ ๋ณ€๊ฒฝ" + }, + "DELETE": { + "TITLE": "์•ฑ ์‚ญ์ œ", + "DESCRIPTION": "์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?" + } + }, + "NEXTSTEPS": { + "TITLE": "๋‹ค์Œ ๋‹จ๊ณ„", + "0": { + "TITLE": "์—ญํ•  ์ถ”๊ฐ€", + "DESC": "ํ”„๋กœ์ ํŠธ ์—ญํ• ์„ ์ž…๋ ฅํ•˜์„ธ์š”" + }, + "1": { + "TITLE": "์‚ฌ์šฉ์ž ์ถ”๊ฐ€", + "DESC": "์กฐ์ง์˜ ์ƒˆ ์‚ฌ์šฉ์ž๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”" + }, + "2": { + "TITLE": "๋„์›€๋ง ๋ฐ ์ง€์›", + "DESC": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒ์„ฑ์— ๋Œ€ํ•œ ๋ฌธ์„œ๋ฅผ ์ฝ๊ฑฐ๋‚˜ ์ง€์›ํŒ€์— ๋ฌธ์˜ํ•˜์„ธ์š”" + } + } + }, + "NAMEDIALOG": { + "TITLE": "์•ฑ ์ด๋ฆ„ ๋ณ€๊ฒฝ", + "DESCRIPTION": "์•ฑ์˜ ์ƒˆ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”", + "NAME": "์ƒˆ ์ด๋ฆ„" + }, + "NAME": "์ด๋ฆ„", + "TYPE": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์œ ํ˜•", + "AUTHMETHOD": "์ธ์ฆ ๋ฐฉ๋ฒ•", + "AUTHMETHODSECTION": "์ธ์ฆ ๋ฐฉ๋ฒ•", + "GRANT": "๊ถŒํ•œ ๋ถ€์—ฌ ์œ ํ˜•", + "ADDITIONALORIGINS": "์ถ”๊ฐ€ ์ถœ์ฒ˜", + "ADDITIONALORIGINSDESC": "๋ฆฌ๋””๋ ‰์…˜์— ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ์ถ”๊ฐ€ ์ถœ์ฒ˜๋ฅผ ์•ฑ์— ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด ์—ฌ๊ธฐ์—์„œ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "ORIGINS": "์ถœ์ฒ˜", + "NOTANORIGIN": "์ž…๋ ฅ๋œ ๊ฐ’์ด ์œ ํšจํ•œ ์ถœ์ฒ˜๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค", + "PROSWITCH": "์ „๋ฌธ๊ฐ€ ๋ชจ๋“œ๋กœ ์ง„ํ–‰ํ•˜๊ธฐ", + "NAMEANDTYPESECTION": "์ด๋ฆ„๊ณผ ์œ ํ˜•", + "TITLEFIRST": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ด๋ฆ„", + "TYPETITLE": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์œ ํ˜•", + "OIDC": { + "WELLKNOWN": "์ถ”๊ฐ€ ๋งํฌ๋Š”
ํƒ์ƒ‰ ์—”๋“œํฌ์ธํŠธ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "INFO": { + "ISSUER": "๋ฐœ๊ธ‰์ž", + "CLIENTID": "ํด๋ผ์ด์–ธํŠธ ID" + }, + "CURRENT": "ํ˜„์žฌ ๊ตฌ์„ฑ", + "TOKENSECTIONTITLE": "์ธ์ฆ ํ† ํฐ ์˜ต์…˜", + "REDIRECTSECTIONTITLE": "๋ฆฌ๋””๋ ‰์…˜ ์„ค์ •", + "REDIRECTTITLE": "๋กœ๊ทธ์ธ ํ›„ ๋ฆฌ๋””๋ ‰์…˜๋  URI๋ฅผ ์ง€์ •ํ•˜์„ธ์š”.", + "POSTREDIRECTTITLE": "๋กœ๊ทธ์•„์›ƒ ํ›„ ๋ฆฌ๋””๋ ‰์…˜ URI์ž…๋‹ˆ๋‹ค.", + "REDIRECTDESCRIPTIONWEB": "๋ฆฌ๋””๋ ‰์…˜ URI๋Š” https://๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ๋ชจ๋“œ์—์„œ๋งŒ http://๊ฐ€ ์œ ํšจํ•ฉ๋‹ˆ๋‹ค.", + "REDIRECTDESCRIPTIONNATIVE": "๋ฆฌ๋””๋ ‰์…˜ URI๋Š” http://127.0.0.1, http://[::1] ๋˜๋Š” http://localhost์™€ ๊ฐ™์€ ์ž์ฒด ํ”„๋กœํ† ์ฝœ๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "REDIRECTNOTVALID": "์œ ํšจํ•˜์ง€ ์•Š์€ ๋ฆฌ๋””๋ ‰์…˜ URI์ž…๋‹ˆ๋‹ค.", + "COMMAORENTERSEPERATION": "โ†ต๋กœ ๊ตฌ๋ถ„", + "TYPEREQUIRED": "์œ ํ˜•์€ ํ•„์ˆ˜ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.", + "TITLE": "OIDC ๊ตฌ์„ฑ", + "CLIENTID": "ํด๋ผ์ด์–ธํŠธ ID", + "CLIENTSECRET": "ํด๋ผ์ด์–ธํŠธ ์‹œํฌ๋ฆฟ", + "CLIENTSECRET_NOSECRET": "์„ ํƒํ•œ ์ธ์ฆ ํ”Œ๋กœ์šฐ์—์„œ๋Š” ์‹œํฌ๋ฆฟ์ด ํ•„์š”ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", + "CLIENTSECRET_DESCRIPTION": "ํด๋ผ์ด์–ธํŠธ ์‹œํฌ๋ฆฟ์„ ์•ˆ์ „ํ•œ ๊ณณ์— ๋ณด๊ด€ํ•˜์„ธ์š”. ๋Œ€ํ™” ์ƒ์ž๊ฐ€ ๋‹ซํžˆ๋ฉด ์‹œํฌ๋ฆฟ์ด ์‚ฌ๋ผ์ง‘๋‹ˆ๋‹ค.", + "REGENERATESECRET": "ํด๋ผ์ด์–ธํŠธ ์‹œํฌ๋ฆฟ ์žฌ์ƒ์„ฑ", + "DEVMODE": "๊ฐœ๋ฐœ ๋ชจ๋“œ", + "DEVMODE_ENABLED": "ํ™œ์„ฑํ™”๋จ", + "DEVMODE_DISABLED": "๋น„ํ™œ์„ฑํ™”๋จ", + "DEVMODEDESC": "์ฃผ์˜: ๊ฐœ๋ฐœ ๋ชจ๋“œ๊ฐ€ ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ ๋ฆฌ๋””๋ ‰์…˜ URI๊ฐ€ ๊ฒ€์ฆ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + "SKIPNATIVEAPPSUCCESSPAGE": "๋กœ๊ทธ์ธ ์„ฑ๊ณต ํŽ˜์ด์ง€ ๊ฑด๋„ˆ๋›ฐ๊ธฐ", + "SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "๋„ค์ดํ‹ฐ๋ธŒ ์•ฑ ๋กœ๊ทธ์ธ ํ›„ ์„ฑ๊ณต ํŽ˜์ด์ง€๋ฅผ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.", + "REDIRECT": "๋ฆฌ๋””๋ ‰์…˜ URI", + "REDIRECTSECTION": "๋ฆฌ๋””๋ ‰์…˜ URI", + "POSTLOGOUTREDIRECT": "๋กœ๊ทธ์•„์›ƒ ํ›„ ๋ฆฌ๋””๋ ‰์…˜ URI", + "RESPONSESECTION": "์‘๋‹ต ์œ ํ˜•", + "GRANTSECTION": "๊ถŒํ•œ ๋ถ€์—ฌ ์œ ํ˜•", + "GRANTTITLE": "๊ถŒํ•œ ๋ถ€์—ฌ ์œ ํ˜•์„ ์„ ํƒํ•˜์„ธ์š”. ์ฐธ๊ณ : Implicit์€ ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.", + "APPTYPE": { + "0": "์›น", + "1": "์‚ฌ์šฉ์ž ์—์ด์ „ํŠธ", + "2": "๋„ค์ดํ‹ฐ๋ธŒ" + }, + "RESPONSETYPE": "์‘๋‹ต ์œ ํ˜•", + "RESPONSE": { + "0": "์ฝ”๋“œ", + "1": "ID ํ† ํฐ", + "2": "ํ† ํฐ-ID ํ† ํฐ" + }, + "REFRESHTOKEN": "์ƒˆ๋กœ ๊ณ ์นจ ํ† ํฐ", + "GRANTTYPE": "๊ถŒํ•œ ๋ถ€์—ฌ ์œ ํ˜•", + "GRANT": { + "0": "Authorization Code", + "1": "Implicit", + "2": "Refresh Token", + "3": "Device Code", + "4": "Token Exchange" + }, + "AUTHMETHOD": { + "0": "๊ธฐ๋ณธ", + "1": "POST", + "2": "์—†์Œ", + "3": "ํ”„๋ผ์ด๋น— ํ‚ค JWT" + }, + "TOKENTYPE": "์ธ์ฆ ํ† ํฐ ์œ ํ˜•", + "TOKENTYPE0": "Bearer Token", + "TOKENTYPE1": "JWT", + "UNSECUREREDIRECT": "์ •๋ง๋กœ ์ด ์„ค์ •์„ ์•Œ๊ณ  ๊ณ„์‹ ๊ฐ€์š”?", + "OVERVIEWSECTION": "๊ฐœ์š”", + "OVERVIEWTITLE": "๊ตฌ์„ฑ์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์„ค์ •์„ ๊ฒ€ํ† ํ•˜์„ธ์š”.", + "ACCESSTOKENROLEASSERTION": "์•ก์„ธ์Šค ํ† ํฐ์— ์‚ฌ์šฉ์ž ์—ญํ•  ์ถ”๊ฐ€", + "ACCESSTOKENROLEASSERTION_DESCRIPTION": "์„ ํƒ ์‹œ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ๋œ ์—ญํ• ์ด ์•ก์„ธ์Šค ํ† ํฐ์— ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค.", + "IDTOKENROLEASSERTION": "ID ํ† ํฐ์— ์‚ฌ์šฉ์ž ์—ญํ•  ์ถ”๊ฐ€", + "IDTOKENROLEASSERTION_DESCRIPTION": "์„ ํƒ ์‹œ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ๋œ ์—ญํ• ์ด ID ํ† ํฐ์— ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค.", + "IDTOKENUSERINFOASSERTION": "ID ํ† ํฐ ๋‚ด ์‚ฌ์šฉ์ž ์ •๋ณด", + "IDTOKENUSERINFOASSERTION_DESCRIPTION": "ํด๋ผ์ด์–ธํŠธ๊ฐ€ ID ํ† ํฐ์—์„œ ํ”„๋กœํ•„, ์ด๋ฉ”์ผ, ์ „ํ™”๋ฒˆํ˜ธ ๋ฐ ์ฃผ์†Œ ํด๋ ˆ์ž„์„ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "CLOCKSKEW": "OP์™€ ํด๋ผ์ด์–ธํŠธ ๊ฐ„์˜ ์‹œ๊ฐ„ ์˜ค์ฐจ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. (0-5์ดˆ) ๋™์•ˆ exp ํด๋ ˆ์ž„์— ์ถ”๊ฐ€๋˜๊ณ  iats, auth_time ๋ฐ nbf์—์„œ ๊ฐ์†Œ๋ฉ๋‹ˆ๋‹ค.", + "RECOMMENDED": "๊ถŒ์žฅ", + "NOTRECOMMENDED": "๊ถŒ์žฅํ•˜์ง€ ์•Š์Œ", + "SELECTION": { + "APPTYPE": { + "WEB": { + "TITLE": "์›น", + "DESCRIPTION": ".net, PHP, Node.js, Java ๋“ฑ๊ณผ ๊ฐ™์€ ์ผ๋ฐ˜ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜" + }, + "NATIVE": { + "TITLE": "๋„ค์ดํ‹ฐ๋ธŒ", + "DESCRIPTION": "๋ชจ๋ฐ”์ผ ์•ฑ, ๋ฐ์Šคํฌํ†ฑ, ์Šค๋งˆํŠธ ๊ธฐ๊ธฐ ๋“ฑ" + }, + "USERAGENT": { + "TITLE": "์‚ฌ์šฉ์ž ์—์ด์ „ํŠธ", + "DESCRIPTION": "๋‹จ์ผ ํŽ˜์ด์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(SPA) ๋ฐ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰๋˜๋Š” ๋ชจ๋“  JS ํ”„๋ ˆ์ž„์›Œํฌ" + } + } + } + }, + "API": { + "INFO": { + "CLIENTID": "ํด๋ผ์ด์–ธํŠธ ID" + }, + "REGENERATESECRET": "ํด๋ผ์ด์–ธํŠธ ์‹œํฌ๋ฆฟ ์žฌ์ƒ์„ฑ", + "SELECTION": { + "TITLE": "API", + "DESCRIPTION": "์ผ๋ฐ˜์ ์ธ API" + }, + "AUTHMETHOD": { + "0": "๊ธฐ๋ณธ", + "1": "ํ”„๋ผ์ด๋น— ํ‚ค JWT" + } + }, + "SAML": { + "SELECTION": { + "TITLE": "SAML", + "DESCRIPTION": "SAML ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜" + }, + "CONFIGSECTION": "SAML ๊ตฌ์„ฑ", + "CHOOSEMETADATASOURCE": "SAML ๊ตฌ์„ฑ์„ ๋‹ค์Œ ์˜ต์…˜ ์ค‘ ํ•˜๋‚˜๋กœ ์ œ๊ณตํ•˜์„ธ์š”:", + "METADATAOPT1": "์˜ต์…˜ 1. ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํŒŒ์ผ์ด ์œ„์น˜ํ•œ URL์„ ์ง€์ •ํ•˜์„ธ์š”", + "METADATAOPT2": "์˜ต์…˜ 2. ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ XML์ด ํฌํ•จ๋œ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์„ธ์š”", + "METADATAOPT3": "์˜ต์…˜ 3. ENTITYID ๋ฐ ACS URL์„ ์ œ๊ณตํ•˜์—ฌ ์ตœ์†Œํ•œ์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํŒŒ์ผ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ƒ์„ฑ", + "UPLOAD": "XML ํŒŒ์ผ ์—…๋กœ๋“œ", + "METADATA": "๋ฉ”ํƒ€๋ฐ์ดํ„ฐ", + "METADATAFROMFILE": "ํŒŒ์ผ์—์„œ ๊ฐ€์ ธ์˜จ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ", + "CERTIFICATE": "SAML ์ธ์ฆ์„œ", + "DOWNLOADCERT": "SAML ์ธ์ฆ์„œ ๋‹ค์šด๋กœ๋“œ", + "CREATEMETADATA": "๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ƒ์„ฑ", + "ENTITYID": "์—”ํ„ฐํ‹ฐ ID", + "ACSURL": "ACS ์—”๋“œํฌ์ธํŠธ URL" + }, + "AUTHMETHODS": { + "CODE": { + "TITLE": "์ฝ”๋“œ", + "DESCRIPTION": "ํ† ํฐ๊ณผ ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ๊ตํ™˜ํ•ฉ๋‹ˆ๋‹ค" + }, + "PKCE": { + "TITLE": "PKCE", + "DESCRIPTION": "๋” ๋†’์€ ๋ณด์•ˆ์„ ์œ„ํ•ด ์ •์  ํด๋ผ์ด์–ธํŠธ ์‹œํฌ๋ฆฟ ๋Œ€์‹  ์ž„์˜ ํ•ด์‹œ ์‚ฌ์šฉ" + }, + "POST": { + "TITLE": "POST", + "DESCRIPTION": "ํผ์˜ ์ผ๋ถ€๋กœ client_id ๋ฐ client_secret ์ „์†ก" + }, + "PK_JWT": { + "TITLE": "ํ”„๋ผ์ด๋น— ํ‚ค JWT", + "DESCRIPTION": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธ์ฆ์„ ์œ„ํ•ด ํ”„๋ผ์ด๋น— ํ‚ค ์‚ฌ์šฉ" + }, + "BASIC": { + "TITLE": "๊ธฐ๋ณธ", + "DESCRIPTION": "์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ์ธ์ฆ" + }, + "IMPLICIT": { + "TITLE": "์•”๋ฌต์ ", + "DESCRIPTION": "์ธ์ฆ ์—”๋“œํฌ์ธํŠธ์—์„œ ์ง์ ‘ ํ† ํฐ ์ˆ˜์‹ " + }, + "DEVICECODE": { + "TITLE": "๋””๋ฐ”์ด์Šค ์ฝ”๋“œ", + "DESCRIPTION": "์ปดํ“จํ„ฐ ๋˜๋Š” ์Šค๋งˆํŠธํฐ์—์„œ ์žฅ์น˜ ์ธ์ฆ" + }, + "CUSTOM": { + "TITLE": "์‚ฌ์šฉ์ž ์ •์˜", + "DESCRIPTION": "์„ค์ •์ด ๋‹ค๋ฅธ ์˜ต์…˜๊ณผ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค." + } + }, + "TOAST": { + "REACTIVATED": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋‹ค์‹œ ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "DEACTIVATED": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "OIDCUPDATED": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "APIUPDATED": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "UPDATED": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "CREATED": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "CLIENTSECRETREGENERATED": "ํด๋ผ์ด์–ธํŠธ ์‹œํฌ๋ฆฟ์ด ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "DELETED": "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "CONFIGCHANGED": "๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ๊ฐ์ง€๋˜์—ˆ์Šต๋‹ˆ๋‹ค!" + } + }, + "GENDERS": { + "0": "์•Œ ์ˆ˜ ์—†์Œ", + "1": "์—ฌ์„ฑ", + "2": "๋‚จ์„ฑ", + "3": "๊ธฐํƒ€" + }, + "LANGUAGES": { + "de": "Deutsch", + "en": "English", + "es": "Espaรฑol", + "fr": "Franรงais", + "it": "Italiano", + "ja": "ๆ—ฅๆœฌ่ชž", + "pl": "Polski", + "zh": "็ฎ€ไฝ“ไธญๆ–‡", + "bg": "ะ‘ัŠะปะณะฐั€ัะบะธ", + "pt": "Portuguese", + "mk": "ะœะฐะบะตะดะพะฝัะบะธ", + "cs": "ฤŒeลกtina", + "ru": "ะ ัƒััะบะธะน", + "nl": "Nederlands", + "sv": "Svenska", + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" + }, + "MEMBER": { + "ADD": "๋งค๋‹ˆ์ € ์ถ”๊ฐ€", + "CREATIONTYPE": "์ƒ์„ฑ ์œ ํ˜•", + "CREATIONTYPES": { + "3": "IAM", + "2": "์กฐ์ง", + "0": "์†Œ์œ  ํ”„๋กœ์ ํŠธ", + "1": "๋ถ€์—ฌ๋œ ํ”„๋กœ์ ํŠธ", + "4": "ํ”„๋กœ์ ํŠธ" + }, + "EDITROLE": "์—ญํ•  ํŽธ์ง‘", + "EDITFOR": "์‚ฌ์šฉ์ž์˜ ์—ญํ•  ํŽธ์ง‘: {{value}}", + "DIALOG": { + "DELETE_TITLE": "๋งค๋‹ˆ์ € ์ œ๊ฑฐ", + "DELETE_DESCRIPTION": "๋งค๋‹ˆ์ €๋ฅผ ์ œ๊ฑฐํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๊ณ„์†ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?" + }, + "SHOWDETAILS": "์„ธ๋ถ€ ์ •๋ณด ๋ณด๋ ค๋ฉด ํด๋ฆญํ•˜์„ธ์š”." + }, + "ROLESLABEL": "์—ญํ• ", + "GRANTS": { + "TITLE": "๊ถŒํ•œ", + "DESC": "์กฐ์ง์˜ ๋ชจ๋“  ๊ถŒํ•œ์ž…๋‹ˆ๋‹ค.", + "DELETE": "๊ถŒํ•œ ์‚ญ์ œ", + "EMPTY": "๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค", + "ADD": "๊ถŒํ•œ ์ƒ์„ฑ", + "ADD_BTN": "์ƒˆ๋กœ ์ถ”๊ฐ€", + "PROJECT": { + "TITLE": "๊ถŒํ•œ", + "DESCRIPTION": "์ง€์ •๋œ ํ”„๋กœ์ ํŠธ์— ๋Œ€ํ•œ ๊ถŒํ•œ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ๊ถŒํ•œ์ด ์žˆ๋Š” ํ”„๋กœ์ ํŠธ์™€ ์‚ฌ์šฉ์ž๋งŒ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "USER": { + "TITLE": "๊ถŒํ•œ", + "DESCRIPTION": "์ง€์ •๋œ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ๊ถŒํ•œ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ๊ถŒํ•œ์ด ์žˆ๋Š” ํ”„๋กœ์ ํŠธ์™€ ์‚ฌ์šฉ์ž๋งŒ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "CREATE": { + "TITLE": "๊ถŒํ•œ ์ƒ์„ฑ", + "DESCRIPTION": "์กฐ์ง, ํ”„๋กœ์ ํŠธ ๋ฐ ํ•ด๋‹น ์—ญํ• ์„ ๊ฒ€์ƒ‰ํ•˜์„ธ์š”." + }, + "EDIT": { + "TITLE": "๊ถŒํ•œ ๋ณ€๊ฒฝ" + }, + "DETAIL": { + "TITLE": "๊ถŒํ•œ ์„ธ๋ถ€ ์ •๋ณด", + "DESCRIPTION": "์—ฌ๊ธฐ์—์„œ ๊ถŒํ•œ์˜ ๋ชจ๋“  ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "TOAST": { + "UPDATED": "๊ถŒํ•œ์ด ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "REMOVED": "๊ถŒํ•œ์ด ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "BULKREMOVED": "๊ถŒํ•œ์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "CANTSHOWINFO": "์ด ์‚ฌ์šฉ์ž๊ฐ€ ์†ํ•œ ์กฐ์ง์˜ ๊ตฌ์„ฑ์›์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ์ž์˜ ํ”„๋กœํ•„์„ ๋ณผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." + }, + "DIALOG": { + "DELETE_TITLE": "๊ถŒํ•œ ์‚ญ์ œ", + "DELETE_DESCRIPTION": "๊ถŒํ•œ์„ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๊ณ„์†ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "BULK_DELETE_TITLE": "๊ถŒํ•œ ์‚ญ์ œ", + "BULK_DELETE_DESCRIPTION": "์—ฌ๋Ÿฌ ๊ถŒํ•œ์„ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๊ณ„์†ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?" + } + }, + "CHANGES": { + "LISTTITLE": "์ตœ๊ทผ ๋ณ€๊ฒฝ ์‚ฌํ•ญ", + "BOTTOM": "๋ชฉ๋ก์˜ ๋์— ๋„๋‹ฌํ–ˆ์Šต๋‹ˆ๋‹ค.", + "LOADMORE": "๋” ๋ถˆ๋Ÿฌ์˜ค๊ธฐ", + "ORG": { + "TITLE": "ํ™œ๋™", + "DESCRIPTION": "์กฐ์ง ๋ณ€๊ฒฝ์„ ์œ ๋ฐœํ•œ ์ตœ์‹  ์ด๋ฒคํŠธ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "PROJECT": { + "TITLE": "ํ™œ๋™", + "DESCRIPTION": "ํ”„๋กœ์ ํŠธ ๋ณ€๊ฒฝ์„ ์œ ๋ฐœํ•œ ์ตœ์‹  ์ด๋ฒคํŠธ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "USER": { + "TITLE": "ํ™œ๋™", + "DESCRIPTION": "์‚ฌ์šฉ์ž ๋ณ€๊ฒฝ์„ ์œ ๋ฐœํ•œ ์ตœ์‹  ์ด๋ฒคํŠธ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + } + } +} diff --git a/console/src/assets/i18n/mk.json b/console/src/assets/i18n/mk.json index 5625374d1d..ab0481b6bf 100644 --- a/console/src/assets/i18n/mk.json +++ b/console/src/assets/i18n/mk.json @@ -1385,7 +1385,8 @@ "nl": "Nederlands", "sv": "Svenska", "id": "Bahasa Indonesia", - "hu": "Magyar" + "hu": "Magyar", + "ko": "ํ•œ๊ตญ์–ด" } }, "SMTP": { @@ -1622,7 +1623,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "KEYS": { "emailVerificationDoneText": "ะ•-ะฟะพัˆั‚ะฐั‚ะฐ ะต ะฒะตั€ะธั„ะธั†ะธั€ะฐะฝะฐ", @@ -1722,8 +1724,8 @@ "ALLOWUSERNAMEPASSWORD_DESC": "ะ”ะพะทะฒะพะปะตะฝะฐ ะต ะบะพะฝะฒะตะฝั†ะธะพะฝะฐะปะฝะฐ ะฝะฐั˜ะฐะฒะฐ ัะพ ะบะพั€ะธัะฝะธั‡ะบะพ ะธะผะต ะธ ะปะพะทะธะฝะบะฐ.", "ALLOWEXTERNALIDP_DESC": "ะะฐั˜ะฐะฒะฐั‚ะฐ ะต ะดะพะทะฒะพะปะตะฝะฐ ะทะฐ ะฟะพะดะดั€ะถัƒะฒะฐะฝะธั‚ะต IDPs", "ALLOWREGISTER_DESC": "ะ”ะพะบะพะปะบัƒ ะต ะธะทะฑั€ะฐะฝะฐ ะพะฟั†ะธั˜ะฐั‚ะฐ, ัะต ะฟั€ะธะบะฐะถัƒะฒะฐ ะดะพะฟะพะปะฝะธั‚ะตะปะตะฝ ั‡ะตะบะพั€ ะทะฐ ั€ะตะณะธัั‚ั€ะธั€ะฐัšะต ะฝะฐ ะบะพั€ะธัะฝะธะบ ะฒะพ ะฝะฐั˜ะฐะฒะฐั‚ะฐ.", - "FORCEMFA": "ะ—ะฐะดะพะปะถะธั‚ะตะปะฝะฐ MFA", - "FORCEMFALOCALONLY": "Force MFA ะทะฐ ะปะพะบะฐะปะฝะธ ะบะพั€ะธัะฝะธั†ะธ", + "FORCEMFA": "ะะฐะผะตั‚ะฝะตั‚ะต MFA ะทะฐ ัะธั‚ะต ะบะพั€ะธัะฝะธั†ะธ", + "FORCEMFALOCALONLY": "ะะฐะผะตั‚ะฝะตั‚ะต MFA ัะฐะผะพ ะทะฐ ะปะพะบะฐะปะฝะพ ะฐะฒั‚ะตะฝั‚ะธั„ะธั†ะธั€ะฐะฝะธ ะบะพั€ะธัะฝะธั†ะธ", "FORCEMFALOCALONLY_DESC": "ะะบะพ ะต ะธะทะฑั€ะฐะฝะฐ ะพะฟั†ะธั˜ะฐั‚ะฐ, ะปะพะบะฐะปะฝะธั‚ะต ะฐะฒั‚ะตะฝั‚ะธั†ะธั€ะฐะฝะธ ะบะพั€ะธัะฝะธั†ะธ ั‚ั€ะตะฑะฐ ะดะฐ ะบะพะฝั„ะธะณัƒั€ะธั€ะฐะฐั‚ ะฒั‚ะพั€ ั„ะฐะบั‚ะพั€ ะทะฐ ะฝะฐั˜ะฐะฒัƒะฒะฐัšะต.", "HIDEPASSWORDRESET_DESC": "ะ”ะพะบะพะปะบัƒ ะต ะธะทะฑั€ะฐะฝะฐ ะพะฟั†ะธั˜ะฐั‚ะฐ, ะบะพั€ะธัะฝะธะบะพั‚ ะฝะตะผะฐ ะดะฐ ะผะพะถะต ะดะฐ ั˜ะฐ ั€ะตัะตั‚ะธั€ะฐ ัะฒะพั˜ะฐั‚ะฐ ะปะพะทะธะฝะบะฐ ะฒะพ ะฟั€ะพั†ะตัะพั‚ ะฝะฐ ะฝะฐั˜ะฐะฒะฐ.", "HIDELOGINNAMESUFFIX": "ะกะพะบั€ะธั˜ ะณะพ ััƒั„ะธะบัะพั‚ ะฝะฐ ะบะพั€ะธัะฝะธั‡ะบะพั‚ะพ ะธะผะต", @@ -2560,7 +2562,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "MEMBER": { "ADD": "ะ”ะพะดะฐั˜ ะœะตะฝะฐัŸะตั€", diff --git a/console/src/assets/i18n/nl.json b/console/src/assets/i18n/nl.json index 3e67d36f29..efc5513e68 100644 --- a/console/src/assets/i18n/nl.json +++ b/console/src/assets/i18n/nl.json @@ -1620,7 +1620,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "KEYS": { "emailVerificationDoneText": "E-mail verificatie voltooid", @@ -1720,8 +1721,8 @@ "ALLOWUSERNAMEPASSWORD_DESC": "De conventionele login met gebruikersnaam en wachtwoord is toegestaan.", "ALLOWEXTERNALIDP_DESC": "De login is toegestaan voor de onderliggende identiteitsproviders", "ALLOWREGISTER_DESC": "Als de optie is geselecteerd, verschijnt er een extra stap voor het registreren van een gebruiker in het login proces.", - "FORCEMFA": "Forceer MFA", - "FORCEMFALOCALONLY": "Forceer MFA voor lokaal geauthenticeerde gebruikers", + "FORCEMFA": "MFA afdwingen voor alle gebruikers", + "FORCEMFALOCALONLY": "MFA alleen afdwingen voor lokaal geverifieerde gebruikers", "FORCEMFALOCALONLY_DESC": "Als de optie is geselecteerd, moeten lokaal geauthenticeerde gebruikers een tweede factor configureren voor login.", "HIDEPASSWORDRESET_DESC": "Als de optie is geselecteerd, kan de gebruiker zijn wachtwoord niet resetten in het login proces.", "HIDELOGINNAMESUFFIX": "Verberg Inlognaam achtervoegsel", @@ -2580,7 +2581,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "MEMBER": { "ADD": "Voeg een Manager toe", diff --git a/console/src/assets/i18n/pl.json b/console/src/assets/i18n/pl.json index e9874d286c..0443b89a89 100644 --- a/console/src/assets/i18n/pl.json +++ b/console/src/assets/i18n/pl.json @@ -1383,7 +1383,8 @@ "nl": "Nederlands", "sv": "Svenska", "id": "Bahasa Indonesia", - "hu": "Magyar" + "hu": "Magyar", + "ko": "ํ•œ๊ตญ์–ด" } }, "SMTP": { @@ -1620,7 +1621,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "KEYS": { "emailVerificationDoneText": "Weryfikacja adresu e-mail zakoล„czona", @@ -1720,8 +1722,8 @@ "ALLOWUSERNAMEPASSWORD_DESC": "Zwykล‚e logowanie za pomocฤ… nazwy uลผytkownika i hasล‚a jest dozwolone.", "ALLOWEXTERNALIDP_DESC": "Logowanie jest dozwolone dla dostawcรณw toลผsamoล›ci podstawowych", "ALLOWREGISTER_DESC": "Jeล›li ta opcja jest zaznaczona, pojawi siฤ™ dodatkowy krok rejestracji uลผytkownika w procesie logowania.", - "FORCEMFA": "Wymuล› MFA", - "FORCEMFALOCALONLY": "Wymuล› MFA dla lokalnych uลผytkownikรณw", + "FORCEMFA": "Wymuล› MFA dla wszystkich uลผytkownikรณw", + "FORCEMFALOCALONLY": "Wymuล› MFA tylko dla lokalnie uwierzytelnionych uลผytkownikรณw", "FORCEMFALOCALONLY_DESC": "Jeล›li ta opcja jest zaznaczona, lokalni uwierzytelnieni uลผytkownicy muszฤ… skonfigurowaฤ‡ drugi czynnik logowania.", "HIDEPASSWORDRESET_DESC": "Jeล›li ta opcja jest zaznaczona, uลผytkownik nie moลผe zresetowaฤ‡ swojego hasล‚a w procesie logowania.", "HIDELOGINNAMESUFFIX": "Ukryj sufiks nazwy uลผytkownika", @@ -2563,7 +2565,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "MEMBER": { "ADD": "Dodaj managera", diff --git a/console/src/assets/i18n/pt.json b/console/src/assets/i18n/pt.json index 1885358bc1..3bbb4e9c9b 100644 --- a/console/src/assets/i18n/pt.json +++ b/console/src/assets/i18n/pt.json @@ -1385,7 +1385,8 @@ "nl": "Nederlands", "sv": "Svenska", "id": "Bahasa Indonesia", - "hu": "Magyar" + "hu": "Magyar", + "ko": "ํ•œ๊ตญ์–ด" } }, "SMTP": { @@ -1622,7 +1623,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "KEYS": { "emailVerificationDoneText": "Verificaรงรฃo de email concluรญda", @@ -1722,7 +1724,8 @@ "ALLOWUSERNAMEPASSWORD_DESC": "O login convencional com nome de usuรกrio e senha รฉ permitido.", "ALLOWEXTERNALIDP_DESC": "O login รฉ permitido para os provedores de identidade subjacentes", "ALLOWREGISTER_DESC": "Se a opรงรฃo estiver selecionada, uma etapa adicional para registrar um usuรกrio aparecerรก no login.", - "FORCEMFA": "Forรงar MFA", + "FORCEMFA": "Forรงar MFA para todos os utilizadores", + "FORCEMFALOCALONLY": "Forรงar MFA apenas para utilizadores autenticados localmente", "HIDEPASSWORDRESET_DESC": "Se a opรงรฃo estiver selecionada, o usuรกrio nรฃo poderรก redefinir sua senha no processo de login.", "HIDELOGINNAMESUFFIX": "Ocultar sufixo do nome de login", "HIDELOGINNAMESUFFIX_DESC": "Oculta o sufixo do nome de login na interface de login", @@ -2558,7 +2561,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "MEMBER": { "ADD": "Adicionar um Gerente", diff --git a/console/src/assets/i18n/ru.json b/console/src/assets/i18n/ru.json index 56ef096d73..cdbb49d708 100644 --- a/console/src/assets/i18n/ru.json +++ b/console/src/assets/i18n/ru.json @@ -1428,7 +1428,8 @@ "nl": "Nederlands", "sv": "Svenska", "id": "Bahasa Indonesia", - "hu": "Magyar" + "hu": "Magyar", + "ko": "ํ•œ๊ตญ์–ด" } }, "SMTP": { @@ -1677,7 +1678,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "LOCALE": "ะšะพะด ัะทั‹ะบะฐ", "LOCALES": { @@ -1792,7 +1794,8 @@ "ALLOWUSERNAMEPASSWORD_DESC": "ะ”ะพะฟัƒัะบะฐะตั‚ัั ัั‚ะฐะฝะดะฐั€ั‚ะฝั‹ะน ะฒั…ะพะด ั ะธะผะตะฝะตะผ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะธ ะฟะฐั€ะพะปะตะผ.", "ALLOWEXTERNALIDP_DESC": "ะ’ั…ะพะด ั€ะฐะทั€ะตัˆั‘ะฝ ะดะปั ะพัะฝะพะฒะฝั‹ั… ะฟะพัั‚ะฐะฒั‰ะธะบะพะฒ ะธะดะตะฝั‚ะธั„ะธะบะฐั†ะธะพะฝะฝั‹ั… ะดะฐะฝะฝั‹ั….", "ALLOWREGISTER_DESC": "ะ•ัะปะธ ะดะฐะฝะฝั‹ะน ะฟะฐั€ะฐะผะตั‚ั€ ะฒั‹ะฑั€ะฐะฝ, ะฟั€ะธ ะฒั…ะพะดะต ะฒ ัะธัั‚ะตะผัƒ ะฟะพัะฒะปัะตั‚ัั ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝั‹ะน ัˆะฐะณ ะดะปั ั€ะตะณะธัั‚ั€ะฐั†ะธะธ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั.", - "FORCEMFA": "ะŸั€ะธะฝัƒะดะธั‚ะตะปัŒะฝะฐั ะผะฝะพะณะพั„ะฐะบั‚ะพั€ะฝะฐั ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธั (MFA)", + "FORCEMFA": "ะŸั€ะธะฝัƒะดะธั‚ะตะปัŒะฝะฐั ะผะฝะพะณะพั„ะฐะบั‚ะพั€ะฝะฐั ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธั ะดะปั ะฒัะตั… ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะน", + "FORCEMFALOCALONLY": "ะŸั€ะธะฝัƒะดะธั‚ะตะปัŒะฝะฐั ะผะฝะพะณะพั„ะฐะบั‚ะพั€ะฝะฐั ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธั ั‚ะพะปัŒะบะพ ะดะปั ะปะพะบะฐะปัŒะฝะพ ะฐัƒั‚ะตะฝั‚ะธั„ะธั†ะธั€ะพะฒะฐะฝะฝั‹ั… ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะน", "FORCEMFA_DESC": "ะ•ัะปะธ ะดะฐะฝะฝั‹ะน ะฟะฐั€ะฐะผะตั‚ั€ ะฒั‹ะฑั€ะฐะฝ, ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะธ ะดะพะปะถะฝั‹ ะฝะฐัั‚ั€ะพะธั‚ัŒ ะดะฒัƒั…ั„ะฐะบั‚ะพั€ะฝัƒัŽ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธัŽ ะดะปั ะฒั…ะพะดะฐ ะฒ ัะธัั‚ะตะผัƒ.", "HIDEPASSWORDRESET": "ะกะบั€ั‹ั‚ัŒ ัะฑั€ะพั ะฟะฐั€ะพะปั", "HIDEPASSWORDRESET_DESC": "ะ•ัะปะธ ะดะฐะฝะฝั‹ะน ะฟะฐั€ะฐะผะตั‚ั€ ะฒั‹ะฑั€ะฐะฝ, ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ะผะพะถะตั‚ ัะฑั€ะพัะธั‚ัŒ ัะฒะพะน ะฟะฐั€ะพะปัŒ ะฒ ะฟั€ะพั†ะตััะต ะฒั…ะพะดะฐ ะฒ ัะธัั‚ะตะผัƒ.", @@ -2670,7 +2673,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "MEMBER": { "ADD": "ะ”ะพะฑะฐะฒะธั‚ัŒ ะผะตะฝะตะดะถะตั€ะฐ", diff --git a/console/src/assets/i18n/sv.json b/console/src/assets/i18n/sv.json index d56384e419..93ed8ac72b 100644 --- a/console/src/assets/i18n/sv.json +++ b/console/src/assets/i18n/sv.json @@ -1388,7 +1388,8 @@ "nl": "Nederlands", "sv": "Svenska", "id": "Bahasa Indonesia", - "hu": "Magyar" + "hu": "Magyar", + "ko": "ํ•œ๊ตญ์–ด" } }, "SMTP": { @@ -1625,7 +1626,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "KEYS": { "emailVerificationDoneText": "E-postverifiering klar", @@ -1725,8 +1727,8 @@ "ALLOWUSERNAMEPASSWORD_DESC": "Den konventionella inloggningen med anvรคndarnamn och lรถsenord รคr tillรฅten.", "ALLOWEXTERNALIDP_DESC": "Inloggning รคr tillรฅten fรถr de underliggande identitetsleverantรถrerna", "ALLOWREGISTER_DESC": "Om alternativet รคr valt visas ett ytterligare steg fรถr att registrera en anvรคndare i inloggningen.", - "FORCEMFA": "Tvinga MFA", - "FORCEMFALOCALONLY": "Tvinga MFA fรถr lokalt autentiserade anvรคndare", + "FORCEMFA": "Tvinga MFA fรถr alla anvรคndare", + "FORCEMFALOCALONLY": "Tvinga MFA endast fรถr lokalt autentiserade anvรคndare", "FORCEMFALOCALONLY_DESC": "Om alternativet รคr valt mรฅste lokalt autentiserade anvรคndare konfigurera en andra faktor fรถr inloggning.", "HIDEPASSWORDRESET_DESC": "Om alternativet รคr valt kan anvรคndaren inte รฅterstรคlla sitt lรถsenord i inloggningsprocessen.", "HIDELOGINNAMESUFFIX": "Dรถlj inloggningsnamn suffix", @@ -2592,7 +2594,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "MEMBER": { "ADD": "Lรคgg till en administratรถr", diff --git a/console/src/assets/i18n/zh.json b/console/src/assets/i18n/zh.json index bb602d4e65..4f1b1d1d46 100644 --- a/console/src/assets/i18n/zh.json +++ b/console/src/assets/i18n/zh.json @@ -1384,7 +1384,8 @@ "nl": "Nederlands", "sv": "Svenska", "id": "Bahasa Indonesia", - "hu": "Magyar" + "hu": "Magyar", + "ko": "ํ•œ๊ตญ์–ด" } }, "SMTP": { @@ -1620,7 +1621,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "KEYS": { "emailVerificationDoneText": "็”ตๅญ้‚ฎไปถ้ชŒ่ฏๅฎŒๆˆ", @@ -1720,8 +1722,8 @@ "ALLOWUSERNAMEPASSWORD_DESC": "ๅ…่ฎธไฝฟ็”จ็”จๆˆทๅๅ’Œๅฏ†็ ่ฟ›่กŒ็™ปๅฝ•ใ€‚", "ALLOWEXTERNALIDP_DESC": "ๅ…่ฎธๅค–้ƒจ่บซไปฝๆไพ›่€…่ฟ›่กŒ็™ปๅฝ•", "ALLOWREGISTER_DESC": "ๅฆ‚ๆžœ้€‰ๆ‹ฉไบ†่ฏฅ้€‰้กน๏ผŒ็™ปๅฝ•ไธญไผšๅ‡บ็Žฐไธ€ไธช็”จไบŽๆณจๅ†Œ็”จๆˆท็š„้™„ๅŠ ๆญฅ้ชคใ€‚", - "FORCEMFA": "ๅผบๅˆถไฝฟ็”จ MFA", - "FORCEMFALOCALONLY": "ๅฏนๆœฌๅœฐ็”จๆˆทๅผบๅˆถๆ‰ง่กŒ MFA", + "FORCEMFA": "ๅผบๅˆถๆ‰€ๆœ‰็”จๆˆทไฝฟ็”จ MFA", + "FORCEMFALOCALONLY": "ไป…ๅผบๅˆถๆœฌๅœฐ่ฎค่ฏ็”จๆˆทไฝฟ็”จ MFA", "FORCEMFALOCALONLY_DESC": "ๅฆ‚ๆžœ้€‰ๆ‹ฉ่ฏฅ้€‰้กน๏ผŒๆœฌๅœฐ็ป่ฟ‡่บซไปฝ้ชŒ่ฏ็š„็”จๆˆทๅฟ…้กป้…็ฝฎ็ฌฌไบŒไธช็™ปๅฝ•ๅ› ็ด ใ€‚", "HIDEPASSWORDRESET_DESC": "ๅฆ‚ๆžœ้€‰ๆ‹ฉ่ฏฅ้€‰้กน๏ผŒๅˆ™็”จๆˆทๆ— ๆณ•ๅœจ็™ปๅฝ•่ฟ‡็จ‹ไธญ้‡็ฝฎๅ…ถๅฏ†็ ใ€‚", "HIDELOGINNAMESUFFIX": "้š่—็™ปๅฝ•ๅๅŽ็ผ€", @@ -2563,7 +2565,8 @@ "ru": "ะ ัƒััะบะธะน", "nl": "Nederlands", "sv": "Svenska", - "id": "Bahasa Indonesia" + "id": "Bahasa Indonesia", + "ko": "ํ•œ๊ตญ์–ด" }, "MEMBER": { "ADD": "ๆทปๅŠ ็ฎก็†่€…", diff --git a/console/yarn.lock b/console/yarn.lock index a820c97d73..c54f09c395 100644 --- a/console/yarn.lock +++ b/console/yarn.lock @@ -4537,9 +4537,9 @@ critters@0.0.20: pretty-bytes "^5.3.0" cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" @@ -6042,9 +6042,9 @@ http-proxy-agent@^5.0.0: debug "4" http-proxy-middleware@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" - integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== + version "2.0.7" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz#915f236d92ae98ef48278a95dedf17e991936ec6" + integrity sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA== dependencies: "@types/http-proxy" "^1.17.8" http-proxy "^1.18.1" diff --git a/docs/docs/apis/actions/v3/testing-locally.md b/docs/docs/apis/actions/v3/testing-locally.md index 7662c2bfe0..b5b3cb389f 100644 --- a/docs/docs/apis/actions/v3/testing-locally.md +++ b/docs/docs/apis/actions/v3/testing-locally.md @@ -48,6 +48,20 @@ func main() { What happens here is only a target which prints out the received request, which could also be handled with a different logic. +### Check Signature + +To additionally check the signature header you can add the following to the example: +```go + // validate signature + if err := actions.ValidatePayload(sentBody, req.Header.Get(actions.SigningHeader), signingKey); err != nil { + // if the signed content is not equal the sent content return an error + http.Error(w, "error", http.StatusInternalServerError) + return + } +``` + +Where you can replace 'signingKey' with the key received in the next step 'Create target'. + ## Create target As you see in the example above the target is created with HTTP and port '8090' and if we want to use it as webhook, the target can be created as follows: diff --git a/docs/docs/apis/actions/v3/usage.md b/docs/docs/apis/actions/v3/usage.md index 686c9d5445..2e89f3ce36 100644 --- a/docs/docs/apis/actions/v3/usage.md +++ b/docs/docs/apis/actions/v3/usage.md @@ -64,6 +64,13 @@ There are different types of Targets: The API documentation to create a target can be found [here](/apis/resources/action_service_v3/zitadel-actions-create-target) +### Content Signing + +To ensure the integrity of request content, each call includes a 'ZITADEL-Signature' in the headers. This header contains an HMAC value computed from the request content and a timestamp, which can be used to time out requests. The logic for this process is provided in 'pkg/actions/signing.go'. The goal is to verify that the HMAC value in the header matches the HMAC value computed by the Target, ensuring that the sent and received requests are identical. + +Each Target resource now contains also a Signing Key, which gets generated and returned when a Target is [created](/apis/resources/action_service_v3/zitadel-actions-create-target), +and can also be newly generated when a Target is [patched](/apis/resources/action_service_v3/zitadel-actions-patch-target). + ## Execution ZITADEL decides on specific conditions if one or more Targets have to be called. diff --git a/docs/docs/apis/benchmarks/_template.mdx b/docs/docs/apis/benchmarks/_template.mdx new file mode 100644 index 0000000000..a6a9109309 --- /dev/null +++ b/docs/docs/apis/benchmarks/_template.mdx @@ -0,0 +1,77 @@ + + +## Summary + +TODO: describe the outcome of the test? + +## Performance test results + +| Metric | Value | +|-:-------------------------------------|-:-----| +| Baseline | none | +| Purpose | | +| Test start | UTC | +| Test duration | 30min | +| Executed test | | +| k6 version | | +| VUs | | +| Client location | | +| Client machine specification | vCPU:
memory: Gb | +| ZITADEL location | | +| ZITADEL container specification | vCPU:
Memory: Gb
Container count: | +| ZITADEL Version | | +| ZITADEL Configuration | | +| ZITADEL feature flags | | +| Database | type: crdb / psql
version: | +| Database location | | +| Database specification | vCPU:
memory: Gb | +| ZITADEL metrics during test | | +| Observed errors | | +| Top 3 most expensive database queries | | +| Database metrics during test | | +| k6 Iterations per second | | +| k6 overview | | +| k6 output | | +| flowchart outcome | | + + +## Endpoint latencies + +import OutputSource from "!!raw-loader!./output.json"; + +import { BenchmarkChart } from '/src/components/benchmark_chart'; + + + +## k6 output {#k6-output} + +```bash +TODO: add summary of k6 +``` + diff --git a/docs/docs/apis/benchmarks/index.mdx b/docs/docs/apis/benchmarks/index.mdx new file mode 100644 index 0000000000..e5d89dbae8 --- /dev/null +++ b/docs/docs/apis/benchmarks/index.mdx @@ -0,0 +1,111 @@ +--- +title: Benchmarks +sidebar_label: Benchmarks +--- + +import DocCardList from '@theme/DocCardList'; + +Benchmarks are crucial to understand if ZITADEL fulfills your expected workload and what resources it needs to do so. + +This document explains the process and goals of load-testing zitadel in a cloud environment. + +The results can be found on sub pages. + +## Goals + +The primary goal is to assess if ZITADEL can scale to required proportion. The goals might change over time and maturity of ZITADEL. At the moment the goal is to assess how the applicationโ€™s performance scales. There are some concrete goals we have to meet: + +1. [https://github.com/zitadel/zitadel/issues/8352](https://github.com/zitadel/zitadel/issues/8352) defines 1000 JWT profile auth/sec +2. [https://github.com/zitadel/zitadel/issues/4424](https://github.com/zitadel/zitadel/issues/4424) defines 1200 logins / sec. + +## Procedure + +First we determine the โ€œtargetโ€ of our load-test. The target is expressed as a make recipe in the load-test [Makefile](https://github.com/zitadel/zitadel/blob/main/load-test/Makefile). See also the load-test [readme](https://github.com/zitadel/zitadel/blob/main/load-test/README.md) on how to configure and run load-tests. +A target should be tested for longer periods of time, as it might take time for certain metrics to show up. For example, cloud SQL samples query insights. A runtime of at least **30 minutes** is advised at the moment. + +After each iteration of load-test, we should consult the [After test procedure](#after-test-procedure) to conclude an outcome: + +1. Scale +2. Log potential issuer and scale +3. Terminate testing and resolve issues + + +## Methodology + +### Benchmark definition + +Tests are implemented in the ecosystem of [k6](https://k6.io). The tests are publicly available in the [zitadel repository](https://github.com/zitadel/zitadel/tree/main/load-test). Custom extensions of k6 are implemented in the [xk6-modules repository](https://github.com/zitadel/xk6-modules). +The tests must at least measure the request duration for each API call. This gives an indication on how zitadel behaves over the duration of the load test. + +### Metrics + +The following metrics must be collected for each test iteration. The metrics are used to follow the decision path of the [After test procedure](https://drive.google.com/open?id=1WVr7aA8dGgV1zd2jUg1y1h_o37mkZF2O6M5Mhafn_NM): + +| Metric | Type | Description | Unit | +| :---- | :---- | :---- | :---- | +| Baseline | Comparison | Defines the baseline the test is compared against. If not specified the baseline defined in this document is used. | Link to test result | +| Purpose | Description | Description what should been proved with this test run | text +| Test start | Setup | Timestamp when the test started. This is useful for gathering additional data like metrics or logs later | Date | +| Test duration | Setup | Duration of the test | Duration | +| Executed test | Setup | Name of the make recipe executed. Further information about specific test cases can be found [here](?tab=t.0#heading=h.xav4f3s5r2f3). | Name of the make recipe | +| k6 version | Setup | Version of the test client (k6) used | semantic version | +| VUs | Setup | Virtual Users which execute the test scenario in parallel | Number | +| Client location | Setup | Region or location of the machine which executed the test client. If not further specified the hoster is Google Cloud | Location / Region | +| Client machine specification | Setup | Definition of the client machine the test client ran on. The resources of the machine could be maxed out during tests therefore we collect this metric as well. The description must at least clarify the following metrics: vCPU Memory egress bandwidth | **vCPU**: Amount of threads ([additional info](https://cloud.google.com/compute/docs/cpu-platforms)) **memory**: GB **egress bandwidth**:Gbps | +| ZITADEL location | Setup | Region or location of the deployment of zitadel. If not further specified the hoster is Google Cloud | Location / Region | +| ZITADEL container specification | Setup | As ZITADEL is mainly run in cloud environments it should also be run as a container during the load tests. The description must at least clarify the following metrics: vCPU Memory egress bandwidth Scale | **vCPU**: Amount of threads ([additional info](https://cloud.google.com/compute/docs/cpu-platforms)) **memory**: GB **egress bandwidth**:Gbps **scale**: The amount of containers running during the test. The amount must not vary during the tests | +| ZITADEL Version | Setup | The version of zitadel deployed | Semantic version or commit | +| ZITADEL Configuration | Setup | Configuration of zitadel which deviates from the defaults and is not secret | yaml | +| ZITADEL feature flags | Setup | Changed feature flags | yaml | +| Database | Setup | Database type and version | **type**: crdb / psql **version**: semantic version | +| Database location | Setup | Region or location of the deployment of the database. If not further specified the hoster is Google Cloud SQL | Location / Region | +| Database specification | Setup | The description must at least clarify the following metrics: vCPU, Memory and egress bandwidth (Scale) | **vCPU**: Amount of threads ([additional info](https://cloud.google.com/compute/docs/cpu-platforms)) **memory**: GB **egress bandwidth**:Gbps **scale**: Amount of crdb nodes if crdb is used | +| ZITADEL metrics during test | Result | This metric helps understanding the bottlenecks of the executed test. At least the following metrics must be provided: CPU usage Memory usage | **CPU usage** in percent **Memory usage** in percent | +| Observed errors | Result | Errors worth mentioning, mostly unexpected errors | description | +| Top 3 most expensive database queries | Result | The execution plan of the top 3 most expensive database queries during the test execution | database execution plan | +| Database metrics during test | Result | This metric helps understanding the bottlenecks of the executed test. At least the following metrics must be provided: CPU usage Memory usage | **CPU usage** in percent **Memory usage** in percent | +| k6 Iterations per second | Result | How many test iterations were done per second | Number | +| k6 overview | Result | Shows some basic metrics aggregated over the test run At least the following metrics must be included: duration per request (min, max, avg, p50, p95, p99) VUS For simplicity just add the whole test result printed to the terminal | terminal output | +| k6 output | Result | Trends and metrics generated during the test, this contains detailed information for each step executed during each iteration | csv | + +### Test setup + +#### Make recipes + +Details about the tests implemented can be found in [this readme](https://github.com/zitadel/zitadel/blob/main/load-test/README.md#test). + +### Test conclusion + +After each iteration of load-test, we should consult the [Flowchart](#after-test-procedure) to conclude an outcome: + +1. [Scale](#scale) +2. [Log potential issue and scale](#potential-issues) +3. [Terminate testing](#termination) and resolve issues + +#### Scale {#scale} + +An outcome of scale means that the service hit some kind of resource limit, like CPU or RAM which can be increased. In such cases we increase the suggested parameter and rerun the load-test for the same target. On the next test we should analyse if the increase in scale resulted in a performance improvement proportional to the scale parameter. For example if we scale from 1 to 2 containers, it might be reasonable to expect a doubling of iterations / sec. If such an increase is not noticed, there might be another bottleneck or unlying issue, such as locking. + +#### Potential issues {#potential-issues} + +A potential issue has an impact on performance, but does not prevent us to scale. Such issues must be logged in GH issues and load-testing can continue. The issue can be resolved at a later time and the load-tests repeated when it is. This is primarily for issues which require big changes to ZITADEL. + +#### Termination {#termination} + +Scaling no longer improves iterations / second, or some kind of critical error or bug is experienced. The root cause of the issue must be resolved before we can continue with increasing scale. + +### After test procedure + +This flowchart shows the procedure after running a test. + +![Flowchart](/img/benchmark/Flowchart.svg) + +## Baseline + +Will be established as soon as the goal described above is reached. + +## Test results + +This chapter provides a table linking to the detailed test results. + + diff --git a/docs/docs/apis/benchmarks/v2.65.0/machine_jwt_profile_grant/index.mdx b/docs/docs/apis/benchmarks/v2.65.0/machine_jwt_profile_grant/index.mdx new file mode 100644 index 0000000000..f2e198c510 --- /dev/null +++ b/docs/docs/apis/benchmarks/v2.65.0/machine_jwt_profile_grant/index.mdx @@ -0,0 +1,75 @@ +--- +title: machine jwt profile grant benchmark of zitadel v2.65.0 +sidebar_label: machine jwt profile grant +--- + +## Summary + +Tests are halted after this test run because of too many [client read events](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/wait-event.clientread.html) on the database. + +## Performance test results + +| Metric | Value | +| :---- | :---- | +| Baseline | none | +| Test start | 22-10-2024 16:20 UTC | +| Test duration | 30min | +| Executed test | machine\_jwt\_profile\_grant | +| k6 version | v0.54.0 | +| VUs | 50 | +| Client location | US1 | +| Client machine specification | e2-high-cpu-4 | +| Zitadel location | US1 | +| Zitadel container specification | vCPUs: 2
Memory: 512 MiB
Container count: 2 | +| Zitadel feature flags | none | +| Database | postgres v15 | +| Database location | US1 | +| Database specification | vCPUs: 4
Memory: 16 GiB | +| Zitadel metrics during test | | +| Observed errors | Many client read events during push | +| Top 3 most expensive database queries | 1: Query events `instance_id = $1 AND aggregate_type = $2 AND aggregate_id = $3 AND event_type = ANY($4)`
2: latest sequence query during push events
3: writing events during push (caused lock wait events) | +| k6 iterations per second | 193 | +| k6 overview | [output](#k6-output) | +| flowchart outcome | Halt tests, must resolve an issue | + +## /token endpoint latencies + +import OutputSource from "!!raw-loader!./output.json"; + +import { BenchmarkChart } from '/src/components/benchmark_chart'; + + + +## k6 output {#k6-output} + +```bash +checks...............................: 100.00% โœ“ 695739 โœ— 0 +data_received........................: 479 MB 265 kB/s +data_sent............................: 276 MB 153 kB/s +http_req_blocked.....................: min=178ns avg=5ยตs max=119.8ms p(50)=460ns p(95)=702ns p(99)=921ns +http_req_connecting..................: min=0s avg=1.24ยตs max=43.45ms p(50)=0s p(95)=0s p(99)=0s +http_req_duration....................: min=18ms avg=255.3ms max=1.22s p(50)=241.56ms p(95)=479.19ms p(99)=600.92ms +{ expected_response:true }.........: min=18ms avg=255.3ms max=1.22s p(50)=241.56ms p(95)=479.19ms p(99)=600.92ms +http_req_failed......................: 0.00% โœ“ 0 โœ— 347998 +http_req_receiving...................: min=25.92ยตs avg=536.96ยตs max=401.94ms p(50)=89.44ยตs p(95)=2.39ms p(99)=11.12ms +http_req_sending.....................: min=24.01ยตs avg=63.86ยตs max=4.48ms p(50)=60.97ยตs p(95)=88.69ยตs p(99)=141.74ยตs +http_req_tls_handshaking.............: min=0s avg=2.8ยตs max=51.05ms p(50)=0s p(95)=0s p(99)=0s +http_req_waiting.....................: min=17.65ms avg=254.7ms max=1.22s p(50)=240.88ms p(95)=478.6ms p(99)=600.6ms +http_reqs............................: 347998 192.80552/s +iteration_duration...................: min=33.86ms avg=258.77ms max=1.22s p(50)=245ms p(95)=482.61ms p(99)=604.32ms +iterations...........................: 347788 192.689171/s +login_ui_enter_login_name_duration...: min=218.61ms avg=218.61ms max=218.61ms p(50)=218.61ms p(95)=218.61ms p(99)=218.61ms +login_ui_enter_password_duration.....: min=18ms avg=18ms max=18ms p(50)=18ms p(95)=18ms p(99)=18ms +login_ui_init_login_duration.........: min=90.96ms avg=90.96ms max=90.96ms p(50)=90.96ms p(95)=90.96ms p(99)=90.96ms +login_ui_token_duration..............: min=140.02ms avg=140.02ms max=140.02ms p(50)=140.02ms p(95)=140.02ms p(99)=140.02ms +oidc_token_duration..................: min=29.85ms avg=255.38ms max=1.22s p(50)=241.61ms p(95)=479.23ms p(99)=600.95ms +org_create_org_duration..............: min=64.51ms avg=64.51ms max=64.51ms p(50)=64.51ms p(95)=64.51ms p(99)=64.51ms +user_add_machine_key_duration........: min=44.93ms avg=87.89ms max=159.52ms p(50)=84.43ms p(95)=144.59ms p(99)=155.54ms +user_create_machine_duration.........: min=65.75ms avg=266.53ms max=421.58ms p(50)=276.59ms p(95)=380.84ms p(99)=414.43ms +vus..................................: 0 min=0 max=50 +vus_max..............................: 50 min=50 max=50 + +running (30m04.9s), 00/50 VUs, 347788 complete and 0 interrupted iterations +default โœ“ [======================================] 50 VUs 30m0s +``` + diff --git a/docs/docs/apis/benchmarks/v2.65.0/machine_jwt_profile_grant/output.json b/docs/docs/apis/benchmarks/v2.65.0/machine_jwt_profile_grant/output.json new file mode 100644 index 0000000000..c2316eae29 --- /dev/null +++ b/docs/docs/apis/benchmarks/v2.65.0/machine_jwt_profile_grant/output.json @@ -0,0 +1,1803 @@ +[ + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:35+02","p50":152.742548,"p95":189.4438704479804,"p99":214.3874318037758}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:36+02","p50":134.31271792,"p95":176.85990823356335,"p99":189.17410793315742}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:37+02","p50":149.12034540000002,"p95":234.05779399949907,"p99":246.90676967010307}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:38+02","p50":141.79488025,"p95":298.87885997760134,"p99":314.37352053326987}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:39+02","p50":183.23340781250002,"p95":283.2726692992518,"p99":292.49417304653673}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:40+02","p50":213.86190366666668,"p95":284.20557151433445,"p99":390.4803307008381}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:41+02","p50":78.26700088888889,"p95":445.6252991799176,"p99":464.8756637391315}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:42+02","p50":93.90013371875,"p95":393.90886492717925,"p99":404.07198386581877}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:43+02","p50":99.26241149999998,"p95":456.46543169375514,"p99":482.1890742862892}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:44+02","p50":259.33973561111117,"p95":387.0035960871835,"p99":402.3678526092014}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:45+02","p50":163.7966285,"p95":280.9855007966218,"p99":297.6403551814313}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:46+02","p50":74.6244951875,"p95":394.0813611938515,"p99":412.85163931081155}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:47+02","p50":180.3818921111111,"p95":391.04031958131907,"p99":409.423791903923}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:48+02","p50":196.83148822222222,"p95":390.84742817174293,"p99":422.014462269928}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:49+02","p50":192.46919175,"p95":261.7432952727178,"p99":297.98025934050463}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:50+02","p50":163.3955145,"p95":373.8762231012586,"p99":402.81260525012493}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:51+02","p50":169.287491,"p95":252.19139975148394,"p99":260.0374195735345}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:52+02","p50":172.3803739375,"p95":254.90392808387185,"p99":298.5229499636707}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:53+02","p50":159.63037021875002,"p95":325.96280276943094,"p99":339.4366488568575}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:54+02","p50":151.7369948125,"p95":232.77033866258023,"p99":245.4879663243437}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:55+02","p50":146.12840375000002,"p95":276.01754821507706,"p99":286.9390759050336}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:56+02","p50":166.82458387499997,"p95":244.8316436244864,"p99":255.96774487884497}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:57+02","p50":171.2386103125,"p95":231.91869474922763,"p99":255.3471724571259}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:58+02","p50":132.43773015625,"p95":305.5849403249092,"p99":317.7783982236719}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:20:59+02","p50":98.29408981249999,"p95":345.2765417731901,"p99":359.39837518970154}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:00+02","p50":174.8437813125,"p95":279.72543384122116,"p99":295.9230939623306}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:01+02","p50":156.1206249375,"p95":257.0086383694033,"p99":290.1174500658493}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:02+02","p50":125.861364375,"p95":335.8766849864509,"p99":344.69110596878573}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:03+02","p50":116.19827,"p95":289.9331628960681,"p99":308.2670122635899}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:04+02","p50":88.807123,"p95":309.88822512970404,"p99":329.24833508430766}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:05+02","p50":125.42367399999999,"p95":313.7408337804247,"p99":330.610476359817}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:06+02","p50":148.7421011875,"p95":288.69967697102777,"p99":307.14991885160015}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:07+02","p50":123.452076,"p95":324.4732774677882,"p99":338.0723913875208}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:08+02","p50":94.517341,"p95":310.1070211770424,"p99":322.64246295973874}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:09+02","p50":199.0995741875,"p95":266.4028019143426,"p99":283.33593921839906}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:10+02","p50":159.412726375,"p95":231.16968194039453,"p99":236.0824995757258}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:11+02","p50":162.84928418750002,"p95":252.365319447649,"p99":276.632285485939}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:12+02","p50":145.54211509375,"p95":264.97463031152597,"p99":277.07558885515954}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:13+02","p50":137.4853945625,"p95":383.14112161791684,"p99":401.6204909024353}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:14+02","p50":110.63827400000001,"p95":324.8113530617447,"p99":348.8752330086517}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:15+02","p50":180.9934134375,"p95":294.84876206776056,"p99":304.4406698862913}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:16+02","p50":211.72708266666666,"p95":253.80525846297198,"p99":269.59085696040177}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:17+02","p50":209.98856577777778,"p95":253.06196131151407,"p99":264.4179178528454}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:18+02","p50":156.35097040625,"p95":293.5385406069375,"p99":401.6490299732654}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:19+02","p50":133.221681875,"p95":399.4656152824501,"p99":418.854191442893}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:20+02","p50":118.86549344444445,"p95":405.91692504971184,"p99":417.90231864442757}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:21+02","p50":191.00690390625,"p95":305.106348396054,"p99":334.6109268334396}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:22+02","p50":131.788673875,"p95":318.6432634163847,"p99":340.73336806457513}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:23+02","p50":119.2033654375,"p95":278.4747170609107,"p99":290.79545231027504}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:24+02","p50":103.891219625,"p95":354.52017043143763,"p99":365.99442424978156}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:25+02","p50":110.90601099999999,"p95":409.53373378248904,"p99":439.4092664350481}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:26+02","p50":110.04638775000001,"p95":353.0635395443648,"p99":390.9164269659906}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:27+02","p50":139.166329,"p95":330.2447540706727,"p99":343.60334007957596}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:28+02","p50":173.25523311111112,"p95":353.7907797741324,"p99":362.92556865444755}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:29+02","p50":268.14744616666667,"p95":350.00666472811616,"p99":364.6646985822191}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:30+02","p50":215.313930875,"p95":273.1942162963142,"p99":288.4021928619313}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:31+02","p50":151.94144256250001,"p95":302.8211043623749,"p99":316.1713350709291}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:32+02","p50":204.56433033333334,"p95":350.7035384562023,"p99":367.32154349208736}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:33+02","p50":184.935939625,"p95":244.05362925004005,"p99":265.5505863892994}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:34+02","p50":181.39206394444446,"p95":263.64057324885795,"p99":279.00201451316264}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:35+02","p50":205.52729209375002,"p95":266.2276509014458,"p99":275.20543244661326}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:36+02","p50":184.00616190625001,"p95":337.11831113507805,"p99":366.7406586168554}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:37+02","p50":175.3103945,"p95":395.91543717141724,"p99":416.1321127693481}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:38+02","p50":170.106749,"p95":233.25833817163755,"p99":255.1431567000685}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:39+02","p50":175.60114431250003,"p95":251.45913770202392,"p99":271.3119037283981}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:40+02","p50":177.2062990625,"p95":294.1022916899325,"p99":307.97861875463434}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:41+02","p50":177.1961231875,"p95":320.58236328688787,"p99":336.7532011867485}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:42+02","p50":162.4275845,"p95":361.9094602270508,"p99":385.3591560391846}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:43+02","p50":189.32301237500002,"p95":356.5451459530969,"p99":376.63635141138747}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:44+02","p50":145.7136858125,"p95":344.11261977926944,"p99":359.2462192147102}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:45+02","p50":172.38470781249998,"p95":353.6295055632823,"p99":372.84916331113646}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:46+02","p50":206.59438666666668,"p95":441.1500573161624,"p99":474.9812698063977}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:47+02","p50":172.873287375,"p95":320.8506584619575,"p99":358.5100670831585}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:48+02","p50":169.6534998125,"p95":303.4536858748769,"p99":330.31731258393904}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:49+02","p50":173.02729125000002,"p95":298.78449271407396,"p99":339.62662085937404}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:50+02","p50":163.46804384375002,"p95":296.1723000890368,"p99":315.01419666908794}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:51+02","p50":171.338443,"p95":248.84376248058607,"p99":259.60469639209316}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:52+02","p50":163.3185848125,"p95":270.9658209938422,"p99":290.1653841856108}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:53+02","p50":147.25774171875003,"p95":263.3940735966012,"p99":284.50598333436756}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:54+02","p50":176.44091971875,"p95":266.5990478841503,"p99":300.03993930488224}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:55+02","p50":142.82466225,"p95":289.0258727321505,"p99":300.2080874461651}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:56+02","p50":175.36170518749998,"p95":330.08911763715554,"p99":375.1193799273758}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:57+02","p50":162.222671625,"p95":281.79289915946663,"p99":294.4279530910194}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:58+02","p50":143.71196425,"p95":362.0538523995184,"p99":381.575093799727}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:21:59+02","p50":149.01801776000002,"p95":195.78059584427396,"p99":209.09926001296807}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:00+02","p50":168.380417625,"p95":267.1608095025909,"p99":282.49195416929626}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:01+02","p50":128.7791685,"p95":262.1669432662675,"p99":270.3051170505457}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:02+02","p50":127.9593894375,"p95":252.83218249204972,"p99":274.0372604026485}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:03+02","p50":127.1617695,"p95":267.06822044613585,"p99":283.4020022676983}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:04+02","p50":106.68187865625,"p95":307.734150013091,"p99":316.84643377242423}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:05+02","p50":179.382695,"p95":249.17453328077286,"p99":266.2734021554391}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:06+02","p50":172.73768656250002,"p95":228.5491930327513,"p99":264.98035601136826}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:07+02","p50":128.65640771875002,"p95":337.6269146065091,"p99":371.7743257245612}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:08+02","p50":100.0326530625,"p95":303.43871650302395,"p99":334.7614539394679}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:09+02","p50":119.12006821875,"p95":335.46215280524103,"p99":350.0083065727637}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:10+02","p50":125.806114,"p95":288.15592741759167,"p99":301.6352730747447}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:11+02","p50":133.42285409375,"p95":316.6540532220981,"p99":329.05199164620757}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:12+02","p50":95.71434059375,"p95":308.4402244789036,"p99":320.67318234980775}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:13+02","p50":155.3043468125,"p95":394.8728883021291,"p99":414.4798793780084}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:14+02","p50":164.24609787499998,"p95":295.26223958864114,"p99":312.8365331274672}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:15+02","p50":140.62968171875002,"p95":297.0755794801937,"p99":311.255825160466}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:16+02","p50":167.9410644375,"p95":338.7029358086513,"p99":354.43729884373784}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:17+02","p50":155.62452675,"p95":282.73366318783974,"p99":320.7858698425536}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:18+02","p50":162.254539375,"p95":324.6161681515989,"p99":340.7320104777489}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:19+02","p50":145.28746665625,"p95":309.1353151952988,"p99":321.34194833209347}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:20+02","p50":155.42283681249998,"p95":227.0559183476073,"p99":285.1077931350944}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:21+02","p50":147.71090025,"p95":330.71194199860406,"p99":343.6829263263979}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:22+02","p50":161.43343950000002,"p95":224.56334413866048,"p99":235.36415136311962}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:23+02","p50":179.38427725,"p95":249.76000027644014,"p99":260.70670063443185}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:24+02","p50":225.33285412500004,"p95":281.72253456857135,"p99":299.9099734186788}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:25+02","p50":166.78473862500002,"p95":232.1957648318806,"p99":299.35293979125305}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:26+02","p50":172.81051031249999,"p95":338.5984408025247,"p99":365.33059938493534}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:27+02","p50":180.41219675,"p95":254.84395884091364,"p99":280.9361037687831}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:28+02","p50":170.17445437499998,"p95":244.8935166378977,"p99":282.9037624571953}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:29+02","p50":196.90349796875,"p95":264.5450147089112,"p99":284.2996830777185}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:30+02","p50":149.61717309375,"p95":258.4944096041763,"p99":280.0767955062025}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:31+02","p50":115.37371856249999,"p95":342.0609913932303,"p99":369.31108017582085}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:32+02","p50":198.97898015625,"p95":275.73095247318406,"p99":330.507157946228}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:33+02","p50":147.3347025,"p95":324.7720196622293,"p99":338.60460008617355}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:34+02","p50":118.407053,"p95":281.2860384632016,"p99":326.8983674016011}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:35+02","p50":152.8172223125,"p95":271.18395431658706,"p99":314.1567188235145}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:36+02","p50":133.17060325,"p95":331.59060301692466,"p99":343.9984966597519}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:37+02","p50":139.62863121875,"p95":284.8950458756842,"p99":294.2825206762035}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:38+02","p50":180.022578625,"p95":258.0588529323397,"p99":289.3623059911065}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:39+02","p50":157.79121866666665,"p95":299.27330007239976,"p99":317.4350921771674}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:40+02","p50":208.92391650000002,"p95":344.031906960927,"p99":363.73983160268403}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:41+02","p50":202.68174344444444,"p95":305.52328825860576,"p99":337.10060789617324}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:42+02","p50":84.37220403124999,"p95":361.3133458600537,"p99":371.2432742504656}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:43+02","p50":231.49854371875,"p95":349.888320585701,"p99":384.09872475423884}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:44+02","p50":183.9890441875,"p95":271.10930583288854,"p99":298.76474763219386}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:45+02","p50":182.8464445,"p95":246.2624996970563,"p99":271.49484056651687}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:46+02","p50":93.68166966666666,"p95":463.97366856742735,"p99":481.7509814693787}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:47+02","p50":115.90802022222222,"p95":457.24317004265833,"p99":481.81383054922867}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:48+02","p50":127.41821911111111,"p95":444.18740387771396,"p99":455.7524692107775}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:49+02","p50":139.04014443749998,"p95":294.4670809434862,"p99":305.7424495887248}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:50+02","p50":98.5424918125,"p95":347.32745104648734,"p99":363.6402334598913}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:51+02","p50":119.86771771875,"p95":261.4503544709988,"p99":271.10387682557536}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:52+02","p50":130.5921068125,"p95":335.50228157148223,"p99":345.1897153513317}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:53+02","p50":147.82731846875,"p95":305.32917860643096,"p99":327.81091039268375}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:54+02","p50":184.37881875,"p95":414.9498528464594,"p99":436.0652476570034}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:55+02","p50":134.9334113888889,"p95":394.22851781011155,"p99":423.1439984321022}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:56+02","p50":94.6194291875,"p95":369.1436656720502,"p99":429.596956807251}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:57+02","p50":103.10241853124998,"p95":358.68138499470405,"p99":369.60892119685246}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:58+02","p50":96.860279125,"p95":298.3693310251786,"p99":321.95436359483097}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:22:59+02","p50":126.16320309375,"p95":358.2172364309369,"p99":380.3571782561917}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:00+02","p50":163.9286190625,"p95":287.8749754242624,"p99":304.07535824718906}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:01+02","p50":110.296562,"p95":356.67980462961077,"p99":369.1336770410352}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:02+02","p50":121.2899105625,"p95":376.36945194076355,"p99":393.2726901395225}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:03+02","p50":118.94529322222222,"p95":364.647289347859,"p99":379.42877625152425}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:04+02","p50":168.66469700000002,"p95":335.5127914306146,"p99":348.81079129140187}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:05+02","p50":109.39908218750001,"p95":343.64168488864436,"p99":356.2030261326771}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:06+02","p50":90.71489159375001,"p95":395.67789852777827,"p99":414.08672484933857}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:07+02","p50":102.102573,"p95":479.7651860054539,"p99":509.84110983474443}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:08+02","p50":86.0721990625,"p95":506.7270854694948,"p99":533.6720435621214}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:09+02","p50":156.2318160625,"p95":342.99648317675934,"p99":372.053304515549}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:10+02","p50":188.14600168750002,"p95":306.32164278457384,"p99":363.1293493959436}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:11+02","p50":192.2799678888889,"p95":300.28397298561293,"p99":314.459901629174}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:12+02","p50":153.60912234375002,"p95":411.5785920044621,"p99":428.19369271948545}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:13+02","p50":111.96040766666665,"p95":435.55265119979356,"p99":452.5854287691793}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:14+02","p50":139.76533733333335,"p95":455.35024341441203,"p99":475.24830211597254}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:15+02","p50":198.77428865625,"p95":325.2470845571967,"p99":354.2798636108484}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:16+02","p50":127.02826,"p95":397.94563417990844,"p99":418.64960824809697}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:17+02","p50":106.55520544444444,"p95":452.29131266254143,"p99":470.03969402793314}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:18+02","p50":113.88863299999998,"p95":525.0521919118939,"p99":529.8129106062248}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:19+02","p50":111.85406478125,"p95":389.74024184425025,"p99":402.0315347526336}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:20+02","p50":175.7656145,"p95":347.27385680249597,"p99":358.86244784611324}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:21+02","p50":157.78282825,"p95":317.69338495123293,"p99":341.9171689902344}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:22+02","p50":131.46160666666665,"p95":369.19526349180256,"p99":387.8415062052717}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:23+02","p50":204.50269084374997,"p95":354.0385939407066,"p99":375.9711954611883}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:24+02","p50":169.301663,"p95":321.4563997478036,"p99":339.69673819850203}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:25+02","p50":263.8962338333334,"p95":347.43064110933443,"p99":372.9916210012765}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:26+02","p50":239.87338566666668,"p95":347.0042002027512,"p99":389.0801205401683}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:27+02","p50":187.87346983333336,"p95":378.2293020718398,"p99":392.0665997422705}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:28+02","p50":278.3228516111111,"p95":413.9842850428133,"p99":420.58175994584656}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:29+02","p50":230.29268366666668,"p95":388.3813619979001,"p99":414.3347986147597}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:30+02","p50":205.597269,"p95":342.4151837143591,"p99":372.75280997233176}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:31+02","p50":185.66734133333333,"p95":366.79302571517263,"p99":386.82752764575815}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:32+02","p50":113.17833633333333,"p95":382.2272483782675,"p99":399.6901822997966}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:33+02","p50":224.427364,"p95":318.78927568640904,"p99":379.3479040931482}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:34+02","p50":171.93366183333333,"p95":337.67122743761195,"p99":347.5229087776327}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:35+02","p50":160.31750171875,"p95":296.4358263359214,"p99":305.3764371393015}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:36+02","p50":190.399403125,"p95":329.76192083518026,"p99":358.4830148763714}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:37+02","p50":189.664540625,"p95":291.7815892821846,"p99":306.7206485249286}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:38+02","p50":112.06204344444444,"p95":369.52459361610045,"p99":409.1660215970068}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:39+02","p50":276.3946736111111,"p95":403.4417701618221,"p99":483.7071277961846}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:40+02","p50":214.50907466666663,"p95":368.17170445707075,"p99":387.5157735438919}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:41+02","p50":91.36098566666665,"p95":401.8118318129068,"p99":417.86146135524916}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:42+02","p50":237.6363456875,"p95":333.55795506752133,"p99":345.2556038274617}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:43+02","p50":208.2464379375,"p95":285.59666313684176,"p99":306.4541200015583}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:44+02","p50":108.37741083333333,"p95":381.99226537324716,"p99":399.8603670226402}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:45+02","p50":224.89928093749998,"p95":356.48579109293877,"p99":370.34997563481807}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:46+02","p50":187.40087555555556,"p95":365.9536206192869,"p99":395.0626114183414}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:47+02","p50":219.2488168333333,"p95":352.2197022321227,"p99":360.55958806999826}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:48+02","p50":128.14031988888888,"p95":370.29248196576475,"p99":381.02069397623825}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:49+02","p50":175.8623413125,"p95":328.54612543744935,"p99":351.48333055523966}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:50+02","p50":170.20777471875,"p95":248.07310912902372,"p99":259.2663498275757}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:51+02","p50":189.34810149999998,"p95":254.63217625896917,"p99":271.4447753241811}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:52+02","p50":155.339734625,"p95":247.59586218575285,"p99":297.08098081845475}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:53+02","p50":162.452478,"p95":235.18443832070545,"p99":251.61101743764354}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:54+02","p50":197.10812044444447,"p95":340.2054231095977,"p99":372.61731303294516}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:55+02","p50":148.3748315625,"p95":304.8272034887056,"p99":328.65983394512176}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:56+02","p50":112.314109625,"p95":371.49208539474535,"p99":389.60865558733843}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:57+02","p50":84.32444275,"p95":344.43789664394535,"p99":355.5426731411834}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:58+02","p50":125.430037875,"p95":363.9490895496088,"p99":386.1311214982054}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:23:59+02","p50":113.5846795,"p95":339.92753503626835,"p99":357.93413792771963}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:00+02","p50":112.61793766666666,"p95":352.45506782568015,"p99":357.97922475105383}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:01+02","p50":161.57308468749997,"p95":330.8013208029374,"p99":348.19938232203555}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:02+02","p50":88.23626255555557,"p95":411.7930310908556,"p99":436.56527862182617}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:03+02","p50":155.04360022222224,"p95":363.3401143622393,"p99":378.03465003131817}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:04+02","p50":134.3302317777778,"p95":442.9010524626141,"p99":476.7326678590927}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:05+02","p50":132.83108988888887,"p95":407.68930056825303,"p99":427.3419876943593}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:06+02","p50":235.7166078333333,"p95":427.09760607859795,"p99":450.61696512708517}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:07+02","p50":185.73770733333333,"p95":345.20340229425943,"p99":369.64291598097344}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:08+02","p50":127.86131288888889,"p95":346.50813979244816,"p99":381.3701055143173}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:09+02","p50":204.58561616666668,"p95":385.83124888707266,"p99":404.65335307042693}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:10+02","p50":134.93217372222225,"p95":335.8876011389651,"p99":344.50895744095425}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:11+02","p50":145.890436125,"p95":371.3015613146542,"p99":380.6078602409513}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:12+02","p50":137.35259468749996,"p95":320.9258905171859,"p99":341.68218627207045}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:13+02","p50":109.34771977777778,"p95":501.9964330002911,"p99":515.4457118845553}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:14+02","p50":100.66297100000001,"p95":463.74451614195823,"p99":513.6433440599309}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:15+02","p50":109.57291790625,"p95":371.5828618090343,"p99":388.3091798479202}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:16+02","p50":150.32008,"p95":468.928458806547,"p99":488.85305357821943}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:17+02","p50":124.15826366666666,"p95":427.9907656928685,"p99":449.04125851229384}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:18+02","p50":114.9532918888889,"p95":448.636051890614,"p99":485.4835679474907}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:19+02","p50":176.6278854375,"p95":323.2559806472752,"p99":347.9221029315009}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:20+02","p50":211.88934416666666,"p95":262.22510196252955,"p99":281.2612729744716}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:21+02","p50":180.81894740625,"p95":259.6539730155451,"p99":269.67856666175607}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:22+02","p50":196.672637,"p95":266.94895766008,"p99":298.00997561267087}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:23+02","p50":164.28795509375,"p95":272.20733088255565,"p99":286.64984232324144}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:24+02","p50":239.44598994444445,"p95":422.5159794253372,"p99":439.2937172332916}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:25+02","p50":187.291075,"p95":354.77484739822967,"p99":374.8504402552736}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:26+02","p50":194.9652128333333,"p95":275.41904993178474,"p99":288.84490828162814}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:27+02","p50":203.21512822222223,"p95":355.13161239707694,"p99":395.83519843569616}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:28+02","p50":262.27167633333335,"p95":334.4411825664316,"p99":350.01384678881715}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:29+02","p50":194.2414280625,"p95":304.2947880739841,"p99":343.91379995135236}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:30+02","p50":172.834217,"p95":286.87646965032957,"p99":336.19378734832765}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:31+02","p50":207.0231061875,"p95":302.47231828407706,"p99":316.54491024100827}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:32+02","p50":181.0753525,"p95":321.49112572849896,"p99":351.5424148507652}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:33+02","p50":153.663604,"p95":276.54249616299137,"p99":293.4143785937378}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:34+02","p50":160.192512,"p95":392.5024005158302,"p99":406.7674115495229}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:35+02","p50":115.11713325,"p95":374.7461023176923,"p99":403.76463397014857}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:36+02","p50":195.5520289375,"p95":330.24640575101205,"p99":378.25412397826955}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:37+02","p50":153.22983206249998,"p95":302.1021002301149,"p99":320.31823634316964}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:38+02","p50":190.33183956249997,"p95":264.897034113108,"p99":280.9633527328908}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:39+02","p50":161.7138188125,"p95":297.7743485606594,"p99":329.2594311006222}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:40+02","p50":198.20748,"p95":377.3354283850393,"p99":406.5434730048401}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:41+02","p50":139.463968625,"p95":266.8608783056278,"p99":289.8984755721698}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:42+02","p50":114.82625178125,"p95":349.68077743478915,"p99":361.65627729169654}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:43+02","p50":232.9297346875,"p95":309.02823247616396,"p99":327.71930822933507}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:44+02","p50":146.7611834375,"p95":314.5849987406578,"p99":368.70549214709615}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:45+02","p50":133.7292851875,"p95":297.54178871003234,"p99":310.04929223846005}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:46+02","p50":140.45567866666667,"p95":412.6909459159718,"p99":451.2378967052231}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:47+02","p50":195.74258483333332,"p95":391.1413024729508,"p99":426.36363475451185}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:48+02","p50":141.246246,"p95":379.41185379152716,"p99":406.632193284086}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:49+02","p50":162.00057353125,"p95":288.8276731247837,"p99":313.6953214802961}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:50+02","p50":150.4389035,"p95":353.7256544370002,"p99":383.2435627905927}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:51+02","p50":175.6269825,"p95":248.15324239547587,"p99":264.5156661502189}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:52+02","p50":159.8990134375,"p95":300.3999955336074,"p99":325.6686536526949}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:53+02","p50":162.34384893749998,"p95":297.086694576544,"p99":332.67165895619155}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:54+02","p50":146.514717375,"p95":265.59541198054677,"p99":281.9044932644291}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:55+02","p50":175.333063,"p95":334.77658803869053,"p99":373.9112342957733}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:56+02","p50":131.2167323125,"p95":288.0913442904415,"p99":310.9476716580658}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:57+02","p50":146.7495225625,"p95":297.6914536560938,"p99":339.77493364830207}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:58+02","p50":142.8694296875,"p95":285.2685503283086,"p99":313.73204227613735}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:24:59+02","p50":206.041962,"p95":277.91586266952214,"p99":301.9713539372003}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:00+02","p50":187.0775275625,"p95":301.2439236612787,"p99":318.2949744250431}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:01+02","p50":116.769160625,"p95":297.3274914935353,"p99":314.41500567552447}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:02+02","p50":121.01627225,"p95":302.45192923908996,"p99":323.91664271936037}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:03+02","p50":115.9357485,"p95":291.0901811554011,"p99":310.4947344401798}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:04+02","p50":128.8123610625,"p95":293.8868525166878,"p99":309.0480242172456}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:05+02","p50":160.981729375,"p95":307.9032117912121,"p99":320.6469388115158}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:06+02","p50":147.9102275,"p95":278.95437096888554,"p99":299.85087856600524}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:07+02","p50":177.696945125,"p95":255.77304940730096,"p99":274.12012098093413}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:08+02","p50":145.12405118750002,"p95":309.2880586250639,"p99":335.62102451202105}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:09+02","p50":188.25798759375,"p95":291.11708460701226,"p99":316.63812602788687}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:10+02","p50":167.07433734375,"p95":276.3570106623106,"p99":298.2238179203718}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:11+02","p50":157.453457625,"p95":249.26331657150982,"p99":261.95071847950743}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:12+02","p50":170.7052875,"p95":272.21180737058626,"p99":291.06299620788957}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:13+02","p50":166.31834390625,"p95":255.44433066577483,"p99":272.64818770659184}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:14+02","p50":179.1684726875,"p95":257.67500678455974,"p99":283.6161977368152}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:15+02","p50":190.28375031250002,"p95":244.14229401287508,"p99":265.4086650401878}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:16+02","p50":194.85328783333333,"p95":268.6921016668321,"p99":336.8306007708764}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:17+02","p50":217.8591368888889,"p95":270.79396199034244,"p99":293.95661484732244}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:18+02","p50":194.134548,"p95":285.16345218963994,"p99":309.2768642459817}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:19+02","p50":178.6079338125,"p95":268.24652942969857,"p99":300.60436891284706}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:20+02","p50":161.261008,"p95":276.3407417475745,"p99":293.17805059296325}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:21+02","p50":172.40981234375,"p95":233.20667978189297,"p99":247.1107588331442}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:22+02","p50":188.83555416666664,"p95":297.4500083579718,"p99":307.55552874326133}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:23+02","p50":170.39420646874999,"p95":211.03686184797803,"p99":224.53489826566695}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:24+02","p50":164.35997515625002,"p95":276.49485311147856,"p99":292.59989314810065}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:25+02","p50":143.64633949999998,"p95":272.80621859268007,"p99":281.2128650680747}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:26+02","p50":149.58510009375,"p95":307.63088744441603,"p99":335.33292383728195}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:27+02","p50":168.15508775,"p95":285.7666212016733,"p99":313.92114463868904}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:28+02","p50":188.28659266666668,"p95":295.94769378776715,"p99":315.91148610047435}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:29+02","p50":178.75765806249998,"p95":295.6567288852167,"p99":317.2789837458754}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:30+02","p50":152.27584396875,"p95":341.79098242099843,"p99":352.3200402101381}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:31+02","p50":163.94469566666666,"p95":323.94278911006387,"p99":347.9313984693055}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:32+02","p50":190.45950209375002,"p95":295.96613964145297,"p99":314.3702196151154}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:33+02","p50":180.63572125000002,"p95":284.8222163975665,"p99":318.613081566169}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:34+02","p50":188.64411037500003,"p95":266.3229787653223,"p99":286.69827406677865}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:35+02","p50":184.52384700000002,"p95":263.67790348002853,"p99":303.49705895792914}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:36+02","p50":179.47607453125,"p95":355.8448701697182,"p99":382.9379842353823}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:37+02","p50":181.09762290625002,"p95":251.98523698942577,"p99":268.9887313744817}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:38+02","p50":129.08299715625,"p95":305.38149777157025,"p99":339.56128032651185}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:39+02","p50":173.939551375,"p95":295.13943168028476,"p99":306.4496784014864}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:40+02","p50":184.15993678125,"p95":219.87376809752035,"p99":242.36668579811266}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:41+02","p50":176.92126165624998,"p95":255.3895744386714,"p99":273.5941948558238}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:42+02","p50":184.09578978125,"p95":242.15070153322625,"p99":255.75301165470577}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:43+02","p50":188.93190390625,"p95":270.17291396094987,"p99":288.7374878324158}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:44+02","p50":170.27386246875,"p95":302.0877696634417,"p99":322.2277050531044}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:45+02","p50":171.28439425,"p95":294.0131721756791,"p99":312.53864885021994}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:46+02","p50":216.75257355555553,"p95":322.79357449730196,"p99":356.2870012329533}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:47+02","p50":195.4499258333333,"p95":390.5324061168661,"p99":428.31820336249876}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:48+02","p50":237.23479605555553,"p95":356.1376857394992,"p99":380.66792645569654}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:49+02","p50":198.9062845,"p95":303.59157814383747,"p99":335.63319875815586}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:50+02","p50":197.55178744444444,"p95":246.66201525187805,"p99":286.0360022681031}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:51+02","p50":203.1253768125,"p95":298.65952319879835,"p99":323.86093111381956}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:52+02","p50":157.29233521875,"p95":317.64778697795776,"p99":350.21748591659593}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:53+02","p50":162.37203615625,"p95":305.0249680160875,"p99":344.69489125115444}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:54+02","p50":174.49744640625,"p95":279.51049966483635,"p99":299.0728399718125}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:55+02","p50":155.20962281250002,"p95":267.78474178160235,"p99":295.7095052904222}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:56+02","p50":157.371921875,"p95":293.56901468093776,"p99":319.46319449705885}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:57+02","p50":157.63671618749999,"p95":280.69837198581314,"p99":305.63588144813065}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:58+02","p50":169.2675446875,"p95":288.29848496026744,"p99":320.18566677379846}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:25:59+02","p50":173.090842,"p95":295.79925527414963,"p99":309.57774177467587}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:00+02","p50":206.27317477777777,"p95":332.6504074051008,"p99":364.18589949073026}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:01+02","p50":182.27632059375,"p95":221.51328541887975,"p99":266.13306615993787}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:02+02","p50":168.33315818749998,"p95":233.01725007892423,"p99":262.8184382386236}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:03+02","p50":172.41379237499999,"p95":247.44395826688336,"p99":268.36499833924194}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:04+02","p50":174.61597949999998,"p95":271.83778823314736,"p99":291.0804720828}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:05+02","p50":187.59158225,"p95":282.9914468398229,"p99":309.3119564544549}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:06+02","p50":181.2318166875,"p95":251.55728365982807,"p99":268.7432143299179}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:07+02","p50":172.82143659375,"p95":270.6421756053556,"p99":301.884456831074}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:08+02","p50":172.9038281875,"p95":251.8655380025793,"p99":298.0483943289444}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:09+02","p50":176.55819175,"p95":243.4837930472659,"p99":266.464914060235}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:10+02","p50":176.31888506250002,"p95":281.58976408667183,"p99":308.106734991333}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:11+02","p50":174.0874495,"p95":221.15128212171365,"p99":235.78570532621}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:12+02","p50":176.74896453125,"p95":249.94871128860984,"p99":268.7346848409734}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:13+02","p50":179.19973940625,"p95":322.88291914086074,"p99":346.68211576098633}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:14+02","p50":167.114223,"p95":279.35395499266207,"p99":304.0353890021672}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:15+02","p50":189.64666166666666,"p95":296.94451521529624,"p99":339.07841855854224}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:16+02","p50":182.53102394444443,"p95":292.5580229132622,"p99":311.0710275786543}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:17+02","p50":215.63680366666665,"p95":353.435264173954,"p99":391.11029739471434}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:18+02","p50":199.57578966666668,"p95":283.7855816993978,"p99":325.47797826733233}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:19+02","p50":180.462869625,"p95":294.70932847846296,"p99":353.2521015840626}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:20+02","p50":171.41940599999998,"p95":277.59080925117195,"p99":308.29028416486597}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:21+02","p50":175.43739625,"p95":255.71843944816638,"p99":288.13309434937094}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:22+02","p50":155.1737825625,"p95":319.33560243206887,"p99":352.25093038751055}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:23+02","p50":145.570873375,"p95":296.3754123819015,"p99":317.5755562688322}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:24+02","p50":160.4218194375,"p95":340.7114007824508,"p99":364.4656010268531}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:25+02","p50":162.16974831250002,"p95":279.9444344233162,"p99":296.29224127451107}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:26+02","p50":145.263541375,"p95":303.46550683899375,"p99":317.1922718414955}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:27+02","p50":171.87917687499998,"p95":297.13505856224157,"p99":326.78417222842313}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:28+02","p50":131.22916377777779,"p95":359.898864760616,"p99":376.2556717880466}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:29+02","p50":119.6028695,"p95":364.6834245433574,"p99":392.0918980344329}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:30+02","p50":152.96088740625,"p95":328.00408136766305,"p99":351.2401286689439}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:31+02","p50":102.28407511111112,"p95":460.5637817656824,"p99":478.0751268420224}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:32+02","p50":126.57270088888889,"p95":360.61222046122094,"p99":371.3287526978929}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:33+02","p50":132.26185315625,"p95":393.72701131120203,"p99":408.43086990150164}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:34+02","p50":165.71685333333335,"p95":356.6648353605751,"p99":384.9428702928367}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:35+02","p50":144.32326233333333,"p95":325.38959274268797,"p99":355.6178618845525}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:36+02","p50":141.32919166666667,"p95":354.349809254661,"p99":378.2323980121696}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:37+02","p50":129.520349,"p95":365.91650590921705,"p99":388.41206783638785}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:38+02","p50":107.45927116666667,"p95":388.48086245010336,"p99":410.1962572701168}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:39+02","p50":99.57383877777777,"p95":416.093772003908,"p99":424.19737575689317}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:40+02","p50":103.85079288888888,"p95":426.45169877547954,"p99":433.7994967553639}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:41+02","p50":106.83163511111111,"p95":423.48536041654,"p99":458.9282701152256}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:42+02","p50":139.0369598888889,"p95":367.1590390966934,"p99":378.2780998659148}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:43+02","p50":150.3051636666667,"p95":352.1733739595711,"p99":370.4108028213062}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:44+02","p50":183.41215333333332,"p95":366.32920334733535,"p99":389.2896098950312}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:45+02","p50":119.20249233333334,"p95":342.0096206443362,"p99":361.2770049820531}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:46+02","p50":142.06372866666666,"p95":431.9168797226615,"p99":453.44524299833773}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:47+02","p50":166.04477699999998,"p95":494.36011340903895,"p99":528.4672684806739}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:48+02","p50":243.59032388888886,"p95":367.2906143898193,"p99":396.595394825233}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:49+02","p50":172.526195,"p95":356.46564384029017,"p99":398.40816489110944}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:50+02","p50":156.869940375,"p95":270.63714209433107,"p99":298.84650663945007}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:51+02","p50":184.6008940625,"p95":302.04423156519533,"p99":330.38419290871815}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:52+02","p50":136.397303,"p95":348.60078693502504,"p99":379.10041040987636}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:53+02","p50":208.5958850625,"p95":297.4139880978174,"p99":342.4072156456852}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:54+02","p50":167.1972085,"p95":339.2144632359161,"p99":357.6255409016114}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:55+02","p50":200.5229682222222,"p95":284.90606338766986,"p99":323.40569906101587}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:56+02","p50":210.01563066666668,"p95":338.4560215353322,"p99":359.1871781713526}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:57+02","p50":179.1637526875,"p95":335.4905377814721,"p99":400.75122673833704}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:58+02","p50":189.334459,"p95":289.19623811589145,"p99":322.82845078143214}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:26:59+02","p50":197.29853,"p95":254.67813269819308,"p99":286.1629846492476}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:00+02","p50":190.4065411875,"p95":284.5076546309163,"p99":331.9035186802502}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:01+02","p50":181.47052406249998,"p95":279.69881130609434,"p99":313.719389493555}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:02+02","p50":151.94579166666668,"p95":354.61666319084213,"p99":371.7136036657352}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:03+02","p50":166.281235,"p95":386.73531692902384,"p99":420.649655327538}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:04+02","p50":148.7445223125,"p95":359.46318257351254,"p99":376.27688912959866}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:05+02","p50":184.21798096875,"p95":277.70900109177575,"p99":296.8218056136756}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:06+02","p50":162.87833112500002,"p95":314.527402146051,"p99":339.50987744527913}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:07+02","p50":168.53892825,"p95":306.9428177077141,"p99":334.97422145559887}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:08+02","p50":192.90900859375,"p95":285.82773883297716,"p99":341.94940182323097}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:09+02","p50":194.55982674999998,"p95":246.99063782974244,"p99":280.4351090385742}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:10+02","p50":174.28953725,"p95":247.9744066636477,"p99":279.17424074895763}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:11+02","p50":193.928404125,"p95":253.76260802380764,"p99":284.1286567328668}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:12+02","p50":189.29576181250002,"p95":263.7371795551295,"p99":278.0667329535737}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:13+02","p50":182.957293,"p95":294.6086662118673,"p99":317.8614731482558}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:14+02","p50":203.753277875,"p95":281.0476899822991,"p99":316.78528554933285}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:15+02","p50":180.89417,"p95":234.47657934764052,"p99":263.55548825027086}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:16+02","p50":203.712888,"p95":323.5563643740608,"p99":364.48719365641284}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:17+02","p50":260.81837955555557,"p95":380.940437108066,"p99":410.5779094341323}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:18+02","p50":198.35689022222223,"p95":365.16697414600145,"p99":396.2802318050656}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:19+02","p50":174.5708140625,"p95":285.0906010951182,"p99":328.2365876712575}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:20+02","p50":181.09996243749998,"p95":276.39923109533675,"p99":315.6490332285991}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:21+02","p50":186.5207919375,"p95":270.1056047462885,"p99":321.6402500349741}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:22+02","p50":184.4209691875,"p95":246.49267304373691,"p99":269.07445565216636}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:23+02","p50":193.4719618888889,"p95":273.3790767838836,"p99":314.5124505561542}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:24+02","p50":186.08233903125,"p95":266.5522896144972,"p99":304.5487637141478}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:25+02","p50":172.57913712500002,"p95":253.0352081657167,"p99":262.7493767693462}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:26+02","p50":178.66033675,"p95":310.59731244291976,"p99":344.55369092844967}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:27+02","p50":188.10271955555552,"p95":270.31915788613077,"p99":356.1369984561987}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:28+02","p50":159.21614743749998,"p95":287.4595513562077,"p99":316.61637117335323}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:29+02","p50":182.15573256250002,"p95":246.1517650496707,"p99":281.4713636001492}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:30+02","p50":173.9616719375,"p95":238.7210853960729,"p99":269.03982139480013}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:31+02","p50":176.48987084375,"p95":269.9758206322698,"p99":310.2472167075813}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:32+02","p50":169.6217251875,"p95":334.4223009461551,"p99":381.64384746658703}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:33+02","p50":173.198981,"p95":295.5843631533242,"p99":322.93146719270993}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:34+02","p50":162.326174875,"p95":309.13122795573594,"p99":349.75819565876577}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:35+02","p50":195.27382344444445,"p95":309.3739377559875,"p99":349.0708461076501}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:36+02","p50":181.84496800000002,"p95":338.0018862877129,"p99":369.45319263241146}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:37+02","p50":179.28821343749996,"p95":307.6492964786487,"p99":347.9981545432668}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:38+02","p50":183.731094,"p95":314.62089908723397,"p99":346.75015494358547}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:39+02","p50":194.126655625,"p95":260.54428832613536,"p99":295.5964730170975}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:40+02","p50":177.77776921875,"p95":288.4279057673874,"p99":329.3559257648399}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:41+02","p50":177.5621809375,"p95":305.15425345334114,"p99":376.9097950054536}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:42+02","p50":211.76522111111112,"p95":357.18577438551364,"p99":405.6006579650128}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:43+02","p50":184.48686756249998,"p95":255.88449723447326,"p99":300.79711599596027}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:44+02","p50":184.867052125,"p95":257.1018845733412,"p99":351.9285792595954}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:45+02","p50":177.895700875,"p95":254.4358383973141,"p99":303.02956470006563}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:46+02","p50":172.86840555555557,"p95":338.72827478282875,"p99":394.7819297608509}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:47+02","p50":254.53267066666663,"p95":380.7524738069238,"p99":407.6565277409205}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:48+02","p50":234.29611466666668,"p95":359.137765516373,"p99":411.1764193704636}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:49+02","p50":225.3586666111111,"p95":343.9276526965337,"p99":428.88149968406293}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:50+02","p50":172.047994125,"p95":265.0060092756381,"p99":308.71778692960356}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:51+02","p50":176.81159381249998,"p95":266.21200436137303,"p99":303.04123896559526}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:52+02","p50":181.7798923125,"p95":285.306894141206,"p99":359.1930122079897}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:53+02","p50":178.96946090625002,"p95":271.8641297672259,"p99":308.8865002192125}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:54+02","p50":187.8267409375,"p95":294.422145442864,"p99":313.28276309497954}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:55+02","p50":181.83900615624998,"p95":282.85299389284353,"p99":414.0699751253739}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:56+02","p50":175.685083625,"p95":305.89563840037823,"p99":392.00051498221444}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:57+02","p50":181.27693875,"p95":283.32694764093947,"p99":371.5354144100413}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:58+02","p50":189.2878153125,"p95":303.2108947008197,"p99":338.6312464663994}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:27:59+02","p50":184.22582406249998,"p95":264.0269759026263,"p99":287.74213224001693}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:00+02","p50":174.34488290624998,"p95":277.9093237921628,"p99":306.53292767350075}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:01+02","p50":172.891099125,"p95":313.25551782171726,"p99":370.5475053258922}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:02+02","p50":173.47537109375,"p95":267.0225890905224,"p99":326.83412361638716}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:03+02","p50":180.10718253125,"p95":256.32498419858223,"p99":370.94424462468885}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:04+02","p50":185.4720615625,"p95":273.6668380397305,"p99":301.26450008935115}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:05+02","p50":183.60832521875,"p95":295.63168707886484,"p99":313.20009102869676}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:06+02","p50":188.33244009375,"p95":292.2807335878806,"p99":330.34957164277387}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:07+02","p50":175.0624861111111,"p95":282.2448508753879,"p99":371.1998730442781}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:08+02","p50":193.189431375,"p95":289.6453383929767,"p99":385.4908967383757}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:09+02","p50":177.4467085,"p95":270.8755065380138,"p99":305.5201443798356}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:10+02","p50":183.44905834374998,"p95":281.39212976917014,"p99":356.07378722806266}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:11+02","p50":180.18716506250001,"p95":297.3179076449388,"p99":381.84803981396294}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:12+02","p50":179.81955490625,"p95":273.71195399226775,"p99":395.0524973836839}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:13+02","p50":197.876847,"p95":304.39508602396387,"p99":410.64733257506373}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:14+02","p50":204.00739866666666,"p95":345.92283645437016,"p99":428.4246764596453}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:15+02","p50":186.27291156249998,"p95":317.68894601801037,"p99":372.11997879711726}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:16+02","p50":195.23936694444447,"p95":313.743992693481,"p99":384.4459505873584}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:17+02","p50":231.78869322222224,"p95":395.5796082287531,"p99":437.190137809721}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:18+02","p50":209.29811977777777,"p95":387.20049555944814,"p99":530.0324769997549}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:19+02","p50":178.382816375,"p95":283.33926716237164,"p99":372.69932674834064}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:20+02","p50":175.56871912500003,"p95":285.42811584741463,"p99":339.69615951487253}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:21+02","p50":182.872967125,"p95":284.1898924847056,"p99":361.01803691475106}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:22+02","p50":179.582868125,"p95":293.5345978478228,"p99":361.35547253136446}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:23+02","p50":178.9781014375,"p95":288.1660969617573,"p99":321.85255615265464}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:24+02","p50":179.80794075,"p95":274.03609601131484,"p99":309.22765644909475}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:25+02","p50":167.9727905,"p95":282.9057814949338,"p99":353.0839556728606}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:26+02","p50":168.2010754375,"p95":290.81359906178733,"p99":353.23707309792997}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:27+02","p50":178.9828831875,"p95":303.51690530413777,"p99":345.44209892959077}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:28+02","p50":179.24066525,"p95":283.17939501104735,"p99":322.1220969011841}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:29+02","p50":182.18125053125,"p95":278.39569861031396,"p99":328.2225490846484}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:30+02","p50":168.5792554375,"p95":280.10370993942666,"p99":339.67714086049267}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:31+02","p50":185.55199322222222,"p95":290.30181307020194,"p99":325.63648723553825}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:32+02","p50":183.94873153124996,"p95":291.04865962262016,"p99":364.07572688195205}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:33+02","p50":175.4366629375,"p95":319.27942885836194,"p99":451.61856201362133}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:34+02","p50":183.21118928125,"p95":304.4462418187314,"p99":392.6879494052341}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:35+02","p50":181.02044783333335,"p95":277.3016030049224,"p99":327.82674934820557}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:36+02","p50":191.68535487499997,"p95":309.53458136322985,"p99":385.6664749968862}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:37+02","p50":181.94860699999998,"p95":285.48172275946047,"p99":345.92694605340574}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:38+02","p50":177.33195643750003,"p95":280.75634856703374,"p99":320.6769525189438}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:39+02","p50":184.9492619375,"p95":285.2504567660826,"p99":358.27477145018486}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:40+02","p50":183.04699634375,"p95":273.8091363463696,"p99":336.4280685615406}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:41+02","p50":173.40073978125002,"p95":299.81940583663135,"p99":362.2512695265634}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:42+02","p50":192.40045434374997,"p95":288.41179595461534,"p99":392.5507122601442}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:43+02","p50":178.7790755,"p95":292.0326294655233,"p99":371.9958734281445}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:44+02","p50":191.67839765624998,"p95":300.24229647855213,"p99":381.395401676014}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:45+02","p50":187.67865516666666,"p95":275.18243718778587,"p99":315.0424383924927}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:46+02","p50":197.05481855555558,"p95":371.42625160446977,"p99":467.6787794722595}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:47+02","p50":240.87116833333334,"p95":391.572233295926,"p99":519.9037980597662}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:48+02","p50":235.87759300000002,"p95":409.31382142708264,"p99":456.40398974086264}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:49+02","p50":224.59980644444443,"p95":355.4837907680993,"p99":423.4581333588619}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:50+02","p50":203.3833103333333,"p95":359.28066545660334,"p99":411.95622871698185}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:51+02","p50":173.208011,"p95":307.059043303128,"p99":387.6606016126637}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:52+02","p50":192.56502483333335,"p95":339.2739105763652,"p99":392.4409275000506}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:53+02","p50":199.82310011111113,"p95":297.42499997561225,"p99":345.9870140928783}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:54+02","p50":188.8862,"p95":322.0846949810638,"p99":378.0526086578979}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:55+02","p50":180.85722199999998,"p95":288.82187167933654,"p99":343.70991487231447}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:56+02","p50":185.458497125,"p95":284.31283382909396,"p99":335.0110088045254}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:57+02","p50":173.795028,"p95":279.8780628272852,"p99":339.887694766423}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:58+02","p50":174.3704303125,"p95":284.08356637899925,"p99":340.7857195103054}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:28:59+02","p50":188.94265933333335,"p95":296.63605886391923,"p99":361.7627259493022}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:00+02","p50":180.5411283888889,"p95":361.76556212719953,"p99":524.4916457930836}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:01+02","p50":186.18883196875,"p95":274.26070087586845,"p99":342.69958166062355}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:02+02","p50":187.39715016666665,"p95":338.78737315855227,"p99":428.3477456998158}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:03+02","p50":180.85751015625,"p95":310.2928600412392,"p99":383.3905066506598}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:04+02","p50":191.18199811111108,"p95":319.8406756083638,"p99":389.0285095677936}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:05+02","p50":175.32893340625003,"p95":286.3750313720981,"p99":349.6345958194578}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:06+02","p50":183.2386705,"p95":286.19398265717626,"p99":345.68618192171385}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:07+02","p50":192.226055,"p95":297.8554963492075,"p99":387.10925219785975}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:08+02","p50":182.3250955,"p95":294.3484677958275,"p99":363.44833844165186}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:09+02","p50":183.173839125,"p95":283.8246264684485,"p99":396.3395074730677}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:10+02","p50":187.69375688888888,"p95":291.33825403262443,"p99":346.2634405867634}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:11+02","p50":190.76420799999997,"p95":298.0675107736571,"p99":369.94269045167687}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:12+02","p50":185.63644483333334,"p95":292.96604209052634,"p99":348.131268917387}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:13+02","p50":190.84302672222222,"p95":304.11442938740254,"p99":348.86566499752047}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:14+02","p50":199.46363427777774,"p95":310.30567044360345,"p99":378.27760563399363}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:15+02","p50":195.37079677777777,"p95":342.68533850806176,"p99":435.84184006633376}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:16+02","p50":227.98423083333333,"p95":371.98305139658953,"p99":452.75762399102973}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:17+02","p50":206.52422533333333,"p95":348.3823036163349,"p99":436.44131284798}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:18+02","p50":222.68072266666664,"p95":441.58051954525274,"p99":477.3179562999191}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:19+02","p50":209.60902766666666,"p95":427.4816595137386,"p99":490.64022842250347}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:20+02","p50":195.28943727777778,"p95":386.7631437744287,"p99":528.5339059680829}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:21+02","p50":177.19292612499999,"p95":296.9966029884962,"p99":351.0876559623608}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:22+02","p50":187.82212983333332,"p95":320.7605695487688,"p99":356.4046808374877}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:23+02","p50":200.91790177777776,"p95":325.3274707046466,"p99":382.9400430357437}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:24+02","p50":192.095791,"p95":305.1662079537307,"p99":368.2749778338752}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:25+02","p50":196.35827400000002,"p95":353.0490556321643,"p99":452.9795076071234}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:26+02","p50":201.5129793333333,"p95":313.126704331702,"p99":380.70473433486416}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:27+02","p50":191.16391925,"p95":322.38616867259213,"p99":383.38376561450195}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:28+02","p50":189.79404555555558,"p95":318.48205436716233,"p99":403.98749458813404}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:29+02","p50":202.74254166666665,"p95":294.48037518920864,"p99":337.5341222375772}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:30+02","p50":200.895257,"p95":328.09237700937075,"p99":385.4590321867692}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:31+02","p50":195.1853305,"p95":317.0557495971711,"p99":341.8695390660572}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:32+02","p50":185.53336349999998,"p95":313.77426831526185,"p99":349.27972168170163}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:33+02","p50":185.72398405555555,"p95":311.7927280365346,"p99":385.93252687193103}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:34+02","p50":196.32199388888887,"p95":313.3746737635519,"p99":397.15976756807805}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:35+02","p50":195.77611466666667,"p95":318.6987151699731,"p99":391.9965806837673}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:36+02","p50":205.0182351666667,"p95":339.42000078268126,"p99":413.6267370639348}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:37+02","p50":194.602442,"p95":290.1181957805015,"p99":318.45405940259076}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:38+02","p50":191.95687622222223,"p95":297.9188392002153,"p99":600.743555807945}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:39+02","p50":205.7433178888889,"p95":306.8241684115159,"p99":417.0625454678657}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:40+02","p50":203.63462788888887,"p95":308.77570929680644,"p99":403.33060484419275}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:41+02","p50":188.15050315625,"p95":341.6003651119405,"p99":389.12308656003614}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:42+02","p50":193.08033874999998,"p95":293.74547187106015,"p99":362.63748073884443}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:43+02","p50":205.56052744444446,"p95":347.56138723170784,"p99":410.31080260947607}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:44+02","p50":229.83359483333334,"p95":389.4037414396386,"p99":503.9098959869328}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:45+02","p50":196.40565455555557,"p95":340.76758979425955,"p99":415.5161074771428}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:46+02","p50":204.35345344444445,"p95":377.3570702471015,"p99":426.65224602999325}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:47+02","p50":225.82986166666666,"p95":396.9132368823718,"p99":479.39892228121164}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:48+02","p50":236.2227238888889,"p95":420.8084648244519,"p99":585.2791829253792}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:49+02","p50":240.21362611111113,"p95":414.5261726602379,"p99":504.4608610409298}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:50+02","p50":179.1758516875,"p95":311.6895966241861,"p99":416.06294661549447}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:51+02","p50":258.72182475,"p95":449.94842317300106,"p99":494.3432882395859}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:52+02","p50":237.80108577777779,"p95":442.9910982815889,"p99":543.2034437793208}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:53+02","p50":195.665854,"p95":310.5751608634274,"p99":357.82289057198335}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:54+02","p50":196.82895044444444,"p95":319.5017947959468,"p99":373.76577025522687}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:55+02","p50":200.800851,"p95":342.9027650422167,"p99":400.36503775353503}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:56+02","p50":182.81177111111114,"p95":330.3610955594633,"p99":375.26893745562074}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:57+02","p50":196.06131533333334,"p95":315.32270442228736,"p99":392.0299460193381}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:58+02","p50":194.06245544444445,"p95":364.67851196668533,"p99":415.71667057423065}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:29:59+02","p50":191.181879,"p95":322.84256489709725,"p99":388.43254446045876}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:00+02","p50":215.1314085,"p95":322.17242880714775,"p99":391.6174805017881}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:01+02","p50":195.36236833333336,"p95":320.0050312237211,"p99":356.24980559045576}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:02+02","p50":191.07694611111114,"p95":294.1568991743737,"p99":355.2651012445986}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:03+02","p50":194.71243922222223,"p95":320.18561128697405,"p99":380.92638734178587}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:04+02","p50":202.7954959444444,"p95":359.1465304480229,"p99":422.0879062050114}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:05+02","p50":204.89305344444446,"p95":328.98988321948696,"p99":443.6920679938033}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:06+02","p50":189.607352,"p95":308.7473520167389,"p99":362.31522194940186}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:07+02","p50":197.15711483333334,"p95":305.333828006937,"p99":402.04787657486725}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:08+02","p50":205.9379448888889,"p95":321.8561829285126,"p99":390.4513909561615}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:09+02","p50":200.68760466666666,"p95":297.40365169285434,"p99":377.10381864853736}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:10+02","p50":205.77781333333334,"p95":329.51096899003556,"p99":362.6896775728693}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:11+02","p50":194.04743865624997,"p95":308.2045224999106,"p99":370.28523751307915}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:12+02","p50":187.54421494444443,"p95":304.8126183170655,"p99":374.488265570702}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:13+02","p50":207.95467661111113,"p95":353.32314698984675,"p99":425.3301876491327}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:14+02","p50":204.24703350000001,"p95":335.6742798255293,"p99":385.04407642636494}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:15+02","p50":199.99838666666665,"p95":343.40152375740155,"p99":414.83322244828344}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:16+02","p50":215.66121344444446,"p95":389.6706946554674,"p99":483.6929230183053}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:17+02","p50":252.786444,"p95":392.7790993085299,"p99":468.75099819384195}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:18+02","p50":250.53043150000002,"p95":484.7992541378441,"p99":597.2657474953403}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:19+02","p50":211.7113725,"p95":363.0630051821494,"p99":439.0834621645174}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:20+02","p50":196.90273866666666,"p95":327.408693076395,"p99":409.49469937997054}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:21+02","p50":195.202466,"p95":332.31202677364854,"p99":392.09884465544843}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:22+02","p50":207.1027911111111,"p95":318.4890294689935,"p99":383.3717451876423}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:23+02","p50":194.8952988888889,"p95":336.54343823294545,"p99":379.69921173835564}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:24+02","p50":203.22173966666665,"p95":325.858825276369,"p99":375.65041018047714}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:25+02","p50":208.34684211111116,"p95":321.4584466321313,"p99":375.1542131777668}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:26+02","p50":208.5398351111111,"p95":333.55307064500647,"p99":414.4640012401466}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:27+02","p50":207.8972022222222,"p95":313.9749510282942,"p99":365.0789934250443}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:28+02","p50":207.131656,"p95":325.7565959555035,"p99":381.7642503274655}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:29+02","p50":199.86096222222224,"p95":352.385276092414,"p99":457.95726721027086}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:30+02","p50":203.449391,"p95":311.8331262542561,"p99":368.2060998664541}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:31+02","p50":201.434107,"p95":323.3725637632765,"p99":412.7679559767577}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:32+02","p50":195.92857949999998,"p95":331.18196339855,"p99":350.3222537354736}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:33+02","p50":209.50369350000003,"p95":340.38541028134443,"p99":403.06620992008783}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:34+02","p50":204.23807227777777,"p95":332.1988236080222,"p99":374.7636895254688}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:35+02","p50":208.2409052222222,"p95":314.55598781393724,"p99":361.87513171565223}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:36+02","p50":199.80649922222221,"p95":346.9903820318022,"p99":420.8994297507582}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:37+02","p50":202.18685877777776,"p95":344.1347232513341,"p99":385.2447149118266}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:38+02","p50":199.01452222222224,"p95":314.9894906094337,"p99":382.90295159783483}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:39+02","p50":197.9540558888889,"p95":298.5944473896455,"p99":361.7452332461603}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:40+02","p50":193.04107544444446,"p95":336.9445636221,"p99":439.2198271483893}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:41+02","p50":204.85928644444445,"p95":329.6950419662266,"p99":376.36683230102875}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:42+02","p50":202.08633,"p95":335.5709988721693,"p99":449.73176842874045}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:43+02","p50":204.33184033333336,"p95":341.681939853343,"p99":398.2243132224665}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:44+02","p50":216.2977343888889,"p95":338.53055366873934,"p99":403.5138837327957}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:45+02","p50":210.7147877222222,"p95":349.652755861713,"p99":411.895023365963}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:46+02","p50":234.20458283333335,"p95":420.9659186840737,"p99":465.7731906653318}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:47+02","p50":293.1767015,"p95":474.4596434355699,"p99":539.9835101593533}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:48+02","p50":264.5489315,"p95":488.1557553457098,"p99":638.3500447652283}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:49+02","p50":249.61188066666668,"p95":454.0098626703217,"p99":509.3023160842629}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:50+02","p50":210.4459478888889,"p95":310.4518044317118,"p99":393.9216711000547}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:51+02","p50":207.11965961111113,"p95":291.8754875246973,"p99":377.99808943608855}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:52+02","p50":203.21053294444445,"p95":319.5771978321209,"p99":370.6410599894028}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:53+02","p50":201.05646166666668,"p95":326.7955876665544,"p99":370.4877728239765}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:54+02","p50":211.28015066666663,"p95":344.0611608850927,"p99":390.8962635882895}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:55+02","p50":203.21056233333334,"p95":322.7953190818432,"p99":399.29807289031123}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:56+02","p50":194.33037533333334,"p95":326.5859764551394,"p99":410.8137740690634}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:57+02","p50":196.94781300000002,"p95":349.0178860734527,"p99":383.11062646083474}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:58+02","p50":204.45741644444445,"p95":327.84456876539326,"p99":381.39637339495084}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:30:59+02","p50":202.78864411111113,"p95":343.9740485821655,"p99":432.9572875947094}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:00+02","p50":205.95223866666666,"p95":335.41241242368073,"p99":387.34601652950363}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:01+02","p50":211.46082533333333,"p95":360.6423262300987,"p99":440.72140459379574}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:02+02","p50":197.3168747777778,"p95":329.8497866886737,"p99":374.03593301870967}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:03+02","p50":201.06334833333335,"p95":332.5075342838344,"p99":389.4220417752116}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:04+02","p50":202.27969394444446,"p95":353.7058692879639,"p99":457.29652491950606}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:05+02","p50":213.40677316666665,"p95":353.859651326473,"p99":433.6958857299137}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:06+02","p50":202.95112300000002,"p95":353.25017717877125,"p99":402.259600662328}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:07+02","p50":202.82333622222222,"p95":378.725918430907,"p99":453.3172039213264}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:08+02","p50":205.31515066666665,"p95":331.4173666829703,"p99":356.3674236977348}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:09+02","p50":199.81312433333335,"p95":332.1951719743659,"p99":372.910739085881}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:10+02","p50":212.85129116666667,"p95":329.4328011459733,"p99":419.0517945991316}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:11+02","p50":208.479479,"p95":336.37588835243065,"p99":443.67065954572126}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:12+02","p50":201.95191027777778,"p95":356.8024198754394,"p99":470.981126758841}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:13+02","p50":214.89258866666668,"p95":362.81409435534954,"p99":465.5712323945394}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:14+02","p50":214.901326,"p95":352.42506585925935,"p99":463.7408573168888}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:15+02","p50":211.19516933333333,"p95":349.87133545118365,"p99":430.80135017974516}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:16+02","p50":216.68483155555555,"p95":427.9566579194666,"p99":503.48659728901697}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:17+02","p50":271.41614725,"p95":441.15044961888987,"p99":512.1111401653092}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:18+02","p50":306.50639475,"p95":569.0558931402464,"p99":688.2556815319238}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:19+02","p50":254.88855933333332,"p95":476.27378646646827,"p99":597.141738387}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:20+02","p50":198.59727344444445,"p95":320.7389186073849,"p99":421.7967471746555}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:21+02","p50":206.5006558888889,"p95":342.536433827621,"p99":431.1285347640934}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:22+02","p50":217.01676633333332,"p95":314.94728825959515,"p99":373.5243463510456}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:23+02","p50":219.68843355555555,"p95":323.98048004122876,"p99":431.36936606665995}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:24+02","p50":208.99038966666663,"p95":384.34630963680394,"p99":467.5369988175614}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:25+02","p50":236.496023,"p95":384.22419352286676,"p99":427.69464637662}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:26+02","p50":206.78477172222222,"p95":338.1532447306398,"p99":391.5990644091611}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:27+02","p50":215.75523866666668,"p95":347.49228730711417,"p99":382.6851442212737}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:28+02","p50":225.08358466666667,"p95":360.465705259843,"p99":460.8789688364668}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:29+02","p50":218.99032866666667,"p95":374.5318866430343,"p99":449.60486558010365}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:30+02","p50":217.46841788888887,"p95":353.05215357354984,"p99":441.3271211384466}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:31+02","p50":209.3592971666667,"p95":333.3041163535206,"p99":430.5565161539979}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:32+02","p50":218.3212752222222,"p95":373.2606719534786,"p99":451.42541734322}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:33+02","p50":222.234025,"p95":397.0059874037057,"p99":793.392067381136}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:34+02","p50":217.33779122222222,"p95":373.3611613553316,"p99":464.0301309402456}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:35+02","p50":216.2161926111111,"p95":368.94490332340814,"p99":417.49581946537967}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:36+02","p50":213.15242594444445,"p95":375.2710341566048,"p99":455.5329708254852}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:37+02","p50":218.75487983333332,"p95":375.00843727908887,"p99":437.8240498166828}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:38+02","p50":202.4453447777778,"p95":343.02197042780926,"p99":403.13196793447014}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:39+02","p50":204.0129807222222,"p95":328.8427881609154,"p99":386.41570954950714}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:40+02","p50":211.9851253333333,"p95":334.1692656585097,"p99":380.46787664077857}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:41+02","p50":211.3932218888889,"p95":332.68919332244724,"p99":368.890662632473}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:42+02","p50":213.8660166666667,"p95":362.8466214451796,"p99":434.8911987468617}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:43+02","p50":217.3899536111111,"p95":346.0583209492775,"p99":458.01952588379385}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:44+02","p50":212.36583444444446,"p95":436.4938474673834,"p99":497.11531839578294}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:45+02","p50":223.41937505555555,"p95":369.2475102060952,"p99":395.1562643494711}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:46+02","p50":233.03533766666663,"p95":476.76958996716024,"p99":545.7290004457728}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:47+02","p50":247.136315,"p95":426.3290815725408,"p99":556.8003014434757}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:48+02","p50":267.027059375,"p95":441.90775357704644,"p99":500.54315025215794}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:49+02","p50":278.28536125000005,"p95":540.1597657444663,"p99":589.0782165439439}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:50+02","p50":255.21872077777778,"p95":459.0029476219091,"p99":563.1064010536222}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:51+02","p50":210.07645122222223,"p95":348.043192212614,"p99":433.9214216951654}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:52+02","p50":218.8208072777778,"p95":369.6221431672909,"p99":449.7027166413131}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:53+02","p50":227.08810566666668,"p95":367.7262492031312,"p99":440.15627429259945}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:54+02","p50":220.03400722222224,"p95":389.7109632064009,"p99":471.74365632854847}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:55+02","p50":217.39400366666666,"p95":369.9641647675552,"p99":412.9882192422233}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:56+02","p50":223.94330666666667,"p95":353.05924872795214,"p99":410.5398413230288}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:57+02","p50":203.63400333333334,"p95":345.79593741989925,"p99":389.4707619506445}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:58+02","p50":216.1525446666667,"p95":362.81613961797296,"p99":499.07007394086384}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:31:59+02","p50":224.3493111111111,"p95":356.99095574862196,"p99":454.9698276131661}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:00+02","p50":213.83111416666668,"p95":347.0250929388242,"p99":403.7637512958574}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:01+02","p50":222.49984299999997,"p95":339.04559366448564,"p99":396.24504114500377}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:02+02","p50":223.620714,"p95":335.90819822589737,"p99":410.91454987573576}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:03+02","p50":226.24937166666666,"p95":335.32358695026153,"p99":367.93180820174575}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:04+02","p50":218.08913655555557,"p95":435.8915802509346,"p99":489.28708586331175}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:05+02","p50":217.93237133333332,"p95":328.9693759232018,"p99":392.5351731268709}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:06+02","p50":222.4663835,"p95":406.6362453829376,"p99":500.8691196273427}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:07+02","p50":226.61218944444445,"p95":354.61878946562285,"p99":416.5205221994543}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:08+02","p50":222.77214600000002,"p95":374.46663888822206,"p99":450.8094589098823}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:09+02","p50":219.8126467777778,"p95":392.00014755658765,"p99":513.9537291904709}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:10+02","p50":224.03142144444442,"p95":352.97279644524684,"p99":435.4032528243065}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:11+02","p50":208.24536694444444,"p95":378.0713102377484,"p99":431.6813703477011}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:12+02","p50":209.8761542222222,"p95":369.5424052136536,"p99":451.2388182447891}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:13+02","p50":219.23786166666665,"p95":363.25913980668,"p99":451.12399298057295}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:14+02","p50":223.97180244444448,"p95":360.2883645230989,"p99":435.269975978215}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:15+02","p50":216.71007194444442,"p95":337.97084148830606,"p99":425.6973328247461}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:16+02","p50":233.9444117222222,"p95":425.433444166523,"p99":475.6684020970764}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:17+02","p50":231.26625750000002,"p95":434.71429186028485,"p99":520.2974088405571}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:18+02","p50":266.73170337500005,"p95":490.28090702285914,"p99":608.0986997067176}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:19+02","p50":324.23335299999997,"p95":515.6776790467342,"p99":609.0101567112661}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:20+02","p50":235.59441133333334,"p95":449.6935749005503,"p99":553.0957678274817}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:21+02","p50":218.5602545555556,"p95":372.14105988772064,"p99":430.6909131883452}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:22+02","p50":225.17245699999998,"p95":363.09618414827094,"p99":452.7774334426458}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:23+02","p50":210.15913133333333,"p95":373.7111297360679,"p99":461.5869807403679}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:24+02","p50":238.57727033333333,"p95":381.973308155411,"p99":477.66297981008245}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:25+02","p50":216.27082833333336,"p95":378.01305271718786,"p99":466.8391950271168}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:26+02","p50":242.8518598333333,"p95":359.6791281525676,"p99":428.9229971366134}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:27+02","p50":231.63725011111111,"p95":349.4298509186148,"p99":397.71019343576194}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:28+02","p50":226.732339,"p95":405.62277149829583,"p99":484.44164217784237}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:29+02","p50":228.32962716666668,"p95":361.53411524699186,"p99":414.4297816233115}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:30+02","p50":233.88495633333332,"p95":326.2271343763365,"p99":369.9302257789562}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:31+02","p50":226.84978122222222,"p95":360.97489976813193,"p99":463.8371062346909}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:32+02","p50":236.76296433333334,"p95":354.7024958877034,"p99":413.6180070585396}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:33+02","p50":224.9032802222222,"p95":375.59095100642446,"p99":419.86932278964713}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:34+02","p50":240.32421677777776,"p95":392.4488996499612,"p99":431.8389848001766}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:35+02","p50":230.42220444444447,"p95":378.56464276099706,"p99":505.89891194351765}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:36+02","p50":234.5692048888889,"p95":401.51542342983174,"p99":461.6675951622534}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:37+02","p50":227.309022,"p95":355.8756864912835,"p99":455.98563471131206}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:38+02","p50":224.007426,"p95":376.11202465679236,"p99":464.66399719672154}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:39+02","p50":228.86314066666668,"p95":356.3182932732327,"p99":403.7778434523003}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:40+02","p50":230.80453366666669,"p95":404.52124239434767,"p99":535.0275503524186}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:41+02","p50":218.62569861111112,"p95":360.09291733054164,"p99":439.7718299076786}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:42+02","p50":217.58812883333334,"p95":371.6389735550856,"p99":437.86308375483134}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:43+02","p50":233.06853166666664,"p95":399.19068332490605,"p99":479.7929705137174}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:44+02","p50":234.47938266666665,"p95":392.6643144576529,"p99":449.9907589968376}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:45+02","p50":232.08557983333333,"p95":382.68468427319885,"p99":488.2678124033661}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:46+02","p50":244.62408449999998,"p95":446.9696500811621,"p99":515.7216993169636}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:47+02","p50":255.19688,"p95":399.67054350503884,"p99":525.5660708170998}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:48+02","p50":285.83534387500004,"p95":487.26039093950254,"p99":544.4698768061297}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:49+02","p50":301.3556585,"p95":510.964604578166,"p99":635.2784504037246}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:50+02","p50":264.18729766666667,"p95":472.6264136536513,"p99":636.7521938510426}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:51+02","p50":227.35487550000002,"p95":377.1048645129325,"p99":458.53888587810513}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:52+02","p50":251.922576,"p95":447.350989489799,"p99":554.7686835216598}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:53+02","p50":263.13434474999997,"p95":530.8507723858329,"p99":721.0481155224113}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:54+02","p50":225.58388261111114,"p95":380.161137656028,"p99":468.34563998138475}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:55+02","p50":242.1133463333333,"p95":384.5402654924929,"p99":505.5946372716129}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:56+02","p50":232.08675422222223,"p95":384.213887055137,"p99":429.20222261100054}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:57+02","p50":223.09726055555555,"p95":404.80337730472684,"p99":511.3158119601204}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:58+02","p50":234.7839282777778,"p95":358.6693596630655,"p99":451.8204360131163}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:32:59+02","p50":232.87882766666667,"p95":425.7467114883852,"p99":487.68536225521467}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:00+02","p50":237.06443000000002,"p95":399.4266287533381,"p99":476.90989017555665}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:01+02","p50":248.4058461666667,"p95":396.54308326479105,"p99":474.22467991028736}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:02+02","p50":230.37859744444447,"p95":393.55309706906684,"p99":453.449538335906}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:03+02","p50":232.3341578333333,"p95":374.4735733048958,"p99":465.26900734739206}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:04+02","p50":264.482689875,"p95":410.27116344049404,"p99":469.7166974274752}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:05+02","p50":246.8850505,"p95":395.45494734583315,"p99":442.84180295915985}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:06+02","p50":230.89117733333333,"p95":353.4638644950161,"p99":389.47989380728535}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:07+02","p50":232.73668866666668,"p95":377.9326010976375,"p99":437.5844729626205}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:08+02","p50":235.79598088888892,"p95":416.665821860076,"p99":505.7929715148871}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:09+02","p50":233.68560366666665,"p95":373.1392250865016,"p99":418.8801051078911}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:10+02","p50":230.8417433333333,"p95":420.2855908858211,"p99":473.8649640798705}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:11+02","p50":223.24582850000002,"p95":393.8979325222867,"p99":476.9909705780468}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:12+02","p50":244.14954133333333,"p95":403.7375241260774,"p99":477.99835210016084}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:13+02","p50":246.41228644444445,"p95":361.8135735635612,"p99":402.8533168321066}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:14+02","p50":229.61298766666667,"p95":363.6033308717476,"p99":455.7843266998546}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:15+02","p50":244.89581355555552,"p95":400.5891745289134,"p99":456.673000659389}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:16+02","p50":253.74574712499998,"p95":398.5470624757168,"p99":436.57160895948317}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:17+02","p50":290.77609774999996,"p95":534.8039849119766,"p99":591.1491747143622}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:18+02","p50":287.630905,"p95":555.7053628811187,"p99":759.3057628708649}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:19+02","p50":260.139722,"p95":434.3915614445654,"p99":583.3201923909776}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:20+02","p50":243.41405466666666,"p95":369.17734439572047,"p99":455.30727302378466}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:21+02","p50":231.53368149999997,"p95":365.0432653110473,"p99":407.24337827021407}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:22+02","p50":242.505883,"p95":412.4999347425413,"p99":524.601658194849}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:23+02","p50":232.54534833333332,"p95":374.5556629026492,"p99":442.560841053041}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:24+02","p50":239.58688183333334,"p95":398.5046884272634,"p99":480.59723467273955}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:25+02","p50":247.6962111111111,"p95":387.1799167187638,"p99":464.32631229422833}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:26+02","p50":241.012872,"p95":368.4025947332088,"p99":447.3605011210189}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:27+02","p50":233.9766694444444,"p95":386.96228628693285,"p99":477.81864650684935}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:28+02","p50":235.542109,"p95":389.2316282820778,"p99":448.98848502568865}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:29+02","p50":245.02206544444445,"p95":393.23018982993966,"p99":459.86451168006994}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:30+02","p50":223.270794,"p95":355.406840181513,"p99":387.4610252394195}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:31+02","p50":239.80010344444443,"p95":394.09504108615874,"p99":494.7818513401794}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:32+02","p50":246.17028566666667,"p95":386.3242740246104,"p99":417.96801886121796}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:33+02","p50":247.03520083333333,"p95":390.1639436671072,"p99":459.0386687274175}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:34+02","p50":241.73285116666668,"p95":375.95783428324125,"p99":601.1601099873352}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:35+02","p50":255.778945125,"p95":419.4284088787367,"p99":545.8958604787023}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:36+02","p50":243.52722133333333,"p95":460.7516336206549,"p99":532.6451249426284}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:37+02","p50":244.04432766666665,"p95":388.6908727975695,"p99":422.97644730704116}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:38+02","p50":220.39450349999998,"p95":568.1284529653406,"p99":796.0735677623483}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:39+02","p50":246.928389,"p95":472.18584955523835,"p99":812.0410928928576}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:40+02","p50":227.95230333333333,"p95":370.18308553036735,"p99":465.310982619061}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:41+02","p50":227.30706838888887,"p95":399.8503266134108,"p99":454.9765421770229}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:42+02","p50":225.83264633333334,"p95":403.40213752225685,"p99":480.22409354110385}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:43+02","p50":243.92712533333335,"p95":377.5723761829224,"p99":432.4551040263641}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:44+02","p50":242.24754425,"p95":426.6340734641848,"p99":490.11104219123314}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:45+02","p50":257.8771213333333,"p95":407.0849706850665,"p99":560.2219665034747}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:46+02","p50":246.939657375,"p95":450.8215187908593,"p99":489.9558820302191}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:47+02","p50":290.98755325,"p95":497.5922189429208,"p99":551.4503608069765}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:48+02","p50":306.20941975,"p95":527.852640478988,"p99":641.807728294407}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:49+02","p50":292.03312174999996,"p95":518.6509610362588,"p99":583.629724640347}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:50+02","p50":252.55858233333333,"p95":437.9912643892018,"p99":561.7242892578134}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:51+02","p50":236.48447816666666,"p95":417.9918249314029,"p99":464.1891630546756}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:52+02","p50":248.784396,"p95":393.80324938535057,"p99":452.50154753660036}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:53+02","p50":239.0274007222222,"p95":373.2718030940702,"p99":433.1383861347618}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:54+02","p50":233.55698716666666,"p95":394.4068900888228,"p99":459.51528932271003}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:55+02","p50":248.1247286111111,"p95":422.1556352405434,"p99":461.31605151419063}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:56+02","p50":232.1230298888889,"p95":382.47190553403607,"p99":453.6062800068715}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:57+02","p50":245.4926118888889,"p95":439.4423141718396,"p99":481.5430297317166}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:58+02","p50":232.844899875,"p95":436.8947481744818,"p99":541.0581313336654}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:33:59+02","p50":266.1794130555556,"p95":384.41047095709104,"p99":434.79699793671415}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:00+02","p50":236.73512316666665,"p95":393.8240202940875,"p99":502.21442247265816}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:01+02","p50":231.73938011111113,"p95":375.5038520823298,"p99":490.72883558253477}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:02+02","p50":234.91059538888888,"p95":386.9306789033506,"p99":435.4778516190729}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:03+02","p50":250.90772750000002,"p95":418.5410255227275,"p99":484.13693815435596}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:04+02","p50":253.2914976666667,"p95":438.1993889250488,"p99":485.156488437088}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:05+02","p50":237.86275966666665,"p95":412.84416064722546,"p99":531.4925584635935}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:06+02","p50":241.4130301111111,"p95":384.6835320394778,"p99":466.4485572138905}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:07+02","p50":246.9962435,"p95":430.61991138997416,"p99":548.2566847912502}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:08+02","p50":252.04718633333334,"p95":415.8835463977967,"p99":506.81570539813833}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:09+02","p50":242.890341,"p95":415.68252231719333,"p99":459.3355598804331}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:10+02","p50":257.612865375,"p95":396.18150317915325,"p99":547.5985127782313}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:11+02","p50":241.42066133333336,"p95":384.0826483941463,"p99":475.19276299889685}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:12+02","p50":230.87129155555556,"p95":414.3070638313751,"p99":516.0454287644143}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:13+02","p50":257.038431375,"p95":395.0868904290263,"p99":520.8248982555652}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:14+02","p50":257.551038,"p95":392.02358257368945,"p99":446.8837309689145}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:15+02","p50":242.0045942222222,"p95":398.8031564911429,"p99":463.7348498225236}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:16+02","p50":263.97582162500004,"p95":470.7002272789613,"p99":596.6145870656634}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:17+02","p50":262.332346,"p95":475.3494265709779,"p99":658.0977595134699}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:18+02","p50":293.60957375,"p95":560.0902498358479,"p99":778.0352557814922}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:19+02","p50":269.52456137499996,"p95":487.2904865060484,"p99":606.1188282251981}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:20+02","p50":244.15401133333333,"p95":387.7079944607147,"p99":461.90501719173835}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:21+02","p50":242.50962461111112,"p95":407.23767418207547,"p99":450.81608595880124}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:22+02","p50":253.49955575,"p95":405.8076309284006,"p99":479.3382726868348}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:23+02","p50":274.9960685,"p95":428.6451952173658,"p99":535.5117178568265}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:24+02","p50":247.22204250000001,"p95":379.9838824606048,"p99":469.887887423934}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:25+02","p50":243.24941633333333,"p95":387.08692911201007,"p99":522.0354548741606}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:26+02","p50":256.83797849999996,"p95":435.508455010896,"p99":550.692052602832}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:27+02","p50":304.496019875,"p95":446.85923004462387,"p99":534.6948307420452}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:28+02","p50":257.481522125,"p95":445.0183366163345,"p99":538.8034372824739}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:29+02","p50":238.31051549999998,"p95":427.5492712721037,"p99":482.23812962444117}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:30+02","p50":247.265616125,"p95":450.8236542639635,"p99":507.2346711872134}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:31+02","p50":244.04404427777777,"p95":415.78895576956944,"p99":455.62288074609944}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:32+02","p50":244.34603562499998,"p95":433.7406606232805,"p99":522.8088149285817}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:33+02","p50":237.54384644444443,"p95":387.55320204594807,"p99":480.8667602889709}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:34+02","p50":258.3482255,"p95":397.8261588699484,"p99":489.93941125999265}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:35+02","p50":246.02377483333336,"p95":371.4415451996841,"p99":480.1354843534546}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:36+02","p50":246.23659855555556,"p95":398.50261767405425,"p99":550.2927599834298}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:37+02","p50":253.42273262499998,"p95":427.3329114535793,"p99":506.6480114395923}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:38+02","p50":246.60848294444443,"p95":395.13165166255476,"p99":462.32764865563485}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:39+02","p50":245.709733,"p95":422.92360313488217,"p99":558.3094882057648}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:40+02","p50":247.76838394444442,"p95":399.6298373327687,"p99":524.8320113544054}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:41+02","p50":231.99763283333334,"p95":396.4600884229193,"p99":468.1577781916847}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:42+02","p50":257.294788,"p95":412.975906559,"p99":477.2232233898587}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:43+02","p50":247.16260511111113,"p95":437.994821644352,"p99":616.0052474575958}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:44+02","p50":254.80792699999998,"p95":423.8831210833641,"p99":480.05263252854013}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:45+02","p50":250.00091700000002,"p95":444.52197677016926,"p99":514.3937235638236}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:46+02","p50":251.906231,"p95":532.076560612113,"p99":591.2071615058823}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:47+02","p50":262.25566625,"p95":464.3827456603379,"p99":609.9958891009597}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:48+02","p50":284.721244375,"p95":531.728444953583,"p99":640.5216567479637}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:49+02","p50":338.290922125,"p95":589.1906216921661,"p99":691.8721883108179}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:50+02","p50":320.7673855,"p95":515.5176785436153,"p99":632.0176443304506}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:51+02","p50":270.6670875,"p95":439.75460668313167,"p99":534.7910984565306}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:52+02","p50":247.18855844444445,"p95":414.9266299242001,"p99":449.263458878932}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:53+02","p50":252.72339575,"p95":461.86140911139967,"p99":506.16770534266567}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:54+02","p50":244.2179183333333,"p95":403.0196703677668,"p99":476.57731770493507}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:55+02","p50":254.95947675,"p95":428.0794374766185,"p99":480.5137230691939}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:56+02","p50":239.356133,"p95":417.97537687333147,"p99":575.2845023377519}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:57+02","p50":252.05772174999998,"p95":445.2623466346369,"p99":681.62964024632}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:58+02","p50":251.509722,"p95":406.68917443704447,"p99":488.2903484970379}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:34:59+02","p50":269.968730125,"p95":462.955597932823,"p99":517.51872375743}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:00+02","p50":271.291179625,"p95":408.15900895877274,"p99":468.0575583917348}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:01+02","p50":260.6519265,"p95":408.2398278800601,"p99":486.9506066071282}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:02+02","p50":249.08638399999998,"p95":402.8122317941038,"p99":425.2730846728821}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:03+02","p50":240.812377,"p95":387.9569571473711,"p99":487.22251631128}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:04+02","p50":255.25976799999998,"p95":389.6793000755879,"p99":468.7659897720661}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:05+02","p50":250.62307099999998,"p95":410.2871182714511,"p99":497.14616014005856}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:06+02","p50":246.606369,"p95":439.569528999143,"p99":540.7300664410473}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:07+02","p50":250.63701862499997,"p95":439.88441669453726,"p99":495.77547170241456}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:08+02","p50":250.99789633333333,"p95":384.7336377220992,"p99":460.1935018582964}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:09+02","p50":255.797218625,"p95":382.21282533441325,"p99":437.8775799206567}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:10+02","p50":260.1340413333333,"p95":462.7808593478576,"p99":544.0607919301765}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:11+02","p50":255.126604,"p95":437.71638282977256,"p99":509.5122343222742}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:12+02","p50":254.16688375,"p95":469.7708445514171,"p99":596.4816492280378}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:13+02","p50":255.737143,"p95":470.07591599670377,"p99":555.2960708614478}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:14+02","p50":251.8899373333333,"p95":401.569131154243,"p99":439.75819112880185}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:15+02","p50":252.79897012500004,"p95":382.81354998726715,"p99":466.82819427431633}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:16+02","p50":258.368427625,"p95":478.7574591130687,"p99":550.6379207093775}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:17+02","p50":269.95823225000004,"p95":508.84886587641716,"p99":581.3479067453766}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:18+02","p50":319.929041875,"p95":623.0270191611027,"p99":744.8653670339498}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:19+02","p50":320.10733,"p95":578.515384161166,"p99":802.1934291480673}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:20+02","p50":254.131393125,"p95":429.48825539788913,"p99":520.7591032995374}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:21+02","p50":263.7090465,"p95":464.01810903141245,"p99":543.333945996479}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:22+02","p50":265.382925,"p95":444.47017766590363,"p99":499.2965828000684}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:23+02","p50":262.59565216666664,"p95":445.50939130510335,"p99":505.75773296498255}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:24+02","p50":255.2947625,"p95":447.27120964095207,"p99":522.845152996975}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:25+02","p50":266.405042,"p95":430.21109693399,"p99":527.1653022898302}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:26+02","p50":257.5719805,"p95":425.71357152069425,"p99":475.99853154133797}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:27+02","p50":258.42057625,"p95":451.2807406978629,"p99":519.8596373823572}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:28+02","p50":263.750858875,"p95":413.1412213547309,"p99":501.4988705335603}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:29+02","p50":261.3700325,"p95":459.44244356627036,"p99":545.8286856570244}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:30+02","p50":259.16923124999994,"p95":394.999657350305,"p99":474.09946806015637}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:31+02","p50":260.73462974999995,"p95":402.0080005847919,"p99":480.7240314587393}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:32+02","p50":247.95554925,"p95":443.7761597778764,"p99":568.4041524165335}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:33+02","p50":273.68144574999997,"p95":418.8592395719484,"p99":445.7310229781904}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:34+02","p50":271.24839637499997,"p95":415.6710178238098,"p99":474.1082294845698}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:35+02","p50":271.00155233333334,"p95":464.5032853302579,"p99":564.5255068388929}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:36+02","p50":270.057148,"p95":429.41141660477575,"p99":489.42654765290973}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:37+02","p50":279.260726875,"p95":473.10020290691523,"p99":517.9944379044347}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:38+02","p50":259.893681,"p95":475.40265398043715,"p99":516.7147075678382}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:39+02","p50":241.16473766666664,"p95":401.106435010652,"p99":486.8981021822858}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:40+02","p50":264.733921,"p95":438.0484842146253,"p99":464.77467181394763}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:41+02","p50":253.783912,"p95":390.63252882195974,"p99":489.4342463915157}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:42+02","p50":248.65580075000003,"p95":394.7830699308772,"p99":504.30505801576044}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:43+02","p50":295.8947515,"p95":528.4741479112611,"p99":617.1819563232746}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:44+02","p50":284.473048125,"p95":454.27704807440466,"p99":527.7279918741376}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:45+02","p50":249.64267566666663,"p95":436.2224412011648,"p99":497.9041390465624}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:46+02","p50":270.368884,"p95":517.5866726913666,"p99":571.0812230935578}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:47+02","p50":314.154541625,"p95":554.5483804525378,"p99":697.1422834208388}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:48+02","p50":330.89126825,"p95":566.703627649873,"p99":684.9721087983279}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:49+02","p50":327.57335762499997,"p95":602.7722687312624,"p99":716.9294886656778}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:50+02","p50":267.41302675,"p95":488.1631517109785,"p99":536.3552451085968}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:51+02","p50":268.711671625,"p95":393.6989624800772,"p99":445.9623439904365}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:52+02","p50":258.78581877777776,"p95":406.33913889978095,"p99":484.40464599093536}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:53+02","p50":255.63745699999998,"p95":416.6679169158888,"p99":486.7021321201973}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:54+02","p50":253.28816762499997,"p95":424.1734274351335,"p99":505.7028296532907}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:55+02","p50":261.75738533333333,"p95":439.2747612949972,"p99":470.0899060178227}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:56+02","p50":254.1992995,"p95":438.37555251568114,"p99":504.4832310588999}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:57+02","p50":271.47276312500003,"p95":398.8732578309153,"p99":530.2756969538971}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:58+02","p50":265.66792,"p95":443.20559678939367,"p99":533.555235527616}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:35:59+02","p50":266.24755,"p95":441.10460870551316,"p99":464.42411755782365}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:00+02","p50":247.36104799999998,"p95":424.21022066528747,"p99":525.1935427021211}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:01+02","p50":308.055564,"p95":547.7484162400189,"p99":754.7858044313889}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:02+02","p50":297.69547950000003,"p95":470.56979023146846,"p99":550.9621809520797}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:03+02","p50":273.73311424999997,"p95":391.54654427341166,"p99":467.90297496202754}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:04+02","p50":279.3649785,"p95":440.68600435456733,"p99":601.7719794049216}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:05+02","p50":260.66567775,"p95":415.4385318268583,"p99":498.1817095879083}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:06+02","p50":244.37345525,"p95":419.7056218058458,"p99":501.14800422493624}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:07+02","p50":265.3689635,"p95":454.1364713933226,"p99":499.5201940366664}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:08+02","p50":282.50187800000003,"p95":476.86135509599245,"p99":544.7850116231317}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:09+02","p50":258.74034725,"p95":421.0682923532711,"p99":549.8008959157944}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:10+02","p50":250.69572349999999,"p95":479.76608016887957,"p99":563.4040498106232}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:11+02","p50":265.02781425,"p95":418.6808485449755,"p99":479.3354043331985}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:12+02","p50":287.13989349999997,"p95":417.95321612878786,"p99":493.66395250505826}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:13+02","p50":265.90458675,"p95":429.3374977181099,"p99":540.6860666412163}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:14+02","p50":267.35199725,"p95":462.12737333626444,"p99":518.3628795830326}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:15+02","p50":270.26036425,"p95":425.64670490455245,"p99":492.6650219917927}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:16+02","p50":267.952325,"p95":457.5532252522965,"p99":663.9122299074402}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:17+02","p50":274.56824300000005,"p95":495.6762548673733,"p99":628.0609482288104}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:18+02","p50":342.886177375,"p95":563.050607191615,"p99":773.9684411976912}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:19+02","p50":311.35315875000003,"p95":618.8045532641,"p99":676.2139193190757}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:20+02","p50":258.68767425,"p95":434.68928235208654,"p99":527.718808758278}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:21+02","p50":264.1264005,"p95":432.4782100336876,"p99":496.36295273950196}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:22+02","p50":284.1780665,"p95":443.2347498551246,"p99":518.5824665314648}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:23+02","p50":281.483592875,"p95":419.242792908064,"p99":490.7284401088722}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:24+02","p50":272.64804025,"p95":416.24347988170456,"p99":472.5741446776733}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:25+02","p50":280.950089125,"p95":426.0589155050207,"p99":566.034674371351}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:26+02","p50":251.6446375,"p95":396.3501515190627,"p99":495.75921183264967}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:27+02","p50":271.6921563333333,"p95":474.90097030203987,"p99":519.697609997715}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:28+02","p50":266.936282875,"p95":442.30553009358806,"p99":509.80180702896024}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:29+02","p50":260.72009649999995,"p95":455.6411531602584,"p99":524.1748620250402}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:30+02","p50":264.44158949999996,"p95":404.5745870633924,"p99":491.91618528585434}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:31+02","p50":269.81570650000003,"p95":419.10434405671907,"p99":488.8180724874473}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:32+02","p50":260.94705250000004,"p95":454.2397197066002,"p99":510.50326549041364}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:33+02","p50":274.13322,"p95":439.01972856016585,"p99":482.5345382981339}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:34+02","p50":254.45976655555557,"p95":408.23470826867293,"p99":513.4013226754913}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:35+02","p50":267.26025725,"p95":464.78726307099674,"p99":540.4649847713737}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:36+02","p50":263.451475,"p95":439.8489624030719,"p99":528.4659436606503}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:37+02","p50":273.681362,"p95":413.1530599547871,"p99":475.3887482464139}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:38+02","p50":262.42256325,"p95":455.4396345786124,"p99":580.7996150937603}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:39+02","p50":257.9202376666667,"p95":437.1068716493566,"p99":486.99432259580396}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:40+02","p50":258.56056875,"p95":433.37247478405715,"p99":555.8528159423342}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:41+02","p50":264.389997,"p95":438.2981008643098,"p99":547.3491188609486}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:42+02","p50":276.49165312499997,"p95":430.88875801244956,"p99":485.78888851100425}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:43+02","p50":280.752932125,"p95":446.3170856669779,"p99":531.9637418814247}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:44+02","p50":282.32115875,"p95":484.47938888746734,"p99":602.8593631017479}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:45+02","p50":263.9226543333333,"p95":445.83545400655476,"p99":601.6209245441456}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:46+02","p50":291.68244425,"p95":497.7035354248284,"p99":679.3175245265692}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:47+02","p50":312.990089125,"p95":486.8317967950743,"p99":505.312604825201}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:48+02","p50":329.2914065,"p95":551.4049445607922,"p99":680.2606640784159}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:49+02","p50":339.16039,"p95":593.5391209677692,"p99":646.3333434984534}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:50+02","p50":276.62490599999995,"p95":558.3999952588991,"p99":600.6332137365399}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:51+02","p50":250.670752,"p95":409.05569041803824,"p99":533.5402634348641}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:52+02","p50":272.426645375,"p95":571.8618228908813,"p99":675.4606616498987}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:53+02","p50":267.51233625,"p95":422.6446393228599,"p99":531.6386685849448}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:54+02","p50":291.44980050000004,"p95":454.23875224000966,"p99":493.3651159520278}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:55+02","p50":274.67588487499995,"p95":441.77680315136587,"p99":540.8149981984445}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:56+02","p50":267.484300125,"p95":458.66542662605326,"p99":599.1730176369266}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:57+02","p50":262.621663,"p95":493.3482629882154,"p99":551.2015649434089}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:58+02","p50":281.55920000000003,"p95":445.7603713362245,"p99":496.409015278841}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:36:59+02","p50":278.549184,"p95":460.764101054255,"p99":532.8517307298717}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:00+02","p50":257.17045125,"p95":455.6271916242397,"p99":546.5039346143418}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:01+02","p50":271.48472825,"p95":427.6455074257917,"p99":533.3504298355958}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:02+02","p50":277.759975125,"p95":447.2331365456114,"p99":500.83704259983995}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:03+02","p50":275.49270187499997,"p95":405.2120361834234,"p99":479.98993033915207}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:04+02","p50":281.118100625,"p95":510.59994351032475,"p99":613.3954287719366}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:05+02","p50":271.03451125000004,"p95":475.3531457091773,"p99":520.8336449481296}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:06+02","p50":263.57687075,"p95":455.16643932775304,"p99":563.6589416264572}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:07+02","p50":290.43707325,"p95":472.7174846760864,"p99":560.3053150923405}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:08+02","p50":286.53179824999995,"p95":453.03868992775585,"p99":570.84568331039}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:09+02","p50":272.77136099999996,"p95":423.93573187629914,"p99":505.7369940903995}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:10+02","p50":280.7256185,"p95":463.9208990961666,"p99":520.664117339571}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:11+02","p50":275.613451,"p95":467.25170113098903,"p99":517.4252537377548}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:12+02","p50":248.20316125,"p95":454.2489062066117,"p99":547.0151625803135}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:13+02","p50":271.80180625,"p95":494.06151770305723,"p99":552.4011694815388}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:14+02","p50":290.31772524999997,"p95":521.2814904891643,"p99":599.5541574068633}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:15+02","p50":278.0536915,"p95":448.11469075231,"p99":561.162434063208}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:16+02","p50":313.3462895,"p95":619.5034486344389,"p99":702.6555097997456}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:17+02","p50":324.854590625,"p95":579.6841767499525,"p99":750.3554944991853}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:18+02","p50":343.512835,"p95":627.1252977442357,"p99":678.921302243248}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:19+02","p50":347.22449875,"p95":641.9308238686408,"p99":726.5546061571674}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:20+02","p50":272.753773,"p95":456.93943756108297,"p99":545.0433867799397}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:21+02","p50":287.305234,"p95":482.93960357461015,"p99":527.3300204129798}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:22+02","p50":291.84331899999995,"p95":459.0205791523464,"p99":636.5230674784857}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:23+02","p50":272.12157075000005,"p95":421.2066189373023,"p99":493.5883063361807}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:24+02","p50":275.994239875,"p95":472.16574953047,"p99":525.6466085327122}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:25+02","p50":282.369423,"p95":400.93488896156776,"p99":504.1080421592221}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:26+02","p50":279.14357125000004,"p95":435.26581782687947,"p99":490.03172875637534}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:27+02","p50":278.045139,"p95":437.25978724796465,"p99":525.4959172818178}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:28+02","p50":280.65772525,"p95":491.2533090767055,"p99":595.1768315263453}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:29+02","p50":294.69866025,"p95":483.799874165232,"p99":563.3968928920141}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:30+02","p50":280.822649,"p95":428.03382758572747,"p99":464.46219796906854}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:31+02","p50":275.64132825,"p95":446.15891757751916,"p99":507.87054447163007}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:32+02","p50":269.5587385,"p95":430.96664200211677,"p99":669.9642561105194}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:33+02","p50":279.663449125,"p95":438.6697980329419,"p99":520.7928192715287}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:34+02","p50":289.255497,"p95":499.0736842099783,"p99":528.0643940131398}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:35+02","p50":322.683688,"p95":558.1222331505617,"p99":758.9454577473765}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:36+02","p50":295.59765225,"p95":624.6576934951707,"p99":714.7788967299953}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:37+02","p50":261.1673405,"p95":435.0841642586198,"p99":480.58191710951616}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:38+02","p50":276.519091,"p95":478.10282919305,"p99":546.4376535161714}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:39+02","p50":267.70598937499994,"p95":442.08557746862556,"p99":526.6979442846423}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:40+02","p50":278.05879112499997,"p95":459.12646396856746,"p99":562.7794775632623}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:41+02","p50":268.1261875,"p95":480.67277178579377,"p99":526.5582824514285}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:42+02","p50":265.645195625,"p95":445.7156446246986,"p99":535.8363589735332}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:43+02","p50":281.2477945,"p95":449.5835557982304,"p99":515.9120453508282}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:44+02","p50":275.003384375,"p95":514.9206479877232,"p99":594.6198214315237}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:45+02","p50":270.0799885,"p95":454.97443280930185,"p99":524.0589443352184}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:46+02","p50":295.65093625000003,"p95":492.2391704338405,"p99":534.6472352812043}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:47+02","p50":274.08601450000003,"p95":442.37287061170724,"p99":586.8032892054558}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:48+02","p50":374.62705025,"p95":614.2161898959117,"p99":718.6417095456095}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:49+02","p50":384.14167225,"p95":646.9470666795946,"p99":709.5936829303309}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:50+02","p50":307.04816675,"p95":580.9987341745286,"p99":657.1635294630051}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:51+02","p50":264.51252750000003,"p95":405.9214381572137,"p99":485.2863852560253}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:52+02","p50":288.733014625,"p95":436.28569338081195,"p99":499.4273233914039}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:53+02","p50":292.59008775,"p95":444.8126474846289,"p99":559.6404062197221}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:54+02","p50":265.45254250000005,"p95":444.6517892553885,"p99":521.8394724585886}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:55+02","p50":276.110035,"p95":439.42550232806536,"p99":510.11889809915357}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:56+02","p50":269.87857225,"p95":408.3670248879873,"p99":486.81305099183794}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:57+02","p50":274.41081124999994,"p95":439.4784172497368,"p99":487.9560559297927}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:58+02","p50":272.0299305,"p95":483.57158045087084,"p99":564.1303695331484}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:37:59+02","p50":294.5038185,"p95":440.11316648074296,"p99":542.4094273271429}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:00+02","p50":294.01674149999997,"p95":473.3424680057596,"p99":583.2569641643033}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:01+02","p50":295.58635225,"p95":455.02840676387115,"p99":521.1404888461552}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:02+02","p50":303.802941125,"p95":451.2165169351802,"p99":511.0916807555754}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:03+02","p50":286.47689275000005,"p95":479.99778429102633,"p99":567.6860556975946}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:04+02","p50":283.83911750000004,"p95":466.3207577074134,"p99":560.1058221574355}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:05+02","p50":296.90665624999997,"p95":433.7798157127762,"p99":575.7135354705439}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:06+02","p50":270.504869,"p95":483.0571026430154,"p99":556.9352219606143}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:07+02","p50":272.069074625,"p95":457.8241872004946,"p99":521.6077715802451}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:08+02","p50":286.7748935,"p95":514.7952817698724,"p99":680.5006881987887}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:09+02","p50":279.39346625,"p95":460.0734857394414,"p99":528.4559263084283}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:10+02","p50":292.86708899999996,"p95":448.0299446216389,"p99":584.2344832331862}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:11+02","p50":302.73789650000003,"p95":513.5038546738189,"p99":615.2702772098422}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:12+02","p50":278.865654875,"p95":441.64487278980135,"p99":511.978653663738}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:13+02","p50":284.784116125,"p95":457.41361447205577,"p99":533.5180968186232}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:14+02","p50":275.32788000000005,"p95":458.3162684359131,"p99":556.7599309027099}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:15+02","p50":280.3231195,"p95":445.0551637334065,"p99":582.3993081457844}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:16+02","p50":301.43420525,"p95":530.2744790843103,"p99":640.1278627152329}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:17+02","p50":364.4796525,"p95":578.1403585420021,"p99":603.9809204001413}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:18+02","p50":372.29258325,"p95":704.1429628307343,"p99":774.5320789924278}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:19+02","p50":360.78676,"p95":610.7048109200895,"p99":736.5407505721814}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:20+02","p50":314.049614,"p95":613.2237474310322,"p99":762.3890784030613}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:21+02","p50":282.7069275,"p95":475.41358644634107,"p99":535.6855813607083}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:22+02","p50":278.298527,"p95":424.1571578855939,"p99":457.30417616728636}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:23+02","p50":284.983961125,"p95":454.2553327711692,"p99":562.3987605130988}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:24+02","p50":323.141547375,"p95":521.9283128823034,"p99":546.8692202282961}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:25+02","p50":304.54330775,"p95":471.48780923213565,"p99":526.0347048071785}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:26+02","p50":292.379567,"p95":471.84134074121897,"p99":520.0252552257032}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:27+02","p50":299.916064,"p95":462.18045082281066,"p99":526.3080132471199}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:28+02","p50":294.7068685,"p95":454.14565936854245,"p99":562.630079958622}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:29+02","p50":282.64017725,"p95":462.15185026630354,"p99":524.8979255207462}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:30+02","p50":298.0146975,"p95":453.1234867757131,"p99":501.8353833705611}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:31+02","p50":285.517019375,"p95":474.32733556678096,"p99":607.0127939875014}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:32+02","p50":316.60584700000004,"p95":436.0858531417457,"p99":564.3585470786509}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:33+02","p50":276.259842625,"p95":432.824868558483,"p99":535.3429050789955}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:34+02","p50":288.039445875,"p95":457.41460762805696,"p99":526.8870811959748}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:35+02","p50":304.45541275,"p95":433.01749780563546,"p99":529.5591907882152}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:36+02","p50":290.926061375,"p95":469.41253246797226,"p99":567.7128335475919}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:37+02","p50":289.969403,"p95":460.42590394879903,"p99":514.1239983952655}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:38+02","p50":291.094792375,"p95":470.95182639752295,"p99":565.8061726239405}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:39+02","p50":293.434761,"p95":447.9659659214381,"p99":530.5451185746626}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:40+02","p50":282.9847665,"p95":504.0198940813208,"p99":627.297626658765}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:41+02","p50":281.394672,"p95":508.38208074886796,"p99":705.631788158936}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:42+02","p50":283.62036,"p95":456.1519186505919,"p99":527.400940347641}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:43+02","p50":296.907472,"p95":470.9682533727894,"p99":577.844560855461}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:44+02","p50":309.17395650000003,"p95":489.76920722176743,"p99":538.4678099386673}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:45+02","p50":284.83044674999996,"p95":511.9471709640339,"p99":638.2307501275484}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:46+02","p50":324.24727399999995,"p95":514.4866818407793,"p99":653.1810312161302}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:47+02","p50":292.47771575,"p95":460.39423205013384,"p99":495.5933168066232}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:48+02","p50":361.477539,"p95":702.9946877955523,"p99":863.3035797861188}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:49+02","p50":347.163134,"p95":722.0058020472488,"p99":865.4673349531555}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:50+02","p50":330.57022037499996,"p95":703.469716732043,"p99":835.1083508007011}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:51+02","p50":284.812362125,"p95":496.3238719336624,"p99":557.5309844423194}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:52+02","p50":299.3001195,"p95":471.77067894943366,"p99":533.9160366149049}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:53+02","p50":292.06249325,"p95":457.5616723272429,"p99":534.5011652540618}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:54+02","p50":294.1137175,"p95":482.43980061224505,"p99":584.8751213080201}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:55+02","p50":305.75706875,"p95":467.83652219410607,"p99":503.4050448995853}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:56+02","p50":283.83761525,"p95":447.8575097564939,"p99":507.14210297411324}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:57+02","p50":282.3411345,"p95":459.26619966412596,"p99":536.5603360367336}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:58+02","p50":295.537558625,"p95":458.08335727020454,"p99":522.9812536097942}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:38:59+02","p50":294.9872435,"p95":437.5866677396869,"p99":520.258768646837}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:00+02","p50":303.54293925,"p95":446.6625401886058,"p99":533.2071036481738}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:01+02","p50":289.70852399999995,"p95":507.7120067326195,"p99":537.6489259911264}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:02+02","p50":297.58836175,"p95":448.46979712468783,"p99":483.5936854827015}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:03+02","p50":286.61284487499995,"p95":467.224118861816,"p99":645.2514354724825}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:04+02","p50":296.334842125,"p95":473.1896239246984,"p99":604.3109553382325}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:05+02","p50":282.262784625,"p95":500.17499748450706,"p99":592.0214839451652}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:06+02","p50":299.263343125,"p95":450.785239057786,"p99":504.1242538318785}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:07+02","p50":306.72614887500004,"p95":503.68155193797935,"p99":639.3609644677372}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:08+02","p50":388.798586375,"p95":595.2196786744255,"p99":670.3795597631452}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:09+02","p50":313.7536325,"p95":499.0897153087473,"p99":622.1571526460896}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:10+02","p50":292.8736505,"p95":475.12504706742715,"p99":615.6898882612362}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:11+02","p50":287.72190224999997,"p95":470.03727405909814,"p99":521.5958924355562}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:12+02","p50":294.019459,"p95":421.21563264722636,"p99":481.86109114819715}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:13+02","p50":291.92281475,"p95":495.63964192564924,"p99":578.7918773636441}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:14+02","p50":278.930134125,"p95":523.824413685155,"p99":609.3752381564026}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:15+02","p50":279.509395625,"p95":470.93278338554813,"p99":641.5617679089451}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:16+02","p50":335.45656575,"p95":523.4278485714962,"p99":637.7313868931882}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:17+02","p50":313.2136555,"p95":500.6458104606156,"p99":643.7488418568447}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:18+02","p50":411.448944,"p95":659.0515835216574,"p99":692.7601942719373}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:19+02","p50":323.019279,"p95":633.9363027837603,"p99":831.3469673552698}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:20+02","p50":291.513243,"p95":434.4102281724081,"p99":498.8238963223419}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:21+02","p50":278.19095050000004,"p95":499.5846070849953,"p99":589.5221163984547}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:22+02","p50":275.92579249999994,"p95":478.9669818728042,"p99":539.2421971128979}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:23+02","p50":281.12221275,"p95":489.3655113104573,"p99":532.2930449638558}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:24+02","p50":287.6263035,"p95":473.5327284865749,"p99":527.5643380773906}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:25+02","p50":285.844445,"p95":476.5885602038138,"p99":596.8086480897759}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:26+02","p50":276.659429,"p95":483.23488431402643,"p99":553.1055383279042}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:27+02","p50":292.8026555,"p95":437.6286556834668,"p99":562.5607714173016}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:28+02","p50":289.2635385,"p95":468.5254924632532,"p99":495.9771608658907}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:29+02","p50":289.365346125,"p95":486.97665285133246,"p99":566.1052013866753}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:30+02","p50":269.47354875,"p95":444.0457907483142,"p99":522.7294281529155}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:31+02","p50":316.68122225,"p95":492.2670159791336,"p99":599.7145179515344}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:32+02","p50":278.94372112499997,"p95":494.4792191545054,"p99":543.801619090188}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:33+02","p50":290.986736875,"p95":449.1992817243483,"p99":516.4929860083512}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:34+02","p50":301.91486,"p95":445.82400281291024,"p99":522.3193553207207}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:35+02","p50":299.5034515,"p95":476.16217412559655,"p99":523.689367272183}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:36+02","p50":308.474279625,"p95":499.1238591518811,"p99":589.2124832457032}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:37+02","p50":288.27218625,"p95":442.9043426567468,"p99":485.648556105432}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:38+02","p50":302.109373,"p95":486.71924880357255,"p99":530.5639405975213}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:39+02","p50":282.0861065,"p95":455.0125066332538,"p99":529.4192212293186}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:40+02","p50":299.5464095,"p95":492.77070150722494,"p99":573.5600539197319}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:41+02","p50":292.971954,"p95":484.06476146641495,"p99":570.5580891688219}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:42+02","p50":311.76950550000004,"p95":439.17795159897804,"p99":511.91554700526433}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:43+02","p50":297.67031762500005,"p95":458.6554031959731,"p99":538.8457759646666}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:44+02","p50":296.6030455,"p95":496.20232048572876,"p99":567.816771563633}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:45+02","p50":310.283842875,"p95":463.5535547799375,"p99":530.6328237687378}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:46+02","p50":341.0196415,"p95":526.4154583080785,"p99":612.0404280131067}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:47+02","p50":309.946251625,"p95":471.0679451358792,"p99":506.83550367945577}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:48+02","p50":408.892771,"p95":612.1856057077842,"p99":753.6998635432913}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:49+02","p50":375.8138045,"p95":658.1641567728309,"p99":878.4842786047897}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:50+02","p50":324.0173475,"p95":670.7768270410317,"p99":862.9408644143576}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:51+02","p50":297.91106625,"p95":495.0000450699778,"p99":574.8004294732361}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:52+02","p50":305.74394675,"p95":439.0423736705117,"p99":519.7990007475805}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:53+02","p50":312.20286575,"p95":449.5555775160666,"p99":504.76247497174694}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:54+02","p50":294.8860905,"p95":501.68926737833505,"p99":564.6064657294828}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:55+02","p50":310.47490425,"p95":492.3080736637569,"p99":545.570634742797}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:56+02","p50":300.711862,"p95":448.523817298154,"p99":547.4589189224587}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:57+02","p50":285.361698875,"p95":426.74876815992815,"p99":544.319046544888}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:58+02","p50":284.739319,"p95":455.2454605485891,"p99":505.4754467727804}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:39:59+02","p50":303.65188,"p95":428.5325339329714,"p99":492.497304639669}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:00+02","p50":313.89264749999995,"p95":465.8342206242869,"p99":525.137867824834}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:01+02","p50":305.00714975,"p95":486.31293282384075,"p99":553.5618117954156}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:02+02","p50":302.10090625,"p95":441.6590150125473,"p99":504.86669759222843}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:03+02","p50":314.53987037499996,"p95":522.940527460797,"p99":617.7475047280867}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:04+02","p50":327.82806875,"p95":459.01161000943864,"p99":554.916915678565}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:05+02","p50":295.581257625,"p95":509.83931894021174,"p99":571.2683574212518}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:06+02","p50":298.40544324999996,"p95":446.2547257418549,"p99":511.4758402779052}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:07+02","p50":288.66548800000004,"p95":471.302635162027,"p99":517.955740443758}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:08+02","p50":306.576096,"p95":462.53459915288596,"p99":579.3859896210632}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:09+02","p50":303.828008,"p95":488.7604992657844,"p99":614.1007459973416}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:10+02","p50":297.930899875,"p95":437.89464585127587,"p99":598.5329666812044}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:11+02","p50":296.85393650000003,"p95":460.11099566975935,"p99":553.600011615623}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:12+02","p50":292.716156125,"p95":487.6084974831651,"p99":533.3088350333521}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:13+02","p50":309.138551,"p95":521.042852219364,"p99":577.1700352138172}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:14+02","p50":323.65315875,"p95":458.5195512760284,"p99":495.41461625261974}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:15+02","p50":299.84042124999996,"p95":474.80214611007483,"p99":560.7688814104268}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:16+02","p50":323.49443275,"p95":526.8861012911833,"p99":609.8603744477457}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:17+02","p50":353.7937945,"p95":606.0710592379974,"p99":737.9176218182773}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:18+02","p50":399.14815600000003,"p95":635.1591814926941,"p99":802.3005647703493}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:19+02","p50":356.54469274999997,"p95":634.793854917179,"p99":699.0339292064401}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:20+02","p50":297.6499905,"p95":486.16026861651227,"p99":669.111151363678}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:21+02","p50":300.78904025,"p95":472.0395010926522,"p99":587.9472115621968}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:22+02","p50":299.28269774999995,"p95":535.6570898374453,"p99":647.6625711216443}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:23+02","p50":306.28313349999996,"p95":491.04700728000256,"p99":572.0145518629989}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:24+02","p50":297.6418995,"p95":444.35196201995467,"p99":560.9877170154724}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:25+02","p50":306.4591745,"p95":448.0956320624989,"p99":503.77743094976614}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:26+02","p50":307.7786835,"p95":502.2503078251931,"p99":584.5106716337091}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:27+02","p50":307.887486875,"p95":489.97355275501536,"p99":561.8933482998109}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:28+02","p50":296.552464,"p95":492.39513455147284,"p99":575.1306444471833}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:29+02","p50":304.83733962499997,"p95":469.97202904058065,"p99":540.6657381220723}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:30+02","p50":297.06478787500004,"p95":508.65188531755604,"p99":660.0527787129575}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:31+02","p50":332.04694025000003,"p95":486.4280021698817,"p99":596.3989477609406}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:32+02","p50":294.55854,"p95":488.3112866523909,"p99":522.8180566459126}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:33+02","p50":297.38822175,"p95":530.4617622704887,"p99":577.5223368259461}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:34+02","p50":307.84212249999996,"p95":478.93599936241674,"p99":492.83389687346266}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:35+02","p50":303.214311375,"p95":500.39238821960964,"p99":573.1897440080343}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:36+02","p50":308.248709625,"p95":512.7063705251439,"p99":549.0270344249051}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:37+02","p50":319.3844325,"p95":454.44361620906085,"p99":479.01550769645667}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:38+02","p50":309.10320075,"p95":460.1251080360451,"p99":538.551745776207}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:39+02","p50":309.55519400000003,"p95":524.1800892570419,"p99":595.7085964175634}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:40+02","p50":308.913221125,"p95":482.71049663483933,"p99":512.1885086505567}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:41+02","p50":304.82266549999997,"p95":523.9982732200986,"p99":658.6942209644012}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:42+02","p50":309.01759749999997,"p95":482.5976166110712,"p99":539.3997563117584}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:43+02","p50":306.00587162499994,"p95":503.6285551948016,"p99":590.7161858025829}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:44+02","p50":373.21234125,"p95":607.9167637566718,"p99":724.1480606345654}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:45+02","p50":315.50000550000004,"p95":580.1693204951665,"p99":664.2680299818707}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:46+02","p50":345.46472274999996,"p95":572.9140044497159,"p99":629.8515626681366}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:47+02","p50":335.88026549999995,"p95":645.0842873623425,"p99":818.6681102975144}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:48+02","p50":356.88882087499996,"p95":596.6027891325829,"p99":768.5156516738011}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:49+02","p50":380.8114975,"p95":560.6301455998201,"p99":623.0124684732971}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:50+02","p50":391.33717,"p95":709.434419001908,"p99":791.0037296776726}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:51+02","p50":300.99483925000004,"p95":548.6453005997798,"p99":809.444665581554}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:52+02","p50":311.69538,"p95":529.7192219903793,"p99":655.2737100052185}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:53+02","p50":321.52313200000003,"p95":519.3159208392374,"p99":664.8758497415256}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:54+02","p50":297.32266925,"p95":513.4077142465279,"p99":594.4405908971854}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:55+02","p50":299.3202725,"p95":474.77076873040846,"p99":558.6933262662697}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:56+02","p50":316.05039324999996,"p95":507.46733855047296,"p99":651.8103151627821}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:57+02","p50":291.718621875,"p95":484.4998487995039,"p99":567.7552356438165}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:58+02","p50":305.58951912500004,"p95":524.6972196583592,"p99":584.3777284589825}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:40:59+02","p50":312.85441375000005,"p95":455.7878425775516,"p99":557.08559745153}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:00+02","p50":313.15196349999997,"p95":526.8877192496118,"p99":620.5821026369466}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:01+02","p50":329.83175575,"p95":512.0655578011895,"p99":615.1726061007461}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:02+02","p50":308.7025895,"p95":486.3438130353107,"p99":553.8112277463046}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:03+02","p50":311.64479700000004,"p95":505.6599191714747,"p99":638.9092862032937}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:04+02","p50":344.98547987499995,"p95":524.8518197825521,"p99":625.855102418458}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:05+02","p50":338.65577825,"p95":511.973442047192,"p99":619.0625848790436}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:06+02","p50":327.2365685,"p95":532.8625153432456,"p99":624.5471610542664}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:07+02","p50":348.76077075,"p95":551.1159969202657,"p99":711.7168278118286}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:08+02","p50":323.111841,"p95":469.02636642978285,"p99":531.8206015098572}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:09+02","p50":304.785499125,"p95":466.42241931136516,"p99":538.9242017375652}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:10+02","p50":283.58626275,"p95":555.9782627755885,"p99":685.2704095387935}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:11+02","p50":295.619051,"p95":472.8689771328274,"p99":590.1278786215696}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:12+02","p50":298.2463655,"p95":493.30942877872025,"p99":594.2155398430829}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:13+02","p50":297.318209375,"p95":486.9838799174398,"p99":563.6399148381553}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:14+02","p50":333.85226675,"p95":484.20084607643696,"p99":519.5828539244461}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:15+02","p50":303.30325650000003,"p95":487.6196518884309,"p99":643.6907188242235}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:16+02","p50":347.79974225,"p95":577.1969705619294,"p99":631.6691500440741}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:17+02","p50":318.02333775,"p95":456.20864617872104,"p99":541.6072872126236}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:18+02","p50":378.190816,"p95":619.9570623480663,"p99":685.133048977197}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:19+02","p50":419.6789895,"p95":645.2635026278042,"p99":811.0415010838723}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:20+02","p50":330.65390875,"p95":600.125367850236,"p99":667.8689065744419}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:21+02","p50":295.71968325,"p95":535.2554408393985,"p99":638.9240327161928}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:22+02","p50":310.5602875,"p95":490.85670221864615,"p99":594.962364588532}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:23+02","p50":307.4521145,"p95":511.08938277472186,"p99":572.715615489068}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:24+02","p50":293.3413045,"p95":534.2967215712881,"p99":651.0559156541538}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:25+02","p50":318.8742605,"p95":499.16811679777,"p99":624.0814159430123}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:26+02","p50":315.435583125,"p95":494.4907585545782,"p99":583.2955762381969}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:27+02","p50":297.67247999999995,"p95":477.88036936586894,"p99":556.6162288245912}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:28+02","p50":324.94858925,"p95":517.539524587432,"p99":583.698400930685}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:29+02","p50":323.78296075,"p95":517.4314450198067,"p99":612.6719742975177}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:30+02","p50":345.84636675,"p95":566.0622260694211,"p99":694.4989783399391}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:31+02","p50":338.43930275,"p95":540.3704727656424,"p99":647.513178981524}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:32+02","p50":315.49332525,"p95":528.8310196370462,"p99":596.4071484533229}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:33+02","p50":320.25945975,"p95":500.6513166889908,"p99":557.4933477221203}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:34+02","p50":324.80278575,"p95":486.9775078169079,"p99":574.1470536415329}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:35+02","p50":293.403228375,"p95":515.0906835788616,"p99":597.309944086947}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:36+02","p50":285.350331375,"p95":487.1931897997964,"p99":553.8792018729384}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:37+02","p50":309.449604,"p95":515.0356002773511,"p99":549.5819831853958}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:38+02","p50":306.8011365,"p95":445.0728730842158,"p99":482.2868831689527}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:39+02","p50":318.655461,"p95":470.20219925142516,"p99":511.0270397748909}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:40+02","p50":310.432168,"p95":514.7370236046787,"p99":551.264536050488}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:41+02","p50":301.745571,"p95":506.9501718176842,"p99":616.5461299818114}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:42+02","p50":318.40706175,"p95":529.5567735206054,"p99":590.7033028432536}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:43+02","p50":312.969415375,"p95":503.445947123498,"p99":631.2859423757071}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:44+02","p50":322.49209924999997,"p95":522.9672847355642,"p99":647.7381676844205}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:45+02","p50":315.16366874999994,"p95":537.4620491556883,"p99":719.7507715904802}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:46+02","p50":323.68091525,"p95":532.9382123968081,"p99":562.3216370969019}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:47+02","p50":346.68994337500004,"p95":584.8454665628483,"p99":744.5853204668167}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:48+02","p50":386.425078,"p95":784.7564577795508,"p99":888.3893254928813}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:49+02","p50":403.349631,"p95":608.9356836375546,"p99":743.9230100532303}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:50+02","p50":351.079016,"p95":642.5210489758571,"p99":770.4706950490704}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:51+02","p50":302.89721,"p95":496.89994052726433,"p99":525.067742970924}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:52+02","p50":322.574773625,"p95":514.2510070523305,"p99":652.4840814783556}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:53+02","p50":337.6034145,"p95":467.2438498147283,"p99":528.269330598588}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:54+02","p50":318.559557625,"p95":457.28120237560074,"p99":574.6904906653624}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:55+02","p50":303.70893375,"p95":549.8322872879548,"p99":628.297075645979}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:56+02","p50":311.8553755,"p95":454.46989390990416,"p99":569.136391301688}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:57+02","p50":320.513333,"p95":515.0829108275394,"p99":575.8809617809062}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:58+02","p50":326.8126315,"p95":470.78010936192743,"p99":505.53433785863115}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:41:59+02","p50":312.40408525,"p95":509.3496198810025,"p99":633.0293030510302}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:00+02","p50":323.30809425,"p95":484.1047928735289,"p99":616.0170981187821}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:01+02","p50":336.66561175000004,"p95":509.280181792151,"p99":561.3578553932957}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:02+02","p50":316.45768325,"p95":528.5621460147039,"p99":586.6443262122751}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:03+02","p50":322.49223674999996,"p95":514.7743987051591,"p99":579.0984374669387}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:04+02","p50":323.482571375,"p95":548.3249511110104,"p99":605.0175722270608}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:05+02","p50":299.96462725000004,"p95":490.0013817073374,"p99":580.3914451528213}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:06+02","p50":334.218077,"p95":503.7678927114556,"p99":565.7659849495211}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:07+02","p50":302.65574200000003,"p95":534.2894679439306,"p99":573.4125380651896}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:08+02","p50":329.6782675,"p95":507.9696080238378,"p99":586.1021280679006}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:09+02","p50":297.7699725,"p95":510.34910517110586,"p99":579.692875771944}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:10+02","p50":316.15952975,"p95":487.4539040628368,"p99":557.6102038947726}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:11+02","p50":341.07417675,"p95":541.9830433137706,"p99":603.3066621361351}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:12+02","p50":330.58212175,"p95":474.92338477380133,"p99":572.1673446765309}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:13+02","p50":331.43706525,"p95":542.5854363821318,"p99":600.7207056061845}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:14+02","p50":308.8410665,"p95":517.7612954149139,"p99":618.871893006094}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:15+02","p50":309.9929055,"p95":541.2307780276724,"p99":671.8582049587717}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:16+02","p50":338.404977625,"p95":545.8150527256497,"p99":614.7325576396663}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:17+02","p50":348.29992825,"p95":507.2583596070805,"p99":565.1056478395903}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:18+02","p50":420.027892,"p95":625.1432876739595,"p99":776.8548635069598}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:19+02","p50":416.82770400000004,"p95":654.5306135843845,"p99":740.6236414797163}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:20+02","p50":330.53776112500003,"p95":581.1028939177265,"p99":653.1910018763497}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:21+02","p50":369.1526625,"p95":596.8604122721432,"p99":700.4589788419195}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:22+02","p50":345.38576450000005,"p95":535.6493107181265,"p99":606.224765239501}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:23+02","p50":309.920321,"p95":487.89770756828415,"p99":603.1206214935105}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:24+02","p50":324.47928575000003,"p95":508.20170975121056,"p99":666.8904040487128}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:25+02","p50":349.18265337500003,"p95":479.5071976475417,"p99":505.8866622045445}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:26+02","p50":330.54887562499994,"p95":450.7554980978613,"p99":489.7120110605159}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:27+02","p50":343.730371875,"p95":493.7631183877071,"p99":555.04478314539}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:28+02","p50":337.99702012500006,"p95":510.4028049346196,"p99":629.3045883811124}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:29+02","p50":339.243583,"p95":537.2696932108249,"p99":587.8088385430145}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:30+02","p50":315.843077,"p95":470.7670652149177,"p99":571.4889124666214}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:31+02","p50":350.03122237499997,"p95":522.2099784214005,"p99":589.0143701041894}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:32+02","p50":326.924258375,"p95":509.3178959582295,"p99":564.8230784894492}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:33+02","p50":316.035223,"p95":522.067869312798,"p99":592.4449681284857}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:34+02","p50":321.66661899999997,"p95":694.2694242244008,"p99":804.5431043952805}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:35+02","p50":308.22360349999997,"p95":514.3069604655512,"p99":595.8185673666338}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:36+02","p50":330.61765,"p95":533.8932072991197,"p99":647.332133349102}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:37+02","p50":350.51526249999995,"p95":511.5237501097942,"p99":582.5538779358175}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:38+02","p50":305.87990575000003,"p95":473.1666702928462,"p99":582.0423681405124}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:39+02","p50":325.0968145,"p95":524.3519463490534,"p99":548.2684647625961}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:40+02","p50":313.85357999999997,"p95":508.5812824218935,"p99":600.2153679181232}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:41+02","p50":309.533420125,"p95":488.89638473662484,"p99":630.9097560657021}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:42+02","p50":336.22685449999994,"p95":468.71740999862425,"p99":548.4321008022217}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:43+02","p50":323.4750845,"p95":510.9212389961422,"p99":587.6782060044155}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:44+02","p50":316.91954875,"p95":528.9628326702832,"p99":679.8281462052851}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:45+02","p50":308.04483700000003,"p95":482.90176352845396,"p99":564.8573764919057}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:46+02","p50":356.40417025,"p95":491.9380949132031,"p99":581.6491912525258}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:47+02","p50":330.98668475,"p95":563.7884276829878,"p99":661.9602661078023}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:48+02","p50":418.76185,"p95":715.4987217449041,"p99":841.5310871147861}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:49+02","p50":445.64414,"p95":770.0418201068027,"p99":906.1397841380792}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:50+02","p50":379.91148699999997,"p95":650.2271047559034,"p99":757.348003292974}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:51+02","p50":334.6581065,"p95":549.8081047190972,"p99":651.5491413281812}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:52+02","p50":332.70984875,"p95":521.9127705617464,"p99":598.6372660789403}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:53+02","p50":333.0945265,"p95":529.5789459835682,"p99":618.296226774334}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:54+02","p50":324.59780025,"p95":556.8821676770366,"p99":688.2358825259287}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:55+02","p50":340.29491575000003,"p95":509.0787693624846,"p99":617.5421699590264}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:56+02","p50":327.57181325,"p95":545.5759211806184,"p99":595.4499519324455}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:57+02","p50":387.08001774999997,"p95":589.778122813007,"p99":710.7826134849253}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:58+02","p50":349.606655875,"p95":564.0891520012915,"p99":664.5532803822157}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:42:59+02","p50":331.03212662500005,"p95":489.86048063179726,"p99":558.7947575490754}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:00+02","p50":321.2447595,"p95":468.42394510229684,"p99":534.6001989460735}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:01+02","p50":328.57371725,"p95":505.8325801870539,"p99":588.3147489771061}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:02+02","p50":324.208074,"p95":539.6714646978564,"p99":633.4924001450978}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:03+02","p50":328.3624995,"p95":504.8007488905276,"p99":577.6911164909186}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:04+02","p50":342.49406200000004,"p95":556.0203131826987,"p99":658.8486427469214}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:05+02","p50":326.3104575,"p95":550.9121454604501,"p99":649.8969540989366}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:06+02","p50":344.91071875,"p95":509.65463865328184,"p99":568.3216354299843}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:07+02","p50":318.521784875,"p95":511.2065145025327,"p99":591.3988925365489}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:08+02","p50":312.596453375,"p95":507.95963390923663,"p99":590.5658738199346}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:09+02","p50":338.92585825000003,"p95":533.7577879749393,"p99":576.3004978027649}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:10+02","p50":336.47683625,"p95":562.383729663859,"p99":599.8609079838258}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:11+02","p50":327.113832625,"p95":517.527400115967,"p99":578.5028320652666}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:12+02","p50":327.3078845,"p95":481.74701875358863,"p99":522.8601290657116}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:13+02","p50":354.90136375,"p95":572.4460333586337,"p99":640.893094044488}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:14+02","p50":330.89827925,"p95":564.9274914541887,"p99":652.2808648848171}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:15+02","p50":347.57848225,"p95":540.4815923843818,"p99":651.1613865393839}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:16+02","p50":340.834904,"p95":569.4828697374805,"p99":692.0377972832339}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:17+02","p50":358.112802,"p95":626.5573374826258,"p99":648.2736702828009}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:18+02","p50":440.42444950000004,"p95":650.9863843936222,"p99":782.4971167358399}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:19+02","p50":413.64021149999996,"p95":747.1482057006587,"p99":917.8008568120785}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:20+02","p50":318.55529125,"p95":587.0976869933598,"p99":650.5547205419783}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:21+02","p50":327.19383750000003,"p95":467.3512144772246,"p99":513.6789322461367}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:22+02","p50":331.60111799999993,"p95":483.6494313948022,"p99":542.9437926127504}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:23+02","p50":340.103423625,"p95":547.2012210528068,"p99":583.4993008570337}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:24+02","p50":349.34509975,"p95":508.2810611659913,"p99":542.3164453763083}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:25+02","p50":342.29770725000003,"p95":541.0706412453068,"p99":578.5239365249439}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:26+02","p50":327.26271862500005,"p95":519.156284634458,"p99":591.2775145309606}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:27+02","p50":321.31289200000003,"p95":550.4422337083914,"p99":727.8195606816721}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:28+02","p50":335.58940375,"p95":526.3931017083185,"p99":624.7924379633237}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:29+02","p50":346.36725175000004,"p95":539.4061676581839,"p99":625.2804323605232}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:30+02","p50":336.19097550000004,"p95":551.3631965409066,"p99":634.8267021399197}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:31+02","p50":342.493862125,"p95":542.1946994635236,"p99":608.1275651678474}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:32+02","p50":343.356515,"p95":543.9354308191412,"p99":585.5432913191397}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:33+02","p50":322.77636037499997,"p95":546.2677592612876,"p99":604.0025447862323}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:34+02","p50":346.44739775,"p95":573.6535181222276,"p99":644.8819079760972}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:35+02","p50":334.605818,"p95":558.9967375222055,"p99":641.1136848389373}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:36+02","p50":314.65849875000004,"p95":486.85783002175623,"p99":506.13328598721125}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:37+02","p50":313.059019375,"p95":513.1926294755925,"p99":574.490563398947}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:38+02","p50":356.152302,"p95":629.7512799691596,"p99":712.5001832770805}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:39+02","p50":350.729707625,"p95":490.0353317711048,"p99":541.0986881269837}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:40+02","p50":321.2167895,"p95":564.3305929822646,"p99":632.0842256974883}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:41+02","p50":315.42502625,"p95":463.52723249894916,"p99":598.9443610534997}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:42+02","p50":322.43123075,"p95":522.2043013386689,"p99":626.181152649395}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:43+02","p50":327.319726,"p95":511.11810669396976,"p99":564.4221545269623}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:44+02","p50":350.81368125,"p95":546.9990960221184,"p99":657.1244605480948}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:45+02","p50":337.696921,"p95":529.0149504819892,"p99":613.8735210176029}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:46+02","p50":357.2893755,"p95":563.1612284092183,"p99":657.4573452784075}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:47+02","p50":339.6019,"p95":582.7254937191329,"p99":629.5898235055847}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:48+02","p50":402.135537,"p95":711.9188947588846,"p99":892.3447114332228}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:49+02","p50":441.45477600000004,"p95":733.9246420231929,"p99":935.1766653473816}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:50+02","p50":406.062491,"p95":760.0104188199185,"p99":834.7430915743317}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:51+02","p50":320.406151875,"p95":543.5071282592572,"p99":576.6741897093175}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:52+02","p50":349.87627725,"p95":514.9634185391819,"p99":609.1754714589429}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:53+02","p50":377.359371625,"p95":598.4808368906163,"p99":675.7592210577669}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:54+02","p50":336.052020625,"p95":569.2151762421723,"p99":683.7743174141632}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:55+02","p50":330.08588075,"p95":529.6835019443193,"p99":641.9929328891768}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:56+02","p50":303.15323575,"p95":510.21732344072774,"p99":681.9766942057275}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:57+02","p50":339.1372965,"p95":509.0658947488823,"p99":591.6627659989495}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:58+02","p50":319.83823225000003,"p95":544.7309274728975,"p99":664.1933552479275}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:43:59+02","p50":351.64510112500005,"p95":528.0506835276024,"p99":668.6147146735203}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:00+02","p50":348.252881625,"p95":543.181292921331,"p99":633.5930048063681}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:01+02","p50":331.607667,"p95":539.2300831935438,"p99":631.455975505971}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:02+02","p50":324.649678625,"p95":544.1680615725603,"p99":677.5085549666525}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:03+02","p50":315.25527325,"p95":555.5250743470641,"p99":609.622951829411}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:04+02","p50":365.672411,"p95":543.0288468875787,"p99":652.3411585622215}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:05+02","p50":342.568414,"p95":512.793715325577,"p99":618.4076284013751}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:06+02","p50":333.78929875,"p95":509.0509641225709,"p99":583.3571742093377}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:07+02","p50":309.444478,"p95":504.94748119617464,"p99":584.7733703681555}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:08+02","p50":358.30726975,"p95":555.707903055953,"p99":639.9491676502342}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:09+02","p50":343.423042125,"p95":528.4406846797634,"p99":650.2524399178006}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:10+02","p50":344.6283565,"p95":517.3996959602098,"p99":584.3293029321632}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:11+02","p50":360.36645450000003,"p95":554.0392444659653,"p99":625.9818996899108}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:12+02","p50":366.55893175,"p95":567.4357889148151,"p99":626.7209425291443}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:13+02","p50":346.86440675,"p95":543.6496711809788,"p99":642.096952304966}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:14+02","p50":353.39149075,"p95":577.5596644226237,"p99":643.319340748392}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:15+02","p50":341.09941225,"p95":488.57378077602004,"p99":554.1846986540642}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:16+02","p50":363.713310375,"p95":573.9259659905728,"p99":694.3959354154669}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:17+02","p50":428.348118,"p95":620.7357997010351,"p99":719.3941255517722}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:18+02","p50":400.901007,"p95":755.4515822323385,"p99":820.3866738953035}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:19+02","p50":371.872225,"p95":672.2152944671152,"p99":771.9139738783016}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:20+02","p50":336.51824462499997,"p95":537.1944035475506,"p99":594.6638948443408}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:21+02","p50":322.795362625,"p95":507.3882789071787,"p99":549.3647344492305}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:22+02","p50":325.145735,"p95":503.24908884989725,"p99":611.0339718718205}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:23+02","p50":342.52409925,"p95":605.2133816231802,"p99":743.5402453059545}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:24+02","p50":326.113772375,"p95":546.3615107799446,"p99":633.9234585743316}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:25+02","p50":358.885657625,"p95":605.5025837244076,"p99":672.8392904294071}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:26+02","p50":335.03479175,"p95":507.94215605371664,"p99":539.8503027720566}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:27+02","p50":320.792308,"p95":502.848236345376,"p99":565.9427686338806}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:28+02","p50":340.089294,"p95":567.740797113977,"p99":625.8843573938685}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:29+02","p50":330.62131000000005,"p95":503.91396347586146,"p99":651.4610301186677}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:30+02","p50":313.853461875,"p95":532.662647136313,"p99":635.7935862359376}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:31+02","p50":352.82864575,"p95":583.297518264915,"p99":672.5121789563389}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:32+02","p50":364.65018524999994,"p95":585.8284566888414,"p99":642.1417981324215}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:33+02","p50":366.73125949999996,"p95":608.3559323920485,"p99":725.2928327889746}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:34+02","p50":346.7205105,"p95":564.2498423319068,"p99":631.0187985758477}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:35+02","p50":357.026326625,"p95":643.3134047584376,"p99":691.3110414690401}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:36+02","p50":338.037225625,"p95":587.7517571396664,"p99":666.2727045889056}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:37+02","p50":332.331228875,"p95":557.5391620648119,"p99":733.9624281525951}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:38+02","p50":348.61139025,"p95":538.1252050438933,"p99":623.5037710384449}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:39+02","p50":350.319776875,"p95":548.902283918745,"p99":582.9400503304043}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:40+02","p50":324.78724625,"p95":468.23032480021186,"p99":590.6577618388824}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:41+02","p50":338.604237625,"p95":496.69199885883165,"p99":580.3399563173156}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:42+02","p50":338.38548525,"p95":514.1438362161766,"p99":585.1993780636466}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:43+02","p50":332.84213387499994,"p95":596.9369607276868,"p99":693.003808062556}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:44+02","p50":328.30492449999997,"p95":553.5345307069192,"p99":613.0522008717575}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:45+02","p50":355.51230275,"p95":536.2873064674928,"p99":629.3372348191824}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:46+02","p50":362.88431024999994,"p95":662.7800251113944,"p99":740.9045094218789}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:47+02","p50":363.03878275,"p95":624.5005102746338,"p99":702.4394397960845}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:48+02","p50":452.140451,"p95":641.6064560464545,"p99":808.9536519214856}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:49+02","p50":460.25323149999997,"p95":684.3763280765312,"p99":787.8054245690737}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:50+02","p50":360.07355974999996,"p95":576.2068855519274,"p99":673.0585240240574}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:51+02","p50":339.56782,"p95":556.4122995985499,"p99":584.4511258181686}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:52+02","p50":340.96431025,"p95":542.1747362165277,"p99":694.0009480606399}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:53+02","p50":346.59112725,"p95":545.775912490787,"p99":592.8181228979234}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:54+02","p50":345.29219962499997,"p95":574.982256025082,"p99":638.1739965908649}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:55+02","p50":339.69288850000004,"p95":536.0492649755479,"p99":587.7214661975629}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:56+02","p50":354.87745574999997,"p95":589.7209300949339,"p99":648.512332062253}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:57+02","p50":399.225588,"p95":606.0997370246735,"p99":762.3074757153626}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:58+02","p50":361.609880375,"p95":587.3312141333265,"p99":649.0144829675403}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:44:59+02","p50":375.4963745,"p95":597.3958477720232,"p99":658.8760655571076}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:00+02","p50":324.150401375,"p95":560.6063027899701,"p99":635.3311170758727}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:01+02","p50":353.793433125,"p95":585.4320197072103,"p99":625.3968822394033}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:02+02","p50":337.484696,"p95":510.8684445387955,"p99":545.4295805129166}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:03+02","p50":347.23752025,"p95":543.4740464520517,"p99":596.8581490632382}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:04+02","p50":346.26664337500006,"p95":558.1099372979429,"p99":737.0618150952544}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:05+02","p50":328.94622749999996,"p95":497.8070499913093,"p99":551.2574102764773}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:06+02","p50":339.4385945,"p95":549.4102414865409,"p99":590.4435919302292}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:07+02","p50":363.29308737499997,"p95":549.8564751976868,"p99":594.4757302067794}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:08+02","p50":333.527347,"p95":602.2613198320062,"p99":668.4757034237766}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:09+02","p50":350.487534,"p95":553.9185693652901,"p99":647.0859333514304}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:10+02","p50":357.864382,"p95":541.1163411903921,"p99":593.630792498826}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:11+02","p50":342.684916,"p95":535.7652182280488,"p99":579.419762448008}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:12+02","p50":366.39387325,"p95":537.5041689847051,"p99":643.3912362304791}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:13+02","p50":350.523786,"p95":523.1297766173515,"p99":606.7601464649315}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:14+02","p50":359.49665175,"p95":552.57607071309,"p99":626.9760799493027}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:15+02","p50":349.344872,"p95":546.5477102665989,"p99":590.8390068376393}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:16+02","p50":376.904857,"p95":676.5819791843779,"p99":819.2726504091942}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:17+02","p50":364.3164845,"p95":633.4672356310823,"p99":772.715747096447}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:18+02","p50":450.038067,"p95":702.4808938677854,"p99":871.8367253081093}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:19+02","p50":410.343016,"p95":643.1498567238561,"p99":759.7305919120329}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:20+02","p50":346.97637737499997,"p95":501.28509446006524,"p99":640.3220917080525}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:21+02","p50":332.378516,"p95":536.7870575605347,"p99":622.6890286930113}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:22+02","p50":335.417158375,"p95":499.1350125774557,"p99":676.754268993287}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:23+02","p50":359.990161625,"p95":595.0249133781737,"p99":706.7902989884295}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:24+02","p50":349.976341375,"p95":517.5214534847408,"p99":602.1449828580975}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:25+02","p50":345.627530375,"p95":537.8296353127979,"p99":614.846616106573}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:26+02","p50":399.3272875,"p95":596.5065870531112,"p99":734.8957623056144}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:27+02","p50":364.2523795,"p95":553.1759960812092,"p99":644.5089362873192}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:28+02","p50":336.62618837499997,"p95":514.3392470972312,"p99":564.6238786615079}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:29+02","p50":358.803955,"p95":551.1398884214469,"p99":598.8946582802253}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:30+02","p50":334.55181775,"p95":510.92113687125885,"p99":557.5052702214638}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:31+02","p50":353.88141775,"p95":520.9150071872731,"p99":587.7909470786}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:32+02","p50":342.56084062499997,"p95":515.0521171364501,"p99":601.0929402814107}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:33+02","p50":324.0906635,"p95":523.2055296571111,"p99":657.5371172251472}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:34+02","p50":347.01409925,"p95":540.2876733650646,"p99":585.7058553339463}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:35+02","p50":327.12255100000004,"p95":521.5998255642677,"p99":610.0326364807672}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:36+02","p50":371.173282625,"p95":523.8652652994544,"p99":600.0573835936073}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:37+02","p50":363.48669525,"p95":580.3229243209925,"p99":640.8220176608982}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:38+02","p50":348.821429625,"p95":503.57228163743963,"p99":551.6865038716606}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:39+02","p50":327.1297095,"p95":533.3160169962772,"p99":603.5995423386149}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:40+02","p50":347.57051150000007,"p95":515.7508554867925,"p99":624.7439295672657}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:41+02","p50":348.17471850000004,"p95":503.79439140520384,"p99":526.4679940250745}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:42+02","p50":342.02924125,"p95":540.9323713551387,"p99":663.7699225116005}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:43+02","p50":346.85442975,"p95":573.970615412692,"p99":671.3220911510086}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:44+02","p50":348.94339,"p95":553.8250227548432,"p99":608.9172629492889}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:45+02","p50":362.167644,"p95":572.6568605889731,"p99":642.5339761294265}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:46+02","p50":449.165789,"p95":649.6537892811198,"p99":806.4412916986909}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:47+02","p50":412.744774,"p95":722.7844726881544,"p99":813.37351721904}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:48+02","p50":442.513619,"p95":695.6580571621427,"p99":764.0540100782699}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:49+02","p50":413.373996,"p95":742.3946704845505,"p99":898.4970946870388}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:50+02","p50":401.431331,"p95":806.9983492125108,"p99":1089.5320209498882}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:51+02","p50":327.89469187500003,"p95":578.4287207270136,"p99":657.2523128917635}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:52+02","p50":337.102387,"p95":576.2936623412663,"p99":660.1718600363254}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:53+02","p50":361.21950712499995,"p95":569.2638940045007,"p99":699.6975276886852}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:54+02","p50":331.06800899999996,"p95":524.4758514649495,"p99":558.5047467185478}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:55+02","p50":364.307729,"p95":546.9215105486043,"p99":609.24503898455}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:56+02","p50":338.17483325,"p95":537.529060036429,"p99":596.3516635225019}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:57+02","p50":335.948761,"p95":524.5735766707137,"p99":649.8923334592514}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:58+02","p50":345.1245805,"p95":551.4527573691249,"p99":689.5070633250904}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:45:59+02","p50":340.35369575,"p95":574.0492728977716,"p99":734.3485056186771}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:00+02","p50":326.01020625,"p95":534.3457177984557,"p99":642.8531181339529}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:01+02","p50":349.556566,"p95":518.3864366541636,"p99":694.0963477342606}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:02+02","p50":360.87075749999997,"p95":579.7477344035956,"p99":680.0530456225418}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:03+02","p50":357.371667,"p95":533.109212540358,"p99":578.6317545747489}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:04+02","p50":341.749813625,"p95":573.3093022196327,"p99":644.7616532399826}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:05+02","p50":320.6170245,"p95":535.0045539217391,"p99":574.1511910854016}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:06+02","p50":349.35370550000005,"p95":615.5754129626853,"p99":664.3451944788022}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:07+02","p50":342.0677505,"p95":500.23475022103133,"p99":546.5653979372277}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:08+02","p50":342.98735500000004,"p95":570.6678997224845,"p99":616.7477293901939}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:09+02","p50":347.29162375,"p95":541.7223988069749,"p99":712.2159962368186}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:10+02","p50":365.035491125,"p95":540.6129649454722,"p99":637.7218718838925}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:11+02","p50":350.03384875,"p95":575.908056891989,"p99":631.3995757880421}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:12+02","p50":338.81277525,"p95":560.0984794637244,"p99":775.6743723776826}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:13+02","p50":347.71150775,"p95":551.2569839859333,"p99":604.849165405223}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:14+02","p50":374.16590175,"p95":548.4767495970355,"p99":572.9626778943658}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:15+02","p50":337.250061625,"p95":506.517735715367,"p99":544.5336736389237}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:16+02","p50":374.160351,"p95":599.6476712737378,"p99":651.4095189924625}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:17+02","p50":391.4443225,"p95":574.6293131787165,"p99":675.6484147643184}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:18+02","p50":422.722661,"p95":801.8904405198309,"p99":863.7027500875104}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:19+02","p50":426.4683285,"p95":723.6471293044243,"p99":884.0684621323076}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:20+02","p50":345.12625475,"p95":583.8862624139952,"p99":661.0541674827142}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:21+02","p50":345.53076375,"p95":536.2967791234223,"p99":637.1758599501028}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:22+02","p50":379.48715925,"p95":564.6169836967816,"p99":652.4221536137562}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:23+02","p50":376.84858725000004,"p95":520.5663124032963,"p99":668.127548019948}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:24+02","p50":351.26797224999996,"p95":544.0758982710404,"p99":647.1024968332835}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:25+02","p50":360.2830105,"p95":575.9003097748199,"p99":664.8352036892582}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:26+02","p50":340.13485075,"p95":559.0044568046784,"p99":618.4696541508598}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:27+02","p50":356.90814687500006,"p95":539.6683896089304,"p99":588.990574314398}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:28+02","p50":364.363302,"p95":557.9573939390931,"p99":714.4486014077387}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:29+02","p50":369.2119467499999,"p95":559.8195483858964,"p99":605.4333882320802}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:30+02","p50":353.59453824999997,"p95":540.943501826767,"p99":604.0178129430085}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:31+02","p50":341.64630150000005,"p95":598.2149039603522,"p99":688.8407177724839}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:32+02","p50":363.850076875,"p95":548.4968659399699,"p99":624.3300117749095}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:33+02","p50":320.93730625,"p95":565.3146409970316,"p99":671.0347354925132}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:34+02","p50":370.12893325,"p95":585.4740319925847,"p99":650.0584546206627}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:35+02","p50":361.22903125,"p95":537.0443487406942,"p99":618.8847951454678}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:36+02","p50":382.79561574999997,"p95":522.8193759887354,"p99":591.3114227743597}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:37+02","p50":345.733393125,"p95":641.1866238535613,"p99":699.9755659561661}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:38+02","p50":353.763625875,"p95":547.664618671233,"p99":623.113616392451}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:39+02","p50":367.84983475,"p95":538.0287704812815,"p99":603.2834892632761}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:40+02","p50":329.19345899999996,"p95":517.658193310068,"p99":596.2428623315711}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:41+02","p50":378.69003875,"p95":557.4398456603016,"p99":633.9591832523568}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:42+02","p50":348.52142287500004,"p95":519.0831945842875,"p99":561.6199110145434}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:43+02","p50":354.59012375,"p95":525.9576285781751,"p99":665.4675615167932}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:44+02","p50":381.149179,"p95":570.1894354293403,"p99":674.8120213161407}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:45+02","p50":376.36328249999997,"p95":534.1634561133704,"p99":607.3743401134548}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:46+02","p50":375.87968712500003,"p95":607.4298005219961,"p99":754.2779052279494}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:47+02","p50":367.839692,"p95":614.6653641036158,"p99":684.1563988919105}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:48+02","p50":470.60785,"p95":705.6385812547643,"p99":841.9683077408989}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:49+02","p50":461.235327,"p95":805.8623452664671,"p99":896.7389074969918}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:50+02","p50":410.10131075000004,"p95":685.3478531655312,"p99":824.0622178336725}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:51+02","p50":327.795443,"p95":547.0594844745756,"p99":619.2649680240106}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:52+02","p50":354.61930975,"p95":558.4728557765573,"p99":636.97769057809}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:53+02","p50":345.214233375,"p95":527.2921589568558,"p99":558.6288495968028}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:54+02","p50":350.352511375,"p95":503.8315707582582,"p99":609.9543784524154}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:55+02","p50":363.90394137500004,"p95":572.6521485627101,"p99":665.6983828493146}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:56+02","p50":365.492157,"p95":523.3605533984289,"p99":571.8051118889975}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:57+02","p50":371.169569,"p95":523.2952542524438,"p99":571.2109034612259}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:58+02","p50":436.911162,"p95":638.304762745309,"p99":709.4670206928821}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:46:59+02","p50":411.18073,"p95":612.3270736110799,"p99":715.6366150032653}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:00+02","p50":352.9105875,"p95":540.8583767316579,"p99":621.0581579829368}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:01+02","p50":376.91366800000003,"p95":608.9526297512717,"p99":633.7185279041666}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:02+02","p50":374.34576412499996,"p95":595.1244913522681,"p99":627.2598151529543}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:03+02","p50":356.11296812500007,"p95":534.0915329685939,"p99":612.1283232763257}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:04+02","p50":373.62495075000004,"p95":571.1913684215087,"p99":599.2966416379137}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:05+02","p50":349.42042949999995,"p95":629.4699964729137,"p99":722.7621205956013}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:06+02","p50":376.271132,"p95":591.6972141875145,"p99":688.8064591543424}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:07+02","p50":363.15761062499996,"p95":580.7751883201435,"p99":687.49893791833}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:08+02","p50":359.12589075000005,"p95":627.3962238782893,"p99":771.416657282438}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:09+02","p50":331.537497875,"p95":552.8859099029235,"p99":667.0232644884268}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:10+02","p50":344.60761149999996,"p95":670.7708161270356,"p99":732.9215999619885}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:11+02","p50":329.5246875,"p95":582.709590824389,"p99":722.9900399842286}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:12+02","p50":365.487416,"p95":560.7896041474411,"p99":621.9079438566717}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:13+02","p50":366.85857175,"p95":565.3370858114414,"p99":654.078266817527}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:14+02","p50":376.624474125,"p95":600.2568704768521,"p99":654.9918930514531}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:15+02","p50":385.75254862500003,"p95":578.4678990606368,"p99":641.6020224810624}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:16+02","p50":366.97081049999997,"p95":647.33773638466,"p99":743.7291821785717}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:17+02","p50":393.7892745,"p95":618.3646556576285,"p99":698.0266578596397}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:18+02","p50":431.89287,"p95":756.3606272760418,"p99":857.3252262805039}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:19+02","p50":502.68915549999997,"p95":795.9049426001947,"p99":934.9490622697764}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:20+02","p50":420.67546100000004,"p95":674.8818829643311,"p99":793.2769937691173}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:21+02","p50":355.45911025,"p95":563.6235681177752,"p99":665.7891341768741}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:22+02","p50":351.042864,"p95":550.1976598919637,"p99":624.438729203361}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:23+02","p50":336.83872825,"p95":535.7430034556264,"p99":700.4418950208094}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:24+02","p50":382.423903,"p95":595.7934666937183,"p99":652.1804639763667}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:25+02","p50":356.95381125,"p95":588.686524896492,"p99":675.3769710497913}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:26+02","p50":342.142293375,"p95":527.0026947642408,"p99":658.6139797398522}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:27+02","p50":351.54080849999997,"p95":598.2845428583211,"p99":640.1780923918863}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:28+02","p50":351.10330275,"p95":568.0761515172472,"p99":618.3176152456906}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:29+02","p50":382.7587185,"p95":602.253188061808,"p99":655.8339036481781}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:30+02","p50":356.3262155,"p95":576.6441496147552,"p99":662.9474969482193}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:31+02","p50":377.2490755,"p95":546.042730181945,"p99":592.7406988252216}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:32+02","p50":366.4939325,"p95":575.1407336788749,"p99":658.0646213520932}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:33+02","p50":334.68827450000003,"p95":550.26795746339,"p99":610.1698392888622}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:34+02","p50":368.598075,"p95":605.5791265990532,"p99":672.2396189265404}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:35+02","p50":362.02651275,"p95":519.6200709826431,"p99":580.9427359890051}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:36+02","p50":367.76984849999997,"p95":587.425418991338,"p99":638.4740805902625}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:37+02","p50":360.245454375,"p95":608.7418731459536,"p99":653.1512138291268}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:38+02","p50":406.948706,"p95":607.1203332223666,"p99":640.8209670412433}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:39+02","p50":391.03764775,"p95":548.5080014495067,"p99":656.0194364837594}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:40+02","p50":407.506433,"p95":641.3765128161691,"p99":693.6325246361919}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:41+02","p50":392.0926725,"p95":606.018242698949,"p99":677.4727272443628}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:42+02","p50":380.680381,"p95":580.4023302434218,"p99":685.8592293568843}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:43+02","p50":371.282735,"p95":577.8334548774365,"p99":667.6755771671204}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:44+02","p50":381.125060125,"p95":600.6462395270736,"p99":728.9498561144115}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:45+02","p50":364.24978,"p95":546.2749676986276,"p99":615.2926799143088}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:46+02","p50":394.563987875,"p95":589.5063062651146,"p99":652.27759246698}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:47+02","p50":398.128657,"p95":620.3325964756989,"p99":708.7442627269784}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:48+02","p50":489.1820475,"p95":720.4108372897692,"p99":886.7874097963199}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:49+02","p50":445.4518175,"p95":728.9021142257258,"p99":949.7441621377039}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:50+02","p50":480.368007,"p95":793.0226181554071,"p99":971.231016473387}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:51+02","p50":370.2256745,"p95":524.9930269471047,"p99":632.8595963865823}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:52+02","p50":382.141342,"p95":577.2822490433972,"p99":682.8809792750768}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:53+02","p50":351.329848,"p95":582.114161490897,"p99":671.9360361640039}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:54+02","p50":366.29761499999995,"p95":565.6847946252662,"p99":673.3247704165912}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:55+02","p50":360.08347075,"p95":608.0463271750035,"p99":709.0654027889042}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:56+02","p50":387.609815,"p95":597.6089246492459,"p99":687.7932023599882}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:57+02","p50":378.806582,"p95":563.3652127297133,"p99":627.0161513699355}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:58+02","p50":367.4258025,"p95":553.874677023038,"p99":702.2397284850064}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:47:59+02","p50":385.534227,"p95":567.5638756617304,"p99":658.5302650030661}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:00+02","p50":358.108514375,"p95":592.2173371127257,"p99":647.7260890681663}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:01+02","p50":372.657969,"p95":650.653177755713,"p99":722.1162193479786}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:02+02","p50":374.977187,"p95":561.7995290611792,"p99":638.3017917092704}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:03+02","p50":384.11512424999995,"p95":554.6637274162941,"p99":596.0777641940341}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:04+02","p50":345.30697775000004,"p95":647.6057250044437,"p99":733.7630055309344}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:05+02","p50":371.919690375,"p95":538.4030222033645,"p99":666.8601673203542}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:06+02","p50":370.85242025,"p95":566.8579537161854,"p99":596.3000648483257}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:07+02","p50":371.3460725,"p95":600.864875038826,"p99":696.5931488108387}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:08+02","p50":377.0203545,"p95":593.0985303793224,"p99":717.2182938407545}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:09+02","p50":350.117952625,"p95":551.1117659721687,"p99":629.2865839315465}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:10+02","p50":384.89380324999996,"p95":557.8008546511566,"p99":636.580461088732}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:11+02","p50":366.029803,"p95":564.3884609931798,"p99":661.2605684219055}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:12+02","p50":359.28891,"p95":561.3114050814762,"p99":670.2915985139274}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:13+02","p50":368.3550525,"p95":528.7123712326683,"p99":660.9452924902478}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:14+02","p50":403.413705,"p95":647.2213528704553,"p99":787.9417279004001}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:15+02","p50":316.89532575,"p95":585.3375307634708,"p99":746.9125202290841}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:16+02","p50":366.46199775,"p95":564.5855855665229,"p99":720.3569641328735}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:17+02","p50":395.0455565,"p95":630.3617309191131,"p99":712.5628947219238}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:18+02","p50":452.543411,"p95":666.0552077854462,"p99":818.2982870411587}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:19+02","p50":475.983971,"p95":815.1069785751895,"p99":992.4595084467697}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:20+02","p50":396.29265350000003,"p95":760.2994612084045,"p99":881.2400988808289}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:21+02","p50":350.0927785,"p95":534.3606522786283,"p99":685.6315452478809}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:22+02","p50":355.531666,"p95":562.436802332802,"p99":655.665564886801}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:23+02","p50":379.008702,"p95":526.9116277446686,"p99":579.3579618329161}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:24+02","p50":369.6668505,"p95":562.9086056966726,"p99":642.9324887840419}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:25+02","p50":374.783023125,"p95":556.1949239988519,"p99":648.6149735306576}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:26+02","p50":373.97536675,"p95":545.8640974620236,"p99":603.0847192951051}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:27+02","p50":357.790776875,"p95":592.9894363913226,"p99":698.608809728214}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:28+02","p50":380.39239150000003,"p95":560.935014845125,"p99":713.1072916162286}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:29+02","p50":375.62008249999997,"p95":549.8600039423752,"p99":614.5286269966126}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:30+02","p50":354.73620475,"p95":518.6924974618819,"p99":653.8115801267328}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:31+02","p50":408.81667400000003,"p95":580.7742718071449,"p99":743.639358516593}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:32+02","p50":373.717473,"p95":591.7884088895145,"p99":653.9233233542363}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:33+02","p50":457.6231675,"p95":713.9853752811575,"p99":801.5314229417592}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:34+02","p50":398.69369099999994,"p95":576.9453266908774,"p99":638.878944097023}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:35+02","p50":380.10456575,"p95":575.6161159108634,"p99":609.1460214958057}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:36+02","p50":390.482404,"p95":564.0768451614668,"p99":716.9423898276623}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:37+02","p50":376.32667000000004,"p95":600.187044783256,"p99":781.7169870735951}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:38+02","p50":387.6777615,"p95":560.6641300469131,"p99":595.6967540945435}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:39+02","p50":402.231564125,"p95":530.5324569812227,"p99":632.398123102675}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:40+02","p50":387.8637995,"p95":594.2622456500368,"p99":640.2609036061725}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:41+02","p50":372.434851,"p95":524.2710856375901,"p99":578.9491680272847}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:42+02","p50":387.522010625,"p95":580.4659249738294,"p99":711.8193150877477}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:43+02","p50":387.458622,"p95":620.741373132667,"p99":658.9881203480729}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:44+02","p50":392.4298125,"p95":629.7071045733506,"p99":705.2516679337415}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:45+02","p50":376.85462225,"p95":533.5347648917531,"p99":639.7289377307749}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:46+02","p50":395.706921,"p95":641.9824354647773,"p99":702.8896531357329}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:47+02","p50":376.69207400000005,"p95":657.763545992053,"p99":805.5041914577865}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:48+02","p50":509.502717,"p95":755.5429311040151,"p99":935.4421637327413}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:49+02","p50":474.339925,"p95":745.6588229550018,"p99":864.3030752003326}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:50+02","p50":416.080606,"p95":740.9167848159949,"p99":896.6391244707446}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:51+02","p50":374.4881395,"p95":568.7187625705914,"p99":640.6493812109198}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:52+02","p50":377.6801165,"p95":627.671924471273,"p99":727.8040352599335}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:53+02","p50":407.80580174999994,"p95":637.1368828413749,"p99":749.3144091134742}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:54+02","p50":390.149391,"p95":532.2912166657791,"p99":621.3468670521851}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:55+02","p50":353.1494325,"p95":578.9051062667124,"p99":622.0327491599936}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:56+02","p50":380.552572,"p95":573.046774797695,"p99":643.197298432375}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:57+02","p50":397.34807575,"p95":601.8214968569139,"p99":737.5489854612472}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:58+02","p50":379.87111775,"p95":601.284545349459,"p99":641.9105274041752}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:48:59+02","p50":372.430882,"p95":574.5653418701997,"p99":656.5257328923083}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:00+02","p50":391.869787,"p95":594.046199838078,"p99":661.4024955055589}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:01+02","p50":380.62370150000004,"p95":601.2354013430615,"p99":699.7857347286258}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:02+02","p50":381.049654,"p95":606.5920663715998,"p99":664.5980062494796}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:03+02","p50":383.42459199999996,"p95":620.6149392064591,"p99":717.3536460669555}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:04+02","p50":394.31672,"p95":608.5648653262712,"p99":752.5454078678036}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:05+02","p50":393.24571000000003,"p95":628.0916439125399,"p99":697.0547843593827}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:06+02","p50":403.32300975,"p95":600.9828152580207,"p99":705.8764485959186}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:07+02","p50":381.233005,"p95":593.6824948727258,"p99":646.7162360751807}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:08+02","p50":378.452192125,"p95":585.3596847937857,"p99":658.7346346348761}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:09+02","p50":406.263443,"p95":601.8851923236006,"p99":657.2011940190091}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:10+02","p50":379.06424675,"p95":536.3294893311079,"p99":657.037147603148}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:11+02","p50":385.3275725,"p95":601.5528484018837,"p99":655.0453609955755}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:12+02","p50":364.032396875,"p95":653.4345941447176,"p99":756.3619943769318}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:13+02","p50":375.1136185,"p95":563.4905159659334,"p99":609.4856272265067}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:14+02","p50":374.0972925,"p95":603.8206581368601,"p99":665.2039553373108}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:15+02","p50":387.637838,"p95":577.552652499797,"p99":693.657785178182}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:16+02","p50":395.9279765,"p95":573.2520221783011,"p99":623.4036501542654}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:17+02","p50":385.011343,"p95":578.0024207123759,"p99":739.5986926926594}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:18+02","p50":501.070556,"p95":739.1737046452372,"p99":817.5306489707167}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:19+02","p50":487.48740399999997,"p95":714.5395390926317,"p99":788.6833549983197}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:20+02","p50":398.6928585,"p95":624.9289234712396,"p99":735.1492829041281}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:21+02","p50":381.2014705,"p95":552.4190413578395,"p99":607.1950033001709}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:22+02","p50":378.4648505,"p95":572.1401214373743,"p99":672.7073571081388}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:23+02","p50":381.756316,"p95":546.8975953816202,"p99":656.8522043579336}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:24+02","p50":398.69236900000004,"p95":564.599991420596,"p99":666.2908761377087}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:25+02","p50":369.738289,"p95":591.8191478315905,"p99":667.6489562640949}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:26+02","p50":374.1937285,"p95":561.258875386296,"p99":635.1006114370289}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:27+02","p50":364.389940375,"p95":607.656058991329,"p99":638.7479768448577}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:28+02","p50":392.7941255,"p95":582.934002763613,"p99":668.4763312391167}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:29+02","p50":395.1537375,"p95":581.2645812752047,"p99":629.3970892682247}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:30+02","p50":378.461375,"p95":560.356492213997,"p99":644.3948260291138}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:31+02","p50":382.706153,"p95":554.023332663457,"p99":595.6850969616621}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:32+02","p50":378.17704625,"p95":596.8606748886185,"p99":652.8597620553479}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:33+02","p50":361.20687,"p95":554.0451086429039,"p99":665.8597491082382}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:34+02","p50":375.875681,"p95":573.009036371263,"p99":677.5763840911723}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:35+02","p50":398.95089375,"p95":551.1305156222186,"p99":625.1852731621457}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:36+02","p50":403.060358,"p95":578.952155218065,"p99":617.1697987665615}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:37+02","p50":358.56926925,"p95":602.9619980526259,"p99":706.9007030513372}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:38+02","p50":369.853585875,"p95":558.05031432408,"p99":656.9718285308442}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:39+02","p50":376.7368085,"p95":541.9252990983949,"p99":597.4204704358468}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:40+02","p50":374.443547,"p95":610.7528968129975,"p99":718.2325440708454}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:41+02","p50":390.63388,"p95":566.5547698392392,"p99":751.4906429859648}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:42+02","p50":386.25493274999997,"p95":558.2470035515831,"p99":651.1849266849058}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:43+02","p50":386.3001798749999,"p95":568.7246794361353,"p99":680.0371702720599}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:44+02","p50":414.451416,"p95":582.8826341209705,"p99":688.0970412610598}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:45+02","p50":396.874899,"p95":573.0194268884987,"p99":673.6045943448377}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:46+02","p50":442.434258,"p95":598.4165902697932,"p99":720.8006863922512}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:47+02","p50":403.353726,"p95":548.8214058317883,"p99":825.8187105583257}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:48+02","p50":512.466077,"p95":732.5617006177514,"p99":875.0522002655697}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:49+02","p50":498.12606500000004,"p95":711.3046155963399,"p99":893.5885908343587}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:50+02","p50":419.23831099999995,"p95":653.8646418435065,"p99":740.4648088355135}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:51+02","p50":376.37620575,"p95":552.512115076958,"p99":619.9807175853116}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:52+02","p50":390.14741749999996,"p95":588.6646891362872,"p99":655.2341834101849}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:53+02","p50":400.861338,"p95":577.2982991460228,"p99":600.915501021375}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:54+02","p50":379.451339125,"p95":575.3869842699014,"p99":657.3521219556268}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:55+02","p50":407.52232649999996,"p95":604.6002093730469,"p99":653.4454181223144}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:56+02","p50":363.082460375,"p95":540.8862117627307,"p99":679.5614941885009}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:57+02","p50":365.796288125,"p95":567.4904118875198,"p99":652.1854918458216}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:58+02","p50":372.912214,"p95":562.0312605474297,"p99":632.4050374625965}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:49:59+02","p50":369.301701,"p95":593.6903148726128,"p99":677.3486074447236}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:00+02","p50":421.84070199999996,"p95":580.7411737502654,"p99":621.4996571411447}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:01+02","p50":392.687742,"p95":617.2486047475122,"p99":686.2983731112414}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:02+02","p50":383.580548,"p95":617.7467801069415,"p99":707.103503131856}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:03+02","p50":386.919284,"p95":593.5768893762719,"p99":679.0635569386003}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:04+02","p50":451.1858505,"p95":710.2390074153294,"p99":824.9659940393234}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:05+02","p50":386.025572,"p95":683.426417292915,"p99":896.3165627046494}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:06+02","p50":384.505966,"p95":589.1070330830125,"p99":694.5420661562305}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:07+02","p50":399.1031545,"p95":592.4421404059601,"p99":675.8345912961425}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:08+02","p50":394.995577,"p95":604.3202049969374,"p99":674.6714073621386}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:09+02","p50":367.20676249999997,"p95":585.9666742111297,"p99":659.7609447760358}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:10+02","p50":385.432987,"p95":552.5380288014563,"p99":639.7719383094694}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:11+02","p50":404.909684,"p95":600.2965685052718,"p99":678.1246169718006}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:12+02","p50":400.76854000000003,"p95":609.4308743893686,"p99":640.4930314384975}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:13+02","p50":380.7047025,"p95":576.3048701383278,"p99":672.3878423597536}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:14+02","p50":382.82686674999997,"p95":582.7837581255008,"p99":661.5016473044512}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:15+02","p50":378.88070849999997,"p95":584.1112044746536,"p99":627.313500304875}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:16+02","p50":403.2571525,"p95":632.6679983820713,"p99":682.0945478748445}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:17+02","p50":413.427646,"p95":606.5683102387396,"p99":738.8084224646201}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:18+02","p50":488.05521,"p95":703.4761239822246,"p99":768.0045405116186}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:19+02","p50":526.1706945,"p95":757.2231546335336,"p99":822.1608808752136}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:20+02","p50":428.938374,"p95":735.4151300372282,"p99":845.5614114277008}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:21+02","p50":389.45504625,"p95":642.9555771926074,"p99":747.944182983573}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:22+02","p50":421.3969125,"p95":613.8132536197404,"p99":715.3682359889335}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:23+02","p50":407.713257,"p95":575.4587298318987,"p99":654.8682521849261}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:24+02","p50":406.7370325,"p95":612.7913894636231,"p99":682.8240717960119}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:25+02","p50":414.999551,"p95":679.9491651280817,"p99":712.1398508435125}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:26+02","p50":400.381467,"p95":629.3210713000857,"p99":757.5066780656276}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:27+02","p50":403.815541,"p95":601.0086458383829,"p99":731.782436562891}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:28+02","p50":436.904756,"p95":585.4005056937589,"p99":624.0598723059244}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:29+02","p50":433.925698,"p95":586.8056523985105,"p99":627.1120268679717}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:30+02","p50":416.318149,"p95":604.0334259981772,"p99":670.8958782901761}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:31+02","p50":404.17097,"p95":609.5917478201899,"p99":678.6095126874237}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:32+02","p50":406.893869,"p95":564.5321166061897,"p99":685.2356767854099}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:33+02","p50":390.560496,"p95":565.1381103080156,"p99":639.992465843625}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:34+02","p50":405.088062,"p95":584.9710610011844,"p99":642.5561481885514}, + {"metric_name":"oidc_token_duration","timestamp":"2024-10-22 18:50:35+02","p50":379.604884,"p95":610.3624684022362,"p99":696.5676583588892} +] diff --git a/docs/docs/apis/benchmarks/v2.66.0/machine_jwt_profile_grant/index.mdx b/docs/docs/apis/benchmarks/v2.66.0/machine_jwt_profile_grant/index.mdx new file mode 100644 index 0000000000..50dd7dc7ec --- /dev/null +++ b/docs/docs/apis/benchmarks/v2.66.0/machine_jwt_profile_grant/index.mdx @@ -0,0 +1,104 @@ +--- +title: machine jwt profile grant benchmark of zitadel v2.66.0 +sidebar_label: machine jwt profile grant +--- + +## Summary + +The tests showed heavy database load by time by the first two database queries. These queries need to be analyzed further. + +## Performance test results + +| Metric | Value | +|:--------------------------------------|:------| +| Baseline | none | +| Purpose | Test current performance | +| Test start | 15:39 UTC | +| Test duration | 30min | +| Executed test | machine\_jwt\_profile\_grant | +| k6 version | v0.54.0 | +| VUs | 150 | +| Client location | US1 | +| ZITADEL location | US1 | +| ZITADEL container specification | vCPU: 2
Memory: 512 Mib
Container count: 5 | +| ZITADEL Version | v2.66.0 | +| ZITADEL feature flags | webKey: true, improvedPerformance: \[\"IMPROVED\_PERFORMANCE\_ORG\_BY\_ID\", \"IMPROVED\_PERFORMANCE\_PROJECT\", \"IMPROVED\_PERFORMANCE\_USER\_GRANT\", \"IMPROVED\_PERFORMANCE\_ORG\_DOMAIN\_VERIFIED\", \"IMPROVED\_PERFORMANCE\_PROJECT\_GRANT\"\] | +| Database | type: psql
version: v15.8 | +| Database location | US1 | +| Database specification | vCPU: 8
memory: 32Gib | +| ZITADEL metrics during test | | +| Observed errors | | +| Top 3 most expensive database queries | 1: Write events using the newly added eventstore.push function
2: Query events by instance\_id, aggregate\_type, aggregate\_id, event\_types
3: Query user
| +| k6 Iterations per second | 439 | +| k6 output | [output](#k6-output) | +| flowchart outcome | Scale out | + +## Endpoint latencies + +import OutputSource from "!!raw-loader!./output.json"; + +import { BenchmarkChart } from '/src/components/benchmark_chart'; + + + +## k6 output {#k6-output} + +```bash + โœ“ openid configuration + โœ— token status ok + โ†ณ 99% โ€” โœ“ 790655 / โœ— 5 + โœ— access token returned + โ†ณ 99% โ€” โœ“ 790655 / โœ— 5 + + โ–ˆ setup + + โœ“ user defined + โœ“ authorize status ok + โœ“ login name status ok + โœ“ login shows password page + โœ“ password status ok + โœ“ password callback + โœ“ code set + โœ“ token status ok + โœ“ access token created + โœ“ id token created + โœ“ info created + โœ“ org created + โœ“ create user is status ok + โœ“ generate machine key status ok + + โ–ˆ teardown + + โœ“ org removed + + checks...............................: 99.99% โœ“ 1581773 โœ— 10 + data_received........................: 1.1 GB 623 kB/s + data_sent............................: 628 MB 347 kB/s + http_req_blocked.....................: min=167ns avg=20.68ยตs max=493.59ms p(50)=468ns p(95)=717ns p(99)=928ns + http_req_connecting..................: min=0s avg=10.06ยตs max=388.27ms p(50)=0s p(95)=0s p(99)=0s + http_req_duration....................: min=17.71ms avg=337.34ms max=16.27s p(50)=249.03ms p(95)=888.75ms p(99)=1.4s + { expected_response:true }.........: min=17.71ms avg=337.29ms max=3.56s p(50)=249.03ms p(95)=888.7ms p(99)=1.4s + http_req_failed......................: 0.00% โœ“ 5 โœ— 791265 + http_req_receiving...................: min=25.49ยตs avg=1.58ms max=539.43ms p(50)=89.69ยตs p(95)=7.55ms p(99)=23.46ms + http_req_sending.....................: min=22.7ยตs avg=69.14ยตs max=480.23ms p(50)=59.16ยตs p(95)=85.15ยตs p(99)=129.88ยตs + http_req_tls_handshaking.............: min=0s avg=9.38ยตs max=98.15ms p(50)=0s p(95)=0s p(99)=0s + http_req_waiting.....................: min=15.11ms avg=335.69ms max=16.26s p(50)=246.91ms p(95)=888.27ms p(99)=1.4s + http_reqs............................: 791270 437.256468/s + iteration_duration...................: min=32.28ms avg=341.46ms max=16.27s p(50)=253ms p(95)=892.49ms p(99)=1.41s + iterations...........................: 790660 436.919382/s + login_ui_enter_login_name_duration...: min=179.27ms avg=179.27ms max=179.27ms p(50)=179.27ms p(95)=179.27ms p(99)=179.27ms + login_ui_enter_password_duration.....: min=17.71ms avg=17.71ms max=17.71ms p(50)=17.71ms p(95)=17.71ms p(99)=17.71ms + login_ui_init_login_duration.........: min=77.66ms avg=77.66ms max=77.66ms p(50)=77.66ms p(95)=77.66ms p(99)=77.66ms + login_ui_token_duration..............: min=86.79ms avg=86.79ms max=86.79ms p(50)=86.79ms p(95)=86.79ms p(99)=86.79ms + oidc_token_duration..................: min=28.38ms avg=337.54ms max=16.27s p(50)=249.17ms p(95)=889.01ms p(99)=1.4s + org_create_org_duration..............: min=44.94ms avg=44.94ms max=44.94ms p(50)=44.94ms p(95)=44.94ms p(99)=44.94ms + user_add_machine_key_duration........: min=38.11ms avg=66.64ms max=160.59ms p(50)=60.28ms p(95)=104.99ms p(99)=112.5ms + user_create_machine_duration.........: min=37.12ms avg=122.76ms max=1.03s p(50)=78.25ms p(95)=266.95ms p(99)=306.94ms + vus..................................: 150 min=0 max=150 + vus_max..............................: 150 min=150 max=150 + + +running (30m09.6s), 000/150 VUs, 790660 complete and 0 interrupted iterations +default โœ“ [======================================] 150 VUs 30m0s +``` + diff --git a/docs/docs/apis/benchmarks/v2.66.0/machine_jwt_profile_grant/output.json b/docs/docs/apis/benchmarks/v2.66.0/machine_jwt_profile_grant/output.json new file mode 100644 index 0000000000..e2c43e09e2 --- /dev/null +++ b/docs/docs/apis/benchmarks/v2.66.0/machine_jwt_profile_grant/output.json @@ -0,0 +1,1803 @@ +[ + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:38:44+01","p50":101.95622399999999,"p95":137.6454028957974,"p99":157.2097258345785}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:38:45+01","p50":160.71148648611108,"p95":242.88793605301868,"p99":283.76071553355786}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:38:46+01","p50":121.72930775694444,"p95":417.7653542187909,"p99":436.3731336767896}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:38:47+01","p50":113.88456833538461,"p95":441.91780702667364,"p99":474.0527494362228}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:38:48+01","p50":138.66216244378697,"p95":392.44944815358485,"p99":413.12171234236575}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:38:49+01","p50":188.4568091775148,"p95":285.6134255112239,"p99":301.9917086591737}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:38:50+01","p50":193.87928416319446,"p95":400.48394514500757,"p99":431.03960871716765}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:38:51+01","p50":100.58784778472221,"p95":388.16808085544255,"p99":452.6248447790737}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:38:52+01","p50":84.70682090495868,"p95":459.5434618697324,"p99":496.90298018895936}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:38:53+01","p50":84.85543177272727,"p95":405.27415783347095,"p99":429.8696062663708}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:38:54+01","p50":78.19703018181819,"p95":412.3267073848172,"p99":434.65174111990393}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:38:55+01","p50":98.09821723611111,"p95":541.8270667530961,"p99":569.7796809969452}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:38:56+01","p50":88.92674323958335,"p95":604.1480300615494,"p99":629.4101199430855}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:38:57+01","p50":94.25996172189349,"p95":552.0454750890208,"p99":578.8507372053331}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:38:58+01","p50":69.09293158333334,"p95":651.3896680271364,"p99":677.6142473733806}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:38:59+01","p50":59.70957779338842,"p95":754.4046575486248,"p99":800.369793227911}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:00+01","p50":83.38675619834713,"p95":679.7530003826953,"p99":698.8895711397541}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:01+01","p50":86.97336683333333,"p95":634.8256747769915,"p99":672.9910167700718}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:02+01","p50":116.58535063194445,"p95":563.9482791114166,"p99":590.9092625559061}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:03+01","p50":137.22131244444446,"p95":389.24244922502425,"p99":417.7380193225627}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:04+01","p50":98.48047405555555,"p95":478.40393591816127,"p99":568.753388686688}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:05+01","p50":65.81398447569445,"p95":662.2519370419724,"p99":697.4593990805466}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:06+01","p50":76.7836114752066,"p95":693.045640942108,"p99":712.0740948468391}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:07+01","p50":87.3815950697436,"p95":533.8020075679781,"p99":555.6812275544797}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:08+01","p50":108.40741219230769,"p95":508.4610614833938,"p99":523.5754284794996}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:09+01","p50":123.75995270512819,"p95":427.4367694853075,"p99":453.9057932773251}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:10+01","p50":143.06203653472224,"p95":436.019242594773,"p99":500.96013612772214}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:11+01","p50":163.82245444444445,"p95":352.2676896285294,"p99":395.2562410976543}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:12+01","p50":189.35444572948717,"p95":280.58916577935,"p99":329.13701846991734}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:13+01","p50":158.01874584375,"p95":305.0929308849436,"p99":344.95560191032104}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:14+01","p50":98.86212096428572,"p95":343.1827672970643,"p99":359.07650118063657}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:15+01","p50":148.2732390625,"p95":343.3010464739998,"p99":366.40679934058517}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:16+01","p50":144.74630972307693,"p95":337.1911591209745,"p99":364.8760598073751}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:17+01","p50":158.87796496875004,"p95":379.51867407425647,"p99":401.08808145494316}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:18+01","p50":197.64607608333336,"p95":356.5588904414338,"p99":392.4581539708023}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:19+01","p50":129.4125514201183,"p95":390.3371094746815,"p99":420.35230749515904}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:20+01","p50":97.61605846564103,"p95":490.68944690923774,"p99":527.4489383970988}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:21+01","p50":107.76460658680556,"p95":477.3659518223023,"p99":496.1928347163977}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:22+01","p50":117.44868988717948,"p95":454.1808149556858,"p99":515.2328602515254}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:23+01","p50":97.55113141124261,"p95":452.9805033099765,"p99":480.48952179725205}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:24+01","p50":131.61317731250003,"p95":370.7860591967902,"p99":400.50686612839235}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:25+01","p50":127.78259752366861,"p95":389.51509347240165,"p99":412.85549195026647}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:26+01","p50":124.32231885487181,"p95":377.5170314677562,"p99":417.6425894308979}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:27+01","p50":90.28449679166665,"p95":387.41615753598063,"p99":453.32418886782455}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:28+01","p50":69.83177338,"p95":726.3041636570783,"p99":760.3623750899031}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:29+01","p50":82.90674688461537,"p95":748.2743932268411,"p99":791.7971064829702}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:30+01","p50":94.60348115972222,"p95":569.5002533690139,"p99":617.3139484939122}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:31+01","p50":126.65370924305556,"p95":451.9422106836249,"p99":489.58885982269874}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:32+01","p50":103.59756552071006,"p95":463.21392981184965,"p99":490.88010029968535}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:33+01","p50":107.23317684615382,"p95":463.4600011584177,"p99":524.1793939104941}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:34+01","p50":122.73644040236687,"p95":426.10495775129675,"p99":457.26206028565736}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:35+01","p50":180.13561702564104,"p95":353.7763569813716,"p99":371.91369185396013}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:36+01","p50":127.1379665739645,"p95":295.7125128164935,"p99":327.2167265282798}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:37+01","p50":163.18437498214286,"p95":309.76309655697787,"p99":358.1931256399682}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:38+01","p50":152.10465000000002,"p95":291.44487577096913,"p99":355.2782387550554}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:39+01","p50":171.80184869444443,"p95":291.25106553931664,"p99":321.8108586276755}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:40+01","p50":131.7923757485207,"p95":358.1267161681937,"p99":386.3548208418736}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:41+01","p50":109.11131225694443,"p95":500.85207023846687,"p99":541.9965055489822}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:42+01","p50":94.31880577777777,"p95":514.1331498675038,"p99":538.6291467694282}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:43+01","p50":88.45482849999998,"p95":506.87047220237343,"p99":533.217965195303}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:44+01","p50":75.99285691666667,"p95":563.1341731490003,"p99":586.8454878953421}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:45+01","p50":78.04575791319444,"p95":560.6020778561364,"p99":584.0347969075983}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:46+01","p50":69.93837122222222,"p95":583.1436883813226,"p99":609.789652659574}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:47+01","p50":75.74691906944444,"p95":595.6732498260205,"p99":629.4469651198204}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:48+01","p50":90.56894379166665,"p95":547.4183809439039,"p99":567.6402171650184}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:49+01","p50":121.98702320710059,"p95":437.4328312784021,"p99":464.1821426945799}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:50+01","p50":143.58755762500002,"p95":419.3740978008817,"p99":447.02481925501246}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:51+01","p50":135.25529236692307,"p95":417.0512811258649,"p99":437.9065658626807}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:52+01","p50":112.64030973611109,"p95":452.6488015395233,"p99":486.3007172425156}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:53+01","p50":81.60573402366865,"p95":494.22594630013674,"p99":512.9955964094256}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:54+01","p50":92.42321304166666,"p95":538.7354114677075,"p99":561.8000694492002}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:55+01","p50":87.73330956615384,"p95":560.3798702778885,"p99":582.9945306706032}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:56+01","p50":91.72019077083336,"p95":535.0953102886127,"p99":585.5578139556216}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:57+01","p50":85.15941284615384,"p95":540.8847860170158,"p99":560.1400723319731}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:58+01","p50":90.26350492361111,"p95":556.9696732176005,"p99":573.1325806344051}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:39:59+01","p50":77.84642229166667,"p95":533.2522054634483,"p99":558.3325973766355}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:00+01","p50":132.17229913194444,"p95":509.04760358599725,"p99":549.6209992342126}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:01+01","p50":167.21761793750002,"p95":365.123159374226,"p99":407.55215734951634}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:02+01","p50":142.67088705208334,"p95":375.7471040833458,"p99":401.52165325263525}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:03+01","p50":105.18881233333333,"p95":349.77825306078137,"p99":366.9560180681512}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:04+01","p50":101.856276,"p95":395.8278248530486,"p99":427.90799470867177}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:05+01","p50":74.82229978512396,"p95":616.9319200625013,"p99":644.8957025972545}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:06+01","p50":91.40010851041666,"p95":590.8001965581633,"p99":630.5051493763192}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:07+01","p50":86.183248875,"p95":574.3462907923283,"p99":593.9599358535249}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:08+01","p50":82.26859711805555,"p95":614.6232196523804,"p99":632.3237096065822}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:09+01","p50":78.90578863223142,"p95":613.5982234619646,"p99":644.100604879699}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:10+01","p50":95.57105683471073,"p95":568.8889026592448,"p99":598.6244983987975}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:11+01","p50":95.46569968749999,"p95":545.6172264823972,"p99":575.6657091616918}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:12+01","p50":93.12112121900827,"p95":569.0027889956261,"p99":595.4926556786131}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:13+01","p50":100.53065516666666,"p95":536.042215766721,"p99":572.2321731558561}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:14+01","p50":97.57324775,"p95":562.0529697343982,"p99":591.0343833802948}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:15+01","p50":133.57539063888888,"p95":509.0541015674988,"p99":556.303328832901}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:16+01","p50":124.22675591666666,"p95":464.2705362198908,"p99":493.2546924270477}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:17+01","p50":128.24074092708335,"p95":498.61606921289257,"p99":537.9222896753763}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:18+01","p50":137.99689933333335,"p95":466.84183049648993,"p99":495.4667485496285}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:19+01","p50":111.77674313888889,"p95":475.4774850918696,"p99":503.3384257174796}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:20+01","p50":113.31951005621303,"p95":458.1750315689811,"p99":500.49116268753056}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:21+01","p50":119.84649527083336,"p95":424.25072243071565,"p99":456.11141327825175}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:22+01","p50":157.37732627810652,"p95":414.97423303714146,"p99":435.9454448856339}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:23+01","p50":130.9790169375,"p95":413.1754348629612,"p99":448.3356487720349}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:24+01","p50":87.58673572222222,"p95":414.52946198751005,"p99":455.28479627477213}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:25+01","p50":97.83618783333334,"p95":336.86894100654496,"p99":355.1840208984475}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:26+01","p50":98.32786904166666,"p95":378.06166936114215,"p99":409.0661119619925}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:27+01","p50":120.63735804166667,"p95":365.3308771819533,"p99":387.5634260185882}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:28+01","p50":256.3922228515625,"p95":507.4061893079345,"p99":588.9136951626673}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:29+01","p50":405.2968411,"p95":741.7480717913759,"p99":865.2794983107214}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:30+01","p50":300.7969605510204,"p95":431.0513314097269,"p99":554.9928161084022}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:31+01","p50":318.7500045833333,"p95":509.25965059560923,"p99":636.8053048485546}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:32+01","p50":293.3239689591837,"p95":439.3350883331949,"p99":604.5661200978217}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:33+01","p50":273.23968116326535,"p95":491.07233060415945,"p99":539.8570070911121}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:34+01","p50":325.1737032,"p95":571.2120406992194,"p99":660.3595275364341}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:35+01","p50":266.80053713265306,"p95":436.41712692247944,"p99":613.138877821022}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:36+01","p50":317.9744242,"p95":540.1798305671141,"p99":661.0712341334892}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:37+01","p50":335.40736559722217,"p95":570.3400851910666,"p99":662.043744033508}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:38+01","p50":167.68907577083328,"p95":308.11974913143024,"p99":583.1417500005509}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:39+01","p50":143.81799477384618,"p95":292.8504029544521,"p99":319.7505007721857}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:40+01","p50":165.2808483372781,"p95":326.9120415372973,"p99":369.96195431708315}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:41+01","p50":180.46724924999998,"p95":329.4187870243736,"p99":355.25900759258434}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:42+01","p50":133.77021155555556,"p95":437.1314825049485,"p99":475.67759025469496}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:43+01","p50":107.67179333333333,"p95":520.2970250481079,"p99":556.6558343640891}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:44+01","p50":99.09475161805555,"p95":566.443634520363,"p99":606.1468685731234}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:45+01","p50":121.84509769097222,"p95":482.78248615905056,"p99":513.6306669925133}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:46+01","p50":181.11651979166666,"p95":383.50846679154193,"p99":419.782887811665}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:47+01","p50":149.23981741715977,"p95":353.0961334702595,"p99":370.79685625247606}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:48+01","p50":145.85921094538463,"p95":362.8218351790662,"p99":391.8143269314747}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:49+01","p50":163.54859145454543,"p95":392.8813798870608,"p99":418.67251844226706}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:50+01","p50":138.59491245867767,"p95":408.39317442940467,"p99":426.51578888154745}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:51+01","p50":166.57350719526627,"p95":379.2455483057585,"p99":427.8573923744141}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:52+01","p50":148.1943132692308,"p95":411.96511940726975,"p99":462.58051847259236}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:53+01","p50":164.11855828819444,"p95":385.8763284527696,"p99":409.9494071513559}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:54+01","p50":148.42442637499997,"p95":334.94317389465846,"p99":399.9033468358762}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:55+01","p50":134.6974950473373,"p95":407.5446963280021,"p99":446.61813724848315}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:56+01","p50":119.78755362847222,"p95":505.06636059252895,"p99":539.0253774909463}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:57+01","p50":105.84350979166668,"p95":532.024426948585,"p99":553.3202703303175}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:58+01","p50":106.51078458333332,"p95":472.1730293651093,"p99":490.6947052263731}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:40:59+01","p50":96.89142987499999,"p95":495.9329837888282,"p99":535.8807128890589}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:00+01","p50":95.8949944876033,"p95":524.2076775969031,"p99":543.3839198861698}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:01+01","p50":79.37144154,"p95":615.0193961395058,"p99":635.3428000241315}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:02+01","p50":73.42690711570248,"p95":602.7664669140363,"p99":626.0170225410861}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:03+01","p50":76.64229597222221,"p95":585.5528533539062,"p99":623.9457972382203}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:04+01","p50":69.70040529166666,"p95":537.4732614117437,"p99":562.4937842162401}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:05+01","p50":64.1410094628099,"p95":629.9419693858313,"p99":658.1186802511063}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:06+01","p50":76.2555816198347,"p95":607.0660525936493,"p99":630.9929336713673}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:07+01","p50":66.50165761157024,"p95":603.3572839567105,"p99":624.2669383155626}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:08+01","p50":61.79705429752066,"p95":640.9749931221494,"p99":662.1256739697008}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:09+01","p50":74.08435375,"p95":665.7778572553271,"p99":692.6510113831741}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:10+01","p50":69.47923140909091,"p95":592.2587113220148,"p99":610.4134084533755}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:11+01","p50":62.77434199173554,"p95":581.7786063104679,"p99":596.8846012632675}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:12+01","p50":71.80177740495868,"p95":561.2790312832076,"p99":584.8997773869073}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:13+01","p50":62.85025523140496,"p95":604.31724675006,"p99":624.7182492899176}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:14+01","p50":60.00107763636363,"p95":668.0291777059201,"p99":683.9823378800266}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:15+01","p50":59.28935307438016,"p95":631.9506173526725,"p99":649.3750163239507}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:16+01","p50":58.90572745454546,"p95":634.084268904297,"p99":649.510729109484}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:17+01","p50":61.80572913636363,"p95":609.7891813478544,"p99":620.0572333747921}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:18+01","p50":71.60840007024792,"p95":559.8874666964492,"p99":592.5992380026972}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:19+01","p50":58.13061114,"p95":650.3538829152382,"p99":663.5863870429329}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:20+01","p50":61.86582962,"p95":620.9669647622484,"p99":651.462879988739}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:21+01","p50":66.77992954545455,"p95":631.1164914842583,"p99":639.9338938968889}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:22+01","p50":60.17779063636363,"p95":634.6677554071666,"p99":661.1144794389195}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:23+01","p50":65.02052834000001,"p95":653.0454981229543,"p99":673.1218770871357}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:24+01","p50":57.13980746,"p95":675.790308628906,"p99":698.9799746068121}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:25+01","p50":58.24718095867768,"p95":662.7384424419728,"p99":688.2042338089752}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:26+01","p50":70.69783547,"p95":595.248382066493,"p99":611.8900123906598}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:27+01","p50":61.548106250000004,"p95":659.5202249164303,"p99":686.0183081571658}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:28+01","p50":63.888166275,"p95":620.474516779485,"p99":636.3685913138737}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:29+01","p50":62.02698227,"p95":666.8902032875698,"p99":696.1166366655381}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:30+01","p50":65.35355684999999,"p95":620.7848496363005,"p99":635.5683915472309}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:31+01","p50":70.44304855,"p95":691.8590752161153,"p99":715.0191105740284}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:32+01","p50":67.37997759500001,"p95":626.2066631117002,"p99":649.2957250652213}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:33+01","p50":66.06885879338843,"p95":597.9353302482874,"p99":625.2392518280727}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:34+01","p50":64.74006789999999,"p95":670.8292389837939,"p99":699.4323136105946}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:35+01","p50":61.849484842975215,"p95":596.5549910826903,"p99":617.1200690922201}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:36+01","p50":64.5200382231405,"p95":617.9165390848217,"p99":640.6491890718996}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:37+01","p50":60.77042837190083,"p95":643.824495813177,"p99":666.3431619080753}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:38+01","p50":61.46832467,"p95":644.0652491371304,"p99":657.3184414108292}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:39+01","p50":62.77397028,"p95":641.0440278738972,"p99":668.1244157608188}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:40+01","p50":62.267536326446276,"p95":622.08602280511,"p99":641.5338435452259}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:41+01","p50":61.03319342975206,"p95":622.916360216743,"p99":641.9112676301595}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:42+01","p50":61.3407558,"p95":638.0191288815209,"p99":656.8496002549076}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:43+01","p50":63.57414913,"p95":662.8738484911922,"p99":688.1531705990941}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:44+01","p50":66.8209046,"p95":628.9202321552625,"p99":664.8463409218803}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:45+01","p50":66.85840900000001,"p95":674.493846461769,"p99":690.2472327520633}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:46+01","p50":60.28434102066115,"p95":639.4882704575268,"p99":662.7946079308878}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:47+01","p50":65.43626285123968,"p95":632.8991563900032,"p99":664.3373402649083}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:48+01","p50":62.16148514049587,"p95":640.6230485705134,"p99":664.84912550807}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:49+01","p50":61.4274126,"p95":615.9519005600997,"p99":628.6935576113124}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:50+01","p50":67.42087760330577,"p95":651.1394828499429,"p99":666.3791948174235}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:51+01","p50":66.33640635,"p95":616.8089937642235,"p99":665.8788403719442}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:52+01","p50":67.29680968181817,"p95":656.7313668107452,"p99":685.2968477682801}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:53+01","p50":66.51175005785124,"p95":687.0938422258783,"p99":707.1657349871975}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:54+01","p50":72.6408527,"p95":618.6133521127764,"p99":656.3228593347724}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:55+01","p50":74.88716230991734,"p95":641.9809766175623,"p99":666.4717551134484}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:56+01","p50":75.43533901652891,"p95":639.7904861585171,"p99":658.5598886644584}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:57+01","p50":82.90082569,"p95":632.488243639808,"p99":664.2611276988092}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:58+01","p50":83.19439482644628,"p95":581.8008534366982,"p99":612.3138335722941}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:41:59+01","p50":92.55629436363638,"p95":611.8925145952585,"p99":626.219947496443}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:00+01","p50":95.513820605,"p95":580.0704659946305,"p99":626.8086981582727}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:01+01","p50":108.95095749,"p95":648.124213288216,"p99":680.864462389479}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:02+01","p50":95.03935828099175,"p95":622.914999664873,"p99":656.4921832904374}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:03+01","p50":104.651274825,"p95":570.7769805655242,"p99":594.8087301179568}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:04+01","p50":110.46424965000001,"p95":601.150437160345,"p99":637.7155409400348}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:05+01","p50":107.70065435000001,"p95":603.7190336415115,"p99":623.1267803379897}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:06+01","p50":113.75183295000002,"p95":598.3224279675898,"p99":625.4230125962575}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:07+01","p50":113.91106854,"p95":544.487881893275,"p99":564.2903968583831}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:08+01","p50":113.7979202479339,"p95":564.4837890360361,"p99":590.7293302601231}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:09+01","p50":119.65436459090907,"p95":533.9626266754418,"p99":551.6154114423613}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:10+01","p50":113.0136901570248,"p95":523.9769248315413,"p99":545.518070093442}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:11+01","p50":123.92614024380164,"p95":506.42123658007125,"p99":532.7641218319454}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:12+01","p50":145.4265089338843,"p95":468.43344217495695,"p99":497.37859887193326}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:13+01","p50":145.93830121000002,"p95":473.22702027860896,"p99":492.7592998734981}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:14+01","p50":135.92363158677682,"p95":445.3027095028191,"p99":475.7678405123181}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:15+01","p50":165.54328979999997,"p95":434.6364409680323,"p99":454.4874806157722}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:16+01","p50":167.34084095000003,"p95":406.3332601374499,"p99":428.0369057653067}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:17+01","p50":166.32390059999997,"p95":443.7535240054734,"p99":466.6222055482558}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:18+01","p50":170.08523334,"p95":422.39782791789116,"p99":438.3282328818248}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:19+01","p50":169.13981641322314,"p95":421.4605632742555,"p99":439.93749897164884}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:20+01","p50":174.71706541999998,"p95":398.21559156475405,"p99":416.4561073500684}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:21+01","p50":184.40369287603306,"p95":400.77875542481297,"p99":422.971553433959}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:22+01","p50":209.63455621999998,"p95":406.91382347536154,"p99":426.2201664470338}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:23+01","p50":204.59003566,"p95":381.18141161440485,"p99":396.25733442948984}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:24+01","p50":194.13508244628102,"p95":325.05685187326657,"p99":387.45018498952106}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:25+01","p50":205.38787896999997,"p95":351.5181279746652,"p99":373.1731277970198}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:26+01","p50":200.13284370000002,"p95":303.5068036878929,"p99":323.23277671187566}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:27+01","p50":199.30461376033057,"p95":296.33566031043307,"p99":316.0184037624205}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:28+01","p50":205.86356354999998,"p95":322.3229898696147,"p99":354.09381297954513}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:29+01","p50":208.87985079999999,"p95":277.64585103760453,"p99":294.5102807418717}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:30+01","p50":225.88604066666667,"p95":325.21909291452874,"p99":362.11784659269614}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:31+01","p50":252.2089251234568,"p95":337.82816208494785,"p99":383.4915778469143}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:32+01","p50":214.8128714,"p95":282.13472025581854,"p99":307.7542446805807}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:33+01","p50":210.5768091198347,"p95":276.48536003305173,"p99":304.67822830494305}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:34+01","p50":205.83594004958678,"p95":291.3119604168843,"p99":316.2611438917699}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:35+01","p50":210.74379012,"p95":274.73700556582423,"p99":311.2203219175575}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:36+01","p50":211.37787944214878,"p95":263.3916352598567,"p99":287.3125816832063}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:37+01","p50":206.339958,"p95":255.74564694823394,"p99":265.7220561802488}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:38+01","p50":208.36953597933882,"p95":273.11675931813215,"p99":313.24789656309736}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:39+01","p50":211.89849715999998,"p95":275.84168402106525,"p99":314.7386794059194}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:40+01","p50":205.1854362479339,"p95":268.2655841678027,"p99":293.3185302644855}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:41+01","p50":207.35127854545456,"p95":270.85956489751214,"p99":291.65609142568263}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:42+01","p50":205.92993213636365,"p95":283.1462784024512,"p99":309.22032307148646}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:43+01","p50":206.59181983471072,"p95":277.4724594397631,"p99":302.24686135942727}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:44+01","p50":209.84103933,"p95":279.8546277080067,"p99":326.286694177766}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:45+01","p50":203.9019317644628,"p95":276.30867342578046,"p99":306.31588786837807}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:46+01","p50":209.10944416,"p95":278.39238641104095,"p99":307.1239100316689}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:47+01","p50":205.3218318,"p95":308.2991569233706,"p99":352.2819971887245}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:48+01","p50":217.29072868000003,"p95":318.3889288404758,"p99":377.52654887112936}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:49+01","p50":211.9904,"p95":307.6337409904953,"p99":351.1546811078254}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:50+01","p50":208.13657170000002,"p95":287.2357787762207,"p99":319.7593246253825}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:51+01","p50":222.21164414,"p95":304.9733275307495,"p99":333.9077577947789}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:52+01","p50":218.760900975,"p95":297.4669348291186,"p99":331.7858540176414}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:53+01","p50":208.4477652,"p95":302.56381131755626,"p99":331.0214724889977}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:54+01","p50":208.05792780000002,"p95":306.67040230477045,"p99":336.8534546874857}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:55+01","p50":212.81048582000003,"p95":280.7631852382431,"p99":307.2718171869035}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:56+01","p50":215.96929854000004,"p95":296.1545302726932,"p99":334.6363757980954}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:57+01","p50":223.24622268000002,"p95":280.3165774794676,"p99":299.39099125476474}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:58+01","p50":223.96217385,"p95":286.4173673343284,"p99":308.1830709748355}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:42:59+01","p50":218.43836751999999,"p95":302.39487687001923,"p99":406.682880411872}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:00+01","p50":234.8941296111111,"p95":303.56291413841745,"p99":345.7983497641659}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:01+01","p50":245.7841760740741,"p95":360.66756654836814,"p99":389.5592164468436}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:02+01","p50":215.50010050999998,"p95":305.9556130113415,"p99":332.6421174126827}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:03+01","p50":218.16031095000002,"p95":310.2951306234005,"p99":346.2025961755987}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:04+01","p50":218.085952945,"p95":298.8350058431988,"p99":337.89815800755997}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:05+01","p50":218.96447296000002,"p95":267.5901645456911,"p99":289.3522355091627}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:06+01","p50":220.39892079999998,"p95":283.95188726599815,"p99":310.7981320721116}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:07+01","p50":213.57276013999999,"p95":313.78784598577283,"p99":349.422183201581}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:08+01","p50":215.91361588,"p95":308.82812132209614,"p99":355.2259709296504}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:09+01","p50":224.995786825,"p95":273.11564905940844,"p99":289.4293419120888}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:10+01","p50":213.89981111000003,"p95":342.9151325472899,"p99":384.3140006659305}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:11+01","p50":223.008151295,"p95":289.6920305527179,"p99":305.6966092911037}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:12+01","p50":221.8847448,"p95":307.178971093112,"p99":385.76419502867776}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:13+01","p50":234.74603814000002,"p95":292.51374163980984,"p99":315.0928189088354}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:14+01","p50":230.63315959000002,"p95":287.3956519807023,"p99":301.51953251960276}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:15+01","p50":223.60981683999998,"p95":307.95663767034637,"p99":349.638760208001}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:16+01","p50":232.97873244444443,"p95":308.72932054506146,"p99":344.55854463161046}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:17+01","p50":229.12226423,"p95":318.7243294689348,"p99":347.6248296594744}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:18+01","p50":224.33404088000003,"p95":314.6390919687667,"p99":357.6844157447042}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:19+01","p50":239.99917183000002,"p95":304.0081723412635,"p99":326.3215513153874}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:20+01","p50":225.27772783999998,"p95":303.8724074122683,"p99":345.05536394420034}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:21+01","p50":218.40284165499997,"p95":299.9323947706719,"p99":347.0350404891139}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:22+01","p50":226.55160652499998,"p95":311.46804004498324,"p99":372.86824873449433}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:23+01","p50":224.2333895,"p95":313.11968587788846,"p99":356.92745905419355}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:24+01","p50":221.58133381999997,"p95":286.5379765821935,"p99":302.86770657890827}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:25+01","p50":231.01130158,"p95":302.9296034892931,"p99":334.1885577605097}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:26+01","p50":227.965134425,"p95":281.4081944148631,"p99":298.91211110043923}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:27+01","p50":228.18345068,"p95":289.1175699341779,"p99":320.84109184553336}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:28+01","p50":231.28718193,"p95":301.1543272443212,"p99":334.93013820060577}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:29+01","p50":230.96784336419756,"p95":295.7985523423554,"p99":326.63685934051324}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:30+01","p50":236.41569886419757,"p95":323.15221895455,"p99":355.84738286540124}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:31+01","p50":261.18275559374996,"p95":326.2367171339679,"p99":374.44677768891717}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:32+01","p50":238.72037855555558,"p95":310.91794728413095,"p99":333.3537962823639}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:33+01","p50":223.91749006,"p95":296.0411621026263,"p99":327.2011292525082}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:34+01","p50":228.110582575,"p95":288.3759004796407,"p99":327.18031116862744}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:35+01","p50":219.99303003,"p95":266.16024120951624,"p99":297.2480038146499}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:36+01","p50":221.60760616,"p95":294.2332467053376,"p99":331.77470583390715}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:37+01","p50":226.73102002000002,"p95":276.6364447585766,"p99":299.84920572380304}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:38+01","p50":215.39194108,"p95":278.7312414047483,"p99":306.65246902191797}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:39+01","p50":221.04404290500003,"p95":289.14730379333156,"p99":316.7282016582161}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:40+01","p50":236.00444958024693,"p95":310.8881672806127,"p99":338.07933216078237}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:41+01","p50":226.170667525,"p95":303.86026826403423,"p99":335.0505934994669}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:42+01","p50":226.994670365,"p95":286.2058999214015,"p99":307.1751533944094}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:43+01","p50":227.91483996296301,"p95":313.03218783865435,"p99":366.6153134771373}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:44+01","p50":222.98006688,"p95":288.77783355749546,"p99":330.51672576852735}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:45+01","p50":213.16388796500001,"p95":319.39473805074215,"p99":351.18802659971954}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:46+01","p50":229.6286215,"p95":298.96879081458866,"p99":340.49306620084207}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:47+01","p50":227.3207768,"p95":287.0410479432773,"p99":311.70544844752754}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:48+01","p50":224.02806297,"p95":280.3114930597334,"p99":367.70523267018575}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:49+01","p50":229.5843895555556,"p95":287.2554511472848,"p99":311.97252648708655}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:50+01","p50":226.86120169999998,"p95":297.8654988220625,"p99":324.8528261715774}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:51+01","p50":227.73033625,"p95":304.7452693077776,"p99":341.4966641090006}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:52+01","p50":229.43261528395064,"p95":329.4608420761883,"p99":360.45441631729886}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:53+01","p50":233.44179237037037,"p95":312.98086624497876,"p99":345.14207424462825}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:54+01","p50":224.76305555,"p95":340.9300107624405,"p99":375.940175119682}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:55+01","p50":226.80129230499998,"p95":289.6117079601,"p99":313.86055017679143}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:56+01","p50":231.6949609691358,"p95":287.8531604420075,"p99":306.5860878605385}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:57+01","p50":228.59216644499998,"p95":287.5490156433385,"p99":314.3855962102075}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:58+01","p50":229.8766922901235,"p95":300.1019888481416,"p99":317.0127521601634}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:43:59+01","p50":232.12182987654322,"p95":307.11689395967244,"p99":335.2247744974556}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:00+01","p50":242.03274538888886,"p95":359.7712343747422,"p99":401.0317873695488}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:01+01","p50":266.9841349765625,"p95":335.79608987190034,"p99":367.0953763685167}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:02+01","p50":227.54199040740738,"p95":352.7645655675264,"p99":384.78265775848007}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:03+01","p50":222.60332568500002,"p95":295.3146603694484,"p99":343.1357868478847}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:04+01","p50":227.53948897000004,"p95":286.3642131627198,"p99":307.7116735806589}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:05+01","p50":227.25509795999997,"p95":284.17668873254644,"p99":309.2026960913931}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:06+01","p50":229.002703175,"p95":288.51951185059767,"p99":307.3055339416721}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:07+01","p50":227.90352341999997,"p95":288.9389762859282,"p99":329.1693938114319}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:08+01","p50":226.700259525,"p95":294.5930806485263,"p99":320.7139166845181}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:09+01","p50":228.866691705,"p95":276.82862095413003,"p99":293.4586540926369}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:10+01","p50":230.24321654,"p95":291.5135872513003,"p99":321.6061616105754}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:11+01","p50":236.0547376296296,"p95":288.9985622980626,"p99":311.3583808458735}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:12+01","p50":232.31932432098768,"p95":297.6697089763983,"p99":328.42913460863093}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:13+01","p50":212.32758394444446,"p95":382.07793913507794,"p99":548.9407757282638}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:14+01","p50":222.2969005185185,"p95":457.4753442707809,"p99":517.0199566288567}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:15+01","p50":230.35156744444444,"p95":323.1959750449393,"p99":382.71152511653355}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:16+01","p50":237.35684742592593,"p95":322.60051178565215,"p99":349.8818183007172}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:17+01","p50":231.4525040246914,"p95":309.3719507983617,"p99":341.557965587876}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:18+01","p50":241.18758633333334,"p95":311.7570389046515,"p99":339.5561322990873}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:19+01","p50":232.93229305555556,"p95":362.1249660573662,"p99":388.89702842814336}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:20+01","p50":229.90953520987654,"p95":303.6292440166583,"p99":330.8865856812748}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:21+01","p50":236.90551922222224,"p95":300.9845318302042,"p99":334.66252012507255}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:22+01","p50":232.92155444444444,"p95":316.20003558752245,"p99":361.23624300495186}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:23+01","p50":231.0479861975308,"p95":312.2876871735196,"p99":354.6363389151595}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:24+01","p50":233.12522743209877,"p95":313.8506659652578,"p99":346.43192357389285}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:25+01","p50":248.16989191358022,"p95":318.2944778836649,"p99":343.74699294701884}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:26+01","p50":229.23957338271606,"p95":322.247106772999,"p99":397.41408053560554}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:27+01","p50":235.00642464814817,"p95":303.18924745042443,"p99":364.5679387146967}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:28+01","p50":237.69411672839507,"p95":328.83617695734245,"p99":367.39538193397186}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:29+01","p50":231.2979863333333,"p95":302.8665096443494,"p99":343.48030228696393}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:30+01","p50":246.83790505555555,"p95":323.3037096058812,"p99":354.97172799137496}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:31+01","p50":266.25567065625,"p95":381.7247247812427,"p99":408.5804087836418}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:32+01","p50":250.92026525000003,"p95":457.4742196225,"p99":518.8273736379653}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:33+01","p50":227.916567,"p95":335.5662265405199,"p99":373.54549423814433}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:34+01","p50":233.71457569135802,"p95":299.3744070049061,"p99":329.22239441504337}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:35+01","p50":229.9242444197531,"p95":310.27277688896584,"p99":343.97856698501363}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:36+01","p50":236.080865691358,"p95":296.2819347079152,"p99":324.3035311676905}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:37+01","p50":237.9940661111111,"p95":296.22591404141497,"p99":313.67635408998837}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:38+01","p50":234.7515991728395,"p95":295.0866239729916,"p99":328.48329296940375}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:39+01","p50":245.54870330864196,"p95":310.58100590344674,"p99":347.35950749358636}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:40+01","p50":236.0710164197531,"p95":303.0492662037728,"p99":339.2475535310731}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:41+01","p50":233.89671609259256,"p95":300.5567627187972,"p99":332.46072604693126}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:42+01","p50":233.92950116049386,"p95":304.69936422163414,"p99":351.7700806455592}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:43+01","p50":239.86293933333334,"p95":302.0943083402211,"p99":336.6915734134861}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:44+01","p50":237.01349629629627,"p95":293.9614255010273,"p99":316.271315578567}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:45+01","p50":241.03661314814815,"p95":293.95325496818117,"p99":311.90670126999265}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:46+01","p50":248.6397497222222,"p95":321.1083276743935,"p99":355.53217551825617}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:47+01","p50":236.83440436419752,"p95":307.77608052907715,"p99":336.16516838472984}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:48+01","p50":237.78182008641974,"p95":296.75326864755885,"p99":334.11718776960356}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:49+01","p50":247.34182591358024,"p95":316.32987245415734,"p99":345.93814732074355}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:50+01","p50":236.2530198888889,"p95":320.1980873117887,"p99":352.30335366892115}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:51+01","p50":242.4144787222222,"p95":294.36881323788333,"p99":321.2963556315209}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:52+01","p50":249.93633536419753,"p95":315.82175934213325,"p99":339.7380642858667}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:53+01","p50":249.5573993950617,"p95":320.7931308942644,"p99":345.27954962262606}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:54+01","p50":240.79447177777777,"p95":308.69344150712243,"p99":327.8232104120652}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:55+01","p50":243.64542290123458,"p95":306.03520016397994,"p99":327.64600322032805}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:56+01","p50":241.75838503703704,"p95":290.2335840942161,"p99":318.6701166426756}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:57+01","p50":244.1633235925926,"p95":306.87419694930196,"p99":333.6326824178183}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:58+01","p50":245.86742325925928,"p95":327.6107458238275,"p99":357.74392194234366}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:44:59+01","p50":241.5525650123457,"p95":314.85224848993306,"p99":336.1264216632122}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:00+01","p50":253.033641765625,"p95":360.22433500278765,"p99":394.2293334993372}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:01+01","p50":282.9873596530612,"p95":379.22299309784813,"p99":420.5163859353969}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:02+01","p50":261.2101283888889,"p95":349.5979372506568,"p99":380.0417791396627}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:03+01","p50":242.76836553086417,"p95":317.6506345034635,"p99":352.6504838312211}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:04+01","p50":242.29955274074075,"p95":315.85430882457143,"p99":379.5246074787372}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:05+01","p50":244.17885608641973,"p95":301.2072668961855,"p99":319.67482698173524}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:06+01","p50":246.99386586419755,"p95":294.79096765591675,"p99":323.8580152355954}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:07+01","p50":244.983715808642,"p95":320.0715005697858,"p99":359.3221083824115}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:08+01","p50":252.29098775925928,"p95":329.8536805369474,"p99":380.0450008629608}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:09+01","p50":248.63151905555554,"p95":312.04804377199986,"p99":339.19033159178736}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:10+01","p50":244.60815769135803,"p95":319.6249185810434,"p99":341.8980779112456}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:11+01","p50":243.62099285185184,"p95":316.3863579225111,"p99":359.2064474374562}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:12+01","p50":242.5257604197531,"p95":319.937880594814,"p99":345.90183122712205}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:13+01","p50":251.4814667160494,"p95":347.12832185550633,"p99":387.00833247104526}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:14+01","p50":244.03338811111112,"p95":329.5356001124202,"p99":353.70902560328153}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:15+01","p50":252.42461846875,"p95":352.93799996342045,"p99":396.57703143454955}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:16+01","p50":241.96543055555554,"p95":350.0999032539423,"p99":381.9900515419493}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:17+01","p50":237.49905385185184,"p95":348.28513285813546,"p99":378.2041651995187}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:18+01","p50":233.28525844444442,"p95":360.3378021336139,"p99":385.50966882857773}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:19+01","p50":235.27731401234567,"p95":392.25916967316795,"p99":415.1317684099419}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:20+01","p50":228.74149416666666,"p95":382.436929919615,"p99":417.29958860276696}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:21+01","p50":223.8554472222222,"p95":397.19822636716356,"p99":419.3491243445099}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:22+01","p50":228.93348565686276,"p95":390.45854274879633,"p99":418.0448409027219}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:23+01","p50":229.05229897530864,"p95":435.94131868004234,"p99":475.9577755427845}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:24+01","p50":224.6719653765432,"p95":404.15345074513885,"p99":431.1063728551382}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:25+01","p50":223.1738973271605,"p95":427.83862540364817,"p99":447.6870681274729}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:26+01","p50":219.4280582222222,"p95":424.4125297381542,"p99":452.2128250626185}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:27+01","p50":221.28637337499998,"p95":479.25856039177324,"p99":505.7553833616252}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:28+01","p50":220.13827125,"p95":497.69187694629755,"p99":521.2643295716357}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:29+01","p50":207.90608811111113,"p95":489.3627019916342,"p99":515.6024039426687}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:30+01","p50":220.88996113281254,"p95":557.7241497582178,"p99":605.0783330651728}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:31+01","p50":239.9994074387755,"p95":633.4437467143093,"p99":663.949303313808}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:32+01","p50":227.19496610937503,"p95":597.1201047446923,"p99":642.2356207996032}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:33+01","p50":208.9667705185185,"p95":534.2685504683992,"p99":565.457320744766}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:34+01","p50":197.3172998271605,"p95":577.5774809833255,"p99":615.3160591011061}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:35+01","p50":196.6841504814815,"p95":590.6343385262705,"p99":621.236526317459}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:36+01","p50":192.16300379012347,"p95":594.937989638396,"p99":616.6474479613671}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:37+01","p50":191.98542124074072,"p95":606.6746978951647,"p99":637.0841462819167}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:38+01","p50":199.094159140625,"p95":608.8222806475949,"p99":643.7600327943096}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:39+01","p50":198.6228311358025,"p95":603.4780945745752,"p99":633.6055038901814}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:40+01","p50":197.91195058024692,"p95":568.9742578396002,"p99":597.6751088185949}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:41+01","p50":187.92167739506175,"p95":587.6157216882028,"p99":630.434425031493}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:42+01","p50":193.8545081328125,"p95":576.7168153036739,"p99":609.471843039989}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:43+01","p50":182.7041825,"p95":626.5635759521475,"p99":656.6105761140519}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:44+01","p50":182.002420296875,"p95":595.2455152799928,"p99":666.524966823246}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:45+01","p50":191.43340067901235,"p95":541.5282873718245,"p99":577.426050821677}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:46+01","p50":199.72619957812498,"p95":643.5090654767054,"p99":675.4957638449421}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:47+01","p50":207.5420738271605,"p95":633.8917496826473,"p99":678.5703506387939}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:48+01","p50":164.81341753124997,"p95":634.4198897592404,"p99":662.86963345962}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:49+01","p50":174.15371123437498,"p95":654.5268345700912,"p99":689.1564842725492}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:50+01","p50":180.313614845679,"p95":666.8810886539575,"p99":726.1272797738256}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:51+01","p50":191.733401859375,"p95":630.6399569315726,"p99":646.1715571890819}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:52+01","p50":208.895401984375,"p95":606.6619703857724,"p99":640.2750225670086}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:53+01","p50":210.49941197656247,"p95":541.696203820779,"p99":577.569265250039}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:54+01","p50":254.5180668125,"p95":478.10806471849406,"p99":546.5435760246972}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:55+01","p50":229.85918274999997,"p95":546.262029470349,"p99":591.1771795484085}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:56+01","p50":174.65561250000002,"p95":607.8544517046315,"p99":634.0928913900585}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:57+01","p50":183.8232034921875,"p95":667.0396639921627,"p99":685.5928076427662}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:58+01","p50":195.130553140625,"p95":609.2753202277082,"p99":648.4411754986186}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:45:59+01","p50":183.1262176484375,"p95":641.7196272421642,"p99":675.980125018514}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:00+01","p50":175.864330359375,"p95":718.4392911164153,"p99":750.7176252789826}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:01+01","p50":259.4954200714286,"p95":777.5164146899575,"p99":813.5769222000613}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:02+01","p50":188.16883884375,"p95":730.0940563236533,"p99":775.5735355237389}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:03+01","p50":191.58450478125,"p95":620.0481701912838,"p99":668.8960170888228}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:04+01","p50":175.841472296875,"p95":632.8241188411656,"p99":665.6329794227838}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:05+01","p50":175.40988267187498,"p95":642.8582759503653,"p99":664.626595705243}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:06+01","p50":162.648231890625,"p95":711.5610653251928,"p99":735.6088418108253}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:07+01","p50":183.725167734375,"p95":713.8705531019855,"p99":738.3491766403465}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:08+01","p50":205.39213659876543,"p95":674.3890294992779,"p99":713.7598256706095}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:09+01","p50":232.22102699999996,"p95":515.8753497536136,"p99":546.1046437778377}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:10+01","p50":221.6818236796875,"p95":557.9633368008311,"p99":593.6550660394516}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:11+01","p50":201.258418984375,"p95":530.9680855909236,"p99":573.2599308174435}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:12+01","p50":211.8697521875,"p95":529.6859483082727,"p99":561.3951712317014}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:13+01","p50":206.766411484375,"p95":567.5206615242497,"p99":614.1787438853047}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:14+01","p50":304.241553859375,"p95":512.3258756998524,"p99":536.6138555834252}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:15+01","p50":303.19803565625006,"p95":489.98481228281395,"p99":522.9720132191463}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:16+01","p50":308.9834941796875,"p95":460.19994999820847,"p99":496.7734299254275}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:17+01","p50":287.605237484375,"p95":394.30359972952937,"p99":444.0751214773295}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:18+01","p50":275.86983460937495,"p95":404.98791292437505,"p99":449.0815109612393}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:19+01","p50":292.66104935937506,"p95":422.1997539417769,"p99":456.06440989265013}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:20+01","p50":256.19148906249995,"p95":455.1225759756719,"p99":478.6134592895532}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:21+01","p50":203.071612828125,"p95":599.7232979431302,"p99":652.3018377973488}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:22+01","p50":184.572660578125,"p95":708.3567861859196,"p99":749.9978142965715}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:23+01","p50":179.27201665625,"p95":742.0952314548227,"p99":801.2938597553085}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:24+01","p50":189.0404942421875,"p95":714.11674297969,"p99":738.5481027947288}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:25+01","p50":190.13964307812503,"p95":648.5751613377107,"p99":695.8864858107758}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:26+01","p50":185.01441,"p95":744.4800640991624,"p99":772.8078027810068}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:27+01","p50":176.379387765625,"p95":731.3401126864051,"p99":763.1530903995427}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:28+01","p50":183.20303623437502,"p95":694.7755455998175,"p99":726.9407615041866}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:29+01","p50":172.45682875000003,"p95":762.5253784376276,"p99":795.9366136895187}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:30+01","p50":154.4886441122449,"p95":742.4563853324298,"p99":778.4863342859178}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:31+01","p50":218.32736989795916,"p95":818.4413222946499,"p99":857.634896244293}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:32+01","p50":199.55808895918366,"p95":805.7552451962433,"p99":836.1199240491286}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:33+01","p50":184.13282901562502,"p95":724.3228182066305,"p99":747.5561410408754}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:34+01","p50":228.3469134453125,"p95":710.6347505156864,"p99":763.6017112995186}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:35+01","p50":219.93913431250002,"p95":624.2612106298724,"p99":691.6263712410774}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:36+01","p50":190.55343200000002,"p95":623.4027338032672,"p99":661.6895393242999}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:37+01","p50":212.14271306249998,"p95":588.2951044012299,"p99":620.9212367747192}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:38+01","p50":234.58604350000002,"p95":577.7735167885902,"p99":615.3833853898707}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:39+01","p50":239.61629159375,"p95":464.1651513131973,"p99":546.9423849048862}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:40+01","p50":292.46323740625,"p95":409.63920697818253,"p99":469.5404719516118}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:41+01","p50":227.675191125,"p95":503.85976609547976,"p99":553.924288017417}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:42+01","p50":226.64132925,"p95":603.5267217380252,"p99":644.8777912905831}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:43+01","p50":199.1105529375,"p95":643.8826120289554,"p99":672.7115565878887}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:44+01","p50":174.5728428828125,"p95":694.4032371676702,"p99":736.6072573001676}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:45+01","p50":172.085016125,"p95":743.4974839411611,"p99":795.3485996004028}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:46+01","p50":183.005939609375,"p95":754.6950008615736,"p99":786.6471891985135}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:47+01","p50":208.466747328125,"p95":706.2681708744914,"p99":737.65150653682}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:48+01","p50":246.9391240390625,"p95":590.9976223694864,"p99":665.6865845471425}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:49+01","p50":216.42823199999998,"p95":597.9715750012991,"p99":636.8980246520805}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:50+01","p50":251.113149375,"p95":581.0220640798034,"p99":619.7855946713543}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:51+01","p50":233.2108146328125,"p95":570.2510566929836,"p99":624.6462220877168}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:52+01","p50":223.53510228125,"p95":576.7560597538044,"p99":601.9914905856793}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:53+01","p50":256.2180309140625,"p95":519.3446504099135,"p99":552.7567635974259}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:54+01","p50":244.90737917187496,"p95":551.1302996945136,"p99":583.3979395160695}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:55+01","p50":244.2594149296875,"p95":578.3673924578494,"p99":611.230872182735}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:56+01","p50":271.960776625,"p95":506.1509154173652,"p99":543.6596564271631}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:57+01","p50":251.7292739921875,"p95":514.4367051248188,"p99":548.8995316722279}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:58+01","p50":257.4327385390625,"p95":551.2531465787594,"p99":600.7844830728993}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:46:59+01","p50":226.29275295918367,"p95":573.46389100471,"p99":607.488826888814}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:00+01","p50":158.01631525510203,"p95":688.4556226623076,"p99":726.1140858411645}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:01+01","p50":263.582781,"p95":785.0761925368176,"p99":834.3156718260684}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:02+01","p50":271.7865777346939,"p95":618.5424746651571,"p99":675.8486690594968}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:03+01","p50":251.528862140625,"p95":479.0324366316913,"p99":508.13196977759077}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:04+01","p50":272.764998,"p95":463.1456154859989,"p99":492.5051863097601}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:05+01","p50":251.34881190625,"p95":515.0898139056978,"p99":551.6839977448759}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:06+01","p50":178.59173807812502,"p95":607.2977458906694,"p99":650.6791628030777}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:07+01","p50":228.528214484375,"p95":612.7287301368407,"p99":662.3054589351979}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:08+01","p50":269.133687015625,"p95":487.560907427792,"p99":521.3191350775146}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:09+01","p50":289.095168,"p95":459.2765764709546,"p99":500.31729044287636}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:10+01","p50":237.16472143750002,"p95":495.3849000834253,"p99":519.7289208531666}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:11+01","p50":290.9932819375,"p95":463.7811181815878,"p99":519.7174620174709}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:12+01","p50":258.854821328125,"p95":480.86759390169664,"p99":517.8000236548042}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:13+01","p50":240.463201625,"p95":585.2571056350987,"p99":641.181980439687}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:14+01","p50":217.14943262500003,"p95":644.3636885138629,"p99":682.302154629669}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:15+01","p50":238.20522790625,"p95":592.661404156106,"p99":620.7804603361654}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:16+01","p50":253.40245743877554,"p95":538.3297126158285,"p99":593.0840711909748}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:17+01","p50":297.05636955102045,"p95":499.91498118751207,"p99":521.7519758634403}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:18+01","p50":288.1675955918367,"p95":491.402148217257,"p99":544.5610512041274}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:19+01","p50":260.479531578125,"p95":510.95569657420856,"p99":609.1119799084473}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:20+01","p50":284.88819455102043,"p95":546.3360550552809,"p99":593.226342864191}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:21+01","p50":316.47126771874997,"p95":397.17570840094487,"p99":545.4049952443041}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:22+01","p50":300.7380733673469,"p95":390.06157530521165,"p99":434.4400018280599}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:23+01","p50":317.3342994296875,"p95":471.1538393901341,"p99":498.7268114635818}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:24+01","p50":312.3114796953125,"p95":479.9899206529178,"p99":508.73059234328105}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:25+01","p50":303.44096385714283,"p95":432.24111573472936,"p99":481.15480448994924}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:26+01","p50":295.2548508469387,"p95":414.8377016582926,"p99":454.60888461715126}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:27+01","p50":282.79908851562504,"p95":442.81139118377405,"p99":472.3974882147641}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:28+01","p50":265.6355371020408,"p95":542.0216534247596,"p99":598.2860635018455}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:29+01","p50":306.47830520408166,"p95":520.2475861884038,"p99":598.9080517879603}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:30+01","p50":336.10097310204077,"p95":521.6890196125762,"p99":590.9175295823351}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:31+01","p50":327.7472398333333,"p95":683.1995407896691,"p99":729.6082376169705}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:32+01","p50":269.0071397142857,"p95":631.085476807135,"p99":666.320322275085}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:33+01","p50":295.03508512244895,"p95":589.338263032539,"p99":631.7643346847112}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:34+01","p50":249.8829897142857,"p95":583.8902808736984,"p99":619.9919637827294}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:35+01","p50":252.86402424489796,"p95":573.0543828894796,"p99":598.4470002061379}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:36+01","p50":219.82085685714287,"p95":579.4600081886587,"p99":621.7511878869092}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:37+01","p50":245.50718857142854,"p95":616.2387932650793,"p99":678.351689813037}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:38+01","p50":250.49263643877552,"p95":602.5751674895109,"p99":641.185069785058}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:39+01","p50":233.51451746938775,"p95":612.6201817991691,"p99":648.9237323123665}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:40+01","p50":216.0034506122449,"p95":569.0684257077188,"p99":620.8073523999672}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:41+01","p50":250.4272383671875,"p95":600.218748778727,"p99":656.0170346708195}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:42+01","p50":206.4858886122449,"p95":629.3186483907494,"p99":678.8911320336118}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:43+01","p50":277.00825892857137,"p95":535.3907336961008,"p99":592.3082711132507}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:44+01","p50":252.9347802857143,"p95":498.12035919245324,"p99":617.8742110423693}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:45+01","p50":254.9217653061224,"p95":517.4190094289047,"p99":564.0159873910244}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:46+01","p50":267.98508674489796,"p95":537.5984335198178,"p99":584.562653411211}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:47+01","p50":311.4633385816326,"p95":519.1129243049223,"p99":541.8988053559627}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:48+01","p50":340.391139744898,"p95":471.9430775032337,"p99":497.39183832360317}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:49+01","p50":367.09360151020417,"p95":520.4733203573115,"p99":564.6715372804565}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:50+01","p50":337.7105508775511,"p95":530.2094405821729,"p99":578.1108267085857}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:51+01","p50":300.3466880612245,"p95":571.241293134362,"p99":611.3414432796807}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:52+01","p50":247.19063304081632,"p95":647.1589957449572,"p99":674.28939230445}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:53+01","p50":255.582212125,"p95":637.857276136227,"p99":677.6535077579799}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:54+01","p50":296.7022103367347,"p95":451.5140622661691,"p99":546.5301015580468}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:55+01","p50":283.5862878367347,"p95":440.25651179571787,"p99":468.89946693176364}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:56+01","p50":314.56377830612246,"p95":438.53360951214563,"p99":480.9842112853651}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:57+01","p50":302.2940052857143,"p95":455.12424920122913,"p99":495.88523729636097}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:58+01","p50":268.82244810204077,"p95":569.849262972912,"p99":620.3263257291067}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:47:59+01","p50":216.31429573469387,"p95":611.0886964712064,"p99":647.7019792534868}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:00+01","p50":217.94831810204082,"p95":594.550644939399,"p99":650.7976690020821}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:01+01","p50":313.5328812222222,"p95":723.875334869905,"p99":775.6773649439392}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:02+01","p50":250.93725970408164,"p95":685.7735414509722,"p99":720.7377627211465}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:03+01","p50":202.44430571428572,"p95":647.4959573656823,"p99":680.689731691963}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:04+01","p50":183.31401714285715,"p95":671.2769395130892,"p99":710.5193306389084}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:05+01","p50":182.30028423469386,"p95":587.8527808406545,"p99":629.5860477890368}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:06+01","p50":208.96191375510207,"p95":595.1338501026898,"p99":635.7649387988863}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:07+01","p50":151.1706621836735,"p95":615.8021337093631,"p99":654.5062712858038}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:08+01","p50":202.9434863877551,"p95":610.7181859192923,"p99":644.4745153171597}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:09+01","p50":216.3411271122449,"p95":554.995367901815,"p99":612.3734393997192}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:10+01","p50":251.48215348979588,"p95":604.7923876059812,"p99":638.4972198188}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:11+01","p50":267.63262057142856,"p95":561.875279334098,"p99":599.8694049838954}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:12+01","p50":270.4322100612245,"p95":605.7305240160875,"p99":641.9719404369918}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:13+01","p50":307.4224391836735,"p95":466.3995110011762,"p99":523.641878718961}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:14+01","p50":291.61121361224497,"p95":441.45558275731696,"p99":480.8666306603563}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:15+01","p50":262.130187377551,"p95":575.5714525922123,"p99":611.5036096487136}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:16+01","p50":264.17186257142856,"p95":607.0769702199005,"p99":645.565693578789}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:17+01","p50":228.8468317142857,"p95":719.1735706277701,"p99":754.8677493859416}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:18+01","p50":219.38354603061225,"p95":748.7627153832609,"p99":772.63493998806}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:19+01","p50":166.42149718367344,"p95":757.3193736554908,"p99":788.6931652842703}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:20+01","p50":199.4642451632653,"p95":691.3631589838145,"p99":728.2780502629342}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:21+01","p50":197.2340604489796,"p95":638.0026640036712,"p99":673.4952658540275}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:22+01","p50":180.60037740816327,"p95":630.5889597556105,"p99":689.0832717456519}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:23+01","p50":173.7753414387755,"p95":670.8670350198843,"p99":732.9260544131212}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:24+01","p50":213.12469955102043,"p95":750.9354503928142,"p99":805.5751668207803}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:25+01","p50":247.2843455714286,"p95":736.4944946133068,"p99":780.7774727300904}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:26+01","p50":218.10012491836736,"p95":730.837990191177,"p99":778.9385891134154}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:27+01","p50":262.002551122449,"p95":700.5616359582401,"p99":756.9048626470887}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:28+01","p50":289.7629767040816,"p95":672.3096414120706,"p99":728.4566563747978}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:29+01","p50":264.45821714285717,"p95":726.7055618058066,"p99":774.2369000614326}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:30+01","p50":262.5185723265306,"p95":704.4912015363733,"p99":758.7186197180715}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:31+01","p50":275.39993044444446,"p95":820.9562307042065,"p99":866.4028677042666}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:32+01","p50":263.83988056944446,"p95":791.1161073841017,"p99":823.3289688891775}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:33+01","p50":293.15725185714285,"p95":714.45539924708,"p99":746.5249241090489}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:34+01","p50":277.87784152040814,"p95":606.5874947062987,"p99":643.4790099747429}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:35+01","p50":315.46635521428567,"p95":585.6415155324551,"p99":638.0046302508625}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:36+01","p50":293.73228835714286,"p95":568.4201803959612,"p99":601.677044758728}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:37+01","p50":304.021280755102,"p95":534.1499460419994,"p99":566.0640603125194}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:38+01","p50":291.27754679591834,"p95":596.8311867759784,"p99":637.7817745441773}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:39+01","p50":291.7688119591836,"p95":570.5321142449335,"p99":606.8888851653304}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:40+01","p50":220.57998365306125,"p95":648.0817195437627,"p99":690.4200619598942}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:41+01","p50":181.35881422448978,"p95":630.6161193099124,"p99":663.1168061252422}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:42+01","p50":233.47329135714284,"p95":629.8588389740045,"p99":681.69014375104}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:43+01","p50":206.12514671428568,"p95":634.7490289664078,"p99":668.1127304456902}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:44+01","p50":230.05858548979592,"p95":610.4103756934267,"p99":647.3481937841301}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:45+01","p50":227.6803363163265,"p95":652.7560365684249,"p99":674.2915624855079}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:46+01","p50":219.16174310204082,"p95":654.8532907665656,"p99":681.5681750076035}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:47+01","p50":261.55030512244895,"p95":701.8015864900239,"p99":745.0727206702061}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:48+01","p50":199.50218967346942,"p95":720.286582982032,"p99":761.7266217137771}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:49+01","p50":207.7118072361111,"p95":697.3990260208933,"p99":737.2076939909625}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:50+01","p50":229.83912271428568,"p95":642.5168307631476,"p99":718.6087997955627}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:51+01","p50":199.4962662244898,"p95":725.2457697092244,"p99":767.3374947650689}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:52+01","p50":225.12055385714282,"p95":792.3813827129654,"p99":834.9856485649822}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:53+01","p50":275.20692365306127,"p95":767.3419334572144,"p99":812.4921307580223}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:54+01","p50":287.75460132653063,"p95":615.8124095386316,"p99":644.4055491448177}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:55+01","p50":324.6991015612245,"p95":450.5179323133089,"p99":481.17750134109593}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:56+01","p50":309.69928067346933,"p95":462.22181212931645,"p99":494.2742319524162}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:57+01","p50":336.73044108163265,"p95":432.12092161979996,"p99":494.46356324406554}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:58+01","p50":298.5005638163266,"p95":536.7536071167829,"p99":577.6714997420926}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:48:59+01","p50":256.16331506122447,"p95":630.5773388793722,"p99":674.6860054113004}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:00+01","p50":315.01835883333337,"p95":628.3399353781933,"p99":659.8670093930912}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:01+01","p50":326.41320616666667,"p95":664.2803946569802,"p99":705.5644797870874}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:02+01","p50":333.11842710204076,"p95":658.0510888573646,"p99":738.0648201247335}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:03+01","p50":267.6654635714286,"p95":630.5173188595422,"p99":666.5418691842566}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:04+01","p50":250.1251854489796,"p95":668.4904877046738,"p99":715.8688544002647}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:05+01","p50":239.87387402040818,"p95":700.7402548585661,"p99":732.7372357321857}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:06+01","p50":308.1462176836735,"p95":672.9652978880952,"p99":732.0085463307366}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:07+01","p50":271.5102277142857,"p95":635.6537464520687,"p99":680.2661475241514}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:08+01","p50":194.82290154081633,"p95":678.943645395757,"p99":708.2607905562362}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:09+01","p50":195.82785436734693,"p95":664.6283447619234,"p99":716.5776645683796}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:10+01","p50":179.55097002040816,"p95":652.1014714476751,"p99":708.4647517406023}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:11+01","p50":267.15907522448975,"p95":662.8770245201224,"p99":697.7052784995032}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:12+01","p50":252.65635636734692,"p95":699.9834343847629,"p99":756.2344805371079}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:13+01","p50":168.83482230612245,"p95":775.6032698063703,"p99":802.3864349583652}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:14+01","p50":177.04911975510205,"p95":759.9058693882338,"p99":794.8441235651238}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:15+01","p50":215.46989947959182,"p95":827.9849969941054,"p99":863.5576883822783}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:16+01","p50":244.72863320408163,"p95":819.8267221073417,"p99":861.9256245875945}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:17+01","p50":247.65149255102043,"p95":816.893547750237,"p99":861.397996304507}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:18+01","p50":190.79785398979593,"p95":755.5367149874246,"p99":798.9995327060509}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:19+01","p50":177.90122285714284,"p95":842.5017985169943,"p99":901.0984668560262}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:20+01","p50":181.24320881632656,"p95":723.0778248226597,"p99":753.7043911433326}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:21+01","p50":232.86854135714287,"p95":807.4350707396437,"p99":849.8820889607906}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:22+01","p50":226.3353195408163,"p95":763.0796598611014,"p99":802.684185327797}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:23+01","p50":285.10776713265307,"p95":755.7566123523562,"p99":806.283001315978}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:24+01","p50":278.96483992857145,"p95":564.1073512942185,"p99":601.303322523529}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:25+01","p50":230.11129585714284,"p95":572.2232770836935,"p99":621.8223184013094}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:26+01","p50":260.4715514081633,"p95":589.653434636575,"p99":646.3135151688205}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:27+01","p50":297.49965203061225,"p95":605.7346359881351,"p99":653.595959161232}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:28+01","p50":324.20771035714284,"p95":612.1225503336275,"p99":664.0239953370972}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:29+01","p50":242.86036108163265,"p95":637.8542562071713,"p99":691.5349705285105}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:30+01","p50":326.9510569444444,"p95":659.4176353137568,"p99":689.3172762019271}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:31+01","p50":422.1142157777778,"p95":660.2246952363407,"p99":701.9573469096808}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:32+01","p50":407.29931266666665,"p95":686.1880085401222,"p99":768.2805382751241}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:33+01","p50":370.35678734693875,"p95":631.171413237976,"p99":691.6925847327595}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:34+01","p50":385.4349486527778,"p95":620.3432873032896,"p99":664.9538954432413}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:35+01","p50":349.7823824591837,"p95":528.6318580349683,"p99":570.5756554172879}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:36+01","p50":293.44755961224485,"p95":581.4513809351383,"p99":631.3032772461875}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:37+01","p50":158.1090405,"p95":771.046252974354,"p99":833.8409945352173}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:38+01","p50":210.12150216326532,"p95":810.136747740759,"p99":850.43340344699}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:39+01","p50":233.41591395918365,"p95":787.104556139013,"p99":821.9807377053685}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:40+01","p50":211.39938287755103,"p95":649.5231221465376,"p99":681.7740350028846}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:41+01","p50":187.79516838775507,"p95":694.2393694875407,"p99":737.8125144993144}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:42+01","p50":178.11608097959183,"p95":784.4959204703014,"p99":810.8484535742016}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:43+01","p50":212.56817719444442,"p95":807.9591018700502,"p99":859.8467408750333}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:44+01","p50":245.03757230612246,"p95":809.037358257567,"p99":874.7658788003873}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:45+01","p50":196.24992130612245,"p95":898.4895167553177,"p99":953.3507689584064}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:46+01","p50":190.3318681388889,"p95":914.2285259831106,"p99":959.8850585649524}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:47+01","p50":195.5173472346939,"p95":959.3202006818742,"p99":1000.9494398815613}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:48+01","p50":184.50272018367346,"p95":975.0540629023902,"p99":1028.0474435122735}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:49+01","p50":172.43706444444445,"p95":1000.5504190296685,"p99":1039.3101055874263}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:50+01","p50":176.12599834693876,"p95":991.955907383474,"p99":1056.651332457127}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:51+01","p50":192.83361381632656,"p95":929.4040478826789,"p99":961.6774082491893}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:52+01","p50":171.06279916666668,"p95":1050.2466231297776,"p99":1086.2698165653587}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:53+01","p50":187.69819093877553,"p95":966.6000803524362,"p99":1038.5667927939362}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:54+01","p50":262.93424997222223,"p95":805.2355285315774,"p99":835.6190509706734}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:55+01","p50":263.7011396666667,"p95":749.0425741900706,"p99":785.7489275768019}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:56+01","p50":254.5929839897959,"p95":689.0688472197627,"p99":735.3318426415015}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:57+01","p50":229.01028008163266,"p95":736.0207365457726,"p99":767.4870870355835}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:58+01","p50":177.41243418055558,"p95":821.9965485097788,"p99":868.7488204838979}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:49:59+01","p50":177.5423672142857,"p95":854.8054950166754,"p99":891.4001902251564}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:00+01","p50":232.751821125,"p95":792.7245134323476,"p99":841.6830461617395}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:01+01","p50":357.09595363999995,"p95":807.6742122680157,"p99":868.5508825909972}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:02+01","p50":384.88291988888886,"p95":693.7077141471891,"p99":748.7991827527824}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:03+01","p50":341.3901864583333,"p95":618.2006362886523,"p99":671.7348371837898}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:04+01","p50":290.0262582777778,"p95":551.2350136023898,"p99":590.4993630156627}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:05+01","p50":247.53281379591837,"p95":585.1334404714663,"p99":671.7946695821286}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:06+01","p50":245.1405831734694,"p95":627.3119392067134,"p99":672.2021893241043}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:07+01","p50":263.0313428333333,"p95":710.7437825144805,"p99":757.3655499176946}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:08+01","p50":313.2946479489796,"p95":632.3995838705497,"p99":690.6386440958757}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:09+01","p50":309.71640130612246,"p95":646.5312788463018,"p99":692.3444851869435}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:10+01","p50":291.5116583333333,"p95":678.9456039414844,"p99":719.4741016060143}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:11+01","p50":247.4525357638889,"p95":715.3150937066807,"p99":770.202491234325}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:12+01","p50":256.91562170833333,"p95":822.8229415507406,"p99":857.3877650207448}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:13+01","p50":350.2451974583333,"p95":694.180970263835,"p99":734.4712791127859}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:14+01","p50":295.8837635,"p95":643.1692278796513,"p99":676.5734446503601}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:15+01","p50":249.25259957142856,"p95":731.039217044241,"p99":783.4135469863903}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:16+01","p50":282.1010397222222,"p95":621.2189683105596,"p99":686.1081747415816}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:17+01","p50":317.7719535714286,"p95":620.9165889371661,"p99":662.0153017419891}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:18+01","p50":304.9711680833334,"p95":711.6286877260729,"p99":765.1888006898537}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:19+01","p50":245.3608416122449,"p95":753.3537800879025,"p99":788.3542437851373}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:20+01","p50":267.3894616527778,"p95":765.120830193491,"p99":806.7844042345974}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:21+01","p50":252.00112095833333,"p95":795.4309959551565,"p99":846.250153396117}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:22+01","p50":257.9827359166666,"p95":848.151917783076,"p99":896.2650979179803}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:23+01","p50":274.145184,"p95":821.2434141199059,"p99":860.4601457637034}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:24+01","p50":217.65360108333334,"p95":830.1614667523102,"p99":874.2310621651154}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:25+01","p50":233.60031431944446,"p95":907.1971434224346,"p99":955.9839069642389}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:26+01","p50":240.85364033333335,"p95":878.2735575402669,"p99":921.9643030966624}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:27+01","p50":224.50892091666665,"p95":972.9795623815575,"p99":1024.9794388472706}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:28+01","p50":219.21432555555555,"p95":932.9332859249272,"p99":965.5029334533549}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:29+01","p50":245.21078283333335,"p95":898.8331366967443,"p99":935.8155560190335}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:30+01","p50":251.73489925,"p95":924.6551552393952,"p99":963.7606569456129}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:31+01","p50":299.15889634,"p95":1009.3684660938048,"p99":1067.5733165765564}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:32+01","p50":283.00178158333335,"p95":892.9365963166667,"p99":972.7566402801285}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:33+01","p50":288.59570141666666,"p95":642.9699988920811,"p99":704.2038800256644}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:34+01","p50":233.91520419444444,"p95":640.5056968653475,"p99":692.4353522680283}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:35+01","p50":243.48975591666667,"p95":629.7289437817052,"p99":662.980682823232}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:36+01","p50":367.4264822777778,"p95":570.6297589094617,"p99":621.7960802736907}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:37+01","p50":412.21100561111115,"p95":531.0147252686044,"p99":560.8520791238547}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:38+01","p50":302.31776891666664,"p95":538.7371326112262,"p99":570.3056439926338}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:39+01","p50":350.01690983333333,"p95":557.9754440309506,"p99":598.8796463162976}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:40+01","p50":369.10604508333336,"p95":548.6413507899823,"p99":588.074490153387}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:41+01","p50":352.52418083333333,"p95":509.1970042143006,"p99":545.1269255462641}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:42+01","p50":275.8046955,"p95":610.1278769276375,"p99":653.1770788058777}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:43+01","p50":337.0602440555556,"p95":528.0543318146509,"p99":562.3203251728974}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:44+01","p50":333.31347808333334,"p95":520.0800473239761,"p99":550.8248432725978}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:45+01","p50":369.3045985833333,"p95":542.7586470700342,"p99":585.1772218148546}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:46+01","p50":385.1074580972222,"p95":594.2531054522882,"p99":637.8252573396504}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:47+01","p50":359.75081897222225,"p95":616.818020960564,"p99":658.4227053491068}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:48+01","p50":318.0803700833333,"p95":592.3211290232764,"p99":636.3279250522747}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:49+01","p50":352.8745123888889,"p95":575.7061632431127,"p99":620.2092781273814}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:50+01","p50":294.6118072777777,"p95":732.6763470138973,"p99":795.7476610492917}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:51+01","p50":267.6389947777778,"p95":853.6423991551874,"p99":893.7280611513605}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:52+01","p50":298.8672672083333,"p95":720.270760875473,"p99":760.1027405553074}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:53+01","p50":363.66741581944444,"p95":602.2334419589254,"p99":672.6714655328321}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:54+01","p50":360.3935595138889,"p95":563.8654476013079,"p99":635.6237797320375}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:55+01","p50":353.06049720833335,"p95":673.2495430821555,"p99":709.3764554137927}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:56+01","p50":345.24859388888893,"p95":716.6402988058396,"p99":749.9069588414832}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:57+01","p50":316.7553612777778,"p95":629.8471193551975,"p99":736.9077736421423}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:58+01","p50":350.3084880833333,"p95":540.2617259051588,"p99":566.9858631039102}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:50:59+01","p50":329.7124712222222,"p95":654.1963415827814,"p99":692.2894115461464}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:00+01","p50":363.11332699999997,"p95":638.1175898229219,"p99":677.778917627408}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:01+01","p50":361.9246164,"p95":728.8194141236572,"p99":772.4014526322086}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:02+01","p50":344.6417695,"p95":710.514338173093,"p99":767.9764669562712}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:03+01","p50":315.6137375555555,"p95":651.1352718685192,"p99":716.6396755126839}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:04+01","p50":359.2164488333333,"p95":593.1473668153159,"p99":667.4548380854272}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:05+01","p50":341.7379495833333,"p95":622.148440790928,"p99":653.823010676464}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:06+01","p50":327.4376976666667,"p95":559.2251195744841,"p99":582.0864893680587}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:07+01","p50":295.15239225,"p95":638.0812943355162,"p99":685.7684850193697}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:08+01","p50":258.798005,"p95":675.9539531302865,"p99":723.2838926055374}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:09+01","p50":206.4661363888889,"p95":772.3226981780624,"p99":790.5112328309054}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:10+01","p50":172.57769662500002,"p95":866.0717009368169,"p99":914.9698191842862}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:11+01","p50":145.25547579166667,"p95":946.3163659245613,"p99":977.525876203806}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:12+01","p50":193.2539854722222,"p95":818.383654799432,"p99":913.586492557867}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:13+01","p50":225.6351079722222,"p95":768.7019840160299,"p99":820.8716055379806}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:14+01","p50":229.01689916666666,"p95":747.215407369273,"p99":790.2111607016062}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:15+01","p50":221.292599,"p95":750.4961695073712,"p99":808.9807229809342}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:16+01","p50":196.37402872222222,"p95":682.0777072098721,"p99":726.7288219186311}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:17+01","p50":190.16513420833334,"p95":812.5413360454965,"p99":849.8157338033662}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:18+01","p50":232.73869458333334,"p95":847.4000609675222,"p99":896.2574517781492}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:19+01","p50":360.4496757499999,"p95":759.564010738389,"p99":802.210356711471}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:20+01","p50":382.0703809583333,"p95":584.8737398498881,"p99":641.557236670868}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:21+01","p50":378.28542605555555,"p95":537.2307462181835,"p99":572.3123693292084}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:22+01","p50":373.43254791666664,"p95":508.34406448673883,"p99":556.2622691921134}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:23+01","p50":313.0650181666667,"p95":555.1118203865398,"p99":608.1270624399718}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:24+01","p50":222.93760040277778,"p95":658.7465430108713,"p99":704.0114360467727}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:25+01","p50":207.34105824999997,"p95":702.0649725402367,"p99":753.6413390549736}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:26+01","p50":227.73295469444443,"p95":747.5707576870867,"p99":793.3291291221905}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:27+01","p50":272.50558872222217,"p95":764.6816371163563,"p99":813.2427938974666}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:28+01","p50":309.5283793055555,"p95":798.9674634760178,"p99":857.6943701513884}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:29+01","p50":252.50616216666666,"p95":770.9116246683192,"p99":829.7381835070153}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:30+01","p50":271.7295911666667,"p95":674.7639156325763,"p99":747.8882395540962}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:31+01","p50":321.60635128,"p95":747.4322600361223,"p99":829.7056477928247}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:32+01","p50":318.55685791999997,"p95":728.6779540911377,"p99":796.0917552800045}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:33+01","p50":253.72161077777778,"p95":744.3839663405748,"p99":805.5960536532798}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:34+01","p50":221.1902518333333,"p95":801.5295453612677,"p99":841.4301221009894}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:35+01","p50":235.79668816666666,"p95":947.6681222798079,"p99":1002.7705592868974}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:36+01","p50":237.13540481944446,"p95":1035.0199011551333,"p99":1078.6594361096372}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:37+01","p50":237.27923127777777,"p95":1005.3791963613697,"p99":1034.6381518837509}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:38+01","p50":232.37556958333332,"p95":964.200127590515,"p99":999.270264032548}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:39+01","p50":172.6535795,"p95":883.3304856135723,"p99":922.2330521395636}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:40+01","p50":235.3118431111111,"p95":1007.030670971171,"p99":1059.2463906274756}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:41+01","p50":219.22015383333334,"p95":966.1499451770582,"p99":1013.814959957652}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:42+01","p50":225.4246216111111,"p95":937.9560786580615,"p99":990.525491020605}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:43+01","p50":266.998449,"p95":768.472677443443,"p99":790.68812258214}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:44+01","p50":296.4195334583334,"p95":759.7153912791351,"p99":807.9325334881524}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:45+01","p50":329.8890652777778,"p95":739.2803737327665,"p99":776.3784747708817}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:46+01","p50":369.5049073333334,"p95":765.0827141163503,"p99":812.9511989784386}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:47+01","p50":405.6622713611111,"p95":658.6494135970574,"p99":725.5720878641205}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:48+01","p50":376.35138683333327,"p95":636.9394394852822,"p99":678.2554176954127}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:49+01","p50":288.6572325833333,"p95":680.9975577590938,"p99":741.0568015681433}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:50+01","p50":337.7684687222222,"p95":668.4837382185967,"p99":730.8973774902496}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:51+01","p50":354.4068409166666,"p95":687.3171094744396,"p99":747.503416478281}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:52+01","p50":284.7650793333333,"p95":756.0798964911882,"p99":803.8046861301827}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:53+01","p50":243.71827522222222,"p95":855.6706006548725,"p99":886.6307743973313}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:54+01","p50":239.34775395833333,"p95":829.8078331581468,"p99":864.7351892999089}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:55+01","p50":289.6232888888889,"p95":805.6281692743639,"p99":855.7631043456125}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:56+01","p50":213.705781625,"p95":680.8015810177518,"p99":716.3165355049147}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:57+01","p50":218.7783870833333,"p95":792.4774368050212,"p99":826.3914821593476}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:58+01","p50":223.99196520833334,"p95":809.4883584725319,"p99":849.7334008862052}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:51:59+01","p50":225.57330788888888,"p95":837.6030075170193,"p99":910.6171174994888}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:00+01","p50":261.6736129444444,"p95":834.8122725692295,"p99":892.5535712757959}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:01+01","p50":336.57737352,"p95":887.7374767717981,"p99":954.9117732258312}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:02+01","p50":297.7958395416667,"p95":937.9005997186459,"p99":974.8765939926308}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:03+01","p50":278.77338683333335,"p95":908.478637977017,"p99":951.2951484017616}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:04+01","p50":220.33508416666666,"p95":873.6952795659374,"p99":945.6623612358437}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:05+01","p50":218.15404141666667,"p95":804.3374894404935,"p99":887.1643425245933}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:06+01","p50":285.3974315555555,"p95":869.5817736027927,"p99":915.3567266545468}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:07+01","p50":334.5556079305555,"p95":816.1295129409742,"p99":924.1645395825865}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:08+01","p50":306.11594533333334,"p95":791.2671940574018,"p99":823.2272334731022}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:09+01","p50":332.39367563999997,"p95":804.1929120134095,"p99":896.089106693659}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:10+01","p50":283.97461400000003,"p95":520.8344369048348,"p99":568.1455581599979}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:11+01","p50":349.30411050000004,"p95":616.9979405553923,"p99":655.9401847056141}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:12+01","p50":375.1463212,"p95":621.2467017191784,"p99":664.0508424550491}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:13+01","p50":311.39326124999997,"p95":583.0275376605474,"p99":678.9757754074459}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:14+01","p50":365.36966268055556,"p95":517.3713077985053,"p99":556.6780555533378}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:15+01","p50":410.59621538,"p95":585.4724612005226,"p99":638.4622551732244}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:16+01","p50":322.9337403611111,"p95":538.5112158947547,"p99":617.1411458983166}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:17+01","p50":362.5340684166667,"p95":481.27532426773587,"p99":534.3989468561473}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:18+01","p50":406.0639786666666,"p95":528.6984088255444,"p99":556.4567750179897}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:19+01","p50":378.12681788888887,"p95":549.6776664390405,"p99":590.5081452322896}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:20+01","p50":348.86642324999997,"p95":592.8788972859822,"p99":648.0182775590066}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:21+01","p50":374.99033488888887,"p95":595.3042290766754,"p99":660.1157346963976}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:22+01","p50":394.17485813888896,"p95":602.4777883754703,"p99":666.7406430166192}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:23+01","p50":384.54747966666656,"p95":767.4054292080198,"p99":844.0674283322068}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:24+01","p50":419.57192394444445,"p95":639.1985472911435,"p99":666.9092054162006}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:25+01","p50":385.01371131944444,"p95":593.4732597692307,"p99":671.9583502351868}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:26+01","p50":368.3308778611111,"p95":517.6831989334784,"p99":581.6018405815163}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:27+01","p50":382.4727758888889,"p95":547.0931887992382,"p99":593.3894839185056}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:28+01","p50":370.07349355555556,"p95":620.2916959594695,"p99":706.923731942607}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:29+01","p50":364.36369145833334,"p95":590.376672124801,"p99":655.1386691270626}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:30+01","p50":326.19569012,"p95":739.1644861075238,"p99":847.881542571065}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:31+01","p50":457.39104491999996,"p95":889.4244497556896,"p99":930.6864593652197}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:32+01","p50":385.83988927999997,"p95":745.2166101923433,"p99":821.6973727743919}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:33+01","p50":447.7296030416667,"p95":686.4642273047496,"p99":744.4630491057422}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:34+01","p50":364.9335878333334,"p95":643.830875092489,"p99":731.5411683412381}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:35+01","p50":239.17875716666666,"p95":789.9590224970556,"p99":837.0256470445022}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:36+01","p50":232.99302149999997,"p95":773.1262954996162,"p99":809.0378805053864}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:37+01","p50":196.97383750000003,"p95":767.206744473455,"p99":798.0031691178723}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:38+01","p50":224.09097855555555,"p95":830.6913760480581,"p99":861.5883874630814}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:39+01","p50":248.64919499999996,"p95":770.9210702745752,"p99":814.2055792747278}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:40+01","p50":288.8832440555555,"p95":791.8469247168061,"p99":821.6319206646576}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:41+01","p50":307.5551613333333,"p95":812.6314984557199,"p99":875.3220388074997}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:42+01","p50":263.26160687500004,"p95":693.3723035327848,"p99":784.438192099159}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:43+01","p50":275.0909941388889,"p95":675.7917381694069,"p99":720.7460705019286}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:44+01","p50":268.8368551666667,"p95":694.8647338612408,"p99":747.4254714066393}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:45+01","p50":310.0996642222222,"p95":644.2566701408876,"p99":712.6995620103797}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:46+01","p50":326.9766276388889,"p95":685.6952607063548,"p99":738.8868778545265}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:47+01","p50":409.75493,"p95":690.136426862182,"p99":732.7927522789984}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:48+01","p50":436.1781789583334,"p95":602.6775699672609,"p99":662.0720164259795}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:49+01","p50":397.2522094722222,"p95":712.6670809711751,"p99":752.8393592412498}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:50+01","p50":385.7045836944444,"p95":679.4701803967641,"p99":727.6730696094652}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:51+01","p50":416.7350911527778,"p95":567.1270516653473,"p99":629.982962326778}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:52+01","p50":395.3485313333333,"p95":657.659761369291,"p99":707.075718515871}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:53+01","p50":324.32118744444443,"p95":821.3542146693491,"p99":859.0360721342458}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:54+01","p50":304.3428443055555,"p95":813.4875807056729,"p99":866.0953501121521}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:55+01","p50":297.1517840277778,"p95":792.7633554876207,"p99":844.8658474071922}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:56+01","p50":332.60000537499997,"p95":826.6443667570861,"p99":890.5930670700983}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:57+01","p50":372.30711272222226,"p95":758.0525675261204,"p99":809.7245128334522}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:58+01","p50":304.41271751388894,"p95":858.1220593646757,"p99":902.3199152902644}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:52:59+01","p50":341.358296125,"p95":791.3732858433074,"p99":831.5479848800275}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:00+01","p50":368.5528496,"p95":741.1958927923525,"p99":772.8089488782324}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:01+01","p50":394.45223963999996,"p95":850.2547978410255,"p99":902.0851749574022}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:02+01","p50":344.71889369999997,"p95":839.9161523393681,"p99":907.6541181421223}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:03+01","p50":377.2381041666666,"p95":701.9813667115608,"p99":762.6677455976487}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:04+01","p50":332.411377625,"p95":767.6817025133928,"p99":811.0842237062594}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:05+01","p50":394.9147620555555,"p95":703.4400714141362,"p99":798.9047652727122}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:06+01","p50":343.6006621666667,"p95":597.6747144737091,"p99":644.5087135134887}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:07+01","p50":378.1326162222222,"p95":581.184018396926,"p99":621.3988198252964}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:08+01","p50":340.17102,"p95":725.7103397322296,"p99":767.4708243471863}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:09+01","p50":386.2321766666667,"p95":690.4641615081554,"p99":750.2768240456156}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:10+01","p50":341.84729439999995,"p95":713.5109754160393,"p99":779.9890986686635}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:11+01","p50":318.49557716666664,"p95":696.5810967809877,"p99":732.9240224781194}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:12+01","p50":375.0555484722222,"p95":643.7833107005183,"p99":709.2490573056906}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:13+01","p50":340.18869461111115,"p95":637.37439026828,"p99":686.0178707066663}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:14+01","p50":396.09816787499994,"p95":704.4209652609845,"p99":765.3058866658316}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:15+01","p50":389.4643341944445,"p95":633.8318001342496,"p99":731.0643432822428}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:16+01","p50":284.7880068333333,"p95":678.5356099516601,"p99":713.6480968486939}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:17+01","p50":376.0422746944444,"p95":739.9962334918392,"p99":775.5075578829145}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:18+01","p50":409.1034925000001,"p95":634.43206756217,"p99":702.0922816836243}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:19+01","p50":382.5472612777778,"p95":624.4558370640053,"p99":685.2042066444998}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:20+01","p50":377.47892422222225,"p95":641.320727382314,"p99":685.4550028454837}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:21+01","p50":385.94666960000006,"p95":697.0560304781274,"p99":755.487760128266}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:22+01","p50":438.45296852777784,"p95":642.9136879666701,"p99":671.3878626511779}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:23+01","p50":437.10219468055556,"p95":574.6737047722418,"p99":619.8709956932539}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:24+01","p50":379.16992286111116,"p95":551.4591229632356,"p99":581.4295108524966}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:25+01","p50":375.40319275,"p95":587.6738454205366,"p99":636.0937416565845}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:26+01","p50":401.31185567999995,"p95":656.4257006806112,"p99":687.5971995276983}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:27+01","p50":456.27446533333335,"p95":585.4698940101434,"p99":622.9307585471022}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:28+01","p50":396.20219716,"p95":634.0018777546911,"p99":678.2594843880145}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:29+01","p50":434.50417411111107,"p95":623.0007670908152,"p99":650.724139146936}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:30+01","p50":424.5066547,"p95":672.9092010085528,"p99":717.2891449701591}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:31+01","p50":434.75345056,"p95":665.5107148074112,"p99":708.7747189676713}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:32+01","p50":483.16504688000003,"p95":681.7897638719528,"p99":745.655702756788}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:33+01","p50":413.39353927777773,"p95":590.0480181090198,"p99":655.9893154499312}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:34+01","p50":415.0310074166666,"p95":611.5296181354103,"p99":662.7930506260986}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:35+01","p50":371.86373125,"p95":633.9961798270682,"p99":693.6297971340542}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:36+01","p50":351.80695497222223,"p95":674.0125164805722,"p99":726.6331989368584}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:37+01","p50":296.48255159999997,"p95":888.5716013743852,"p99":954.5629962548713}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:38+01","p50":292.04048416666666,"p95":879.1548737936603,"p99":949.9540517755048}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:39+01","p50":246.0174892777778,"p95":824.965561712392,"p99":897.8978984506731}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:40+01","p50":237.7705973888889,"p95":740.0725950936458,"p99":803.3794989738702}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:41+01","p50":231.15823144444445,"p95":803.5425579858188,"p99":844.9466545300922}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:42+01","p50":216.43426149999996,"p95":824.2761332189064,"p99":879.2194844873964}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:43+01","p50":249.48521912,"p95":894.38316735234,"p99":929.0837991929143}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:44+01","p50":283.45253104166665,"p95":846.5877458305217,"p99":898.3621037102973}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:45+01","p50":316.94064724,"p95":849.6438059263613,"p99":886.5365854898353}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:46+01","p50":346.119021,"p95":746.7282455432893,"p99":784.5593017268067}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:47+01","p50":363.697700875,"p95":758.7561317782719,"p99":811.5567042350829}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:48+01","p50":275.36590816666666,"p95":778.4693146747056,"p99":855.5455380702457}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:49+01","p50":290.8124626,"p95":847.7370841571593,"p99":907.3133227836933}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:50+01","p50":361.7866794444445,"p95":879.7080822376165,"p99":922.6077304433136}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:51+01","p50":357.9138285833333,"p95":845.2916342662621,"p99":888.427658538208}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:52+01","p50":330.14235826,"p95":820.7346740064464,"p99":877.160341989295}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:53+01","p50":381.0325011111111,"p95":804.7511741871082,"p99":858.7214423855572}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:54+01","p50":387.5051856,"p95":822.2259824908244,"p99":885.8470550928803}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:55+01","p50":384.6572393,"p95":861.7827497379739,"p99":923.4825342802438}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:56+01","p50":328.92843077777775,"p95":773.6982395651048,"p99":827.2388824170542}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:57+01","p50":382.76444324000005,"p95":808.9609603459417,"p99":876.9974567397275}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:58+01","p50":412.96602607999995,"p95":739.0029966562278,"p99":798.0999166121173}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:53:59+01","p50":342.20051233333334,"p95":749.5185807205829,"p99":782.6177797616576}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:00+01","p50":394.701882,"p95":780.0519333322796,"p99":829.0074917296662}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:01+01","p50":370.50946704,"p95":956.6801471348592,"p99":1015.9650052171261}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:02+01","p50":406.35339740000006,"p95":965.7036588731289,"p99":1032.9974992184282}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:03+01","p50":411.4556474,"p95":662.7960667226583,"p99":728.7094533433857}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:04+01","p50":406.37992088,"p95":655.2238341901138,"p99":693.1924472899337}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:05+01","p50":403.11374272222224,"p95":619.272154066654,"p99":678.9464765433988}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:06+01","p50":421.90354444,"p95":543.8354732568521,"p99":612.5916831497136}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:07+01","p50":384.632138,"p95":656.6191064848235,"p99":696.8630291268482}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:08+01","p50":338.61795136,"p95":673.909299900646,"p99":722.8354763130308}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:09+01","p50":429.83440252,"p95":631.3158834182082,"p99":668.301307234765}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:10+01","p50":397.8500336666666,"p95":528.9896323749066,"p99":579.3771011491699}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:11+01","p50":366.43875280000003,"p95":576.0389320557467,"p99":601.0840738320446}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:12+01","p50":377.24842808000005,"p95":626.6542265569894,"p99":679.381295528687}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:13+01","p50":432.33425314,"p95":541.1568412028666,"p99":583.7253049821547}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:14+01","p50":381.2746884,"p95":614.9666958594009,"p99":691.9696509354982}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:15+01","p50":330.803917,"p95":677.1682500547681,"p99":742.1522610069972}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:16+01","p50":309.9333351,"p95":758.9540969489733,"p99":813.463337098773}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:17+01","p50":358.95499368000003,"p95":737.9214126075019,"p99":778.5644817269149}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:18+01","p50":397.663931,"p95":615.086217850904,"p99":668.0484956535645}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:19+01","p50":429.00513308,"p95":669.0946374348083,"p99":728.85036035331}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:20+01","p50":413.0990724,"p95":622.9957779690224,"p99":664.8194154293737}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:21+01","p50":290.07376312,"p95":685.5403460886738,"p99":721.9188822499709}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:22+01","p50":311.77640024000004,"p95":728.9294482312265,"p99":801.7153805051167}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:23+01","p50":355.1588239166667,"p95":646.052827836689,"p99":695.4105803787842}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:24+01","p50":353.45010192,"p95":669.9484256486783,"p99":710.8693406583424}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:25+01","p50":335.30985410000005,"p95":691.7627321485306,"p99":726.9044959324378}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:26+01","p50":361.13428883999995,"p95":756.4065765432156,"p99":815.5474131759453}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:27+01","p50":361.29307375,"p95":696.2274641508275,"p99":765.0978776066285}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:28+01","p50":416.3067763,"p95":685.738203201137,"p99":739.8430238757658}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:29+01","p50":330.0524271,"p95":744.6750916592404,"p99":783.2408023358279}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:30+01","p50":361.60821496,"p95":816.0494606199702,"p99":880.8009484371357}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:31+01","p50":433.30588981249997,"p95":908.1685612947623,"p99":984.3471195292307}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:32+01","p50":448.93784936000003,"p95":783.5184148589387,"p99":845.8501551917205}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:33+01","p50":335.99682996,"p95":731.324016796836,"p99":776.238572359648}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:34+01","p50":326.83725564,"p95":767.1644797290053,"p99":810.4942556510016}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:35+01","p50":310.5724983,"p95":840.6513940942654,"p99":863.0169384689345}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:36+01","p50":373.3023782,"p95":692.7452524695855,"p99":754.0653348087969}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:37+01","p50":460.20648517999996,"p95":646.6387354728828,"p99":683.9232266571579}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:38+01","p50":332.05750980000005,"p95":674.3638019451939,"p99":729.6529745516267}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:39+01","p50":477.50348312000006,"p95":650.8963044132549,"p99":706.2729387592335}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:40+01","p50":480.75029911111113,"p95":602.0398662610263,"p99":638.5837775093694}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:41+01","p50":408.25061588,"p95":584.343231112489,"p99":633.2007006967955}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:42+01","p50":414.2472136,"p95":538.9985778976721,"p99":568.296154559248}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:43+01","p50":402.67941962,"p95":677.268038578432,"p99":739.2684555479947}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:44+01","p50":421.0484436,"p95":621.4037148245721,"p99":684.0350968217099}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:45+01","p50":385.34943350000003,"p95":663.1105020731571,"p99":713.5566042757835}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:46+01","p50":363.171233,"p95":670.1359688457547,"p99":713.3349683781001}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:47+01","p50":404.91409696,"p95":646.1998698443601,"p99":754.4473631028923}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:48+01","p50":397.43670232,"p95":629.2294473823131,"p99":697.1562948713538}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:49+01","p50":378.22623488000005,"p95":704.0032898766115,"p99":749.4917683284268}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:50+01","p50":323.4843978,"p95":747.7976574681868,"p99":790.1089498177142}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:51+01","p50":316.09317272000004,"p95":806.04750913099,"p99":854.3686610661658}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:52+01","p50":318.64449682,"p95":841.7025510754512,"p99":896.8443007886262}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:53+01","p50":331.94399924000004,"p95":754.7488961179165,"p99":785.4900998050819}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:54+01","p50":286.56402147999995,"p95":901.4052410586368,"p99":961.3330722875982}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:55+01","p50":289.12937582000006,"p95":1011.6713439832068,"p99":1045.5264822261424}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:56+01","p50":248.80357315999998,"p95":1006.6597335838775,"p99":1028.6829051979237}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:57+01","p50":187.0497954,"p95":1267.3960736373888,"p99":1334.6425493148404}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:58+01","p50":199.72879476,"p95":1354.489945877821,"p99":1400.629926331944}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:54:59+01","p50":216.1498943,"p95":1308.5300745160987,"p99":1331.8017563382855}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:00+01","p50":233.1045344,"p95":1284.5872392383894,"p99":1352.0405371455438}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:01+01","p50":241.49801943750003,"p95":1287.4417678376371,"p99":1383.3185035379445}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:02+01","p50":211.95823230000002,"p95":1361.2079540751129,"p99":1416.453834296713}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:03+01","p50":236.40451296,"p95":1329.779486412736,"p99":1381.085196059685}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:04+01","p50":259.50443052,"p95":1067.6386801126544,"p99":1145.2690279007836}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:05+01","p50":242.27871055999998,"p95":1015.4942670567623,"p99":1074.7681892114329}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:06+01","p50":251.12089559999998,"p95":992.9381983987165,"p99":1036.4950490827541}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:07+01","p50":285.9962614,"p95":1136.0268257834432,"p99":1214.2336846307574}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:08+01","p50":298.24962196,"p95":1135.6397944598243,"p99":1189.5013802177325}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:09+01","p50":286.32753518,"p95":957.562279237845,"p99":1006.6251888044386}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:10+01","p50":323.96486949999996,"p95":944.4131883608749,"p99":981.8378903168444}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:11+01","p50":299.30596732,"p95":909.5205953234912,"p99":953.0456752045827}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:12+01","p50":344.56753968,"p95":917.0416467262091,"p99":1006.7959972350883}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:13+01","p50":330.03559808,"p95":901.7160891404394,"p99":944.0302666649898}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:14+01","p50":293.2251769999999,"p95":926.8758345855962,"p99":962.5475740588192}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:15+01","p50":271.38507772,"p95":939.0260261340137,"p99":981.4483618870797}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:16+01","p50":309.44405906000003,"p95":784.318831007983,"p99":844.3583583810993}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:17+01","p50":432.62145244000004,"p95":710.6471413851959,"p99":739.9856701339851}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:18+01","p50":350.60745327999996,"p95":719.0148563938918,"p99":756.085466977768}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:19+01","p50":330.54460592000004,"p95":775.002291225549,"p99":819.0590204817838}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:20+01","p50":334.48834784,"p95":759.2517875605816,"p99":828.1892589598165}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:21+01","p50":397.36779564,"p95":882.8732611015357,"p99":922.7172349714148}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:22+01","p50":416.47482736,"p95":865.7065040765826,"p99":903.262753823052}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:23+01","p50":346.9420717,"p95":802.9601666098233,"p99":869.4614825801162}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:24+01","p50":393.87548252,"p95":770.0980893793998,"p99":806.603586890467}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:25+01","p50":458.96309936000006,"p95":703.7090115036873,"p99":775.9134350690108}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:26+01","p50":461.0146954,"p95":669.759415087366,"p99":709.5994916988902}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:27+01","p50":425.45649543999997,"p95":742.147750426179,"p99":787.2429684761734}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:28+01","p50":434.48275373999996,"p95":628.3262293999,"p99":717.4064534631467}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:29+01","p50":351.32969132,"p95":749.9548065761353,"p99":806.0872950974605}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:30+01","p50":297.78048636000005,"p95":826.1648954241546,"p99":895.0294472455206}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:31+01","p50":384.2033020625,"p95":1070.5739817144542,"p99":1112.1202494332254}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:32+01","p50":353.64675193999994,"p95":1189.604973768994,"p99":1247.2020603480319}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:33+01","p50":239.14545788,"p95":1166.6974851019536,"p99":1219.1489738891078}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:34+01","p50":216.83242976000002,"p95":1170.4503308862318,"p99":1227.5429726298332}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:35+01","p50":229.08312379999998,"p95":1155.2040464554627,"p99":1204.177650796592}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:36+01","p50":254.2850476,"p95":1185.5965109716474,"p99":1248.9033482931156}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:37+01","p50":270.36888932,"p95":1159.9119408946651,"p99":1209.0197779631992}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:38+01","p50":253.41361988,"p95":1139.6311330202384,"p99":1162.285131187622}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:39+01","p50":281.60743564,"p95":1103.8471568950902,"p99":1142.455159528547}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:40+01","p50":229.25446229999997,"p95":933.9261076707617,"p99":996.3348238082093}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:41+01","p50":267.50354166,"p95":1088.2970881620877,"p99":1125.5946009694267}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:42+01","p50":265.96746326,"p95":1007.9630611809492,"p99":1066.388558937748}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:43+01","p50":244.5367071,"p95":958.2433373874197,"p99":1067.626232143448}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:44+01","p50":272.4118856,"p95":1036.7074471437304,"p99":1078.4103036432953}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:45+01","p50":287.90826804000005,"p95":1019.7026013280017,"p99":1092.7689554055778}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:46+01","p50":262.67606642,"p95":1081.5903545411152,"p99":1127.3948215575942}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:47+01","p50":231.43718866,"p95":1114.029116508869,"p99":1183.3765995373917}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:48+01","p50":253.85626417999998,"p95":1252.0888013438864,"p99":1276.2174379807989}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:49+01","p50":239.131615,"p95":1209.7755027840565,"p99":1265.36922884206}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:50+01","p50":212.59336468,"p95":1276.3480767885167,"p99":1316.5846206872138}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:51+01","p50":214.89168692000004,"p95":1265.238003397526,"p99":1320.3595291856782}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:52+01","p50":212.62718678000002,"p95":1283.3832648901775,"p99":1319.0670669120966}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:53+01","p50":233.84688764000003,"p95":1267.240974392183,"p99":1306.3195936194436}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:54+01","p50":224.05557508,"p95":1234.3282995541706,"p99":1293.8881523143507}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:55+01","p50":247.94847004,"p95":1292.078441751325,"p99":1356.6597680590241}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:56+01","p50":277.4196574,"p95":1186.2048899884826,"p99":1254.8225845562017}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:57+01","p50":262.6753578,"p95":1122.7872047415071,"p99":1182.028884562132}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:58+01","p50":264.60421183999995,"p95":1117.4284230075123,"p99":1160.0261269237694}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:55:59+01","p50":270.93567306,"p95":1126.4508775776849,"p99":1156.1352791619677}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:00+01","p50":237.0544943,"p95":1213.3388356086052,"p99":1285.231468485841}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:01+01","p50":340.224278625,"p95":1386.6449382029614,"p99":1468.9306646416908}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:02+01","p50":391.75232736,"p95":1381.1741532211217,"p99":1438.7461658934014}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:03+01","p50":409.5893499,"p95":908.9975410322901,"p99":943.8937028508496}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:04+01","p50":502.98675156,"p95":735.6130979813775,"p99":825.0827324411514}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:05+01","p50":508.62081959999995,"p95":647.7398416454902,"p99":697.9107535818921}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:06+01","p50":479.00760568000004,"p95":631.7469312590968,"p99":681.7494679837584}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:07+01","p50":435.13712391999996,"p95":600.3594325278127,"p99":647.4719589252825}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:08+01","p50":388.3946441,"p95":604.7476320897559,"p99":647.6033462540167}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:09+01","p50":340.48776962,"p95":703.1193632544762,"p99":783.3057744908529}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:10+01","p50":333.56228219999997,"p95":722.3168222679459,"p99":766.4055108214656}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:11+01","p50":350.37652607999996,"p95":765.7146108421124,"p99":824.5666915061942}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:12+01","p50":382.86146764,"p95":826.5064959743003,"p99":872.9591447135977}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:13+01","p50":353.6670913,"p95":873.8096013045111,"p99":907.5215508249341}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:14+01","p50":352.35307961999996,"p95":910.1929521207294,"p99":977.2790004754486}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:15+01","p50":315.63083922,"p95":878.2206149153399,"p99":930.7301026867217}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:16+01","p50":416.16674439999997,"p95":808.4342742193945,"p99":887.5345496570649}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:17+01","p50":385.26746048000007,"p95":796.1051157837645,"p99":855.0251652078518}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:18+01","p50":458.79918055999997,"p95":793.6403815422891,"p99":884.1727401358299}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:19+01","p50":458.4300303,"p95":702.8565842657522,"p99":748.811872859414}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:20+01","p50":461.01460324,"p95":635.757179931597,"p99":683.6643829145517}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:21+01","p50":429.90323279999996,"p95":743.9629846954317,"p99":798.2602812370186}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:22+01","p50":402.54342540000005,"p95":786.6608894061912,"p99":837.5974862978171}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:23+01","p50":405.03802892000004,"p95":722.1222634662902,"p99":778.7402504746435}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:24+01","p50":368.5930135,"p95":711.4263894876838,"p99":759.9696588325396}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:25+01","p50":430.68463762,"p95":736.6715483106835,"p99":805.8435429784103}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:26+01","p50":481.2318462,"p95":703.3188934801223,"p99":765.5434543798857}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:27+01","p50":365.186744,"p95":693.326585856878,"p99":749.5361973901668}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:28+01","p50":363.4449766000001,"p95":690.1448051502995,"p99":731.2481323553895}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:29+01","p50":281.57706012000006,"p95":816.1336498138564,"p99":877.6273295394625}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:30+01","p50":377.18776829999996,"p95":847.2516219483769,"p99":900.22625928404}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:31+01","p50":497.5834539375,"p95":891.7020322620158,"p99":953.9438775516348}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:32+01","p50":346.8785468125,"p95":1045.9009802910373,"p99":1090.9405255936204}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:33+01","p50":273.31321859999997,"p95":1004.8453879768746,"p99":1081.6911939864942}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:34+01","p50":332.16112519999996,"p95":1095.0452094078066,"p99":1167.7051165375794}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:35+01","p50":337.61471372,"p95":1035.2542212101482,"p99":1084.1257866699298}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:36+01","p50":311.04855354,"p95":1059.9076032960595,"p99":1124.9549142783128}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:37+01","p50":270.09264676000004,"p95":1135.8208467005065,"p99":1185.8908946884296}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:38+01","p50":259.252566,"p95":1120.8785571228354,"p99":1183.2332453106371}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:39+01","p50":315.0637054,"p95":944.4408038219177,"p99":986.8414174087259}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:40+01","p50":314.37342288,"p95":954.8589981008489,"p99":980.7044269218632}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:41+01","p50":284.30682627999994,"p95":901.7362358870563,"p99":986.3828686475931}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:42+01","p50":315.17893456,"p95":930.3146580690732,"p99":1014.3349843842475}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:43+01","p50":290.040257,"p95":1119.0786337004793,"p99":1199.1782786195888}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:44+01","p50":299.33398334,"p95":1062.2987158529656,"p99":1140.803423950081}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:45+01","p50":233.46090107999999,"p95":1151.6376701296144,"p99":1200.0341685499686}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:46+01","p50":247.38120411999998,"p95":1279.937013403983,"p99":1324.121274298983}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:47+01","p50":227.3517311,"p95":1303.1708704527339,"p99":1360.12331886654}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:48+01","p50":256.2809042,"p95":1119.040681131122,"p99":1199.965282231458}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:49+01","p50":230.85126449999998,"p95":1133.1275066933395,"p99":1197.757758316496}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:50+01","p50":260.99369866,"p95":1134.7345127580911,"p99":1183.3118349570263}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:51+01","p50":227.1546781,"p95":1072.272919942504,"p99":1118.9456006087723}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:52+01","p50":259.33545352,"p95":986.9217335892054,"p99":1047.464165256409}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:53+01","p50":301.8407956,"p95":972.5192322878748,"p99":1032.1156223417897}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:54+01","p50":326.76326792,"p95":862.810722775288,"p99":909.5803762079476}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:55+01","p50":300.38906490000005,"p95":839.3019853332406,"p99":893.4715725576935}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:56+01","p50":359.06145146,"p95":844.8173108700956,"p99":883.135729778678}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:57+01","p50":299.48482640000003,"p95":970.1896724877304,"p99":1034.5983656418257}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:58+01","p50":397.57804764,"p95":856.8922947259601,"p99":903.2902919370122}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:56:59+01","p50":392.54401054,"p95":875.0276883630997,"p99":943.955445407579}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:00+01","p50":349.1070972,"p95":943.5794664721861,"p99":1005.3166373994055}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:01+01","p50":339.6679836875,"p95":1177.2743886517549,"p99":1226.2079772180114}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:02+01","p50":343.13432793749996,"p95":1235.222528610471,"p99":1317.8757675563809}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:03+01","p50":366.30110812,"p95":986.8015307402529,"p99":1023.7419323131263}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:04+01","p50":399.51522624,"p95":713.1408000887947,"p99":747.2741061325378}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:05+01","p50":434.72579620000005,"p95":662.2130725297279,"p99":726.5785288847532}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:06+01","p50":397.67283878,"p95":647.7514479274649,"p99":689.2638160795187}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:07+01","p50":385.50906384,"p95":738.7249611085691,"p99":767.9808155936778}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:08+01","p50":383.41557939999996,"p95":856.4393977894537,"p99":925.3709467741288}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:09+01","p50":483.68991476,"p95":729.586450087727,"p99":800.4132629093657}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:10+01","p50":406.90425,"p95":737.1791104025131,"p99":791.2790262248509}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:11+01","p50":389.58412928,"p95":729.2618919992746,"p99":800.4653494611495}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:12+01","p50":415.36276990000005,"p95":679.061726705846,"p99":722.4347528260846}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:13+01","p50":406.98933339999996,"p95":652.6531125879239,"p99":682.2326805354953}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:14+01","p50":467.97655999999995,"p95":740.2319270467935,"p99":786.0745924850826}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:15+01","p50":355.94396952,"p95":860.0352539588087,"p99":897.2271763138933}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:16+01","p50":289.25486692,"p95":942.2256321551854,"p99":997.7677184795206}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:17+01","p50":319.76045672,"p95":877.0618981598789,"p99":932.0780652918746}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:18+01","p50":239.80404577999997,"p95":1024.6173090271666,"p99":1077.2964688776822}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:19+01","p50":193.12364966,"p95":1030.8926519002496,"p99":1067.1008136204987}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:20+01","p50":236.5812899,"p95":1009.4241185782694,"p99":1035.776187787364}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:21+01","p50":277.49069776,"p95":876.9815180302694,"p99":929.6678462571867}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:22+01","p50":275.66744987999994,"p95":1056.1014057409034,"p99":1105.0969600038793}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:23+01","p50":248.80205772,"p95":1207.202113449867,"p99":1269.7558420112814}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:24+01","p50":272.8927695,"p95":1188.182763308424,"p99":1249.3211266131764}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:25+01","p50":294.4491807200001,"p95":1125.4400339690906,"p99":1171.4966970980677}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:26+01","p50":389.73766844,"p95":1047.001018540144,"p99":1108.892007179311}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:27+01","p50":429.3190364,"p95":836.2903029179487,"p99":881.7743468848786}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:28+01","p50":468.54090252,"p95":900.7679829712415,"p99":940.1121784611819}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:29+01","p50":411.47991916,"p95":905.4550502238745,"p99":943.3211215307244}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:30+01","p50":362.02141049999994,"p95":906.8196147758464,"p99":983.4989074641609}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:31+01","p50":322.90900075,"p95":995.1203460261723,"p99":1036.8146382049924}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:32+01","p50":367.79457290625,"p95":981.7886828799245,"p99":1028.8226607578586}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:33+01","p50":269.2401516,"p95":926.0037455867906,"p99":980.0305938557015}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:34+01","p50":305.7290486,"p95":1011.01607576981,"p99":1069.7811194305343}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:35+01","p50":214.19705972000003,"p95":1070.000221884475,"p99":1113.7809740610257}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:36+01","p50":258.64008864,"p95":1091.5989622175243,"p99":1149.0595232886817}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:37+01","p50":291.27162884000006,"p95":1107.9505514600635,"p99":1150.0936595297223}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:38+01","p50":320.56000464,"p95":1159.3710620537454,"p99":1203.6356690551675}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:39+01","p50":356.8938183,"p95":1079.5747307954462,"p99":1163.176085797266}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:40+01","p50":405.11472952,"p95":897.5538978986646,"p99":956.6281736585164}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:41+01","p50":432.35478720000003,"p95":779.5281100456482,"p99":814.4864391696188}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:42+01","p50":387.58351419999997,"p95":829.0659139947232,"p99":888.3248607584267}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:43+01","p50":314.01460787999997,"p95":800.7819089373688,"p99":856.6462762104177}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:44+01","p50":356.01624095999995,"p95":812.1045285450529,"p99":864.358327268134}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:45+01","p50":504.3944483,"p95":742.8679540730515,"p99":824.1621478287697}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:46+01","p50":549.9859488,"p95":739.7809005018709,"p99":787.2670317863198}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:47+01","p50":469.2477424,"p95":728.6841429041207,"p99":795.5359488160102}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:48+01","p50":508.6552557000001,"p95":670.3795027329365,"p99":709.8069249460946}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:49+01","p50":445.6615128,"p95":745.1682351088618,"p99":809.0169110135179}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:50+01","p50":374.5514552,"p95":737.247035157303,"p99":794.3447574525082}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:51+01","p50":295.62798232,"p95":793.9530607237452,"p99":845.5056677283487}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:52+01","p50":259.0301415,"p95":924.8302857645881,"p99":969.3365612119379}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:53+01","p50":290.14288554,"p95":911.5893949432145,"p99":945.2653490212193}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:54+01","p50":239.15335439999998,"p95":1030.444572739606,"p99":1069.8451879358477}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:55+01","p50":300.46250799999996,"p95":1017.4782258791332,"p99":1070.154116551463}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:56+01","p50":266.01037540000004,"p95":1039.0952350391083,"p99":1092.2099289953956}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:57+01","p50":336.5110432,"p95":1141.4368568790583,"p99":1201.3220397920322}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:58+01","p50":366.72726339999997,"p95":1065.8914943852085,"p99":1111.7190018627057}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:57:59+01","p50":360.09214504,"p95":996.0890198895962,"p99":1095.8170484538296}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:00+01","p50":364.69347832000005,"p95":877.163821657981,"p99":915.0921963467737}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:01+01","p50":435.21682175,"p95":1075.719895153781,"p99":1148.560603641164}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:02+01","p50":479.05649071875,"p95":1101.1120018213037,"p99":1191.1826187366066}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:03+01","p50":370.72535492,"p95":889.8102047709954,"p99":946.7171505680153}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:04+01","p50":334.21516304,"p95":1044.505268094341,"p99":1139.0388111009938}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:05+01","p50":375.86099730000007,"p95":1133.0229097328704,"p99":1173.9939707185335}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:06+01","p50":419.54386940000006,"p95":980.3225509994079,"p99":1096.0016857619055}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:07+01","p50":336.733586,"p95":994.9971102801377,"p99":1045.5975751770723}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:08+01","p50":316.00438790000004,"p95":1052.7515636397752,"p99":1107.8133244757555}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:09+01","p50":351.2290178,"p95":965.8748839099898,"p99":1047.39176631782}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:10+01","p50":346.80602652000005,"p95":1037.3550456325868,"p99":1076.1083508352124}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:11+01","p50":452.24222088,"p95":834.8852481205188,"p99":903.6534870733628}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:12+01","p50":464.18115020000005,"p95":642.0411864112673,"p99":710.4150309925842}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:13+01","p50":375.479682,"p95":758.1107541409783,"p99":802.7778872081605}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:14+01","p50":431.81022466,"p95":858.300869132487,"p99":933.592865034019}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:15+01","p50":489.45943449999993,"p95":775.6314540364419,"p99":847.962337112061}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:16+01","p50":469.27894860000004,"p95":750.888097725657,"p99":819.4368593248557}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:17+01","p50":466.82771539999993,"p95":843.7135111978948,"p99":938.9659155833066}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:18+01","p50":519.77498144,"p95":714.7735984568184,"p99":746.0568035888793}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:19+01","p50":520.3147824,"p95":709.6108389318147,"p99":790.4423354204082}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:20+01","p50":454.69084088,"p95":802.3370231192147,"p99":836.5269660078272}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:21+01","p50":431.32321250000007,"p95":909.5782643657427,"p99":964.2061518528128}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:22+01","p50":381.73367096,"p95":924.9803347393224,"p99":982.8420958155704}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:23+01","p50":331.68117876,"p95":907.8721970337026,"p99":942.3215469965783}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:24+01","p50":326.01035024000004,"p95":1016.4654596040541,"p99":1075.19524213198}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:25+01","p50":355.54321664,"p95":1039.717569216236,"p99":1093.8185934755784}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:26+01","p50":278.84134770000003,"p95":1146.205299194525,"p99":1218.7177286993829}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:27+01","p50":292.4578058,"p95":1211.9907382275708,"p99":1262.1156826691547}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:28+01","p50":283.08576955999996,"p95":1211.8622107159185,"p99":1297.7322183675662}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:29+01","p50":289.05377608000003,"p95":1284.6774725367175,"p99":1351.480960181459}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:30+01","p50":345.92260221875,"p95":1279.7275864510298,"p99":1367.5057884460196}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:31+01","p50":398.19659884375,"p95":1192.7272584847365,"p99":1290.1957617024964}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:32+01","p50":313.882550375,"p95":1306.5175193080547,"p99":1374.3497744433437}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:33+01","p50":275.54068558000006,"p95":1223.05735029875,"p99":1319.7532198173103}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:34+01","p50":267.74866144,"p95":1219.9975267540933,"p99":1264.5567571637346}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:35+01","p50":315.7367923,"p95":1151.3250680019476,"p99":1202.8329975055103}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:36+01","p50":320.5500758,"p95":1065.2840770677215,"p99":1132.0997277355489}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:37+01","p50":396.04671274000003,"p95":1068.8524999815636,"p99":1119.202173020711}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:38+01","p50":338.08247484000003,"p95":1007.4665267653726,"p99":1072.954123484378}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:39+01","p50":364.3668196,"p95":754.791949644097,"p99":803.2388631714265}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:40+01","p50":360.61949568,"p95":856.8443697463345,"p99":913.9182722375676}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:41+01","p50":349.72353869999995,"p95":1061.3458826271446,"p99":1118.3944011214542}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:42+01","p50":239.64374082,"p95":1182.507627784883,"p99":1227.9732746189527}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:43+01","p50":235.7197858,"p95":1355.4293380407967,"p99":1408.8571447679087}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:44+01","p50":296.78245810000004,"p95":1408.1687905492415,"p99":1453.917711219625}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:45+01","p50":300.50966776000007,"p95":1246.7138667026784,"p99":1341.7172133704016}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:46+01","p50":333.28718572,"p95":1152.5320147501513,"p99":1192.4113165128663}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:47+01","p50":377.97702024,"p95":1115.7254581891364,"p99":1178.832249058434}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:48+01","p50":327.9865061875,"p95":949.9168810982022,"p99":1018.5666551298614}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:49+01","p50":329.2999094,"p95":977.4308532567962,"p99":1016.857414300109}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:50+01","p50":458.51724915999995,"p95":831.740744931358,"p99":877.7489285528956}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:51+01","p50":467.44700520000004,"p95":824.0415255390159,"p99":870.1199001065771}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:52+01","p50":461.57772850000003,"p95":855.2744091000475,"p99":883.0852859742498}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:53+01","p50":484.25451164,"p95":813.6726527877815,"p99":903.8292531857317}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:54+01","p50":562.74375368,"p95":714.645227059034,"p99":769.4900637607064}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:55+01","p50":558.3744868399999,"p95":714.603362480608,"p99":787.0594298704743}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:56+01","p50":528.065768,"p95":726.3140412589262,"p99":768.4639606287608}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:57+01","p50":468.44308806,"p95":730.607578660367,"p99":777.5005196939392}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:58+01","p50":480.1072402,"p95":771.4684741980727,"p99":811.3600906097755}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:58:59+01","p50":423.52134676,"p95":806.102781696187,"p99":849.4244295275721}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:00+01","p50":447.6473225,"p95":767.0564484497323,"p99":814.7158534860421}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:01+01","p50":670.1639395,"p95":818.3660700869951,"p99":894.3097760871372}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:02+01","p50":579.968928,"p95":840.0522082325334,"p99":924.7306056415267}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:03+01","p50":409.52701139999994,"p95":791.400620911422,"p99":833.6115882444172}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:04+01","p50":480.74249048,"p95":858.446063036429,"p99":924.3532730880061}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:05+01","p50":493.86745932,"p95":793.8484347521294,"p99":891.6514025846448}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:06+01","p50":503.00869536,"p95":707.5608842001768,"p99":766.4481488458815}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:07+01","p50":446.7047443199999,"p95":732.8975775305025,"p99":784.2090599086849}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:08+01","p50":443.6299222400001,"p95":675.8365122516063,"p99":787.8422447429314}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:09+01","p50":446.2239348,"p95":676.0366937052796,"p99":730.2456811592422}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:10+01","p50":484.41588424,"p95":691.552294421825,"p99":771.7678810416132}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:11+01","p50":409.1241148,"p95":731.5684430451552,"p99":778.1050226085949}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:12+01","p50":412.8476838,"p95":685.186796900581,"p99":722.8749597384816}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:13+01","p50":426.4423246,"p95":703.8644782377653,"p99":750.7845951079803}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:14+01","p50":463.79773248000004,"p95":702.6447670155354,"p99":775.6442146433227}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:15+01","p50":509.17797410000003,"p95":741.9910991929672,"p99":771.4171255720368}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:16+01","p50":432.76381884,"p95":793.5088406433524,"p99":924.4603668276673}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:17+01","p50":520.2229302399999,"p95":752.0814903606627,"p99":799.5684956319533}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:18+01","p50":448.36303052000005,"p95":796.3597455593388,"p99":852.1273552687354}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:19+01","p50":344.08083324999996,"p95":862.0750866456473,"p99":920.0804788825207}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:20+01","p50":464.6192106799999,"p95":874.1757213886256,"p99":956.2818377526527}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:21+01","p50":512.07610176,"p95":718.4169571842101,"p99":767.4090250798824}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:22+01","p50":461.65116020000005,"p95":781.0651170408299,"p99":834.0496501097717}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:23+01","p50":485.5306144,"p95":720.7938410514453,"p99":777.1701240678788}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:24+01","p50":414.37206572,"p95":718.1310419619126,"p99":793.5687120217998}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:25+01","p50":401.1198763599999,"p95":755.876786938769,"p99":805.0451092795033}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:26+01","p50":324.98312148,"p95":848.4122304455406,"p99":911.9963356094869}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:27+01","p50":389.81453120000003,"p95":843.9371717415853,"p99":890.7586888226085}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:28+01","p50":443.795063625,"p95":857.4720472290392,"p99":912.1474033337047}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:29+01","p50":433.94968632,"p95":841.851378178957,"p99":915.3437029142351}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:30+01","p50":503.95550575000004,"p95":779.5877722546229,"p99":817.272524877471}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:31+01","p50":633.5656961875,"p95":892.9160582861742,"p99":936.8784041967201}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:32+01","p50":689.6775119687501,"p95":846.2514725405498,"p99":938.2470059975965}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:33+01","p50":420.84068206,"p95":818.1779144031025,"p99":882.2821742704887}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:34+01","p50":452.93968408,"p95":748.3596820722591,"p99":807.5742626219387}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:35+01","p50":416.29938439999995,"p95":864.6454801909074,"p99":916.3731302860942}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:36+01","p50":463.5475261,"p95":872.4921711269667,"p99":937.6462802473488}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:37+01","p50":413.74672451999993,"p95":905.2554121473956,"p99":983.9038533615537}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:38+01","p50":409.48736468000004,"p95":856.9544039638855,"p99":909.0892436706927}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:39+01","p50":389.8248627,"p95":868.6098857488134,"p99":921.7148423558159}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:40+01","p50":344.2292738,"p95":839.1935802877866,"p99":881.2609500367889}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:41+01","p50":346.7925172,"p95":950.6889530208272,"p99":985.2517418997936}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:42+01","p50":326.62641546,"p95":934.5463450636968,"p99":1013.3277539604215}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:43+01","p50":312.60239315999996,"p95":857.6209930594213,"p99":899.546291250639}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:44+01","p50":376.5497767187499,"p95":895.9849932721825,"p99":943.8155160021622}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:45+01","p50":440.746051375,"p95":919.6412343554778,"p99":986.1681137916092}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:46+01","p50":447.9712063200001,"p95":958.5427756260987,"p99":1060.3157812348136}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:47+01","p50":456.21867093749995,"p95":852.6917267819111,"p99":909.7960023076332}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:48+01","p50":459.19242316,"p95":748.2600818025804,"p99":797.439148998914}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:49+01","p50":505.65841575,"p95":652.7568129464436,"p99":733.1735807014727}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:50+01","p50":404.79807312,"p95":738.5028303807358,"p99":801.9166470379581}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:51+01","p50":290.85314350000004,"p95":955.0576914630678,"p99":993.0295452962375}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:52+01","p50":273.92152675,"p95":1066.472334990564,"p99":1120.8082584798276}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:53+01","p50":305.02749951999994,"p95":1148.5708324001473,"p99":1204.5413314804805}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:54+01","p50":296.8829594,"p95":1199.567254405099,"p99":1260.5372572301483}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:55+01","p50":282.68535284,"p95":1326.8499839542087,"p99":1357.909935063963}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:56+01","p50":247.85930779999998,"p95":1233.971583642542,"p99":1277.8010873724697}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:57+01","p50":242.06895225,"p95":1291.3411411178147,"p99":1342.3507907432374}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:58+01","p50":294.41228925,"p95":1326.6353642993754,"p99":1371.361604827898}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 16:59:59+01","p50":264.6782276,"p95":1378.67481306825,"p99":1450.5745288366754}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:00+01","p50":256.8066340625,"p95":1373.8662746155958,"p99":1437.6407693063645}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:01+01","p50":277.07714859375,"p95":1430.6590500299822,"p99":1498.896697427592}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:02+01","p50":255.91042134375,"p95":1598.1019676371614,"p99":1734.3704218267055}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:03+01","p50":229.57550356000002,"p95":1696.6231724577617,"p99":1741.7754104958733}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:04+01","p50":215.94945053125002,"p95":1448.8173705870752,"p99":1597.171465832263}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:05+01","p50":250.81661130000003,"p95":1474.1969914765775,"p99":1532.0604853143025}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:06+01","p50":318.30047725,"p95":1379.4660429889284,"p99":1457.6106646230917}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:07+01","p50":296.10575748,"p95":1371.5720435444232,"p99":1438.398997665289}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:08+01","p50":310.30135900000005,"p95":1276.8541217251852,"p99":1375.7508471897297}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:09+01","p50":324.61803356,"p95":1268.3503677327963,"p99":1334.0207465151766}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:10+01","p50":310.61837412499995,"p95":1118.6992901947972,"p99":1169.858637197795}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:11+01","p50":264.44422427999996,"p95":1253.4007735552764,"p99":1337.7334010542859}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:12+01","p50":282.95834049999996,"p95":1370.0375084611505,"p99":1409.8450066190928}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:13+01","p50":251.71732762000002,"p95":1387.0912283932494,"p99":1448.4463730911255}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:14+01","p50":239.77846034375,"p95":1485.8565785667743,"p99":1531.5640100604624}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:15+01","p50":265.63580804,"p95":1461.055881513004,"p99":1516.169582163746}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:16+01","p50":305.06471128,"p95":1418.0504749809015,"p99":1490.2805457760708}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:17+01","p50":365.92930575,"p95":1278.0673796078856,"p99":1332.1449607623836}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:18+01","p50":338.07147384,"p95":1207.8018534585383,"p99":1283.180626170243}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:19+01","p50":389.1698988125,"p95":1105.7264406951017,"p99":1158.5958294484985}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:20+01","p50":336.83239459999993,"p95":1123.0797544735824,"p99":1180.287071246669}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:21+01","p50":351.27304000000004,"p95":1183.2277076278476,"p99":1229.6496526538115}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:22+01","p50":337.25127134375003,"p95":1222.0596721526138,"p99":1265.6357135058497}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:23+01","p50":411.67429782,"p95":1121.4313489646242,"p99":1184.0013063645783}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:24+01","p50":372.12722075,"p95":1037.5583362300986,"p99":1121.4334752823468}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:25+01","p50":358.94511409374996,"p95":1113.4368757003874,"p99":1168.703088913448}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:26+01","p50":335.399527,"p95":1245.4090109917734,"p99":1337.7595733927058}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:27+01","p50":217.0584515,"p95":1299.7565216301439,"p99":1362.3630580998797}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:28+01","p50":290.05654131250003,"p95":1347.6446803194558,"p99":1395.1166595507148}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:29+01","p50":318.8217935625,"p95":1333.1252860993443,"p99":1379.3142076579184}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:30+01","p50":412.7397759375,"p95":1091.5723159084755,"p99":1204.0126030216459}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:31+01","p50":471.34652300000005,"p95":1165.9845495583122,"p99":1230.9619462373223}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:32+01","p50":489.24911299999997,"p95":1149.3562186741772,"p99":1209.2903007229222}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:33+01","p50":409.55080269999996,"p95":1000.9215262222518,"p99":1102.028283573578}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:34+01","p50":356.77803240624996,"p95":1045.8965672853271,"p99":1120.8221252000324}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:35+01","p50":299.6639951,"p95":1245.4198075950894,"p99":1303.1494872729797}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:36+01","p50":308.27954093750003,"p95":1318.6333646767896,"p99":1371.4519142280053}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:37+01","p50":296.95125137499997,"p95":1258.0613694350657,"p99":1319.8753401443994}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:38+01","p50":242.22241176000003,"p95":1226.9435109000488,"p99":1281.4951884865056}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:39+01","p50":351.40504819999995,"p95":1327.5645072608443,"p99":1385.9433720104546}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:40+01","p50":331.6019381875,"p95":1146.2044852120741,"p99":1225.8783657254414}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:41+01","p50":274.50845423999993,"p95":1412.633810252578,"p99":1458.1476701360189}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:42+01","p50":240.99243412500002,"p95":1362.5005430577285,"p99":1427.3493709728339}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:43+01","p50":271.41480644,"p95":1421.7638064450173,"p99":1497.0160478736734}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:44+01","p50":272.73868490625,"p95":1483.1230503132479,"p99":1546.2702801880585}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:45+01","p50":342.97998931250004,"p95":1316.0247621134738,"p99":1402.1832111210408}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:46+01","p50":367.92398596,"p95":1224.4723878160569,"p99":1316.3653151012775}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:47+01","p50":403.04360275,"p95":1131.497387748942,"p99":1211.780453446203}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:48+01","p50":462.5544555,"p95":969.3734170711749,"p99":1014.1721077419987}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:49+01","p50":383.88462375000006,"p95":999.0967503558016,"p99":1049.0881787377261}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:50+01","p50":377.50671904,"p95":1065.8351270002802,"p99":1124.8977055295038}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:51+01","p50":374.0603104375,"p95":1010.9933955756006,"p99":1087.2457707759208}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:52+01","p50":304.4090130625,"p95":1099.8624152295101,"p99":1186.8683443010832}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:53+01","p50":326.94160424999995,"p95":1087.458853347962,"p99":1134.1467520068607}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:54+01","p50":373.28528659375,"p95":1004.7598703866058,"p99":1078.0421877515412}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:55+01","p50":442.55948025,"p95":1027.5687645615303,"p99":1082.9457439019307}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:56+01","p50":455.06786600000004,"p95":906.6042555620081,"p99":972.4418643908546}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:57+01","p50":398.3586268125,"p95":982.2136200221285,"p99":1063.2275540859828}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:58+01","p50":427.8044215,"p95":993.419917844108,"p99":1087.530292932532}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:00:59+01","p50":419.6879015,"p95":928.2230856002031,"p99":1005.494435786173}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:00+01","p50":428.03856021875004,"p95":943.3408889327696,"p99":1010.6441153174541}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:01+01","p50":443.80421331249994,"p95":1120.3839794815858,"p99":1224.3089030061353}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:02+01","p50":330.72416975000004,"p95":1319.9551871595768,"p99":1374.513199573954}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:03+01","p50":308.868786375,"p95":1181.2607610089533,"p99":1258.5231168961257}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:04+01","p50":278.05828125,"p95":1105.7181125820864,"p99":1152.538185560535}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:05+01","p50":226.0628300625,"p95":1233.523458154641,"p99":1312.4420734525825}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:06+01","p50":221.04589365624997,"p95":1342.2640396102813,"p99":1392.4634917685332}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:07+01","p50":331.088105625,"p95":1472.6465262075792,"p99":1539.74797023108}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:08+01","p50":364.40633,"p95":1378.2779767054153,"p99":1455.6673160619164}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:09+01","p50":355.4938865,"p95":1232.6525205629393,"p99":1286.3614757544817}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:10+01","p50":444.01465028125,"p95":1176.1312283163688,"p99":1267.5995221146356}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:11+01","p50":460.36731987499996,"p95":963.1437316647844,"p99":1010.7553366085858}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:12+01","p50":348.6620261875,"p95":1157.098749246374,"p99":1209.0690921518678}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:13+01","p50":330.929518625,"p95":1123.3466312844735,"p99":1161.8901714263807}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:14+01","p50":280.80906319999997,"p95":1211.2271769580402,"p99":1276.1489093388725}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:15+01","p50":202.5970185,"p95":1054.039386469398,"p99":1095.7894092189892}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:16+01","p50":206.204974,"p95":960.2604881633181,"p99":1017.7444118097401}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:17+01","p50":204.76002453125,"p95":935.8978527156486,"p99":990.6852471083901}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:18+01","p50":286.80225265624995,"p95":998.7506113811944,"p99":1053.0257077525328}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:19+01","p50":348.41833125,"p95":914.3245040351516,"p99":967.1049710458984}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:20+01","p50":406.33185225,"p95":887.03263948242,"p99":961.8514902111516}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:21+01","p50":373.62391175000005,"p95":873.7941156592511,"p99":917.9207695006152}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:22+01","p50":384.1419971875,"p95":957.6718643504519,"p99":1008.0354960233902}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:23+01","p50":358.4550986874999,"p95":930.9010295038123,"p99":985.9724332767548}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:24+01","p50":388.71930775,"p95":900.4278588649084,"p99":958.6752755243512}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:25+01","p50":399.74073675,"p95":995.0417358608591,"p99":1043.4076431941376}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:26+01","p50":421.72374343749993,"p95":947.8117726786147,"p99":1011.8275186556208}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:27+01","p50":400.90323693749997,"p95":945.879280691314,"p99":985.9757093771556}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:28+01","p50":576.28487346875,"p95":861.9257649916646,"p99":935.0216043097272}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:29+01","p50":491.4461351875,"p95":854.9085999441995,"p99":920.5622834571533}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:30+01","p50":333.9964570625,"p95":860.0384215524153,"p99":908.1505602787666}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:31+01","p50":369.61234721874996,"p95":1098.8220424147707,"p99":1179.9086379717135}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:32+01","p50":406.55642393749997,"p95":1310.4675732788626,"p99":1372.3805498009688}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:33+01","p50":401.1109846875,"p95":1096.4234767759017,"p99":1261.31174702314}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:34+01","p50":371.8543209375,"p95":1081.5963490005306,"p99":1130.7102780844837}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:35+01","p50":353.534412125,"p95":1189.1395890160136,"p99":1236.5630415562048}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:36+01","p50":440.9433736875,"p95":972.5048970138604,"p99":1020.4256151303157}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:37+01","p50":576.10298625,"p95":868.6593772067789,"p99":909.6946102949876}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:38+01","p50":471.6856993125,"p95":862.0346265736099,"p99":941.6552965172004}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:39+01","p50":407.7286164375,"p95":804.3631566765524,"p99":898.4146348053677}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:40+01","p50":495.472425875,"p95":778.8919335706856,"p99":821.4438198893938}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:41+01","p50":390.015073875,"p95":915.6852682304248,"p99":968.8527980200281}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:42+01","p50":436.70397121875,"p95":1035.5519280481237,"p99":1079.741326754084}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:43+01","p50":399.66618312500003,"p95":1015.8571091822237,"p99":1061.5754131000485}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:44+01","p50":530.4277997500001,"p95":985.067257898768,"p99":1020.2450028939083}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:45+01","p50":488.66430675000004,"p95":798.7751331608897,"p99":839.0750598332825}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:46+01","p50":560.9766404375,"p95":878.9209963021202,"p99":954.149067567142}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:47+01","p50":505.62762825000004,"p95":929.5634865387963,"p99":977.3061333858404}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:48+01","p50":511.5911427500001,"p95":933.4198547144293,"p99":1005.8549665717044}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:49+01","p50":489.37948306249996,"p95":743.3766657749824,"p99":791.7762660572671}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:50+01","p50":523.76476725,"p95":682.6771869110567,"p99":740.3636572551417}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:51+01","p50":527.6462293750001,"p95":728.8734756788447,"p99":770.5431688815513}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:52+01","p50":557.694719125,"p95":715.0237951346518,"p99":786.6559587488437}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:53+01","p50":508.480639125,"p95":667.0940064109975,"p99":727.979009902976}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:54+01","p50":525.19123475,"p95":672.500880473401,"p99":728.8034774426936}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:55+01","p50":500.75535475,"p95":745.9783345827702,"p99":820.9949372550859}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:56+01","p50":484.0476434375,"p95":745.8610955601308,"p99":798.0840848942604}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:57+01","p50":434.06750350000004,"p95":781.8392150984273,"p99":813.8131414826131}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:58+01","p50":495.87167309375,"p95":689.0007507138381,"p99":728.9923556229758}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:01:59+01","p50":399.89193224999997,"p95":828.1196123810889,"p99":865.8575651366497}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:00+01","p50":307.79317031249997,"p95":907.5518388220042,"p99":963.6233573454666}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:01+01","p50":450.2785736875,"p95":1018.4391522637959,"p99":1093.8440466487082}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:02+01","p50":596.3215379999999,"p95":1091.8929729112756,"p99":1162.5406727544032}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:03+01","p50":539.162731875,"p95":848.0737903566577,"p99":906.5794161626329}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:04+01","p50":490.9393101875,"p95":795.3625589787633,"p99":848.4238285406649}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:05+01","p50":531.8070108125,"p95":775.7002985962014,"p99":827.0155542848335}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:06+01","p50":437.9073600625,"p95":848.3419694726712,"p99":905.993471892788}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:07+01","p50":431.15010034375,"p95":835.2641611670256,"p99":884.7950432175136}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:08+01","p50":351.8099315,"p95":879.648664891788,"p99":938.5206180565004}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:09+01","p50":304.160279375,"p95":946.47605494226,"p99":1035.3025574571952}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:10+01","p50":289.87681231249996,"p95":930.3402899532545,"p99":985.5760806319204}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:11+01","p50":297.51204471875,"p95":1011.708443413406,"p99":1069.4752120934954}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:12+01","p50":325.833794,"p95":1082.4082505675233,"p99":1140.0157160715016}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:13+01","p50":489.27371640625,"p95":872.0172264079048,"p99":960.0499716747796}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:14+01","p50":517.49301075,"p95":764.5488196049198,"p99":868.9921633781624}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:15+01","p50":502.704573125,"p95":718.4235492219174,"p99":745.5714971204739}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:16+01","p50":516.57124675,"p95":706.5996530187166,"p99":774.4198485369101}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:17+01","p50":525.2879698749999,"p95":725.9181978473864,"p99":799.3484394643592}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:18+01","p50":487.47193625,"p95":829.2221960716347,"p99":864.4230949769935}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:19+01","p50":490.115499,"p95":729.2113911087985,"p99":806.4557772201262}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:20+01","p50":512.13397978125,"p95":728.420884377361,"p99":802.8961748299433}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:21+01","p50":465.733527875,"p95":826.0496244955046,"p99":871.1046259509058}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:22+01","p50":447.53847562500005,"p95":917.1888370360566,"p99":986.5875071976982}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:23+01","p50":389.37695,"p95":1147.2464825701666,"p99":1215.0142711447982}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:24+01","p50":368.36683644,"p95":1115.1854948421724,"p99":1296.7192834727678}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:25+01","p50":431.46925762500007,"p95":849.5357697776268,"p99":912.5563385431905}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:26+01","p50":437.3141676875,"p95":832.877900316882,"p99":873.9508624078612}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:27+01","p50":481.08365634375,"p95":845.2556975099542,"p99":898.9850942347562}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:28+01","p50":534.8528012187501,"p95":814.1404909721426,"p99":909.5114925533416}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:29+01","p50":477.38707828125,"p95":838.2755947543105,"p99":918.7837006917653}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:30+01","p50":437.2799144375,"p95":886.9269572216167,"p99":964.1211330747919}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:31+01","p50":506.8406602222222,"p95":1130.3126061921832,"p99":1199.9750577549203}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:32+01","p50":519.0099894375,"p95":1195.5841332815523,"p99":1258.7531618418063}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:33+01","p50":550.3733055625,"p95":920.8027147532234,"p99":993.9141517019248}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:34+01","p50":510.54620706249995,"p95":905.9136580712307,"p99":953.6132146731028}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:35+01","p50":489.36180653125,"p95":964.7543322763221,"p99":998.1965240270135}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:36+01","p50":456.76863718749996,"p95":1048.6677480940914,"p99":1147.1578452513922}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:37+01","p50":405.79225190625,"p95":1101.61412981618,"p99":1167.5982818806763}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:38+01","p50":316.966581,"p95":1224.6288159488392,"p99":1266.172592892285}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:39+01","p50":249.5340845,"p95":1407.0580122595059,"p99":1494.7236534193687}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:40+01","p50":260.53212384375,"p95":1571.2938356809989,"p99":1639.5003233703987}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:41+01","p50":225.52530562500002,"p95":1502.4722683780058,"p99":1564.3753142137614}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:42+01","p50":257.60909884375,"p95":1300.2464042245335,"p99":1373.4228285685876}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:43+01","p50":331.811686,"p95":1411.7601681428653,"p99":1454.1124919311462}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:44+01","p50":284.41342540625,"p95":1373.598490453252,"p99":1436.9030629483366}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:45+01","p50":246.68098774999999,"p95":1394.307158559348,"p99":1498.149565495338}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:46+01","p50":245.33991400000002,"p95":1555.503754686955,"p99":1595.8455725054039}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:47+01","p50":217.0294381875,"p95":1632.1419017092649,"p99":1664.720494045454}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:48+01","p50":244.815718125,"p95":1658.5602877277875,"p99":1708.044500925355}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:49+01","p50":274.428511625,"p95":1571.856909066107,"p99":1673.5466836566543}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:50+01","p50":279.71968509375,"p95":1550.5602458935891,"p99":1611.063363525159}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:51+01","p50":323.95805925,"p95":1435.5251841731526,"p99":1513.0679296905519}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:52+01","p50":338.77594350000004,"p95":1393.3739504209116,"p99":1467.8625657480736}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:53+01","p50":320.25516303125,"p95":1418.3375261919466,"p99":1476.9829054384304}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:54+01","p50":278.661301125,"p95":1423.9185551519054,"p99":1495.3326177168342}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:55+01","p50":296.930953125,"p95":1555.8541939877027,"p99":1620.862156959202}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:56+01","p50":287.72675490625,"p95":1551.6493149217442,"p99":1630.5869621121428}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:57+01","p50":273.16591412500003,"p95":1429.361953071834,"p99":1491.3193079872308}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:58+01","p50":223.601614375,"p95":1481.4620352691375,"p99":1530.1255760190202}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:02:59+01","p50":243.47778575,"p95":1583.5778080413677,"p99":1627.8361638798458}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:00+01","p50":241.70434378125,"p95":1562.3451970964811,"p99":1618.923581356467}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:01+01","p50":247.93508266666666,"p95":1780.2658506151527,"p99":1852.7282519598946}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:02+01","p50":284.545861375,"p95":1909.5568453008889,"p99":1950.6261838792636}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:03+01","p50":223.1869029375,"p95":1665.3045776966958,"p99":1741.0172296839123}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:04+01","p50":217.67565925,"p95":1669.6400619169488,"p99":1712.5491345015582}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:05+01","p50":216.4439175,"p95":1676.0199154431627,"p99":1735.4953434306804}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:06+01","p50":232.8211455,"p95":1668.4443933128014,"p99":1710.3306143587417}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:07+01","p50":238.81680631249998,"p95":1668.784569885818,"p99":1726.8824277667966}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:08+01","p50":224.18347271874998,"p95":1669.7859471795648,"p99":1751.8882853493544}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:09+01","p50":255.56739228125002,"p95":1632.1900846751348,"p99":1689.733404448866}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:10+01","p50":282.37240340625004,"p95":1645.857232627638,"p99":1704.6216824828723}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:11+01","p50":287.11778799999996,"p95":1587.3114926286707,"p99":1629.9237721143281}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:12+01","p50":266.06492771875,"p95":1468.2654557670596,"p99":1519.8887532449721}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:13+01","p50":313.8869051875,"p95":1507.8693824706697,"p99":1556.9385347526907}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:14+01","p50":300.05446115625,"p95":1504.55750885628,"p99":1557.498578343833}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:15+01","p50":336.539755,"p95":1410.9941225333614,"p99":1534.357548577939}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:16+01","p50":361.6662794375,"p95":1343.7055491422527,"p99":1420.223939673569}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:17+01","p50":304.1360496875,"p95":1317.252028974455,"p99":1389.6991550341975}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:18+01","p50":257.2506144375,"p95":1437.9188404859888,"p99":1517.8337303722278}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:19+01","p50":342.03214075,"p95":1498.4250639910283,"p99":1552.08769069655}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:20+01","p50":359.33994087499997,"p95":1403.7187220484773,"p99":1525.2660110698346}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:21+01","p50":330.47825075000003,"p95":1263.4691199619258,"p99":1332.9154464997362}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:22+01","p50":404.3325505625,"p95":1151.6122958333142,"p99":1286.7977221252313}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:23+01","p50":477.983473,"p95":1047.358963936724,"p99":1106.5748304423305}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:24+01","p50":605.150412,"p95":829.8513527875087,"p99":938.4385975928965}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:25+01","p50":657.6632271249999,"p95":799.4701946358708,"p99":835.6046202065172}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:26+01","p50":566.38617075,"p95":789.7231718794752,"p99":823.8667890042235}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:27+01","p50":551.2718941875,"p95":862.8547551825673,"p99":952.7182545217929}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:28+01","p50":496.386323,"p95":979.390549759819,"p99":1043.5024977365665}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:29+01","p50":492.17342531249994,"p95":1085.2769396604529,"p99":1129.4739239835453}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:30+01","p50":391.99678325,"p95":1166.320532032022,"p99":1223.5079453269605}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:31+01","p50":461.4230819375,"p95":1172.3456338252888,"p99":1246.8573567444182}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:32+01","p50":434.40661846875,"p95":1354.620486797615,"p99":1416.5760314428085}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:33+01","p50":396.9980335,"p95":1341.456705223662,"p99":1418.2780499455514}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:34+01","p50":408.0605675625,"p95":1087.9780024853078,"p99":1164.9712159393998}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:35+01","p50":495.60596609375,"p95":1009.2177950838983,"p99":1068.8434326190877}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:36+01","p50":398.364537,"p95":1004.6770212253564,"p99":1078.0422638852644}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:37+01","p50":514.8410815312501,"p95":1069.5893657980494,"p99":1119.1435913276966}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:38+01","p50":492.5351595625,"p95":957.1923070940961,"p99":1109.8519915556749}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:39+01","p50":486.5265169375,"p95":1061.5015782010819,"p99":1123.5239192186427}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:40+01","p50":570.277847625,"p95":878.7198873932593,"p99":958.2418932165126}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:41+01","p50":433.7697501875,"p95":871.9915896014445,"p99":925.0943730779752}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:42+01","p50":462.87368190625,"p95":861.8533969640756,"p99":925.1568213114422}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:43+01","p50":350.05137484375,"p95":956.456481930902,"p99":1048.7539046323716}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:44+01","p50":272.7799403125,"p95":1141.7027832465083,"p99":1194.6874864128397}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:45+01","p50":272.07913425000004,"p95":1179.6071727594324,"p99":1235.9118927836637}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:46+01","p50":286.6193708125,"p95":1184.6878270029315,"p99":1229.8974995134963}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:47+01","p50":444.4442155,"p95":1272.525276680043,"p99":1342.056948450781}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:48+01","p50":410.54002521875,"p95":1217.3226085600884,"p99":1253.3684290439667}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:49+01","p50":359.11055053125,"p95":1144.3413137052314,"p99":1193.586138161663}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:50+01","p50":381.86686078125,"p95":1261.5027272343673,"p99":1366.7869136822524}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:51+01","p50":396.060829875,"p95":1205.9486232195206,"p99":1283.2509160344687}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:52+01","p50":440.9412880625,"p95":1130.164366385832,"p99":1220.508437647624}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:53+01","p50":393.6379438125,"p95":1087.457335263016,"p99":1156.0123133859759}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:54+01","p50":360.08196309375,"p95":1162.3183009985373,"p99":1213.8491897790823}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:55+01","p50":270.7513948125,"p95":1289.2412074169342,"p99":1391.3035269296663}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:56+01","p50":261.17554528125,"p95":1344.1463458929707,"p99":1436.9730327770244}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:57+01","p50":359.90874153125003,"p95":1226.1483832053516,"p99":1289.6511622184084}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:58+01","p50":419.63465284375,"p95":1089.1000972351355,"p99":1133.8074047620232}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:03:59+01","p50":426.1286608125,"p95":1181.8483155161216,"p99":1232.4081276638142}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:00+01","p50":407.65264456250003,"p95":1201.6717852207219,"p99":1258.5439710263524}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:01+01","p50":350.46005983333333,"p95":1424.7195962940973,"p99":1471.7136663441659}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:02+01","p50":305.26081462499997,"p95":1462.7733437964034,"p99":1505.9757252541328}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:03+01","p50":311.1174865625,"p95":1393.9437645516275,"p99":1489.3854107943516}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:04+01","p50":294.04617465625,"p95":1095.249260658051,"p99":1138.6390054021836}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:05+01","p50":331.30367034375,"p95":1194.6772804601114,"p99":1242.3397683720568}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:06+01","p50":365.3450695,"p95":1198.4173186879182,"p99":1268.3778623635249}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:07+01","p50":393.644743625,"p95":1278.556557193039,"p99":1369.765332407082}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:08+01","p50":370.1451470625,"p95":1353.0526307275213,"p99":1416.0828640852294}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:09+01","p50":341.91079534375,"p95":1342.7974576645238,"p99":1390.5403857883468}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:10+01","p50":290.30416125,"p95":1460.2677428478567,"p99":1505.5529785807075}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:11+01","p50":241.18618571874998,"p95":1434.7513577073012,"p99":1498.2506420944753}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:12+01","p50":266.130397,"p95":1561.626805089447,"p99":1609.1036210942812}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:13+01","p50":245.38688790624997,"p95":1482.3675191450511,"p99":1538.6322598553265}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:14+01","p50":272.4391511875,"p95":1596.9340431644964,"p99":1647.3903891978791}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:15+01","p50":349.7718371875,"p95":1561.57747434123,"p99":1631.509725051961}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:16+01","p50":372.99533303125,"p95":1371.7184807966048,"p99":1472.0763482383318}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:17+01","p50":381.5341501875,"p95":1402.192141518408,"p99":1473.7827869945459}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:18+01","p50":443.9494804375,"p95":1259.8984596167154,"p99":1314.1753718595664}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:19+01","p50":407.50983299999996,"p95":1053.7803540517082,"p99":1114.8658188149518}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:20+01","p50":330.038518375,"p95":1024.3757621961452,"p99":1105.0813751103217}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:21+01","p50":317.2547094375,"p95":1158.5443819353698,"p99":1224.6838087607991}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:22+01","p50":368.7601756875,"p95":1011.4479282594612,"p99":1100.1364356769827}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:23+01","p50":336.21394940625,"p95":1019.0957898091343,"p99":1078.404404797233}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:24+01","p50":412.995593,"p95":1047.0999994891702,"p99":1109.344957522729}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:25+01","p50":378.12793931249996,"p95":1025.9672355873738,"p99":1079.9647466002807}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:26+01","p50":410.930432625,"p95":1095.576843986663,"p99":1152.6132804493534}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:27+01","p50":426.31201799999997,"p95":970.0312945294149,"p99":1025.8238758906475}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:28+01","p50":461.8975015,"p95":1076.4309229496637,"p99":1172.6907522789058}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:29+01","p50":392.19302175,"p95":1211.9792057994634,"p99":1302.3766266167374}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:30+01","p50":253.30220624999998,"p95":1234.4706469999267,"p99":1271.1972375555115}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:31+01","p50":257.86143555555554,"p95":1328.8974781038303,"p99":1388.9802430381335}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:32+01","p50":325.16261872222225,"p95":1367.208211870984,"p99":1480.5690954539682}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:33+01","p50":358.778842,"p95":1268.7960678874576,"p99":1323.257177013688}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:34+01","p50":403.53463403125,"p95":1067.1791485678382,"p99":1145.3592779546284}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:35+01","p50":352.18259375,"p95":1106.6601172881947,"p99":1175.3228713645174}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:36+01","p50":351.20903043749996,"p95":1116.1011381038857,"p99":1164.5974901468549}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:37+01","p50":454.15392265625,"p95":1098.1344513710008,"p99":1141.4094042726092}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:38+01","p50":471.66136890625,"p95":1051.8374283678675,"p99":1110.409138757636}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:39+01","p50":399.3553395,"p95":1099.8626506148682,"p99":1182.1713657134671}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:40+01","p50":477.3820301875,"p95":1146.330773358639,"p99":1204.5507817873208}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:41+01","p50":542.93573425,"p95":1017.227366558991,"p99":1124.3543623450003}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:42+01","p50":539.945251,"p95":972.4583595878144,"p99":1045.5935240660401}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:43+01","p50":406.12722703124996,"p95":947.0021226884148,"p99":999.317351181486}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:44+01","p50":320.7812405,"p95":1033.871573568444,"p99":1114.8959639402199}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:45+01","p50":247.5284226875,"p95":1032.3039571599331,"p99":1071.4264279664496}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:46+01","p50":316.16519590625,"p95":1074.0998399624527,"p99":1123.1176419550802}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:47+01","p50":375.48857428125,"p95":1122.62939359618,"p99":1197.1433623743415}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:48+01","p50":436.10489325000003,"p95":1145.5356950049988,"p99":1201.1084639476662}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:49+01","p50":401.0414736875,"p95":1162.8687949156524,"p99":1238.7793130857183}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:50+01","p50":397.1332230625,"p95":1053.4150216872583,"p99":1142.1139045798407}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:51+01","p50":258.55396665625005,"p95":1093.0868789985632,"p99":1152.9485494992423}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:52+01","p50":280.93637606249996,"p95":1122.8647814105198,"p99":1207.9476703703308}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:53+01","p50":346.85615846875,"p95":1096.3395703804645,"p99":1142.5716697106998}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:54+01","p50":319.37706393750005,"p95":1142.7605045631972,"p99":1198.4144942098667}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:55+01","p50":303.94073568749997,"p95":1119.3090427493532,"p99":1187.227187754051}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:56+01","p50":285.59444699999995,"p95":1167.8035451491626,"p99":1239.726972310678}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:57+01","p50":338.44454925,"p95":1239.3030068431465,"p99":1275.7018183915854}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:58+01","p50":255.96545290625002,"p95":1188.5508220265638,"p99":1283.7131456380082}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:04:59+01","p50":283.17503700000003,"p95":1341.1839532549097,"p99":1410.4147991878556}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:00+01","p50":284.00554146875,"p95":1463.3712290228382,"p99":1527.9328021911845}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:01+01","p50":401.3173766666667,"p95":1434.7140358634138,"p99":1479.0404789858585}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:02+01","p50":428.57984375,"p95":1525.601126599747,"p99":1600.4857368781127}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:03+01","p50":369.58383271875,"p95":1367.651479377807,"p99":1453.967504780501}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:04+01","p50":402.96431625,"p95":1232.3071255755783,"p99":1309.3488060609598}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:05+01","p50":337.75996200000003,"p95":1222.3190906596108,"p99":1270.1436745046153}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:06+01","p50":319.54442675,"p95":1350.822235532204,"p99":1435.4977420017408}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:07+01","p50":343.82702553125,"p95":1398.4806488460997,"p99":1487.7265993565375}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:08+01","p50":326.29797587499996,"p95":1440.5712667033724,"p99":1512.919273649264}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:09+01","p50":268.5333929375,"p95":1418.7705229785752,"p99":1496.0151162976238}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:10+01","p50":250.10513674999999,"p95":1563.7743063357082,"p99":1615.682293749618}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:11+01","p50":273.68944112500003,"p95":1513.3243355312181,"p99":1572.243980261366}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:12+01","p50":291.73424593749996,"p95":1558.30763056493,"p99":1620.272713257736}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:13+01","p50":322.39391746875,"p95":1409.3866068146513,"p99":1493.5897174023821}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:14+01","p50":319.72705946875004,"p95":1503.656238943843,"p99":1563.3758630933817}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:15+01","p50":288.27486096875003,"p95":1497.9882414428384,"p99":1564.8138617559234}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:16+01","p50":245.58372740624998,"p95":1538.2397306973498,"p99":1635.782074877011}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:17+01","p50":304.04231050000004,"p95":1591.626314179972,"p99":1677.8650240455627}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:18+01","p50":296.23218118750003,"p95":1616.7703769208717,"p99":1710.3877224281246}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:19+01","p50":314.88241421875,"p95":1551.932980202591,"p99":1629.9841333596282}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:20+01","p50":390.4989616875,"p95":1368.751019992241,"p99":1408.7492995817558}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:21+01","p50":320.59373662499996,"p95":1450.5147917726617,"p99":1519.071425807252}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:22+01","p50":284.02978840625,"p95":1461.451512131657,"p99":1510.643271689516}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:23+01","p50":279.0636414375,"p95":1519.0169156925285,"p99":1562.062272963212}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:24+01","p50":274.325259,"p95":1646.7579885684427,"p99":1702.917154563032}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:25+01","p50":257.03439790625004,"p95":1567.1555420799948,"p99":1621.754768337983}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:26+01","p50":257.08461931249997,"p95":1697.574380484249,"p99":1740.107734550926}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:27+01","p50":228.5068239375,"p95":1654.8879698416893,"p99":1727.171493318413}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:28+01","p50":249.21318131249998,"p95":1772.693056329228,"p99":1817.8091819361266}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:29+01","p50":224.58208925,"p95":1755.6047212561896,"p99":1807.262657387853}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:30+01","p50":252.79144934375,"p95":1817.8145306279002,"p99":1866.379781304015}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:31+01","p50":306.76991533333336,"p95":1966.176637948251,"p99":2066.870963740402}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:32+01","p50":282.58361733333334,"p95":2137.2746187834755,"p99":2186.32995451353}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:33+01","p50":278.552697375,"p95":1767.445499423131,"p99":1946.8632338337784}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:34+01","p50":303.181731625,"p95":1704.4198928533342,"p99":1773.0230208545208}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:35+01","p50":308.0156649375,"p95":1602.614735204647,"p99":1667.3689897998422}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:36+01","p50":356.32229625,"p95":1489.0229413817644,"p99":1613.2725549303614}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:37+01","p50":343.90240562500003,"p95":1428.807654526174,"p99":1494.710956605878}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:38+01","p50":293.08430518750004,"p95":1505.4802454013316,"p99":1596.0966140438013}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:39+01","p50":261.127584875,"p95":1593.3398968456452,"p99":1663.6901066066991}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:40+01","p50":301.9970823125,"p95":1679.7013472583224,"p99":1738.2871955395688}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:41+01","p50":278.73169943749997,"p95":1680.875020447156,"p99":1750.0490670013378}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:42+01","p50":283.98032659374996,"p95":1438.112722768378,"p99":1510.3263956796686}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:43+01","p50":381.2891103125,"p95":1373.8334974724226,"p99":1440.4676265293465}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:44+01","p50":424.36096699999996,"p95":1429.2412467060515,"p99":1480.247798462422}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:45+01","p50":404.5327584375,"p95":1350.9321980407344,"p99":1400.7133538258781}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:46+01","p50":395.37590518750005,"p95":1406.0518784615974,"p99":1474.5047530692518}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:47+01","p50":424.16779134374997,"p95":1270.118065203299,"p99":1417.2027806771769}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:48+01","p50":483.5946184375,"p95":1125.7023971981844,"p99":1173.6769715508397}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:49+01","p50":523.2461419375,"p95":1111.8885390386563,"p99":1175.1148986376197}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:50+01","p50":493.3145861875,"p95":1126.3749809660417,"p99":1187.2050726930008}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:51+01","p50":485.62401793749996,"p95":1170.671746817452,"p99":1249.869949970225}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:52+01","p50":375.33573915625,"p95":1172.6301490253572,"p99":1226.4049537688268}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:53+01","p50":432.89421381249997,"p95":1242.0035552350375,"p99":1298.170266649762}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:54+01","p50":434.7152865,"p95":1237.8399370978884,"p99":1301.9258377521535}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:55+01","p50":394.81720975,"p95":1223.1887834027032,"p99":1287.8939013727388}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:56+01","p50":390.3632971875,"p95":1092.8065360958503,"p99":1142.433870135969}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:57+01","p50":322.04309396875,"p95":1012.6762337363058,"p99":1082.2933943403984}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:58+01","p50":312.6388766250001,"p95":1030.0413480029465,"p99":1087.6335890640287}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:05:59+01","p50":247.4165023125,"p95":1035.632261511802,"p99":1081.3702653294831}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:00+01","p50":289.830662,"p95":1155.0678122287432,"p99":1253.4184490132654}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:01+01","p50":483.65295688888887,"p95":1304.148789603818,"p99":1365.4294759013871}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:02+01","p50":408.23393653125,"p95":1368.2785093369134,"p99":1431.8431825724313}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:03+01","p50":284.457265,"p95":1355.6433049089214,"p99":1426.1600297281984}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:04+01","p50":360.542467,"p95":1323.938563444254,"p99":1421.6217052107336}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:05+01","p50":365.69909009375004,"p95":1257.539513912955,"p99":1311.932770733215}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:06+01","p50":361.56167962499995,"p95":1097.7989467907848,"p99":1173.9210891124194}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:07+01","p50":371.25324540625,"p95":1149.6112820575554,"p99":1193.330575341949}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:08+01","p50":371.0302539375,"p95":1031.5072860223147,"p99":1108.2099965042057}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:09+01","p50":311.54698109375,"p95":1023.3589308805781,"p99":1112.666629273579}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:10+01","p50":316.07706109375005,"p95":1047.2560342413622,"p99":1089.9983291922667}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:11+01","p50":285.5257350625,"p95":1067.1254121504555,"p99":1149.229493040175}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:12+01","p50":341.00916678125,"p95":1033.6519712134707,"p99":1105.2822447265753}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:13+01","p50":385.0462053125,"p95":969.5855393743718,"p99":1021.6151338609174}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:14+01","p50":475.7539206875,"p95":973.5409310039764,"p99":1014.4331232804456}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:15+01","p50":361.74082325,"p95":992.4800208172222,"p99":1052.0541741479883}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:16+01","p50":297.86539990625005,"p95":1051.8186292995497,"p99":1121.5936544029787}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:17+01","p50":316.77178159375,"p95":1344.2454284926334,"p99":1432.1328669541592}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:18+01","p50":345.15273449999995,"p95":1362.9327780658111,"p99":1456.314712411845}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:19+01","p50":346.60168350000004,"p95":1203.2250740975664,"p99":1264.011091016971}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:20+01","p50":329.854547625,"p95":1329.56260461685,"p99":1411.8671522504392}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:21+01","p50":331.64297671875,"p95":1369.5252941519325,"p99":1439.3392633589267}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:22+01","p50":322.91674475,"p95":1454.6938690824288,"p99":1528.5498516439804}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:23+01","p50":329.52848537500006,"p95":1421.3845545543995,"p99":1480.390018925598}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:24+01","p50":364.1062195,"p95":1422.5455446569476,"p99":1497.7550356817073}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:25+01","p50":336.866436875,"p95":1344.3262204260957,"p99":1458.2943538082216}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:26+01","p50":332.0278978125,"p95":1458.210987771324,"p99":1515.5878125292525}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:27+01","p50":408.47199493749997,"p95":1441.0163304239159,"p99":1491.0600777861328}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:28+01","p50":364.47389675,"p95":1301.7809773102908,"p99":1382.024266349303}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:29+01","p50":289.61514,"p95":1437.8915892150503,"p99":1487.8665462129688}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:30+01","p50":261.2820050625,"p95":1574.3773639595154,"p99":1715.295600204656}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:31+01","p50":306.48532166666666,"p95":1900.4856953233914,"p99":1959.3899135003596}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:32+01","p50":347.07674394444444,"p95":1987.0920070010422,"p99":2046.1503789964656}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:33+01","p50":300.21185675,"p95":1786.3974738490601,"p99":1915.3588858500366}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:34+01","p50":293.3286683125,"p95":1591.0667253357801,"p99":1689.430731814074}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:35+01","p50":274.99551725000003,"p95":1624.037876941772,"p99":1686.7823469788561}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:36+01","p50":333.399291,"p95":1641.735723714206,"p99":1723.8424876724278}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:37+01","p50":295.24999812500005,"p95":1570.3558324015498,"p99":1659.7156597555115}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:38+01","p50":298.74792981250005,"p95":1639.9654198898286,"p99":1670.3889338263195}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:39+01","p50":305.118326875,"p95":1582.3132393376777,"p99":1620.8188857369055}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:40+01","p50":308.32676787500003,"p95":1577.2948544976623,"p99":1658.4143919724352}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:41+01","p50":324.82144384374993,"p95":1377.0318782547195,"p99":1450.6158322236463}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:42+01","p50":407.526544,"p95":1398.358110193944,"p99":1451.3371241151028}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:43+01","p50":422.76700881249997,"p95":1251.4498667376015,"p99":1356.538421428661}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:44+01","p50":387.5582893125,"p95":1228.1671347072893,"p99":1282.79574028743}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:45+01","p50":428.45975753125003,"p95":1206.508172558035,"p99":1251.9194606676383}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:46+01","p50":424.32365725,"p95":1234.4082672546745,"p99":1300.568644231573}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:47+01","p50":353.2118588125,"p95":1348.6170292041893,"p99":1429.658215864481}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:48+01","p50":264.49746424999995,"p95":1548.300570024333,"p99":1633.0994539534195}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:49+01","p50":288.97203225,"p95":1727.1941183661468,"p99":1794.3136480413234}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:50+01","p50":297.10481690625,"p95":1705.7105864712246,"p99":1800.8056620704351}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:51+01","p50":274.99192153125,"p95":1723.0523103513785,"p99":1771.664264527322}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:52+01","p50":284.01368212499995,"p95":1688.5993043329759,"p99":1749.2266185395813}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:53+01","p50":258.03191350000003,"p95":1707.2516698695897,"p99":1739.192990437422}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:54+01","p50":227.24005699999998,"p95":1735.6724076769008,"p99":1791.190239160469}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:55+01","p50":221.98793968750002,"p95":1700.9969694957285,"p99":1766.105690881879}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:56+01","p50":268.84264296875,"p95":1759.0433248118882,"p99":1806.5413080155663}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:57+01","p50":280.11956,"p95":1718.042489642283,"p99":1787.760332321291}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:58+01","p50":217.2884128125,"p95":1757.4904433020538,"p99":1819.8057201981865}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:06:59+01","p50":241.4069425,"p95":1835.210164831108,"p99":1892.3429971231203}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:00+01","p50":248.387096,"p95":1846.6099793137623,"p99":1912.5780353766986}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:01+01","p50":255.74650155555557,"p95":1809.3214380860227,"p99":1896.8878216613693}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:02+01","p50":252.743748875,"p95":1966.5199657566636,"p99":2016.8234461833163}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:03+01","p50":247.6069545,"p95":2041.2048859617253,"p99":2081.4418472215825}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:04+01","p50":257.18020931250004,"p95":1904.2600373306616,"p99":1943.1625608557847}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:05+01","p50":260.81548150000003,"p95":1823.685767876216,"p99":1891.6286333270893}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:06+01","p50":242.24287184375,"p95":1752.6306567508327,"p99":1853.960570859661}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:07+01","p50":296.8724628125,"p95":1609.301349962394,"p99":1661.3783602785338}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:08+01","p50":278.1744650625,"p95":1672.696581013998,"p99":1740.9258568679563}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:09+01","p50":291.3791745625,"p95":1624.916695769646,"p99":1694.4589007127427}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:10+01","p50":347.1897555,"p95":1515.5909914722167,"p99":1678.6294267623798}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:11+01","p50":341.071074,"p95":1415.7749070213001,"p99":1534.860402762062}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:12+01","p50":467.80584,"p95":1227.727381418774,"p99":1324.1613154856905}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:13+01","p50":507.54137709375004,"p95":1068.085906363531,"p99":1123.388905908791}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:14+01","p50":484.67312025,"p95":1019.9437474933646,"p99":1114.2225871840149}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:15+01","p50":477.65720799999997,"p95":987.1540037817998,"p99":1046.0016645031992}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:16+01","p50":456.17110275,"p95":978.3969959360065,"p99":1036.2624035804695}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:17+01","p50":442.512968625,"p95":1010.6646197001905,"p99":1096.6705168904227}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:18+01","p50":506.61885674999996,"p95":1107.3383585927638,"p99":1170.0028905870734}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:19+01","p50":486.68116381249996,"p95":1029.9143351451428,"p99":1122.7667519185438}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:20+01","p50":521.12841134375,"p95":957.4677914988818,"p99":991.3602230051482}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:21+01","p50":526.2432866874999,"p95":988.5824761467683,"p99":1055.888588842828}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:22+01","p50":438.86447515625,"p95":905.620798816803,"p99":966.1974065131759}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:23+01","p50":458.27564928125,"p95":986.1479512966905,"p99":1043.1246015947606}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:24+01","p50":525.754246375,"p95":969.580744779557,"p99":1018.5835064626189}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:25+01","p50":510.90560928125007,"p95":1010.0074454929586,"p99":1047.8161540518001}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:26+01","p50":522.57500925,"p95":988.2640079508752,"p99":1088.793849333809}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:27+01","p50":534.49082275,"p95":809.6402136078312,"p99":907.502464096427}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:28+01","p50":526.8640839062499,"p95":929.2991993946384,"p99":980.5723535757481}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:29+01","p50":530.96312471875,"p95":933.9860093570272,"p99":993.4890586453822}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:30+01","p50":571.1106760555555,"p95":875.5911535023666,"p99":944.159775608982}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:31+01","p50":697.6506065,"p95":1234.25593943824,"p99":1317.8892574024583}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:32+01","p50":714.376971611111,"p95":1174.2999372771146,"p99":1263.8422928783473}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:33+01","p50":573.3186310000001,"p95":1173.3807107685222,"p99":1252.903421315887}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:34+01","p50":515.4901324375,"p95":1069.216212916209,"p99":1159.8631627630261}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:35+01","p50":511.6343217499999,"p95":1154.9382942379966,"p99":1197.6823566329765}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:36+01","p50":474.62153118749995,"p95":1164.3838092998692,"p99":1215.7137372707175}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:37+01","p50":464.73654625,"p95":1189.1063654051695,"p99":1244.2673679435577}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:38+01","p50":442.74434175,"p95":1183.1499836750208,"p99":1268.7039497214894}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:39+01","p50":439.8443479375,"p95":1135.7784411096636,"p99":1224.470696467803}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:40+01","p50":481.74800412499997,"p95":1215.7399186814737,"p99":1301.2503561900405}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:41+01","p50":424.57774221874996,"p95":1295.9946804074864,"p99":1360.2668308326015}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:42+01","p50":437.0834298125,"p95":1327.1855433752337,"p99":1399.2812585717315}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:43+01","p50":336.3055981875,"p95":1404.5267862641706,"p99":1466.9410361389862}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:44+01","p50":326.76183121875,"p95":1626.0442089630806,"p99":1695.9256774298287}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:45+01","p50":359.8491203125,"p95":1601.0866901695294,"p99":1642.1718319782706}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:46+01","p50":394.23339603125,"p95":1482.5219496927775,"p99":1568.083420501815}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:47+01","p50":300.0798480625,"p95":1418.873605437893,"p99":1478.706121210202}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:48+01","p50":313.0404033125,"p95":1431.8802600432023,"p99":1522.568714345523}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:49+01","p50":442.11343387500006,"p95":1416.567270455724,"p99":1496.4330829792348}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:50+01","p50":392.8795325,"p95":1373.4451170509722,"p99":1411.4217423685309}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:51+01","p50":474.7321375,"p95":1182.8752136171909,"p99":1299.5592740875898}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:52+01","p50":522.590415625,"p95":1134.0824906493535,"p99":1201.2878057499809}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:53+01","p50":476.317135375,"p95":1113.0289169222544,"p99":1173.4862054520684}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:54+01","p50":565.2378813749999,"p95":1095.8516640520784,"p99":1157.3129277538146}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:55+01","p50":498.52211384375005,"p95":1066.15780215531,"p99":1102.7254026161752}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:56+01","p50":434.183253625,"p95":1140.7481515431027,"p99":1258.4825391730192}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:57+01","p50":432.682649,"p95":1203.0053655482461,"p99":1253.016329662368}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:58+01","p50":437.493586875,"p95":1240.4738252545808,"p99":1302.7382019095692}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:07:59+01","p50":444.197144375,"p95":1078.6903479039972,"p99":1160.5056427697152}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:00+01","p50":496.92812299999997,"p95":1199.162786436264,"p99":1261.3355736722413}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:01+01","p50":556.9536431666667,"p95":1230.4517246924556,"p99":1307.9512818466796}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:02+01","p50":484.902241,"p95":1267.2466029647492,"p99":1349.804507144013}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:03+01","p50":439.7490656875,"p95":1296.222106880402,"p99":1334.8310805175925}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:04+01","p50":453.805850125,"p95":1234.3673040904464,"p99":1350.2085841899598}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:05+01","p50":429.40374893750004,"p95":1240.6345841702407,"p99":1314.5706062136353}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:06+01","p50":376.37309803125,"p95":1179.9916788505102,"p99":1230.6770133426262}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:07+01","p50":375.6287781875,"p95":1149.499974572711,"p99":1199.4068368444564}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:08+01","p50":297.382116125,"p95":1111.5522314791258,"p99":1178.3547829037132}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:09+01","p50":328.76074221875,"p95":1182.5503406513822,"p99":1246.9218950070574}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:10+01","p50":272.8324859375,"p95":1082.1353934854137,"p99":1211.1838941586648}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:11+01","p50":308.39708578125,"p95":1108.0701601463068,"p99":1146.8861241574677}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:12+01","p50":254.17744134375,"p95":1097.265012777626,"p99":1158.932061552124}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:13+01","p50":253.31748953125,"p95":1090.814105026771,"p99":1143.7979327423957}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:14+01","p50":261.551596,"p95":1101.2164490263704,"p99":1155.999651232667}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:15+01","p50":304.22117965625,"p95":1204.4729781773642,"p99":1254.1068851973923}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:16+01","p50":317.9041055,"p95":1216.307649752094,"p99":1273.1865497752237}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:17+01","p50":331.36072628125,"p95":1367.5685705664266,"p99":1440.9281961454667}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:18+01","p50":459.33826925,"p95":1418.9548532522651,"p99":1482.1253930152245}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:19+01","p50":456.25150718750007,"p95":1357.048695054238,"p99":1432.1037448811894}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:20+01","p50":417.207693375,"p95":1350.4690518827524,"p99":1418.6895526505455}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:21+01","p50":278.4812806875,"p95":1412.3298143224913,"p99":1468.1486892532869}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:22+01","p50":249.4043701875,"p95":1505.0483397557373,"p99":1546.8839660699996}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:23+01","p50":256.42888290625,"p95":1579.5369205821244,"p99":1628.8257072227289}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:24+01","p50":244.01007312500002,"p95":1718.6295320909496,"p99":1786.4807587733653}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:25+01","p50":225.79654825,"p95":1832.4185597102642,"p99":1894.4642495406495}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:26+01","p50":211.6351926875,"p95":1850.837247518722,"p99":1912.5559504938735}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:27+01","p50":225.43305625,"p95":1963.2912985002831,"p99":2014.0140711015663}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:28+01","p50":230.6408721875,"p95":1846.7780771972734,"p99":1924.3864902263829}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:29+01","p50":246.57681887500001,"p95":1823.4961229449777,"p99":1895.3639102247278}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:30+01","p50":248.30398050000002,"p95":1877.0947759549574,"p99":1939.204811906602}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:31+01","p50":304.1475393333333,"p95":2050.9967223914227,"p99":2117.9385163153925}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:32+01","p50":315.2603467777778,"p95":2150.9935741085155,"p99":2220.453660322976}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:33+01","p50":282.24254056250004,"p95":2002.541657610548,"p99":2055.9744421269215}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:34+01","p50":298.65164681249996,"p95":1810.476411665589,"p99":1855.2673514229255}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:35+01","p50":324.621955,"p95":1660.8495221106373,"p99":1744.1787307954621}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:36+01","p50":288.27276175,"p95":1371.766223288791,"p99":1491.9048104506364}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:37+01","p50":333.7823635625,"p95":1522.6434561626093,"p99":1626.3904841728056}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:38+01","p50":307.28389278125,"p95":1619.3232651317285,"p99":1669.649876016037}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:39+01","p50":266.0406035,"p95":1687.9496892222526,"p99":1722.2587455763069}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:40+01","p50":274.0707415,"p95":1803.0503029937695,"p99":1888.338850841073}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:41+01","p50":282.5498419375,"p95":1870.4509298657517,"p99":1932.3210351522412}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:42+01","p50":389.8601725625,"p95":1740.8414846840099,"p99":1780.7737843000582}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:43+01","p50":420.11246374999996,"p95":1559.6230469744103,"p99":1689.856260700873}, + {"metric_name":"oidc_token_duration","timestamp":"2024-12-09 17:08:44+01","p50":410.55254825000003,"p95":1466.747852620311,"p99":1526.2728165811207} +] diff --git a/docs/docs/apis/openidoauth/claims.md b/docs/docs/apis/openidoauth/claims.md index c06fb2c1e3..e82f0b4059 100644 --- a/docs/docs/apis/openidoauth/claims.md +++ b/docs/docs/apis/openidoauth/claims.md @@ -6,67 +6,72 @@ sidebar_label: Claims ZITADEL asserts claims on different places according to the corresponding specifications or project and clients settings. Please check below the matrix for an overview where which scope is asserted. -| Claims | Userinfo | Introspection | ID Token | Access Token | -| :------------------------------------------------ | :------------- | --------------------------------------- | ------------------------------------------- | ---------------------------------------------------- | -| acr | No | No | Yes | No | -| act | No | After Token Exchange with `actor_token` | After Token Exchange with `actor_token` | When JWT and after Token Exchange with `actor_token` | -| address | When requested | When requested | When requested and response_type `id_token` | No | -| amr | No | No | Yes | No | -| aud | No | Yes | Yes | When JWT | -| auth_time | No | No | Yes | No | -| azp (client_id when Introspect) | No | Yes | Yes | When JWT | -| email | When requested | When requested | When requested and response_type `id_token` | No | -| email_verified | When requested | When requested | When requested and response_type `id_token` | No | -| exp | No | Yes | Yes | When JWT | -| family_name | When requested | When requested | When requested and response_type `id_token` | No | -| gender | When requested | When requested | When requested and response_type `id_token` | No | -| given_name | When requested | When requested | When requested and response_type `id_token` | No | -| iat | No | Yes | Yes | When JWT | -| iss | No | Yes | Yes | When JWT | -| jti | No | Yes | No | When JWT | -| locale | When requested | When requested | When requested and response_type `id_token` | No | -| name | When requested | When requested | When requested and response_type `id_token` | No | -| nbf | No | Yes | Yes | When JWT | -| nonce | No | No | Yes | No | -| phone | When requested | When requested | When requested and response_type `id_token` | No | -| phone_verified | When requested | When requested | When requested and response_type `id_token` | No | -| preferred_username (username when Introspect) | When requested | When requested | Yes | No | -| sub | Yes | Yes | Yes | When JWT | -| urn:zitadel:iam:org:domain:primary:\{domainname} | When requested | When requested | When requested | When JWT and requested | -| urn:zitadel:iam:org:project:roles | When requested | When requested | When requested or configured | When JWT and requested or configured | -| urn:zitadel:iam:user:metadata | When requested | When requested | When requested | When JWT and requested | -| urn:zitadel:iam:user:resourceowner:id | When requested | When requested | When requested | When JWT and requested | -| urn:zitadel:iam:user:resourceowner:name | When requested | When requested | When requested | When JWT and requested | -| urn:zitadel:iam:user:resourceowner:primary_domain | When requested | When requested | When requested | When JWT and requested | +| Claims | Userinfo | Introspection | ID Token | Access Token | +|:--------------------------------------------------|:---------------|-----------------------------------------|-------------------------------------------------|------------------------------------------------------| +| acr | No | No | Yes | No | +| act | No | After Token Exchange with `actor_token` | After Token Exchange with `actor_token` | When JWT and after Token Exchange with `actor_token` | +| address | When requested | When requested | When requested and response_type `id_token` | No | +| amr | No | No | Yes | No | +| aud | No | Yes | Yes | When JWT | +| auth_time | No | No | Yes | No | +| azp (client_id when Introspect) | No | Yes | Yes | When JWT | +| email | When requested | When requested | When requested and response_type `id_token` | No | +| email_verified | When requested | When requested | When requested and response_type `id_token` | No | +| exp | No | Yes | Yes | When JWT | +| family_name | When requested | When requested | When requested and response_type `id_token` | No | +| gender | When requested | When requested | When requested and response_type `id_token` | No | +| given_name | When requested | When requested | When requested and response_type `id_token` | No | +| iat | No | Yes | Yes | When JWT | +| iss | No | Yes | Yes | When JWT | +| jti | No | Yes | No | When JWT | +| locale | When requested | When requested | When requested and response_type `id_token` | No | +| name | When requested | When requested | When requested and response_type `id_token` | No | +| nbf | No | Yes | Yes | When JWT | +| nonce | No | No | When provided in the authorization request [^1] | No | +| phone | When requested | When requested | When requested and response_type `id_token` | No | +| phone_verified | When requested | When requested | When requested and response_type `id_token` | No | +| preferred_username (username when Introspect) | When requested | When requested | Yes | No | +| sid | No | No | Yes | No | +| sub | Yes | Yes | Yes | When JWT | +| urn:zitadel:iam:org:domain:primary:\{domainname} | When requested | When requested | When requested | When JWT and requested | +| urn:zitadel:iam:org:project:roles | When requested | When requested | When requested or configured | When JWT and requested or configured | +| urn:zitadel:iam:user:metadata | When requested | When requested | When requested | When JWT and requested | +| urn:zitadel:iam:user:resourceowner:id | When requested | When requested | When requested | When JWT and requested | +| urn:zitadel:iam:user:resourceowner:name | When requested | When requested | When requested | When JWT and requested | +| urn:zitadel:iam:user:resourceowner:primary_domain | When requested | When requested | When requested | When JWT and requested | + +[^1]: The nonce can also be used to distinguish between an id_token and a logout_token as latter must never include a nonce. ## Standard Claims -| Claims | Example | Description | -| :----------------- | :------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -| acr | TBA | TBA | -| act | `{"iss": "$CUSTOM-DOMAIN","sub": "259241944654282754"}` | JSON object describing the actor from the `actor_token` after [token exchange](/docs/guides/integrate/token-exchange#actor-token) | -| address | `Lerchenfeldstrasse 3, 9014 St. Gallen` | TBA | -| amr | `pwd mfa` | Authentication Method References as defined in [RFC8176](https://tools.ietf.org/html/rfc8176)
`password` value is deprecated, please check `pwd` | -| aud | `69234237810729019` | The audience of the token, by default all client id's and the project id are included | -| auth_time | `1311280969` | Unix time of the authentication | -| azp | `69234237810729234` | Client id of the client who requested the token | -| email | `road.runner@acme.ch` | Email Address of the subject | -| email_verified | `true` | Boolean if the email was verified by ZITADEL | -| exp | `1311281970` | Time the token expires (as unix time) | -| family_name | `Runner` | The subjects family name | -| gender | `other` | Gender of the subject | -| given_name | `Road` | Given name of the subject | -| iat | `1311280970` | Time of the token was issued at (as unix time) | -| iss | `$CUSTOM-DOMAIN` | Issuing domain of a token | -| jti | `69234237813329048` | Unique id of the token | -| locale | `en` | Language from the subject | -| name | `Road Runner` | The subjects full name | -| nbf | `1311280970` | Time the token must not be used before (as unix time) | -| nonce | `blQtVEJHNTF0WHhFQmhqZ0RqeHJsdzdkd2d...` | The nonce provided by the client | -| phone | `+41 79 XXX XX XX` | Phone number provided by the user | -| phone_verified | `true` | Boolean if the phone was verified by ZITADEL | -| preferred_username | `road.runner@acme.caos.ch` | ZITADEL's login name of the user. Consist of `username@primarydomain` | -| sub | `77776025198584418` | Subject ID of the user | +| Claims | Example | Description | +|:-------------------|:---------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| acr | TBA | TBA | +| act | `{"iss": "$CUSTOM-DOMAIN","sub": "259241944654282754"}` | JSON object describing the actor from the `actor_token` after [token exchange](/docs/guides/integrate/token-exchange#actor-token) | +| address | `Lerchenfeldstrasse 3, 9014 St. Gallen` | TBA | +| amr | `pwd mfa` | Authentication Method References as defined in [RFC8176](https://tools.ietf.org/html/rfc8176)
`password` value is deprecated, please check `pwd` | +| aud | `69234237810729019` | The audience of the token, by default all client id's and the project id are included | +| auth_time | `1311280969` | Unix time of the authentication | +| azp | `69234237810729234` | Client id of the client who requested the token | +| email | `road.runner@acme.ch` | Email Address of the subject | +| email_verified | `true` | Boolean if the email was verified by ZITADEL | +| events | `{ "http://schemas.openid.net/event/backchannel-logout": {} }` | Security Events such as Back-Channel Logout | +| exp | `1311281970` | Time the token expires (as unix time) | +| family_name | `Runner` | The subjects family name | +| gender | `other` | Gender of the subject | +| given_name | `Road` | Given name of the subject | +| iat | `1311280970` | Time of the token was issued at (as unix time) | +| iss | `$CUSTOM-DOMAIN` | Issuing domain of a token | +| jti | `69234237813329048` | Unique id of the token | +| locale | `en` | Language from the subject | +| name | `Road Runner` | The subjects full name | +| nbf | `1311280970` | Time the token must not be used before (as unix time) | +| nonce | `blQtVEJHNTF0WHhFQmhqZ0RqeHJsdzdkd2d...` | The nonce provided by the client | +| phone | `+41 79 XXX XX XX` | Phone number provided by the user | +| phone_verified | `true` | Boolean if the phone was verified by ZITADEL | +| preferred_username | `road.runner@acme.caos.ch` | ZITADEL's login name of the user. Consist of `username@primarydomain` | +| sid | `291693710356251044` | String identifier for a session. This represents a session of a user agent for a logged-in end-User. Different sid values are used to identify distinct sessions at an OP. | +| sub | `77776025198584418` | Subject ID of the user | ## Custom Claims @@ -100,12 +105,12 @@ https://github.com/zitadel/actions/blob/main/examples/custom_roles.js#L20-L33 ZITADEL reserves some claims to assert certain data. Please check out the [reserved scopes](scopes#reserved-scopes). | Claims | Example | Description | -| :------------------------------------------------ | :------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| urn:zitadel:iam:action:\{actionname}:log | `{"urn:zitadel:iam:action:appendCustomClaims:log": ["test log", "another test log"]}` | This claim is set during Actions as a log, e.g. if two custom claims with the same keys are set. | -| urn:zitadel:iam:org:domain:primary:\{domainname} | `{"urn:zitadel:iam:org:domain:primary": "acme.ch"}` | This claim represents the primary domain of the organization the user belongs to. | +|:--------------------------------------------------|:---------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| urn:zitadel:iam:action:\{actionname}:log | `{"urn:zitadel:iam:action:appendCustomClaims:log": ["test log", "another test log"]}` | This claim is set during Actions as a log, e.g. if two custom claims with the same keys are set. | +| urn:zitadel:iam:org:domain:primary:\{domainname} | `{"urn:zitadel:iam:org:domain:primary": "acme.ch"}` | This claim represents the primary domain of the organization the user belongs to. | | urn:zitadel:iam:org:project:roles | `{"urn:zitadel:iam:org:project:roles": [ {"user": {"id1": "acme.zitade.ch", "id2": "caos.ch"} } ] }` | When roles are asserted, ZITADEL does this by providing the `id` and `primaryDomain` below the role. This gives you the option to check in which organization a user has the role on the current project (where your client belongs to). | -| urn:zitadel:iam:org:project:\{projectid}:roles | `{"urn:zitadel:iam:org:project:id3:roles": [ {"user": {"id1": "acme.zitade.ch", "id2": "caos.ch"} } ] }` | When roles are asserted, ZITADEL does this by providing the `id` and `primaryDomain` below the role. This gives you the option to check in which organization a user has the role on a specific project. | -| urn:zitadel:iam:roles:\{rolename} | TBA | TBA | +| urn:zitadel:iam:org:project:\{projectid}:roles | `{"urn:zitadel:iam:org:project:id3:roles": [ {"user": {"id1": "acme.zitade.ch", "id2": "caos.ch"} } ] }` | When roles are asserted, ZITADEL does this by providing the `id` and `primaryDomain` below the role. This gives you the option to check in which organization a user has the role on a specific project. | +| urn:zitadel:iam:roles:\{rolename} | TBA | TBA | | urn:zitadel:iam:user:metadata | `{"urn:zitadel:iam:user:metadata": [ {"key": "VmFsdWU=" } ] }` | The metadata claim will include all metadata of a user. The values are base64 encoded. | | urn:zitadel:iam:user:resourceowner:id | `{"urn:zitadel:iam:user:resourceowner:id": "orgid"}` | This claim represents the id of the resource owner organisation of the user. | | urn:zitadel:iam:user:resourceowner:name | `{"urn:zitadel:iam:user:resourceowner:name": "ACME"}` | This claim represents the name of the resource owner organisation of the user. | diff --git a/docs/docs/examples/login/flutter.md b/docs/docs/examples/login/flutter.md index 87822a01d6..f6e8352831 100644 --- a/docs/docs/examples/login/flutter.md +++ b/docs/docs/examples/login/flutter.md @@ -75,8 +75,8 @@ To install run: ```bash flutter pub add http -flutter pub add flutter_web_auth_2 -flutter pub add flutter_secure_storage +flutter pub add oidc +flutter pub add oidc_default_store ``` #### Setup for Android diff --git a/docs/docs/guides/integrate/back-channel-logout.mdx b/docs/docs/guides/integrate/back-channel-logout.mdx new file mode 100644 index 0000000000..6f635320bb --- /dev/null +++ b/docs/docs/guides/integrate/back-channel-logout.mdx @@ -0,0 +1,177 @@ +--- +title: OIDC Back-Channel Logout +sidebar_label: Back-Channel Logout +--- + +The Back-Channel Logout implements [OpenID Connect Back-Channel Logout 1.0](https://openid.net/specs/openid-connect-backchannel-1_0.html) +and can be used to notify clients about session termination at the OpenID Provider. This guide will explain how +back-channel logout is implemented inside ZITADEL and gives some usage examples. + +:::info +Back-Channel Logout is currently an experimental [beta](/docs/support/software-release-cycles-support#beta) feature. +Be sure to enable it on the [feature API](#feature-api) before using it. +We highly recommend also enabling the `webkey` feature on your instance. This will prevent issues with automatic key rotation. +::: + +In this guide we assume your already familiar with getting and validation token. You should already have a good +understanding on the following topics before starting with this guide: + +- Integrate your app with the [OIDC flow](/docs/guides/integrate/login/oidc/login-users) to obtain tokens +- [Claims](/docs/apis/openidoauth/claims) +- [Scope](/docs/apis/openidoauth/scopes) +- Audience + +## Concept + +ZITADEL provides the possibility for OpenID Connect clients to be notified about the session termination, for example +if a user signs out from another client using the same SSO session. +This allows the client to also invalidate the user's session without the need for an active browser session. + +![Authentication and Back-Channel Logout Flow](/img/guides/back-channel-logout/back-channel-logout-flow.png) + +1. When an unauthenticated user visits your application, +2. it will create an authorization request to the authorization endpoint. +3. The Authorization Server (ZITADEL) will send an HTTP 302 to the user's browser, which will redirect them to the login UI. +4. The user will have to authenticate using the demanded auth mechanics. +5. Your application will be called on the registered callback path (redirect_uri) for the authorization code exchange. +See [OIDC Flow](/docs/guides/integrate/login/oidc/login-users) for more details. +On successful exchange, an SSO session will be created. +6. If the user opens another application, +7. the application will also create an authorization request to the authorization endpoint. +8. ZITADEL can then reuse the existing SSO session and will not ask the user to authenticate again and directly return the code for exchange. +See [OIDC Flow](/docs/guides/integrate/login/oidc/login-users) again for details. +9. At a later point, the user signs out from one of the applications, in this case the second one. +10. The application will redirect the user to the end_session endpoint. +11. ZITADEL will terminate the SSO session and redirect the user back to the application's post_logout_redirect_uri. +The application can delete the local session. +12. ZITADEL will also send a back-channel logout request to every registered application with previously opened sessions. +The application can then invalidate the user's session without the need for an active browser session. + +### Indicating Support + +As required by the [specification](https://openid.net/specs/openid-connect-backchannel-1_0.html#BCSupport), ZITADEL +will advertise the `backchannel_logout_supported` and `backchannel_logout_session_supported` +on the discovery endpoint once the feature flag is enabled. +The latter boolean indicates, that ZITADEL will provide a session ID (`sid`) claim as part of the logout token. This +provides the possibility to match the exact SSO session, which was terminated. + +### Client + +To enable the back-channel logout on a client, they simply need to register a `backchannel_logout_uri` as part of their +configuration, e.g. [creating an OIDC application](https://zitadel.com/docs/apis/resources/mgmt/management-service-add-oidc-app). + +As soon as the URI is set, every new authorization request will register a back-channel notification to be sent once +the session is terminated by a user sign out. + +## Back-Channel Request + +When the session is terminated, ZITADEL will send back-channel logout requests asynchronously to every registered +client of the corresponding session. +The request is an `application/x-www-form-urlencoded` POST request to the registered URI including a `logout_token` +parameter in the body. + +Please be aware that body *may* contain other values in addition to logout_token. Values that are not understood by the +implementation *must* be ignored. + +### Logout Token + +The `logout_token` sent in the request is a JWT similar to an ID Token. + +:::info +Note however, that a Logout Token must never contain a `nonce` claim, to make sure it cannot be used as an ID Token. +::: + +The following Claims are used within the Logout Token: + +| Claim | Example | Description | +|--------|----------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------| +| iss | `$CUSTOM-DOMAIN` | Issuer Identifier | +| sub | `77776025198584418` | Subject Identifier (the user who signed out) | +| aud | `69234237810729019` | Audience(s), will always contain your client_id | +| iat | `1311280970` | Issued at time | +| exp | `1311281970` | Expiration time (by default 15min after the issued at time) | +| jti | `69234237813329048` | Unique identifier for the token | +| events | `{ "http://schemas.openid.net/event/backchannel-logout": {} }` | JSON object, which always contains http://schemas.openid.net/event/backchannel-logout. This declares that the JWT is a Logout Token. | +| sid | `291693710356251044` | Session ID - String identifier for a Session. | + +#### Validation + +Verify the Logout Token the same way you verify an ID Token including signature validation, issuer, audience, expiration and issued at time claim checks. +Make sure that either a subject (`sub`) and / or a session ID (`sid`) is present in the token to identify the user or its session. +Also, check that the `events` claim contains the `http://schemas.openid.net/event/backchannel-logout` value. +Optionally, you can also verify that the TokenID (`jti`) has not been used before. + +For details on how to validate the logout token, please refer to the [OpenID Connect Back-Channel Logout specification](https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation). + +## Example + +This example assumes that your application is registered with client_id `243864426485212395@example` and the back-channel logout +URI is set to `https://example.com/logout`. + +### Authentication + +When the user signed in to your application, ZITADEL issued the following id_token: +``` +eyJhbGciOiJSUzI1NiIsImtpZCI6IjI5NjkzMzA1NjAxNzY0Nzg3NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJzdWIiOiIxNjQ4NDk0OTQyOTc0NzczNzciLCJhdWQiOlsiMjQzODY0NDI2NDg1MjEyMzk1QGV4YW1wbGUiLCIyNDM4NjQzODExNjk4ODY0NDMiXSwiZXhwIjoxNzMzNzgyNDQ0LCJpYXQiOjE3MzM3MzkyNDQsImF1dGhfdGltZSI6MTczMzczOTI0NCwiYW1yIjpbInVzZXIiLCJtZmEiXSwiYXpwIjoiMjQzODY0NDI2NDg1MjEyMzk1QGV4YW1wbGUiLCJjbGllbnRfaWQiOiIyNDM4NjQ0MjY0ODUyMTIzOTVAZXhhbXBsZSIsImF0X2hhc2giOiJSWVFPSkJuT01LS0hrN1VnLWY1eFJnIiwic2lkIjoiVjFfMjk3MzY0ODE4OTgwMDM0MDA0In0K.lZxHE_Z4tiaDQE-DPtYjnvb0H9rz4wMoGfBMeEm4EG837DGJb7RTq7PuMHWc4Z2e_6lilwfVBWDEOhmrnjmkQwDVxInbbJfN0NiWgeqoW-C1SZ_G00UVIbJdaxPy2-haRihDNNpy0Gjmi7q3FkGXGqkJx9S7ZtC5ISbXLnqfbRbuapoMs7hHNf-Iltf8v7dMs3K8dcAPSHJm0X0x6Cu1ZMeAS2a6H05xKXGM0bRK830AZlL8xmxTNj_q_WZKzxz304XrRNHvYRcHKmJqURSHvRNUR38QeNaiKzINlV2sVvPEY6Dru_PHSPNFu7YLWiUi34VUla6VTxy9ctI_BtI4nw +``` + +This represents the following claims: +```json +{ + "iss": "http://localhost:8080", + "sub": "164849494297477377", + "aud": [ + "243864426485212395@example", + "243864381169886443" + ], + "exp": 1733782444, + "iat": 1733739244, + "auth_time": 1733739244, + "amr": [ + "user", + "mfa" + ], + "azp": "243864426485212395@example", + "client_id": "243864426485212395@example", + "at_hash": "RYQOJBnOMKKHk7Ug-f5xRg", + "sid": "V1_297364818980034004" +} +``` + +As you can see, the `sid` claim is present in the token. This represents the application's specific session ID of the +user session. + + +### Sign Out and Back-Channel Logout + +When the user signs out from another application, ZITADEL will send a POST request to `https://example.com/logout` with +the following body: + +```http +POST /logout HTTP/1.1 +Host: example.com +Content-Type: application/x-www-form-urlencoded + +logout_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjI5NjkzMzA1NjAxNzY0Nzg3NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJzdWIiOiIxNjQ4NDk0OTQyOTc0NzczNzciLCJhdWQiOlsiMjQzODY0NDI2NDg1MjEyMzk1QGV4YW1wbGUiXSwiaWF0IjoxNzMzNzM5MjUyLCJleHAiOjE3MzM3NDAxNTMsImp0aSI6IjI5NzM2NDgzNDQ5ODkyMTEzNyIsImV2ZW50cyI6eyJodHRwOi8vc2NoZW1hcy5vcGVuaWQubmV0L2V2ZW50L2JhY2tjaGFubmVsLWxvZ291dCI6e319LCJzaWQiOiJWMV8yOTczNjQ4MTg5ODAwMzQwMDQifQo.ELOPuS61fy8GgCKEtru5df4-9GI4-KQlNf_DMp6b5mtJZIrfykA_M7lYOxOskYhTDicBoQ2jjOjzsDqktI4r6ptD068c5LEOx-k2OVk7ybADsK7tht5omYy4tsbHmkDCZN065WMH0SQH7NKGroVW-MACi6Peuiz3nlQsfho0EnLECqhZT60qxu6qtofvBVhHe15Zlkzffy0vxjKEIeJoTmX_cNVsHlrC_n1vTqZStBrkqu3_rwZxZuynX47vf7_kj_kKhJ3TffRF561n1AP5xnhZ9i--rnaucbGtGGImlKi2sdqC4GzjtdlaKJaRuVF-91x758SLBxqJXPucroJoWw +``` + +```json +{ + "iss": "http://localhost:8080", + "sub": "164849494297477377", + "aud": [ + "243864426485212395@example" + ], + "iat": 1733739252, + "exp": 1733740153, + "jti": "297364834498921137", + "events": { + "http://schemas.openid.net/event/backchannel-logout": {} + }, + "sid": "V1_297364818980034004" +} +``` + +After validating the token, the application can now invalidate the user's local session based on the `sid` claim. + +Some applications might also want to delete all user sessions. In this case, the `sub` claim can be used to identify the user. diff --git a/docs/docs/guides/integrate/identity-providers/google.mdx b/docs/docs/guides/integrate/identity-providers/google.mdx index ef191054a6..182a2dc748 100644 --- a/docs/docs/guides/integrate/identity-providers/google.mdx +++ b/docs/docs/guides/integrate/identity-providers/google.mdx @@ -13,7 +13,7 @@ import { ResponsivePlayer } from "../../../../src/components/player"; - + ## Open the Google Identity Provider Template diff --git a/docs/docs/guides/integrate/token-exchange.mdx b/docs/docs/guides/integrate/token-exchange.mdx index ab3ee26f48..00fda4f17b 100644 --- a/docs/docs/guides/integrate/token-exchange.mdx +++ b/docs/docs/guides/integrate/token-exchange.mdx @@ -10,7 +10,7 @@ import TokenExchangeResponse from "../../apis/openidoauth/_token_exchange_respon The Token Exchange grant implements [RFC 8693, OAuth 2.0 Token Exchange](https://www.rfc-editor.org/rfc/rfc8693) and can be used to exchange tokens to a different scope, audience or subject. Changing the subject of an authenticated token is called impersonation or delegation. This guide will explain how token exchange is implemented inside ZITADEL and gives some usage examples. :::info -Token Exchange is currently an experimental beta](/docs/support/software-release-cycles-support#beta) feature. Be sure to enable it on the [feature API](#feature-api) before using it. +Token Exchange is currently an [experimental beta](/docs/support/software-release-cycles-support#beta) feature. Be sure to enable it on the [feature API](#feature-api) before using it. ::: In this guide we assume that the application performing the token exchange is already in possession of tokens. You should already have a good understanding on the following topics before starting with this guide: diff --git a/docs/docs/guides/integrate/zitadel-apis/event-api.md b/docs/docs/guides/integrate/zitadel-apis/event-api.md index ed35aa1c8e..c79cb27e8e 100644 --- a/docs/docs/guides/integrate/zitadel-apis/event-api.md +++ b/docs/docs/guides/integrate/zitadel-apis/event-api.md @@ -114,10 +114,13 @@ curl --request POST \ }' ``` -## Example: Find out when user have been authenticated +## Example: Find out which users have authenticated -The following example shows you how you could use the events search to get all events where a token has been created. -Also we include the refresh tokens in this example to know when the user has become a new token. +### OIDC session + +The following example shows you how you could use the events search to get all events where a user has authenticated using OIDC. +Also we include the refresh tokens in this example to know when the user has received a new token. +Sessions without tokens events may by created during implicit flow with ID Token only, which do not create an access token. ```bash curl --request POST \ @@ -127,13 +130,25 @@ curl --request POST \ --data '{ "asc": true, "limit": 1000, - "event_types": [ - "user.token.added", - "user.refresh.token.added" - ] + "eventTypes": [ + "oidc_session.added", + "oidc_session.access_token.added", + "oidc_session.refresh_token.added", + "oidc_session.refresh_token.renewed" + ], + "aggregateTypes": [ + "oidc_session" + ] }' ``` + + ## Example: Get failed login attempt diff --git a/docs/docs/guides/manage/customize/texts.md b/docs/docs/guides/manage/customize/texts.md index d3a3fd5299..0d4bfdd21e 100644 --- a/docs/docs/guides/manage/customize/texts.md +++ b/docs/docs/guides/manage/customize/texts.md @@ -51,6 +51,7 @@ ZITADEL is available in the following languages - Dutch (nl) - Swedish (sv) - Hungarian (hu) +- ํ•œ๊ตญ์–ด (ko) A language is displayed based on your agent's language header. If a users language header doesn't match any of the supported or [restricted](#restrict-languages) languages, the instances default language will be used. diff --git a/docs/docs/guides/manage/user/reg-create-user.md b/docs/docs/guides/manage/user/reg-create-user.md index 0ac85e3b33..9c523cba24 100644 --- a/docs/docs/guides/manage/user/reg-create-user.md +++ b/docs/docs/guides/manage/user/reg-create-user.md @@ -6,7 +6,7 @@ The ZITADEL API has different possibilities to create users. This can be used, if you are building your own registration page. Use the following API call to create your users: -[Create User (Human)](/apis/resources/mgmt/management-service-import-human-user.api.mdx) +[Create User (Human)](apis/resources/user_service_v2/user-service-add-human-user.api.mdx) ## With Username and Password diff --git a/docs/docs/guides/start/quickstart.mdx b/docs/docs/guides/start/quickstart.mdx index 99f193ddd5..b5c3ff5d6d 100644 --- a/docs/docs/guides/start/quickstart.mdx +++ b/docs/docs/guides/start/quickstart.mdx @@ -7,7 +7,7 @@ import { ResponsivePlayer } from "../../../src/components/player"; In this quick start guide, we will be learning some fundamentals on how to set up ZITADEL for user management and application security. Thereafter, we will secure a React-based Single Page Application (SPA) using ZITADEL. - + The sample application allows users to securely log in to ZITADEL using the OIDC Proof Key for Code Exchange (PKCE) flow. This flow ensures that the authentication process is secure by using a code verifier and a code challenge, which are sent to ZITADEL to obtain an access token. The access token is then used by the app to access the userinfo endpoint to retrieve and display information about the logged-in user. The app also has a logout feature that allows users to end their session and clear their access token. Overall, the app provides a simple and secure way for users to authenticate and access protected resources within ZITADEL. diff --git a/docs/docs/legal/subprocessors.md b/docs/docs/legal/subprocessors.md index b5fa71ee03..bc10deae95 100644 --- a/docs/docs/legal/subprocessors.md +++ b/docs/docs/legal/subprocessors.md @@ -4,7 +4,7 @@ sidebar_label: Third Party Sub-Processors custom_edit_url: null --- -Last updated on November 15, 2023 +Last updated on December 5, 2025. In order to achieve the best possible transparency we publish which sub-processors and services we use to provide ZITADEL and related services. The table shows what activity each entity performs. @@ -13,9 +13,5 @@ This explains the limited processing of customer data the entity is authorized t We regularly audit all data processing agreements that we have with our sub-processors to guarantee that they adhere to the same level of privacy as ours to protect your personal data. -The following table indicates which sub-processors have access to end-user data. We try to minimize the number of sub-processors that handle end-user data on our behalf to reduce any vendor related risks. +You can find [the full list of sub-processors in our trust center](https://trust.zitadel.com/subprocessors). We try to minimize the number of sub-processors that handle end-user data on our behalf to reduce any vendor related risks. Some providers are used by default, but you can opt-out of the default provide and replace the sub-processor by a provider of your choice. - -import { SubProcessorTable } from "../../src/components/subprocessors"; - - diff --git a/docs/docs/self-hosting/deploy/knative.mdx b/docs/docs/self-hosting/deploy/knative.mdx index 0613e7f7e2..b26c7189bd 100644 --- a/docs/docs/self-hosting/deploy/knative.mdx +++ b/docs/docs/self-hosting/deploy/knative.mdx @@ -27,7 +27,7 @@ kubectl apply -f https://raw.githubusercontent.com/zitadel/zitadel/main/deploy/k ```bash kn service create zitadel \ ---image ghcr.io/zitadel/zitadel:stable \ +--image ghcr.io/zitadel/zitadel:latest \ --port 8080 \ --env ZITADEL_DATABASE_COCKROACH_HOST=cockroachdb \ --env ZITADEL_EXTERNALSECURE=false \ diff --git a/docs/docs/self-hosting/deploy/linux.mdx b/docs/docs/self-hosting/deploy/linux.mdx index 359bf26c69..eb7f4dc90d 100644 --- a/docs/docs/self-hosting/deploy/linux.mdx +++ b/docs/docs/self-hosting/deploy/linux.mdx @@ -11,7 +11,7 @@ import NoteInstanceNotFound from "./troubleshooting/_note_instance_not_found.mdx ## Install PostgreSQL Download a `postgresql` binary as described [in the PostgreSQL docs](https://www.postgresql.org/download/linux/). -ZITADEL is tested against PostgreSQL and CockroachDB latest stable tag and Ubuntu 20.04. +ZITADEL is tested against PostgreSQL and CockroachDB latest stable tag and Ubuntu 22.04. ## Run PostgreSQL diff --git a/docs/docs/self-hosting/deploy/loadbalancing-example/docker-compose.yaml b/docs/docs/self-hosting/deploy/loadbalancing-example/docker-compose.yaml index 4c455621fb..2b9266c798 100644 --- a/docs/docs/self-hosting/deploy/loadbalancing-example/docker-compose.yaml +++ b/docs/docs/self-hosting/deploy/loadbalancing-example/docker-compose.yaml @@ -15,7 +15,7 @@ services: restart: 'always' networks: - 'zitadel' - image: 'ghcr.io/zitadel/zitadel:stable' + image: 'ghcr.io/zitadel/zitadel:latest' command: 'start-from-init --config /example-zitadel-config.yaml --config /example-zitadel-secrets.yaml --steps /example-zitadel-init-steps.yaml --masterkey "${ZITADEL_MASTERKEY}" --tlsMode external' depends_on: db: diff --git a/docs/docs/self-hosting/deploy/macos.mdx b/docs/docs/self-hosting/deploy/macos.mdx index a9673ab8b3..f736255478 100644 --- a/docs/docs/self-hosting/deploy/macos.mdx +++ b/docs/docs/self-hosting/deploy/macos.mdx @@ -11,7 +11,7 @@ import NoteInstanceNotFound from './troubleshooting/_note_instance_not_found.mdx ## Install PostgreSQL Download a `postgresql` binary as described [in the PostgreSQL docs](https://www.postgresql.org/download/macosx/). -ZITADEL is tested against PostgreSQL and CockroachDB latest stable tag and Ubuntu 20.04. +ZITADEL is tested against PostgreSQL and CockroachDB latest stable tag and Ubuntu 22.04. ## Run PostgreSQL diff --git a/docs/docs/self-hosting/manage/cache.md b/docs/docs/self-hosting/manage/cache.md new file mode 100644 index 0000000000..def2ece633 --- /dev/null +++ b/docs/docs/self-hosting/manage/cache.md @@ -0,0 +1,253 @@ +--- +title: Caches +sidebar_label: Caches +--- + +ZITADEL supports the use of a caches to speed up the lookup of frequently needed objects. As opposed to HTTP caches which might reside between ZITADEL and end-user applications, the cache build into ZITADEL uses active invalidation when an object gets updated. Another difference is that HTTP caches only cache the result of a complete request and the built-in cache stores objects needed for the internal business logic. For example, each request made to ZITADEL needs to retrieve and set [instance](/docs/concepts/structure/instance) information in middleware. + +:::info +Caches is currently an [experimental beta](/docs/support/software-release-cycles-support#beta) feature. +::: + +## Configuration + +The `Caches` configuration entry defines *connectors* which can be used by several objects. It is possible to mix *connectors* with different objects based on operational needs. + +```yaml +Caches: + Connectors: + SomeConnector: + Enabled: true + SomeOption: foo + SomeObject: + # Connector must be enabled above. + # When connector is empty, this cache will be disabled. + Connector: "SomeConnector" + MaxAge: 1h + LastUsage: 10m + # Log enables cache-specific logging. Default to error log to stderr when omitted. + Log: + Level: error +``` + +For a full configuration reference, please see the [runtime configuration file](/docs/self-hosting/manage/configure#runtime-configuration-file) section's `defaults.yaml`. + +## Connectors + +ZITADEL supports a number of *connectors*. Connectors integrate a cache with a storage backend. Users can combine connectors with the type of object cache depending on their operational and performance requirements. +When no connector is specified for an object cache, then no caching is performed. This is the current default. + +### Auto prune + +Some connectors take an `AutoPrune` option. This is provided for caches which don't have built-in expiry and cleanup routines. The auto pruner is a routine launched by ZITADEL and scans and removes outdated objects in the cache. Pruning can take a cost as they typically involve some kind of scan. However, using a long interval can cause higher storage utilization. + +```yaml +Caches: + Connectors: + Memory: + Enabled: true + # AutoPrune removes invalidated or expired object from the cache. + AutoPrune: + Interval: 1m + TimeOut: 5s +``` + +### Redis cache + +Redis is supported in simple mode. Cluster and Sentinel are not yet supported. There is also a circuit-breaker provided which prevents a single point of failure, should the single Redis instance become unavailable. + +Benefits: + +- Centralized cache with single source of truth +- Consistent invalidation +- Very fast when network latency is kept to a minimum +- Built-in object expiry, no pruner required + +Drawbacks: + +- Increased operational overhead: need to run a Redis instance as part of your infrastructure. +- When running multiple servers of ZITADEL in different regions, network roundtrip time might impact performance, neutralizing the benefit of a cache. + +#### Circuit breaker + +A [circuit breaker](https://learn.microsoft.com/en-us/previous-versions/msp-n-p/dn589784(v=pandp.10)?redirectedfrom=MSDN) is provided for the Redis connector, to prevent a single point of failure in the case persistent errors. When the circuit breaker opens, the cache is temporary disabled and ignored. ZITADEL will continue to operate using queries to the database. + +```yaml +Caches: + Connectors: + Redis: + Enabled: true + Addr: localhost:6379 + # Many other options... + CircuitBreaker: + # Interval when the counters are reset to 0. + # 0 interval never resets the counters until the CB is opened. + Interval: 0 + # Amount of consecutive failures permitted + MaxConsecutiveFailures: 5 + # The ratio of failed requests out of total requests + MaxFailureRatio: 0.1 + # Timeout after opening of the CB, until the state is set to half-open. + Timeout: 60s + # The allowed amount of requests that are allowed to pass when the CB is half-open. + MaxRetryRequests: 1 +``` + +### PostgreSQL cache + +PostgreSQL can be used to store objects in unlogged tables. [Unlogged tables](https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-UNLOGGED) do not write to the WAL log and are therefore faster than regular tables. If the PostgreSQL server crashes, the data from those tables are lost. ZITADEL always creates the cache schema in the `zitadel` database during [setup](./updating_scaling#the-setup-phase). This connector requires a [pruner](#auto-prune) routine. + +Benefits: + +- Centralized cache with single source of truth +- No operational overhead. Reuses the query connection pool and the existing `zitadel` database. +- Consistent invalidation +- Faster than regular queries which often contain `JOIN` clauses. + +Drawbacks: + +- Slowest of the available caching options +- Might put additional strain on the database server, limiting horizontal scalability +- CockroachDB does not support unlogged tables. When this connector is enabled against CockroachDB, it does work but little to no performance benefit is to be expected. + +### Local memory cache + +ZITADEL is capable of caching object in local application memory, using hash-maps. Each ZITADEL server manages its own copy of the cache. This connector requires a [pruner](#auto-prune) routine. + +Benefits: + +- Fastest of the available caching options +- No operational overhead + +Drawbacks: + +- Inconsistent invalidation. An object validated in one ZITADEL server will not get invalidated in other servers. +- There's no single source of truth. Different servers may operate on a different version of an object +- Data is duplicated in each server, consuming more total memory inside a deployment. + +The drawbacks restricts its usefulness in distributed deployments. However simple installations running a single server can benefit greatly from this type of cache. For example test, development or home deployments. +If inconsistency is acceptable for short periods of time, one can choose to use this type of cache in distributed deployments with short max age configuration. + +**For example**: A ZITADEL deployment with 2 servers is serving 1000 req/sec total. The installation only has one instance[^1]. There is only a small amount of data cached (a few kB) so duplication is not a problem in this case. It is acceptable for [instance level setting](/docs/guides/manage/console/default-settings) to be out-dated for a short amount of time. When the memory cache is enabled for the instance objects, with a max age of 1 second, the instance only needs to be obtained from the database 2 times per second (once for each server). Saving 998 of redundant queries. Once an instance level setting is changed, it takes up to 1 second for all the servers to get the new state. + +## Objects + +The following section describes the type of objects ZITADEL can currently cache. Objects are actively invalidated at the cache backend when one of their properties is changed. Each object cache defines: + +- `Connector`: Selects the used [connector](#connectors) back-end. Must be activated first. +- `MaxAge`: the amount of time that an object is considered valid. When this age is passed the object is ignored (cache miss) and possibly cleaned up by the [pruner](#auto-prune) or other built-in garbage collection. +- `LastUsage`: defines usage based lifetime. Each time an object is used, its usage timestamp is updated. Popular objects remain cached, while unused objects are cleaned up. This option can be used to indirectly limit the size of the cache. +- `Log`: allows specific log settings for the cache. This can be used to debug a certain cache without having to change the global log level. + +```yaml +Caches: + SomeObject: + # Connector must be enabled above. + # When connector is empty, this cache will be disabled. + Connector: "" + MaxAge: 1h + LastUsage: 10m + # Log enables cache-specific logging. Default to error log to stderr when omitted. + Log: + Level: error + AddSource: true + Formatter: + Format: text +``` + +### Instance + +All HTTP and gRPC requests sent to ZITADEL receive an instance context. The instance is usually resolved by the domain from the request. In some cases, like the [system service](/docs/apis/resources/system/system-service), the instance can be resolved by its ID. An instance object contains many of the [default settings](/docs/guides/manage/console/default-settings): + +- Instance [features](/docs/guides/manage/console/default-settings#features) +- Instance domains: generated and [custom](/docs/guides/manage/cloud/instances#add-custom-domain) +- [Trusted domains](/docs/apis/resources/admin/admin-service-add-instance-trusted-domain) +- Security settings ([IFrame policy](/docs/guides/solution-scenarios/configurations#embedding-zitadel-in-an-iframe)) +- Limits[^2] +- [Allowed languages](/docs/guides/manage/console/default-settings#languages) + +These settings typically change infrequently in production. ***Every*** request made to ZITADEL needs to query for the instance. This is a typical case of set once, get many times where a cache can provide a significant optimization. + +### Milestones + +Milestones are used to track the administrator's progress in setting up their instance. Milestones are used to render *your next steps* in the [console](/docs/guides/manage/console/overview) landing page. +Milestones are reached upon the first time a certain action is performed. For example the first application created or the first human login. In order to push a "reached" event only once, ZITADEL must keep track of the current state of milestones by an eventstore query every time an eligible action is performed. This can cause an unwanted overhead on production servers, therefore they are cached. + +As an extra optimization, once all milestones are reached by the instance, an in-memory flag is set and the milestone state is never queried again from the database nor cache. +For single instance setups which fulfilled all milestone (*your next steps* in console) it is not needed to enable this cache. We mainly use it for ZITADEL cloud where there are many instances with *incomplete* milestones. + +### Organization + +Most resources like users, project and applications are part of an [organization](/docs/concepts/structure/organizations). Therefore many parts of the ZITADEL logic search for an organization by ID or by their primary domain. +Organization objects are quite small and receive infrequent updates after they are created: + +- Change of organization name +- Deactivation / Reactivation +- Change of primary domain +- Removal + +## Examples + +Currently caches are in beta and disabled by default. However, if you want to give caching a try, the following sections contains some suggested configurations for different setups. + +The following configuration is recommended for single instance setups with a single ZITADEL server: + +```yaml +Caches: + Memory: + Enabled: true + Instance: + Connector: "memory" + MaxAge: 1h + Organization: + Connector: "memory" + MaxAge: 1h +``` + +The following configuration is recommended for single instance setups with high traffic on multiple servers, where Redis is not available: + +```yaml +Caches: + Memory: + Enabled: true + Postgres: + Enabled: true + Instance: + Connector: "memory" + MaxAge: 1s + Milestones: + Connector: "postgres" + MaxAge: 1h + LastUsage: 10m + Organization: + Connector: "memory" + MaxAge: 1s +``` + +When running many instances on multiple servers: + +```yaml +Caches: + Connectors: + Redis: + Enabled: true + # Other connection options + + Instance: + Connector: "redis" + MaxAge: 1h + LastUsage: 10m + Milestones: + Connector: "redis" + MaxAge: 1h + LastUsage: 10m + Organization: + Connector: "redis" + MaxAge: 1h + LastUsage: 10m +``` +---- + +[^1]: Many deployments of ZITADEL have only one or few [instances](/docs/concepts/structure/instance). Multiple instances are mostly used for ZITADEL cloud, where each customer gets at least one instance. + +[^2]: Limits are imposed by the system API, usually when customers exceed their subscription in ZITADEL cloud. \ No newline at end of file diff --git a/docs/docs/self-hosting/manage/configure/docker-compose.yaml b/docs/docs/self-hosting/manage/configure/docker-compose.yaml index 69ebdd9093..8e5c9fbc05 100644 --- a/docs/docs/self-hosting/manage/configure/docker-compose.yaml +++ b/docs/docs/self-hosting/manage/configure/docker-compose.yaml @@ -5,7 +5,7 @@ services: restart: "always" networks: - "zitadel" - image: "ghcr.io/zitadel/zitadel:stable" + image: "ghcr.io/zitadel/zitadel:latest" command: 'start-from-init --config /example-zitadel-config.yaml --config /example-zitadel-secrets.yaml --steps /example-zitadel-init-steps.yaml --masterkey "${ZITADEL_MASTERKEY}" --tlsMode disabled' ports: - "8080:8080" diff --git a/docs/docs/support/advisory/a10013.md b/docs/docs/support/advisory/a10013.md new file mode 100644 index 0000000000..f95d1d62fc --- /dev/null +++ b/docs/docs/support/advisory/a10013.md @@ -0,0 +1,25 @@ +--- +title: Technical Advisory 10013 +--- + +## Date + +Date: 2024-12-09 + +## Description + +ZITADEL currently provides a "latest" and "stable" release tags as well as maintenance branches, where bug fixes are backported. +We also publish release candidates regularly. + +The "stable" release channel was introduced for users seeking a more reliable and production-ready version of the software. +However, most customers have their own deployment policies and cycles. +Backports and security fixes are currently done as needed or required by customers. +zitadel.cloud follows a similar approach, where the latest release is deployed a few days after its creation. + +## Mitigation + +If you used the "stable" Docker release, please consider switching to a specific version tag and follow the [release notes on GitHub](https://github.com/zitadel/zitadel/releases) for latest changes. + +## Impact + +The "stable" version will no longer be published or updated, and the corresponding Docker image tag will not be maintained anymore. diff --git a/docs/docs/support/software-release-cycles-support.md b/docs/docs/support/software-release-cycles-support.md index 6e6e094c10..4a9484dd27 100644 --- a/docs/docs/support/software-release-cycles-support.md +++ b/docs/docs/support/software-release-cycles-support.md @@ -82,7 +82,7 @@ Features in General Availability are not marked explicitly. All release channels receive regular updates and bug fixes. However, the timing and frequency of updates may differ between the channels. -The choice between the "release candidate", "latest" and "stable" release channels depends on the specific requirements, preferences, and risk tolerance of the users. +The choice between the "release candidate", "latest" and stable release channels depends on the specific requirements, preferences, and risk tolerance of the users. [List of all releases](https://github.com/zitadel/zitadel/releases) @@ -100,19 +100,6 @@ The "latest" release channel is designed for users who prefer to access the most It provides early access to new functionalities and improvements but may involve a higher degree of risk as it is the most actively developed version. Users opting for the latest release channel should be aware that occasional bugs or issues may arise due to the ongoing development process. -### Stable - -The "stable" release channel is intended for users seeking a more reliable and production-ready version of the software. -It offers a well-tested and validated release with fewer known issues and a higher level of stability. -The stable release channel undergoes rigorous quality assurance and testing processes to ensure that it meets the highest standards of reliability and performance. -It is recommended for users who prioritize stability over immediate access to the latest features. - -Current Stable Version: - -```yaml reference -https://github.com/zitadel/zitadel/blob/main/release-channels.yaml -``` - ## Maintenance ZITADEL Cloud follows a regular deployment cycle to ensure our product remains up-to-date, secure, and provides new features. diff --git a/docs/docs/support/technical_advisory.mdx b/docs/docs/support/technical_advisory.mdx index 6e5c6ac519..7562ff3870 100644 --- a/docs/docs/support/technical_advisory.mdx +++ b/docs/docs/support/technical_advisory.mdx @@ -190,6 +190,30 @@ We understand that these advisories may include breaking changes, and we aim to 2.59.0 2024-08-19 + + + A-10012 + + Tncreased transaction duration for projections + Breaking Behavior Change + + In version 2.63.0 we've increased the transaction duration for projections to resolve outdated projections or dead-locks. + + 2.63.0 + 2024-09-26 + + + + A-10013 + + Deprecation of "stable" version + Breaking Behavior Change + + The "stable" version will no longer be published or updated, and the corresponding Docker image tag will not be maintained anymore. + + - + 2024-12-09 + ## Subscribe to our Mailing List diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index c0c7d5a45c..f5f349d951 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -289,7 +289,7 @@ module.exports = { outputDir: "docs/apis/resources/user_service_v2", sidebarOptions: { groupPathsBy: "tag", - categoryLinkSource: "tag", + categoryLinkSource: "auto", }, }, session_v2: { @@ -297,7 +297,7 @@ module.exports = { outputDir: "docs/apis/resources/session_service_v2", sidebarOptions: { groupPathsBy: "tag", - categoryLinkSource: "tag", + categoryLinkSource: "auto", }, }, oidc_v2: { @@ -305,7 +305,7 @@ module.exports = { outputDir: "docs/apis/resources/oidc_service_v2", sidebarOptions: { groupPathsBy: "tag", - categoryLinkSource: "tag", + categoryLinkSource: "auto", }, }, settings_v2: { @@ -313,7 +313,7 @@ module.exports = { outputDir: "docs/apis/resources/settings_service_v2", sidebarOptions: { groupPathsBy: "tag", - categoryLinkSource: "tag", + categoryLinkSource: "auto", }, }, user_schema_v3: { diff --git a/docs/package.json b/docs/package.json index 3d7b2fe036..f9636418dd 100644 --- a/docs/package.json +++ b/docs/package.json @@ -17,7 +17,7 @@ "generate:grpc": "buf generate ../proto", "generate:apidocs": "docusaurus gen-api-docs all", "generate:configdocs": "cp -r ../cmd/defaults.yaml ./docs/self-hosting/manage/configure/ && cp -r ../cmd/setup/steps.yaml ./docs/self-hosting/manage/configure/", - "generate:re-gen": "yarn clean-all && yarn gen-all", + "generate:re-gen": "yarn generate:clean-all && yarn generate", "generate:clean-all": "docusaurus clean-api-docs all" }, "dependencies": { @@ -44,6 +44,7 @@ "react": "^18.2.0", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18.2.0", + "react-google-charts": "^5.2.1", "react-player": "^2.15.1", "sitemap": "7.1.1", "swc-loader": "^0.2.3", diff --git a/docs/sidebars.js b/docs/sidebars.js index 8c83f9bf1d..05a2c42342 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -258,6 +258,7 @@ module.exports = { ], }, "guides/integrate/token-exchange", + "guides/integrate/back-channel-logout", { type: "category", label: "Service Users", @@ -841,6 +842,43 @@ module.exports = { label: "Rate Limits (Cloud)", // The link label href: "/legal/policies/rate-limit-policy", // The internal path }, + { + type: "category", + label: "Benchmarks", + collapsed: false, + link: { + type: "doc", + id: "apis/benchmarks/index", + }, + items: [ + { + type: "category", + label: "v2.65.0", + link: { + title: "v2.65.0", + slug: "/apis/benchmarks/v2.65.0", + description: + "Benchmark results of Zitadel v2.65.0\n" + }, + items: [ + "apis/benchmarks/v2.65.0/machine_jwt_profile_grant/index", + ], + }, + { + type: "category", + label: "v2.66.0", + link: { + title: "v2.66.0", + slug: "/apis/benchmarks/v2.66.0", + description: + "Benchmark results of Zitadel v2.66.0\n" + }, + items: [ + "apis/benchmarks/v2.66.0/machine_jwt_profile_grant/index", + ], + }, + ], + }, ], selfHosting: [ { @@ -889,6 +927,7 @@ module.exports = { "self-hosting/manage/http2", "self-hosting/manage/tls_modes", "self-hosting/manage/database/database", + "self-hosting/manage/cache", "self-hosting/manage/updating_scaling", "self-hosting/manage/usage_control", { diff --git a/docs/src/components/benchmark_chart.jsx b/docs/src/components/benchmark_chart.jsx new file mode 100644 index 0000000000..f9ac920cad --- /dev/null +++ b/docs/src/components/benchmark_chart.jsx @@ -0,0 +1,45 @@ +import React from "react"; +import Chart from "react-google-charts"; + +export function BenchmarkChart(testResults=[], height='500px') { + + const options = { + legend: { position: 'bottom' }, + focusTarget: 'category', + hAxis: { + title: 'timestamp', + }, + vAxis: { + title: 'latency (ms)', + }, + }; + + const data = [ + [ + {type:"datetime", label: "timestamp"}, + {type:"number", label: "p50"}, + {type:"number", label: "p95"}, + {type:"number", label: "p99"}, + ], + ] + + JSON.parse(testResults.testResults).forEach((result) => { + data.push([ + new Date(result.timestamp), + result.p50, + result.p95, + result.p99, + ]) + }); + + return ( + + ); +} \ No newline at end of file diff --git a/docs/static/img/benchmark/Flowchart.svg b/docs/static/img/benchmark/Flowchart.svg new file mode 100644 index 0000000000..e2a078ab96 --- /dev/null +++ b/docs/static/img/benchmark/Flowchart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/static/img/guides/back-channel-logout/back-channel-logout-flow.png b/docs/static/img/guides/back-channel-logout/back-channel-logout-flow.png new file mode 100644 index 0000000000..1ee48ed7f7 Binary files /dev/null and b/docs/static/img/guides/back-channel-logout/back-channel-logout-flow.png differ diff --git a/docs/yarn.lock b/docs/yarn.lock index 538212d391..94698e4821 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -3909,9 +3909,9 @@ cross-fetch@3.1.5: node-fetch "2.6.7" cross-spawn@^7.0.0, cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" @@ -9479,6 +9479,11 @@ react-fast-compare@^3.0.1, react-fast-compare@^3.2.0, react-fast-compare@^3.2.2: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== +react-google-charts@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/react-google-charts/-/react-google-charts-5.2.1.tgz#d9cbe8ed45d7c0fafefea5c7c3361bee76648454" + integrity sha512-mCbPiObP8yWM5A9ogej7Qp3/HX4EzOwuEzUYvcfHtL98Xt4V/brD14KgfDzSNNtyD48MNXCpq5oVaYKt0ykQUQ== + react-helmet-async@*: version "2.0.5" resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-2.0.5.tgz#cfc70cd7bb32df7883a8ed55502a1513747223ec" diff --git a/go.mod b/go.mod index cf4e755605..cf5cbf919d 100644 --- a/go.mod +++ b/go.mod @@ -56,6 +56,7 @@ require ( github.com/redis/go-redis/v9 v9.7.0 github.com/rs/cors v1.11.1 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 + github.com/sony/gobreaker/v2 v2.0.0 github.com/sony/sonyflake v1.2.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 @@ -78,13 +79,13 @@ require ( go.opentelemetry.io/otel/sdk v1.29.0 go.opentelemetry.io/otel/sdk/metric v1.29.0 go.opentelemetry.io/otel/trace v1.29.0 - go.uber.org/mock v0.4.0 - golang.org/x/crypto v0.27.0 + go.uber.org/mock v0.5.0 + golang.org/x/crypto v0.29.0 golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 - golang.org/x/net v0.28.0 + golang.org/x/net v0.31.0 golang.org/x/oauth2 v0.23.0 - golang.org/x/sync v0.8.0 - golang.org/x/text v0.19.0 + golang.org/x/sync v0.9.0 + golang.org/x/text v0.20.0 google.golang.org/api v0.187.0 google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd google.golang.org/grpc v1.65.0 @@ -204,7 +205,7 @@ require ( go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect - golang.org/x/sys v0.25.0 + golang.org/x/sys v0.27.0 gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 015fea1b80..bacef90c1a 100644 --- a/go.sum +++ b/go.sum @@ -670,6 +670,8 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sony/gobreaker/v2 v2.0.0 h1:23AaR4JQ65y4rz8JWMzgXw2gKOykZ/qfqYunll4OwJ4= +github.com/sony/gobreaker/v2 v2.0.0/go.mod h1:8JnRUz80DJ1/ne8M8v7nmTs2713i58nIt4s7XcGe/DI= github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ= github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= @@ -783,8 +785,8 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= @@ -807,8 +809,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= @@ -871,8 +873,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -888,8 +890,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -932,8 +934,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -948,8 +950,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= diff --git a/internal/admin/repository/eventsourcing/handler/handler.go b/internal/admin/repository/eventsourcing/handler/handler.go index 06720144e1..ec268c25a1 100644 --- a/internal/admin/repository/eventsourcing/handler/handler.go +++ b/internal/admin/repository/eventsourcing/handler/handler.go @@ -18,9 +18,11 @@ type Config struct { BulkLimit uint64 FailureCountUntilSkip uint64 - HandleActiveInstances time.Duration TransactionDuration time.Duration Handlers map[string]*ConfigOverwrites + ActiveInstancer interface { + ActiveInstances() []string + } } type ConfigOverwrites struct { @@ -34,6 +36,9 @@ func Register(ctx context.Context, config Config, view *view.View, static static return } + // make sure the slice does not contain old values + projections = nil + projections = append(projections, newStyling(ctx, config.overwrite("Styling"), static, @@ -63,13 +68,13 @@ func ProjectInstance(ctx context.Context) error { func (config Config) overwrite(viewModel string) handler2.Config { c := handler2.Config{ - Client: config.Client, - Eventstore: config.Eventstore, - BulkLimit: uint16(config.BulkLimit), - RequeueEvery: 3 * time.Minute, - HandleActiveInstances: config.HandleActiveInstances, - MaxFailureCount: uint8(config.FailureCountUntilSkip), - TransactionDuration: config.TransactionDuration, + Client: config.Client, + Eventstore: config.Eventstore, + BulkLimit: uint16(config.BulkLimit), + RequeueEvery: 3 * time.Minute, + MaxFailureCount: uint8(config.FailureCountUntilSkip), + TransactionDuration: config.TransactionDuration, + ActiveInstancer: config.ActiveInstancer, } overwrite, ok := config.Handlers[viewModel] if !ok { diff --git a/internal/admin/repository/eventsourcing/repository.go b/internal/admin/repository/eventsourcing/repository.go index f9ba285a82..f6209391c6 100644 --- a/internal/admin/repository/eventsourcing/repository.go +++ b/internal/admin/repository/eventsourcing/repository.go @@ -6,6 +6,7 @@ import ( admin_handler "github.com/zitadel/zitadel/internal/admin/repository/eventsourcing/handler" admin_view "github.com/zitadel/zitadel/internal/admin/repository/eventsourcing/view" "github.com/zitadel/zitadel/internal/database" + "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/static" ) @@ -13,7 +14,7 @@ type Config struct { Spooler admin_handler.Config } -func Start(ctx context.Context, conf Config, static static.Storage, dbClient *database.DB) error { +func Start(ctx context.Context, conf Config, static static.Storage, dbClient *database.DB, queries *query.Queries) error { view, err := admin_view.StartView(dbClient) if err != nil { return err diff --git a/internal/api/grpc/admin/integration_test/iam_settings_test.go b/internal/api/grpc/admin/integration_test/iam_settings_test.go index 9eca09c06c..603d4a5d32 100644 --- a/internal/api/grpc/admin/integration_test/iam_settings_test.go +++ b/internal/api/grpc/admin/integration_test/iam_settings_test.go @@ -18,8 +18,6 @@ import ( ) func TestServer_GetSecurityPolicy(t *testing.T) { - t.Parallel() - instance := integration.NewInstance(CTX) adminCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -72,8 +70,6 @@ func TestServer_GetSecurityPolicy(t *testing.T) { } func TestServer_SetSecurityPolicy(t *testing.T) { - t.Parallel() - instance := integration.NewInstance(CTX) adminCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) diff --git a/internal/api/grpc/admin/integration_test/restrictions_allow_public_org_registrations_test.go b/internal/api/grpc/admin/integration_test/restrictions_allow_public_org_registrations_test.go index 3cbcf8abd0..9aed3f5924 100644 --- a/internal/api/grpc/admin/integration_test/restrictions_allow_public_org_registrations_test.go +++ b/internal/api/grpc/admin/integration_test/restrictions_allow_public_org_registrations_test.go @@ -21,8 +21,6 @@ import ( ) func TestServer_Restrictions_DisallowPublicOrgRegistration(t *testing.T) { - t.Parallel() - instance := integration.NewInstance(CTX) regOrgUrl, err := url.Parse("http://" + instance.Domain + ":8080/ui/login/register/org") require.NoError(t, err) diff --git a/internal/api/grpc/admin/integration_test/restrictions_allowed_languages_test.go b/internal/api/grpc/admin/integration_test/restrictions_allowed_languages_test.go index e00b7f221b..52a1607bba 100644 --- a/internal/api/grpc/admin/integration_test/restrictions_allowed_languages_test.go +++ b/internal/api/grpc/admin/integration_test/restrictions_allowed_languages_test.go @@ -24,8 +24,6 @@ import ( ) func TestServer_Restrictions_AllowedLanguages(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute) defer cancel() diff --git a/internal/api/grpc/org/v2/integration_test/org_test.go b/internal/api/grpc/org/v2/integration_test/org_test.go index 165eb1471f..8de5e40bb5 100644 --- a/internal/api/grpc/org/v2/integration_test/org_test.go +++ b/internal/api/grpc/org/v2/integration_test/org_test.go @@ -42,8 +42,6 @@ func TestMain(m *testing.M) { } func TestServer_AddOrganization(t *testing.T) { - t.Parallel() - idpResp := Instance.AddGenericOAuthProvider(CTX, Instance.DefaultOrg.Id) tests := []struct { diff --git a/internal/api/grpc/org/v2/integration_test/query_test.go b/internal/api/grpc/org/v2/integration_test/query_test.go index bd0352ed75..188aeddf9f 100644 --- a/internal/api/grpc/org/v2/integration_test/query_test.go +++ b/internal/api/grpc/org/v2/integration_test/query_test.go @@ -27,8 +27,6 @@ type orgAttr struct { } func TestServer_ListOrganizations(t *testing.T) { - t.Parallel() - type args struct { ctx context.Context req *org.ListOrganizationsRequest diff --git a/internal/api/grpc/resources/action/v3alpha/integration_test/execution_target_test.go b/internal/api/grpc/resources/action/v3alpha/integration_test/execution_target_test.go index c70ed227c1..7aff6afb3f 100644 --- a/internal/api/grpc/resources/action/v3alpha/integration_test/execution_target_test.go +++ b/internal/api/grpc/resources/action/v3alpha/integration_test/execution_target_test.go @@ -26,7 +26,6 @@ import ( ) func TestServer_ExecutionTarget(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) diff --git a/internal/api/grpc/resources/action/v3alpha/integration_test/execution_test.go b/internal/api/grpc/resources/action/v3alpha/integration_test/execution_test.go index 19d97c3857..b56efd6b99 100644 --- a/internal/api/grpc/resources/action/v3alpha/integration_test/execution_test.go +++ b/internal/api/grpc/resources/action/v3alpha/integration_test/execution_test.go @@ -25,7 +25,6 @@ func executionTargetsSingleInclude(include *action.Condition) []*action.Executio } func TestServer_SetExecution_Request(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -207,7 +206,6 @@ func TestServer_SetExecution_Request(t *testing.T) { } func TestServer_SetExecution_Request_Include(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -346,7 +344,6 @@ func TestServer_SetExecution_Request_Include(t *testing.T) { } func TestServer_SetExecution_Response(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -528,7 +525,6 @@ func TestServer_SetExecution_Response(t *testing.T) { } func TestServer_SetExecution_Event(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -716,7 +712,6 @@ func TestServer_SetExecution_Event(t *testing.T) { } func TestServer_SetExecution_Function(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) diff --git a/internal/api/grpc/resources/action/v3alpha/integration_test/query_test.go b/internal/api/grpc/resources/action/v3alpha/integration_test/query_test.go index d6f584b35e..aa748ac4d8 100644 --- a/internal/api/grpc/resources/action/v3alpha/integration_test/query_test.go +++ b/internal/api/grpc/resources/action/v3alpha/integration_test/query_test.go @@ -22,7 +22,6 @@ import ( ) func TestServer_GetTarget(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -63,6 +62,7 @@ func TestServer_GetTarget(t *testing.T) { request.Id = resp.GetDetails().GetId() response.Target.Config.Name = name response.Target.Details = resp.GetDetails() + response.Target.SigningKey = resp.GetSigningKey() return nil }, req: &action.GetTargetRequest{}, @@ -93,6 +93,7 @@ func TestServer_GetTarget(t *testing.T) { request.Id = resp.GetDetails().GetId() response.Target.Config.Name = name response.Target.Details = resp.GetDetails() + response.Target.SigningKey = resp.GetSigningKey() return nil }, req: &action.GetTargetRequest{}, @@ -123,6 +124,7 @@ func TestServer_GetTarget(t *testing.T) { request.Id = resp.GetDetails().GetId() response.Target.Config.Name = name response.Target.Details = resp.GetDetails() + response.Target.SigningKey = resp.GetSigningKey() return nil }, req: &action.GetTargetRequest{}, @@ -155,6 +157,7 @@ func TestServer_GetTarget(t *testing.T) { request.Id = resp.GetDetails().GetId() response.Target.Config.Name = name response.Target.Details = resp.GetDetails() + response.Target.SigningKey = resp.GetSigningKey() return nil }, req: &action.GetTargetRequest{}, @@ -187,6 +190,7 @@ func TestServer_GetTarget(t *testing.T) { request.Id = resp.GetDetails().GetId() response.Target.Config.Name = name response.Target.Details = resp.GetDetails() + response.Target.SigningKey = resp.GetSigningKey() return nil }, req: &action.GetTargetRequest{}, @@ -231,13 +235,13 @@ func TestServer_GetTarget(t *testing.T) { gotTarget := got.GetTarget() integration.AssertResourceDetails(ttt, wantTarget.GetDetails(), gotTarget.GetDetails()) assert.EqualExportedValues(ttt, wantTarget.GetConfig(), gotTarget.GetConfig()) + assert.Equal(ttt, wantTarget.GetSigningKey(), gotTarget.GetSigningKey()) }, retryDuration, tick, "timeout waiting for expected target result") }) } } func TestServer_ListTargets(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -494,6 +498,7 @@ func TestServer_ListTargets(t *testing.T) { for i := range tt.want.Result { integration.AssertResourceDetails(ttt, tt.want.Result[i].GetDetails(), got.Result[i].GetDetails()) assert.EqualExportedValues(ttt, tt.want.Result[i].GetConfig(), got.Result[i].GetConfig()) + assert.NotEmpty(ttt, got.Result[i].GetSigningKey()) } } integration.AssertResourceListDetails(ttt, tt.want, got) @@ -503,7 +508,6 @@ func TestServer_ListTargets(t *testing.T) { } func TestServer_SearchExecutions(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) diff --git a/internal/api/grpc/resources/action/v3alpha/integration_test/target_test.go b/internal/api/grpc/resources/action/v3alpha/integration_test/target_test.go index 60c4ac35a7..b5d1903ca6 100644 --- a/internal/api/grpc/resources/action/v3alpha/integration_test/target_test.go +++ b/internal/api/grpc/resources/action/v3alpha/integration_test/target_test.go @@ -9,6 +9,7 @@ import ( "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/durationpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -21,7 +22,6 @@ import ( ) func TestServer_CreateTarget(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -201,11 +201,12 @@ func TestServer_CreateTarget(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got, err := instance.Client.ActionV3Alpha.CreateTarget(tt.ctx, &action.CreateTargetRequest{Target: tt.req}) if tt.wantErr { - require.Error(t, err) - return + assert.Error(t, err) + } else { + assert.NoError(t, err) + integration.AssertResourceDetails(t, tt.want, got.Details) + assert.NotEmpty(t, got.GetSigningKey()) } - require.NoError(t, err) - integration.AssertResourceDetails(t, tt.want, got.Details) }) } } @@ -218,11 +219,15 @@ func TestServer_PatchTarget(t *testing.T) { ctx context.Context req *action.PatchTargetRequest } + type want struct { + details *resource_object.Details + signingKey bool + } tests := []struct { name string prepare func(request *action.PatchTargetRequest) error args args - want *resource_object.Details + want want wantErr bool }{ { @@ -273,14 +278,42 @@ func TestServer_PatchTarget(t *testing.T) { }, }, }, - want: &resource_object.Details{ - Changed: timestamppb.Now(), - Owner: &object.Owner{ - Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: instance.ID(), + want: want{ + details: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_INSTANCE, + Id: instance.ID(), + }, }, }, }, + { + name: "regenerate signingkey, ok", + prepare: func(request *action.PatchTargetRequest) error { + targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId() + request.Id = targetID + return nil + }, + args: args{ + ctx: isolatedIAMOwnerCTX, + req: &action.PatchTargetRequest{ + Target: &action.PatchTarget{ + ExpirationSigningKey: durationpb.New(0 * time.Second), + }, + }, + }, + want: want{ + details: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_INSTANCE, + Id: instance.ID(), + }, + }, + signingKey: true, + }, + }, { name: "change type, ok", prepare: func(request *action.PatchTargetRequest) error { @@ -300,11 +333,13 @@ func TestServer_PatchTarget(t *testing.T) { }, }, }, - want: &resource_object.Details{ - Changed: timestamppb.Now(), - Owner: &object.Owner{ - Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: instance.ID(), + want: want{ + details: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_INSTANCE, + Id: instance.ID(), + }, }, }, }, @@ -323,11 +358,13 @@ func TestServer_PatchTarget(t *testing.T) { }, }, }, - want: &resource_object.Details{ - Changed: timestamppb.Now(), - Owner: &object.Owner{ - Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: instance.ID(), + want: want{ + details: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_INSTANCE, + Id: instance.ID(), + }, }, }, }, @@ -346,11 +383,13 @@ func TestServer_PatchTarget(t *testing.T) { }, }, }, - want: &resource_object.Details{ - Changed: timestamppb.Now(), - Owner: &object.Owner{ - Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: instance.ID(), + want: want{ + details: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_INSTANCE, + Id: instance.ID(), + }, }, }, }, @@ -371,11 +410,13 @@ func TestServer_PatchTarget(t *testing.T) { }, }, }, - want: &resource_object.Details{ - Changed: timestamppb.Now(), - Owner: &object.Owner{ - Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: instance.ID(), + want: want{ + details: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_INSTANCE, + Id: instance.ID(), + }, }, }, }, @@ -388,11 +429,14 @@ func TestServer_PatchTarget(t *testing.T) { instance.Client.ActionV3Alpha.PatchTarget(tt.args.ctx, tt.args.req) got, err := instance.Client.ActionV3Alpha.PatchTarget(tt.args.ctx, tt.args.req) if tt.wantErr { - require.Error(t, err) - return + assert.Error(t, err) + } else { + assert.NoError(t, err) + integration.AssertResourceDetails(t, tt.want.details, got.Details) + if tt.want.signingKey { + assert.NotEmpty(t, got.SigningKey) + } } - require.NoError(t, err) - integration.AssertResourceDetails(t, tt.want, got.Details) }) } } @@ -444,11 +488,12 @@ func TestServer_DeleteTarget(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got, err := instance.Client.ActionV3Alpha.DeleteTarget(tt.ctx, tt.req) if tt.wantErr { - require.Error(t, err) + assert.Error(t, err) return + } else { + assert.NoError(t, err) + integration.AssertResourceDetails(t, tt.want, got.Details) } - require.NoError(t, err) - integration.AssertResourceDetails(t, tt.want, got.Details) }) } } diff --git a/internal/api/grpc/resources/action/v3alpha/query.go b/internal/api/grpc/resources/action/v3alpha/query.go index ec7ed8b9c8..7cdedd8134 100644 --- a/internal/api/grpc/resources/action/v3alpha/query.go +++ b/internal/api/grpc/resources/action/v3alpha/query.go @@ -97,6 +97,7 @@ func targetToPb(t *query.Target) *action.GetTarget { Timeout: durationpb.New(t.Timeout), Endpoint: t.Endpoint, }, + SigningKey: t.SigningKey, } switch t.TargetType { case domain.TargetTypeWebhook: diff --git a/internal/api/grpc/resources/action/v3alpha/target.go b/internal/api/grpc/resources/action/v3alpha/target.go index 031cd99477..621b6677b7 100644 --- a/internal/api/grpc/resources/action/v3alpha/target.go +++ b/internal/api/grpc/resources/action/v3alpha/target.go @@ -25,7 +25,8 @@ func (s *Server) CreateTarget(ctx context.Context, req *action.CreateTargetReque return nil, err } return &action.CreateTargetResponse{ - Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID), + Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID), + SigningKey: add.SigningKey, }, nil } @@ -34,12 +35,14 @@ func (s *Server) PatchTarget(ctx context.Context, req *action.PatchTargetRequest return nil, err } instanceID := authz.GetInstance(ctx).InstanceID() - details, err := s.command.ChangeTarget(ctx, patchTargetToCommand(req), instanceID) + patch := patchTargetToCommand(req) + details, err := s.command.ChangeTarget(ctx, patch, instanceID) if err != nil { return nil, err } return &action.PatchTargetResponse{ - Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID), + Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID), + SigningKey: patch.SigningKey, }, nil } @@ -83,6 +86,12 @@ func createTargetToCommand(req *action.CreateTargetRequest) *command.AddTarget { } func patchTargetToCommand(req *action.PatchTargetRequest) *command.ChangeTarget { + expirationSigningKey := false + // TODO handle expiration, currently only immediate expiration is supported + if req.GetTarget().GetExpirationSigningKey() != nil { + expirationSigningKey = true + } + reqTarget := req.GetTarget() if reqTarget == nil { return nil @@ -91,8 +100,9 @@ func patchTargetToCommand(req *action.PatchTargetRequest) *command.ChangeTarget ObjectRoot: models.ObjectRoot{ AggregateID: req.GetId(), }, - Name: reqTarget.Name, - Endpoint: reqTarget.Endpoint, + Name: reqTarget.Name, + Endpoint: reqTarget.Endpoint, + ExpirationSigningKey: expirationSigningKey, } if reqTarget.TargetType != nil { switch t := reqTarget.GetTargetType().(type) { diff --git a/internal/api/grpc/resources/user/v3alpha/integration_test/email_test.go b/internal/api/grpc/resources/user/v3alpha/integration_test/email_test.go index 4b5a342905..f64bcebe38 100644 --- a/internal/api/grpc/resources/user/v3alpha/integration_test/email_test.go +++ b/internal/api/grpc/resources/user/v3alpha/integration_test/email_test.go @@ -19,7 +19,6 @@ import ( ) func TestServer_SetContactEmail(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -365,7 +364,6 @@ func TestServer_SetContactEmail(t *testing.T) { } func TestServer_VerifyContactEmail(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -555,7 +553,6 @@ func TestServer_VerifyContactEmail(t *testing.T) { } func TestServer_ResendContactEmailCode(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) diff --git a/internal/api/grpc/resources/user/v3alpha/integration_test/phone_test.go b/internal/api/grpc/resources/user/v3alpha/integration_test/phone_test.go index 9fcacd7457..fbd5805f16 100644 --- a/internal/api/grpc/resources/user/v3alpha/integration_test/phone_test.go +++ b/internal/api/grpc/resources/user/v3alpha/integration_test/phone_test.go @@ -18,7 +18,6 @@ import ( ) func TestServer_SetContactPhone(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -292,7 +291,6 @@ func TestServer_SetContactPhone(t *testing.T) { } func TestServer_VerifyContactPhone(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -484,7 +482,6 @@ func TestServer_VerifyContactPhone(t *testing.T) { } func TestServer_ResendContactPhoneCode(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) diff --git a/internal/api/grpc/resources/user/v3alpha/integration_test/user_test.go b/internal/api/grpc/resources/user/v3alpha/integration_test/user_test.go index 34d12446c5..1bc35a5390 100644 --- a/internal/api/grpc/resources/user/v3alpha/integration_test/user_test.go +++ b/internal/api/grpc/resources/user/v3alpha/integration_test/user_test.go @@ -21,7 +21,6 @@ import ( ) func TestServer_CreateUser(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -230,7 +229,6 @@ func TestServer_CreateUser(t *testing.T) { } func TestServer_PatchUser(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -650,7 +648,6 @@ func TestServer_PatchUser(t *testing.T) { } func TestServer_DeleteUser(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -868,7 +865,6 @@ func unmarshalJSON(data string) *structpb.Struct { } func TestServer_LockUser(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -1070,7 +1066,6 @@ func TestServer_LockUser(t *testing.T) { } func TestServer_UnlockUser(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -1253,7 +1248,6 @@ func TestServer_UnlockUser(t *testing.T) { } func TestServer_DeactivateUser(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -1455,7 +1449,6 @@ func TestServer_DeactivateUser(t *testing.T) { } func TestServer_ActivateUser(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) diff --git a/internal/api/grpc/resources/userschema/v3alpha/integration_test/query_test.go b/internal/api/grpc/resources/userschema/v3alpha/integration_test/query_test.go index ef7fe02807..31ee68ed8d 100644 --- a/internal/api/grpc/resources/userschema/v3alpha/integration_test/query_test.go +++ b/internal/api/grpc/resources/userschema/v3alpha/integration_test/query_test.go @@ -20,7 +20,6 @@ import ( ) func TestServer_ListUserSchemas(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -214,7 +213,6 @@ func TestServer_ListUserSchemas(t *testing.T) { } func TestServer_GetUserSchema(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) diff --git a/internal/api/grpc/resources/userschema/v3alpha/integration_test/userschema_test.go b/internal/api/grpc/resources/userschema/v3alpha/integration_test/userschema_test.go index a264b163eb..33b4565861 100644 --- a/internal/api/grpc/resources/userschema/v3alpha/integration_test/userschema_test.go +++ b/internal/api/grpc/resources/userschema/v3alpha/integration_test/userschema_test.go @@ -19,7 +19,6 @@ import ( ) func TestServer_CreateUserSchema(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -304,7 +303,6 @@ func TestServer_CreateUserSchema(t *testing.T) { } func TestServer_UpdateUserSchema(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -596,7 +594,6 @@ func TestServer_UpdateUserSchema(t *testing.T) { } func TestServer_DeactivateUserSchema(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -678,7 +675,6 @@ func TestServer_DeactivateUserSchema(t *testing.T) { } func TestServer_ReactivateUserSchema(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) @@ -760,7 +756,6 @@ func TestServer_ReactivateUserSchema(t *testing.T) { } func TestServer_DeleteUserSchema(t *testing.T) { - t.Parallel() instance := integration.NewInstance(CTX) ensureFeatureEnabled(t, instance) isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) diff --git a/internal/api/grpc/resources/webkey/v3/integration_test/webkey_integration_test.go b/internal/api/grpc/resources/webkey/v3/integration_test/webkey_integration_test.go index 19d02dcea3..eafa733fd1 100644 --- a/internal/api/grpc/resources/webkey/v3/integration_test/webkey_integration_test.go +++ b/internal/api/grpc/resources/webkey/v3/integration_test/webkey_integration_test.go @@ -216,6 +216,7 @@ func assertFeatureDisabledError(t *testing.T, err error) { } func checkWebKeyListState(ctx context.Context, t *testing.T, instance *integration.Instance, nKeys int, expectActiveKeyID string, config any, creationDate *timestamppb.Timestamp) { + t.Helper() retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, time.Minute) assert.EventuallyWithT(t, func(collect *assert.CollectT) { diff --git a/internal/api/grpc/server/middleware/execution_interceptor_test.go b/internal/api/grpc/server/middleware/execution_interceptor_test.go index f59fd00441..6a5b74c5e4 100644 --- a/internal/api/grpc/server/middleware/execution_interceptor_test.go +++ b/internal/api/grpc/server/middleware/execution_interceptor_test.go @@ -26,6 +26,7 @@ type mockExecutionTarget struct { Endpoint string Timeout time.Duration InterruptOnError bool + SigningKey string } func (e *mockExecutionTarget) SetEndpoint(endpoint string) { @@ -49,6 +50,9 @@ func (e *mockExecutionTarget) GetTargetID() string { func (e *mockExecutionTarget) GetExecutionID() string { return e.ExecutionID } +func (e *mockExecutionTarget) GetSigningKey() string { + return e.SigningKey +} type mockContentRequest struct { Content string @@ -157,6 +161,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { TargetID: "target", TargetType: domain.TargetTypeCall, Timeout: time.Minute, + SigningKey: "signingkey", }, }, targets: []target{ @@ -186,6 +191,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { TargetType: domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, + SigningKey: "signingkey", }, }, @@ -216,6 +222,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { TargetType: domain.TargetTypeCall, Timeout: time.Second, InterruptOnError: true, + SigningKey: "signingkey", }, }, targets: []target{ @@ -245,6 +252,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { TargetType: domain.TargetTypeCall, Timeout: time.Second, InterruptOnError: true, + SigningKey: "signingkey", }, }, targets: []target{ @@ -269,6 +277,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { TargetType: domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, + SigningKey: "signingkey", }, }, targets: []target{ @@ -297,6 +306,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { TargetID: "target", TargetType: domain.TargetTypeAsync, Timeout: time.Second, + SigningKey: "signingkey", }, }, targets: []target{ @@ -325,6 +335,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { TargetID: "target", TargetType: domain.TargetTypeAsync, Timeout: time.Minute, + SigningKey: "signingkey", }, }, targets: []target{ @@ -354,6 +365,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { TargetType: domain.TargetTypeWebhook, Timeout: time.Minute, InterruptOnError: true, + SigningKey: "signingkey", }, }, targets: []target{ @@ -382,6 +394,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { TargetType: domain.TargetTypeWebhook, Timeout: time.Second, InterruptOnError: true, + SigningKey: "signingkey", }, }, targets: []target{ @@ -411,6 +424,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { TargetType: domain.TargetTypeWebhook, Timeout: time.Minute, InterruptOnError: true, + SigningKey: "signingkey", }, }, targets: []target{ @@ -440,6 +454,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { TargetType: domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, + SigningKey: "signingkey", }, &mockExecutionTarget{ InstanceID: "instance", @@ -448,6 +463,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { TargetType: domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, + SigningKey: "signingkey", }, &mockExecutionTarget{ InstanceID: "instance", @@ -456,6 +472,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { TargetType: domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, + SigningKey: "signingkey", }, }, @@ -498,6 +515,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { TargetType: domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, + SigningKey: "signingkey", }, &mockExecutionTarget{ InstanceID: "instance", @@ -506,6 +524,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { TargetType: domain.TargetTypeCall, Timeout: time.Second, InterruptOnError: true, + SigningKey: "signingkey", }, &mockExecutionTarget{ InstanceID: "instance", @@ -514,6 +533,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { TargetType: domain.TargetTypeCall, Timeout: time.Second, InterruptOnError: true, + SigningKey: "signingkey", }, }, targets: []target{ @@ -692,6 +712,7 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) { TargetType: domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, + SigningKey: "signingkey", }, }, targets: []target{ @@ -721,6 +742,7 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) { TargetType: domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, + SigningKey: "signingkey", }, }, targets: []target{ diff --git a/internal/api/grpc/system/integration_test/instance_test.go b/internal/api/grpc/system/integration_test/instance_test.go index 18d2f68bb9..2c8cc8cf89 100644 --- a/internal/api/grpc/system/integration_test/instance_test.go +++ b/internal/api/grpc/system/integration_test/instance_test.go @@ -15,8 +15,6 @@ import ( ) func TestServer_ListInstances(t *testing.T) { - t.Parallel() - isoInstance := integration.NewInstance(CTX) tests := []struct { diff --git a/internal/api/grpc/system/integration_test/limits_auditlogretention_test.go b/internal/api/grpc/system/integration_test/limits_auditlogretention_test.go index 077054eb33..24c224b0fe 100644 --- a/internal/api/grpc/system/integration_test/limits_auditlogretention_test.go +++ b/internal/api/grpc/system/integration_test/limits_auditlogretention_test.go @@ -22,8 +22,6 @@ import ( ) func TestServer_Limits_AuditLogRetention(t *testing.T) { - t.Parallel() - isoInstance := integration.NewInstance(CTX) iamOwnerCtx := isoInstance.WithAuthorization(CTX, integration.UserTypeIAMOwner) userID, projectID, appID, projectGrantID := seedObjects(iamOwnerCtx, t, isoInstance.Client) diff --git a/internal/api/grpc/system/integration_test/limits_block_test.go b/internal/api/grpc/system/integration_test/limits_block_test.go index 46b213f603..d3b9fffbd3 100644 --- a/internal/api/grpc/system/integration_test/limits_block_test.go +++ b/internal/api/grpc/system/integration_test/limits_block_test.go @@ -26,8 +26,6 @@ import ( ) func TestServer_Limits_Block(t *testing.T) { - t.Parallel() - isoInstance := integration.NewInstance(CTX) iamOwnerCtx := isoInstance.WithAuthorization(CTX, integration.UserTypeIAMOwner) tests := []*test{ diff --git a/internal/api/grpc/user/v2/integration_test/email_test.go b/internal/api/grpc/user/v2/integration_test/email_test.go index 5092dbf40d..37d575016b 100644 --- a/internal/api/grpc/user/v2/integration_test/email_test.go +++ b/internal/api/grpc/user/v2/integration_test/email_test.go @@ -17,8 +17,6 @@ import ( ) func TestServer_SetEmail(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() tests := []struct { @@ -148,8 +146,6 @@ func TestServer_SetEmail(t *testing.T) { } func TestServer_ResendEmailCode(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() verifiedUserID := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.Id, gofakeit.Email()).GetUserId() @@ -254,8 +250,6 @@ func TestServer_ResendEmailCode(t *testing.T) { } func TestServer_VerifyEmail(t *testing.T) { - t.Parallel() - userResp := Instance.CreateHumanUser(CTX) tests := []struct { name string diff --git a/internal/api/grpc/user/v2/integration_test/idp_link_test.go b/internal/api/grpc/user/v2/integration_test/idp_link_test.go index ab398c7233..116a095216 100644 --- a/internal/api/grpc/user/v2/integration_test/idp_link_test.go +++ b/internal/api/grpc/user/v2/integration_test/idp_link_test.go @@ -20,8 +20,6 @@ import ( ) func TestServer_AddIDPLink(t *testing.T) { - t.Parallel() - idpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id) type args struct { ctx context.Context @@ -101,8 +99,6 @@ func TestServer_AddIDPLink(t *testing.T) { } func TestServer_ListIDPLinks(t *testing.T) { - t.Parallel() - orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListIDPLinks-%s", gofakeit.AppName()), gofakeit.Email()) instanceIdpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id) @@ -257,8 +253,6 @@ func TestServer_ListIDPLinks(t *testing.T) { } func TestServer_RemoveIDPLink(t *testing.T) { - t.Parallel() - orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListIDPLinks-%s", gofakeit.AppName()), gofakeit.Email()) instanceIdpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id) diff --git a/internal/api/grpc/user/v2/integration_test/otp_test.go b/internal/api/grpc/user/v2/integration_test/otp_test.go index 3070a1d584..01e6c07a40 100644 --- a/internal/api/grpc/user/v2/integration_test/otp_test.go +++ b/internal/api/grpc/user/v2/integration_test/otp_test.go @@ -15,8 +15,6 @@ import ( ) func TestServer_AddOTPSMS(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() Instance.RegisterUserPasskey(CTX, userID) _, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID) @@ -60,7 +58,7 @@ func TestServer_AddOTPSMS(t *testing.T) { wantErr: true, }, { - name: "user mismatch", + name: "no permission", args: args{ ctx: integration.WithAuthorizationToken(context.Background(), sessionTokenOtherUser), req: &user.AddOTPSMSRequest{ @@ -123,22 +121,30 @@ func TestServer_AddOTPSMS(t *testing.T) { } func TestServer_RemoveOTPSMS(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() Instance.RegisterUserPasskey(CTX, userID) _, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID) userVerified := Instance.CreateHumanUser(CTX) Instance.RegisterUserPasskey(CTX, userVerified.GetUserId()) - _, sessionTokenVerified, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userVerified.GetUserId()) - userVerifiedCtx := integration.WithAuthorizationToken(context.Background(), sessionTokenVerified) - _, err := Instance.Client.UserV2.VerifyPhone(userVerifiedCtx, &user.VerifyPhoneRequest{ + _, err := Instance.Client.UserV2.VerifyPhone(CTX, &user.VerifyPhoneRequest{ UserId: userVerified.GetUserId(), VerificationCode: userVerified.GetPhoneCode(), }) require.NoError(t, err) - _, err = Instance.Client.UserV2.AddOTPSMS(userVerifiedCtx, &user.AddOTPSMSRequest{UserId: userVerified.GetUserId()}) + _, err = Instance.Client.UserV2.AddOTPSMS(CTX, &user.AddOTPSMSRequest{UserId: userVerified.GetUserId()}) + require.NoError(t, err) + + userSelf := Instance.CreateHumanUser(CTX) + Instance.RegisterUserPasskey(CTX, userSelf.GetUserId()) + _, sessionTokenSelf, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userSelf.GetUserId()) + userSelfCtx := integration.WithAuthorizationToken(context.Background(), sessionTokenSelf) + _, err = Instance.Client.UserV2.VerifyPhone(CTX, &user.VerifyPhoneRequest{ + UserId: userSelf.GetUserId(), + VerificationCode: userSelf.GetPhoneCode(), + }) + require.NoError(t, err) + _, err = Instance.Client.UserV2.AddOTPSMS(CTX, &user.AddOTPSMSRequest{UserId: userSelf.GetUserId()}) require.NoError(t, err) type args struct { @@ -161,10 +167,24 @@ func TestServer_RemoveOTPSMS(t *testing.T) { }, wantErr: true, }, + { + name: "success, self", + args: args{ + ctx: userSelfCtx, + req: &user.RemoveOTPSMSRequest{ + UserId: userSelf.GetUserId(), + }, + }, + want: &user.RemoveOTPSMSResponse{ + Details: &object.Details{ + ResourceOwner: Instance.DefaultOrg.Details.ResourceOwner, + }, + }, + }, { name: "success", args: args{ - ctx: userVerifiedCtx, + ctx: CTX, req: &user.RemoveOTPSMSRequest{ UserId: userVerified.GetUserId(), }, @@ -191,8 +211,6 @@ func TestServer_RemoveOTPSMS(t *testing.T) { } func TestServer_AddOTPEmail(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() Instance.RegisterUserPasskey(CTX, userID) _, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID) @@ -236,7 +254,7 @@ func TestServer_AddOTPEmail(t *testing.T) { wantErr: true, }, { - name: "user mismatch", + name: "no permission", args: args{ ctx: integration.WithAuthorizationToken(context.Background(), sessionTokenOtherUser), req: &user.AddOTPEmailRequest{ @@ -301,22 +319,30 @@ func TestServer_AddOTPEmail(t *testing.T) { } func TestServer_RemoveOTPEmail(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() Instance.RegisterUserPasskey(CTX, userID) _, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID) userVerified := Instance.CreateHumanUser(CTX) Instance.RegisterUserPasskey(CTX, userVerified.GetUserId()) - _, sessionTokenVerified, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userVerified.GetUserId()) - userVerifiedCtx := integration.WithAuthorizationToken(context.Background(), sessionTokenVerified) - _, err := Instance.Client.UserV2.VerifyEmail(userVerifiedCtx, &user.VerifyEmailRequest{ + _, err := Instance.Client.UserV2.VerifyEmail(CTX, &user.VerifyEmailRequest{ UserId: userVerified.GetUserId(), VerificationCode: userVerified.GetEmailCode(), }) require.NoError(t, err) - _, err = Instance.Client.UserV2.AddOTPEmail(userVerifiedCtx, &user.AddOTPEmailRequest{UserId: userVerified.GetUserId()}) + _, err = Instance.Client.UserV2.AddOTPEmail(CTX, &user.AddOTPEmailRequest{UserId: userVerified.GetUserId()}) + require.NoError(t, err) + + userSelf := Instance.CreateHumanUser(CTX) + Instance.RegisterUserPasskey(CTX, userSelf.GetUserId()) + _, sessionTokenSelf, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userSelf.GetUserId()) + userSelfCtx := integration.WithAuthorizationToken(context.Background(), sessionTokenSelf) + _, err = Instance.Client.UserV2.VerifyEmail(CTX, &user.VerifyEmailRequest{ + UserId: userSelf.GetUserId(), + VerificationCode: userSelf.GetEmailCode(), + }) + require.NoError(t, err) + _, err = Instance.Client.UserV2.AddOTPEmail(CTX, &user.AddOTPEmailRequest{UserId: userSelf.GetUserId()}) require.NoError(t, err) type args struct { @@ -339,10 +365,25 @@ func TestServer_RemoveOTPEmail(t *testing.T) { }, wantErr: true, }, + { + name: "success, self", + args: args{ + ctx: userSelfCtx, + req: &user.RemoveOTPEmailRequest{ + UserId: userSelf.GetUserId(), + }, + }, + want: &user.RemoveOTPEmailResponse{ + Details: &object.Details{ + ChangeDate: timestamppb.Now(), + ResourceOwner: Instance.DefaultOrg.Details.ResourceOwner, + }, + }, + }, { name: "success", args: args{ - ctx: userVerifiedCtx, + ctx: CTX, req: &user.RemoveOTPEmailRequest{ UserId: userVerified.GetUserId(), }, diff --git a/internal/api/grpc/user/v2/integration_test/passkey_test.go b/internal/api/grpc/user/v2/integration_test/passkey_test.go index 9d9cb8a047..055a47ec46 100644 --- a/internal/api/grpc/user/v2/integration_test/passkey_test.go +++ b/internal/api/grpc/user/v2/integration_test/passkey_test.go @@ -19,8 +19,6 @@ import ( ) func TestServer_RegisterPasskey(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() reg, err := Client.CreatePasskeyRegistrationLink(CTX, &user.CreatePasskeyRegistrationLinkRequest{ UserId: userID, @@ -95,15 +93,30 @@ func TestServer_RegisterPasskey(t *testing.T) { wantErr: true, }, { - name: "user mismatch", + name: "user no permission", args: args{ - ctx: CTX, + ctx: UserCTX, req: &user.RegisterPasskeyRequest{ UserId: userID, }, }, wantErr: true, }, + { + name: "user permission", + args: args{ + ctx: IamCTX, + req: &user.RegisterPasskeyRequest{ + UserId: userID, + }, + }, + want: &user.RegisterPasskeyResponse{ + Details: &object.Details{ + ChangeDate: timestamppb.Now(), + ResourceOwner: Instance.DefaultOrg.Id, + }, + }, + }, { name: "user setting its own passkey", args: args{ @@ -141,8 +154,6 @@ func TestServer_RegisterPasskey(t *testing.T) { } func TestServer_VerifyPasskeyRegistration(t *testing.T) { - t.Parallel() - userID, pkr := userWithPasskeyRegistered(t) attestationResponse, err := Instance.WebAuthN.CreateAttestationResponse(pkr.GetPublicKeyCredentialCreationOptions()) @@ -219,8 +230,6 @@ func TestServer_VerifyPasskeyRegistration(t *testing.T) { } func TestServer_CreatePasskeyRegistrationLink(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() type args struct { @@ -354,8 +363,6 @@ func passkeyVerify(t *testing.T, userID string, pkr *user.RegisterPasskeyRespons } func TestServer_RemovePasskey(t *testing.T) { - t.Parallel() - userIDWithout := Instance.CreateHumanUser(CTX).GetUserId() userIDRegistered, pkrRegistered := userWithPasskeyRegistered(t) userIDVerified, passkeyIDVerified := userWithPasskeyVerified(t) @@ -461,8 +468,6 @@ func TestServer_RemovePasskey(t *testing.T) { } func TestServer_ListPasskeys(t *testing.T) { - t.Parallel() - userIDWithout := Instance.CreateHumanUser(CTX).GetUserId() userIDRegistered, _ := userWithPasskeyRegistered(t) userIDVerified, passkeyIDVerified := userWithPasskeyVerified(t) diff --git a/internal/api/grpc/user/v2/integration_test/password_test.go b/internal/api/grpc/user/v2/integration_test/password_test.go index 7707537653..0cd0da7454 100644 --- a/internal/api/grpc/user/v2/integration_test/password_test.go +++ b/internal/api/grpc/user/v2/integration_test/password_test.go @@ -17,8 +17,6 @@ import ( ) func TestServer_RequestPasswordReset(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() tests := []struct { @@ -107,8 +105,6 @@ func TestServer_RequestPasswordReset(t *testing.T) { } func TestServer_SetPassword(t *testing.T) { - t.Parallel() - type args struct { ctx context.Context req *user.SetPasswordRequest diff --git a/internal/api/grpc/user/v2/integration_test/phone_test.go b/internal/api/grpc/user/v2/integration_test/phone_test.go index 47590b6d67..1c1f75854d 100644 --- a/internal/api/grpc/user/v2/integration_test/phone_test.go +++ b/internal/api/grpc/user/v2/integration_test/phone_test.go @@ -18,8 +18,6 @@ import ( ) func TestServer_SetPhone(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() tests := []struct { @@ -124,8 +122,6 @@ func TestServer_SetPhone(t *testing.T) { } func TestServer_ResendPhoneCode(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() verifiedUserID := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.Id, gofakeit.Email()).GetUserId() @@ -201,8 +197,6 @@ func TestServer_ResendPhoneCode(t *testing.T) { } func TestServer_VerifyPhone(t *testing.T) { - t.Parallel() - userResp := Instance.CreateHumanUser(CTX) tests := []struct { name string @@ -256,8 +250,6 @@ func TestServer_VerifyPhone(t *testing.T) { } func TestServer_RemovePhone(t *testing.T) { - t.Parallel() - userResp := Instance.CreateHumanUser(CTX) failResp := Instance.CreateHumanUserNoPhone(CTX) otherUser := Instance.CreateHumanUser(CTX).GetUserId() diff --git a/internal/api/grpc/user/v2/integration_test/query_test.go b/internal/api/grpc/user/v2/integration_test/query_test.go index 3d5b2d9416..a00d1b1a48 100644 --- a/internal/api/grpc/user/v2/integration_test/query_test.go +++ b/internal/api/grpc/user/v2/integration_test/query_test.go @@ -20,8 +20,6 @@ import ( ) func TestServer_GetUserByID(t *testing.T) { - t.Parallel() - orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("GetUserByIDOrg-%s", gofakeit.AppName()), gofakeit.Email()) type args struct { ctx context.Context @@ -188,8 +186,6 @@ func TestServer_GetUserByID(t *testing.T) { } func TestServer_GetUserByID_Permission(t *testing.T) { - t.Parallel() - newOrgOwnerEmail := gofakeit.Email() newOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("GetHuman-%s", gofakeit.AppName()), newOrgOwnerEmail) newUserID := newOrg.CreatedAdmins[0].GetUserId() @@ -337,8 +333,6 @@ type userAttr struct { } func TestServer_ListUsers(t *testing.T) { - t.Parallel() - orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListUsersOrg-%s", gofakeit.AppName()), gofakeit.Email()) userResp := Instance.CreateHumanUserVerified(IamCTX, orgResp.OrganizationId, gofakeit.Email()) type args struct { diff --git a/internal/api/grpc/user/v2/integration_test/totp_test.go b/internal/api/grpc/user/v2/integration_test/totp_test.go index add67876ff..e65756c1c1 100644 --- a/internal/api/grpc/user/v2/integration_test/totp_test.go +++ b/internal/api/grpc/user/v2/integration_test/totp_test.go @@ -18,8 +18,6 @@ import ( ) func TestServer_RegisterTOTP(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() Instance.RegisterUserPasskey(CTX, userID) _, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID) @@ -106,8 +104,6 @@ func TestServer_RegisterTOTP(t *testing.T) { } func TestServer_VerifyTOTPRegistration(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() Instance.RegisterUserPasskey(CTX, userID) _, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID) @@ -211,8 +207,6 @@ func TestServer_VerifyTOTPRegistration(t *testing.T) { } func TestServer_RemoveTOTP(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() Instance.RegisterUserPasskey(CTX, userID) _, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID) diff --git a/internal/api/grpc/user/v2/integration_test/u2f_test.go b/internal/api/grpc/user/v2/integration_test/u2f_test.go index 9ec0011249..b8af753f85 100644 --- a/internal/api/grpc/user/v2/integration_test/u2f_test.go +++ b/internal/api/grpc/user/v2/integration_test/u2f_test.go @@ -17,8 +17,6 @@ import ( ) func TestServer_RegisterU2F(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() otherUser := Instance.CreateHumanUser(CTX).GetUserId() @@ -108,8 +106,6 @@ func TestServer_RegisterU2F(t *testing.T) { } func TestServer_VerifyU2FRegistration(t *testing.T) { - t.Parallel() - ctx, userID, pkr := ctxFromNewUserWithRegisteredU2F(t) attestationResponse, err := Instance.WebAuthN.CreateAttestationResponse(pkr.GetPublicKeyCredentialCreationOptions()) @@ -215,8 +211,6 @@ func ctxFromNewUserWithVerifiedU2F(t *testing.T) (context.Context, string, strin } func TestServer_RemoveU2F(t *testing.T) { - t.Parallel() - userIDWithout := Instance.CreateHumanUser(CTX).GetUserId() ctxRegistered, userIDRegistered, pkrRegistered := ctxFromNewUserWithRegisteredU2F(t) _, userIDVerified, u2fVerified := ctxFromNewUserWithVerifiedU2F(t) diff --git a/internal/api/grpc/user/v2/integration_test/user_test.go b/internal/api/grpc/user/v2/integration_test/user_test.go index e4196c1a30..cf42e9291f 100644 --- a/internal/api/grpc/user/v2/integration_test/user_test.go +++ b/internal/api/grpc/user/v2/integration_test/user_test.go @@ -53,8 +53,6 @@ func TestMain(m *testing.M) { } func TestServer_AddHumanUser(t *testing.T) { - t.Parallel() - idpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id) type args struct { ctx context.Context @@ -681,8 +679,6 @@ func TestServer_AddHumanUser(t *testing.T) { } func TestServer_AddHumanUser_Permission(t *testing.T) { - t.Parallel() - newOrgOwnerEmail := gofakeit.Email() newOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("AddHuman-%s", gofakeit.AppName()), newOrgOwnerEmail) type args struct { @@ -876,8 +872,6 @@ func TestServer_AddHumanUser_Permission(t *testing.T) { } func TestServer_UpdateHumanUser(t *testing.T) { - t.Parallel() - type args struct { ctx context.Context req *user.UpdateHumanUserRequest @@ -1239,8 +1233,6 @@ func TestServer_UpdateHumanUser(t *testing.T) { } func TestServer_UpdateHumanUser_Permission(t *testing.T) { - t.Parallel() - newOrgOwnerEmail := gofakeit.Email() newOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("UpdateHuman-%s", gofakeit.AppName()), newOrgOwnerEmail) newUserID := newOrg.CreatedAdmins[0].GetUserId() @@ -1324,8 +1316,6 @@ func TestServer_UpdateHumanUser_Permission(t *testing.T) { } func TestServer_LockUser(t *testing.T) { - t.Parallel() - type args struct { ctx context.Context req *user.LockUserRequest @@ -1434,8 +1424,6 @@ func TestServer_LockUser(t *testing.T) { } func TestServer_UnLockUser(t *testing.T) { - t.Parallel() - type args struct { ctx context.Context req *user.UnlockUserRequest @@ -1544,8 +1532,6 @@ func TestServer_UnLockUser(t *testing.T) { } func TestServer_DeactivateUser(t *testing.T) { - t.Parallel() - type args struct { ctx context.Context req *user.DeactivateUserRequest @@ -1655,8 +1641,6 @@ func TestServer_DeactivateUser(t *testing.T) { } func TestServer_ReactivateUser(t *testing.T) { - t.Parallel() - type args struct { ctx context.Context req *user.ReactivateUserRequest @@ -1765,8 +1749,6 @@ func TestServer_ReactivateUser(t *testing.T) { } func TestServer_DeleteUser(t *testing.T) { - t.Parallel() - projectResp, err := Instance.CreateProject(CTX) require.NoError(t, err) type args struct { @@ -1866,8 +1848,6 @@ func TestServer_DeleteUser(t *testing.T) { } func TestServer_StartIdentityProviderIntent(t *testing.T) { - t.Parallel() - idpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id) orgIdpResp := Instance.AddOrgGenericOAuthProvider(CTX, Instance.DefaultOrg.Id) orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("NotDefaultOrg-%s", gofakeit.AppName()), gofakeit.Email()) @@ -2131,9 +2111,7 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) { /* func TestServer_RetrieveIdentityProviderIntent(t *testing.T) { - t.Parallel() - - idpID := Instance.AddGenericOAuthProvider(t, CTX) + idpID := Instance.AddGenericOAuthProvider(t, CTX) intentID := Instance.CreateIntent(t, CTX, idpID) successfulID, token, changeDate, sequence := Instance.CreateSuccessfulOAuthIntent(t, CTX, idpID, "", "id") successfulWithUserID, withUsertoken, withUserchangeDate, withUsersequence := Instance.CreateSuccessfulOAuthIntent(t, CTX, idpID, "user", "id") @@ -2421,8 +2399,6 @@ func ctxFromNewUserWithVerifiedPasswordlessLegacy(t *testing.T) (context.Context } func TestServer_ListAuthenticationMethodTypes(t *testing.T) { - t.Parallel() - userIDWithoutAuth := Instance.CreateHumanUser(CTX).GetUserId() userIDWithPasskey := Instance.CreateHumanUser(CTX).GetUserId() @@ -2654,8 +2630,6 @@ func TestServer_ListAuthenticationMethodTypes(t *testing.T) { } func TestServer_CreateInviteCode(t *testing.T) { - t.Parallel() - type args struct { ctx context.Context req *user.CreateInviteCodeRequest @@ -2787,8 +2761,6 @@ func TestServer_CreateInviteCode(t *testing.T) { } func TestServer_ResendInviteCode(t *testing.T) { - t.Parallel() - type args struct { ctx context.Context req *user.ResendInviteCodeRequest @@ -2878,8 +2850,6 @@ func TestServer_ResendInviteCode(t *testing.T) { } func TestServer_VerifyInviteCode(t *testing.T) { - t.Parallel() - type args struct { ctx context.Context req *user.VerifyInviteCodeRequest diff --git a/internal/api/grpc/user/v2/otp.go b/internal/api/grpc/user/v2/otp.go index e2fe6b794d..fd76cf2b93 100644 --- a/internal/api/grpc/user/v2/otp.go +++ b/internal/api/grpc/user/v2/otp.go @@ -13,7 +13,6 @@ func (s *Server) AddOTPSMS(ctx context.Context, req *user.AddOTPSMSRequest) (*us return nil, err } return &user.AddOTPSMSResponse{Details: object.DomainToDetailsPb(details)}, nil - } func (s *Server) RemoveOTPSMS(ctx context.Context, req *user.RemoveOTPSMSRequest) (*user.RemoveOTPSMSResponse, error) { diff --git a/internal/api/grpc/user/v2beta/integration_test/email_test.go b/internal/api/grpc/user/v2beta/integration_test/email_test.go index ebace6daa2..d22355978a 100644 --- a/internal/api/grpc/user/v2beta/integration_test/email_test.go +++ b/internal/api/grpc/user/v2beta/integration_test/email_test.go @@ -17,8 +17,6 @@ import ( ) func TestServer_SetEmail(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() tests := []struct { @@ -148,8 +146,6 @@ func TestServer_SetEmail(t *testing.T) { } func TestServer_ResendEmailCode(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() verifiedUserID := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.Id, gofakeit.Email()).GetUserId() @@ -254,8 +250,6 @@ func TestServer_ResendEmailCode(t *testing.T) { } func TestServer_VerifyEmail(t *testing.T) { - t.Parallel() - userResp := Instance.CreateHumanUser(CTX) tests := []struct { name string diff --git a/internal/api/grpc/user/v2beta/integration_test/otp_test.go b/internal/api/grpc/user/v2beta/integration_test/otp_test.go index 3ffec36ef0..fae6c069a4 100644 --- a/internal/api/grpc/user/v2beta/integration_test/otp_test.go +++ b/internal/api/grpc/user/v2beta/integration_test/otp_test.go @@ -15,8 +15,6 @@ import ( ) func TestServer_AddOTPSMS(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() Instance.RegisterUserPasskey(CTX, userID) _, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID) @@ -60,7 +58,7 @@ func TestServer_AddOTPSMS(t *testing.T) { wantErr: true, }, { - name: "user mismatch", + name: "no permission", args: args{ ctx: integration.WithAuthorizationToken(context.Background(), sessionTokenOtherUser), req: &user.AddOTPSMSRequest{ @@ -123,22 +121,30 @@ func TestServer_AddOTPSMS(t *testing.T) { } func TestServer_RemoveOTPSMS(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() Instance.RegisterUserPasskey(CTX, userID) _, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID) userVerified := Instance.CreateHumanUser(CTX) Instance.RegisterUserPasskey(CTX, userVerified.GetUserId()) - _, sessionTokenVerified, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userVerified.GetUserId()) - userVerifiedCtx := integration.WithAuthorizationToken(context.Background(), sessionTokenVerified) - _, err := Client.VerifyPhone(userVerifiedCtx, &user.VerifyPhoneRequest{ + _, err := Instance.Client.UserV2beta.VerifyPhone(CTX, &user.VerifyPhoneRequest{ UserId: userVerified.GetUserId(), VerificationCode: userVerified.GetPhoneCode(), }) require.NoError(t, err) - _, err = Client.AddOTPSMS(userVerifiedCtx, &user.AddOTPSMSRequest{UserId: userVerified.GetUserId()}) + _, err = Instance.Client.UserV2beta.AddOTPSMS(CTX, &user.AddOTPSMSRequest{UserId: userVerified.GetUserId()}) + require.NoError(t, err) + + userSelf := Instance.CreateHumanUser(CTX) + Instance.RegisterUserPasskey(CTX, userSelf.GetUserId()) + _, sessionTokenSelf, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userSelf.GetUserId()) + userSelfCtx := integration.WithAuthorizationToken(context.Background(), sessionTokenSelf) + _, err = Instance.Client.UserV2beta.VerifyPhone(CTX, &user.VerifyPhoneRequest{ + UserId: userSelf.GetUserId(), + VerificationCode: userSelf.GetPhoneCode(), + }) + require.NoError(t, err) + _, err = Instance.Client.UserV2beta.AddOTPSMS(CTX, &user.AddOTPSMSRequest{UserId: userSelf.GetUserId()}) require.NoError(t, err) type args struct { @@ -161,10 +167,24 @@ func TestServer_RemoveOTPSMS(t *testing.T) { }, wantErr: true, }, + { + name: "success, self", + args: args{ + ctx: userSelfCtx, + req: &user.RemoveOTPSMSRequest{ + UserId: userSelf.GetUserId(), + }, + }, + want: &user.RemoveOTPSMSResponse{ + Details: &object.Details{ + ResourceOwner: Instance.DefaultOrg.Details.ResourceOwner, + }, + }, + }, { name: "success", args: args{ - ctx: userVerifiedCtx, + ctx: CTX, req: &user.RemoveOTPSMSRequest{ UserId: userVerified.GetUserId(), }, @@ -191,8 +211,6 @@ func TestServer_RemoveOTPSMS(t *testing.T) { } func TestServer_AddOTPEmail(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() Instance.RegisterUserPasskey(CTX, userID) _, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID) @@ -301,22 +319,30 @@ func TestServer_AddOTPEmail(t *testing.T) { } func TestServer_RemoveOTPEmail(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() Instance.RegisterUserPasskey(CTX, userID) _, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID) userVerified := Instance.CreateHumanUser(CTX) Instance.RegisterUserPasskey(CTX, userVerified.GetUserId()) - _, sessionTokenVerified, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userVerified.GetUserId()) - userVerifiedCtx := integration.WithAuthorizationToken(context.Background(), sessionTokenVerified) - _, err := Client.VerifyEmail(userVerifiedCtx, &user.VerifyEmailRequest{ + _, err := Client.VerifyEmail(CTX, &user.VerifyEmailRequest{ UserId: userVerified.GetUserId(), VerificationCode: userVerified.GetEmailCode(), }) require.NoError(t, err) - _, err = Client.AddOTPEmail(userVerifiedCtx, &user.AddOTPEmailRequest{UserId: userVerified.GetUserId()}) + _, err = Client.AddOTPEmail(CTX, &user.AddOTPEmailRequest{UserId: userVerified.GetUserId()}) + require.NoError(t, err) + + userSelf := Instance.CreateHumanUser(CTX) + Instance.RegisterUserPasskey(CTX, userSelf.GetUserId()) + _, sessionTokenSelf, _, _ := Instance.CreateVerifiedWebAuthNSession(t, IamCTX, userSelf.GetUserId()) + userSelfCtx := integration.WithAuthorizationToken(context.Background(), sessionTokenSelf) + _, err = Client.VerifyEmail(CTX, &user.VerifyEmailRequest{ + UserId: userSelf.GetUserId(), + VerificationCode: userSelf.GetEmailCode(), + }) + require.NoError(t, err) + _, err = Client.AddOTPEmail(CTX, &user.AddOTPEmailRequest{UserId: userSelf.GetUserId()}) require.NoError(t, err) type args struct { @@ -339,10 +365,25 @@ func TestServer_RemoveOTPEmail(t *testing.T) { }, wantErr: true, }, + { + name: "success, self", + args: args{ + ctx: userSelfCtx, + req: &user.RemoveOTPEmailRequest{ + UserId: userSelf.GetUserId(), + }, + }, + want: &user.RemoveOTPEmailResponse{ + Details: &object.Details{ + ChangeDate: timestamppb.Now(), + ResourceOwner: Instance.DefaultOrg.Details.ResourceOwner, + }, + }, + }, { name: "success", args: args{ - ctx: userVerifiedCtx, + ctx: CTX, req: &user.RemoveOTPEmailRequest{ UserId: userVerified.GetUserId(), }, diff --git a/internal/api/grpc/user/v2beta/integration_test/passkey_test.go b/internal/api/grpc/user/v2beta/integration_test/passkey_test.go index e5a0ec193b..7bc0465956 100644 --- a/internal/api/grpc/user/v2beta/integration_test/passkey_test.go +++ b/internal/api/grpc/user/v2beta/integration_test/passkey_test.go @@ -18,8 +18,6 @@ import ( ) func TestServer_RegisterPasskey(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() reg, err := Client.CreatePasskeyRegistrationLink(CTX, &user.CreatePasskeyRegistrationLinkRequest{ UserId: userID, @@ -94,15 +92,30 @@ func TestServer_RegisterPasskey(t *testing.T) { wantErr: true, }, { - name: "user mismatch", + name: "user no permission", args: args{ - ctx: CTX, + ctx: UserCTX, req: &user.RegisterPasskeyRequest{ UserId: userID, }, }, wantErr: true, }, + { + name: "user permission", + args: args{ + ctx: IamCTX, + req: &user.RegisterPasskeyRequest{ + UserId: userID, + }, + }, + want: &user.RegisterPasskeyResponse{ + Details: &object.Details{ + ChangeDate: timestamppb.Now(), + ResourceOwner: Instance.DefaultOrg.Id, + }, + }, + }, { name: "user setting its own passkey", args: args{ @@ -140,8 +153,6 @@ func TestServer_RegisterPasskey(t *testing.T) { } func TestServer_VerifyPasskeyRegistration(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() reg, err := Client.CreatePasskeyRegistrationLink(CTX, &user.CreatePasskeyRegistrationLinkRequest{ UserId: userID, @@ -230,8 +241,6 @@ func TestServer_VerifyPasskeyRegistration(t *testing.T) { } func TestServer_CreatePasskeyRegistrationLink(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() type args struct { diff --git a/internal/api/grpc/user/v2beta/integration_test/password_test.go b/internal/api/grpc/user/v2beta/integration_test/password_test.go index 5995f87c7f..fa6bc66104 100644 --- a/internal/api/grpc/user/v2beta/integration_test/password_test.go +++ b/internal/api/grpc/user/v2beta/integration_test/password_test.go @@ -17,8 +17,6 @@ import ( ) func TestServer_RequestPasswordReset(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() tests := []struct { @@ -109,8 +107,6 @@ func TestServer_RequestPasswordReset(t *testing.T) { } func TestServer_SetPassword(t *testing.T) { - t.Parallel() - type args struct { ctx context.Context req *user.SetPasswordRequest diff --git a/internal/api/grpc/user/v2beta/integration_test/phone_test.go b/internal/api/grpc/user/v2beta/integration_test/phone_test.go index 03567f4023..cd7199dcea 100644 --- a/internal/api/grpc/user/v2beta/integration_test/phone_test.go +++ b/internal/api/grpc/user/v2beta/integration_test/phone_test.go @@ -18,8 +18,6 @@ import ( ) func TestServer_SetPhone(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() tests := []struct { @@ -126,8 +124,6 @@ func TestServer_SetPhone(t *testing.T) { } func TestServer_ResendPhoneCode(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() verifiedUserID := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.Id, gofakeit.Email()).GetUserId() @@ -204,8 +200,6 @@ func TestServer_ResendPhoneCode(t *testing.T) { } func TestServer_VerifyPhone(t *testing.T) { - t.Parallel() - userResp := Instance.CreateHumanUser(CTX) tests := []struct { name string @@ -258,8 +252,6 @@ func TestServer_VerifyPhone(t *testing.T) { } func TestServer_RemovePhone(t *testing.T) { - t.Parallel() - userResp := Instance.CreateHumanUser(CTX) failResp := Instance.CreateHumanUserNoPhone(CTX) otherUser := Instance.CreateHumanUser(CTX).GetUserId() diff --git a/internal/api/grpc/user/v2beta/integration_test/query_test.go b/internal/api/grpc/user/v2beta/integration_test/query_test.go index 654a84a5d4..fc1d71926e 100644 --- a/internal/api/grpc/user/v2beta/integration_test/query_test.go +++ b/internal/api/grpc/user/v2beta/integration_test/query_test.go @@ -29,8 +29,6 @@ func detailsV2ToV2beta(obj *object.Details) *object_v2beta.Details { } func TestServer_GetUserByID(t *testing.T) { - t.Parallel() - orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("GetUserByIDOrg-%s", gofakeit.AppName()), gofakeit.Email()) type args struct { ctx context.Context @@ -197,8 +195,6 @@ func TestServer_GetUserByID(t *testing.T) { } func TestServer_GetUserByID_Permission(t *testing.T) { - t.Parallel() - timeNow := time.Now().UTC() newOrgOwnerEmail := gofakeit.Email() newOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("GetHuman-%s", gofakeit.AppName()), newOrgOwnerEmail) @@ -347,8 +343,6 @@ type userAttr struct { } func TestServer_ListUsers(t *testing.T) { - t.Parallel() - orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListUsersOrg-%s", gofakeit.AppName()), gofakeit.Email()) userResp := Instance.CreateHumanUserVerified(IamCTX, orgResp.OrganizationId, gofakeit.Email()) type args struct { diff --git a/internal/api/grpc/user/v2beta/integration_test/totp_test.go b/internal/api/grpc/user/v2beta/integration_test/totp_test.go index 9fcce47321..4afe5e1f31 100644 --- a/internal/api/grpc/user/v2beta/integration_test/totp_test.go +++ b/internal/api/grpc/user/v2beta/integration_test/totp_test.go @@ -18,8 +18,6 @@ import ( ) func TestServer_RegisterTOTP(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() Instance.RegisterUserPasskey(CTX, userID) _, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID) @@ -106,8 +104,6 @@ func TestServer_RegisterTOTP(t *testing.T) { } func TestServer_VerifyTOTPRegistration(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() Instance.RegisterUserPasskey(CTX, userID) _, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID) @@ -216,8 +212,6 @@ func TestServer_VerifyTOTPRegistration(t *testing.T) { } func TestServer_RemoveTOTP(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() Instance.RegisterUserPasskey(CTX, userID) _, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID) diff --git a/internal/api/grpc/user/v2beta/integration_test/u2f_test.go b/internal/api/grpc/user/v2beta/integration_test/u2f_test.go index c35231eefc..6e47cbbb99 100644 --- a/internal/api/grpc/user/v2beta/integration_test/u2f_test.go +++ b/internal/api/grpc/user/v2beta/integration_test/u2f_test.go @@ -17,8 +17,6 @@ import ( ) func TestServer_RegisterU2F(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() otherUser := Instance.CreateHumanUser(CTX).GetUserId() @@ -108,8 +106,6 @@ func TestServer_RegisterU2F(t *testing.T) { } func TestServer_VerifyU2FRegistration(t *testing.T) { - t.Parallel() - userID := Instance.CreateHumanUser(CTX).GetUserId() Instance.RegisterUserPasskey(CTX, userID) _, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID) diff --git a/internal/api/grpc/user/v2beta/integration_test/user_test.go b/internal/api/grpc/user/v2beta/integration_test/user_test.go index b5f0b16d20..2e0abbb6b9 100644 --- a/internal/api/grpc/user/v2beta/integration_test/user_test.go +++ b/internal/api/grpc/user/v2beta/integration_test/user_test.go @@ -51,8 +51,6 @@ func TestMain(m *testing.M) { } func TestServer_AddHumanUser(t *testing.T) { - t.Parallel() - idpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id) type args struct { ctx context.Context @@ -638,8 +636,6 @@ func TestServer_AddHumanUser(t *testing.T) { } func TestServer_AddHumanUser_Permission(t *testing.T) { - t.Parallel() - newOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("AddHuman-%s", gofakeit.AppName()), gofakeit.Email()) type args struct { ctx context.Context @@ -832,8 +828,6 @@ func TestServer_AddHumanUser_Permission(t *testing.T) { } func TestServer_UpdateHumanUser(t *testing.T) { - t.Parallel() - type args struct { ctx context.Context req *user.UpdateHumanUserRequest @@ -1195,8 +1189,6 @@ func TestServer_UpdateHumanUser(t *testing.T) { } func TestServer_UpdateHumanUser_Permission(t *testing.T) { - t.Parallel() - newOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("UpdateHuman-%s", gofakeit.AppName()), gofakeit.Email()) newUserID := newOrg.CreatedAdmins[0].GetUserId() type args struct { @@ -1279,8 +1271,6 @@ func TestServer_UpdateHumanUser_Permission(t *testing.T) { } func TestServer_LockUser(t *testing.T) { - t.Parallel() - type args struct { ctx context.Context req *user.LockUserRequest @@ -1389,8 +1379,6 @@ func TestServer_LockUser(t *testing.T) { } func TestServer_UnLockUser(t *testing.T) { - t.Parallel() - type args struct { ctx context.Context req *user.UnlockUserRequest @@ -1499,8 +1487,6 @@ func TestServer_UnLockUser(t *testing.T) { } func TestServer_DeactivateUser(t *testing.T) { - t.Parallel() - type args struct { ctx context.Context req *user.DeactivateUserRequest @@ -1609,8 +1595,6 @@ func TestServer_DeactivateUser(t *testing.T) { } func TestServer_ReactivateUser(t *testing.T) { - t.Parallel() - type args struct { ctx context.Context req *user.ReactivateUserRequest @@ -1719,8 +1703,6 @@ func TestServer_ReactivateUser(t *testing.T) { } func TestServer_DeleteUser(t *testing.T) { - t.Parallel() - projectResp, err := Instance.CreateProject(CTX) require.NoError(t, err) type args struct { @@ -1820,8 +1802,6 @@ func TestServer_DeleteUser(t *testing.T) { } func TestServer_AddIDPLink(t *testing.T) { - t.Parallel() - idpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id) type args struct { ctx context.Context @@ -1901,8 +1881,6 @@ func TestServer_AddIDPLink(t *testing.T) { } func TestServer_StartIdentityProviderIntent(t *testing.T) { - t.Parallel() - idpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id) orgIdpID := Instance.AddOrgGenericOAuthProvider(CTX, Instance.DefaultOrg.Id) orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("NotDefaultOrg-%s", gofakeit.AppName()), gofakeit.Email()) @@ -2166,9 +2144,7 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) { /* func TestServer_RetrieveIdentityProviderIntent(t *testing.T) { - t.Parallel() - - idpID := Instance.AddGenericOAuthProvider(t, CTX) + idpID := Instance.AddGenericOAuthProvider(t, CTX) intentID := Instance.CreateIntent(t, CTX, idpID) successfulID, token, changeDate, sequence := Instance.CreateSuccessfulOAuthIntent(t, CTX, idpID.Id, "", "id") successfulWithUserID, withUsertoken, withUserchangeDate, withUsersequence := Instance.CreateSuccessfulOAuthIntent(t, CTX, idpID.Id, "user", "id") @@ -2428,8 +2404,6 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) { */ func TestServer_ListAuthenticationMethodTypes(t *testing.T) { - t.Parallel() - userIDWithoutAuth := Instance.CreateHumanUser(CTX).GetUserId() userIDWithPasskey := Instance.CreateHumanUser(CTX).GetUserId() diff --git a/internal/api/oidc/auth_request_converter.go b/internal/api/oidc/auth_request_converter.go index cd52cdbe58..2144ca8ba1 100644 --- a/internal/api/oidc/auth_request_converter.go +++ b/internal/api/oidc/auth_request_converter.go @@ -158,7 +158,7 @@ func IpFromContext(ctx context.Context) net.IP { } func PromptToBusiness(oidcPrompt []string) []domain.Prompt { - prompts := make([]domain.Prompt, len(oidcPrompt)) + prompts := make([]domain.Prompt, 0, len(oidcPrompt)) for _, oidcPrompt := range oidcPrompt { switch oidcPrompt { case oidc.PromptNone: diff --git a/internal/api/oidc/auth_request_converter_test.go b/internal/api/oidc/auth_request_converter_test.go index b35d519661..06750aad3c 100644 --- a/internal/api/oidc/auth_request_converter_test.go +++ b/internal/api/oidc/auth_request_converter_test.go @@ -94,3 +94,36 @@ func TestResponseModeToOIDC(t *testing.T) { }) } } + +func TestPromptToBusiness(t *testing.T) { + type args struct { + oidcPrompt []string + } + tests := []struct { + name string + args args + want []domain.Prompt + }{ + { + name: "unspecified", + args: args{nil}, + want: []domain.Prompt{}, + }, + { + name: "invalid", + args: args{[]string{"non_existing_prompt"}}, + want: []domain.Prompt{}, + }, + { + name: "prompt_none", + args: args{[]string{oidc.PromptNone}}, + want: []domain.Prompt{domain.PromptNone}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := PromptToBusiness(tt.args.oidcPrompt) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/internal/api/oidc/integration_test/auth_request_test.go b/internal/api/oidc/integration_test/auth_request_test.go index bd9142a3f6..7ac0e24694 100644 --- a/internal/api/oidc/integration_test/auth_request_test.go +++ b/internal/api/oidc/integration_test/auth_request_test.go @@ -28,8 +28,6 @@ var ( ) func TestOPStorage_CreateAuthRequest(t *testing.T) { - t.Parallel() - clientID, _ := createClient(t, Instance) id := createAuthRequest(t, Instance, clientID, redirectURI) @@ -37,8 +35,6 @@ func TestOPStorage_CreateAuthRequest(t *testing.T) { } func TestOPStorage_CreateAccessToken_code(t *testing.T) { - t.Parallel() - clientID, _ := createClient(t, Instance) authRequestID := createAuthRequest(t, Instance, clientID, redirectURI) sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId()) @@ -78,8 +74,6 @@ func TestOPStorage_CreateAccessToken_code(t *testing.T) { } func TestOPStorage_CreateAccessToken_implicit(t *testing.T) { - t.Parallel() - clientID := createImplicitClient(t) authRequestID := createAuthRequestImplicit(t, clientID, redirectURIImplicit) sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId()) @@ -130,8 +124,6 @@ func TestOPStorage_CreateAccessToken_implicit(t *testing.T) { } func TestOPStorage_CreateAccessAndRefreshTokens_code(t *testing.T) { - t.Parallel() - clientID, _ := createClient(t, Instance) authRequestID := createAuthRequest(t, Instance, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess) sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId()) @@ -155,8 +147,6 @@ func TestOPStorage_CreateAccessAndRefreshTokens_code(t *testing.T) { } func TestOPStorage_CreateAccessAndRefreshTokens_refresh(t *testing.T) { - t.Parallel() - clientID, _ := createClient(t, Instance) provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) @@ -193,8 +183,6 @@ func TestOPStorage_CreateAccessAndRefreshTokens_refresh(t *testing.T) { } func TestOPStorage_RevokeToken_access_token(t *testing.T) { - t.Parallel() - clientID, _ := createClient(t, Instance) provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) @@ -238,8 +226,6 @@ func TestOPStorage_RevokeToken_access_token(t *testing.T) { } func TestOPStorage_RevokeToken_access_token_invalid_token_hint_type(t *testing.T) { - t.Parallel() - clientID, _ := createClient(t, Instance) provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) @@ -277,8 +263,6 @@ func TestOPStorage_RevokeToken_access_token_invalid_token_hint_type(t *testing.T } func TestOPStorage_RevokeToken_refresh_token(t *testing.T) { - t.Parallel() - clientID, _ := createClient(t, Instance) provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) @@ -322,8 +306,6 @@ func TestOPStorage_RevokeToken_refresh_token(t *testing.T) { } func TestOPStorage_RevokeToken_refresh_token_invalid_token_type_hint(t *testing.T) { - t.Parallel() - clientID, _ := createClient(t, Instance) provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) @@ -361,8 +343,6 @@ func TestOPStorage_RevokeToken_refresh_token_invalid_token_type_hint(t *testing. } func TestOPStorage_RevokeToken_invalid_client(t *testing.T) { - t.Parallel() - clientID, _ := createClient(t, Instance) authRequestID := createAuthRequest(t, Instance, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess) sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId()) @@ -393,8 +373,6 @@ func TestOPStorage_RevokeToken_invalid_client(t *testing.T) { } func TestOPStorage_TerminateSession(t *testing.T) { - t.Parallel() - clientID, _ := createClient(t, Instance) provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) @@ -432,8 +410,6 @@ func TestOPStorage_TerminateSession(t *testing.T) { } func TestOPStorage_TerminateSession_refresh_grant(t *testing.T) { - t.Parallel() - clientID, _ := createClient(t, Instance) provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) @@ -478,8 +454,6 @@ func TestOPStorage_TerminateSession_refresh_grant(t *testing.T) { } func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) { - t.Parallel() - clientID, _ := createClient(t, Instance) provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) diff --git a/internal/api/oidc/integration_test/client_test.go b/internal/api/oidc/integration_test/client_test.go index 1b9ccd5cb3..6d61e84437 100644 --- a/internal/api/oidc/integration_test/client_test.go +++ b/internal/api/oidc/integration_test/client_test.go @@ -24,8 +24,6 @@ import ( ) func TestServer_Introspect(t *testing.T) { - t.Parallel() - project, err := Instance.CreateProject(CTX) require.NoError(t, err) app, err := Instance.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), false) @@ -143,8 +141,6 @@ func TestServer_Introspect(t *testing.T) { } func TestServer_Introspect_invalid_auth_invalid_token(t *testing.T) { - t.Parallel() - // ensure that when an invalid authentication and token is sent, the authentication error is returned // https://github.com/zitadel/zitadel/pull/8133 resourceServer, err := Instance.CreateResourceServerClientCredentials(CTX, "xxxxx", "xxxxx") @@ -191,8 +187,6 @@ func assertIntrospection( // TestServer_VerifyClient tests verification by running code flow tests // with clients that have different authentication methods. func TestServer_VerifyClient(t *testing.T) { - t.Parallel() - sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId()) project, err := Instance.CreateProject(CTX) require.NoError(t, err) diff --git a/internal/api/oidc/integration_test/keys_test.go b/internal/api/oidc/integration_test/keys_test.go index e8160017a5..8b66e980d0 100644 --- a/internal/api/oidc/integration_test/keys_test.go +++ b/internal/api/oidc/integration_test/keys_test.go @@ -24,8 +24,6 @@ import ( ) func TestServer_Keys(t *testing.T) { - t.Parallel() - instance := integration.NewInstance(CTX) ctxLogin := instance.WithAuthorization(CTX, integration.UserTypeLogin) diff --git a/internal/api/oidc/integration_test/server_test.go b/internal/api/oidc/integration_test/server_test.go index fcf9bfb65e..ea2fa0e2ee 100644 --- a/internal/api/oidc/integration_test/server_test.go +++ b/internal/api/oidc/integration_test/server_test.go @@ -18,8 +18,6 @@ import ( ) func TestServer_RefreshToken_Status(t *testing.T) { - t.Parallel() - clientID, _ := createClient(t, Instance) provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) diff --git a/internal/api/oidc/integration_test/token_client_credentials_test.go b/internal/api/oidc/integration_test/token_client_credentials_test.go index d43f40e53e..0567b4ce78 100644 --- a/internal/api/oidc/integration_test/token_client_credentials_test.go +++ b/internal/api/oidc/integration_test/token_client_credentials_test.go @@ -22,8 +22,6 @@ import ( ) func TestServer_ClientCredentialsExchange(t *testing.T) { - t.Parallel() - machine, name, clientID, clientSecret, err := Instance.CreateOIDCCredentialsClient(CTX) require.NoError(t, err) diff --git a/internal/api/oidc/integration_test/token_exchange_test.go b/internal/api/oidc/integration_test/token_exchange_test.go index 5b0b86f0ec..1319eea19a 100644 --- a/internal/api/oidc/integration_test/token_exchange_test.go +++ b/internal/api/oidc/integration_test/token_exchange_test.go @@ -143,8 +143,6 @@ func refreshTokenVerifier(ctx context.Context, provider rp.RelyingParty, subject } func TestServer_TokenExchange(t *testing.T) { - t.Parallel() - instance := integration.NewInstance(CTX) ctx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) userResp := instance.CreateHumanUser(ctx) @@ -365,8 +363,6 @@ func TestServer_TokenExchange(t *testing.T) { } func TestServer_TokenExchangeImpersonation(t *testing.T) { - t.Parallel() - instance := integration.NewInstance(CTX) ctx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) userResp := instance.CreateHumanUser(ctx) @@ -581,8 +577,6 @@ func TestServer_TokenExchangeImpersonation(t *testing.T) { // This test tries to call the zitadel API with an impersonated token, // which should fail. func TestImpersonation_API_Call(t *testing.T) { - t.Parallel() - instance := integration.NewInstance(CTX) ctx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) diff --git a/internal/api/oidc/integration_test/token_jwt_profile_test.go b/internal/api/oidc/integration_test/token_jwt_profile_test.go index ac483cf620..5845713317 100644 --- a/internal/api/oidc/integration_test/token_jwt_profile_test.go +++ b/internal/api/oidc/integration_test/token_jwt_profile_test.go @@ -21,8 +21,6 @@ import ( ) func TestServer_JWTProfile(t *testing.T) { - t.Parallel() - user, name, keyData, err := Instance.CreateOIDCJWTProfileClient(CTX) require.NoError(t, err) diff --git a/internal/api/ui/login/init_password_handler.go b/internal/api/ui/login/init_password_handler.go index 4b9c173a2f..b8c6d401c5 100644 --- a/internal/api/ui/login/init_password_handler.go +++ b/internal/api/ui/login/init_password_handler.go @@ -1,6 +1,7 @@ package login import ( + "fmt" "net/http" "net/url" @@ -47,6 +48,15 @@ func InitPasswordLink(origin, userID, code, orgID, authRequestID string) string return externalLink(origin) + EndpointInitPassword + "?" + v.Encode() } +func InitPasswordLinkTemplate(origin, userID, orgID, authRequestID string) string { + return fmt.Sprintf("%s%s?%s=%s&%s=%s&%s=%s&%s=%s", + externalLink(origin), EndpointInitPassword, + queryInitPWUserID, userID, + queryInitPWCode, "{{.Code}}", + queryOrgID, orgID, + QueryAuthRequestID, authRequestID) +} + func (l *Login) handleInitPassword(w http.ResponseWriter, r *http.Request) { authReq := l.checkOptionalAuthRequestOfEmailLinks(r) userID := r.FormValue(queryInitPWUserID) diff --git a/internal/api/ui/login/init_user_handler.go b/internal/api/ui/login/init_user_handler.go index d60c4c3cbd..9a6d052dcd 100644 --- a/internal/api/ui/login/init_user_handler.go +++ b/internal/api/ui/login/init_user_handler.go @@ -1,6 +1,7 @@ package login import ( + "fmt" "net/http" "net/url" "strconv" @@ -55,6 +56,17 @@ func InitUserLink(origin, userID, loginName, code, orgID string, passwordSet boo return externalLink(origin) + EndpointInitUser + "?" + v.Encode() } +func InitUserLinkTemplate(origin, userID, orgID, authRequestID string) string { + return fmt.Sprintf("%s%s?%s=%s&%s=%s&%s=%s&%s=%s&%s=%s&%s=%s", + externalLink(origin), EndpointInitUser, + queryInitUserUserID, userID, + queryInitUserLoginName, "{{.LoginName}}", + queryInitUserCode, "{{.Code}}", + queryOrgID, orgID, + queryInitUserPassword, "{{.PasswordSet}}", + QueryAuthRequestID, authRequestID) +} + func (l *Login) handleInitUser(w http.ResponseWriter, r *http.Request) { authReq := l.checkOptionalAuthRequestOfEmailLinks(r) userID := r.FormValue(queryInitUserUserID) diff --git a/internal/api/ui/login/invite_user_handler.go b/internal/api/ui/login/invite_user_handler.go index 3141af2d78..e083277c93 100644 --- a/internal/api/ui/login/invite_user_handler.go +++ b/internal/api/ui/login/invite_user_handler.go @@ -1,6 +1,7 @@ package login import ( + "fmt" "net/http" "net/url" @@ -50,6 +51,16 @@ func InviteUserLink(origin, userID, loginName, code, orgID string, authRequestID return externalLink(origin) + EndpointInviteUser + "?" + v.Encode() } +func InviteUserLinkTemplate(origin, userID, orgID string, authRequestID string) string { + return fmt.Sprintf("%s%s?%s=%s&%s=%s&%s=%s&%s=%s&%s=%s", + externalLink(origin), EndpointInviteUser, + queryInviteUserUserID, userID, + queryInviteUserLoginName, "{{.LoginName}}", + queryInviteUserCode, "{{.Code}}", + queryOrgID, orgID, + QueryAuthRequestID, authRequestID) +} + func (l *Login) handleInviteUser(w http.ResponseWriter, r *http.Request) { authReq := l.checkOptionalAuthRequestOfEmailLinks(r) userID := r.FormValue(queryInviteUserUserID) diff --git a/internal/api/ui/login/mail_verify_handler.go b/internal/api/ui/login/mail_verify_handler.go index be13663529..864ff76dd2 100644 --- a/internal/api/ui/login/mail_verify_handler.go +++ b/internal/api/ui/login/mail_verify_handler.go @@ -2,6 +2,7 @@ package login import ( "context" + "fmt" "net/http" "net/url" "slices" @@ -52,6 +53,15 @@ func MailVerificationLink(origin, userID, code, orgID, authRequestID string) str return externalLink(origin) + EndpointMailVerification + "?" + v.Encode() } +func MailVerificationLinkTemplate(origin, userID, orgID, authRequestID string) string { + return fmt.Sprintf("%s%s?%s=%s&%s=%s&%s=%s&%s=%s", + externalLink(origin), EndpointMailVerification, + queryUserID, userID, + queryCode, "{{.Code}}", + queryOrgID, orgID, + QueryAuthRequestID, authRequestID) +} + func (l *Login) handleMailVerification(w http.ResponseWriter, r *http.Request) { authReq := l.checkOptionalAuthRequestOfEmailLinks(r) userID := r.FormValue(queryUserID) diff --git a/internal/api/ui/login/mfa_verify_otp_handler.go b/internal/api/ui/login/mfa_verify_otp_handler.go index b39605e667..fb77bbcba9 100644 --- a/internal/api/ui/login/mfa_verify_otp_handler.go +++ b/internal/api/ui/login/mfa_verify_otp_handler.go @@ -31,6 +31,10 @@ func OTPLink(origin, authRequestID, code string, provider domain.MFAType) string return fmt.Sprintf("%s%s?%s=%s&%s=%s&%s=%d", externalLink(origin), EndpointMFAOTPVerify, QueryAuthRequestID, authRequestID, queryCode, code, querySelectedProvider, provider) } +func OTPLinkTemplate(origin, authRequestID string, provider domain.MFAType) string { + return fmt.Sprintf("%s%s?%s=%s&%s=%s&%s=%d", externalLink(origin), EndpointMFAOTPVerify, QueryAuthRequestID, authRequestID, queryCode, "{{.Code}}", querySelectedProvider, provider) +} + // renderOTPVerification renders the OTP verification for SMS and Email based on the passed MFAType. // It will send a new code to either phone or email first. func (l *Login) handleOTPVerification(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, providers []domain.MFAType, selectedProvider domain.MFAType, err error) { diff --git a/internal/api/ui/login/static/i18n/bg.yaml b/internal/api/ui/login/static/i18n/bg.yaml index f89a672b97..ad308b859d 100644 --- a/internal/api/ui/login/static/i18n/bg.yaml +++ b/internal/api/ui/login/static/i18n/bg.yaml @@ -260,6 +260,7 @@ RegistrationUser: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด GenderLabel: ะŸะพะป Female: ะ–ะตะฝัะบะธ ะฟะพะป Male: ะœัŠะถะบะธ @@ -301,6 +302,7 @@ ExternalRegistrationUserOverview: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด TosAndPrivacyLabel: ะŸั€ะฐะฒะธะปะฐ ะธ ัƒัะปะพะฒะธั TosConfirm: ะŸั€ะธะตะผะฐะผ TosLinkText: TOS @@ -371,6 +373,7 @@ ExternalNotFound: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด DeviceAuth: Title: ะฃะฟัŠะปะฝะพะผะพั‰ะฐะฒะฐะฝะต ะฝะฐ ัƒัั‚ั€ะพะนัั‚ะฒะพั‚ะพ UserCode: diff --git a/internal/api/ui/login/static/i18n/cs.yaml b/internal/api/ui/login/static/i18n/cs.yaml index 65d70719f6..032302e3b8 100644 --- a/internal/api/ui/login/static/i18n/cs.yaml +++ b/internal/api/ui/login/static/i18n/cs.yaml @@ -264,6 +264,7 @@ RegistrationUser: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด GenderLabel: Pohlavรญ Female: ลฝena Male: Muลพ @@ -306,6 +307,7 @@ ExternalRegistrationUserOverview: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด TosAndPrivacyLabel: Obchodnรญ podmรญnky TosConfirm: Souhlasรญm s TosLinkText: obchodnรญmi podmรญnkami @@ -382,6 +384,7 @@ ExternalNotFound: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด DeviceAuth: Title: Autorizace zaล™รญzenรญ UserCode: diff --git a/internal/api/ui/login/static/i18n/de.yaml b/internal/api/ui/login/static/i18n/de.yaml index d6b1d86d5e..edbeb652ce 100644 --- a/internal/api/ui/login/static/i18n/de.yaml +++ b/internal/api/ui/login/static/i18n/de.yaml @@ -263,6 +263,7 @@ RegistrationUser: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด GenderLabel: Geschlecht Female: weiblich Male: mรคnnlich @@ -305,6 +306,7 @@ ExternalRegistrationUserOverview: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด TosAndPrivacyLabel: Allgemeine Geschรคftsbedingungen und Datenschutz TosConfirm: Ich akzeptiere die TosLinkText: AGB @@ -381,6 +383,7 @@ ExternalNotFound: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด DeviceAuth: Title: Gerรคt verbinden UserCode: diff --git a/internal/api/ui/login/static/i18n/en.yaml b/internal/api/ui/login/static/i18n/en.yaml index 098a42f4ca..6c58b11257 100644 --- a/internal/api/ui/login/static/i18n/en.yaml +++ b/internal/api/ui/login/static/i18n/en.yaml @@ -264,6 +264,7 @@ RegistrationUser: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด GenderLabel: Gender Female: Female Male: Male @@ -306,6 +307,7 @@ ExternalRegistrationUserOverview: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด TosAndPrivacyLabel: Terms and conditions TosConfirm: I accept the TosLinkText: TOS @@ -382,6 +384,7 @@ ExternalNotFound: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด DeviceAuth: Title: Device Authorization UserCode: diff --git a/internal/api/ui/login/static/i18n/es.yaml b/internal/api/ui/login/static/i18n/es.yaml index 504f2b944b..de57fdcd85 100644 --- a/internal/api/ui/login/static/i18n/es.yaml +++ b/internal/api/ui/login/static/i18n/es.yaml @@ -264,6 +264,7 @@ RegistrationUser: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด GenderLabel: Gรฉnero Female: Mujer Male: Hombre @@ -306,6 +307,7 @@ ExternalRegistrationUserOverview: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด TosAndPrivacyLabel: Tรฉrminos y condiciones TosConfirm: Acepto los TosLinkText: TDS @@ -382,6 +384,7 @@ ExternalNotFound: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด Footer: PoweredBy: Powered By diff --git a/internal/api/ui/login/static/i18n/fr.yaml b/internal/api/ui/login/static/i18n/fr.yaml index e79a95ccc6..8534085ae9 100644 --- a/internal/api/ui/login/static/i18n/fr.yaml +++ b/internal/api/ui/login/static/i18n/fr.yaml @@ -264,6 +264,7 @@ RegistrationUser: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด GenderLabel: Genre Female: Femme Male: Homme @@ -306,6 +307,7 @@ ExternalRegistrationUserOverview: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด TosAndPrivacyLabel: Termes et conditions TosConfirm: J'accepte les TosLinkText: TOS @@ -382,6 +384,7 @@ ExternalNotFound: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด DeviceAuth: Title: Autorisation de l'appareil diff --git a/internal/api/ui/login/static/i18n/hu.yaml b/internal/api/ui/login/static/i18n/hu.yaml index f10829b493..80ed98945c 100644 --- a/internal/api/ui/login/static/i18n/hu.yaml +++ b/internal/api/ui/login/static/i18n/hu.yaml @@ -234,6 +234,7 @@ RegistrationUser: Swedish: Svรฉd Indonesian: Indonรฉz Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด GenderLabel: Nem Female: Nล‘ Male: Fรฉrfi @@ -275,6 +276,7 @@ ExternalRegistrationUserOverview: Swedish: Svรฉd Indonesian: Indonรฉz Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด TosAndPrivacyLabel: Felhasznรกlรกsi feltรฉtelek TosConfirm: Elfogadom a TosLinkText: TOS @@ -345,6 +347,7 @@ ExternalNotFound: Swedish: Svรฉd Indonesian: Indonรฉz Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด DeviceAuth: Title: Eszkรถz engedรฉlyezรฉse UserCode: diff --git a/internal/api/ui/login/static/i18n/id.yaml b/internal/api/ui/login/static/i18n/id.yaml index a8bf57f467..63deb41229 100644 --- a/internal/api/ui/login/static/i18n/id.yaml +++ b/internal/api/ui/login/static/i18n/id.yaml @@ -234,6 +234,7 @@ RegistrationUser: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด GenderLabel: Jenis kelamin Female: Perempuan Male: Pria @@ -274,6 +275,7 @@ ExternalRegistrationUserOverview: Dutch: Nederlands Swedish: Svenska Indonesian: Bahasa Indonesia + Korean: ํ•œ๊ตญ์–ด TosAndPrivacyLabel: Syarat dan Ketentuan TosConfirm: Saya menerima itu TosLinkText: KL @@ -344,6 +346,7 @@ ExternalNotFound: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด DeviceAuth: Title: Otorisasi Perangkat UserCode: diff --git a/internal/api/ui/login/static/i18n/it.yaml b/internal/api/ui/login/static/i18n/it.yaml index 62b49e8bca..46e74d3b13 100644 --- a/internal/api/ui/login/static/i18n/it.yaml +++ b/internal/api/ui/login/static/i18n/it.yaml @@ -264,6 +264,7 @@ RegistrationUser: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด GenderLabel: Genere Female: Femminile Male: Maschile @@ -305,6 +306,7 @@ ExternalRegistrationUserOverview: Dutch: Nederlands Swedish: Svenska Indonesian: Bahasa Indonesia + Korean: ํ•œ๊ตญ์–ด TosAndPrivacyLabel: Termini di servizio TosConfirm: Accetto i TosLinkText: Termini di servizio @@ -381,6 +383,7 @@ ExternalNotFound: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด DeviceAuth: Title: Autorizzazione del dispositivo diff --git a/internal/api/ui/login/static/i18n/ja.yaml b/internal/api/ui/login/static/i18n/ja.yaml index 68899b5c65..9ec99eb912 100644 --- a/internal/api/ui/login/static/i18n/ja.yaml +++ b/internal/api/ui/login/static/i18n/ja.yaml @@ -256,6 +256,7 @@ RegistrationUser: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด GenderLabel: ๆ€งๅˆฅ Female: ๅฅณๆ€ง Male: ็”ทๆ€ง @@ -298,6 +299,7 @@ ExternalRegistrationUserOverview: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด TosAndPrivacyLabel: ๅˆฉ็”จ่ฆ็ด„ TosConfirm: ็งใฏๅˆฉ็”จ่ฆ็ด„ใ‚’ๆ‰ฟ่ซพใ—ใพใ™ใ€‚ TosLinkText: TOS @@ -374,6 +376,7 @@ ExternalNotFound: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด DeviceAuth: Title: ใƒ‡ใƒใ‚คใ‚น่ช่จผ diff --git a/internal/api/ui/login/static/i18n/ko.yaml b/internal/api/ui/login/static/i18n/ko.yaml new file mode 100644 index 0000000000..e62cfcb8b5 --- /dev/null +++ b/internal/api/ui/login/static/i18n/ko.yaml @@ -0,0 +1,521 @@ +Login: + Title: ๋‹ค์‹œ ์˜ค์‹  ๊ฒƒ์„ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค! + Description: ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”. + TitleLinking: ์‚ฌ์šฉ์ž ์—ฐ๊ฒฐ์„ ์œ„ํ•œ ๋กœ๊ทธ์ธ + DescriptionLinking: ์™ธ๋ถ€ ์‚ฌ์šฉ์ž๋ฅผ ์—ฐ๊ฒฐํ•˜๋ ค๋ฉด ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”. + LoginNameLabel: ๋กœ๊ทธ์ธ ์ด๋ฆ„ + UsernamePlaceHolder: ์‚ฌ์šฉ์ž ์ด๋ฆ„ + LoginnamePlaceHolder: username@domain + ExternalUserDescription: ์™ธ๋ถ€ ์‚ฌ์šฉ์ž๋กœ ๋กœ๊ทธ์ธํ•˜์„ธ์š”. + MustBeMemberOfOrg: ์‚ฌ์šฉ์ž๋Š” {{.OrgName}} ์กฐ์ง์˜ ๋ฉค๋ฒ„์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. + RegisterButtonText: ๋“ฑ๋ก + NextButtonText: ๋‹ค์Œ + +LDAP: + Title: ๋กœ๊ทธ์ธ + Description: ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”. + LoginNameLabel: ๋กœ๊ทธ์ธ ์ด๋ฆ„ + PasswordLabel: ๋น„๋ฐ€๋ฒˆํ˜ธ + NextButtonText: ๋‹ค์Œ + +SelectAccount: + Title: ๊ณ„์ • ์„ ํƒ + Description: ๊ณ„์ •์„ ์‚ฌ์šฉํ•˜์„ธ์š” + TitleLinking: ์‚ฌ์šฉ์ž ์—ฐ๊ฒฐ์„ ์œ„ํ•œ ๊ณ„์ • ์„ ํƒ + DescriptionLinking: ์™ธ๋ถ€ ์‚ฌ์šฉ์ž์™€ ์—ฐ๊ฒฐํ•  ๊ณ„์ •์„ ์„ ํƒํ•˜์„ธ์š”. + OtherUser: ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž + SessionState0: ํ™œ์„ฑ + SessionState1: ๋กœ๊ทธ์•„์›ƒ๋จ + MustBeMemberOfOrg: ์‚ฌ์šฉ์ž๋Š” {{.OrgName}} ์กฐ์ง์˜ ๋ฉค๋ฒ„์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +Password: + Title: ๋น„๋ฐ€๋ฒˆํ˜ธ + Description: ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”. + PasswordLabel: ๋น„๋ฐ€๋ฒˆํ˜ธ + MinLength: ์ตœ์†Œ ๊ธธ์ด๋Š” + MinLengthp2: ์ž ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + MaxLength: 70์ž ๋ฏธ๋งŒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + HasUppercase: ๋Œ€๋ฌธ์ž๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + HasLowercase: ์†Œ๋ฌธ์ž๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + HasNumber: ์ˆซ์ž๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + HasSymbol: ๊ธฐํ˜ธ๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + Confirmation: ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค. + ResetLinkText: ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • + BackButtonText: ๋’ค๋กœ + NextButtonText: ๋‹ค์Œ + +UsernameChange: + Title: ์‚ฌ์šฉ์ž ์ด๋ฆ„ ๋ณ€๊ฒฝ + Description: ์ƒˆ ์‚ฌ์šฉ์ž ์ด๋ฆ„์„ ์„ค์ •ํ•˜์„ธ์š” + UsernameLabel: ์‚ฌ์šฉ์ž ์ด๋ฆ„ + CancelButtonText: ์ทจ์†Œ + NextButtonText: ๋‹ค์Œ + +UsernameChangeDone: + Title: ์‚ฌ์šฉ์ž ์ด๋ฆ„ ๋ณ€๊ฒฝ ์™„๋ฃŒ + Description: ์‚ฌ์šฉ์ž ์ด๋ฆ„์ด ์„ฑ๊ณต์ ์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + NextButtonText: ๋‹ค์Œ + +InitPassword: + Title: ๋น„๋ฐ€๋ฒˆํ˜ธ ์„ค์ • + Description: ์•„๋ž˜ ์–‘์‹์— ์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด ๋ฐ›์€ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”. + CodeLabel: ์ฝ”๋“œ + NewPasswordLabel: ์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ + NewPasswordConfirmLabel: ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ + ResendButtonText: ์ฝ”๋“œ ์žฌ์ „์†ก + NextButtonText: ๋‹ค์Œ + +InitPasswordDone: + Title: ๋น„๋ฐ€๋ฒˆํ˜ธ ์„ค์ • ์™„๋ฃŒ + Description: ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + NextButtonText: ๋‹ค์Œ + CancelButtonText: ์ทจ์†Œ + +InitUser: + Title: ์‚ฌ์šฉ์ž ํ™œ์„ฑํ™” + Description: ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ์ด๋ฉ”์ผ์„ ์ธ์ฆํ•˜๊ณ  ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์„ค์ •ํ•˜์„ธ์š”. + CodeLabel: ์ฝ”๋“œ + NewPasswordLabel: ์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ + NewPasswordConfirm: ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ + NextButtonText: ๋‹ค์Œ + ResendButtonText: ์ฝ”๋“œ ์žฌ์ „์†ก + +InitUserDone: + Title: ์‚ฌ์šฉ์ž ํ™œ์„ฑํ™” ์™„๋ฃŒ + Description: ์ด๋ฉ”์ผ์ด ์ธ์ฆ๋˜์—ˆ์œผ๋ฉฐ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + NextButtonText: ๋‹ค์Œ + CancelButtonText: ์ทจ์†Œ + +InviteUser: + Title: ์‚ฌ์šฉ์ž ํ™œ์„ฑํ™” + Description: ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ์ด๋ฉ”์ผ์„ ์ธ์ฆํ•˜๊ณ  ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์„ค์ •ํ•˜์„ธ์š”. + CodeLabel: ์ฝ”๋“œ + NewPasswordLabel: ์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ + NewPasswordConfirm: ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ + NextButtonText: ๋‹ค์Œ + ResendButtonText: ์ฝ”๋“œ ์žฌ์ „์†ก + +InitMFAPrompt: + Title: 2๋‹จ๊ณ„ ์ธ์ฆ ์„ค์ • + Description: 2๋‹จ๊ณ„ ์ธ์ฆ์€ ์‚ฌ์šฉ์ž ๊ณ„์ •์— ์ถ”๊ฐ€ ๋ณด์•ˆ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๊ณ„์ • ์ ‘๊ทผ์ด ๋ณธ์ธ์—๊ฒŒ๋งŒ ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค. + Provider0: "์ธ์ฆ ์•ฑ (์˜ˆ: Google/Microsoft Authenticator, Authy)" + Provider1: "์žฅ์น˜ ์ข…์† (์˜ˆ: FaceID, Windows Hello, ์ง€๋ฌธ)" + Provider3: OTP SMS + Provider4: OTP ์ด๋ฉ”์ผ + NextButtonText: ๋‹ค์Œ + SkipButtonText: ๊ฑด๋„ˆ๋›ฐ๊ธฐ + +InitMFAOTP: + Title: 2๋‹จ๊ณ„ ์ธ์ฆ + Description: 2๋‹จ๊ณ„ ์ธ์ฆ์„ ์„ค์ •ํ•˜์„ธ์š”. ์ธ์ฆ ์•ฑ์ด ์—†์œผ๋ฉด ๋‹ค์šด๋กœ๋“œํ•˜์„ธ์š”. + OTPDescription: "์ธ์ฆ ์•ฑ์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์Šค์บ”ํ•˜๊ฑฐ๋‚˜ ๋น„๋ฐ€์„ ๋ณต์‚ฌํ•˜์—ฌ ์•„๋ž˜์— ์ƒ์„ฑ๋œ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š” (์˜ˆ: Google/Microsoft Authenticator, Authy)." + SecretLabel: ๋น„๋ฐ€ + CodeLabel: ์ฝ”๋“œ + NextButtonText: ๋‹ค์Œ + CancelButtonText: ์ทจ์†Œ + +InitMFAOTPSMS: + Title: 2๋‹จ๊ณ„ ์ธ์ฆ + DescriptionPhone: 2๋‹จ๊ณ„ ์ธ์ฆ์„ ์„ค์ •ํ•˜์„ธ์š”. ์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์—ฌ ์ธ์ฆํ•˜์„ธ์š”. + DescriptionCode: 2๋‹จ๊ณ„ ์ธ์ฆ์„ ์„ค์ •ํ•˜์„ธ์š”. ๋ฐ›์€ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜์—ฌ ์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ์ธ์ฆํ•˜์„ธ์š”. + PhoneLabel: ์ „ํ™”๋ฒˆํ˜ธ + CodeLabel: ์ฝ”๋“œ + EditButtonText: ์ˆ˜์ • + ResendButtonText: ์ฝ”๋“œ ์žฌ์ „์†ก + NextButtonText: ๋‹ค์Œ + +InitMFAU2F: + Title: ๋ณด์•ˆ ํ‚ค ์ถ”๊ฐ€ + Description: ๋ณด์•ˆ ํ‚ค๋Š” ํœด๋Œ€ํฐ์— ๋‚ด์žฅ๋˜๊ฑฐ๋‚˜, ๋ธ”๋ฃจํˆฌ์Šค ๋˜๋Š” ์ปดํ“จํ„ฐ USB ํฌํŠธ์— ์ง์ ‘ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ์ธ์ฆ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. + TokenNameLabel: ๋ณด์•ˆ ํ‚ค/์žฅ์น˜ ์ด๋ฆ„ + NotSupported: "WebAuthN์ด ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ตœ์‹  ์ƒํƒœ์ธ์ง€ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š” (์˜ˆ: Chrome, Safari, Firefox)." + RegisterTokenButtonText: ๋ณด์•ˆ ํ‚ค ์ถ”๊ฐ€ + ErrorRetry: ๋‹ค์‹œ ์‹œ๋„, ์ƒˆ ์ฑŒ๋ฆฐ์ง€ ์ƒ์„ฑ ๋˜๋Š” ๋‹ค๋ฅธ ๋ฐฉ๋ฒ• ์„ ํƒ. + +InitMFADone: + Title: 2๋‹จ๊ณ„ ์ธ์ฆ ์™„๋ฃŒ + Description: ์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค! 2๋‹จ๊ณ„ ์ธ์ฆ์„ ์„ฑ๊ณต์ ์œผ๋กœ ์„ค์ •ํ•˜์—ฌ ๊ณ„์ •์„ ๋”์šฑ ์•ˆ์ „ํ•˜๊ฒŒ ๋ณดํ˜ธํ–ˆ์Šต๋‹ˆ๋‹ค. ๋กœ๊ทธ์ธ ์‹œ๋งˆ๋‹ค ์ด ์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. + NextButtonText: ๋‹ค์Œ + CancelButtonText: ์ทจ์†Œ + +MFAProvider: + Provider0: "์ธ์ฆ ์•ฑ (์˜ˆ: Google/Microsoft Authenticator, Authy)" + Provider1: "์žฅ์น˜ ์ข…์† (์˜ˆ: FaceID, Windows Hello, ์ง€๋ฌธ)" + Provider3: OTP SMS + Provider4: OTP ์ด๋ฉ”์ผ + ChooseOther: ๋‹ค๋ฅธ ์˜ต์…˜ ์„ ํƒ + +VerifyMFAOTP: + Title: 2๋‹จ๊ณ„ ์ธ์ฆ ํ™•์ธ + Description: 2๋‹จ๊ณ„ ์ธ์ฆ์„ ํ™•์ธํ•˜์„ธ์š” + CodeLabel: ์ฝ”๋“œ + NextButtonText: ๋‹ค์Œ + +VerifyOTP: + Title: 2๋‹จ๊ณ„ ์ธ์ฆ ํ™•์ธ + Description: 2๋‹จ๊ณ„ ์ธ์ฆ์„ ํ™•์ธํ•˜์„ธ์š” + CodeLabel: ์ฝ”๋“œ + ResendButtonText: ์ฝ”๋“œ ์žฌ์ „์†ก + NextButtonText: ๋‹ค์Œ + +VerifyMFAU2F: + Title: 2๋‹จ๊ณ„ ์ธ์ฆ + Description: "๋“ฑ๋ก๋œ ์žฅ์น˜๋กœ 2๋‹จ๊ณ„ ์ธ์ฆ์„ ์ง„ํ–‰ํ•˜์„ธ์š” (์˜ˆ: FaceID, Windows Hello, ์ง€๋ฌธ)" + NotSupported: "WebAuthN์ด ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ตœ์‹  ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์ง€์›๋˜๋Š” ๋‹ค๋ฅธ ๋ธŒ๋ผ์šฐ์ €๋กœ ๋ณ€๊ฒฝํ•˜์„ธ์š” (์˜ˆ: Chrome, Safari, Firefox)." + ErrorRetry: ๋‹ค์‹œ ์‹œ๋„, ์ƒˆ ์š”์ฒญ ์ƒ์„ฑ ๋˜๋Š” ๋‹ค๋ฅธ ๋ฐฉ๋ฒ• ์„ ํƒ. + ValidateTokenButtonText: 2๋‹จ๊ณ„ ์ธ์ฆ + +Passwordless: + Title: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†์ด ๋กœ๊ทธ์ธ + Description: "FaceID, Windows Hello, ์ง€๋ฌธ๊ณผ ๊ฐ™์€ ์žฅ์น˜์—์„œ ์ œ๊ณตํ•˜๋Š” ์ธ์ฆ ๋ฐฉ๋ฒ•์œผ๋กœ ๋กœ๊ทธ์ธํ•˜์„ธ์š”." + NotSupported: "WebAuthN์ด ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ตœ์‹  ์ƒํƒœ์ธ์ง€ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š” (์˜ˆ: Chrome, Safari, Firefox)." + ErrorRetry: ๋‹ค์‹œ ์‹œ๋„, ์ƒˆ ์ฑŒ๋ฆฐ์ง€ ์ƒ์„ฑ ๋˜๋Š” ๋‹ค๋ฅธ ๋ฐฉ๋ฒ• ์„ ํƒ. + LoginWithPwButtonText: ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋กœ๊ทธ์ธ + ValidateTokenButtonText: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†์ด ๋กœ๊ทธ์ธ + +PasswordlessPrompt: + Title: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ ์„ค์ • + Description: "๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ์„ ์„ค์ •ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? (FaceID, Windows Hello, ์ง€๋ฌธ๊ณผ ๊ฐ™์€ ์žฅ์น˜ ์ธ์ฆ ๋ฐฉ๋ฒ•)" + DescriptionInit: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ์„ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๊ธฐ ๋“ฑ๋ก์„ ์œ„ํ•ด ์ œ๊ณต๋œ ๋งํฌ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. + PasswordlessButtonText: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†์ด ์‚ฌ์šฉ + NextButtonText: ๋‹ค์Œ + SkipButtonText: ๊ฑด๋„ˆ๋›ฐ๊ธฐ + +PasswordlessRegistration: + Title: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ ์„ค์ • + Description: "์žฅ์น˜ ์ด๋ฆ„์„ ์ž…๋ ฅํ•œ ํ›„ ์•„๋ž˜์˜ '๋น„๋ฐ€๋ฒˆํ˜ธ ์—†์ด ๋“ฑ๋ก' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์—ฌ ์ธ์ฆ์„ ์ถ”๊ฐ€ํ•˜์„ธ์š” (์˜ˆ: ๋‚ด ํœด๋Œ€ํฐ, MacBook ๋“ฑ)." + TokenNameLabel: ์žฅ์น˜ ์ด๋ฆ„ + NotSupported: "WebAuthN์ด ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ตœ์‹  ์ƒํƒœ์ธ์ง€ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š” (์˜ˆ: Chrome, Safari, Firefox)." + RegisterTokenButtonText: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†์ด ๋“ฑ๋ก + ErrorRetry: ๋‹ค์‹œ ์‹œ๋„, ์ƒˆ ์ฑŒ๋ฆฐ์ง€ ์ƒ์„ฑ ๋˜๋Š” ๋‹ค๋ฅธ ๋ฐฉ๋ฒ• ์„ ํƒ. + +PasswordlessRegistrationDone: + Title: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ ์„ค์ • ์™„๋ฃŒ + Description: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์žฅ์น˜๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + DescriptionClose: ์ด์ œ ์ด ์ฐฝ์„ ๋‹ซ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + NextButtonText: ๋‹ค์Œ + CancelButtonText: ์ทจ์†Œ + +PasswordChange: + Title: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ + Description: ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ณ€๊ฒฝํ•˜์„ธ์š”. ๊ธฐ์กด ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”. + ExpiredDescription: ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋งŒ๋ฃŒ๋˜์–ด ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ์กด ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”. + OldPasswordLabel: ๊ธฐ์กด ๋น„๋ฐ€๋ฒˆํ˜ธ + NewPasswordLabel: ์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ + NewPasswordConfirmLabel: ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ + CancelButtonText: ์ทจ์†Œ + NextButtonText: ๋‹ค์Œ + Footer: ํ‘ธํ„ฐ + +PasswordChangeDone: + Title: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์™„๋ฃŒ + Description: ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + NextButtonText: ๋‹ค์Œ + +PasswordResetDone: + Title: ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ๋งํฌ ๋ฐœ์†ก๋จ + Description: ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์žฌ์„ค์ •ํ•˜๋ ค๋ฉด ์ด๋ฉ”์ผ์„ ํ™•์ธํ•˜์„ธ์š”. + NextButtonText: ๋‹ค์Œ + +EmailVerification: + Title: ์ด๋ฉ”์ผ ์ธ์ฆ + Description: ์ด๋ฉ”์ผ ์ธ์ฆ์„ ์œ„ํ•ด ์ „์†ก๋œ ์ฝ”๋“œ๋ฅผ ์•„๋ž˜ ์–‘์‹์— ์ž…๋ ฅํ•˜์„ธ์š”. + CodeLabel: ์ฝ”๋“œ + NextButtonText: ๋‹ค์Œ + ResendButtonText: ์ฝ”๋“œ ์žฌ์ „์†ก + +EmailVerificationDone: + Title: ์ด๋ฉ”์ผ ์ธ์ฆ ์™„๋ฃŒ + Description: ์ด๋ฉ”์ผ ์ฃผ์†Œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ธ์ฆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + NextButtonText: ๋‹ค์Œ + CancelButtonText: ์ทจ์†Œ + LoginButtonText: ๋กœ๊ทธ์ธ + +RegisterOption: + Title: ๋“ฑ๋ก ์˜ต์…˜ + Description: ๋“ฑ๋ก ๋ฐฉ๋ฒ•์„ ์„ ํƒํ•˜์„ธ์š” + RegisterUsernamePasswordButtonText: ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋“ฑ๋ก + ExternalLoginDescription: ๋˜๋Š” ์™ธ๋ถ€ ์‚ฌ์šฉ์ž๋กœ ๋“ฑ๋ก + LoginButtonText: ๋กœ๊ทธ์ธ + +RegistrationUser: + Title: ๋“ฑ๋ก + Description: ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”. ์ด๋ฉ”์ผ ์ฃผ์†Œ๋Š” ๋กœ๊ทธ์ธ ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + DescriptionOrgRegister: ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”. + EmailLabel: ์ด๋ฉ”์ผ + UsernameLabel: ์‚ฌ์šฉ์ž ์ด๋ฆ„ + FirstnameLabel: ์ด๋ฆ„ + LastnameLabel: ์„ฑ + LanguageLabel: ์–ธ์–ด + German: Deutsch + English: English + Italian: Italiano + French: Franรงais + Chinese: ็ฎ€ไฝ“ไธญๆ–‡ + Polish: Polski + Japanese: ๆ—ฅๆœฌ่ชž + Spanish: Espaรฑol + Bulgarian: ะ‘ัŠะปะณะฐั€ัะบะธ + Portuguese: Portuguรชs + Macedonian: ะœะฐะบะตะดะพะฝัะบะธ + Czech: ฤŒeลกtina + Russian: ะ ัƒััะบะธะน + Dutch: Nederlands + Swedish: Svenska + Indonesian: Bahasa Indonesia + Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด + GenderLabel: ์„ฑ๋ณ„ + Female: ์—ฌ์„ฑ + Male: ๋‚จ์„ฑ + Diverse: ๊ธฐํƒ€ / X + PasswordLabel: ๋น„๋ฐ€๋ฒˆํ˜ธ + PasswordConfirmLabel: ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ + TosAndPrivacyLabel: ๋™์˜์‚ฌํ•ญ + TosConfirm: ์ด์šฉ ์•ฝ๊ด€์— ๋™์˜ํ•ฉ๋‹ˆ๋‹ค. + TosLinkText: ์ด์šฉ ์•ฝ๊ด€ + PrivacyConfirm: ๊ฐœ์ธ์ •๋ณด ์ˆ˜์ง‘ ๋ฐ ์ด์šฉ์— ๋™์˜ํ•ฉ๋‹ˆ๋‹ค. + PrivacyLinkText: ๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ๋ฐฉ์นจ + ExternalLogin: ๋˜๋Š” ์™ธ๋ถ€ ์‚ฌ์šฉ์ž๋กœ ๋“ฑ๋ก + BackButtonText: ๋กœ๊ทธ์ธ + NextButtonText: ๋‹ค์Œ + +ExternalRegistrationUserOverview: + Title: ์™ธ๋ถ€ ์‚ฌ์šฉ์ž ๋“ฑ๋ก + Description: ์„ ํƒํ•œ ์ œ๊ณต์ž์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค. ์ด์ œ ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ์™„์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + EmailLabel: ์ด๋ฉ”์ผ + UsernameLabel: ์‚ฌ์šฉ์ž ์ด๋ฆ„ + FirstnameLabel: ์ด๋ฆ„ + LastnameLabel: ์„ฑ + NicknameLabel: ๋‹‰๋„ค์ž„ + PhoneLabel: ์ „ํ™”๋ฒˆํ˜ธ + LanguageLabel: ์–ธ์–ด + German: Deutsch + English: English + Italian: Italiano + French: Franรงais + Chinese: ็ฎ€ไฝ“ไธญๆ–‡ + Polish: Polski + Japanese: ๆ—ฅๆœฌ่ชž + Spanish: Espaรฑol + Bulgarian: ะ‘ัŠะปะณะฐั€ัะบะธ + Portuguese: Portuguรชs + Macedonian: ะœะฐะบะตะดะพะฝัะบะธ + Czech: ฤŒeลกtina + Russian: ะ ัƒััะบะธะน + Dutch: Nederlands + Swedish: Svenska + Indonesian: Bahasa Indonesia + Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด + TosAndPrivacyLabel: ๋™์˜์‚ฌํ•ญ + TosConfirm: ์ด์šฉ ์•ฝ๊ด€์— ๋™์˜ํ•ฉ๋‹ˆ๋‹ค. + TosLinkText: ์ด์šฉ ์•ฝ๊ด€ + PrivacyConfirm: ๊ฐœ์ธ์ •๋ณด ์ˆ˜์ง‘ ๋ฐ ์ด์šฉ์— ๋™์˜ํ•ฉ๋‹ˆ๋‹ค. + PrivacyLinkText: ๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ๋ฐฉ์นจ + ExternalLogin: ๋˜๋Š” ์™ธ๋ถ€ ์‚ฌ์šฉ์ž๋กœ ๋“ฑ๋ก + BackButtonText: ๋’ค๋กœ + NextButtonText: ์ €์žฅ + +RegistrationOrg: + Title: ์กฐ์ง ๋“ฑ๋ก + Description: ์กฐ์ง ์ด๋ฆ„๊ณผ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”. + OrgNameLabel: ์กฐ์ง ์ด๋ฆ„ + EmailLabel: ์ด๋ฉ”์ผ + UsernameLabel: ์‚ฌ์šฉ์ž ์ด๋ฆ„ + FirstnameLabel: ์ด๋ฆ„ + LastnameLabel: ์„ฑ + PasswordLabel: ๋น„๋ฐ€๋ฒˆํ˜ธ + PasswordConfirmLabel: ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ + TosAndPrivacyLabel: ๋™์˜์‚ฌํ•ญ + TosConfirm: ์ด์šฉ ์•ฝ๊ด€์— ๋™์˜ํ•ฉ๋‹ˆ๋‹ค. + TosLinkText: ์ด์šฉ ์•ฝ๊ด€ + PrivacyConfirm: ๊ฐœ์ธ์ •๋ณด ์ˆ˜์ง‘ ๋ฐ ์ด์šฉ์— ๋™์˜ํ•ฉ๋‹ˆ๋‹ค. + PrivacyLinkText: ๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ๋ฐฉ์นจ + SaveButtonText: ์กฐ์ง ์ƒ์„ฑ + +LoginSuccess: + Title: ๋กœ๊ทธ์ธ ์„ฑ๊ณต + AutoRedirectDescription: ์ž๋™์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ๋ฆฌ๋””๋ ‰์…˜๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์•„๋ž˜ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”. ์ดํ›„ ์ฐฝ์„ ๋‹ซ์•„๋„ ๋ฉ๋‹ˆ๋‹ค. + RedirectedDescription: ์ด์ œ ์ด ์ฐฝ์„ ๋‹ซ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + NextButtonText: ๋‹ค์Œ + +LogoutDone: + Title: ๋กœ๊ทธ์•„์›ƒ ์™„๋ฃŒ + Description: ์„ฑ๊ณต์ ์œผ๋กœ ๋กœ๊ทธ์•„์›ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + LoginButtonText: ๋กœ๊ทธ์ธ + +LinkingUserPrompt: + Title: ๊ธฐ์กด ์‚ฌ์šฉ์ž ๋ฐœ๊ฒฌ + Description: "๊ธฐ์กด ๊ณ„์ •์„ ์—ฐ๊ฒฐํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ:" + LinkButtonText: ์—ฐ๊ฒฐ + OtherButtonText: ๋‹ค๋ฅธ ์˜ต์…˜ + +LinkingUsersDone: + Title: ์‚ฌ์šฉ์ž ์—ฐ๊ฒฐ + Description: ์‚ฌ์šฉ์ž๊ฐ€ ์—ฐ๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + CancelButtonText: ์ทจ์†Œ + NextButtonText: ๋‹ค์Œ + +ExternalNotFound: + Title: ์™ธ๋ถ€ ์‚ฌ์šฉ์ž ์ฐพ์„ ์ˆ˜ ์—†์Œ + Description: ์™ธ๋ถ€ ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ๊ณ„์ •์„ ์—ฐ๊ฒฐํ•˜๊ฑฐ๋‚˜ ์ƒˆ ๊ณ„์ •์„ ์ž๋™ ๋“ฑ๋กํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? + LinkButtonText: ์—ฐ๊ฒฐ + AutoRegisterButtonText: ๋“ฑ๋ก + TosAndPrivacyLabel: ๋™์˜์‚ฌํ•ญ + TosConfirm: ์ด์šฉ ์•ฝ๊ด€์— ๋™์˜ํ•ฉ๋‹ˆ๋‹ค. + TosLinkText: ์ด์šฉ ์•ฝ๊ด€ + PrivacyConfirm: ๊ฐœ์ธ์ •๋ณด ์ˆ˜์ง‘ ๋ฐ ์ด์šฉ์— ๋™์˜ํ•ฉ๋‹ˆ๋‹ค. + PrivacyLinkText: ๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ๋ฐฉ์นจ + German: Deutsch + English: English + Italian: Italiano + French: Franรงais + Chinese: ็ฎ€ไฝ“ไธญๆ–‡ + Polish: Polski + Japanese: ๆ—ฅๆœฌ่ชž + Spanish: Espaรฑol + Bulgarian: ะ‘ัŠะปะณะฐั€ัะบะธ + Portuguese: Portuguรชs + Macedonian: ะœะฐะบะตะดะพะฝัะบะธ + Czech: ฤŒeลกtina + Russian: ะ ัƒััะบะธะน + Dutch: Nederlands + Swedish: Svenska + Indonesian: Bahasa Indonesia + Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด +DeviceAuth: + Title: ๊ธฐ๊ธฐ ์ธ์ฆ + UserCode: + Label: ์‚ฌ์šฉ์ž ์ฝ”๋“œ + Description: ๊ธฐ๊ธฐ์—์„œ ํ‘œ์‹œ๋œ ์‚ฌ์šฉ์ž ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”. + ButtonNext: ๋‹ค์Œ + Action: + Description: ๊ธฐ๊ธฐ ์ ‘๊ทผ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜์„ธ์š”. + GrantDevice: ๊ธฐ๊ธฐ์—๊ฒŒ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค + AccessToScopes: ๋‹ค์Œ ๋ฒ”์œ„์— ์ ‘๊ทผ ๊ถŒํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค + Button: + Allow: ํ—ˆ์šฉ + Deny: ๊ฑฐ๋ถ€ + Done: + Description: ์™„๋ฃŒ. + Approved: ๊ธฐ๊ธฐ ์ธ์ฆ์ด ์Šน์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ๊ธฐ๊ธฐ๋กœ ๋Œ์•„๊ฐ€์„ธ์š”. + Denied: ๊ธฐ๊ธฐ ์ธ์ฆ์ด ๊ฑฐ๋ถ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ๊ธฐ๊ธฐ๋กœ ๋Œ์•„๊ฐ€์„ธ์š”. + +Footer: + PoweredBy: ์ œ๊ณต์ž + Tos: ์ด์šฉ ์•ฝ๊ด€ + PrivacyPolicy: ๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ๋ฐฉ์นจ + Help: ๋„์›€๋ง + SupportEmail: ์ง€์› ์ด๋ฉ”์ผ + +SignIn: "{{.Provider}}๋กœ ๋กœ๊ทธ์ธ" + +Errors: + Internal: ๋‚ด๋ถ€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค + AuthRequest: + NotFound: ์ธ์ฆ ์š”์ฒญ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + UserAgentNotCorresponding: ์‚ฌ์šฉ์ž ์—์ด์ „ํŠธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + UserAgentNotFound: ์‚ฌ์šฉ์ž ์—์ด์ „ํŠธ ID๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + TokenNotFound: ํ† ํฐ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + RequestTypeNotSupported: ์š”์ฒญ ์œ ํ˜•์ด ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + MissingParameters: ํ•„์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค + User: + NotFound: ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + AlreadyExists: ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + Inactive: ์‚ฌ์šฉ์ž๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค + NotFoundOnOrg: ์„ ํƒ๋œ ์กฐ์ง์—์„œ ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotAllowedOrg: ์‚ฌ์šฉ์ž๋Š” ํ•„์ˆ˜ ์กฐ์ง์˜ ๋ฉค๋ฒ„๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค + NotMatchingUserID: ์‚ฌ์šฉ์ž์™€ ์ธ์ฆ ์š”์ฒญ์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + UserIDMissing: ์‚ฌ์šฉ์ž ID๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + Invalid: ์ž˜๋ชป๋œ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค + DomainNotAllowedAsUsername: ๋„๋ฉ”์ธ์ด ์ด๋ฏธ ์˜ˆ์•ฝ๋˜์–ด ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotAllowedToLink: ์™ธ๋ถ€ ๋กœ๊ทธ์ธ ์ œ๊ณต์ž์™€ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Profile: + NotFound: ํ”„๋กœํ•„์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotChanged: ํ”„๋กœํ•„์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + Empty: ํ”„๋กœํ•„์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + FirstNameEmpty: ํ”„๋กœํ•„์— ์ด๋ฆ„์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + LastNameEmpty: ํ”„๋กœํ•„์— ์„ฑ์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + IDMissing: ํ”„๋กœํ•„ ID๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค + Email: + NotFound: ์ด๋ฉ”์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Invalid: ์ž˜๋ชป๋œ ์ด๋ฉ”์ผ์ž…๋‹ˆ๋‹ค + AlreadyVerified: ์ด๋ฉ”์ผ์ด ์ด๋ฏธ ์ธ์ฆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + NotChanged: ์ด๋ฉ”์ผ์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + Empty: ์ด๋ฉ”์ผ์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + IDMissing: ์ด๋ฉ”์ผ ID๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค + Phone: + NotFound: ์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Invalid: ์ž˜๋ชป๋œ ์ „ํ™”๋ฒˆํ˜ธ์ž…๋‹ˆ๋‹ค + AlreadyVerified: ์ „ํ™”๋ฒˆํ˜ธ๊ฐ€ ์ด๋ฏธ ์ธ์ฆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + Empty: ์ „ํ™”๋ฒˆํ˜ธ๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + NotChanged: ์ „ํ™”๋ฒˆํ˜ธ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + Address: + NotFound: ์ฃผ์†Œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotChanged: ์ฃผ์†Œ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + Username: + AlreadyExists: ์‚ฌ์šฉ์ž ์ด๋ฆ„์ด ์ด๋ฏธ ์‚ฌ์šฉ ์ค‘์ž…๋‹ˆ๋‹ค + Reserved: ์‚ฌ์šฉ์ž ์ด๋ฆ„์ด ์ด๋ฏธ ์˜ˆ์•ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + Empty: ์‚ฌ์šฉ์ž ์ด๋ฆ„์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + Password: + ConfirmationWrong: ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ์ด ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Empty: ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + Invalid: ์ž˜๋ชป๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ์ž…๋‹ˆ๋‹ค + InvalidAndLocked: ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ž˜๋ชป๋˜์—ˆ๊ณ  ์‚ฌ์šฉ์ž๊ฐ€ ์ž ๊ฒผ์Šต๋‹ˆ๋‹ค. ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•˜์„ธ์š”. + NotChanged: ์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ํ˜„์žฌ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ๋‹ค๋ฅด๊ฒŒ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค + UsernameOrPassword: + Invalid: ์‚ฌ์šฉ์ž ์ด๋ฆ„ ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค + PasswordComplexityPolicy: + NotFound: ๋น„๋ฐ€๋ฒˆํ˜ธ ์ •์ฑ…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + MinLength: ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋„ˆ๋ฌด ์งง์Šต๋‹ˆ๋‹ค + HasLower: ๋น„๋ฐ€๋ฒˆํ˜ธ์— ์†Œ๋ฌธ์ž๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค + HasUpper: ๋น„๋ฐ€๋ฒˆํ˜ธ์— ๋Œ€๋ฌธ์ž๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค + HasNumber: ๋น„๋ฐ€๋ฒˆํ˜ธ์— ์ˆซ์ž๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค + HasSymbol: ๋น„๋ฐ€๋ฒˆํ˜ธ์— ๊ธฐํ˜ธ๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค + Code: + Expired: ์ฝ”๋“œ๊ฐ€ ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + Invalid: ์ž˜๋ชป๋œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค + Empty: ์ฝ”๋“œ๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + CryptoCodeNil: ์•”ํ˜ธํ™” ์ฝ”๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค + NotFound: ์ฝ”๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + GeneratorAlgNotSupported: ์ง€์›๋˜์ง€ ์•Š๋Š” ์ƒ์„ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ž…๋‹ˆ๋‹ค + EmailVerify: + UserIDEmpty: ์‚ฌ์šฉ์ž ID๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + ExternalData: + CouldNotRead: ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ฝ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + MFA: + NoProviders: ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋‹ค์ค‘ ์ธ์ฆ ์ œ๊ณต์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค + OTP: + AlreadyReady: ๋‹ค์ค‘ ์ธ์ฆ OTP(์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ)๊ฐ€ ์ด๋ฏธ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค + NotExisting: ๋‹ค์ค‘ ์ธ์ฆ OTP(์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ)๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + InvalidCode: ์ž˜๋ชป๋œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค + NotReady: ๋‹ค์ค‘ ์ธ์ฆ OTP(์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ)๊ฐ€ ์ค€๋น„๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + Locked: ์‚ฌ์šฉ์ž๊ฐ€ ์ž ๊ฒผ์Šต๋‹ˆ๋‹ค + SomethingWentWrong: ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค + NotActive: ์‚ฌ์šฉ์ž๊ฐ€ ํ™œ์„ฑ ์ƒํƒœ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค + ExternalIDP: + IDPTypeNotImplemented: IDP ์œ ํ˜•์ด ๊ตฌํ˜„๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + NotAllowed: ์™ธ๋ถ€ ๋กœ๊ทธ์ธ ์ œ๊ณต์ž๊ฐ€ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + IDPConfigIDEmpty: ID ์ œ๊ณต์ž ID๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + ExternalUserIDEmpty: ์™ธ๋ถ€ ์‚ฌ์šฉ์ž ID๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + UserDisplayNameEmpty: ์‚ฌ์šฉ์ž ํ‘œ์‹œ ์ด๋ฆ„์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + NoExternalUserData: ์™ธ๋ถ€ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + CreationNotAllowed: ์ด ์ œ๊ณต์ž์—์„œ๋Š” ์ƒˆ ์‚ฌ์šฉ์ž ์ƒ์„ฑ์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + LinkingNotAllowed: ์ด ์ œ๊ณต์ž์—์„œ๋Š” ์‚ฌ์šฉ์ž๋ฅผ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NoOptionAllowed: ์ด ์ œ๊ณต์ž์—์„œ๋Š” ์ƒ์„ฑ๊ณผ ์—ฐ๊ฒฐ์ด ๋ชจ๋‘ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•˜์„ธ์š”. + GrantRequired: ๋กœ๊ทธ์ธ ๋ถˆ๊ฐ€. ์‚ฌ์šฉ์ž๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ตœ์†Œํ•œ ํ•˜๋‚˜์˜ ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•˜์„ธ์š”. + ProjectRequired: ๋กœ๊ทธ์ธ ๋ถˆ๊ฐ€. ์‚ฌ์šฉ์ž์˜ ์กฐ์ง์ด ํ”„๋กœ์ ํŠธ์— ํ—ˆ๊ฐ€๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•˜์„ธ์š”. + IdentityProvider: + InvalidConfig: ID ์ œ๊ณต์ž ์„ค์ •์ด ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค + IAM: + LockoutPolicy: + NotExisting: ์ž ๊ธˆ ์ •์ฑ…์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Org: + LoginPolicy: + RegistrationNotAllowed: ๋“ฑ๋ก์ด ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + DeviceAuth: + NotExisting: ์‚ฌ์šฉ์ž ์ฝ”๋“œ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + +optional: (์„ ํƒ ์‚ฌํ•ญ) diff --git a/internal/api/ui/login/static/i18n/mk.yaml b/internal/api/ui/login/static/i18n/mk.yaml index 2424701077..dbb988a0a6 100644 --- a/internal/api/ui/login/static/i18n/mk.yaml +++ b/internal/api/ui/login/static/i18n/mk.yaml @@ -264,6 +264,7 @@ RegistrationUser: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด GenderLabel: ะŸะพะป Female: ะ–ะตะฝัะบะธ Male: ะœะฐัˆะบะธ @@ -306,6 +307,7 @@ ExternalRegistrationUserOverview: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด TosAndPrivacyLabel: ะŸั€ะฐะฒะธะปะฐ ะธ ัƒัะปะพะฒะธ TosConfirm: ะกะต ัะพะณะปะฐััƒะฒะฐะผ ัะพ TosLinkText: ะฟั€ะฐะฒะธะปะฐั‚ะฐ ะทะฐ ะบะพั€ะธัั‚ะตัšะต @@ -382,6 +384,7 @@ ExternalNotFound: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด DeviceAuth: Title: ะžะฒะปะฐัั‚ัƒะฒะฐัšะต ะฟั€ะตะบัƒ ัƒั€ะตะด diff --git a/internal/api/ui/login/static/i18n/nl.yaml b/internal/api/ui/login/static/i18n/nl.yaml index 017e1a52d8..3bbcee94b6 100644 --- a/internal/api/ui/login/static/i18n/nl.yaml +++ b/internal/api/ui/login/static/i18n/nl.yaml @@ -264,6 +264,7 @@ RegistrationUser: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด GenderLabel: Geslacht Female: Vrouw Male: Man @@ -306,6 +307,7 @@ ExternalRegistrationUserOverview: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด TosAndPrivacyLabel: Algemene voorwaarden TosConfirm: Ik accepteer de TosLinkText: AV @@ -382,6 +384,7 @@ ExternalNotFound: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด DeviceAuth: Title: Apparaat Autorisatie UserCode: diff --git a/internal/api/ui/login/static/i18n/pl.yaml b/internal/api/ui/login/static/i18n/pl.yaml index 367da1a2cd..2c8b4fddf0 100644 --- a/internal/api/ui/login/static/i18n/pl.yaml +++ b/internal/api/ui/login/static/i18n/pl.yaml @@ -264,6 +264,7 @@ RegistrationUser: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด GenderLabel: Pล‚eฤ‡ Female: Kobieta Male: Mฤ™ลผczyzna @@ -306,6 +307,7 @@ ExternalRegistrationUserOverview: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด TosAndPrivacyLabel: Warunki i zasady TosConfirm: Akceptujฤ™ TosLinkText: Warunki korzystania @@ -382,6 +384,7 @@ ExternalNotFound: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด DeviceAuth: Title: Autoryzacja urzฤ…dzenia diff --git a/internal/api/ui/login/static/i18n/pt.yaml b/internal/api/ui/login/static/i18n/pt.yaml index b0c6cb6c8e..f03f120ed8 100644 --- a/internal/api/ui/login/static/i18n/pt.yaml +++ b/internal/api/ui/login/static/i18n/pt.yaml @@ -260,6 +260,7 @@ RegistrationUser: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด GenderLabel: Gรชnero Female: Feminino Male: Masculino @@ -302,6 +303,7 @@ ExternalRegistrationUserOverview: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด TosAndPrivacyLabel: Termos e condiรงรตes TosConfirm: Eu aceito os TosLinkText: termos de serviรงo @@ -378,6 +380,7 @@ ExternalNotFound: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด DeviceAuth: Title: Autorizaรงรฃo de dispositivo diff --git a/internal/api/ui/login/static/i18n/ru.yaml b/internal/api/ui/login/static/i18n/ru.yaml index 9c1796c59a..03239e0612 100644 --- a/internal/api/ui/login/static/i18n/ru.yaml +++ b/internal/api/ui/login/static/i18n/ru.yaml @@ -1,246 +1,247 @@ Login: Title: ะ”ะพะฑั€ะพ ะฟะพะถะฐะปะพะฒะฐั‚ัŒ! - Description: ะ’ะฒะตะดะธั‚ะต ะฒะฐัˆะธ ะดะฐะฝะฝั‹ะต. + Description: ะ’ะฒะตะดะธั‚ะต ัะฒะพะธ ะดะฐะฝะฝั‹ะต ะดัะป ะฒั…ะพะดะฐ. TitleLinking: ะ’ั…ะพะด ะดะปั ะฟั€ะธะฒัะทะบะธ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะน - DescriptionLinking: ะ’ะฒะตะดะธั‚ะต ะดะฐะฝะฝั‹ะต ะดะปั ะฒั…ะพะดะฐ, ั‡ั‚ะพะฑั‹ ะฟั€ะธะฒัะทะฐั‚ัŒ ะฒะฝะตัˆะฝะตะณะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะบ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŽ ZITADEL. + DescriptionLinking: ะ’ะฒะตะดะธั‚ะต ะดะฐะฝะฝั‹ะต ะดะปั ะฒั…ะพะดะฐ, ั‡ั‚ะพะฑั‹ ะฟั€ะธะฒัะทะฐั‚ัŒ ะฒะฝะตัˆะฝะตะณะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะบ ัƒั‡ั‘ั‚ะฝะพะน ะทะฐะฟะธัะธ ZITADEL. LoginNameLabel: ะ›ะพะณะธะฝ UsernamePlaceHolder: ะปะพะณะธะฝ LoginnamePlaceHolder: username@domain - ExternalUserDescription: ะ’ะพะนั‚ะธ ะฟะพะด ะฒะฝะตัˆะฝะธะผ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะผ. - MustBeMemberOfOrg: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ ัƒั‡ะฐัั‚ะฝะธะบะพะผ ะพั€ะณะฐะฝะธะทะฐั†ะธะธ {{.OrgName}}. - RegisterButtonText: ะทะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐั‚ัŒัั - NextButtonText: ะดะฐะปะตะต + ExternalUserDescription: ะ’ะพะนั‚ะธ ะบะฐะบ ะฒะฝะตัˆะฝะธะน ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒ. + MustBeMemberOfOrg: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ ั‡ะปะตะฝะพะผ ะพั€ะณะฐะฝะธะทะฐั†ะธะธ {{.OrgName}}. + RegisterButtonText: ะ—ะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐั‚ัŒัั + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ LDAP: Title: ะ’ะพะนั‚ะธ - Description: ะ’ะฒะตะดะธั‚ะต ะฒะฐัˆะธ ะดะฐะฝะฝั‹ะต ะดัะป ะฒั…ะพะดะฐ. - LoginNameLabel: ะ˜ะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั + Description: ะ’ะฒะตะดะธั‚ะต ัะฒะพะธ ะดะฐะฝะฝั‹ะต ะดะปั ะฒั…ะพะดะฐ. + LoginNameLabel: ะ›ะพะณะธะฝ PasswordLabel: ะŸะฐั€ะพะปัŒ - NextButtonText: ัะปะตะดัƒัŽั‰ะธะน + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ + SelectAccount: Title: ะ’ั‹ะฑะพั€ ัƒั‡ั‘ั‚ะฝะพะน ะทะฐะฟะธัะธ - Description: ะ’ั‹ะฑะตั€ะธั‚ะต ะฒะฐัˆัƒ ัƒั‡ั‘ั‚ะฝัƒัŽ ะทะฐะฟะธััŒ. + Description: ะ’ั‹ะฑะตั€ะธั‚ะต ัƒั‡ั‘ั‚ะฝัƒัŽ ะทะฐะฟะธััŒ. TitleLinking: ะ’ั‹ะฑะตั€ะธั‚ะต ัƒั‡ั‘ั‚ะฝัƒัŽ ะทะฐะฟะธััŒ ะดะปั ะฟั€ะธะฒัะทะบะธ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั DescriptionLinking: ะ’ั‹ะฑะตั€ะธั‚ะต ัะฒะพัŽ ัƒั‡ั‘ั‚ะฝัƒัŽ ะทะฐะฟะธััŒ ะดะปั ัะฒัะทะธ ั ะฒะฝะตัˆะฝะธะผ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะผ. - OtherUser: ะ”ั€ัƒะณะพะน ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒ + OtherUser: ะ”ั€ัƒะณะฐั ัƒั‡ั‘ั‚ะฝะฐั ะทะฐะฟะธััŒ SessionState0: ะฐะบั‚ะธะฒะฝั‹ะน SessionState1: ะฝะตะฐะบั‚ะธะฒะฝั‹ะน - MustBeMemberOfOrg: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ ัƒั‡ะฐัั‚ะฝะธะบะพะผ ะพั€ะณะฐะฝะธะทะฐั†ะธะธ {{.OrgName}}. + MustBeMemberOfOrg: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ ั‡ะปะตะฝะพะผ ะพั€ะณะฐะฝะธะทะฐั†ะธะธ {{.OrgName}}. Password: Title: ะŸะฐั€ะพะปัŒ - Description: ะ’ะฒะตะดะธั‚ะต ัะฒะพะธ ะดะฐะฝะฝั‹ะต ะดะปั ะฒั…ะพะดะฐ. + Description: ะ’ะฒะตะดะธั‚ะต ะดะฐะฝะฝั‹ะต ะดะปั ะฒั…ะพะดะฐ. PasswordLabel: ะŸะฐั€ะพะปัŒ - MinLength: ะ”ะพะปะถะฝะพ ะฑั‹ั‚ัŒ ะฝะต ะผะตะฝะตะต + MinLength: ะœะธะฝะธะผัƒะผ MinLengthp2: ัะธะผะฒะพะปะพะฒ. - MaxLength: ะ”ะพะปะถะฝะพ ะฑั‹ั‚ัŒ ะผะตะฝัŒัˆะต 70 ัะธะผะฒะพะปะพะฒ. - HasUppercase: ะ”ะพะปะถะฝะพ ัะพะดะตั€ะถะฐั‚ัŒ ะทะฐะณะปะฐะฒะฝัƒัŽ ะฑัƒะบะฒัƒ. - HasLowercase: ะ”ะพะปะถะฝะพ ัะพะดะตั€ะถะฐั‚ัŒ ัั‚ั€ะพั‡ะฝัƒัŽ ะฑัƒะบะฒัƒ. - HasNumber: ะ”ะพะปะถะฝะพ ัะพะดะตั€ะถะฐั‚ัŒ ั‡ะธัะปะพ. - HasSymbol: ะ”ะพะปะถะฝะพ ัะพะดะตั€ะถะฐั‚ัŒ ัะธะผะฒะพะป. - Confirmation: ะŸะพะดั‚ะฒะตั€ะถะดะตะฝะธะต ะฟะฐั€ะพะปั ัะพะฒะฟะฐะดะฐะตั‚. + MaxLength: ะะต ะฑะพะปะตะต 70 ัะธะผะฒะพะปะพะฒ. + HasUppercase: ะ”ะพะปะถะตะฝ ัะพะดะตั€ะถะฐั‚ัŒ ะทะฐะณะปะฐะฒะฝัƒัŽ ะฑัƒะบะฒัƒ. + HasLowercase: ะ”ะพะปะถะตะฝ ัะพะดะตั€ะถะฐั‚ัŒ ัั‚ั€ะพั‡ะฝัƒัŽ ะฑัƒะบะฒัƒ. + HasNumber: ะ”ะพะปะถะตะฝ ัะพะดะตั€ะถะฐั‚ัŒ ั†ะธั„ั€ัƒ. + HasSymbol: ะ”ะพะปะถะตะฝ ัะพะดะตั€ะถะธั‚ัŒ ัะฟะตั†ะธะฐะปัŒะฝั‹ะน ัะธะผะฒะพะป. + Confirmation: ะŸะฐั€ะพะปะธ ะดะพะปะถะฝั‹ ัะพะฒะฟะฐะดะฐั‚ัŒ. ResetLinkText: ะกะฑั€ะพัะธั‚ัŒ ะฟะฐั€ะพะปัŒ BackButtonText: ะะฐะทะฐะด - NextButtonText: ะ’ะฟะตั€ะตะด + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ UsernameChange: Title: ะ˜ะทะผะตะฝะตะฝะธะต ะปะพะณะธะฝะฐ Description: ะฃัั‚ะฐะฝะพะฒะธั‚ะต ะฝะพะฒั‹ะน ะปะพะณะธะฝ. UsernameLabel: ะ›ะพะณะธะฝ - CancelButtonText: ะพั‚ะผะตะฝะฐ - NextButtonText: ะดะฐะปะตะต + CancelButtonText: ะžั‚ะผะตะฝะฐ + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ UsernameChangeDone: Title: ะ›ะพะณะธะฝ ะธะทะผะตะฝั‘ะฝ Description: ะ’ะฐัˆ ะปะพะณะธะฝ ะฑั‹ะป ัƒัะฟะตัˆะฝะพ ะธะทะผะตะฝั‘ะฝ. - NextButtonText: ะดะฐะปะตะต + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ InitPassword: Title: ะฃัั‚ะฐะฝะพะฒะบะฐ ะฟะฐั€ะพะปั Description: ะ’ะฒะตะดะธั‚ะต ะบะพะด ะธะท ะฟะธััŒะผะฐ, ะพั‚ะฟั€ะฐะฒะปะตะฝะฝะพะณะพ ะฝะฐ ะฒะฐัˆัƒ ัะปะตะบั‚ั€ะพะฝะฝัƒัŽ ะฟะพั‡ั‚ัƒ, ั‡ั‚ะพะฑั‹ ัƒัั‚ะฐะฝะพะฒะธั‚ัŒ ะฟะฐั€ะพะปัŒ. - CodeLabel: ะšะพะด + CodeLabel: ะšะพะด ะธะท ะฟะธััŒะผะฐ NewPasswordLabel: ะะพะฒั‹ะน ะฟะฐั€ะพะปัŒ - NewPasswordConfirmLabel: ะŸะพะดั‚ะฒะตั€ะถะดะตะฝะธะต ะฟะฐั€ะพะปั - ResendButtonText: ะฟะพะฒั‚ะพั€ะฝะพ ะพั‚ะฟั€ะฐะฒะธั‚ัŒ ะบะพะด - NextButtonText: ะดะฐะปะตะต + NewPasswordConfirmLabel: ะŸะพะฒั‚ะพั€ะธั‚ะต ะฟะฐั€ะพะปัŒ + ResendButtonText: ะžั‚ะฟั€ะฐะฒะธั‚ัŒ ะบะพะด ะตั‰ั‘ ั€ะฐะท + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ InitPasswordDone: Title: ะŸะฐั€ะพะปัŒ ัƒัั‚ะฐะฝะพะฒะปะตะฝ Description: ะŸะฐั€ะพะปัŒ ัƒัะฟะตัˆะฝะพ ัƒัั‚ะฐะฝะพะฒะปะตะฝ. - NextButtonText: ะดะฐะปะตะต - CancelButtonText: ะพั‚ะผะตะฝะฐ + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ + CancelButtonText: ะžั‚ะผะตะฝะฐ InitUser: - Title: ะะบั‚ะธะฒะฐั†ะธั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั - Description: ะŸะพะดั‚ะฒะตั€ะดะธั‚ะต ะฒะฐัˆัƒ ัะปะตะบั‚ั€ะพะฝะฝัƒัŽ ะฟะพั‡ั‚ัƒ ะบะพะดะพะผ ะธะท ะฟะธััŒะผะฐ ะธ ัƒัั‚ะฐะฝะพะฒะธั‚ะต ะฟะฐั€ะพะปัŒ. - CodeLabel: ะšะพะด + Title: ะะบั‚ะธะฒะฐั†ะธั ัƒั‡ั‘ั‚ะฝะพะน ะทะฐะฟะธัะธ + Description: ะ’ะฒะตะดะธั‚ะต ะบะพะด ะธะท ะฟะธััŒะผะฐ ะดะปั ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะธั ัะปะตะบั‚ั€ะพะฝะฝะพะน ะฟะพั‡ั‚ั‹ ะธ ัƒัั‚ะฐะฝะพะฒะธั‚ะต ะฝะพะฒั‹ะน ะฟะฐั€ะพะปัŒ. + CodeLabel: ะšะพะด ะธะท ะฟะธััŒะผะฐ NewPasswordLabel: ะะพะฒั‹ะน ะฟะฐั€ะพะปัŒ - NewPasswordConfirm: ะŸะพะดั‚ะฒะตั€ะถะดะตะฝะธะต ะฟะฐั€ะพะปั - NextButtonText: ะดะฐะปะตะต - ResendButtonText: ะฟะพะฒั‚ะพั€ะฝะพ ะพั‚ะฟั€ะฐะฒะธั‚ัŒ ะบะพะด + NewPasswordConfirm: ะŸะพะฒั‚ะพั€ะธั‚ะต ะฟะฐั€ะพะปัŒ + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ + ResendButtonText: ะžั‚ะฟั€ะฐะฒะธั‚ัŒ ะบะพะด ะตั‰ั‘ ั€ะฐะท InitUserDone: - Title: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฐะบั‚ะธะฒะธั€ะพะฒะฐะฝ + Title: ะฃั‡ั‘ั‚ะฝะฐั ะทะฐะฟะธััŒ ะฐะบั‚ะธะฒะธั€ะพะฒะฐะฝะฐ Description: ะญะปะตะบั‚ั€ะพะฝะฝะฐั ะฟะพั‡ั‚ะฐ ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะฐ ะธ ะฟะฐั€ะพะปัŒ ัƒัะฟะตัˆะฝะพ ัƒัั‚ะฐะฝะพะฒะปะตะฝ. - NextButtonText: ะดะฐะปะตะต - CancelButtonText: ะพั‚ะผะตะฝะฐ + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ + CancelButtonText: ะžั‚ะผะตะฝะฐ InviteUser: - Title: ะะบั‚ะธะฒะธั€ะพะฒะฐั‚ัŒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั - Description: ะŸั€ะพะฒะตั€ัŒั‚ะต ัะฒะพะน ะฐะดั€ะตั ัะปะตะบั‚ั€ะพะฝะฝะพะน ะฟะพั‡ั‚ั‹ ั ะฟะพะผะพั‰ัŒัŽ ะบะพะดะฐ ะฝะธะถะต ะธ ัƒัั‚ะฐะฝะพะฒะธั‚ะต ัะฒะพะน ะฟะฐั€ะพะปัŒ. - CodeLabel: ะšะพะด + Title: ะะบั‚ะธะฒะฐั†ะธั ัƒั‡ั‘ั‚ะฝะพะน ะทะฐะฟะธัะธ + Description: ะ’ะฒะตะดะธั‚ะต ะบะพะด ะธะท ะฟะธััŒะผะฐ ะดะปั ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะธั ัะปะตะบั‚ั€ะพะฝะฝะพะน ะฟะพั‡ั‚ั‹ ะธ ัƒัั‚ะฐะฝะพะฒะธั‚ะต ะฝะพะฒั‹ะน ะฟะฐั€ะพะปัŒ. + CodeLabel: ะšะพะด ะธะท ะฟะธััŒะผะฐ NewPasswordLabel: ะะพะฒั‹ะน ะฟะฐั€ะพะปัŒ - NewPasswordConfirm: ะŸะพะดั‚ะฒะตั€ะดะธั‚ัŒ ะฟะฐั€ะพะปัŒ - NextButtonText: ะ”ะฐะปะตะต - ResendButtonText: ะžั‚ะฟั€ะฐะฒะธั‚ัŒ ะบะพะด ะฟะพะฒั‚ะพั€ะฝะพ + NewPasswordConfirm: ะŸะพะฒั‚ะพั€ะธั‚ะต ะฟะฐั€ะพะปัŒ + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ + ResendButtonText: ะžั‚ะฟั€ะฐะฒะธั‚ัŒ ะบะพะด ะตั‰ั‘ ั€ะฐะท InitMFAPrompt: Title: ะฃัั‚ะฐะฝะพะฒะบะฐ ะดะฒัƒั…ั„ะฐะบั‚ะพั€ะฝะพะน ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ - Description: ะ”ะฒัƒั…ั„ะฐะบั‚ะพั€ะฝะฐั ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธั ะพะฑะตัะฟะตั‡ะธะฒะฐะตั‚ ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝัƒัŽ ะทะฐั‰ะธั‚ัƒ ะฒะฐัˆะตะน ัƒั‡ั‘ั‚ะฝะพะน ะทะฐะฟะธัะธ. - Provider0: ะงะตั€ะตะท ะฟั€ะธะปะพะถะตะฝะธะต (ะฝะฐะฟั€ะธะผะตั€, Google/Microsoft Authenticator, Authy) - Provider1: ะงะตั€ะตะท ัƒัั‚ั€ะพะนัั‚ะฒะพ (ะฝะฐะฟั€ะธะผะตั€, FaceID, Windows Hello, Fingerprint) - Provider3: OTP SMS - Provider4: ะญะปะตะบั‚ั€ะพะฝะฝะฐั ะฟะพั‡ั‚ะฐ OTP - NextButtonText: ัะปะตะดัƒัŽั‰ะธะน - SkipButtonText: ัะบะธะฟ + Description: ะฃัั‚ะฐะฝะพะฒะธั‚ะต ะดะฒัƒั…ั„ะฐะบั‚ะพั€ะฝัƒัŽ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธัŽ ะดะปั ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝะพะน ะทะฐั‰ะธั‚ั‹ ะฒะฐัˆะตะน ัƒั‡ั‘ั‚ะฝะพะน ะทะฐะฟะธัะธ. + Provider0: ะŸั€ะธะปะพะถะตะฝะธะต ะดะปั ะบะพะดะพะฒ (ะฝะฐะฟั€ะธะผะตั€, Google/Microsoft Authenticator ะธะปะธ Authy) + Provider1: ะก ะฟะพะผะพั‰ัŒัŽ ัƒัั‚ั€ะพะนัั‚ะฒะฐ (Face ID, Windows Hello, ะพั‚ะฟะตั‡ะฐั‚ะพะบ ะฟะฐะปัŒั†ะฐ) + Provider3: ะŸะพะปัƒั‡ะฐั‚ัŒ ะบะพะด ะฟะพ ะกะœะก + Provider4: ะŸะพะปัƒั‡ะฐั‚ัŒ ะบะพะด ะฟะพ ัะปะตะบั‚ั€ะพะฝะฝะพะน ะฟะพั‡ั‚ะต + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ + SkipButtonText: ะŸั€ะพะฟัƒัั‚ะธั‚ัŒ InitMFAOTP: - Title: ะŸะพะดั‚ะฒะตั€ะถะดะตะฝะธะต ะดะฒัƒั…ั„ะฐะบั‚ะพั€ะฝะพะน ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ - Description: ะกะพะทะดะฐะนั‚ะต ะดะฒัƒั…ั„ะฐะบั‚ะพั€ะฝัƒัŽ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธัŽ. ะ—ะฐะณั€ัƒะทะธั‚ะต ะฟั€ะธะปะพะถะตะฝะธะต ะดะปั ะฟั€ะพะฒะตั€ะบะธ ะฟะพะดะปะธะฝะฝะพัั‚ะธ, ะตัะปะธ ัƒ ะฒะฐั ะตะณะพ ะตั‰ั‘ ะฝะตั‚. - OTPDescription: ะžั‚ัะบะฐะฝะธั€ัƒะนั‚ะต ะบะพะด ั ะฟะพะผะพั‰ัŒัŽ ะฟั€ะธะปะพะถะตะฝะธั ะดะปั ะฟั€ะพะฒะตั€ะบะธ ะฟะพะดะปะธะฝะฝะพัั‚ะธ (ะฝะฐะฟั€ะธะผะตั€, Google/Microsoft Authenticator, Authy) ะธะปะธ ัะณะตะฝะตั€ะธั€ัƒะนั‚ะต ะบะพะด ัƒะบะฐะทะฐะฝะฝะพะณะพ ะบะปัŽั‡ะฐ ะธ ะฒะฒะตะดะธั‚ะต ะตะณะพ ะฒ ะฟะพะปะต ะฝะธะถะต. - SecretLabel: ะšะปัŽั‡ - CodeLabel: ะšะพะด - NextButtonText: ะดะฐะปะตะต - CancelButtonText: ะพั‚ะผะตะฝะฐ + Title: ะะฐัั‚ั€ะพะนะบะฐ ะดะฒัƒั…ั„ะฐะบั‚ะพั€ะฝะพะน ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ + Description: ะะฐัั‚ั€ะพะนั‚ะต ะดะฒัƒั…ั„ะฐะบั‚ะพั€ะฝัƒัŽ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธัŽ. ะ—ะฐะณั€ัƒะทะธั‚ะต ะฟั€ะธะปะพะถะตะฝะธะต ะดะปั ะณะตะฝะตั€ะฐั†ะธะธ ะบะพะดะพะฒ, ะตัะปะธ ะพะฝะพ ัƒ ะฒะฐั ะพั‚ััƒั‚ัั‚ะฒัƒะตั‚. + OTPDescription: ะžั‚ัะบะฐะฝะธั€ัƒะนั‚ะต QR-ะบะพะด ั ะฟะพะผะพั‰ัŒัŽ ะฟั€ะธะปะพะถะตะฝะธั (ะฝะฐะฟั€ะธะผะตั€, Google Authenticator, Microsoft Authenticator ะธะปะธ Authy), ะปะธะฑะพ ัะบะพะฟะธั€ัƒะนั‚ะต ัะตะบั€ะตั‚ะฝั‹ะน ะบะปัŽั‡ ะธ ะฒะฒะตะดะธั‚ะต ะบะพะด ะฝะธะถะต. + SecretLabel: ะกะตะบั€ะตั‚ะฝั‹ะน ะบะปัŽั‡ + CodeLabel: ะšะพะด ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะธั + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ + CancelButtonText: ะžั‚ะผะตะฝะฐ InitMFAOTPSMS: - Title: 2-ั„ะฐะบั‚ะพั€ะฝะฐั ะฒะตั€ะธั„ะธะบะฐั†ะธั - DescriptionPhone: ะกะพะทะดะฐะนั‚ะต ัะฒะพะน 2-ั„ะฐะบั‚ะพั€. ะ’ะฒะตะดะธั‚ะต ัะฒะพะน ะฝะพะผะตั€ ั‚ะตะปะตั„ะพะฝะฐ, ั‡ั‚ะพะฑั‹ ะฟะพะดั‚ะฒะตั€ะดะธั‚ัŒ ะตะณะพ. - DescriptionCode: ะกะพะทะดะฐะนั‚ะต ัะฒะพะน 2-ั„ะฐะบั‚ะพั€. ะ’ะฒะตะดะธั‚ะต ะฟะพะปัƒั‡ะตะฝะฝั‹ะน ะบะพะด, ั‡ั‚ะพะฑั‹ ะฟะพะดั‚ะฒะตั€ะดะธั‚ัŒ ัะฒะพะน ะฝะพะผะตั€ ั‚ะตะปะตั„ะพะฝะฐ. - PhoneLabel: ะขะตะปะตั„ะพะฝ - CodeLabel: ะšะพะด - EditButtonText: ั€ะตะดะฐะบั‚ะธั€ะพะฒะฐั‚ัŒ - ResendButtonText: ะŸะพะฒั‚ะพั€ะฝะฐั ะพั‚ะฟั€ะฐะฒะบะฐ ะบะพะดะฐ - NextButtonText: ัะปะตะดัƒัŽั‰ะธะน + Title: ะะฐัั‚ั€ะพะนะบะฐ ะดะฒัƒั…ั„ะฐะบั‚ะพั€ะฝะพะน ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ + DescriptionPhone: ะ’ะฒะตะดะธั‚ะต ะฝะพะผะตั€ ั‚ะตะปะตั„ะพะฝะฐ ะดะปั ะตะณะพ ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะธั. + DescriptionCode: ะ’ะฒะตะดะธั‚ะต ะบะพะด ะธะท ะกะœะก ะดะปั ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะธั ะฝะพะผะตั€ะฐ ั‚ะตะปะตั„ะพะฝะฐ. + PhoneLabel: ะะพะผะตั€ ั‚ะตะปะตั„ะพะฝะฐ + CodeLabel: ะšะพะด ะธะท ะกะœะก + EditButtonText: ะ˜ะทะผะตะฝะธั‚ัŒ + ResendButtonText: ะžั‚ะฟั€ะฐะฒะธั‚ัŒ ะบะพะด ะตั‰ั‘ ั€ะฐะท + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ InitMFAU2F: Title: ะ”ะพะฑะฐะฒะปะตะฝะธะต ะบะปัŽั‡ะฐ ะฑะตะทะพะฟะฐัะฝะพัั‚ะธ - Description: ะšะปัŽั‡ ะฑะตะทะพะฟะฐัะฝะพัั‚ะธ โ€” ัั‚ะพ ะผะตั‚ะพะด ะฟั€ะพะฒะตั€ะบะธ, ะบะพั‚ะพั€ั‹ะน ะผะพะถะฝะพ ะฒัั‚ั€ะพะธั‚ัŒ ะฒ ั‚ะตะปะตั„ะพะฝ, ะธัะฟะพะปัŒะทัƒั Bluetooth, ะธะปะธ ะฟะพะดะบะปัŽั‡ะธั‚ัŒ ะฝะตะฟะพัั€ะตะดัั‚ะฒะตะฝะฝะพ ะบ USB-ะฟะพั€ั‚ัƒ ะบะพะผะฟัŒัŽั‚ะตั€ะฐ. + Description: ะšะปัŽั‡ ะฑะตะทะพะฟะฐัะฝะพัั‚ะธ โ€” ัั‚ะพ ะผะตั‚ะพะด ะฟั€ะพะฒะตั€ะบะธ, ะบะพั‚ะพั€ั‹ะน ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ะฒัั‚ั€ะพะตะฝ ะฒ ั‚ะตะปะตั„ะพะฝ, ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ Bluetooth ะธะปะธ ะฟะพะดะบะปัŽั‡ะฐั‚ัŒัั ะฝะฐะฟั€ัะผัƒัŽ ะบ USB-ะฟะพั€ั‚ัƒ ะฒะฐัˆะตะณะพ ะบะพะผะฟัŒัŽั‚ะตั€ะฐ. TokenNameLabel: ะะฐะทะฒะฐะฝะธะต ะบะปัŽั‡ะฐ ะฑะตะทะพะฟะฐัะฝะพัั‚ะธ / ัƒัั‚ั€ะพะนัั‚ะฒะฐ - NotSupported: WebAuthN ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั ะฒะฐัˆะธะผ ะฑั€ะฐัƒะทะตั€ะพะผ. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ัƒะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ะพะฝ ะพะฑะฝะพะฒะปั‘ะฝ ะธะปะธ ะธัะฟะพะปัŒะทัƒะนั‚ะต ะดั€ัƒะณะพะน (ะฝะฐะฟั€ะธะผะตั€, Chrome, Safari, Firefox) + NotSupported: ะ’ะฐัˆ ะฑั€ะฐัƒะทะตั€ ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ WebAuthN. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะพะฑะฝะพะฒะธั‚ะต ะตะณะพ ะธะปะธ ะธัะฟะพะปัŒะทัƒะนั‚ะต ะดั€ัƒะณะพะน (ะฝะฐะฟั€ะธะผะตั€, Chrome, Safari, Firefox). RegisterTokenButtonText: ะ”ะพะฑะฐะฒะธั‚ัŒ ะบะปัŽั‡ ะฑะตะทะพะฟะฐัะฝะพัั‚ะธ - ErrorRetry: ะŸะพะฒั‚ะพั€ะธั‚ะต ะฟะพะฟั‹ั‚ะบัƒ ะธะปะธ ะฒั‹ะฑะตั€ะธั‚ะต ะดั€ัƒะณะพะน ะผะตั‚ะพะด. + ErrorRetry: ะŸะพะฒั‚ะพั€ะธั‚ะต ะฟะพะฟั‹ั‚ะบัƒ, ัะพะทะดะฐะนั‚ะต ะฝะพะฒั‹ะน ะทะฐะฟั€ะพั ะธะปะธ ะฒั‹ะฑะตั€ะธั‚ะต ะดั€ัƒะณะพะน ะผะตั‚ะพะด. InitMFADone: Title: ะšะปัŽั‡ ะฑะตะทะพะฟะฐัะฝะพัั‚ะธ ะฟะพะดั‚ะฒะตั€ะถะดั‘ะฝ - Description: ะŸะพะทะดั€ะฐะฒะปััŽ! ะ’ั‹ ั‚ะพะปัŒะบะพ ั‡ั‚ะพ ัƒัะฟะตัˆะฝะพ ะฝะฐัั‚ั€ะพะธะปะธ ะดะฒัƒั…ั„ะฐะบั‚ะพั€ะฝัƒัŽ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธัŽ ะธ ัะดะตะปะฐะปะธ ัะฒะพัŽ ัƒั‡ั‘ั‚ะฝัƒัŽ ะทะฐะฟะธััŒ ะฑะพะปะตะต ะฑะตะทะพะฟะฐัะฝะพะน. ะคะฐะบั‚ะพั€ ะฝะตะพะฑั…ะพะดะธะผะพ ะฒะฒะพะดะธั‚ัŒ ะฟั€ะธ ะบะฐะถะดะพะผ ะฒั…ะพะดะต ะฒ ัะธัั‚ะตะผัƒ. - NextButtonText: ะดะฐะปะตะต - CancelButtonText: ะพั‚ะผะตะฝะฐ + Description: ะžั‚ะปะธั‡ะฝะพ! ะ’ั‹ ัƒัะฟะตัˆะฝะพ ะฝะฐัั‚ั€ะพะธะปะธ ะดะฒัƒั…ั„ะฐะบั‚ะพั€ะฝัƒัŽ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธัŽ ะธ ัะดะตะปะฐะปะธ ัะฒะพัŽ ัƒั‡ั‘ั‚ะฝัƒัŽ ะทะฐะฟะธััŒ ะฑะพะปะตะต ะฑะตะทะพะฟะฐัะฝะพะน. ะคะฐะบั‚ะพั€ ะฝะตะพะฑั…ะพะดะธะผะพ ะฒะฒะพะดะธั‚ัŒ ะฟั€ะธ ะบะฐะถะดะพะผ ะฒั…ะพะดะต ะฒ ัะธัั‚ะตะผัƒ. + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ + CancelButtonText: ะžั‚ะผะตะฝะฐ MFAProvider: - Provider0: ะงะตั€ะตะท ะฟั€ะธะปะพะถะตะฝะธะต (ะฝะฐะฟั€ะธะผะตั€, Google/Microsoft Authenticator, Authy) - Provider1: ะงะตั€ะตะท ัƒัั‚ั€ะพะนัั‚ะฒะพ (ะฝะฐะฟั€ะธะผะตั€, FaceID, Windows Hello, Fingerprint) - Provider3: OTP SMS - Provider4: ะญะปะตะบั‚ั€ะพะฝะฝะฐั ะฟะพั‡ั‚ะฐ OTP + Provider0: ะŸั€ะธะปะพะถะตะฝะธะต ะดะปั ะบะพะดะพะฒ (ะฝะฐะฟั€ะธะผะตั€, Google/Microsoft Authenticator ะธะปะธ Authy) + Provider1: ะก ะฟะพะผะพั‰ัŒัŽ ัƒัั‚ั€ะพะนัั‚ะฒะฐ (Face ID, Windows Hello, ะพั‚ะฟะตั‡ะฐั‚ะพะบ ะฟะฐะปัŒั†ะฐ) + Provider3: ะŸะพะปัƒั‡ะฐั‚ัŒ ะบะพะด ะฟะพ ะกะœะก + Provider4: ะŸะพะปัƒั‡ะฐั‚ัŒ ะบะพะด ะฟะพ ัะปะตะบั‚ั€ะพะฝะฝะพะน ะฟะพั‡ั‚ะต ChooseOther: ะธะปะธ ะฒั‹ะฑะตั€ะธั‚ะต ะดั€ัƒะณะพะน ะฒะฐั€ะธะฐะฝั‚ VerifyMFAOTP: Title: ะŸะพะดั‚ะฒะตั€ะถะดะตะฝะธะต ะดะฒัƒั…ั„ะฐะบั‚ะพั€ะฝะพะน ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ - Description: ะŸะพะดั‚ะฒะตั€ะดะธั‚ะต ะดะฒัƒั…ั„ะฐะบั‚ะพั€ะฝัƒัŽ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธัŽ. + Description: ะ’ะฒะตะดะธั‚ะต ะบะพะด ะดะปั ะฟั€ะพะฒะตั€ะบะธ ะฒั‚ะพั€ะพะณะพ ั„ะฐะบั‚ะพั€ะฐ CodeLabel: ะšะพะด - NextButtonText: ะดะฐะปะตะต + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ VerifyOTP: - Title: ะŸั€ะพะฒะตั€ะบะฐ 2-ั„ะฐะบั‚ะพั€ะฐ - Description: ะŸั€ะพะฒะตั€ัŒั‚ะต ัะฒะพะน ะฒั‚ะพั€ะพะน ั„ะฐะบั‚ะพั€ + Title: ะŸะพะดั‚ะฒะตั€ะถะดะตะฝะธะต ะดะฒัƒั…ั„ะฐะบั‚ะพั€ะฝะพะน ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ + Description: ะ’ะฒะตะดะธั‚ะต ะบะพะด ะดะปั ะฟั€ะพะฒะตั€ะบะธ ะฒั‚ะพั€ะพะณะพ ั„ะฐะบั‚ะพั€ะฐ CodeLabel: ะšะพะด - ResendButtonText: ะŸะพะฒั‚ะพั€ะฝะฐั ะพั‚ะฟั€ะฐะฒะบะฐ ะบะพะดะฐ - NextButtonText: ัะปะตะดัƒัŽั‰ะธะน + ResendButtonText: ะžั‚ะฟั€ะฐะฒะธั‚ัŒ ะบะพะด ะตั‰ั‘ ั€ะฐะท + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ VerifyMFAU2F: Title: ะŸะพะดั‚ะฒะตั€ะถะดะตะฝะธะต ะดะฒัƒั…ั„ะฐะบั‚ะพั€ะฝะพะน ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ - Description: ะŸะพะดั‚ะฒะตั€ะดะธั‚ะต ะดะฒัƒั…ั„ะฐะบั‚ะพั€ะฝัƒัŽ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธัŽ ั ะฟะพะผะพั‰ัŒัŽ ะทะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐะฝะฝะพะณะพ ัƒัั‚ั€ะพะนัั‚ะฒะฐ (ะฝะฐะฟั€ะธะผะตั€, FaceID, Windows Hello, Fingerprint). - NotSupported: WebAuthN ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั ะฒะฐัˆะธะผ ะฑั€ะฐัƒะทะตั€ะพะผ. ะฃะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ะฒั‹ ะธัะฟะพะปัŒะทัƒะตั‚ะต ัะฐะผัƒัŽ ะฝะพะฒัƒัŽ ะฒะตั€ัะธัŽ, ะธะปะธ ะธะทะผะตะฝะธั‚ะต ะฑั€ะฐัƒะทะตั€ ะฝะฐ ะฟะพะดะดะตั€ะถะธะฒะฐะตะผั‹ะน (Chrome, Safari, Firefox) + Description: ะŸะพะดั‚ะฒะตั€ะดะธั‚ะต ะดะฒัƒั…ั„ะฐะบั‚ะพั€ะฝัƒัŽ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธัŽ ั ะฟะพะผะพั‰ัŒัŽ ะทะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐะฝะฝะพะณะพ ัƒัั‚ั€ะพะนัั‚ะฒะฐ (ะฝะฐะฟั€ะธะผะตั€, FaceID, Windows Hello ะธะปะธ ะพั‚ะฟะตั‡ะฐั‚ะบะฐ ะฟะฐะปัŒั†ะฐ). + NotSupported: ะ’ะฐัˆ ะฑั€ะฐัƒะทะตั€ ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ WebAuthN. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะพะฑะฝะพะฒะธั‚ะต ะตะณะพ ะธะปะธ ะธัะฟะพะปัŒะทัƒะนั‚ะต ะดั€ัƒะณะพะน (ะฝะฐะฟั€ะธะผะตั€, Chrome, Safari, Firefox). ErrorRetry: ะŸะพะฒั‚ะพั€ะธั‚ะต ะฟะพะฟั‹ั‚ะบัƒ ะธะปะธ ะฒั‹ะฑะตั€ะธั‚ะต ะดั€ัƒะณะพะน ะผะตั‚ะพะด. - ValidateTokenButtonText: ะŸะพะดั‚ะฒะตั€ะดะธั‚ัŒ ะดะฒัƒั…ั„ะฐะบั‚ะพั€ะฝัƒัŽ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธัŽ + ValidateTokenButtonText: ะŸะพะดั‚ะฒะตั€ะดะธั‚ัŒ Passwordless: Title: ะ’ั…ะพะด ะฑะตะท ะฟะฐั€ะพะปั - Description: ะ’ะพะนะดะธั‚ะต ะฒ ัะธัั‚ะตะผัƒ ั ะฟะพะผะพั‰ัŒัŽ ะผะตั‚ะพะดะพะฒ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ, ะฟั€ะตะดะพัั‚ะฐะฒะปัะตะผั‹ั… ะฒะฐัˆะธะผ ัƒัั‚ั€ะพะนัั‚ะฒะพะผ, ั‚ะฐะบะธั… ะบะฐะบ FaceID, Windows Hello ะธะปะธ Fingerprint. - NotSupported: WebAuthN ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั ะฒะฐัˆะธะผ ะฑั€ะฐัƒะทะตั€ะพะผ. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ัƒะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ะพะฝ ะพะฑะฝะพะฒะปั‘ะฝ ะธะปะธ ะธัะฟะพะปัŒะทัƒะนั‚ะต ะดั€ัƒะณะพะน (ะฝะฐะฟั€ะธะผะตั€, Chrome, Safari, Firefox) + Description: ะ’ะพะนะดะธั‚ะต ะฒ ัะธัั‚ะตะผัƒ ั ะฟะพะผะพั‰ัŒัŽ ะผะตั‚ะพะดะพะฒ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ, ะดะพัั‚ัƒะฟะฝั‹ั… ะฝะฐ ะฒะฐัˆะตะผ ัƒัั‚ั€ะพะนัั‚ะฒะต, ั‚ะฐะบะธั… ะบะฐะบ FaceID, Windows Hello ะธะปะธ ะพั‚ะฟะตั‡ะฐั‚ะพะบ ะฟะฐะปัŒั†ะฐ. + NotSupported: ะ’ะฐัˆ ะฑั€ะฐัƒะทะตั€ ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ WebAuthN. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะพะฑะฝะพะฒะธั‚ะต ะตะณะพ ะธะปะธ ะธัะฟะพะปัŒะทัƒะนั‚ะต ะดั€ัƒะณะพะน (ะฝะฐะฟั€ะธะผะตั€, Chrome, Safari, Firefox). ErrorRetry: ะŸะพะฒั‚ะพั€ะธั‚ะต ะฟะพะฟั‹ั‚ะบัƒ ะธะปะธ ะฒั‹ะฑะตั€ะธั‚ะต ะดั€ัƒะณะพะน ะผะตั‚ะพะด. - LoginWithPwButtonText: ะ’ะพะนั‚ะธ ะฟะพ ะฟะฐั€ะพะปัŽ + LoginWithPwButtonText: ะ’ะพะนั‚ะธ ั ะฟะฐั€ะพะปะตะผ ValidateTokenButtonText: ะ’ะพะนั‚ะธ ะฑะตะท ะฟะฐั€ะพะปั PasswordlessPrompt: - Title: ะฃัั‚ะฐะฝะพะฒะบะฐ ะฒั…ะพะดะฐ ะฑะตะท ะฟะฐั€ะพะปั - Description: ะฅะพั‚ะธั‚ะต ะฝะฐัั‚ั€ะพะธั‚ัŒ ะฒั…ะพะด ะฑะตะท ะฟะฐั€ะพะปั? (ะะฐะฟั€ะธะผะตั€, ะธัะฟะพะปัŒะทัƒั ะผะตั‚ะพะดั‹ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ ะฒะฐัˆะตะณะพ ัƒัั‚ั€ะพะนัั‚ะฒะฐ, ั‚ะฐะบะธะต ะบะฐะบ FaceID, Windows Hello ะธะปะธ Fingerprint). - DescriptionInit: ะ’ะฐะผ ะฝะตะพะฑั…ะพะดะธะผะพ ะฝะฐัั‚ั€ะพะธั‚ัŒ ะฒั…ะพะด ะฑะตะท ะฟะฐั€ะพะปั. ะ’ะพัะฟะพะปัŒะทัƒะนั‚ะตััŒ ััั‹ะปะบะพะน, ะบะพั‚ะพั€ัƒัŽ ะฒั‹ ะฟะพะปัƒั‡ะธะปะธ, ั‡ั‚ะพะฑั‹ ะทะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐั‚ัŒ ัะฒะพั‘ ัƒัั‚ั€ะพะนัั‚ะฒะพ. - PasswordlessButtonText: ะŸะตั€ะตะนั‚ะธ ะบ ะฒั…ะพะดัƒ ะฑะตะท ะฟะฐั€ะพะปั - NextButtonText: ะดะฐะปะตะต - SkipButtonText: ะฟั€ะพะฟัƒัั‚ะธั‚ัŒ + Title: ะะฐัั‚ั€ะพะนะบะฐ ะฒั…ะพะดะฐ ะฑะตะท ะฟะฐั€ะพะปั + Description: ะฅะพั‚ะธั‚ะต ะฝะฐัั‚ั€ะพะธั‚ัŒ ะฒั…ะพะด ะฑะตะท ะฟะฐั€ะพะปั? ะ’ั‹ ัะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะผะตั‚ะพะดั‹ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ ะฒะฐัˆะตะณะพ ัƒัั‚ั€ะพะนัั‚ะฒะฐ, ั‚ะฐะบะธะต ะบะฐะบ FaceID, Windows Hello ะธะปะธ ะพั‚ะฟะตั‡ะฐั‚ะพะบ ะฟะฐะปัŒั†ะฐ. + DescriptionInit: ะ”ะปั ะฝะฐั‡ะฐะปะฐ ะฝะฐัั‚ั€ะพะนะบะธ ะฒั…ะพะดะฐ ะฑะตะท ะฟะฐั€ะพะปั ะฟะตั€ะตะนะดะธั‚ะต ะฟะพ ะฟะพะปัƒั‡ะตะฝะฝะพะน ััั‹ะปะบะต ะธ ะทะฐั€ะตะณะธัั‚ั€ะธั€ัƒะนั‚ะต ัะฒะพั‘ ัƒัั‚ั€ะพะนัั‚ะฒะพ. + PasswordlessButtonText: ะะฐัั‚ั€ะพะธั‚ัŒ ะฒั…ะพะด ะฑะตะท ะฟะฐั€ะพะปั + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ + SkipButtonText: ะŸั€ะพะฟัƒัั‚ะธั‚ัŒ PasswordlessRegistration: - Title: ะฃัั‚ะฐะฝะพะฒะบะฐ ะฒั…ะพะดะฐ ะฑะตะท ะฟะฐั€ะพะปั - Description: ะ”ะพะฑะฐะฒัŒั‚ะต ัะฒะพัŽ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธัŽ, ัƒะบะฐะทะฐะฒ ะธะผั (ะฝะฐะฟั€ะธะผะตั€, MyMobilePhone, MacBook ะธ ั‚ะฐะบ ะดะฐะปะตะต), ะฐ ะทะฐั‚ะตะผ ะฝะฐะถะผะธั‚ะต ะบะฝะพะฟะบัƒ ยซะ—ะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐั‚ัŒ ะฒั…ะพะด ะฑะตะท ะฟะฐั€ะพะปัยป ะฝะธะถะต. - TokenNameLabel: ะะฐะทะฒะฐะฝะธะต ัƒัั‚ั€ะพะนัั‚ะฒะฐ - NotSupported: WebAuthN ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั ะฒะฐัˆะธะผ ะฑั€ะฐัƒะทะตั€ะพะผ. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ัƒะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ะพะฝ ะพะฑะฝะพะฒะปั‘ะฝ ะธะปะธ ะธัะฟะพะปัŒะทัƒะนั‚ะต ะดั€ัƒะณะพะน (ะฝะฐะฟั€ะธะผะตั€, Chrome, Safari, Firefox) + Title: ะะฐัั‚ั€ะพะนะบะฐ ะฒั…ะพะดะฐ ะฑะตะท ะฟะฐั€ะพะปั + Description: ะฃะบะฐะถะธั‚ะต ะธะผั ัƒัั‚ั€ะพะนัั‚ะฒะฐ (ะฝะฐะฟั€ะธะผะตั€, MyMobilePhone, MacBook ะธ ั‚ะฐะบ ะดะฐะปะตะต), ะฐ ะทะฐั‚ะตะผ ะฝะฐะถะผะธั‚ะต ะบะฝะพะฟะบัƒ ยซะ—ะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐั‚ัŒ ะฒั…ะพะด ะฑะตะท ะฟะฐั€ะพะปัยป ะฝะธะถะต. + TokenNameLabel: ะ˜ะผั ัƒัั‚ั€ะพะนัั‚ะฒะฐ + NotSupported: ะ’ะฐัˆ ะฑั€ะฐัƒะทะตั€ ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ WebAuthN. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะพะฑะฝะพะฒะธั‚ะต ะตะณะพ ะธะปะธ ะธัะฟะพะปัŒะทัƒะนั‚ะต ะดั€ัƒะณะพะน (ะฝะฐะฟั€ะธะผะตั€, Chrome, Safari, Firefox). RegisterTokenButtonText: ะ—ะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐั‚ัŒ ะฒั…ะพะด ะฑะตะท ะฟะฐั€ะพะปั ErrorRetry: ะŸะพะฒั‚ะพั€ะธั‚ะต ะฟะพะฟั‹ั‚ะบัƒ ะธะปะธ ะฒั‹ะฑะตั€ะธั‚ะต ะดั€ัƒะณะพะน ะผะตั‚ะพะด. PasswordlessRegistrationDone: - Title: ะฃัั‚ะฐะฝะพะฒะบะฐ ะฒั…ะพะดะฐ ะฑะตะท ะฟะฐั€ะพะปั - Description: ะฃัั‚ั€ะพะนัั‚ะฒะพ ะดะปั ะฒั…ะพะดะฐ ะฑะตะท ะฟะฐั€ะพะปั ัƒัะฟะตัˆะฝะพ ะดะพะฑะฐะฒะปะตะฝะพ. - DescriptionClose: ะขะตะฟะตั€ัŒ ะฒั‹ ะผะพะถะตั‚ะต ะทะฐะบั€ั‹ั‚ัŒ ะดะฐะฝะฝะพะต ะพะบะฝะพ. - NextButtonText: ะดะฐะปะตะต - CancelButtonText: ะพั‚ะผะตะฝะฐ + Title: ะ’ั…ะพะด ะฑะตะท ะฟะฐั€ะพะปั ะฝะฐัั‚ั€ะพะตะฝ + Description: ะฃัั‚ั€ะพะนัั‚ะฒะพ ะดะปั ะฒั…ะพะดะฐ ะฑะตะท ะฟะฐั€ะพะปั ัƒัะฟะตัˆะฝะพ ะทะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐะฝะพ. + DescriptionClose: ะขะตะฟะตั€ัŒ ะฒั‹ ะผะพะถะตั‚ะต ะทะฐะบั€ั‹ั‚ัŒ ัั‚ะพ ะพะบะฝะพ. + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ + CancelButtonText: ะžั‚ะผะตะฝะฐ PasswordChange: Title: ะ˜ะทะผะตะฝะตะฝะธะต ะฟะฐั€ะพะปั - Description: ะ˜ะทะผะตะฝะธั‚ะต ะฒะฐัˆ ะฟะฐั€ะพะปัŒ. ะ’ะฒะตะดะธั‚ะต ัั‚ะฐั€ั‹ะน ะธ ะฝะพะฒั‹ะน ะฟะฐั€ะพะปะธ. - ExpiredDescription: ะกั€ะพะบ ะดะตะนัั‚ะฒะธั ะฒะฐัˆะตะณะพ ะฟะฐั€ะพะปั ะธัั‚ะตะบ, ะธ ะตะณะพ ะฝะตะพะฑั…ะพะดะธะผะพ ะธะทะผะตะฝะธั‚ัŒ. ะ’ะฒะตะดะธั‚ะต ัั‚ะฐั€ั‹ะน ะธ ะฝะพะฒั‹ะน ะฟะฐั€ะพะปัŒ. - OldPasswordLabel: ะกั‚ะฐั€ั‹ะน ะฟะฐั€ะพะปัŒ + Description: ะ’ะฒะตะดะธั‚ะต ั‚ะตะบัƒั‰ะธะน ะฟะฐั€ะพะปัŒ ะธ ะทะฐะดะฐะนั‚ะต ะฝะพะฒั‹ะน. + ExpiredDescription: ะกั€ะพะบ ะดะตะนัั‚ะฒะธั ะฒะฐัˆะตะณะพ ะฟะฐั€ะพะปั ะธัั‚ั‘ะบ. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฒะฒะตะดะธั‚ะต ั‚ะตะบัƒั‰ะธะน ะฟะฐั€ะพะปัŒ ะธ ะทะฐะดะฐะนั‚ะต ะฝะพะฒั‹ะน. + OldPasswordLabel: ะขะตะบัƒั‰ะธะน ะฟะฐั€ะพะปัŒ NewPasswordLabel: ะะพะฒั‹ะน ะฟะฐั€ะพะปัŒ - NewPasswordConfirmLabel: ะŸะพะดั‚ะฒะตั€ะถะดะตะฝะธะต ะฟะฐั€ะพะปั - CancelButtonText: ะพั‚ะผะตะฝะฐ - NextButtonText: ะดะฐะปะตะต - Footer: ะะธะถะฝะธะน ะบะพะปะพะฝั‚ะธั‚ัƒะป + NewPasswordConfirmLabel: ะŸะพะฒั‚ะพั€ะธั‚ะต ะฝะพะฒั‹ะน ะฟะฐั€ะพะปัŒ + CancelButtonText: ะžั‚ะผะตะฝะฐ + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ + Footer: ะŸั€ะธะผะตั‡ะฐะฝะธะต PasswordChangeDone: Title: ะ˜ะทะผะตะฝะตะฝะธะต ะฟะฐั€ะพะปั Description: ะ’ะฐัˆ ะฟะฐั€ะพะปัŒ ะฑั‹ะป ัƒัะฟะตัˆะฝะพ ะธะทะผะตะฝั‘ะฝ. - NextButtonText: ะดะฐะปะตะต + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ PasswordResetDone: Title: ะกัั‹ะปะบะฐ ะดะปั ัะฑั€ะพัะฐ ะฟะฐั€ะพะปั ะพั‚ะฟั€ะฐะฒะปะตะฝะฐ Description: ะŸั€ะพะฒะตั€ัŒั‚ะต ะฒะฐัˆัƒ ัะปะตะบั‚ั€ะพะฝะฝัƒัŽ ะฟะพั‡ั‚ัƒ, ั‡ั‚ะพะฑั‹ ัะฑั€ะพัะธั‚ัŒ ะฟะฐั€ะพะปัŒ. - NextButtonText: ะดะฐะปะตะต + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ EmailVerification: Title: ะŸะพะดั‚ะฒะตั€ะถะดะตะฝะธะต ัะปะตะบั‚ั€ะพะฝะฝะพะน ะฟะพั‡ั‚ั‹ - Description: ะœั‹ ะพั‚ะฟั€ะฐะฒะธะปะธ ะฒะฐะผ ะฟะธััŒะผะพ ะดะปั ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะธั ะฒะฐัˆะตะน ัะปะตะบั‚ั€ะพะฝะฝะพะน ะฟะพั‡ั‚ั‹. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฒะฒะตะดะธั‚ะต ะฟะพะปัƒั‡ะตะฝะฝั‹ะน ะบะพะด ะฒ ะฟะพะปะต ะฝะธะถะต. + Description: ะœั‹ ะพั‚ะฟั€ะฐะฒะธะปะธ ะฟะธััŒะผะพ ั ะบะพะดะพะผ ะดะปั ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะธั ะฒะฐัˆะตะน ัะปะตะบั‚ั€ะพะฝะฝะพะน ะฟะพั‡ั‚ั‹. ะ’ะฒะตะดะธั‚ะต ะบะพะด ะฝะธะถะต. CodeLabel: ะšะพะด - NextButtonText: ะดะฐะปะตะต - ResendButtonText: ะฟะพะฒั‚ะพั€ะฝะพ ะพั‚ะฟั€ะฐะฒะธั‚ัŒ ะบะพะด + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ + ResendButtonText: ะžั‚ะฟั€ะฐะฒะธั‚ัŒ ะบะพะด ะตั‰ั‘ ั€ะฐะท EmailVerificationDone: Title: ะŸะพะดั‚ะฒะตั€ะถะดะตะฝะธะต ัะปะตะบั‚ั€ะพะฝะฝะพะน ะฟะพั‡ั‚ั‹ - Description: ะ’ะฐัˆะฐ ัะปะตะบั‚ั€ะพะฝะฝะฐั ะฟะพั‡ั‚ะฐ ะฑั‹ะปะฐ ัƒัะฟะตัˆะฝะพ ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะฐ. - NextButtonText: ะดะฐะปะตะต - CancelButtonText: ะพั‚ะผะตะฝะฐ - LoginButtonText: ะฒั…ะพะด + Description: ะ’ะฐัˆะฐ ัะปะตะบั‚ั€ะพะฝะฝะฐั ะฟะพั‡ั‚ะฐ ัƒัะฟะตัˆะฝะพ ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะฐ. + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ + CancelButtonText: ะžั‚ะผะตะฝะฐ + LoginButtonText: ะ’ะพะนั‚ะธ RegisterOption: Title: ะกะฟะพัะพะฑั‹ ั€ะตะณะธัั‚ั€ะฐั†ะธะธ Description: ะ’ั‹ะฑะตั€ะธั‚ะต ัะฟะพัะพะฑ ั€ะตะณะธัั‚ั€ะฐั†ะธะธ. - RegisterUsernamePasswordButtonText: ะก ะฟะฐั€ะพะปะตะผ ะปะพะณะธะฝะฐ - ExternalLoginDescription: ะธะปะธ ะทะฐั€ะตะณะธัั‚ั€ะธั€ัƒะนั‚ะตััŒ ะฒะฝะตัˆะฝะธะผ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะผ - LoginButtonText: ะฒั…ะพะด + RegisterUsernamePasswordButtonText: ะก ะปะพะณะธะฝะพะผ ะธ ะฟะฐั€ะพะปะตะผ + ExternalLoginDescription: ะธะปะธ ะทะฐั€ะตะณะธัั‚ั€ะธั€ัƒะนั‚ะตััŒ ั ะฟะพะผะพั‰ัŒัŽ ะฒะฝะตัˆะฝะตะณะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั + LoginButtonText: ะ’ะพะนั‚ะธ RegistrationUser: Title: ะ ะตะณะธัั‚ั€ะฐั†ะธั - Description: ะ’ะฒะตะดะธั‚ะต ะฒะฐัˆะธ ะดะฐะฝะฝั‹ะต. ะญะปะตะบั‚ั€ะพะฝะฝะฐั ะฟะพั‡ั‚ะฐ ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั ะฒ ะบะฐั‡ะตัั‚ะฒะต ะปะพะณะธะฝะฐ. - DescriptionOrgRegister: ะ’ะฒะตะดะธั‚ะต ะฒะฐัˆะธ ะดะฐะฝะฝั‹ะต. + Description: ะ’ะฒะตะดะธั‚ะต ัะฒะพะธ ะดะฐะฝะฝั‹ะต. ะญะปะตะบั‚ั€ะพะฝะฝะฐั ะฟะพั‡ั‚ะฐ ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั ะบะฐะบ ะปะพะณะธะฝ. + DescriptionOrgRegister: ะ’ะฒะตะดะธั‚ะต ัะฒะพะธ ะดะฐะฝะฝั‹ะต. EmailLabel: ะญะปะตะบั‚ั€ะพะฝะฝะฐั ะฟะพั‡ั‚ะฐ UsernameLabel: ะ›ะพะณะธะฝ FirstnameLabel: ะ˜ะผั @@ -263,24 +264,25 @@ RegistrationUser: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด GenderLabel: ะŸะพะป Female: ะ–ะตะฝัะบะธะน Male: ะœัƒะถัะบะพะน Diverse: ะ”ั€ัƒะณะพะน / X PasswordLabel: ะŸะฐั€ะพะปัŒ - PasswordConfirmLabel: ะŸะพะดั‚ะฒะตั€ะถะดะตะฝะธะต ะฟะฐั€ะพะปั + PasswordConfirmLabel: ะŸะพะฒั‚ะพั€ะธั‚ะต ะฟะฐั€ะพะปัŒ TosAndPrivacyLabel: ะฃัะปะพะฒะธั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั TosConfirm: ะฏ ัะพะณะปะฐัะตะฝ ั TosLinkText: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะผ ัะพะณะปะฐัˆะตะฝะธะตะผ PrivacyConfirm: ะฏ ัะพะณะปะฐัะตะฝ ั PrivacyLinkText: ะŸะพะปะธั‚ะธะบะพะน ะบะพะฝั„ะธะดะตะฝั†ะธะฐะปัŒะฝะพัั‚ะธ - ExternalLogin: ะธะปะธ ะทะฐั€ะตะณะธัั‚ั€ะธั€ัƒะนั‚ะตััŒ ะฒะฝะตัˆะฝะธะผ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะผ - BackButtonText: ะฒั…ะพะด - NextButtonText: ะดะฐะปะตะต + ExternalLogin: ะธะปะธ ะทะฐั€ะตะณะธัั‚ั€ะธั€ัƒะนั‚ะตััŒ ั ะฟะพะผะพั‰ัŒัŽ ะฒะฝะตัˆะฝะตะณะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั + BackButtonText: ะ’ะพะนั‚ะธ + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ ExternalRegistrationUserOverview: Title: ะ ะตะณะธัั‚ั€ะฐั†ะธั ะฒะฝะตัˆะฝะตะณะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั - Description: ะœั‹ ะฟะพะปัƒั‡ะธะปะธ ะฒะฐัˆะธ ะดะฐะฝะฝั‹ะต ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ัƒ ะฒั‹ะฑั€ะฐะฝะฝะพะณะพ ะฟั€ะพะฒะฐะนะดะตั€ะฐ. ะขะตะฟะตั€ัŒ ะฒั‹ ะผะพะถะตั‚ะต ะธะทะผะตะฝะธั‚ัŒ ะธะปะธ ะดะพะฟะพะปะฝะธั‚ัŒ ะธั…. + Description: ะœั‹ ะฟะพะปัƒั‡ะธะปะธ ะฒะฐัˆะธ ะดะฐะฝะฝั‹ะต ะพั‚ ะฟั€ะพะฒะฐะนะดะตั€ะฐ. ะ’ั‹ ะผะพะถะตั‚ะต ะธะทะผะตะฝะธั‚ัŒ ะธะปะธ ะดะพะฟะพะปะฝะธั‚ัŒ ะธั…. EmailLabel: ะญะปะตะบั‚ั€ะพะฝะฝะฐั ะฟะพั‡ั‚ะฐ UsernameLabel: ะ›ะพะณะธะฝ FirstnameLabel: ะ˜ะผั @@ -305,14 +307,15 @@ ExternalRegistrationUserOverview: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด TosAndPrivacyLabel: ะฃัะปะพะฒะธั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั TosConfirm: ะฏ ัะพะณะปะฐัะตะฝ ั TosLinkText: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะผ ัะพะณะปะฐัˆะตะฝะธะตะผ PrivacyConfirm: ะฏ ัะพะณะปะฐัะตะฝ ั PrivacyLinkText: ะŸะพะปะธั‚ะธะบะพะน ะบะพะฝั„ะธะดะตะฝั†ะธะฐะปัŒะฝะพัั‚ะธ - ExternalLogin: ะธะปะธ ะทะฐั€ะตะณะธัั‚ั€ะธั€ัƒะนั‚ะตััŒ ะฒะฝะตัˆะฝะธะผ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะผ - BackButtonText: ะฝะฐะทะฐะด - NextButtonText: ัะพั…ั€ะฐะฝะธั‚ัŒ + ExternalLogin: ะธะปะธ ะทะฐั€ะตะณะธัั‚ั€ะธั€ัƒะนั‚ะตััŒ ั ะฟะพะผะพั‰ัŒัŽ ะฒะฝะตัˆะฝะตะณะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั + BackButtonText: ะะฐะทะฐะด + NextButtonText: ะกะพั…ั€ะฐะฝะธั‚ัŒ RegistrationOrg: Title: ะ ะตะณะธัั‚ั€ะฐั†ะธั ะพั€ะณะฐะฝะธะทะฐั†ะธะธ @@ -323,7 +326,7 @@ RegistrationOrg: FirstnameLabel: ะ˜ะผั LastnameLabel: ะคะฐะผะธะปะธั PasswordLabel: ะŸะฐั€ะพะปัŒ - PasswordConfirmLabel: ะŸะพะดั‚ะฒะตั€ะถะดะตะฝะธะต ะฟะฐั€ะพะปั + PasswordConfirmLabel: ะŸะพะฒั‚ะพั€ะธั‚ะต ะฟะฐั€ะพะปัŒ TosAndPrivacyLabel: ะฃัะปะพะฒะธั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั TosConfirm: ะฏ ัะพะณะปะฐัะตะฝ ั TosLinkText: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะผ ัะพะณะปะฐัˆะตะฝะธะตะผ @@ -333,32 +336,32 @@ RegistrationOrg: LoginSuccess: Title: ะฃัะฟะตัˆะฝั‹ะน ะฒั…ะพะด - AutoRedirectDescription: ะ’ั‹ ะฑัƒะดะตั‚ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะฟะตั€ะตะฝะฐะฟั€ะฐะฒะปะตะฝั‹ ะฒ ัะฒะพั‘ ะฟั€ะธะปะพะถะตะฝะธะต. ะ•ัะปะธ ัั‚ะพะณะพ ะฝะต ะฟั€ะพะธะทะพัˆะปะพ, ะฝะฐะถะผะธั‚ะต ะบะฝะพะฟะบัƒ ะฝะธะถะต. ะŸะพัะปะต ัั‚ะพะณะพ ะฒั‹ ะผะพะถะตั‚ะต ะทะฐะบั€ั‹ั‚ัŒ ะพะบะฝะพ. - RedirectedDescription: ะขะตะฟะตั€ัŒ ะฒั‹ ะผะพะถะตั‚ะต ะทะฐะบั€ั‹ั‚ัŒ ะดะฐะฝะฝะพะต ะพะบะฝะพ. - NextButtonText: ะดะฐะปะตะต + AutoRedirectDescription: ะ’ั‹ ะฑัƒะดะตั‚ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะฟะตั€ะตะฝะฐะฟั€ะฐะฒะปะตะฝั‹ ะฒ ะฟั€ะธะปะพะถะตะฝะธะต. ะ•ัะปะธ ัั‚ะพะณะพ ะฝะต ะฟั€ะพะธะทะพัˆะปะพ, ะฝะฐะถะผะธั‚ะต ะบะฝะพะฟะบัƒ ะฝะธะถะต. + RedirectedDescription: ะ’ั‹ ะผะพะถะตั‚ะต ะทะฐะบั€ั‹ั‚ัŒ ัั‚ะพ ะพะบะฝะพ. + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ LogoutDone: Title: ะ’ั‹ั…ะพะด ะธะท ัะธัั‚ะตะผั‹ Description: ะ’ั‹ ัƒัะฟะตัˆะฝะพ ะฒั‹ัˆะปะธ ะธะท ัะธัั‚ะตะผั‹. - LoginButtonText: ะฒั…ะพะด + LoginButtonText: ะ’ะพะนั‚ะธ LinkingUserPrompt: - Title: ะกัƒั‰ะตัั‚ะฒัƒัŽั‰ะธะน ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะฐะนะดะตะฝ - Description: "ะฅะพั‚ะธั‚ะต ะปะธ ะฒั‹ ัะฒัะทะฐั‚ัŒ ััƒั‰ะตัั‚ะฒัƒัŽั‰ัƒัŽ ัƒั‡ะตั‚ะฝัƒัŽ ะทะฐะฟะธััŒ:" - LinkButtonText: ะกะฒัะทัŒ + Title: ะะฐะนะดะตะฝ ััƒั‰ะตัั‚ะฒัƒัŽั‰ะธะน ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒ + Description: "ะฅะพั‚ะธั‚ะต ัะฒัะทะฐั‚ัŒ ัั‚ัƒ ัƒั‡ั‘ั‚ะฝัƒัŽ ะทะฐะฟะธััŒ ั ััƒั‰ะตัั‚ะฒัƒัŽั‰ะตะน?" + LinkButtonText: ะกะฒัะทะฐั‚ัŒ OtherButtonText: ะ”ั€ัƒะณะธะต ะฒะฐั€ะธะฐะฝั‚ั‹ LinkingUsersDone: Title: ะŸั€ะธะฒัะทะบะฐ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั - Description: ะŸั€ะธะฒัะทะบะฐ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะฒั‹ะฟะพะปะฝะตะฝะฐ. - CancelButtonText: ะพั‚ะผะตะฝะฐ - NextButtonText: ะดะฐะปะตะต + Description: ะŸั€ะธะฒัะทะบะฐ ัƒั‡ั‘ั‚ะฝะพะน ะทะฐะฟะธัะธ ะฒั‹ะฟะพะปะฝะตะฝะฐ ัƒัะฟะตัˆะฝะพ. + CancelButtonText: ะžั‚ะผะตะฝะฐ + NextButtonText: ะŸั€ะพะดะพะปะถะธั‚ัŒ ExternalNotFound: Title: ะ’ะฝะตัˆะฝะธะน ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ะฝะฐะนะดะตะฝ - Description: ะ’ะฝะตัˆะฝะธะน ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ะฝะฐะนะดะตะฝ. ะ’ั‹ ะผะพะถะตั‚ะต ะฟั€ะธะฒัะทะฐั‚ัŒ ัะฒะพะตะณะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะธะปะธ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะทะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐั‚ัŒ ะฝะพะฒะพะณะพ. + Description: ะœั‹ ะฝะต ัะผะพะณะปะธ ะฝะฐะนั‚ะธ ัƒะบะฐะทะฐะฝะฝะพะณะพ ะฒะฝะตัˆะฝะตะณะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั. ะ’ั‹ ะผะพะถะตั‚ะต ะฟั€ะธะฒัะทะฐั‚ัŒ ััƒั‰ะตัั‚ะฒัƒัŽั‰ัƒัŽ ัƒั‡ะตั‚ะฝัƒัŽ ะทะฐะฟะธััŒ ะธะปะธ ะทะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐั‚ัŒ ะฝะพะฒะพะณะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั. LinkButtonText: ะŸั€ะธะฒัะทะฐั‚ัŒ - AutoRegisterButtonText: ะทะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐั‚ัŒ + AutoRegisterButtonText: ะ—ะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐั‚ัŒ TosAndPrivacyLabel: ะฃัะปะพะฒะธั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั TosConfirm: ะฏ ัะพะณะปะฐัะตะฝ ั TosLinkText: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะผ ัะพะณะปะฐัˆะตะฝะธะตะผ @@ -381,137 +384,138 @@ ExternalNotFound: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด DeviceAuth: Title: ะะฒั‚ะพั€ะธะทะฐั†ะธั ัƒัั‚ั€ะพะนัั‚ะฒะฐ UserCode: Label: ะšะพะด ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั - Description: ะ’ะฒะตะดะธั‚ะต ะบะพะด ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั, ะฟั€ะตะดัั‚ะฐะฒะปะตะฝะฝั‹ะน ะฝะฐ ัƒัั‚ั€ะพะนัั‚ะฒะต. - ButtonNext: ัะปะตะดัƒัŽั‰ะธะน + Description: ะ’ะฒะตะดะธั‚ะต ะบะพะด, ะพั‚ะพะฑั€ะฐะถะฐะตะผั‹ะน ะฝะฐ ะฒะฐัˆะตะผ ัƒัั‚ั€ะพะนัั‚ะฒะต. + ButtonNext: ะŸั€ะพะดะพะปะถะธั‚ัŒ Action: - Description: ะŸั€ะตะดะพัั‚ะฐะฒัŒั‚ะต ะดะพัั‚ัƒะฟ ะบ ัƒัั‚ั€ะพะนัั‚ะฒัƒ. - GrantDevice: ะ’ั‹ ัะพะฑะธั€ะฐะตั‚ะตััŒ ะฟั€ะตะดะพัั‚ะฐะฒะธั‚ัŒ ัƒัั‚ั€ะพะนัั‚ะฒะพ - AccessToScopes: ะ”ะพัั‚ัƒะฟ ะบ ัะปะตะดัƒัŽั‰ะธะผ ะพะฑะปะฐัั‚ัะผ + Description: ะŸั€ะตะดะพัั‚ะฐะฒัŒั‚ะต ัƒัั‚ั€ะพะนัั‚ะฒัƒ ะดะพัั‚ัƒะฟ ะบ ัะธัั‚ะตะผะต. + GrantDevice: ะฒั‹ ัะพะฑะธั€ะฐะตั‚ะตััŒ ะฟั€ะตะดะพัั‚ะฐะฒะธั‚ัŒ ัƒัั‚ั€ะพะนัั‚ะฒัƒ + AccessToScopes: ะดะพัั‚ัƒะฟ ะบ ัะปะตะดัƒัŽั‰ะธะผ ะพะฑะปะฐัั‚ัะผ Button: - Allow: ั€ะฐะทั€ะตัˆะฐั‚ัŒ - Deny: ะพั‚ั€ะธั†ะฐั‚ัŒ + Allow: ะ ะฐะทั€ะตัˆะฐั‚ัŒ + Deny: ะžั‚ะบะปะพะฝะธั‚ัŒ Done: - Description: ะ”ะพะณะพะฒะพั€ะธะปะธััŒ. - Approved: ะะฒั‚ะพั€ะธะทะฐั†ะธั ัƒัั‚ั€ะพะนัั‚ะฒะฐ ะพะดะพะฑั€ะตะฝะฐ. ะขะตะฟะตั€ัŒ ะฒั‹ ะผะพะถะตั‚ะต ะฒะตั€ะฝัƒั‚ัŒัั ะบ ัƒัั‚ั€ะพะนัั‚ะฒัƒ. - Denied: ะžั‚ะบะฐะทะฐะฝะพ ะฒ ะฐะฒั‚ะพั€ะธะทะฐั†ะธะธ ัƒัั‚ั€ะพะนัั‚ะฒะฐ. ะขะตะฟะตั€ัŒ ะฒั‹ ะผะพะถะตั‚ะต ะฒะตั€ะฝัƒั‚ัŒัั ะบ ัƒัั‚ั€ะพะนัั‚ะฒัƒ. + Description: ะžะฟะตั€ะฐั†ะธั ะทะฐะฒะตั€ัˆะตะฝะฐ. + Approved: ะฃัั‚ั€ะพะนัั‚ะฒะพ ัƒัะฟะตัˆะฝะพ ะฐะฒั‚ะพั€ะธะทะพะฒะฐะฝะพ. ะขะตะฟะตั€ัŒ ะฒั‹ ะผะพะถะตั‚ะต ะฒะตั€ะฝัƒั‚ัŒัั ะบ ัƒัั‚ั€ะพะนัั‚ะฒัƒ. + Denied: ะะฒั‚ะพั€ะธะทะฐั†ะธั ัƒัั‚ั€ะพะนัั‚ะฒะฐ ะพั‚ะบะปะพะฝะตะฝะฐ. ะขะตะฟะตั€ัŒ ะฒั‹ ะผะพะถะตั‚ะต ะฒะตั€ะฝัƒั‚ัŒัั ะบ ัƒัั‚ั€ะพะนัั‚ะฒัƒ. Footer: - PoweredBy: ะะฐ ะฑะฐะทะต + PoweredBy: ะ ะฐะฑะพั‚ะฐะตั‚ ะฝะฐ ะพัะฝะพะฒะต Tos: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะพะต ัะพะณะปะฐัˆะตะฝะธะต PrivacyPolicy: ะŸะพะปะธั‚ะธะบะฐ ะบะพะฝั„ะธะดะตะฝั†ะธะฐะปัŒะฝะพัั‚ะธ Help: ะŸะพะผะพั‰ัŒ SupportEmail: ะญะปะตะบั‚ั€ะพะฝะฝะฐั ะฟะพั‡ั‚ะฐ ัะปัƒะถะฑั‹ ะฟะพะดะดะตั€ะถะบะธ -SignIn: ะ’ั…ะพะด ั ะฟะพะผะพั‰ัŒัŽ {{.Provider}} +SignIn: ะ’ะพะนั‚ะธ ั ะฟะพะผะพั‰ัŒัŽ {{.Provider}} Errors: Internal: ะŸั€ะพะธะทะพัˆะปะฐ ะฒะฝัƒั‚ั€ะตะฝะฝัั ะพัˆะธะฑะบะฐ AuthRequest: - NotFound: ะะต ัƒะดะฐะปะพััŒ ะพะฑะฝะฐั€ัƒะถะธั‚ัŒ ะทะฐะฟั€ะพั ะฐะฒั‚ะพั€ะธะทะฐั†ะธะธ - UserAgentNotCorresponding: User Agent ะฝะต ัะพะพั‚ะฒะตั‚ัั‚ะฒัƒะตั‚ - UserAgentNotFound: ID User Agent ะฝะต ะฝะฐะนะดะตะฝ + NotFound: authrequest ะฝะต ะฝะฐะนะดะตะฝ + UserAgentNotCorresponding: User Agent ะฝะต ัะพะฒะฟะฐะดะฐะตั‚ + UserAgentNotFound: User Agent ID ะฝะต ะฝะฐะนะดะตะฝ TokenNotFound: ะขะพะบะตะฝ ะฝะต ะฝะฐะนะดะตะฝ RequestTypeNotSupported: ะขะธะฟ ะทะฐะฟั€ะพัะฐ ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั MissingParameters: ะžั‚ััƒั‚ัั‚ะฒัƒัŽั‚ ะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะต ะฟะฐั€ะฐะผะตั‚ั€ั‹ User: - NotFound: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ะฝะฐะนะดะตะฝ + NotFound: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ะฝะฐะนะดะตะฝ AlreadyExists: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ัƒะถะต ััƒั‰ะตัั‚ะฒัƒะตั‚ Inactive: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะตะฐะบั‚ะธะฒะตะฝ NotFoundOnOrg: ะะต ัƒะดะฐะปะพััŒ ะฝะฐะนั‚ะธ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะฒ ะฒั‹ะฑั€ะฐะฝะฝะพะน ะพั€ะณะฐะฝะธะทะฐั†ะธะธ - NotAllowedOrg: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ัะฒะปัะตั‚ัั ัƒั‡ะฐัั‚ะฝะธะบะพะผ ั‚ั€ะตะฑัƒะตะผะพะน ะพั€ะณะฐะฝะธะทะฐั†ะธะธ - NotMatchingUserID: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ัะพะฒะฟะฐะดะฐะตั‚ ั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะผ ะฒ ะทะฐะฟั€ะพัะต ะฐะฒั‚ะพั€ะธะทะฐั†ะธะธ - UserIDMissing: UserID ะฟัƒัั‚ะพะน + NotAllowedOrg: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ัะฒะปัะตั‚ัั ั‡ะปะตะฝะพะผ ั‚ั€ะตะฑัƒะตะผะพะน ะพั€ะณะฐะฝะธะทะฐั†ะธะธ + NotMatchingUserID: ะฃะบะฐะทะฐะฝะฝั‹ะน ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะต ัะพะฒะฟะฐะดะฐะตั‚ ั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะตะผ ะฒ authrequest + UserIDMissing: ะะต ัƒะบะฐะทะฐะฝ UserID Invalid: ะะตะฒะตั€ะฝั‹ะต ะดะฐะฝะฝั‹ะต ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั - DomainNotAllowedAsUsername: ะ”ะพะผะตะฝ ัƒะถะต ะทะฐั€ะตะทะตั€ะฒะธั€ะพะฒะฐะฝ ะธ ะฝะต ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ะธัะฟะพะปัŒะทะพะฒะฐะฝ - NotAllowedToLink: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปั ะฝะต ั€ะฐะทั€ะตัˆะตะฝะพ ะฟั€ะธะฒัะทั‹ะฒะฐั‚ัŒ ะบ ะฒะฝะตัˆะฝะตะผัƒ ะฟั€ะพะฒะฐะนะดะตั€ัƒ ะฒั…ะพะดะฐ + DomainNotAllowedAsUsername: ะ”ะพะผะตะฝ ัƒะถะต ะทะฐั€ะตะทะตั€ะฒะธั€ะพะฒะฐะฝ ะธ ะฝะต ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ะธัะฟะพะปัŒะทะพะฒะฐะฝ ะฒ ะบะฐั‡ะตัั‚ะฒะต ะปะพะณะธะฝะฐ + NotAllowedToLink: ะŸั€ะธะฒัะทะบะฐ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะบ ะฒะฝะตัˆะฝะตะผัƒ ะฟั€ะพะฒะฐะนะดะตั€ัƒ ะทะฐะฟั€ะตั‰ะตะฝะฐ Profile: NotFound: ะŸั€ะพั„ะธะปัŒ ะฝะต ะฝะฐะนะดะตะฝ NotChanged: ะŸั€ะพั„ะธะปัŒ ะฝะต ะธะทะผะตะฝะตะฝ Empty: ะŸั€ะพั„ะธะปัŒ ะฟัƒัั‚ - FirstNameEmpty: ะ˜ะผั ะฒ ะฟั€ะพั„ะธะปะต ะฟัƒัั‚ะพ - LastNameEmpty: ะคะฐะผะธะปะธั ะฒ ะฟั€ะพั„ะธะปะต ะฟัƒัั‚ะฐ + FirstNameEmpty: ะŸะพะปะต ะธะผะตะฝะธ ะฟัƒัั‚ะพ + LastNameEmpty: ะŸะพะปะต ั„ะฐะผะธะปะธะธ ะฟัƒัั‚ะพ IDMissing: ะžั‚ััƒั‚ัั‚ะฒัƒะตั‚ ะธะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ ะฟั€ะพั„ะธะปั Email: NotFound: ะญะปะตะบั‚ั€ะพะฝะฝะฐั ะฟะพั‡ั‚ะฐ ะฝะต ะฝะฐะนะดะตะฝะฐ - Invalid: ะะดั€ะตั ัะปะตะบั‚ั€ะพะฝะฝะพะน ะฟะพั‡ั‚ั‹ ะฝะตะดะตะนัั‚ะฒะธั‚ะตะปะตะฝ + Invalid: ะะตะบะพั€ั€ะตะบั‚ะฝั‹ะน ะฐะดั€ะตั ัะปะตะบั‚ั€ะพะฝะฝะพะน ะฟะพั‡ั‚ั‹ AlreadyVerified: ะญะปะตะบั‚ั€ะพะฝะฝะฐั ะฟะพั‡ั‚ะฐ ัƒะถะต ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะฐ NotChanged: ะะดั€ะตั ัะปะตะบั‚ั€ะพะฝะฝะพะน ะฟะพั‡ั‚ั‹ ะฝะต ะธะทะผะตะฝะธะปัั - Empty: ะญะปะตะบั‚ั€ะพะฝะฝะฐั ะฟะพั‡ั‚ะฐ ะฟัƒัั‚ะฐ + Empty: ะญะปะตะบั‚ั€ะพะฝะฝะฐั ะฟะพั‡ั‚ะฐ ะฝะต ัƒะบะฐะทะฐะฝะฐ IDMissing: ะžั‚ััƒั‚ัั‚ะฒัƒะตั‚ ะธะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ ัะปะตะบั‚ั€ะพะฝะฝะพะน ะฟะพั‡ั‚ั‹ Phone: - NotFound: ะขะตะปะตั„ะพะฝ ะฝะต ะฝะฐะนะดะตะฝ - Invalid: ะขะตะปะตั„ะพะฝ ะฝะตะดะตะนัั‚ะฒะธั‚ะตะปะตะฝ - AlreadyVerified: ะขะตะปะตั„ะพะฝ ัƒะถะต ะฟั€ะพะฒะตั€ะตะฝ - Empty: ะขะตะปะตั„ะพะฝ ะฟัƒัั‚ - NotChanged: ะขะตะปะตั„ะพะฝ ะฝะต ะผะตะฝัะปัั + NotFound: ะะพะผะตั€ ั‚ะตะปะตั„ะพะฝะฐ ะฝะต ะฝะฐะนะดะตะฝ + Invalid: ะะตะฒะตั€ะฝั‹ะน ะฝะพะผะตั€ ั‚ะตะปะตั„ะพะฝะฐ + AlreadyVerified: ะะพะผะตั€ ั‚ะตะปะตั„ะพะฝะฐ ัƒะถะต ะฟะพะดั‚ะฒะตั€ะถะดะตะฝ + Empty: ะะพะผะตั€ ั‚ะตะปะตั„ะพะฝะฐ ะฝะต ัƒะบะฐะทะฐะฝ + NotChanged: ะะพะผะตั€ ั‚ะตะปะตั„ะพะฝะฐ ะฝะต ะธะทะผะตะฝะธะปัั Address: NotFound: ะะดั€ะตั ะฝะต ะฝะฐะนะดะตะฝ NotChanged: ะะดั€ะตั ะฝะต ะธะทะผะตะฝะธะปัั Username: - AlreadyExists: ะ˜ะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ัƒะถะต ะทะฐะฝัั‚ะพ - Reserved: ะ˜ะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ัƒะถะต ะทะฐะฝัั‚ะพ - Empty: ะ˜ะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะฟัƒัั‚ะพ + AlreadyExists: ะ›ะพะณะธะฝ ัƒะถะต ะทะฐะฝัั‚ + Reserved: ะ›ะพะณะธะฝ ัƒะถะต ะทะฐะฝัั‚ + Empty: ะ›ะพะณะธะฝ ะฝะต ัƒะบะฐะทะฐะฝ Password: - ConfirmationWrong: ะะตะฒะตั€ะฝะพะต ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะธะต ะฟะฐั€ะพะปั - Empty: ะŸะฐั€ะพะปัŒ ะฟัƒัั‚ะพะน + ConfirmationWrong: ะฃะบะฐะทะฐะฝะฝั‹ะต ะฟะฐั€ะพะปะธ ะฝะต ัะพะฒะฟะฐะดะฐัŽั‚ + Empty: ะŸะฐั€ะพะปัŒ ะฝะต ัƒะบะฐะทะฐะฝ Invalid: ะะตะฒะตั€ะฝั‹ะน ะฟะฐั€ะพะปัŒ InvalidAndLocked: ะะตะฒะตั€ะฝั‹ะน ะฟะฐั€ะพะปัŒ, ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะทะฐะฑะปะพะบะธั€ะพะฒะฐะฝ. ะžะฑั€ะฐั‚ะธั‚ะตััŒ ะบ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ัƒ. - NotChanged: ะŸะฐั€ะพะปัŒ ะฝะต ะธะทะผะตะฝะตะฝ + NotChanged: ะŸะฐั€ะพะปัŒ ะฝะต ะธะทะผะตะฝะธะปัั UsernameOrPassword: - Invalid: ะ›ะพะณะธะฝ ะธะปะธ ะฟะฐั€ะพะปัŒ ะฝะตะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝั‹ + Invalid: ะะตะฒะตั€ะฝั‹ะน ะปะพะณะธะฝ ะธะปะธ ะฟะฐั€ะพะปัŒ PasswordComplexityPolicy: NotFound: ะŸะพะปะธั‚ะธะบะฐ ะฟะฐั€ะพะปะตะน ะฝะต ะฝะฐะนะดะตะฝะฐ MinLength: ะŸะฐั€ะพะปัŒ ัะปะธัˆะบะพะผ ะบะพั€ะพั‚ะบะธะน - HasLower: ะŸะฐั€ะพะปัŒ ะดะพะปะถะตะฝ ัะพะดะตั€ะถะฐั‚ัŒ ัั‚ั€ะพั‡ะฝัƒัŽ ะฑัƒะบะฒัƒ - HasUpper: ะŸะฐั€ะพะปัŒ ะดะพะปะถะตะฝ ัะพะดะตั€ะถะฐั‚ัŒ ะทะฐะณะปะฐะฒะฝัƒัŽ ะฑัƒะบะฒัƒ - HasNumber: ะŸะฐั€ะพะปัŒ ะดะพะปะถะตะฝ ัะพะดะตั€ะถะฐั‚ัŒ ั†ะธั„ั€ัƒ - HasSymbol: ะŸะฐั€ะพะปัŒ ะดะพะปะถะตะฝ ัะพะดะตั€ะถะฐั‚ัŒ ัะธะผะฒะพะป + HasLower: ะŸะฐั€ะพะปัŒ ะดะพะปะถะตะฝ ัะพะดะตั€ะถะฐั‚ัŒ ั…ะพั‚ั ะฑั‹ ะพะดะฝัƒ ัั‚ั€ะพั‡ะฝัƒัŽ ะฑัƒะบะฒัƒ + HasUpper: ะŸะฐั€ะพะปัŒ ะดะพะปะถะตะฝ ัะพะดะตั€ะถะฐั‚ัŒ ั…ะพั‚ั ะฑั‹ ะพะดะฝัƒ ะทะฐะณะปะฐะฒะฝัƒัŽ ะฑัƒะบะฒัƒ + HasNumber: ะŸะฐั€ะพะปัŒ ะดะพะปะถะตะฝ ัะพะดะตั€ะถะฐั‚ัŒ ั…ะพั‚ั ะฑั‹ ะพะดะฝัƒ ั†ะธั„ั€ัƒ + HasSymbol: ะŸะฐั€ะพะปัŒ ะดะพะปะถะตะฝ ัะพะดะตั€ะถะฐั‚ัŒ ั…ะพั‚ั ะฑั‹ ะพะดะธะฝ ัะฟะตั†ะธะฐะปัŒะฝั‹ะน ัะธะผะฒะพะป Code: Expired: ะšะพะด ะธัั‚ั‘ะบ Invalid: ะะตะฒะตั€ะฝั‹ะน ะบะพะด - Empty: ะšะพะด ะฟัƒัั‚ะพะน - CryptoCodeNil: ะšั€ะธะฟั‚ะพะบะพะด ั€ะฐะฒะตะฝ ะฝัƒะปัŽ - NotFound: ะะต ัƒะดะฐะปะพััŒ ะฝะฐะนั‚ะธ ะบะพะด - GeneratorAlgNotSupported: ะะตะฟะพะดะดะตั€ะถะธะฒะฐะตะผั‹ะน ะฐะปะณะพั€ะธั‚ะผ ะณะตะฝะตั€ะฐั‚ะพั€ะฐ + Empty: ะšะพะด ะฝะต ัƒะบะฐะทะฐะฝ + CryptoCodeNil: ะŸัƒัั‚ะพะน ะบั€ะธะฟั‚ะพะบะพะด + NotFound: ะšะพะด ะฝะต ะฝะฐะนะดะตะฝ + GeneratorAlgNotSupported: ะะปะณะพั€ะธั‚ะผ ะณะตะฝะตั€ะฐั†ะธะธ ะบะพะดะฐ ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั EmailVerify: - UserIDEmpty: UserID ะฟัƒัั‚ะพะน + UserIDEmpty: UserID ะฝะต ัƒะบะฐะทะฐะฝ ExternalData: - CouldNotRead: ะ’ะฝะตัˆะฝะธะต ะดะฐะฝะฝั‹ะต ะฝะต ะผะพะณัƒั‚ ะฑั‹ั‚ัŒ ะพะฑั€ะฐะฑะพั‚ะฐะฝั‹ ะบะพั€ั€ะตะบั‚ะฝะพ + CouldNotRead: ะะต ัƒะดะฐะปะพััŒ ะพะฑั€ะฐะฑะพั‚ะฐั‚ัŒ ะฒะฝะตัˆะฝะธะต ะดะฐะฝะฝั‹ะต MFA: - NoProviders: ะะตั‚ ะดะพัั‚ัƒะฟะฝั‹ั… ะผะฝะพะณะพั„ะฐะบั‚ะพั€ะฝั‹ั… ะฟะพัั‚ะฐะฒั‰ะธะบะพะฒ + NoProviders: ะะตั‚ ะดะพัั‚ัƒะฟะฝั‹ั… ะผะตั‚ะพะดะพะฒ ะผะฝะพะณะพั„ะฐะบั‚ะพั€ะฝะพะน ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ OTP: - AlreadyReady: ะœัƒะปัŒั‚ะธั„ะฐะบั‚ะพั€ OTP (OneTimePassword) ัƒะถะต ะฝะฐัั‚ั€ะพะตะฝ - NotExisting: ะœัƒะปัŒั‚ะธั„ะฐะบั‚ะพั€ OTP (OneTimePassword) ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚ + AlreadyReady: OTP (OneTimePassword) ัƒะถะต ะฝะฐัั‚ั€ะพะตะฝ + NotExisting: OTP (OneTimePassword) ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚ InvalidCode: ะะตะฒะตั€ะฝั‹ะน ะบะพะด - NotReady: ะœัƒะปัŒั‚ะธั„ะฐะบั‚ะพั€ OTP (OneTimePassword) ะฝะต ะณะพั‚ะพะฒ + NotReady: OTP (OneTimePassword) ะฝะต ะณะพั‚ะพะฒ Locked: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะทะฐะฑะปะพะบะธั€ะพะฒะฐะฝ SomethingWentWrong: ะงั‚ะพ-ั‚ะพ ะฟะพัˆะปะพ ะฝะต ั‚ะฐะบ NotActive: ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฝะตะฐะบั‚ะธะฒะตะฝ ExternalIDP: - IDPTypeNotImplemented: IDP ั‚ะธะฟ ะฝะต ั€ะตะฐะปะธะทะพะฒะฐะฝ - NotAllowed: ะ’ะฝะตัˆะฝะธะน ะฟั€ะพะฒะฐะนะดะตั€ ะฒั…ะพะดะฐ ะทะฐะฟั€ะตั‰ั‘ะฝ - IDPConfigIDEmpty: IDP ID ะฟัƒัั‚ะพะน - ExternalUserIDEmpty: External User ID ะฟัƒัั‚ะพะน - UserDisplayNameEmpty: ะžั‚ะพะฑั€ะฐะถะฐะตะผะพะต ะธะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะฟัƒัั‚ะพะต - NoExternalUserData: ะ”ะฐะฝะฝั‹ะต ะฒะฝะตัˆะฝะตะณะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะฝะต ะฟะพะปัƒั‡ะตะฝั‹ - CreationNotAllowed: ะกะพะทะดะฐะฝะธะต ะฝะพะฒะพะณะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะดะปั ะดะฐะฝะฝะพะณะพ ะฟั€ะพะฒะฐะนะดะตั€ะฐ ะฝะต ั€ะฐะทั€ะตัˆะตะฝะพ - LinkingNotAllowed: ะŸั€ะธะฒัะทะบะฐ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ั ะดะฐะฝะฝั‹ะผ ะฟั€ะพะฒะฐะนะดะตั€ะพะผ ะทะฐะฟั€ะตั‰ะตะฝะฐ - NoOptionAllowed: ะะธ ัะพะทะดะฐะฝะธะต, ะฝะธ ัะฒัะทั‹ะฒะฐะฝะธะต ะฝะต ั€ะฐะทั€ะตัˆะตะฝั‹ ะดะปั ัั‚ะพะณะพ ะฟั€ะพะฒะฐะนะดะตั€ะฐ. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะพะฑั€ะฐั‚ะธั‚ะตััŒ ะบ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ัƒ. - GrantRequired: ะ’ั…ะพะด ะฝะตะฒะพะทะผะพะถะตะฝ. ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะดะพะปะถะตะฝ ะธะผะตั‚ัŒ ั…ะพั‚ั ะฑั‹ ะพะดะธะฝ ะดะพะฟัƒัะบ ะฒ ะฟั€ะธะปะพะถะตะฝะธะธ. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ัะฒัะถะธั‚ะตััŒ ั ะฒะฐัˆะธะผ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ะพะผ. - ProjectRequired: ะ’ั…ะพะด ะฝะตะฒะพะทะผะพะถะตะฝ. ะžั€ะณะฐะฝะธะทะฐั†ะธั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะดะพะปะถะฝะฐ ะธะผะตั‚ัŒ ะดะพะฟัƒัะบ ะบ ะฟั€ะพะตะบั‚ัƒ. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ัะฒัะถะธั‚ะตััŒ ั ะฒะฐัˆะธะผ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ะพะผ. + IDPTypeNotImplemented: ะขะธะฟ ะฒะฝะตัˆะฝะตะณะพ ะฟั€ะพะฒะฐะนะดะตั€ะฐ ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั + NotAllowed: ะ”ะพัั‚ัƒะฟ ะบ ะฒะฝะตัˆะฝะตะผัƒ ะฟั€ะพะฒะฐะนะดะตั€ัƒ ะทะฐะฟั€ะตั‰ะตะฝ + IDPConfigIDEmpty: ะะต ัƒะบะฐะทะฐะฝ ะธะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ ะบะพะฝั„ะธะณัƒั€ะฐั†ะธะธ ะฟั€ะพะฒะฐะนะดะตั€ะฐ + ExternalUserIDEmpty: ะะต ัƒะบะฐะทะฐะฝ ะฒะฝะตัˆะฝะธะน ะธะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั + UserDisplayNameEmpty: ะžั‚ะพะฑั€ะฐะถะฐะตะผะพะต ะธะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะฝะต ัƒะบะฐะทะฐะฝะพ + NoExternalUserData: ะ’ะฝะตัˆะฝะธะต ะดะฐะฝะฝั‹ะต ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะพั‚ััƒั‚ัั‚ะฒัƒัŽั‚ + CreationNotAllowed: ะกะพะทะดะฐะฝะธะต ะฝะพะฒะพะณะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะดะปั ัั‚ะพะณะพ ะฟั€ะพะฒะฐะนะดะตั€ะฐ ะทะฐะฟั€ะตั‰ะตะฝะพ + LinkingNotAllowed: ะŸั€ะธะฒัะทะบะฐ ะบ ัั‚ะพะผัƒ ะฟั€ะพะฒะฐะนะดะตั€ัƒ ะทะฐะฟั€ะตั‰ะตะฝะฐ + NoOptionAllowed: ะะธ ัะพะทะดะฐะฝะธะต, ะฝะธ ะฟั€ะธะฒัะทะบะฐ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะบ ัั‚ะพะผัƒ ะฟั€ะพะฒะฐะนะดะตั€ัƒ ะฝะตะฒะพะทะผะพะถะฝั‹. ะžะฑั€ะฐั‚ะธั‚ะตััŒ ะบ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ัƒ. + GrantRequired: ะ’ั…ะพะด ะฝะตะฒะพะทะผะพะถะตะฝ. ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะดะพะปะถะตะฝ ะธะผะตั‚ัŒ ั…ะพั‚ั ะฑั‹ ะพะดะธะฝ ะดะพะฟัƒัะบ ะบ ะฟั€ะธะปะพะถะตะฝะธัŽ. ะžะฑั€ะฐั‚ะธั‚ะตััŒ ะบ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ัƒ. + ProjectRequired: ะ’ั…ะพะด ะฝะตะฒะพะทะผะพะถะตะฝ. ะžั€ะณะฐะฝะธะทะฐั†ะธั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะดะพะปะถะฝะฐ ะธะผะตั‚ัŒ ะดะพัั‚ัƒะฟ ะบ ะฟั€ะพะตะบั‚ัƒ. ะžะฑั€ะฐั‚ะธั‚ะตััŒ ะบ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ัƒ. IdentityProvider: - InvalidConfig: ะะตะดะพะฟัƒัั‚ะธะผะฐั ะบะพะฝั„ะธะณัƒั€ะฐั†ะธั ะฟะพัั‚ะฐะฒั‰ะธะบะฐ ะธะดะตะฝั‚ะธั„ะธะบะฐั†ะธะพะฝะฝั‹ั… ะดะฐะฝะฝั‹ั… + InvalidConfig: ะะตะบะพั€ั€ะตะบั‚ะฝะฐั ะบะพะฝั„ะธะณัƒั€ะฐั†ะธั ะฟั€ะพะฒะฐะนะดะตั€ะฐ ะธะดะตะฝั‚ะธั„ะธะบะฐั†ะธะธ IAM: LockoutPolicy: - NotExisting: ะŸะพะปะธั‚ะธะบะฐ ะฑะปะพะบะธั€ะพะฒะบะธ ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚ + NotExisting: ะŸะพะปะธั‚ะธะบะฐ ะฑะปะพะบะธั€ะพะฒะบะธ ะฝะต ะฝะฐะนะดะตะฝะฐ Org: LoginPolicy: - RegistrationNotAllowed: ะ ะตะณะธัั‚ั€ะฐั†ะธั ะฝะต ะดะพะฟัƒัะบะฐะตั‚ัั + RegistrationNotAllowed: ะ ะตะณะธัั‚ั€ะฐั†ะธั ะทะฐะฟั€ะตั‰ะตะฝะฐ DeviceAuth: NotExisting: ะšะพะด ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚ diff --git a/internal/api/ui/login/static/i18n/sv.yaml b/internal/api/ui/login/static/i18n/sv.yaml index 6d1d5ef8ac..26fee23551 100644 --- a/internal/api/ui/login/static/i18n/sv.yaml +++ b/internal/api/ui/login/static/i18n/sv.yaml @@ -264,6 +264,7 @@ RegistrationUser: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด GenderLabel: Kรถn Female: Man Male: Kvinna @@ -306,6 +307,7 @@ ExternalRegistrationUserOverview: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด TosAndPrivacyLabel: Anvรคndarvillkor TosConfirm: Jag accepterar TosLinkText: Anvรคndarvillkoren @@ -382,6 +384,7 @@ ExternalNotFound: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด DeviceAuth: Title: Tillgรฅng frรฅn hรฅrdvaruenhet UserCode: diff --git a/internal/api/ui/login/static/i18n/zh.yaml b/internal/api/ui/login/static/i18n/zh.yaml index dbbe969b6e..79db3c020e 100644 --- a/internal/api/ui/login/static/i18n/zh.yaml +++ b/internal/api/ui/login/static/i18n/zh.yaml @@ -264,6 +264,7 @@ RegistrationUser: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด GenderLabel: ๆ€งๅˆซ Female: ๅฅณๆ€ง Male: ็”ทๆ€ง @@ -306,6 +307,7 @@ ExternalRegistrationUserOverview: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด TosAndPrivacyLabel: ๆกๆฌพๅ’Œๆกๆฌพ TosConfirm: ๆˆ‘ๆŽฅๅ— TosLinkText: ๆœๅŠกๆกๆฌพ @@ -382,6 +384,7 @@ ExternalNotFound: Swedish: Svenska Indonesian: Bahasa Indonesia Hungarian: Magyar + Korean: ํ•œ๊ตญ์–ด DeviceAuth: Title: ่ฎพๅค‡ๆŽˆๆƒ UserCode: diff --git a/internal/api/ui/login/static/templates/external_not_found_option.html b/internal/api/ui/login/static/templates/external_not_found_option.html index 9173671b16..33bcaeb4e0 100644 --- a/internal/api/ui/login/static/templates/external_not_found_option.html +++ b/internal/api/ui/login/static/templates/external_not_found_option.html @@ -98,6 +98,8 @@ + diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index e098350c07..e35e7b5143 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -1081,6 +1081,15 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth } } + // If the user never had a verified email, we need to verify it. + // This prevents situations, where OTP email is the only MFA method and no verified email is set. + // If the user had a verified email, but change it and has not yet verified the new one, we'll verify it after we checked the MFA methods. + if user.VerifiedEmail == "" && !user.IsEmailVerified { + return append(steps, &domain.VerifyEMailStep{ + InitPassword: !user.PasswordSet && len(idps.Links) == 0, + }), nil + } + step, ok, err := repo.mfaChecked(userSession, request, user, isInternalLogin && len(request.LinkingUsers) == 0) if err != nil { return nil, err diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go index ccd53e06a1..dda8c54872 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go @@ -156,6 +156,7 @@ type mockViewUser struct { PasswordChanged time.Time PasswordChangeRequired bool IsEmailVerified bool + VerifiedEmail string OTPState int32 MFAMaxSetUp int32 MFAInitSkipped time.Time @@ -222,6 +223,7 @@ func (m *mockViewUser) UserByID(context.Context, string, string) (*user_view_mod PasswordSet: m.PasswordSet, PasswordChangeRequired: m.PasswordChangeRequired, IsEmailVerified: m.IsEmailVerified, + VerifiedEmail: m.VerifiedEmail, OTPState: m.OTPState, MFAMaxSetUp: m.MFAMaxSetUp, MFAInitSkipped: m.MFAInitSkipped, @@ -1403,6 +1405,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordVerification: testNow.Add(-5 * time.Minute), }, userViewProvider: &mockViewUser{ + VerifiedEmail: "verified", PasswordSet: true, PasswordlessTokens: user_view_model.WebAuthNTokens{&user_view_model.WebAuthNView{ID: "id", State: int32(user_model.MFAStateReady)}}, OTPState: int32(user_model.MFAStateReady), @@ -1439,9 +1442,10 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordVerification: testNow.Add(-5 * time.Minute), }, userViewProvider: &mockViewUser{ - PasswordSet: true, - OTPState: int32(user_model.MFAStateReady), - MFAMaxSetUp: int32(domain.MFALevelSecondFactor), + VerifiedEmail: "verified", + PasswordSet: true, + OTPState: int32(user_model.MFAStateReady), + MFAMaxSetUp: int32(domain.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, @@ -1469,6 +1473,45 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { }, { "external user, mfa not verified, mfa check step", + fields{ + userSessionViewProvider: &mockViewUserSession{ + PasswordVerification: testNow.Add(-5 * time.Minute), + ExternalLoginVerification: testNow.Add(-5 * time.Minute), + }, + userViewProvider: &mockViewUser{ + VerifiedEmail: "verified", + PasswordSet: true, + OTPState: int32(user_model.MFAStateReady), + MFAMaxSetUp: int32(domain.MFALevelSecondFactor), + }, + userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, + lockoutPolicyProvider: &mockLockoutPolicy{ + policy: &query.LockoutPolicy{ + ShowFailures: true, + }, + }, + idpUserLinksProvider: &mockIDPUserLinks{}, + }, + args{ + &domain.AuthRequest{ + UserID: "UserID", + SelectedIDPConfigID: "IDPConfigID", + LoginPolicy: &domain.LoginPolicy{ + AllowUsernamePassword: true, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, + PasswordCheckLifetime: 10 * 24 * time.Hour, + ExternalLoginCheckLifetime: 10 * 24 * time.Hour, + SecondFactorCheckLifetime: 18 * time.Hour, + }, + }, false}, + []domain.NextStep{&domain.MFAVerificationStep{ + MFAProviders: []domain.MFAType{domain.MFATypeTOTP}, + }}, + nil, + }, + { + "external user, mfa not verified, email never verified, email verification step", fields{ userSessionViewProvider: &mockViewUserSession{ PasswordVerification: testNow.Add(-5 * time.Minute), @@ -1500,8 +1543,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { SecondFactorCheckLifetime: 18 * time.Hour, }, }, false}, - []domain.NextStep{&domain.MFAVerificationStep{ - MFAProviders: []domain.MFAType{domain.MFATypeTOTP}, + []domain.NextStep{&domain.VerifyEMailStep{ + InitPassword: false, }}, nil, }, @@ -1573,13 +1616,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { nil, }, { - "email not verified and password change required, mail verification step", + "email not verified (but had before) and password change required, mail verification step", fields{ userSessionViewProvider: &mockViewUserSession{ PasswordVerification: testNow.Add(-5 * time.Minute), SecondFactorVerification: testNow.Add(-5 * time.Minute), }, userViewProvider: &mockViewUser{ + VerifiedEmail: "verified", PasswordSet: true, PasswordChangeRequired: true, MFAMaxSetUp: int32(domain.MFALevelSecondFactor), diff --git a/internal/auth/repository/eventsourcing/handler/handler.go b/internal/auth/repository/eventsourcing/handler/handler.go index 557890265f..0d87ab06bb 100644 --- a/internal/auth/repository/eventsourcing/handler/handler.go +++ b/internal/auth/repository/eventsourcing/handler/handler.go @@ -19,9 +19,12 @@ type Config struct { BulkLimit uint64 FailureCountUntilSkip uint64 - HandleActiveInstances time.Duration TransactionDuration time.Duration Handlers map[string]*ConfigOverwrites + + ActiveInstancer interface { + ActiveInstances() []string + } } type ConfigOverwrites struct { @@ -31,6 +34,9 @@ type ConfigOverwrites struct { var projections []*handler.Handler func Register(ctx context.Context, configs Config, view *view.View, queries *query2.Queries) { + // make sure the slice does not contain old values + projections = nil + projections = append(projections, newUser(ctx, configs.overwrite("User"), view, @@ -77,13 +83,13 @@ func ProjectInstance(ctx context.Context) error { func (config Config) overwrite(viewModel string) handler2.Config { c := handler2.Config{ - Client: config.Client, - Eventstore: config.Eventstore, - BulkLimit: uint16(config.BulkLimit), - RequeueEvery: 3 * time.Minute, - HandleActiveInstances: config.HandleActiveInstances, - MaxFailureCount: uint8(config.FailureCountUntilSkip), - TransactionDuration: config.TransactionDuration, + Client: config.Client, + Eventstore: config.Eventstore, + BulkLimit: uint16(config.BulkLimit), + RequeueEvery: 3 * time.Minute, + MaxFailureCount: uint8(config.FailureCountUntilSkip), + TransactionDuration: config.TransactionDuration, + ActiveInstancer: config.ActiveInstancer, } overwrite, ok := config.Handlers[viewModel] if !ok { diff --git a/internal/cache/cache.go b/internal/cache/cache.go index 9e92f50988..c7dbad6f2c 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -16,6 +16,7 @@ const ( PurposeUnspecified Purpose = iota PurposeAuthzInstance PurposeMilestones + PurposeOrganization ) // Cache stores objects with a value of type `V`. diff --git a/internal/cache/connector/connector.go b/internal/cache/connector/connector.go index 0c4fb9ccc6..09298fa688 100644 --- a/internal/cache/connector/connector.go +++ b/internal/cache/connector/connector.go @@ -19,8 +19,9 @@ type CachesConfig struct { Postgres pg.Config Redis redis.Config } - Instance *cache.Config - Milestones *cache.Config + Instance *cache.Config + Milestones *cache.Config + Organization *cache.Config } type Connectors struct { diff --git a/internal/cache/connector/redis/circuit_breaker.go b/internal/cache/connector/redis/circuit_breaker.go new file mode 100644 index 0000000000..fd556b52b0 --- /dev/null +++ b/internal/cache/connector/redis/circuit_breaker.go @@ -0,0 +1,91 @@ +package redis + +import ( + "context" + "errors" + "time" + + "github.com/redis/go-redis/v9" + "github.com/sony/gobreaker/v2" + "github.com/zitadel/logging" +) + +const defaultInflightSize = 100000 + +type CBConfig struct { + // Interval when the counters are reset to 0. + // 0 interval never resets the counters until the CB is opened. + Interval time.Duration + // Amount of consecutive failures permitted + MaxConsecutiveFailures uint32 + // The ratio of failed requests out of total requests + MaxFailureRatio float64 + // Timeout after opening of the CB, until the state is set to half-open. + Timeout time.Duration + // The allowed amount of requests that are allowed to pass when the CB is half-open. + MaxRetryRequests uint32 +} + +func (config *CBConfig) readyToTrip(counts gobreaker.Counts) bool { + if config.MaxConsecutiveFailures > 0 && counts.ConsecutiveFailures > config.MaxConsecutiveFailures { + return true + } + if config.MaxFailureRatio > 0 && counts.Requests > 0 { + failureRatio := float64(counts.TotalFailures) / float64(counts.Requests) + return failureRatio > config.MaxFailureRatio + } + return false +} + +// limiter implements [redis.Limiter] as a circuit breaker. +type limiter struct { + inflight chan func(success bool) + cb *gobreaker.TwoStepCircuitBreaker[struct{}] +} + +func newLimiter(config *CBConfig, maxActiveConns int) redis.Limiter { + if config == nil { + return nil + } + // The size of the inflight channel needs to be big enough for maxActiveConns to prevent blocking. + // When that is 0 (no limit), we must set a sane default. + if maxActiveConns <= 0 { + maxActiveConns = defaultInflightSize + } + return &limiter{ + inflight: make(chan func(success bool), maxActiveConns), + cb: gobreaker.NewTwoStepCircuitBreaker[struct{}](gobreaker.Settings{ + Name: "redis cache", + MaxRequests: config.MaxRetryRequests, + Interval: config.Interval, + Timeout: config.Timeout, + ReadyToTrip: config.readyToTrip, + OnStateChange: func(name string, from, to gobreaker.State) { + logging.WithFields("name", name, "from", from, "to", to).Warn("circuit breaker state change") + }, + }), + } +} + +// Allow implements [redis.Limiter]. +func (l *limiter) Allow() error { + done, err := l.cb.Allow() + if err != nil { + return err + } + l.inflight <- done + return nil +} + +// ReportResult implements [redis.Limiter]. +// +// ReportResult checks the error returned by the Redis client. +// `nil`, [redis.Nil] and [context.Canceled] are not considered failures. +// Any other error, like connection or [context.DeadlineExceeded] is counted as a failure. +func (l *limiter) ReportResult(err error) { + done := <-l.inflight + done(err == nil || + errors.Is(err, redis.Nil) || + errors.Is(err, context.Canceled) || + redis.HasErrorPrefix(err, "NOSCRIPT")) +} diff --git a/internal/cache/connector/redis/circuit_breaker_test.go b/internal/cache/connector/redis/circuit_breaker_test.go new file mode 100644 index 0000000000..ba61d18071 --- /dev/null +++ b/internal/cache/connector/redis/circuit_breaker_test.go @@ -0,0 +1,168 @@ +package redis + +import ( + "context" + "testing" + "time" + + "github.com/sony/gobreaker/v2" + "github.com/stretchr/testify/require" + + "github.com/zitadel/zitadel/internal/cache" +) + +func TestCBConfig_readyToTrip(t *testing.T) { + type fields struct { + MaxConsecutiveFailures uint32 + MaxFailureRatio float64 + } + type args struct { + counts gobreaker.Counts + } + tests := []struct { + name string + fields fields + args args + want bool + }{ + { + name: "disabled", + fields: fields{}, + args: args{ + counts: gobreaker.Counts{ + Requests: 100, + ConsecutiveFailures: 5, + TotalFailures: 10, + }, + }, + want: false, + }, + { + name: "no failures", + fields: fields{ + MaxConsecutiveFailures: 5, + MaxFailureRatio: 0.1, + }, + args: args{ + counts: gobreaker.Counts{ + Requests: 100, + ConsecutiveFailures: 0, + TotalFailures: 0, + }, + }, + want: false, + }, + { + name: "some failures", + fields: fields{ + MaxConsecutiveFailures: 5, + MaxFailureRatio: 0.1, + }, + args: args{ + counts: gobreaker.Counts{ + Requests: 100, + ConsecutiveFailures: 5, + TotalFailures: 10, + }, + }, + want: false, + }, + { + name: "consecutive exceeded", + fields: fields{ + MaxConsecutiveFailures: 5, + MaxFailureRatio: 0.1, + }, + args: args{ + counts: gobreaker.Counts{ + Requests: 100, + ConsecutiveFailures: 6, + TotalFailures: 0, + }, + }, + want: true, + }, + { + name: "ratio exceeded", + fields: fields{ + MaxConsecutiveFailures: 5, + MaxFailureRatio: 0.1, + }, + args: args{ + counts: gobreaker.Counts{ + Requests: 100, + ConsecutiveFailures: 1, + TotalFailures: 11, + }, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config := &CBConfig{ + MaxConsecutiveFailures: tt.fields.MaxConsecutiveFailures, + MaxFailureRatio: tt.fields.MaxFailureRatio, + } + if got := config.readyToTrip(tt.args.counts); got != tt.want { + t.Errorf("CBConfig.readyToTrip() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_redisCache_limiter(t *testing.T) { + c, _ := prepareCache(t, cache.Config{}, withCircuitBreakerOption( + &CBConfig{ + MaxConsecutiveFailures: 2, + MaxFailureRatio: 0.4, + Timeout: 100 * time.Millisecond, + MaxRetryRequests: 1, + }, + )) + + ctx := context.Background() + canceledCtx, cancel := context.WithCancel(ctx) + cancel() + timedOutCtx, cancel := context.WithTimeout(ctx, -1) + defer cancel() + + // CB is and should remain closed + for i := 0; i < 10; i++ { + err := c.Truncate(ctx) + require.NoError(t, err) + } + for i := 0; i < 10; i++ { + err := c.Truncate(canceledCtx) + require.ErrorIs(t, err, context.Canceled) + } + + // Timeout err should open the CB after more than 2 failures + for i := 0; i < 3; i++ { + err := c.Truncate(timedOutCtx) + if i > 2 { + require.ErrorIs(t, err, gobreaker.ErrOpenState) + } else { + require.ErrorIs(t, err, context.DeadlineExceeded) + } + } + + time.Sleep(200 * time.Millisecond) + + // CB should be half-open. If the first command fails, the CB will be Open again + err := c.Truncate(timedOutCtx) + require.ErrorIs(t, err, context.DeadlineExceeded) + err = c.Truncate(timedOutCtx) + require.ErrorIs(t, err, gobreaker.ErrOpenState) + + // Reset the DB to closed + time.Sleep(200 * time.Millisecond) + err = c.Truncate(ctx) + require.NoError(t, err) + + // Exceed the ratio + err = c.Truncate(timedOutCtx) + require.ErrorIs(t, err, context.DeadlineExceeded) + err = c.Truncate(ctx) + require.ErrorIs(t, err, gobreaker.ErrOpenState) +} diff --git a/internal/cache/connector/redis/connector.go b/internal/cache/connector/redis/connector.go index 2d0498dfa0..a10a0c25d0 100644 --- a/internal/cache/connector/redis/connector.go +++ b/internal/cache/connector/redis/connector.go @@ -105,6 +105,8 @@ type Config struct { // Add suffix to client name. Default is empty. IdentitySuffix string + + CircuitBreaker *CBConfig } type Connector struct { @@ -146,6 +148,7 @@ func optionsFromConfig(c Config) *redis.Options { ConnMaxLifetime: c.ConnMaxLifetime, DisableIndentity: c.DisableIndentity, IdentitySuffix: c.IdentitySuffix, + Limiter: newLimiter(c.CircuitBreaker, c.MaxActiveConns), } if c.EnableTLS { opts.TLSConfig = new(tls.Config) diff --git a/internal/cache/connector/redis/redis_test.go b/internal/cache/connector/redis/redis_test.go index 3f45be1502..1909f55e44 100644 --- a/internal/cache/connector/redis/redis_test.go +++ b/internal/cache/connector/redis/redis_test.go @@ -6,7 +6,6 @@ import ( "time" "github.com/alicebob/miniredis/v2" - "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/zitadel/logging" @@ -689,26 +688,34 @@ func Test_redisCache_Truncate(t *testing.T) { } } -func prepareCache(t *testing.T, conf cache.Config) (cache.Cache[testIndex, string, *testObject], *miniredis.Miniredis) { +func prepareCache(t *testing.T, conf cache.Config, options ...func(*Config)) (cache.Cache[testIndex, string, *testObject], *miniredis.Miniredis) { conf.Log = &logging.Config{ Level: "debug", AddSource: true, } server := miniredis.RunT(t) server.Select(testDB) - client := redis.NewClient(&redis.Options{ - Network: "tcp", - Addr: server.Addr(), - }) + + connConfig := Config{ + Enabled: true, + Network: "tcp", + Addr: server.Addr(), + DisableIndentity: true, + } + for _, option := range options { + option(&connConfig) + } + connector := NewConnector(connConfig) t.Cleanup(func() { - client.Close() + connector.Close() server.Close() }) - connector := NewConnector(Config{ - Enabled: true, - Network: "tcp", - Addr: server.Addr(), - }) c := NewCache[testIndex, string, *testObject](conf, connector, testDB, testIndices) return c, server } + +func withCircuitBreakerOption(cb *CBConfig) func(*Config) { + return func(c *Config) { + c.CircuitBreaker = cb + } +} diff --git a/internal/cache/purpose_enumer.go b/internal/cache/purpose_enumer.go index bae47476ff..47ad167d70 100644 --- a/internal/cache/purpose_enumer.go +++ b/internal/cache/purpose_enumer.go @@ -7,11 +7,11 @@ import ( "strings" ) -const _PurposeName = "unspecifiedauthz_instancemilestones" +const _PurposeName = "unspecifiedauthz_instancemilestonesorganization" -var _PurposeIndex = [...]uint8{0, 11, 25, 35} +var _PurposeIndex = [...]uint8{0, 11, 25, 35, 47} -const _PurposeLowerName = "unspecifiedauthz_instancemilestones" +const _PurposeLowerName = "unspecifiedauthz_instancemilestonesorganization" func (i Purpose) String() string { if i < 0 || i >= Purpose(len(_PurposeIndex)-1) { @@ -27,9 +27,10 @@ func _PurposeNoOp() { _ = x[PurposeUnspecified-(0)] _ = x[PurposeAuthzInstance-(1)] _ = x[PurposeMilestones-(2)] + _ = x[PurposeOrganization-(3)] } -var _PurposeValues = []Purpose{PurposeUnspecified, PurposeAuthzInstance, PurposeMilestones} +var _PurposeValues = []Purpose{PurposeUnspecified, PurposeAuthzInstance, PurposeMilestones, PurposeOrganization} var _PurposeNameToValueMap = map[string]Purpose{ _PurposeName[0:11]: PurposeUnspecified, @@ -38,12 +39,15 @@ var _PurposeNameToValueMap = map[string]Purpose{ _PurposeLowerName[11:25]: PurposeAuthzInstance, _PurposeName[25:35]: PurposeMilestones, _PurposeLowerName[25:35]: PurposeMilestones, + _PurposeName[35:47]: PurposeOrganization, + _PurposeLowerName[35:47]: PurposeOrganization, } var _PurposeNames = []string{ _PurposeName[0:11], _PurposeName[11:25], _PurposeName[25:35], + _PurposeName[35:47], } // PurposeString retrieves an enum value from the enum constants string name. diff --git a/internal/command/action_v2_execution_test.go b/internal/command/action_v2_execution_test.go index be05929695..6833125a0a 100644 --- a/internal/command/action_v2_execution_test.go +++ b/internal/command/action_v2_execution_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/execution" @@ -172,6 +173,12 @@ func TestCommands_SetExecutionRequest(t *testing.T) { "https://example.com", time.Second, true, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("12345678"), + }, ), ), ), @@ -221,6 +228,12 @@ func TestCommands_SetExecutionRequest(t *testing.T) { "https://example.com", time.Second, true, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("12345678"), + }, ), ), ), @@ -270,6 +283,12 @@ func TestCommands_SetExecutionRequest(t *testing.T) { "https://example.com", time.Second, true, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("12345678"), + }, ), ), ), @@ -836,6 +855,12 @@ func TestCommands_SetExecutionResponse(t *testing.T) { "https://example.com", time.Second, true, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("12345678"), + }, ), ), expectPushFailed( @@ -930,6 +955,12 @@ func TestCommands_SetExecutionResponse(t *testing.T) { "https://example.com", time.Second, true, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("12345678"), + }, ), ), ), diff --git a/internal/command/action_v2_target.go b/internal/command/action_v2_target.go index d1f06b79b2..95dd097ed0 100644 --- a/internal/command/action_v2_target.go +++ b/internal/command/action_v2_target.go @@ -5,6 +5,8 @@ import ( "net/url" "time" + "github.com/zitadel/zitadel/internal/command/preparation" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore/v1/models" "github.com/zitadel/zitadel/internal/repository/target" @@ -19,6 +21,8 @@ type AddTarget struct { Endpoint string Timeout time.Duration InterruptOnError bool + + SigningKey string } func (a *AddTarget) IsValid() error { @@ -58,7 +62,11 @@ func (c *Commands) AddTarget(ctx context.Context, add *AddTarget, resourceOwner if wm.State.Exists() { return nil, zerrors.ThrowAlreadyExists(nil, "INSTANCE-9axkz0jvzm", "Errors.Target.AlreadyExists") } - + code, err := c.newSigningKey(ctx, c.eventstore.Filter, c.targetEncryption) //nolint + if err != nil { + return nil, err + } + add.SigningKey = code.PlainCode() pushedEvents, err := c.eventstore.Push(ctx, target.NewAddedEvent( ctx, TargetAggregateFromWriteModel(&wm.WriteModel), @@ -67,6 +75,7 @@ func (c *Commands) AddTarget(ctx context.Context, add *AddTarget, resourceOwner add.Endpoint, add.Timeout, add.InterruptOnError, + code.Crypted, )) if err != nil { return nil, err @@ -85,6 +94,9 @@ type ChangeTarget struct { Endpoint *string Timeout *time.Duration InterruptOnError *bool + + ExpirationSigningKey bool + SigningKey *string } func (a *ChangeTarget) IsValid() error { @@ -120,6 +132,17 @@ func (c *Commands) ChangeTarget(ctx context.Context, change *ChangeTarget, resou if !existing.State.Exists() { return nil, zerrors.ThrowNotFound(nil, "COMMAND-xj14f2cccn", "Errors.Target.NotFound") } + + var changedSigningKey *crypto.CryptoValue + if change.ExpirationSigningKey { + code, err := c.newSigningKey(ctx, c.eventstore.Filter, c.targetEncryption) //nolint + if err != nil { + return nil, err + } + changedSigningKey = code.Crypted + change.SigningKey = &code.Plain + } + changedEvent := existing.NewChangedEvent( ctx, TargetAggregateFromWriteModel(&existing.WriteModel), @@ -127,7 +150,9 @@ func (c *Commands) ChangeTarget(ctx context.Context, change *ChangeTarget, resou change.TargetType, change.Endpoint, change.Timeout, - change.InterruptOnError) + change.InterruptOnError, + changedSigningKey, + ) if changedEvent == nil { return writeModelToObjectDetails(&existing.WriteModel), nil } @@ -184,3 +209,7 @@ func (c *Commands) getTargetWriteModelByID(ctx context.Context, id string, resou } return wm, nil } + +func (c *Commands) newSigningKey(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) { + return c.newEncryptedCodeWithDefault(ctx, filter, domain.SecretGeneratorTypeSigningKey, alg, c.defaultSecretGenerators.SigningKey) +} diff --git a/internal/command/action_v2_target_model.go b/internal/command/action_v2_target_model.go index 24dd76c80a..cf20c9923d 100644 --- a/internal/command/action_v2_target_model.go +++ b/internal/command/action_v2_target_model.go @@ -5,6 +5,7 @@ import ( "slices" "time" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/target" @@ -18,6 +19,7 @@ type TargetWriteModel struct { Endpoint string Timeout time.Duration InterruptOnError bool + SigningKey *crypto.CryptoValue State domain.TargetState } @@ -41,6 +43,7 @@ func (wm *TargetWriteModel) Reduce() error { wm.Endpoint = e.Endpoint wm.Timeout = e.Timeout wm.State = domain.TargetActive + wm.SigningKey = e.SigningKey case *target.ChangedEvent: if e.Name != nil { wm.Name = *e.Name @@ -57,6 +60,9 @@ func (wm *TargetWriteModel) Reduce() error { if e.InterruptOnError != nil { wm.InterruptOnError = *e.InterruptOnError } + if e.SigningKey != nil { + wm.SigningKey = e.SigningKey + } case *target.RemovedEvent: wm.State = domain.TargetRemoved } @@ -84,6 +90,7 @@ func (wm *TargetWriteModel) NewChangedEvent( endpoint *string, timeout *time.Duration, interruptOnError *bool, + signingKey *crypto.CryptoValue, ) *target.ChangedEvent { changes := make([]target.Changes, 0) if name != nil && wm.Name != *name { @@ -101,6 +108,10 @@ func (wm *TargetWriteModel) NewChangedEvent( if interruptOnError != nil && wm.InterruptOnError != *interruptOnError { changes = append(changes, target.ChangeInterruptOnError(*interruptOnError)) } + // if signingkey is set, update it as it is encrypted + if signingKey != nil { + changes = append(changes, target.ChangeSigningKey(signingKey)) + } if len(changes) == 0 { return nil } diff --git a/internal/command/action_v2_target_model_test.go b/internal/command/action_v2_target_model_test.go index 8042da23b1..e8c40c04c8 100644 --- a/internal/command/action_v2_target_model_test.go +++ b/internal/command/action_v2_target_model_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/target" @@ -20,6 +21,12 @@ func targetAddEvent(aggID, resourceOwner string) *target.AddedEvent { "https://example.com", time.Second, false, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("12345678"), + }, ) } diff --git a/internal/command/action_v2_target_test.go b/internal/command/action_v2_target_test.go index 12f76c4629..ed7d6163a0 100644 --- a/internal/command/action_v2_target_test.go +++ b/internal/command/action_v2_target_test.go @@ -8,6 +8,7 @@ import ( "github.com/muhlemmer/gu" "github.com/stretchr/testify/assert" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/v1/models" @@ -19,8 +20,10 @@ import ( func TestCommands_AddTarget(t *testing.T) { type fields struct { - eventstore func(t *testing.T) *eventstore.Eventstore - idGenerator id.Generator + eventstore func(t *testing.T) *eventstore.Eventstore + idGenerator id.Generator + newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc + defaultSecretGenerators *SecretGenerators } type args struct { ctx context.Context @@ -132,10 +135,18 @@ func TestCommands_AddTarget(t *testing.T) { "https://example.com", time.Second, false, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("12345678"), + }, ), ), ), - idGenerator: mock.ExpectID(t, "id1"), + idGenerator: mock.ExpectID(t, "id1"), + newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("12345678", time.Hour), + defaultSecretGenerators: &SecretGenerators{}, }, args{ ctx: context.Background(), @@ -186,7 +197,9 @@ func TestCommands_AddTarget(t *testing.T) { targetAddEvent("id1", "instance"), ), ), - idGenerator: mock.ExpectID(t, "id1"), + idGenerator: mock.ExpectID(t, "id1"), + newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("12345678", time.Hour), + defaultSecretGenerators: &SecretGenerators{}, }, args{ ctx: context.Background(), @@ -219,7 +232,9 @@ func TestCommands_AddTarget(t *testing.T) { }(), ), ), - idGenerator: mock.ExpectID(t, "id1"), + idGenerator: mock.ExpectID(t, "id1"), + newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("12345678", time.Hour), + defaultSecretGenerators: &SecretGenerators{}, }, args{ ctx: context.Background(), @@ -244,8 +259,10 @@ func TestCommands_AddTarget(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Commands{ - eventstore: tt.fields.eventstore(t), - idGenerator: tt.fields.idGenerator, + eventstore: tt.fields.eventstore(t), + idGenerator: tt.fields.idGenerator, + newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault, + defaultSecretGenerators: tt.fields.defaultSecretGenerators, } details, err := c.AddTarget(tt.args.ctx, tt.args.add, tt.args.resourceOwner) if tt.res.err == nil { @@ -264,7 +281,9 @@ func TestCommands_AddTarget(t *testing.T) { func TestCommands_ChangeTarget(t *testing.T) { type fields struct { - eventstore func(t *testing.T) *eventstore.Eventstore + eventstore func(t *testing.T) *eventstore.Eventstore + newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc + defaultSecretGenerators *SecretGenerators } type args struct { ctx context.Context @@ -510,10 +529,18 @@ func TestCommands_ChangeTarget(t *testing.T) { target.ChangeTargetType(domain.TargetTypeCall), target.ChangeTimeout(10 * time.Second), target.ChangeInterruptOnError(true), + target.ChangeSigningKey(&crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("12345678"), + }), }, ), ), ), + newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("12345678", time.Hour), + defaultSecretGenerators: &SecretGenerators{}, }, args{ ctx: context.Background(), @@ -521,11 +548,12 @@ func TestCommands_ChangeTarget(t *testing.T) { ObjectRoot: models.ObjectRoot{ AggregateID: "id1", }, - Name: gu.Ptr("name2"), - Endpoint: gu.Ptr("https://example2.com"), - TargetType: gu.Ptr(domain.TargetTypeCall), - Timeout: gu.Ptr(10 * time.Second), - InterruptOnError: gu.Ptr(true), + Name: gu.Ptr("name2"), + Endpoint: gu.Ptr("https://example2.com"), + TargetType: gu.Ptr(domain.TargetTypeCall), + Timeout: gu.Ptr(10 * time.Second), + InterruptOnError: gu.Ptr(true), + ExpirationSigningKey: true, }, resourceOwner: "instance", }, @@ -540,7 +568,9 @@ func TestCommands_ChangeTarget(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Commands{ - eventstore: tt.fields.eventstore(t), + eventstore: tt.fields.eventstore(t), + newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault, + defaultSecretGenerators: tt.fields.defaultSecretGenerators, } details, err := c.ChangeTarget(tt.args.ctx, tt.args.change, tt.args.resourceOwner) if tt.res.err == nil { diff --git a/internal/command/command.go b/internal/command/command.go index bc3f189a4a..ab047fccdb 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -54,6 +54,7 @@ type Commands struct { smtpEncryption crypto.EncryptionAlgorithm smsEncryption crypto.EncryptionAlgorithm userEncryption crypto.EncryptionAlgorithm + targetEncryption crypto.EncryptionAlgorithm userPasswordHasher *crypto.Hasher secretHasher *crypto.Hasher machineKeySize int @@ -108,7 +109,7 @@ func StartCommands( externalDomain string, externalSecure bool, externalPort uint16, - idpConfigEncryption, otpEncryption, smtpEncryption, smsEncryption, userEncryption, domainVerificationEncryption, oidcEncryption, samlEncryption crypto.EncryptionAlgorithm, + idpConfigEncryption, otpEncryption, smtpEncryption, smsEncryption, userEncryption, domainVerificationEncryption, oidcEncryption, samlEncryption, targetEncryption crypto.EncryptionAlgorithm, httpClient *http.Client, permissionCheck domain.PermissionCheck, sessionTokenVerifier func(ctx context.Context, sessionToken string, sessionID string, tokenID string) (err error), @@ -153,6 +154,7 @@ func StartCommands( smtpEncryption: smtpEncryption, smsEncryption: smsEncryption, userEncryption: userEncryption, + targetEncryption: targetEncryption, userPasswordHasher: userPasswordHasher, secretHasher: secretHasher, machineKeySize: int(defaults.SecretGenerators.MachineKeySize), diff --git a/internal/command/instance.go b/internal/command/instance.go index 3491aaf4a2..c5ac4d8472 100644 --- a/internal/command/instance.go +++ b/internal/command/instance.go @@ -157,6 +157,7 @@ type SecretGenerators struct { OTPSMS *crypto.GeneratorConfig OTPEmail *crypto.GeneratorConfig InviteCode *crypto.GeneratorConfig + SigningKey *crypto.GeneratorConfig } type ZitadelConfig struct { diff --git a/internal/command/instance_debug_notification_log_test.go b/internal/command/instance_debug_notification_log_test.go index 9190064f60..32fdd06618 100644 --- a/internal/command/instance_debug_notification_log_test.go +++ b/internal/command/instance_debug_notification_log_test.go @@ -199,7 +199,7 @@ func TestCommandSide_ChangeDebugNotificationProviderLog(t *testing.T) { }, }, { - name: "change, ok", + name: "change, ok 1", fields: fields{ eventstore: eventstoreExpect( t, @@ -232,7 +232,7 @@ func TestCommandSide_ChangeDebugNotificationProviderLog(t *testing.T) { }, }, { - name: "change, ok", + name: "change, ok 2", fields: fields{ eventstore: eventstoreExpect( t, diff --git a/internal/command/notification.go b/internal/command/notification.go new file mode 100644 index 0000000000..b0524afa89 --- /dev/null +++ b/internal/command/notification.go @@ -0,0 +1,162 @@ +package command + +import ( + "context" + "database/sql" + "time" + + "github.com/zitadel/zitadel/internal/crypto" + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/internal/repository/notification" +) + +type NotificationRequest struct { + UserID string + UserResourceOwner string + TriggerOrigin string + URLTemplate string + Code *crypto.CryptoValue + CodeExpiry time.Duration + EventType eventstore.EventType + NotificationType domain.NotificationType + MessageType string + UnverifiedNotificationChannel bool + Args *domain.NotificationArguments + AggregateID string + AggregateResourceOwner string + IsOTP bool + RequiresPreviousDomain bool +} + +type NotificationRetryRequest struct { + NotificationRequest + BackOff time.Duration + NotifyUser *query.NotifyUser +} + +func NewNotificationRequest( + userID, resourceOwner, triggerOrigin string, + eventType eventstore.EventType, + notificationType domain.NotificationType, + messageType string, +) *NotificationRequest { + return &NotificationRequest{ + UserID: userID, + UserResourceOwner: resourceOwner, + TriggerOrigin: triggerOrigin, + EventType: eventType, + NotificationType: notificationType, + MessageType: messageType, + } +} + +func (r *NotificationRequest) WithCode(code *crypto.CryptoValue, expiry time.Duration) *NotificationRequest { + r.Code = code + r.CodeExpiry = expiry + return r +} + +func (r *NotificationRequest) WithURLTemplate(urlTemplate string) *NotificationRequest { + r.URLTemplate = urlTemplate + return r +} + +func (r *NotificationRequest) WithUnverifiedChannel() *NotificationRequest { + r.UnverifiedNotificationChannel = true + return r +} + +func (r *NotificationRequest) WithArgs(args *domain.NotificationArguments) *NotificationRequest { + r.Args = args + return r +} + +func (r *NotificationRequest) WithAggregate(id, resourceOwner string) *NotificationRequest { + r.AggregateID = id + r.AggregateResourceOwner = resourceOwner + return r +} + +func (r *NotificationRequest) WithOTP() *NotificationRequest { + r.IsOTP = true + return r +} + +func (r *NotificationRequest) WithPreviousDomain() *NotificationRequest { + r.RequiresPreviousDomain = true + return r +} + +// RequestNotification writes a new notification.RequestEvent with the notification.Aggregate to the eventstore +func (c *Commands) RequestNotification( + ctx context.Context, + resourceOwner string, + request *NotificationRequest, +) error { + id, err := c.idGenerator.Next() + if err != nil { + return err + } + _, err = c.eventstore.Push(ctx, notification.NewRequestedEvent(ctx, ¬ification.NewAggregate(id, resourceOwner).Aggregate, + request.UserID, + request.UserResourceOwner, + request.AggregateID, + request.AggregateResourceOwner, + request.TriggerOrigin, + request.URLTemplate, + request.Code, + request.CodeExpiry, + request.EventType, + request.NotificationType, + request.MessageType, + request.UnverifiedNotificationChannel, + request.IsOTP, + request.RequiresPreviousDomain, + request.Args)) + return err +} + +// NotificationCanceled writes a new notification.CanceledEvent with the notification.Aggregate to the eventstore +func (c *Commands) NotificationCanceled(ctx context.Context, tx *sql.Tx, id, resourceOwner string, requestError error) error { + var errorMessage string + if requestError != nil { + errorMessage = requestError.Error() + } + _, err := c.eventstore.PushWithClient(ctx, tx, notification.NewCanceledEvent(ctx, ¬ification.NewAggregate(id, resourceOwner).Aggregate, errorMessage)) + return err +} + +// NotificationSent writes a new notification.SentEvent with the notification.Aggregate to the eventstore +func (c *Commands) NotificationSent(ctx context.Context, tx *sql.Tx, id, resourceOwner string) error { + _, err := c.eventstore.PushWithClient(ctx, tx, notification.NewSentEvent(ctx, ¬ification.NewAggregate(id, resourceOwner).Aggregate)) + return err +} + +// NotificationRetryRequested writes a new notification.RetryRequestEvent with the notification.Aggregate to the eventstore +func (c *Commands) NotificationRetryRequested(ctx context.Context, tx *sql.Tx, id, resourceOwner string, request *NotificationRetryRequest, requestError error) error { + var errorMessage string + if requestError != nil { + errorMessage = requestError.Error() + } + _, err := c.eventstore.PushWithClient(ctx, tx, notification.NewRetryRequestedEvent(ctx, ¬ification.NewAggregate(id, resourceOwner).Aggregate, + request.UserID, + request.UserResourceOwner, + request.AggregateID, + request.AggregateResourceOwner, + request.TriggerOrigin, + request.URLTemplate, + request.Code, + request.CodeExpiry, + request.EventType, + request.NotificationType, + request.MessageType, + request.UnverifiedNotificationChannel, + request.IsOTP, + request.Args, + request.NotifyUser, + request.BackOff, + errorMessage)) + return err +} diff --git a/internal/command/org_domain_model.go b/internal/command/org_domain_model.go index 43ab9e52dd..b387e6f4b7 100644 --- a/internal/command/org_domain_model.go +++ b/internal/command/org_domain_model.go @@ -202,6 +202,8 @@ func (wm *OrgDomainVerifiedWriteModel) AppendEvents(events ...eventstore.Event) continue } wm.WriteModel.AppendEvents(e) + case *org.OrgRemovedEvent: + wm.WriteModel.AppendEvents(e) } } } @@ -214,6 +216,11 @@ func (wm *OrgDomainVerifiedWriteModel) Reduce() error { wm.ResourceOwner = e.Aggregate().ResourceOwner case *org.DomainRemovedEvent: wm.Verified = false + case *org.OrgRemovedEvent: + if wm.ResourceOwner != e.Aggregate().ID { + continue + } + wm.Verified = false } } return wm.WriteModel.Reduce() @@ -225,6 +232,7 @@ func (wm *OrgDomainVerifiedWriteModel) Query() *eventstore.SearchQueryBuilder { AggregateTypes(org.AggregateType). EventTypes( org.OrgDomainVerifiedEventType, - org.OrgDomainRemovedEventType). + org.OrgDomainRemovedEventType, + org.OrgRemovedEventType). Builder() } diff --git a/internal/command/user_human_otp.go b/internal/command/user_human_otp.go index e505288cbd..97596aabd8 100644 --- a/internal/command/user_human_otp.go +++ b/internal/command/user_human_otp.go @@ -7,7 +7,6 @@ import ( "github.com/pquerna/otp" "github.com/zitadel/logging" - "github.com/zitadel/zitadel/internal/api/authz" http_util "github.com/zitadel/zitadel/internal/api/http" "github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/crypto" @@ -79,10 +78,8 @@ func (c *Commands) createHumanTOTP(ctx context.Context, userID, resourceOwner st logging.WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get human for loginname") return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-SqyJz", "Errors.User.NotFound") } - if authz.GetCtxData(ctx).UserID != userID { - if err := c.checkPermission(ctx, domain.PermissionUserCredentialWrite, human.ResourceOwner, userID); err != nil { - return nil, err - } + if err := c.checkPermissionUpdateUserCredentials(ctx, human.ResourceOwner, userID); err != nil { + return nil, err } org, err := c.getOrg(ctx, human.ResourceOwner) if err != nil { @@ -139,10 +136,8 @@ func (c *Commands) HumanCheckMFATOTPSetup(ctx context.Context, userID, code, use if err != nil { return nil, err } - if authz.GetCtxData(ctx).UserID != userID { - if err := c.checkPermission(ctx, domain.PermissionUserCredentialWrite, existingOTP.ResourceOwner, userID); err != nil { - return nil, err - } + if err := c.checkPermissionUpdateUserCredentials(ctx, existingOTP.ResourceOwner, userID); err != nil { + return nil, err } if existingOTP.State == domain.MFAStateUnspecified || existingOTP.State == domain.MFAStateRemoved { return nil, zerrors.ThrowNotFound(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotExisting") @@ -242,10 +237,8 @@ func (c *Commands) HumanRemoveTOTP(ctx context.Context, userID, resourceOwner st if existingOTP.State == domain.MFAStateUnspecified || existingOTP.State == domain.MFAStateRemoved { return nil, zerrors.ThrowNotFound(nil, "COMMAND-Hd9sd", "Errors.User.MFA.OTP.NotExisting") } - if userID != authz.GetCtxData(ctx).UserID { - if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingOTP.ResourceOwner, userID); err != nil { - return nil, err - } + if err := c.checkPermissionUpdateUser(ctx, existingOTP.ResourceOwner, userID); err != nil { + return nil, err } userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel) pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanOTPRemovedEvent(ctx, userAgg)) @@ -286,10 +279,8 @@ func (c *Commands) addHumanOTPSMS(ctx context.Context, userID, resourceOwner str if err != nil { return nil, err } - if authz.GetCtxData(ctx).UserID != userID { - if err := c.checkPermission(ctx, domain.PermissionUserCredentialWrite, otpWriteModel.ResourceOwner(), userID); err != nil { - return nil, err - } + if err := c.checkPermissionUpdateUserCredentials(ctx, otpWriteModel.ResourceOwner(), userID); err != nil { + return nil, err } if otpWriteModel.otpAdded { return nil, zerrors.ThrowAlreadyExists(nil, "COMMAND-Ad3g2", "Errors.User.MFA.OTP.AlreadyReady") @@ -318,10 +309,8 @@ func (c *Commands) RemoveHumanOTPSMS(ctx context.Context, userID, resourceOwner if err != nil { return nil, err } - if userID != authz.GetCtxData(ctx).UserID { - if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingOTP.WriteModel.ResourceOwner, userID); err != nil { - return nil, err - } + if err := c.checkPermissionUpdateUser(ctx, existingOTP.WriteModel.ResourceOwner, userID); err != nil { + return nil, err } if !existingOTP.otpAdded { return nil, zerrors.ThrowNotFound(nil, "COMMAND-Sr3h3", "Errors.User.MFA.OTP.NotExisting") @@ -420,10 +409,8 @@ func (c *Commands) addHumanOTPEmail(ctx context.Context, userID, resourceOwner s if err != nil { return nil, err } - if authz.GetCtxData(ctx).UserID != userID { - if err := c.checkPermission(ctx, domain.PermissionUserCredentialWrite, otpWriteModel.ResourceOwner(), userID); err != nil { - return nil, err - } + if err := c.checkPermissionUpdateUserCredentials(ctx, otpWriteModel.ResourceOwner(), userID); err != nil { + return nil, err } if otpWriteModel.otpAdded { return nil, zerrors.ThrowAlreadyExists(nil, "COMMAND-MKL2s", "Errors.User.MFA.OTP.AlreadyReady") @@ -452,10 +439,8 @@ func (c *Commands) RemoveHumanOTPEmail(ctx context.Context, userID, resourceOwne if err != nil { return nil, err } - if userID != authz.GetCtxData(ctx).UserID { - if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingOTP.WriteModel.ResourceOwner, userID); err != nil { - return nil, err - } + if err := c.checkPermissionUpdateUser(ctx, existingOTP.WriteModel.ResourceOwner, userID); err != nil { + return nil, err } if !existingOTP.otpAdded { return nil, zerrors.ThrowNotFound(nil, "COMMAND-b312D", "Errors.User.MFA.OTP.NotExisting") diff --git a/internal/command/user_human_password.go b/internal/command/user_human_password.go index 9b686f88b7..a67a4b91da 100644 --- a/internal/command/user_human_password.go +++ b/internal/command/user_human_password.go @@ -110,7 +110,7 @@ type setPasswordVerification func(ctx context.Context) (newEncodedPassword strin // setPasswordWithPermission returns a permission check as [setPasswordVerification] implementation func (c *Commands) setPasswordWithPermission(userID, orgID string) setPasswordVerification { return func(ctx context.Context) (_ string, err error) { - return "", c.checkPermission(ctx, domain.PermissionUserWrite, orgID, userID) + return "", c.checkPermissionUpdateUser(ctx, orgID, userID) } } diff --git a/internal/command/user_human_webauthn.go b/internal/command/user_human_webauthn.go index 3555466359..3b8a66e0d5 100644 --- a/internal/command/user_human_webauthn.go +++ b/internal/command/user_human_webauthn.go @@ -6,7 +6,6 @@ import ( "github.com/zitadel/logging" - "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" @@ -146,10 +145,8 @@ func (c *Commands) addHumanWebAuthN(ctx context.Context, userID, resourceowner, if err != nil { return nil, nil, nil, err } - if authz.GetCtxData(ctx).UserID != userID { - if err = c.checkPermission(ctx, domain.PermissionUserCredentialWrite, user.ResourceOwner, userID); err != nil { - return nil, nil, nil, err - } + if err := c.checkPermissionUpdateUserCredentials(ctx, user.ResourceOwner, userID); err != nil { + return nil, nil, nil, err } org, err := c.getOrg(ctx, user.ResourceOwner) if err != nil { @@ -603,10 +600,9 @@ func (c *Commands) removeHumanWebAuthN(ctx context.Context, userID, webAuthNID, if existingWebAuthN.State == domain.MFAStateUnspecified || existingWebAuthN.State == domain.MFAStateRemoved { return nil, zerrors.ThrowNotFound(nil, "COMMAND-DAfb2", "Errors.User.WebAuthN.NotFound") } - if userID != authz.GetCtxData(ctx).UserID { - if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingWebAuthN.ResourceOwner, existingWebAuthN.AggregateID); err != nil { - return nil, err - } + + if err := c.checkPermissionUpdateUser(ctx, existingWebAuthN.ResourceOwner, existingWebAuthN.AggregateID); err != nil { + return nil, err } userAgg := UserAggregateFromWriteModel(&existingWebAuthN.WriteModel) diff --git a/internal/command/user_v2.go b/internal/command/user_v2.go index 032ac0b8f7..033a16eb9a 100644 --- a/internal/command/user_v2.go +++ b/internal/command/user_v2.go @@ -127,6 +127,16 @@ func (c *Commands) checkPermissionUpdateUser(ctx context.Context, resourceOwner, return nil } +func (c *Commands) checkPermissionUpdateUserCredentials(ctx context.Context, resourceOwner, userID string) error { + if userID != "" && userID == authz.GetCtxData(ctx).UserID { + return nil + } + if err := c.checkPermission(ctx, domain.PermissionUserCredentialWrite, resourceOwner, userID); err != nil { + return err + } + return nil +} + func (c *Commands) checkPermissionDeleteUser(ctx context.Context, resourceOwner, userID string) error { if userID != "" && userID == authz.GetCtxData(ctx).UserID { return nil diff --git a/internal/command/user_v2_email.go b/internal/command/user_v2_email.go index cc81f7399c..1618e2cd48 100644 --- a/internal/command/user_v2_email.go +++ b/internal/command/user_v2_email.go @@ -6,7 +6,6 @@ import ( "github.com/zitadel/logging" - "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" @@ -118,10 +117,8 @@ func (c *Commands) changeUserEmailWithGeneratorEvents(ctx context.Context, userI if err != nil { return nil, err } - if authz.GetCtxData(ctx).UserID != userID { - if err = c.checkPermission(ctx, domain.PermissionUserWrite, cmd.aggregate.ResourceOwner, userID); err != nil { - return nil, err - } + if err = c.checkPermissionUpdateUser(ctx, cmd.aggregate.ResourceOwner, userID); err != nil { + return nil, err } if err = cmd.Change(ctx, domain.EmailAddress(email)); err != nil { return nil, err @@ -137,10 +134,8 @@ func (c *Commands) resendUserEmailCodeWithGeneratorEvents(ctx context.Context, u if err != nil { return nil, err } - if authz.GetCtxData(ctx).UserID != userID { - if err = c.checkPermission(ctx, domain.PermissionUserWrite, cmd.aggregate.ResourceOwner, userID); err != nil { - return nil, err - } + if err = c.checkPermissionUpdateUser(ctx, cmd.aggregate.ResourceOwner, userID); err != nil { + return nil, err } if cmd.model.Code == nil { return nil, zerrors.ThrowPreconditionFailed(err, "EMAIL-5w5ilin4yt", "Errors.User.Code.Empty") diff --git a/internal/command/user_v2_human_test.go b/internal/command/user_v2_human_test.go index a50061c010..2b4399fb2a 100644 --- a/internal/command/user_v2_human_test.go +++ b/internal/command/user_v2_human_test.go @@ -1725,6 +1725,323 @@ func TestCommandSide_AddUserHuman(t *testing.T) { wantID: "user1", }, }, + { + name: "register human (validate domain), already verified", + fields: fields{ + eventstore: expectEventstore( + expectFilter(), + expectFilter( + eventFromEventPusher( + org.NewDomainPolicyAddedEvent(context.Background(), + &userAgg.Aggregate, + false, + true, + true, + ), + ), + ), + expectFilter( + eventFromEventPusher( + org.NewDomainVerifiedEvent(context.Background(), + &org.NewAggregate("existing").Aggregate, + "example.com", + ), + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), + newCode: mockEncryptedCode("userinit", time.Hour), + }, + args: args{ + ctx: context.Background(), + orgID: "org1", + human: &AddHuman{ + Username: "username@example.com", + FirstName: "firstname", + LastName: "lastname", + Email: Email{ + Address: "email@example.com", + }, + PreferredLanguage: language.English, + Register: true, + UserAgentID: "userAgentID", + AuthRequestID: "authRequestID", + }, + secretGenerator: GetMockSecretGenerator(t), + allowInitMail: true, + codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-SFd21", "Errors.User.DomainNotAllowedAsUsername")) + }, + }, + }, + { + name: "register human (validate domain), ok", + fields: fields{ + eventstore: expectEventstore( + expectFilter(), + expectFilter( + eventFromEventPusher( + org.NewDomainPolicyAddedEvent(context.Background(), + &userAgg.Aggregate, + false, + true, + true, + ), + ), + ), + expectFilter( + eventFromEventPusher( + org.NewDomainVerifiedEvent(context.Background(), + &org.NewAggregate(userAgg.ResourceOwner).Aggregate, + "example.com", + ), + ), + ), + expectPush( + user.NewHumanRegisteredEvent(context.Background(), + &userAgg.Aggregate, + "username@example.com", + "firstname", + "lastname", + "", + "firstname lastname", + language.English, + domain.GenderUnspecified, + "email@example.com", + false, + "userAgentID", + ), + user.NewHumanInitialCodeAddedEvent(context.Background(), + &userAgg.Aggregate, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("userinit"), + }, + time.Hour*1, + "authRequestID", + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), + newCode: mockEncryptedCode("userinit", time.Hour), + }, + args: args{ + ctx: context.Background(), + orgID: "org1", + human: &AddHuman{ + Username: "username@example.com", + FirstName: "firstname", + LastName: "lastname", + Email: Email{ + Address: "email@example.com", + }, + PreferredLanguage: language.English, + Register: true, + UserAgentID: "userAgentID", + AuthRequestID: "authRequestID", + }, + secretGenerator: GetMockSecretGenerator(t), + allowInitMail: true, + codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + res: res{ + want: &domain.ObjectDetails{ + Sequence: 0, + EventDate: time.Time{}, + ResourceOwner: "org1", + }, + wantID: "user1", + }, + }, + { + name: "register human (validate domain, domain removed), ok", + fields: fields{ + eventstore: expectEventstore( + expectFilter(), + expectFilter( + eventFromEventPusher( + org.NewDomainPolicyAddedEvent(context.Background(), + &userAgg.Aggregate, + false, + true, + true, + ), + ), + ), + expectFilter( + eventFromEventPusher( + org.NewDomainVerifiedEvent(context.Background(), + &org.NewAggregate("existing").Aggregate, + "example.com", + ), + ), + eventFromEventPusher( + org.NewDomainRemovedEvent(context.Background(), + &org.NewAggregate("existing").Aggregate, + "example.com", + true, + ), + ), + ), + expectPush( + user.NewHumanRegisteredEvent(context.Background(), + &userAgg.Aggregate, + "username@example.com", + "firstname", + "lastname", + "", + "firstname lastname", + language.English, + domain.GenderUnspecified, + "email@example.com", + false, + "userAgentID", + ), + user.NewHumanInitialCodeAddedEvent(context.Background(), + &userAgg.Aggregate, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("userinit"), + }, + time.Hour*1, + "authRequestID", + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), + newCode: mockEncryptedCode("userinit", time.Hour), + }, + args: args{ + ctx: context.Background(), + orgID: "org1", + human: &AddHuman{ + Username: "username@example.com", + FirstName: "firstname", + LastName: "lastname", + Email: Email{ + Address: "email@example.com", + }, + PreferredLanguage: language.English, + Register: true, + UserAgentID: "userAgentID", + AuthRequestID: "authRequestID", + }, + secretGenerator: GetMockSecretGenerator(t), + allowInitMail: true, + codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + res: res{ + want: &domain.ObjectDetails{ + Sequence: 0, + EventDate: time.Time{}, + ResourceOwner: "org1", + }, + wantID: "user1", + }, + }, + { + name: "register human (validate domain, org removed), ok", + fields: fields{ + eventstore: expectEventstore( + expectFilter(), + expectFilter( + eventFromEventPusher( + org.NewDomainPolicyAddedEvent(context.Background(), + &userAgg.Aggregate, + false, + true, + true, + ), + ), + ), + expectFilter( + eventFromEventPusher( + org.NewDomainVerifiedEvent(context.Background(), + &org.NewAggregate("existing").Aggregate, + "example.com", + ), + ), + eventFromEventPusher( + org.NewOrgRemovedEvent(context.Background(), + &org.NewAggregate("existing").Aggregate, + "org", + []string{}, + false, + []string{}, + []*domain.UserIDPLink{}, + []string{}, + ), + ), + ), + expectPush( + user.NewHumanRegisteredEvent(context.Background(), + &userAgg.Aggregate, + "username@example.com", + "firstname", + "lastname", + "", + "firstname lastname", + language.English, + domain.GenderUnspecified, + "email@example.com", + false, + "userAgentID", + ), + user.NewHumanInitialCodeAddedEvent(context.Background(), + &userAgg.Aggregate, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("userinit"), + }, + time.Hour*1, + "authRequestID", + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), + newCode: mockEncryptedCode("userinit", time.Hour), + }, + args: args{ + ctx: context.Background(), + orgID: "org1", + human: &AddHuman{ + Username: "username@example.com", + FirstName: "firstname", + LastName: "lastname", + Email: Email{ + Address: "email@example.com", + }, + PreferredLanguage: language.English, + Register: true, + UserAgentID: "userAgentID", + AuthRequestID: "authRequestID", + }, + secretGenerator: GetMockSecretGenerator(t), + allowInitMail: true, + codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + res: res{ + want: &domain.ObjectDetails{ + Sequence: 0, + EventDate: time.Time{}, + ResourceOwner: "org1", + }, + wantID: "user1", + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/command/user_v2_invite.go b/internal/command/user_v2_invite.go index 78b46a530e..1325d2e0c9 100644 --- a/internal/command/user_v2_invite.go +++ b/internal/command/user_v2_invite.go @@ -6,7 +6,6 @@ import ( "github.com/zitadel/logging" - "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" @@ -74,10 +73,8 @@ func (c *Commands) ResendInviteCode(ctx context.Context, userID, resourceOwner, if err != nil { return nil, err } - if authz.GetCtxData(ctx).UserID != userID { - if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingCode.ResourceOwner, userID); err != nil { - return nil, err - } + if err := c.checkPermissionUpdateUser(ctx, existingCode.ResourceOwner, userID); err != nil { + return nil, err } if !existingCode.UserState.Exists() { return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-H3b2a", "Errors.User.NotFound") diff --git a/internal/command/user_v2_passkey.go b/internal/command/user_v2_passkey.go index 897a1ab41d..a386049744 100644 --- a/internal/command/user_v2_passkey.go +++ b/internal/command/user_v2_passkey.go @@ -6,7 +6,6 @@ import ( "github.com/zitadel/logging" - "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" @@ -18,7 +17,7 @@ import ( // RegisterUserPasskey creates a passkey registration for the current authenticated user. // UserID, usually taken from the request is compared against the user ID in the context. func (c *Commands) RegisterUserPasskey(ctx context.Context, userID, resourceOwner, rpID string, authenticator domain.AuthenticatorAttachment) (*domain.WebAuthNRegistrationDetails, error) { - if err := authz.UserIDInCTX(ctx, userID); err != nil { + if err := c.checkPermissionUpdateUserCredentials(ctx, resourceOwner, userID); err != nil { return nil, err } return c.registerUserPasskey(ctx, userID, resourceOwner, rpID, authenticator) diff --git a/internal/command/user_v2_passkey_test.go b/internal/command/user_v2_passkey_test.go index a6ba470d2b..0d1009862c 100644 --- a/internal/command/user_v2_passkey_test.go +++ b/internal/command/user_v2_passkey_test.go @@ -34,8 +34,9 @@ func TestCommands_RegisterUserPasskey(t *testing.T) { } userAgg := &user.NewAggregate("user1", "org1").Aggregate type fields struct { - eventstore *eventstore.Eventstore - idGenerator id.Generator + eventstore func(t *testing.T) *eventstore.Eventstore + idGenerator id.Generator + checkPermission domain.PermissionCheck } type args struct { userID string @@ -51,18 +52,22 @@ func TestCommands_RegisterUserPasskey(t *testing.T) { wantErr error }{ { - name: "wrong user", + name: "no permission", + fields: fields{ + eventstore: expectEventstore(), + checkPermission: newMockPermissionCheckNotAllowed(), + }, args: args{ userID: "foo", resourceOwner: "org1", authenticator: domain.AuthenticatorAttachmentCrossPlattform, }, - wantErr: zerrors.ThrowPermissionDenied(nil, "AUTH-Bohd2", "Errors.User.UserIDWrong"), + wantErr: zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"), }, { name: "get human passwordless error", fields: fields{ - eventstore: eventstoreExpect(t, + eventstore: expectEventstore( expectFilterError(io.ErrClosedPipe), ), }, @@ -76,7 +81,7 @@ func TestCommands_RegisterUserPasskey(t *testing.T) { { name: "id generator error", fields: fields{ - eventstore: eventstoreExpect(t, + eventstore: expectEventstore( expectFilter(), // getHumanPasswordlessTokens expectFilter(eventFromEventPusher( user.NewHumanAddedEvent(ctx, @@ -118,9 +123,10 @@ func TestCommands_RegisterUserPasskey(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Commands{ - eventstore: tt.fields.eventstore, - idGenerator: tt.fields.idGenerator, - webauthnConfig: webauthnConfig, + eventstore: tt.fields.eventstore(t), + idGenerator: tt.fields.idGenerator, + webauthnConfig: webauthnConfig, + checkPermission: tt.fields.checkPermission, } _, err := c.RegisterUserPasskey(ctx, tt.args.userID, tt.args.resourceOwner, tt.args.rpID, tt.args.authenticator) require.ErrorIs(t, err, tt.wantErr) diff --git a/internal/command/user_v2_password.go b/internal/command/user_v2_password.go index 67bee2c28f..faa1fe14a6 100644 --- a/internal/command/user_v2_password.go +++ b/internal/command/user_v2_password.go @@ -4,7 +4,6 @@ import ( "context" "io" - "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/repository/user" "github.com/zitadel/zitadel/internal/zerrors" @@ -50,10 +49,8 @@ func (c *Commands) requestPasswordReset(ctx context.Context, userID string, retu if model.UserState == domain.UserStateInitial { return nil, nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Sfe4g", "Errors.User.NotInitialised") } - if authz.GetCtxData(ctx).UserID != userID { - if err = c.checkPermission(ctx, domain.PermissionUserWrite, model.ResourceOwner, userID); err != nil { - return nil, nil, err - } + if err = c.checkPermissionUpdateUser(ctx, model.ResourceOwner, userID); err != nil { + return nil, nil, err } var passwordCode *EncryptedCode var generatorID string diff --git a/internal/command/user_v2_phone.go b/internal/command/user_v2_phone.go index 8b754b36f3..8648f9a564 100644 --- a/internal/command/user_v2_phone.go +++ b/internal/command/user_v2_phone.go @@ -82,10 +82,8 @@ func (c *Commands) changeUserPhoneWithGenerator(ctx context.Context, userID, pho if err != nil { return nil, err } - if authz.GetCtxData(ctx).UserID != userID { - if err = c.checkPermission(ctx, domain.PermissionUserWrite, cmd.aggregate.ResourceOwner, userID); err != nil { - return nil, err - } + if err = c.checkPermissionUpdateUser(ctx, cmd.aggregate.ResourceOwner, userID); err != nil { + return nil, err } if err = cmd.Change(ctx, domain.PhoneNumber(phone)); err != nil { return nil, err @@ -104,10 +102,8 @@ func (c *Commands) resendUserPhoneCodeWithGenerator(ctx context.Context, userID if err != nil { return nil, err } - if authz.GetCtxData(ctx).UserID != userID { - if err = c.checkPermission(ctx, domain.PermissionUserWrite, cmd.aggregate.ResourceOwner, userID); err != nil { - return nil, err - } + if err = c.checkPermissionUpdateUser(ctx, cmd.aggregate.ResourceOwner, userID); err != nil { + return nil, err } if cmd.model.Code == nil && cmd.model.GeneratorID == "" { return nil, zerrors.ThrowPreconditionFailed(err, "PHONE-5xrra88eq8", "Errors.User.Code.Empty") diff --git a/internal/database/cockroach/crdb.go b/internal/database/cockroach/crdb.go index 527becf7b5..cc89be8687 100644 --- a/internal/database/cockroach/crdb.go +++ b/internal/database/cockroach/crdb.go @@ -3,15 +3,18 @@ package cockroach import ( "context" "database/sql" + "fmt" "strconv" "strings" "time" + "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/stdlib" "github.com/mitchellh/mapstructure" "github.com/zitadel/logging" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/database/dialect" ) @@ -72,6 +75,12 @@ func (_ *Config) Decode(configs []interface{}) (dialect.Connector, error) { } func (c *Config) Connect(useAdmin bool, pusherRatio, spoolerRatio float64, purpose dialect.DBPurpose) (*sql.DB, *pgxpool.Pool, error) { + dialect.RegisterAfterConnect(func(ctx context.Context, c *pgx.Conn) error { + // CockroachDB by default does not allow multiple modifications of the same table using ON CONFLICT + // This is needed to fill the fields table of the eventstore during eventstore.Push. + _, err := c.Exec(ctx, "SET enable_multiple_modifications_of_table = on") + return err + }) connConfig, err := dialect.NewConnectionConfig(c.MaxOpenConns, c.MaxIdleConns, pusherRatio, spoolerRatio, purpose) if err != nil { return nil, nil, err @@ -82,6 +91,29 @@ func (c *Config) Connect(useAdmin bool, pusherRatio, spoolerRatio float64, purpo return nil, nil, err } + if len(connConfig.AfterConnect) > 0 { + config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error { + for _, f := range connConfig.AfterConnect { + if err := f(ctx, conn); err != nil { + return err + } + } + return nil + } + } + + // For the pusher we set the app name with the instance ID + if purpose == dialect.DBPurposeEventPusher { + config.BeforeAcquire = func(ctx context.Context, conn *pgx.Conn) bool { + return setAppNameWithID(ctx, conn, purpose, authz.GetInstance(ctx).InstanceID()) + } + config.AfterRelease = func(conn *pgx.Conn) bool { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + return setAppNameWithID(ctx, conn, purpose, "IDLE") + } + } + if connConfig.MaxOpenConns != 0 { config.MaxConns = int32(connConfig.MaxOpenConns) } @@ -200,3 +232,11 @@ func (c Config) String(useAdmin bool, appName string) string { return strings.Join(fields, " ") } + +func setAppNameWithID(ctx context.Context, conn *pgx.Conn, purpose dialect.DBPurpose, id string) bool { + // needs to be set like this because psql complains about parameters in the SET statement + query := fmt.Sprintf("SET application_name = '%s_%s'", purpose.AppName(), id) + _, err := conn.Exec(ctx, query) + logging.OnError(err).Warn("failed to set application name") + return err == nil +} diff --git a/internal/database/database.go b/internal/database/database.go index d6ccf2873c..b86a9f247c 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -18,6 +18,52 @@ import ( "github.com/zitadel/zitadel/internal/zerrors" ) +type ContextQuerier interface { + QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) +} + +type ContextExecuter interface { + ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) +} + +type ContextQueryExecuter interface { + ContextQuerier + ContextExecuter +} + +type Client interface { + ContextQueryExecuter + Beginner + Conn(ctx context.Context) (*sql.Conn, error) +} + +type Beginner interface { + BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) +} + +type Tx interface { + ContextQueryExecuter + Commit() error + Rollback() error +} + +var ( + _ Client = (*sql.DB)(nil) + _ Tx = (*sql.Tx)(nil) +) + +func CloseTransaction(tx Tx, err error) error { + if err != nil { + rollbackErr := tx.Rollback() + logging.OnError(rollbackErr).Error("failed to rollback transaction") + return err + } + + commitErr := tx.Commit() + logging.OnError(commitErr).Error("failed to commit transaction") + return commitErr +} + type Config struct { Dialects map[string]interface{} `mapstructure:",remain"` EventPushConnRatio float64 diff --git a/internal/database/dialect/connections.go b/internal/database/dialect/connections.go index 48f8d6e223..f957870df0 100644 --- a/internal/database/dialect/connections.go +++ b/internal/database/dialect/connections.go @@ -1,8 +1,13 @@ package dialect import ( + "context" "errors" "fmt" + "reflect" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" ) var ( @@ -17,6 +22,7 @@ var ( type ConnectionConfig struct { MaxOpenConns, MaxIdleConns uint32 + AfterConnect []func(ctx context.Context, c *pgx.Conn) error } // takeRatio of MaxOpenConns and MaxIdleConns from config and returns @@ -29,6 +35,7 @@ func (c *ConnectionConfig) takeRatio(ratio float64) (*ConnectionConfig, error) { out := &ConnectionConfig{ MaxOpenConns: uint32(ratio * float64(c.MaxOpenConns)), MaxIdleConns: uint32(ratio * float64(c.MaxIdleConns)), + AfterConnect: c.AfterConnect, } if c.MaxOpenConns != 0 && out.MaxOpenConns < 1 && ratio > 0 { out.MaxOpenConns = 1 @@ -40,6 +47,36 @@ func (c *ConnectionConfig) takeRatio(ratio float64) (*ConnectionConfig, error) { return out, nil } +var afterConnectFuncs []func(ctx context.Context, c *pgx.Conn) error + +func RegisterAfterConnect(f func(ctx context.Context, c *pgx.Conn) error) { + afterConnectFuncs = append(afterConnectFuncs, f) +} + +func RegisterDefaultPgTypeVariants[T any](m *pgtype.Map, name, arrayName string) { + // T + var value T + m.RegisterDefaultPgType(value, name) + + // *T + valueType := reflect.TypeOf(value) + m.RegisterDefaultPgType(reflect.New(valueType).Interface(), name) + + // []T + sliceType := reflect.SliceOf(valueType) + m.RegisterDefaultPgType(reflect.MakeSlice(sliceType, 0, 0).Interface(), arrayName) + + // *[]T + m.RegisterDefaultPgType(reflect.New(sliceType).Interface(), arrayName) + + // []*T + sliceOfPointerType := reflect.SliceOf(reflect.TypeOf(reflect.New(valueType).Interface())) + m.RegisterDefaultPgType(reflect.MakeSlice(sliceOfPointerType, 0, 0).Interface(), arrayName) + + // *[]*T + m.RegisterDefaultPgType(reflect.New(sliceOfPointerType).Interface(), arrayName) +} + // NewConnectionConfig calculates [ConnectionConfig] values from the passed ratios // and returns the config applicable for the requested purpose. // @@ -59,11 +96,13 @@ func NewConnectionConfig(openConns, idleConns uint32, pusherRatio, projectionRat queryConfig := &ConnectionConfig{ MaxOpenConns: openConns, MaxIdleConns: idleConns, + AfterConnect: afterConnectFuncs, } pusherConfig, err := queryConfig.takeRatio(pusherRatio) if err != nil { return nil, fmt.Errorf("event pusher: %w", err) } + spoolerConfig, err := queryConfig.takeRatio(projectionRatio) if err != nil { return nil, fmt.Errorf("projection spooler: %w", err) diff --git a/internal/database/postgres/pg.go b/internal/database/postgres/pg.go index 539aedf0a4..c12e122437 100644 --- a/internal/database/postgres/pg.go +++ b/internal/database/postgres/pg.go @@ -3,15 +3,18 @@ package postgres import ( "context" "database/sql" + "fmt" "strconv" "strings" "time" + "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/stdlib" "github.com/mitchellh/mapstructure" "github.com/zitadel/logging" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/database/dialect" ) @@ -83,6 +86,27 @@ func (c *Config) Connect(useAdmin bool, pusherRatio, spoolerRatio float64, purpo return nil, nil, err } + config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error { + for _, f := range connConfig.AfterConnect { + if err := f(ctx, conn); err != nil { + return err + } + } + return nil + } + + // For the pusher we set the app name with the instance ID + if purpose == dialect.DBPurposeEventPusher { + config.BeforeAcquire = func(ctx context.Context, conn *pgx.Conn) bool { + return setAppNameWithID(ctx, conn, purpose, authz.GetInstance(ctx).InstanceID()) + } + config.AfterRelease = func(conn *pgx.Conn) bool { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + return setAppNameWithID(ctx, conn, purpose, "IDLE") + } + } + if connConfig.MaxOpenConns != 0 { config.MaxConns = int32(connConfig.MaxOpenConns) } @@ -209,3 +233,11 @@ func (c Config) String(useAdmin bool, appName string) string { return strings.Join(fields, " ") } + +func setAppNameWithID(ctx context.Context, conn *pgx.Conn, purpose dialect.DBPurpose, id string) bool { + // needs to be set like this because psql complains about parameters in the SET statement + query := fmt.Sprintf("SET application_name = '%s_%s'", purpose.AppName(), id) + _, err := conn.Exec(ctx, query) + logging.OnError(err).Warn("failed to set application name") + return err == nil +} diff --git a/internal/domain/human_web_auth_n.go b/internal/domain/human_web_auth_n.go index 16590d43ca..62c3424914 100644 --- a/internal/domain/human_web_auth_n.go +++ b/internal/domain/human_web_auth_n.go @@ -96,3 +96,7 @@ func (p *PasswordlessInitCode) Link(baseURL string) string { func PasswordlessInitCodeLink(baseURL, userID, resourceOwner, codeID, code string) string { return fmt.Sprintf("%s?userID=%s&orgID=%s&codeID=%s&code=%s", baseURL, userID, resourceOwner, codeID, code) } + +func PasswordlessInitCodeLinkTemplate(baseURL, userID, resourceOwner, codeID string) string { + return PasswordlessInitCodeLink(baseURL, userID, resourceOwner, codeID, "{{.Code}}") +} diff --git a/internal/domain/notification.go b/internal/domain/notification.go index 756c400c66..81bf6413dd 100644 --- a/internal/domain/notification.go +++ b/internal/domain/notification.go @@ -1,5 +1,9 @@ package domain +import ( + "time" +) + type NotificationType int32 const ( @@ -31,3 +35,32 @@ const ( notificationProviderTypeCount ) + +type NotificationArguments struct { + Origin string `json:"origin,omitempty"` + Domain string `json:"domain,omitempty"` + Expiry time.Duration `json:"expiry,omitempty"` + TempUsername string `json:"tempUsername,omitempty"` + ApplicationName string `json:"applicationName,omitempty"` + CodeID string `json:"codeID,omitempty"` + SessionID string `json:"sessionID,omitempty"` + AuthRequestID string `json:"authRequestID,omitempty"` +} + +// ToMap creates a type safe map of the notification arguments. +// Since these arguments are used in text template, all keys must be PascalCase and types must remain the same (e.g. Duration). +func (n *NotificationArguments) ToMap() map[string]interface{} { + m := make(map[string]interface{}) + if n == nil { + return m + } + m["Origin"] = n.Origin + m["Domain"] = n.Domain + m["Expiry"] = n.Expiry + m["TempUsername"] = n.TempUsername + m["ApplicationName"] = n.ApplicationName + m["CodeID"] = n.CodeID + m["SessionID"] = n.SessionID + m["AuthRequestID"] = n.AuthRequestID + return m +} diff --git a/internal/domain/secret_generator.go b/internal/domain/secret_generator.go index 855e3447c1..25998bd205 100644 --- a/internal/domain/secret_generator.go +++ b/internal/domain/secret_generator.go @@ -15,6 +15,7 @@ const ( SecretGeneratorTypeOTPSMS SecretGeneratorTypeOTPEmail SecretGeneratorTypeInviteCode + SecretGeneratorTypeSigningKey secretGeneratorTypeCount ) diff --git a/internal/domain/url_template.go b/internal/domain/url_template.go index ed39a8257e..063d701d0a 100644 --- a/internal/domain/url_template.go +++ b/internal/domain/url_template.go @@ -7,6 +7,10 @@ import ( "github.com/zitadel/zitadel/internal/zerrors" ) +func RenderURLTemplate(w io.Writer, tmpl string, data any) error { + return renderURLTemplate(w, tmpl, data) +} + func renderURLTemplate(w io.Writer, tmpl string, data any) error { parsed, err := template.New("").Parse(tmpl) if err != nil { diff --git a/internal/eventstore/event_base.go b/internal/eventstore/event_base.go index c2b56128a8..45706641d8 100644 --- a/internal/eventstore/event_base.go +++ b/internal/eventstore/event_base.go @@ -3,8 +3,12 @@ package eventstore import ( "context" "encoding/json" + "strconv" + "strings" "time" + "github.com/zitadel/logging" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/service" ) @@ -84,8 +88,10 @@ func (e *BaseEvent) DataAsBytes() []byte { } // Revision implements action -func (*BaseEvent) Revision() uint16 { - return 0 +func (e *BaseEvent) Revision() uint16 { + revision, err := strconv.ParseUint(strings.TrimPrefix(string(e.Agg.Version), "v"), 10, 16) + logging.OnError(err).Debug("failed to parse event revision") + return uint16(revision) } // Unmarshal implements Event diff --git a/internal/eventstore/eventstore.go b/internal/eventstore/eventstore.go index a8c8e923b5..4954df86c8 100644 --- a/internal/eventstore/eventstore.go +++ b/internal/eventstore/eventstore.go @@ -4,7 +4,6 @@ import ( "context" "errors" "sort" - "sync" "time" "github.com/jackc/pgx/v5/pgconn" @@ -24,10 +23,6 @@ type Eventstore struct { pusher Pusher querier Querier searcher Searcher - - instances []string - lastInstanceQuery time.Time - instancesMu sync.Mutex } var ( @@ -68,8 +63,6 @@ func NewEventstore(config *Config) *Eventstore { pusher: config.Pusher, querier: config.Querier, searcher: config.Searcher, - - instancesMu: sync.Mutex{}, } } @@ -85,6 +78,12 @@ func (es *Eventstore) Health(ctx context.Context) error { // Push pushes the events in a single transaction // an event needs at least an aggregate func (es *Eventstore) Push(ctx context.Context, cmds ...Command) ([]Event, error) { + return es.PushWithClient(ctx, nil, cmds...) +} + +// PushWithClient pushes the events in a single transaction using the provided database client +// an event needs at least an aggregate +func (es *Eventstore) PushWithClient(ctx context.Context, client database.ContextQueryExecuter, cmds ...Command) ([]Event, error) { if es.PushTimeout > 0 { var cancel func() ctx, cancel = context.WithTimeout(ctx, es.PushTimeout) @@ -100,12 +99,24 @@ func (es *Eventstore) Push(ctx context.Context, cmds ...Command) ([]Event, error // https://github.com/zitadel/zitadel/issues/7202 retry: for i := 0; i <= es.maxRetries; i++ { - events, err = es.pusher.Push(ctx, cmds...) - var pgErr *pgconn.PgError - if !errors.As(err, &pgErr) || pgErr.ConstraintName != "events2_pkey" || pgErr.SQLState() != "23505" { + events, err = es.pusher.Push(ctx, client, cmds...) + // if there is a transaction passed the calling function needs to retry + if _, ok := client.(database.Tx); ok { break retry } - logging.WithError(err).Info("eventstore push retry") + var pgErr *pgconn.PgError + if !errors.As(err, &pgErr) { + break retry + } + if pgErr.ConstraintName == "events2_pkey" && pgErr.SQLState() == "23505" { + logging.WithError(err).Info("eventstore push retry") + continue + } + if pgErr.SQLState() == "CR000" || pgErr.SQLState() == "40001" { + logging.WithError(err).Info("eventstore push retry") + continue + } + break retry } if err != nil { return nil, err @@ -225,27 +236,10 @@ func (es *Eventstore) LatestSequence(ctx context.Context, queryFactory *SearchQu return es.querier.LatestSequence(ctx, queryFactory) } -// InstanceIDs returns the instance ids found by the search query -// forceDBCall forces to query the database, the instance ids are not cached -func (es *Eventstore) InstanceIDs(ctx context.Context, maxAge time.Duration, forceDBCall bool, queryFactory *SearchQueryBuilder) ([]string, error) { - es.instancesMu.Lock() - defer es.instancesMu.Unlock() - - if !forceDBCall && time.Since(es.lastInstanceQuery) <= maxAge { - return es.instances, nil - } - - instances, err := es.querier.InstanceIDs(ctx, queryFactory) - if err != nil { - return nil, err - } - - if !forceDBCall { - es.instances = instances - es.lastInstanceQuery = time.Now() - } - - return instances, nil +// InstanceIDs returns the distinct instance ids found by the search query +// Warning: this function can have high impact on performance, only use this function during setup +func (es *Eventstore) InstanceIDs(ctx context.Context, queryFactory *SearchQueryBuilder) ([]string, error) { + return es.querier.InstanceIDs(ctx, queryFactory) } func (es *Eventstore) Client() *database.DB { @@ -283,7 +277,9 @@ type Pusher interface { // Health checks if the connection to the storage is available Health(ctx context.Context) error // Push stores the actions - Push(ctx context.Context, commands ...Command) (_ []Event, err error) + Push(ctx context.Context, client database.ContextQueryExecuter, commands ...Command) (_ []Event, err error) + // Client returns the underlying database connection + Client() *database.DB } type FillFieldsEvent interface { diff --git a/internal/eventstore/eventstore_bench_test.go b/internal/eventstore/eventstore_bench_test.go index 69b958abd8..582391e09f 100644 --- a/internal/eventstore/eventstore_bench_test.go +++ b/internal/eventstore/eventstore_bench_test.go @@ -69,7 +69,7 @@ func Benchmark_Push_SameAggregate(b *testing.B) { b.StartTimer() for n := 0; n < b.N; n++ { - _, err := store.Push(ctx, cmds...) + _, err := store.Push(ctx, store.Client().DB, cmds...) if err != nil { b.Error(err) } @@ -149,7 +149,7 @@ func Benchmark_Push_MultipleAggregate_Parallel(b *testing.B) { b.RunParallel(func(p *testing.PB) { for p.Next() { i++ - _, err := store.Push(ctx, commandCreator(strconv.Itoa(i))...) + _, err := store.Push(ctx, store.Client().DB, commandCreator(strconv.Itoa(i))...) if err != nil { b.Error(err) } diff --git a/internal/eventstore/eventstore_pusher_test.go b/internal/eventstore/eventstore_pusher_test.go index bd97b2e1e6..4e8e663667 100644 --- a/internal/eventstore/eventstore_pusher_test.go +++ b/internal/eventstore/eventstore_pusher_test.go @@ -607,7 +607,7 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) { } } -func pushAggregates(pusher eventstore.Pusher, aggregateCommands [][]eventstore.Command) []error { +func pushAggregates(es *eventstore.Eventstore, aggregateCommands [][]eventstore.Command) []error { wg := sync.WaitGroup{} errs := make([]error, 0) errsMu := sync.Mutex{} @@ -619,7 +619,7 @@ func pushAggregates(pusher eventstore.Pusher, aggregateCommands [][]eventstore.C go func(events []eventstore.Command) { <-ctx.Done() - _, err := pusher.Push(context.Background(), events...) //nolint:contextcheck + _, err := es.Push(context.Background(), events...) //nolint:contextcheck if err != nil { errsMu.Lock() errs = append(errs, err) diff --git a/internal/eventstore/eventstore_querier_test.go b/internal/eventstore/eventstore_querier_test.go index 856bb4a20e..4b7ad78b25 100644 --- a/internal/eventstore/eventstore_querier_test.go +++ b/internal/eventstore/eventstore_querier_test.go @@ -66,6 +66,39 @@ func TestCRDB_Filter(t *testing.T) { }, wantErr: false, }, + { + name: "exclude aggregate type and event type", + args: args{ + searchQuery: eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + AddQuery(). + AggregateTypes(eventstore.AggregateType(t.Name())). + Builder(). + ExcludeAggregateIDs(). + EventTypes("test.updated"). + AggregateTypes(eventstore.AggregateType(t.Name())). + Builder(), + }, + fields: fields{ + existingEvents: []eventstore.Command{ + generateCommand(eventstore.AggregateType(t.Name()), "306"), + generateCommand( + eventstore.AggregateType(t.Name()), + "306", + func(te *testEvent) { + te.EventType = "test.updated" + }, + ), + generateCommand( + eventstore.AggregateType(t.Name()), + "308", + ), + }, + }, + res: res{ + eventCount: 1, + }, + wantErr: false, + }, } for _, tt := range tests { for querierName, querier := range queriers { diff --git a/internal/eventstore/eventstore_test.go b/internal/eventstore/eventstore_test.go index 33e80892c5..9e1aa77db1 100644 --- a/internal/eventstore/eventstore_test.go +++ b/internal/eventstore/eventstore_test.go @@ -330,6 +330,12 @@ func Test_eventData(t *testing.T) { } } +var _ Pusher = (*testPusher)(nil) + +func (repo *testPusher) Client() *database.DB { + return nil +} + type testPusher struct { events []Event errs []error @@ -341,7 +347,7 @@ func (repo *testPusher) Health(ctx context.Context) error { return nil } -func (repo *testPusher) Push(ctx context.Context, commands ...Command) (events []Event, err error) { +func (repo *testPusher) Push(_ context.Context, _ database.ContextQueryExecuter, commands ...Command) (events []Event, err error) { if len(repo.errs) != 0 { err, repo.errs = repo.errs[0], repo.errs[1:] return nil, err @@ -484,6 +490,7 @@ func TestEventstore_Push(t *testing.T) { Type: "test.aggregate", ResourceOwner: "caos", InstanceID: "zitadel", + Version: "v1", }, Data: []byte(nil), User: "editorUser", @@ -528,6 +535,7 @@ func TestEventstore_Push(t *testing.T) { Type: "test.aggregate", ResourceOwner: "caos", InstanceID: "zitadel", + Version: "v1", }, Data: []byte(nil), User: "editorUser", @@ -579,6 +587,7 @@ func TestEventstore_Push(t *testing.T) { Type: "test.aggregate", ResourceOwner: "caos", InstanceID: "zitadel", + Version: "v1", }, Data: []byte(nil), User: "editorUser", @@ -590,6 +599,7 @@ func TestEventstore_Push(t *testing.T) { Type: "test.aggregate", ResourceOwner: "caos", InstanceID: "zitadel", + Version: "v1", }, Data: []byte(nil), User: "editorUser", @@ -652,6 +662,7 @@ func TestEventstore_Push(t *testing.T) { Type: "test.aggregate", ResourceOwner: "caos", InstanceID: "zitadel", + Version: "v1", }, Data: []byte(nil), User: "editorUser", @@ -663,6 +674,7 @@ func TestEventstore_Push(t *testing.T) { Type: "test.aggregate", ResourceOwner: "caos", InstanceID: "zitadel", + Version: "v1", }, Data: []byte(nil), User: "editorUser", @@ -676,6 +688,7 @@ func TestEventstore_Push(t *testing.T) { Type: "test.aggregate", ResourceOwner: "caos", InstanceID: "zitadel", + Version: "v1", }, Data: []byte(nil), User: "editorUser", @@ -772,6 +785,7 @@ func TestEventstore_Push(t *testing.T) { Type: "test.aggregate", ResourceOwner: "caos", InstanceID: "zitadel", + Version: "v1", }, Data: []byte(nil), User: "editorUser", @@ -822,6 +836,7 @@ func TestEventstore_Push(t *testing.T) { Type: "test.aggregate", ResourceOwner: "caos", InstanceID: "zitadel", + Version: "v1", }, Data: []byte(nil), User: "editorUser", @@ -877,6 +892,7 @@ func TestEventstore_Push(t *testing.T) { Type: "test.aggregate", ResourceOwner: "caos", InstanceID: "zitadel", + Version: "v1", }, Data: []byte(nil), User: "editorUser", diff --git a/internal/eventstore/handler/v2/field_handler.go b/internal/eventstore/handler/v2/field_handler.go index 8b71f32519..bbe40ed465 100644 --- a/internal/eventstore/handler/v2/field_handler.go +++ b/internal/eventstore/handler/v2/field_handler.go @@ -41,7 +41,6 @@ func NewFieldHandler(config *Config, name string, eventTypes map[eventstore.Aggr bulkLimit: config.BulkLimit, eventTypes: eventTypes, requeueEvery: config.RequeueEvery, - handleActiveInstances: config.HandleActiveInstances, now: time.Now, maxFailureCount: config.MaxFailureCount, retryFailedAfter: config.RetryFailedAfter, diff --git a/internal/eventstore/handler/v2/handler.go b/internal/eventstore/handler/v2/handler.go index c2e2b2a355..2c2f88f8a0 100644 --- a/internal/eventstore/handler/v2/handler.go +++ b/internal/eventstore/handler/v2/handler.go @@ -23,7 +23,7 @@ import ( ) type EventStore interface { - InstanceIDs(ctx context.Context, maxAge time.Duration, forceLoad bool, query *eventstore.SearchQueryBuilder) ([]string, error) + InstanceIDs(ctx context.Context, query *eventstore.SearchQueryBuilder) ([]string, error) FilterToQueryReducer(ctx context.Context, reducer eventstore.QueryReducer) error Filter(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) Push(ctx context.Context, cmds ...eventstore.Command) ([]eventstore.Event, error) @@ -34,14 +34,17 @@ type Config struct { Client *database.DB Eventstore EventStore - BulkLimit uint16 - RequeueEvery time.Duration - RetryFailedAfter time.Duration - HandleActiveInstances time.Duration - TransactionDuration time.Duration - MaxFailureCount uint8 + BulkLimit uint16 + RequeueEvery time.Duration + RetryFailedAfter time.Duration + TransactionDuration time.Duration + MaxFailureCount uint8 TriggerWithoutEvents Reduce + + ActiveInstancer interface { + ActiveInstances() []string + } } type Handler struct { @@ -52,17 +55,18 @@ type Handler struct { bulkLimit uint16 eventTypes map[eventstore.AggregateType][]eventstore.EventType - maxFailureCount uint8 - retryFailedAfter time.Duration - requeueEvery time.Duration - handleActiveInstances time.Duration - txDuration time.Duration - now nowFunc + maxFailureCount uint8 + retryFailedAfter time.Duration + requeueEvery time.Duration + txDuration time.Duration + now nowFunc triggeredInstancesSync sync.Map triggerWithoutEvents Reduce cacheInvalidations []func(ctx context.Context, aggregates []*eventstore.Aggregate) + + queryInstances func() ([]string, error) } var _ migration.Migration = (*Handler)(nil) @@ -162,13 +166,18 @@ func NewHandler( bulkLimit: config.BulkLimit, eventTypes: aggregates, requeueEvery: config.RequeueEvery, - handleActiveInstances: config.HandleActiveInstances, now: time.Now, maxFailureCount: config.MaxFailureCount, retryFailedAfter: config.RetryFailedAfter, triggeredInstancesSync: sync.Map{}, triggerWithoutEvents: config.TriggerWithoutEvents, txDuration: config.TransactionDuration, + queryInstances: func() ([]string, error) { + if config.ActiveInstancer != nil { + return config.ActiveInstancer.ActiveInstances(), nil + } + return nil, nil + }, } return handler @@ -239,7 +248,7 @@ func (h *Handler) schedule(ctx context.Context) { t.Stop() return case <-t.C: - instances, err := h.queryInstances(ctx) + instances, err := h.queryInstances() h.log().OnError(err).Debug("unable to query instances") h.triggerInstances(call.WithTimestamp(ctx), instances) @@ -356,19 +365,6 @@ func (*existingInstances) Reduce() error { var _ eventstore.QueryReducer = (*existingInstances)(nil) -func (h *Handler) queryInstances(ctx context.Context) ([]string, error) { - if h.handleActiveInstances == 0 { - return h.existingInstances(ctx) - } - - query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsInstanceIDs). - AwaitOpenTransactions(). - AllowTimeTravel(). - CreationDateAfter(h.now().Add(-1 * h.handleActiveInstances)) - - return h.es.InstanceIDs(ctx, h.requeueEvery, false, query) -} - func (h *Handler) existingInstances(ctx context.Context) ([]string, error) { ai := existingInstances{} if err := h.es.FilterToQueryReducer(ctx, &ai); err != nil { diff --git a/internal/eventstore/handler/v2/mock_test.go b/internal/eventstore/handler/v2/mock_test.go index ebd49659f1..4dd0d68861 100644 --- a/internal/eventstore/handler/v2/mock_test.go +++ b/internal/eventstore/handler/v2/mock_test.go @@ -7,12 +7,17 @@ type projection struct { reducers []AggregateReducer } -// Name implements Projection +// ActiveInstances implements [Projection] +func (p *projection) ActiveInstances() []string { + return nil +} + +// Name implements [Projection] func (p *projection) Name() string { return p.name } -// Reducers implements Projection +// Reducers implements [Projection] func (p *projection) Reducers() []AggregateReducer { return p.reducers } diff --git a/internal/eventstore/local_crdb_test.go b/internal/eventstore/local_crdb_test.go index 6df9e9fd29..87c5084fe7 100644 --- a/internal/eventstore/local_crdb_test.go +++ b/internal/eventstore/local_crdb_test.go @@ -9,6 +9,8 @@ import ( "time" "github.com/cockroachdb/cockroach-go/v2/testserver" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/jackc/pgx/v5/stdlib" "github.com/zitadel/logging" "github.com/zitadel/zitadel/cmd/initialise" @@ -39,10 +41,17 @@ func TestMain(m *testing.M) { testCRDBClient = &database.DB{ Database: new(testDB), } - testCRDBClient.DB, err = sql.Open("postgres", ts.PGURL().String()) + + connConfig, err := pgxpool.ParseConfig(ts.PGURL().String()) if err != nil { - logging.WithFields("error", err).Fatal("unable to connect to db") + logging.WithFields("error", err).Fatal("unable to parse db url") } + connConfig.AfterConnect = new_es.RegisterEventstoreTypes + pool, err := pgxpool.NewWithConfig(context.Background(), connConfig) + if err != nil { + logging.WithFields("error", err).Fatal("unable to create db pool") + } + testCRDBClient.DB = stdlib.OpenDBFromPool(pool) if err = testCRDBClient.Ping(); err != nil { logging.WithFields("error", err).Fatal("unable to ping db") } @@ -55,7 +64,7 @@ func TestMain(m *testing.M) { clients["v3(inmemory)"] = testCRDBClient if localDB, err := connectLocalhost(); err == nil { - if err = initDB(localDB); err != nil { + if err = initDB(context.Background(), localDB); err != nil { logging.WithFields("error", err).Fatal("migrations failed") } pushers["v3(singlenode)"] = new_es.NewEventstore(localDB) @@ -69,14 +78,14 @@ func TestMain(m *testing.M) { ts.Stop() }() - if err = initDB(testCRDBClient); err != nil { + if err = initDB(context.Background(), testCRDBClient); err != nil { logging.WithFields("error", err).Fatal("migrations failed") } os.Exit(m.Run()) } -func initDB(db *database.DB) error { +func initDB(ctx context.Context, db *database.DB) error { initialise.ReadStmts("cockroach") config := new(database.Config) config.SetConnector(&cockroach.Config{ @@ -85,7 +94,7 @@ func initDB(db *database.DB) error { }, Database: "zitadel", }) - err := initialise.Init(db, + err := initialise.Init(ctx, db, initialise.VerifyUser(config.Username(), ""), initialise.VerifyDatabase(config.DatabaseName()), initialise.VerifyGrant(config.DatabaseName(), config.Username()), @@ -93,7 +102,7 @@ func initDB(db *database.DB) error { if err != nil { return err } - err = initialise.VerifyZitadel(context.Background(), db, *config) + err = initialise.VerifyZitadel(ctx, db, *config) if err != nil { return err } diff --git a/internal/eventstore/repository/event.go b/internal/eventstore/repository/event.go index 57b85f15ba..d0d2660d79 100644 --- a/internal/eventstore/repository/event.go +++ b/internal/eventstore/repository/event.go @@ -3,8 +3,12 @@ package repository import ( "database/sql" "encoding/json" + "strconv" + "strings" "time" + "github.com/zitadel/logging" + "github.com/zitadel/zitadel/internal/eventstore" ) @@ -82,7 +86,9 @@ func (e *Event) Type() eventstore.EventType { // Revision implements [eventstore.Event] func (e *Event) Revision() uint16 { - return 0 + revision, err := strconv.ParseUint(strings.TrimPrefix(string(e.Version), "v"), 10, 16) + logging.OnError(err).Debug("failed to parse event revision") + return uint16(revision) } // Sequence implements [eventstore.Event] diff --git a/internal/eventstore/repository/mock/repository.mock.go b/internal/eventstore/repository/mock/repository.mock.go index 58a6c8f86f..8d5c0430ad 100644 --- a/internal/eventstore/repository/mock/repository.mock.go +++ b/internal/eventstore/repository/mock/repository.mock.go @@ -136,6 +136,20 @@ func (m *MockPusher) EXPECT() *MockPusherMockRecorder { return m.recorder } +// Client mocks base method. +func (m *MockPusher) Client() *database.DB { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Client") + ret0, _ := ret[0].(*database.DB) + return ret0 +} + +// Client indicates an expected call of Client. +func (mr *MockPusherMockRecorder) Client() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Client", reflect.TypeOf((*MockPusher)(nil).Client)) +} + // Health mocks base method. func (m *MockPusher) Health(arg0 context.Context) error { m.ctrl.T.Helper() @@ -151,10 +165,10 @@ func (mr *MockPusherMockRecorder) Health(arg0 any) *gomock.Call { } // Push mocks base method. -func (m *MockPusher) Push(arg0 context.Context, arg1 ...eventstore.Command) ([]eventstore.Event, error) { +func (m *MockPusher) Push(arg0 context.Context, arg1 database.ContextQueryExecuter, arg2 ...eventstore.Command) ([]eventstore.Event, error) { m.ctrl.T.Helper() - varargs := []any{arg0} - for _, a := range arg1 { + varargs := []any{arg0, arg1} + for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "Push", varargs...) @@ -164,8 +178,8 @@ func (m *MockPusher) Push(arg0 context.Context, arg1 ...eventstore.Command) ([]e } // Push indicates an expected call of Push. -func (mr *MockPusherMockRecorder) Push(arg0 any, arg1 ...any) *gomock.Call { +func (mr *MockPusherMockRecorder) Push(arg0, arg1 any, arg2 ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{arg0}, arg1...) + varargs := append([]any{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Push", reflect.TypeOf((*MockPusher)(nil).Push), varargs...) } diff --git a/internal/eventstore/repository/mock/repository.mock.impl.go b/internal/eventstore/repository/mock/repository.mock.impl.go index d41521ad8f..9ae0b6b1ea 100644 --- a/internal/eventstore/repository/mock/repository.mock.impl.go +++ b/internal/eventstore/repository/mock/repository.mock.impl.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" + "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/repository" ) @@ -78,8 +79,8 @@ func (m *MockRepository) ExpectInstanceIDsError(err error) *MockRepository { // ExpectPush checks if the expectedCommands are send to the Push method. // The call will sleep at least the amount of passed duration. func (m *MockRepository) ExpectPush(expectedCommands []eventstore.Command, sleep time.Duration) *MockRepository { - m.MockPusher.EXPECT().Push(gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, commands ...eventstore.Command) ([]eventstore.Event, error) { + m.MockPusher.EXPECT().Push(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(ctx context.Context, _ database.ContextQueryExecuter, commands ...eventstore.Command) ([]eventstore.Event, error) { m.MockPusher.ctrl.T.Helper() time.Sleep(sleep) @@ -133,8 +134,8 @@ func (m *MockRepository) ExpectPush(expectedCommands []eventstore.Command, sleep func (m *MockRepository) ExpectPushFailed(err error, expectedCommands []eventstore.Command) *MockRepository { m.MockPusher.ctrl.T.Helper() - m.MockPusher.EXPECT().Push(gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, commands ...eventstore.Command) ([]eventstore.Event, error) { + m.MockPusher.EXPECT().Push(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(ctx context.Context, _ database.ContextQueryExecuter, commands ...eventstore.Command) ([]eventstore.Event, error) { if len(expectedCommands) != len(commands) { return nil, fmt.Errorf("unexpected amount of commands: want %d, got %d", len(expectedCommands), len(commands)) } @@ -195,8 +196,8 @@ func (e *mockEvent) CreatedAt() time.Time { } func (m *MockRepository) ExpectRandomPush(expectedCommands []eventstore.Command) *MockRepository { - m.MockPusher.EXPECT().Push(gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, commands ...eventstore.Command) ([]eventstore.Event, error) { + m.MockPusher.EXPECT().Push(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(ctx context.Context, _ database.ContextQueryExecuter, commands ...eventstore.Command) ([]eventstore.Event, error) { assert.Len(m.MockPusher.ctrl.T, commands, len(expectedCommands)) events := make([]eventstore.Event, len(commands)) @@ -213,8 +214,8 @@ func (m *MockRepository) ExpectRandomPush(expectedCommands []eventstore.Command) } func (m *MockRepository) ExpectRandomPushFailed(err error, expectedEvents []eventstore.Command) *MockRepository { - m.MockPusher.EXPECT().Push(gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, events ...eventstore.Command) ([]eventstore.Event, error) { + m.MockPusher.EXPECT().Push(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(ctx context.Context, _ database.ContextQueryExecuter, events ...eventstore.Command) ([]eventstore.Event, error) { assert.Len(m.MockPusher.ctrl.T, events, len(expectedEvents)) return nil, err }, diff --git a/internal/eventstore/repository/search_query.go b/internal/eventstore/repository/search_query.go index e67ed6206c..f84c7f1201 100644 --- a/internal/eventstore/repository/search_query.go +++ b/internal/eventstore/repository/search_query.go @@ -14,21 +14,24 @@ type SearchQuery struct { SubQueries [][]*Filter Tx *sql.Tx + LockRows bool + LockOption eventstore.LockOption AllowTimeTravel bool AwaitOpenTransactions bool Limit uint64 Offset uint32 Desc bool - InstanceID *Filter - InstanceIDs *Filter - ExcludedInstances *Filter - Creator *Filter - Owner *Filter - Position *Filter - Sequence *Filter - CreatedAfter *Filter - CreatedBefore *Filter + InstanceID *Filter + InstanceIDs *Filter + ExcludedInstances *Filter + Creator *Filter + Owner *Filter + Position *Filter + Sequence *Filter + CreatedAfter *Filter + CreatedBefore *Filter + ExcludeAggregateIDs []*Filter } // Filter represents all fields needed to compare a field of an event with a value @@ -130,6 +133,7 @@ func QueryFromBuilder(builder *eventstore.SearchQueryBuilder) (*SearchQuery, err AwaitOpenTransactions: builder.GetAwaitOpenTransactions(), SubQueries: make([][]*Filter, len(builder.GetQueries())), } + query.LockRows, query.LockOption = builder.GetLockRows() for _, f := range []func(builder *eventstore.SearchQueryBuilder, query *SearchQuery) *Filter{ instanceIDFilter, @@ -168,6 +172,21 @@ func QueryFromBuilder(builder *eventstore.SearchQueryBuilder) (*SearchQuery, err query.SubQueries[i] = append(query.SubQueries[i], filter) } } + if excludeAggregateIDs := builder.GetExcludeAggregateIDs(); excludeAggregateIDs != nil { + for _, f := range []func(query *eventstore.ExclusionQuery) *Filter{ + excludeAggregateTypeFilter, + excludeEventTypeFilter, + } { + filter := f(excludeAggregateIDs) + if filter == nil { + continue + } + if err := filter.Validate(); err != nil { + return nil, err + } + query.ExcludeAggregateIDs = append(query.ExcludeAggregateIDs, filter) + } + } return query, nil } @@ -283,3 +302,23 @@ func eventPositionAfterFilter(query *eventstore.SearchQuery) *Filter { } return nil } + +func excludeEventTypeFilter(query *eventstore.ExclusionQuery) *Filter { + if len(query.GetEventTypes()) < 1 { + return nil + } + if len(query.GetEventTypes()) == 1 { + return NewFilter(FieldEventType, query.GetEventTypes()[0], OperationEquals) + } + return NewFilter(FieldEventType, database.TextArray[eventstore.EventType](query.GetEventTypes()), OperationIn) +} + +func excludeAggregateTypeFilter(query *eventstore.ExclusionQuery) *Filter { + if len(query.GetAggregateTypes()) < 1 { + return nil + } + if len(query.GetAggregateTypes()) == 1 { + return NewFilter(FieldAggregateType, query.GetAggregateTypes()[0], OperationEquals) + } + return NewFilter(FieldAggregateType, database.TextArray[eventstore.AggregateType](query.GetAggregateTypes()), OperationIn) +} diff --git a/internal/eventstore/repository/sql/local_crdb_test.go b/internal/eventstore/repository/sql/local_crdb_test.go index fccb169341..0f8c934b47 100644 --- a/internal/eventstore/repository/sql/local_crdb_test.go +++ b/internal/eventstore/repository/sql/local_crdb_test.go @@ -8,11 +8,14 @@ import ( "time" "github.com/cockroachdb/cockroach-go/v2/testserver" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/jackc/pgx/v5/stdlib" "github.com/zitadel/logging" "github.com/zitadel/zitadel/cmd/initialise" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/database/cockroach" + new_es "github.com/zitadel/zitadel/internal/eventstore/v3" ) var ( @@ -29,10 +32,18 @@ func TestMain(m *testing.M) { logging.WithFields("error", err).Fatal("unable to start db") } - testCRDBClient, err = sql.Open("postgres", ts.PGURL().String()) + connConfig, err := pgxpool.ParseConfig(ts.PGURL().String()) if err != nil { - logging.WithFields("error", err).Fatal("unable to connect to db") + logging.WithFields("error", err).Fatal("unable to parse db url") } + connConfig.AfterConnect = new_es.RegisterEventstoreTypes + pool, err := pgxpool.NewWithConfig(context.Background(), connConfig) + if err != nil { + logging.WithFields("error", err).Fatal("unable to create db pool") + } + + testCRDBClient = stdlib.OpenDBFromPool(pool) + if err = testCRDBClient.Ping(); err != nil { logging.WithFields("error", err).Fatal("unable to ping db") } @@ -42,14 +53,14 @@ func TestMain(m *testing.M) { ts.Stop() }() - if err = initDB(&database.DB{DB: testCRDBClient, Database: &cockroach.Config{Database: "zitadel"}}); err != nil { + if err = initDB(context.Background(), &database.DB{DB: testCRDBClient, Database: &cockroach.Config{Database: "zitadel"}}); err != nil { logging.WithFields("error", err).Fatal("migrations failed") } os.Exit(m.Run()) } -func initDB(db *database.DB) error { +func initDB(ctx context.Context, db *database.DB) error { config := new(database.Config) config.SetConnector(&cockroach.Config{User: cockroach.User{Username: "zitadel"}, Database: "zitadel"}) @@ -57,7 +68,7 @@ func initDB(db *database.DB) error { return err } - err := initialise.Init(db, + err := initialise.Init(ctx, db, initialise.VerifyUser(config.Username(), ""), initialise.VerifyDatabase(config.DatabaseName()), initialise.VerifyGrant(config.DatabaseName(), config.Username()), diff --git a/internal/eventstore/repository/sql/query.go b/internal/eventstore/repository/sql/query.go index 8ecea6e3d6..b93e663b17 100644 --- a/internal/eventstore/repository/sql/query.go +++ b/internal/eventstore/repository/sql/query.go @@ -105,6 +105,18 @@ func query(ctx context.Context, criteria querier, searchQuery *eventstore.Search query += " OFFSET ?" } + if q.LockRows { + query += " FOR UPDATE" + switch q.LockOption { + case eventstore.LockOptionWait: // default behavior + case eventstore.LockOptionNoWait: + query += " NOWAIT" + case eventstore.LockOptionSkipLocked: + query += " SKIP LOCKED" + + } + } + query = criteria.placeholder(query) var contextQuerier interface { @@ -271,6 +283,23 @@ func prepareConditions(criteria querier, query *repository.SearchQuery, useV1 bo args = append(args, additionalArgs...) } + excludeAggregateIDs := query.ExcludeAggregateIDs + if len(excludeAggregateIDs) > 0 { + excludeAggregateIDs = append(excludeAggregateIDs, query.InstanceID, query.InstanceIDs, query.Position, query.CreatedAfter, query.CreatedBefore) + } + excludeAggregateIDsClauses, excludeAggregateIDsArgs := prepareQuery(criteria, useV1, excludeAggregateIDs...) + if excludeAggregateIDsClauses != "" { + if clauses != "" { + clauses += " AND " + } + if useV1 { + clauses += "aggregate_id NOT IN (SELECT aggregate_id FROM eventstore.events WHERE " + excludeAggregateIDsClauses + ")" + } else { + clauses += "aggregate_id NOT IN (SELECT aggregate_id FROM eventstore.events2 WHERE " + excludeAggregateIDsClauses + ")" + } + args = append(args, excludeAggregateIDsArgs...) + } + if query.AwaitOpenTransactions { instanceIDs := make(database.TextArray[string], 0, 3) if query.InstanceID != nil { diff --git a/internal/eventstore/repository/sql/query_test.go b/internal/eventstore/repository/sql/query_test.go index 2956c39bd5..abac19ead0 100644 --- a/internal/eventstore/repository/sql/query_test.go +++ b/internal/eventstore/repository/sql/query_test.go @@ -5,6 +5,7 @@ import ( "database/sql" "database/sql/driver" "reflect" + "regexp" "strconv" "testing" "time" @@ -657,10 +658,94 @@ func Test_query_events_with_crdb(t *testing.T) { } } +/* Cockroach test DB doesn't seem to lock +func Test_query_events_with_crdb_locking(t *testing.T) { + type args struct { + searchQuery *eventstore.SearchQueryBuilder + } + type fields struct { + existingEvents []eventstore.Command + client *sql.DB + } + tests := []struct { + name string + fields fields + args args + lockOption eventstore.LockOption + wantErr bool + }{ + { + name: "skip locked", + fields: fields{ + client: testCRDBClient, + existingEvents: []eventstore.Command{ + generateEvent(t, "306", func(e *repository.Event) { e.ResourceOwner = sql.NullString{String: "caos", Valid: true} }), + generateEvent(t, "307", func(e *repository.Event) { e.ResourceOwner = sql.NullString{String: "caos", Valid: true} }), + generateEvent(t, "308", func(e *repository.Event) { e.ResourceOwner = sql.NullString{String: "caos", Valid: true} }), + }, + }, + args: args{ + searchQuery: eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + ResourceOwner("caos"), + }, + lockOption: eventstore.LockOptionNoWait, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := &CRDB{ + DB: &database.DB{ + DB: tt.fields.client, + Database: new(testDB), + }, + } + // setup initial data for query + if _, err := db.Push(context.Background(), tt.fields.existingEvents...); err != nil { + t.Errorf("error in setup = %v", err) + return + } + // first TX should lock and return all events + tx1, err := db.DB.Begin() + require.NoError(t, err) + defer func() { + require.NoError(t, tx1.Rollback()) + }() + searchQuery1 := tt.args.searchQuery.LockRowsDuringTx(tx1, tt.lockOption) + gotEvents1 := []eventstore.Event{} + err = query(context.Background(), db, searchQuery1, eventstore.Reducer(func(event eventstore.Event) error { + gotEvents1 = append(gotEvents1, event) + return nil + }), true) + require.NoError(t, err) + assert.Len(t, gotEvents1, len(tt.fields.existingEvents)) + + // second TX should not return the events, and might return an error + tx2, err := db.DB.Begin() + require.NoError(t, err) + defer func() { + require.NoError(t, tx2.Rollback()) + }() + searchQuery2 := tt.args.searchQuery.LockRowsDuringTx(tx1, tt.lockOption) + gotEvents2 := []eventstore.Event{} + err = query(context.Background(), db, searchQuery2, eventstore.Reducer(func(event eventstore.Event) error { + gotEvents2 = append(gotEvents2, event) + return nil + }), true) + if tt.wantErr { + require.Error(t, err) + } + require.NoError(t, err) + assert.Len(t, gotEvents2, 0) + }) + } +} +*/ + func Test_query_events_mocked(t *testing.T) { type args struct { query *eventstore.SearchQueryBuilder dest interface{} + useV1 bool } type res struct { wantErr bool @@ -684,6 +769,7 @@ func Test_query_events_mocked(t *testing.T) { AddQuery(). AggregateTypes("user"). Builder(), + useV1: true, }, fields: fields{ mock: newMockClient(t).expectQuery(t, @@ -706,6 +792,7 @@ func Test_query_events_mocked(t *testing.T) { AddQuery(). AggregateTypes("user"). Builder(), + useV1: true, }, fields: fields{ mock: newMockClient(t).expectQuery(t, @@ -728,6 +815,7 @@ func Test_query_events_mocked(t *testing.T) { AddQuery(). AggregateTypes("user"). Builder(), + useV1: true, }, fields: fields{ mock: newMockClient(t).expectQuery(t, @@ -751,6 +839,7 @@ func Test_query_events_mocked(t *testing.T) { AddQuery(). AggregateTypes("user"). Builder(), + useV1: true, }, fields: fields{ mock: newMockClient(t).expectQuery(t, @@ -762,6 +851,72 @@ func Test_query_events_mocked(t *testing.T) { wantErr: false, }, }, + { + name: "lock, wait", + args: args{ + dest: &[]*repository.Event{}, + query: eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + OrderDesc(). + Limit(5). + AddQuery(). + AggregateTypes("user"). + Builder().LockRowsDuringTx(nil, eventstore.LockOptionWait), + useV1: true, + }, + fields: fields{ + mock: newMockClient(t).expectQuery(t, + `SELECT creation_date, event_type, event_sequence, event_data, editor_user, resource_owner, instance_id, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE aggregate_type = \$1 ORDER BY event_sequence DESC LIMIT \$2 FOR UPDATE`, + []driver.Value{eventstore.AggregateType("user"), uint64(5)}, + ), + }, + res: res{ + wantErr: false, + }, + }, + { + name: "lock, no wait", + args: args{ + dest: &[]*repository.Event{}, + query: eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + OrderDesc(). + Limit(5). + AddQuery(). + AggregateTypes("user"). + Builder().LockRowsDuringTx(nil, eventstore.LockOptionNoWait), + useV1: true, + }, + fields: fields{ + mock: newMockClient(t).expectQuery(t, + `SELECT creation_date, event_type, event_sequence, event_data, editor_user, resource_owner, instance_id, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE aggregate_type = \$1 ORDER BY event_sequence DESC LIMIT \$2 FOR UPDATE NOWAIT`, + []driver.Value{eventstore.AggregateType("user"), uint64(5)}, + ), + }, + res: res{ + wantErr: false, + }, + }, + { + name: "lock, skip locked", + args: args{ + dest: &[]*repository.Event{}, + query: eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + OrderDesc(). + Limit(5). + AddQuery(). + AggregateTypes("user"). + Builder().LockRowsDuringTx(nil, eventstore.LockOptionSkipLocked), + useV1: true, + }, + fields: fields{ + mock: newMockClient(t).expectQuery(t, + `SELECT creation_date, event_type, event_sequence, event_data, editor_user, resource_owner, instance_id, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE aggregate_type = \$1 ORDER BY event_sequence DESC LIMIT \$2 FOR UPDATE SKIP LOCKED`, + []driver.Value{eventstore.AggregateType("user"), uint64(5)}, + ), + }, + res: res{ + wantErr: false, + }, + }, { name: "error sql conn closed", args: args{ @@ -773,6 +928,7 @@ func Test_query_events_mocked(t *testing.T) { AddQuery(). AggregateTypes("user"). Builder(), + useV1: true, }, fields: fields{ mock: newMockClient(t).expectQueryErr(t, @@ -795,6 +951,7 @@ func Test_query_events_mocked(t *testing.T) { AddQuery(). AggregateTypes("user"). Builder(), + useV1: true, }, fields: fields{ mock: newMockClient(t).expectQueryScanErr(t, @@ -829,6 +986,7 @@ func Test_query_events_mocked(t *testing.T) { AggregateTypes("org"). AggregateIDs("asdf42"). Builder(), + useV1: true, }, fields: fields{ mock: newMockClient(t).expectQuery(t, @@ -840,6 +998,99 @@ func Test_query_events_mocked(t *testing.T) { wantErr: false, }, }, + { + name: "aggregate / event type, position and exclusion, v1", + args: args{ + dest: &[]*repository.Event{}, + query: eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + InstanceID("instanceID"). + OrderDesc(). + Limit(5). + PositionAfter(123.456). + AddQuery(). + AggregateTypes("notify"). + EventTypes("notify.foo.bar"). + Builder(). + ExcludeAggregateIDs(). + AggregateTypes("notify"). + EventTypes("notification.failed", "notification.success"). + Builder(), + useV1: true, + }, + fields: fields{ + mock: newMockClient(t).expectQuery(t, + regexp.QuoteMeta( + `SELECT creation_date, event_type, event_sequence, event_data, editor_user, resource_owner, instance_id, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE instance_id = $1 AND aggregate_type = $2 AND event_type = $3 AND "position" > $4 AND aggregate_id NOT IN (SELECT aggregate_id FROM eventstore.events WHERE aggregate_type = $5 AND event_type = ANY($6) AND instance_id = $7 AND "position" > $8) ORDER BY event_sequence DESC LIMIT $9`, + ), + []driver.Value{"instanceID", eventstore.AggregateType("notify"), eventstore.EventType("notify.foo.bar"), 123.456, eventstore.AggregateType("notify"), []eventstore.EventType{"notification.failed", "notification.success"}, "instanceID", 123.456, uint64(5)}, + ), + }, + res: res{ + wantErr: false, + }, + }, + { + name: "aggregate / event type, position and exclusion, v2", + args: args{ + dest: &[]*repository.Event{}, + query: eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + InstanceID("instanceID"). + OrderDesc(). + Limit(5). + PositionAfter(123.456). + AddQuery(). + AggregateTypes("notify"). + EventTypes("notify.foo.bar"). + Builder(). + ExcludeAggregateIDs(). + AggregateTypes("notify"). + EventTypes("notification.failed", "notification.success"). + Builder(), + useV1: false, + }, + fields: fields{ + mock: newMockClient(t).expectQuery(t, + regexp.QuoteMeta( + `SELECT created_at, event_type, "sequence", "position", payload, creator, "owner", instance_id, aggregate_type, aggregate_id, revision FROM eventstore.events2 WHERE instance_id = $1 AND aggregate_type = $2 AND event_type = $3 AND "position" > $4 AND aggregate_id NOT IN (SELECT aggregate_id FROM eventstore.events2 WHERE aggregate_type = $5 AND event_type = ANY($6) AND instance_id = $7 AND "position" > $8) ORDER BY "position" DESC, in_tx_order DESC LIMIT $9`, + ), + []driver.Value{"instanceID", eventstore.AggregateType("notify"), eventstore.EventType("notify.foo.bar"), 123.456, eventstore.AggregateType("notify"), []eventstore.EventType{"notification.failed", "notification.success"}, "instanceID", 123.456, uint64(5)}, + ), + }, + res: res{ + wantErr: false, + }, + }, + { + name: "aggregate / event type, created after and exclusion, v2", + args: args{ + dest: &[]*repository.Event{}, + query: eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + InstanceID("instanceID"). + OrderDesc(). + Limit(5). + CreationDateAfter(time.Unix(123, 456)). + AddQuery(). + AggregateTypes("notify"). + EventTypes("notify.foo.bar"). + Builder(). + ExcludeAggregateIDs(). + AggregateTypes("notify"). + EventTypes("notification.failed", "notification.success"). + Builder(), + useV1: false, + }, + fields: fields{ + mock: newMockClient(t).expectQuery(t, + regexp.QuoteMeta( + `SELECT created_at, event_type, "sequence", "position", payload, creator, "owner", instance_id, aggregate_type, aggregate_id, revision FROM eventstore.events2 WHERE instance_id = $1 AND aggregate_type = $2 AND event_type = $3 AND created_at > $4 AND aggregate_id NOT IN (SELECT aggregate_id FROM eventstore.events2 WHERE aggregate_type = $5 AND event_type = ANY($6) AND instance_id = $7 AND created_at > $8) ORDER BY "position" DESC, in_tx_order DESC LIMIT $9`, + ), + []driver.Value{"instanceID", eventstore.AggregateType("notify"), eventstore.EventType("notify.foo.bar"), time.Unix(123, 456), eventstore.AggregateType("notify"), []eventstore.EventType{"notification.failed", "notification.success"}, "instanceID", time.Unix(123, 456), uint64(5)}, + ), + }, + res: res{ + wantErr: false, + }, + }, } crdb := NewCRDB(&database.DB{Database: new(testDB)}) for _, tt := range tests { @@ -848,7 +1099,7 @@ func Test_query_events_mocked(t *testing.T) { crdb.DB.DB = tt.fields.mock.client } - err := query(context.Background(), crdb, tt.args.query, tt.args.dest, true) + err := query(context.Background(), crdb, tt.args.query, tt.args.dest, tt.args.useV1) if (err != nil) != tt.res.wantErr { t.Errorf("query() error = %v, wantErr %v", err, tt.res.wantErr) } diff --git a/internal/eventstore/search_query.go b/internal/eventstore/search_query.go index e80ed8295c..df38d15def 100644 --- a/internal/eventstore/search_query.go +++ b/internal/eventstore/search_query.go @@ -21,7 +21,10 @@ type SearchQueryBuilder struct { instanceIDs []string editorUser string queries []*SearchQuery + excludeAggregateIDs *ExclusionQuery tx *sql.Tx + lockRows bool + lockOption LockOption allowTimeTravel bool positionAfter float64 awaitOpenTransactions bool @@ -66,6 +69,10 @@ func (b *SearchQueryBuilder) GetQueries() []*SearchQuery { return b.queries } +func (b *SearchQueryBuilder) GetExcludeAggregateIDs() *ExclusionQuery { + return b.excludeAggregateIDs +} + func (b *SearchQueryBuilder) GetTx() *sql.Tx { return b.tx } @@ -94,6 +101,10 @@ func (q SearchQueryBuilder) GetCreationDateBefore() time.Time { return q.creationDateBefore } +func (q SearchQueryBuilder) GetLockRows() (bool, LockOption) { + return q.lockRows, q.lockOption +} + // ensureInstanceID makes sure that the instance id is always set func (b *SearchQueryBuilder) ensureInstanceID(ctx context.Context) { if b.instanceID == nil && len(b.instanceIDs) == 0 && authz.GetInstance(ctx).InstanceID() != "" { @@ -130,6 +141,20 @@ func (q SearchQuery) GetPositionAfter() float64 { return q.positionAfter } +type ExclusionQuery struct { + builder *SearchQueryBuilder + aggregateTypes []AggregateType + eventTypes []EventType +} + +func (q ExclusionQuery) GetAggregateTypes() []AggregateType { + return q.aggregateTypes +} + +func (q ExclusionQuery) GetEventTypes() []EventType { + return q.eventTypes +} + // Columns defines which fields of the event are needed for the query type Columns int8 @@ -307,6 +332,27 @@ func (builder *SearchQueryBuilder) CreationDateBefore(creationDate time.Time) *S return builder } +type LockOption int + +const ( + // Wait until the previous lock on all of the selected rows is released (default) + LockOptionWait LockOption = iota + // With NOWAIT, the statement reports an error, rather than waiting, if a selected row cannot be locked immediately. + LockOptionNoWait + // With SKIP LOCKED, any selected rows that cannot be immediately locked are skipped. + LockOptionSkipLocked +) + +// LockRowsDuringTx locks the found rows for the duration of the transaction, +// using the [`FOR UPDATE`](https://www.postgresql.org/docs/17/sql-select.html#SQL-FOR-UPDATE-SHARE) lock strength. +// The lock is removed on transaction commit or rollback. +func (builder *SearchQueryBuilder) LockRowsDuringTx(tx *sql.Tx, option LockOption) *SearchQueryBuilder { + builder.tx = tx + builder.lockRows = true + builder.lockOption = option + return builder +} + // AddQuery creates a new sub query. // All fields in the sub query are AND-connected in the storage request. // Multiple sub queries are OR-connected in the storage request. @@ -319,6 +365,16 @@ func (builder *SearchQueryBuilder) AddQuery() *SearchQuery { return query } +// ExcludeAggregateIDs excludes events from the aggregate IDs returned by the [ExclusionQuery]. +// There can be only 1 exclusion query. Subsequent calls overwrite previous definitions. +func (builder *SearchQueryBuilder) ExcludeAggregateIDs() *ExclusionQuery { + query := &ExclusionQuery{ + builder: builder, + } + builder.excludeAggregateIDs = query + return query +} + // Or creates a new sub query on the search query builder func (query SearchQuery) Or() *SearchQuery { return query.builder.AddQuery() @@ -371,3 +427,20 @@ func (query *SearchQuery) matches(command Command) bool { } return true } + +// AggregateTypes filters for events with the given aggregate types +func (query *ExclusionQuery) AggregateTypes(types ...AggregateType) *ExclusionQuery { + query.aggregateTypes = types + return query +} + +// EventTypes filters for events with the given event types +func (query *ExclusionQuery) EventTypes(types ...EventType) *ExclusionQuery { + query.eventTypes = types + return query +} + +// Builder returns the SearchQueryBuilder of the sub query +func (query *ExclusionQuery) Builder() *SearchQueryBuilder { + return query.builder +} diff --git a/internal/eventstore/v3/event.go b/internal/eventstore/v3/event.go index e1c95f13ff..da4e7a0383 100644 --- a/internal/eventstore/v3/event.go +++ b/internal/eventstore/v3/event.go @@ -1,11 +1,13 @@ package eventstore import ( + "context" "encoding/json" + "strconv" "time" "github.com/zitadel/logging" - + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -14,33 +16,98 @@ var ( _ eventstore.Event = (*event)(nil) ) +type command struct { + InstanceID string + AggregateType string + AggregateID string + CommandType string + Revision uint16 + Payload Payload + Creator string + Owner string +} + +func (c *command) Aggregate() *eventstore.Aggregate { + return &eventstore.Aggregate{ + ID: c.AggregateID, + Type: eventstore.AggregateType(c.AggregateType), + ResourceOwner: c.Owner, + InstanceID: c.InstanceID, + Version: eventstore.Version("v" + strconv.Itoa(int(c.Revision))), + } +} + type event struct { - aggregate *eventstore.Aggregate - creator string - revision uint16 - typ eventstore.EventType + command *command createdAt time.Time sequence uint64 position float64 - payload Payload } -func commandToEvent(sequence *latestSequence, command eventstore.Command) (_ *event, err error) { +// TODO: remove on v3 +func commandToEventOld(sequence *latestSequence, cmd eventstore.Command) (_ *event, err error) { var payload Payload - if command.Payload() != nil { - payload, err = json.Marshal(command.Payload()) + if cmd.Payload() != nil { + payload, err = json.Marshal(cmd.Payload()) if err != nil { logging.WithError(err).Warn("marshal payload failed") return nil, zerrors.ThrowInternal(err, "V3-MInPK", "Errors.Internal") } } return &event{ - aggregate: sequence.aggregate, - creator: command.Creator(), - revision: command.Revision(), - typ: command.Type(), - payload: payload, - sequence: sequence.sequence, + command: &command{ + InstanceID: sequence.aggregate.InstanceID, + AggregateType: string(sequence.aggregate.Type), + AggregateID: sequence.aggregate.ID, + CommandType: string(cmd.Type()), + Revision: cmd.Revision(), + Payload: payload, + Creator: cmd.Creator(), + Owner: sequence.aggregate.ResourceOwner, + }, + sequence: sequence.sequence, + }, nil +} + +func commandsToEvents(ctx context.Context, cmds []eventstore.Command) (_ []eventstore.Event, _ []*command, err error) { + events := make([]eventstore.Event, len(cmds)) + commands := make([]*command, len(cmds)) + for i, cmd := range cmds { + if cmd.Aggregate().InstanceID == "" { + cmd.Aggregate().InstanceID = authz.GetInstance(ctx).InstanceID() + } + events[i], err = commandToEvent(cmd) + if err != nil { + return nil, nil, err + } + commands[i] = events[i].(*event).command + } + return events, commands, nil +} + +func commandToEvent(cmd eventstore.Command) (_ eventstore.Event, err error) { + var payload Payload + if cmd.Payload() != nil { + payload, err = json.Marshal(cmd.Payload()) + if err != nil { + logging.WithError(err).Warn("marshal payload failed") + return nil, zerrors.ThrowInternal(err, "V3-MInPK", "Errors.Internal") + } + } + + command := &command{ + InstanceID: cmd.Aggregate().InstanceID, + AggregateType: string(cmd.Aggregate().Type), + AggregateID: cmd.Aggregate().ID, + CommandType: string(cmd.Type()), + Revision: cmd.Revision(), + Payload: payload, + Creator: cmd.Creator(), + Owner: cmd.Aggregate().ResourceOwner, + } + + return &event{ + command: command, }, nil } @@ -56,22 +123,22 @@ func (e *event) EditorUser() string { // Aggregate implements [eventstore.Event] func (e *event) Aggregate() *eventstore.Aggregate { - return e.aggregate + return e.command.Aggregate() } // Creator implements [eventstore.Event] func (e *event) Creator() string { - return e.creator + return e.command.Creator } // Revision implements [eventstore.Event] func (e *event) Revision() uint16 { - return e.revision + return e.command.Revision } // Type implements [eventstore.Event] func (e *event) Type() eventstore.EventType { - return e.typ + return eventstore.EventType(e.command.CommandType) } // CreatedAt implements [eventstore.Event] @@ -91,10 +158,10 @@ func (e *event) Position() float64 { // Unmarshal implements [eventstore.Event] func (e *event) Unmarshal(ptr any) error { - if len(e.payload) == 0 { + if len(e.command.Payload) == 0 { return nil } - if err := json.Unmarshal(e.payload, ptr); err != nil { + if err := json.Unmarshal(e.command.Payload, ptr); err != nil { return zerrors.ThrowInternal(err, "V3-u8qVo", "Errors.Internal") } @@ -103,5 +170,5 @@ func (e *event) Unmarshal(ptr any) error { // DataAsBytes implements [eventstore.Event] func (e *event) DataAsBytes() []byte { - return e.payload + return e.command.Payload } diff --git a/internal/eventstore/v3/event_test.go b/internal/eventstore/v3/event_test.go index 82a3aa1c9b..bd813c6ed2 100644 --- a/internal/eventstore/v3/event_test.go +++ b/internal/eventstore/v3/event_test.go @@ -1,16 +1,122 @@ package eventstore import ( + "context" "encoding/json" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/eventstore" ) func Test_commandToEvent(t *testing.T) { + payload := struct { + ID string + }{ + ID: "test", + } + payloadMarshalled, err := json.Marshal(payload) + if err != nil { + t.Fatalf("marshal of payload failed: %v", err) + } + type args struct { + command eventstore.Command + } + type want struct { + event *event + err func(t *testing.T, err error) + } + tests := []struct { + name string + args args + want want + }{ + { + name: "no payload", + args: args{ + command: &mockCommand{ + aggregate: mockAggregate("V3-Red9I"), + payload: nil, + }, + }, + want: want{ + event: mockEvent( + mockAggregate("V3-Red9I"), + 0, + nil, + ).(*event), + }, + }, + { + name: "struct payload", + args: args{ + command: &mockCommand{ + aggregate: mockAggregate("V3-Red9I"), + payload: payload, + }, + }, + want: want{ + event: mockEvent( + mockAggregate("V3-Red9I"), + 0, + payloadMarshalled, + ).(*event), + }, + }, + { + name: "pointer payload", + args: args{ + command: &mockCommand{ + aggregate: mockAggregate("V3-Red9I"), + payload: &payload, + }, + }, + want: want{ + event: mockEvent( + mockAggregate("V3-Red9I"), + 0, + payloadMarshalled, + ).(*event), + }, + }, + { + name: "invalid payload", + args: args{ + command: &mockCommand{ + aggregate: mockAggregate("V3-Red9I"), + payload: func() {}, + }, + }, + want: want{ + err: func(t *testing.T, err error) { + assert.Error(t, err) + }, + }, + }, + } + for _, tt := range tests { + if tt.want.err == nil { + tt.want.err = func(t *testing.T, err error) { + require.NoError(t, err) + } + } + t.Run(tt.name, func(t *testing.T) { + got, err := commandToEvent(tt.args.command) + + tt.want.err(t, err) + if tt.want.event == nil { + assert.Nil(t, got) + return + } + assert.Equal(t, tt.want.event, got) + }) + } +} + +func Test_commandToEventOld(t *testing.T) { payload := struct { ID string }{ @@ -119,10 +225,258 @@ func Test_commandToEvent(t *testing.T) { } } t.Run(tt.name, func(t *testing.T) { - got, err := commandToEvent(tt.args.sequence, tt.args.command) + got, err := commandToEventOld(tt.args.sequence, tt.args.command) tt.want.err(t, err) assert.Equal(t, tt.want.event, got) }) } } + +func Test_commandsToEvents(t *testing.T) { + ctx := context.Background() + payload := struct { + ID string + }{ + ID: "test", + } + payloadMarshalled, err := json.Marshal(payload) + if err != nil { + t.Fatalf("marshal of payload failed: %v", err) + } + type args struct { + ctx context.Context + cmds []eventstore.Command + } + type want struct { + events []eventstore.Event + commands []*command + err func(t *testing.T, err error) + } + tests := []struct { + name string + args args + want want + }{ + { + name: "no commands", + args: args{ + ctx: ctx, + cmds: nil, + }, + want: want{ + events: []eventstore.Event{}, + commands: []*command{}, + err: func(t *testing.T, err error) { + require.NoError(t, err) + }, + }, + }, + { + name: "single command no payload", + args: args{ + ctx: ctx, + cmds: []eventstore.Command{ + &mockCommand{ + aggregate: mockAggregate("V3-Red9I"), + payload: nil, + }, + }, + }, + want: want{ + events: []eventstore.Event{ + mockEvent( + mockAggregate("V3-Red9I"), + 0, + nil, + ), + }, + commands: []*command{ + { + InstanceID: "instance", + AggregateType: "type", + AggregateID: "V3-Red9I", + Owner: "ro", + CommandType: "event.type", + Revision: 1, + Payload: nil, + Creator: "creator", + }, + }, + err: func(t *testing.T, err error) { + require.NoError(t, err) + }, + }, + }, + { + name: "single command no instance id", + args: args{ + ctx: authz.WithInstanceID(ctx, "instance from ctx"), + cmds: []eventstore.Command{ + &mockCommand{ + aggregate: mockAggregateWithInstance("V3-Red9I", ""), + payload: nil, + }, + }, + }, + want: want{ + events: []eventstore.Event{ + mockEvent( + mockAggregateWithInstance("V3-Red9I", "instance from ctx"), + 0, + nil, + ), + }, + commands: []*command{ + { + InstanceID: "instance from ctx", + AggregateType: "type", + AggregateID: "V3-Red9I", + Owner: "ro", + CommandType: "event.type", + Revision: 1, + Payload: nil, + Creator: "creator", + }, + }, + err: func(t *testing.T, err error) { + require.NoError(t, err) + }, + }, + }, + { + name: "single command with payload", + args: args{ + ctx: ctx, + cmds: []eventstore.Command{ + &mockCommand{ + aggregate: mockAggregate("V3-Red9I"), + payload: payload, + }, + }, + }, + want: want{ + events: []eventstore.Event{ + mockEvent( + mockAggregate("V3-Red9I"), + 0, + payloadMarshalled, + ), + }, + commands: []*command{ + { + InstanceID: "instance", + AggregateType: "type", + AggregateID: "V3-Red9I", + Owner: "ro", + CommandType: "event.type", + Revision: 1, + Payload: payloadMarshalled, + Creator: "creator", + }, + }, + err: func(t *testing.T, err error) { + require.NoError(t, err) + }, + }, + }, + { + name: "multiple commands", + args: args{ + ctx: ctx, + cmds: []eventstore.Command{ + &mockCommand{ + aggregate: mockAggregate("V3-Red9I"), + payload: payload, + }, + &mockCommand{ + aggregate: mockAggregate("V3-Red9I"), + payload: nil, + }, + }, + }, + want: want{ + events: []eventstore.Event{ + mockEvent( + mockAggregate("V3-Red9I"), + 0, + payloadMarshalled, + ), + mockEvent( + mockAggregate("V3-Red9I"), + 0, + nil, + ), + }, + commands: []*command{ + { + InstanceID: "instance", + AggregateType: "type", + AggregateID: "V3-Red9I", + CommandType: "event.type", + Revision: 1, + Payload: payloadMarshalled, + Creator: "creator", + Owner: "ro", + }, + { + InstanceID: "instance", + AggregateType: "type", + AggregateID: "V3-Red9I", + CommandType: "event.type", + Revision: 1, + Payload: nil, + Creator: "creator", + Owner: "ro", + }, + }, + err: func(t *testing.T, err error) { + require.NoError(t, err) + }, + }, + }, + { + name: "invalid command", + args: args{ + ctx: ctx, + cmds: []eventstore.Command{ + &mockCommand{ + aggregate: mockAggregate("V3-Red9I"), + payload: func() {}, + }, + }, + }, + want: want{ + events: nil, + commands: nil, + err: func(t *testing.T, err error) { + assert.Error(t, err) + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotEvents, gotCommands, err := commandsToEvents(tt.args.ctx, tt.args.cmds) + + tt.want.err(t, err) + assert.Equal(t, tt.want.events, gotEvents) + require.Len(t, gotCommands, len(tt.want.commands)) + for i, wantCommand := range tt.want.commands { + assertCommand(t, wantCommand, gotCommands[i]) + } + }) + } +} + +func assertCommand(t *testing.T, want, got *command) { + t.Helper() + assert.Equal(t, want.CommandType, got.CommandType) + assert.Equal(t, want.Payload, got.Payload) + assert.Equal(t, want.Creator, got.Creator) + assert.Equal(t, want.Owner, got.Owner) + assert.Equal(t, want.AggregateID, got.AggregateID) + assert.Equal(t, want.AggregateType, got.AggregateType) + assert.Equal(t, want.InstanceID, got.InstanceID) + assert.Equal(t, want.Revision, got.Revision) +} diff --git a/internal/eventstore/v3/eventstore.go b/internal/eventstore/v3/eventstore.go index 4ecaf6bad2..1bb515527c 100644 --- a/internal/eventstore/v3/eventstore.go +++ b/internal/eventstore/v3/eventstore.go @@ -2,21 +2,161 @@ package eventstore import ( "context" + "database/sql" + "encoding/json" + "errors" + "sync" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" + "github.com/jackc/pgx/v5/stdlib" + "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/database" + "github.com/zitadel/zitadel/internal/database/dialect" + "github.com/zitadel/zitadel/internal/eventstore" ) +func init() { + dialect.RegisterAfterConnect(RegisterEventstoreTypes) +} + var ( // pushPlaceholderFmt defines how data are inserted into the events table pushPlaceholderFmt string // uniqueConstraintPlaceholderFmt defines the format of the unique constraint error returned from the database uniqueConstraintPlaceholderFmt string + + _ eventstore.Pusher = (*Eventstore)(nil) ) type Eventstore struct { client *database.DB } +var ( + textType = &pgtype.Type{ + Name: "text", + OID: pgtype.TextOID, + Codec: pgtype.TextCodec{}, + } + commandType = &pgtype.Type{ + Codec: &pgtype.CompositeCodec{ + Fields: []pgtype.CompositeCodecField{ + { + Name: "instance_id", + Type: textType, + }, + { + Name: "aggregate_type", + Type: textType, + }, + { + Name: "aggregate_id", + Type: textType, + }, + { + Name: "command_type", + Type: textType, + }, + { + Name: "revision", + Type: &pgtype.Type{ + Name: "int2", + OID: pgtype.Int2OID, + Codec: pgtype.Int2Codec{}, + }, + }, + { + Name: "payload", + Type: &pgtype.Type{ + Name: "jsonb", + OID: pgtype.JSONBOID, + Codec: &pgtype.JSONBCodec{ + Marshal: json.Marshal, + Unmarshal: json.Unmarshal, + }, + }, + }, + { + Name: "creator", + Type: textType, + }, + { + Name: "owner", + Type: textType, + }, + }, + }, + } + commandArrayCodec = &pgtype.Type{ + Codec: &pgtype.ArrayCodec{ + ElementType: commandType, + }, + } +) + +var typeMu sync.Mutex + +func RegisterEventstoreTypes(ctx context.Context, conn *pgx.Conn) error { + // conn.TypeMap is not thread safe + typeMu.Lock() + defer typeMu.Unlock() + + m := conn.TypeMap() + + var cmd *command + if _, ok := m.TypeForValue(cmd); ok { + return nil + } + + if commandType.OID == 0 || commandArrayCodec.OID == 0 { + err := conn.QueryRow(ctx, "select oid, typarray from pg_type where typname = $1 and typnamespace = (select oid from pg_namespace where nspname = $2)", "command", "eventstore"). + Scan(&commandType.OID, &commandArrayCodec.OID) + if err != nil { + logging.WithError(err).Debug("failed to get oid for command type") + return nil + } + if commandType.OID == 0 || commandArrayCodec.OID == 0 { + logging.Debug("oid for command type not found") + return nil + } + } + + m.RegisterTypes([]*pgtype.Type{ + { + Name: "eventstore.command", + Codec: commandType.Codec, + OID: commandType.OID, + }, + { + Name: "command", + Codec: commandType.Codec, + OID: commandType.OID, + }, + { + Name: "eventstore._command", + Codec: commandArrayCodec.Codec, + OID: commandArrayCodec.OID, + }, + { + Name: "_command", + Codec: commandArrayCodec.Codec, + OID: commandArrayCodec.OID, + }, + }) + dialect.RegisterDefaultPgTypeVariants[command](m, "eventstore.command", "eventstore._command") + dialect.RegisterDefaultPgTypeVariants[command](m, "command", "_command") + + return nil +} + +// Client implements the [eventstore.Pusher] +func (es *Eventstore) Client() *database.DB { + return es.client +} + func NewEventstore(client *database.DB) *Eventstore { switch client.Type() { case "cockroach": @@ -33,3 +173,45 @@ func NewEventstore(client *database.DB) *Eventstore { func (es *Eventstore) Health(ctx context.Context) error { return es.client.PingContext(ctx) } + +var errTypesNotFound = errors.New("types not found") + +func CheckExecutionPlan(ctx context.Context, conn *sql.Conn) error { + return conn.Raw(func(driverConn any) error { + if _, ok := driverConn.(sqlmock.SqlmockCommon); ok { + return nil + } + conn, ok := driverConn.(*stdlib.Conn) + if !ok { + return errTypesNotFound + } + + return RegisterEventstoreTypes(ctx, conn.Conn()) + }) +} + +func (es *Eventstore) pushTx(ctx context.Context, client database.ContextQueryExecuter) (tx database.Tx, deferrable func(err error) error, err error) { + tx, ok := client.(database.Tx) + if ok { + return tx, nil, nil + } + beginner, ok := client.(database.Beginner) + if !ok { + beginner = es.client + } + + isolationLevel := sql.LevelReadCommitted + // cockroach requires serializable to execute the push function + // because we use [cluster_logical_timestamp()](https://www.cockroachlabs.com/docs/stable/functions-and-operators#system-info-functions) + if es.client.Type() == "cockroach" { + isolationLevel = sql.LevelSerializable + } + tx, err = beginner.BeginTx(ctx, &sql.TxOptions{ + Isolation: isolationLevel, + ReadOnly: false, + }) + if err != nil { + return nil, nil, err + } + return tx, func(err error) error { return database.CloseTransaction(tx, err) }, nil +} diff --git a/internal/eventstore/v3/field.go b/internal/eventstore/v3/field.go index 17037f8bcc..b399e7f5e8 100644 --- a/internal/eventstore/v3/field.go +++ b/internal/eventstore/v3/field.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" @@ -142,7 +143,7 @@ func buildSearchCondition(builder *strings.Builder, index int, conditions map[ev return args } -func handleFieldCommands(ctx context.Context, tx *sql.Tx, commands []eventstore.Command) error { +func (es *Eventstore) handleFieldCommands(ctx context.Context, tx database.Tx, commands []eventstore.Command) error { for _, command := range commands { if len(command.Fields()) > 0 { if err := handleFieldOperations(ctx, tx, command.Fields()); err != nil { @@ -153,7 +154,7 @@ func handleFieldCommands(ctx context.Context, tx *sql.Tx, commands []eventstore. return nil } -func handleFieldFillEvents(ctx context.Context, tx *sql.Tx, events []eventstore.FillFieldsEvent) error { +func handleFieldFillEvents(ctx context.Context, tx database.Tx, events []eventstore.FillFieldsEvent) error { for _, event := range events { if len(event.Fields()) > 0 { if err := handleFieldOperations(ctx, tx, event.Fields()); err != nil { @@ -164,7 +165,7 @@ func handleFieldFillEvents(ctx context.Context, tx *sql.Tx, events []eventstore. return nil } -func handleFieldOperations(ctx context.Context, tx *sql.Tx, operations []*eventstore.FieldOperation) error { +func handleFieldOperations(ctx context.Context, tx database.Tx, operations []*eventstore.FieldOperation) error { for _, operation := range operations { if operation.Set != nil { if err := handleFieldSet(ctx, tx, operation.Set); err != nil { @@ -182,7 +183,7 @@ func handleFieldOperations(ctx context.Context, tx *sql.Tx, operations []*events return nil } -func handleFieldSet(ctx context.Context, tx *sql.Tx, field *eventstore.Field) error { +func handleFieldSet(ctx context.Context, tx database.Tx, field *eventstore.Field) error { if len(field.UpsertConflictFields) == 0 { return handleSearchInsert(ctx, tx, field) } @@ -193,7 +194,7 @@ const ( insertField = `INSERT INTO eventstore.fields (instance_id, resource_owner, aggregate_type, aggregate_id, object_type, object_id, object_revision, field_name, value, value_must_be_unique, should_index) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)` ) -func handleSearchInsert(ctx context.Context, tx *sql.Tx, field *eventstore.Field) error { +func handleSearchInsert(ctx context.Context, tx database.Tx, field *eventstore.Field) error { value, err := json.Marshal(field.Value.Value) if err != nil { return zerrors.ThrowInvalidArgument(err, "V3-fcrW1", "unable to marshal field value") @@ -222,7 +223,7 @@ const ( fieldsUpsertSuffix = ` RETURNING * ) INSERT INTO eventstore.fields (instance_id, resource_owner, aggregate_type, aggregate_id, object_type, object_id, object_revision, field_name, value, value_must_be_unique, should_index) SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11 WHERE NOT EXISTS (SELECT 1 FROM upsert)` ) -func handleSearchUpsert(ctx context.Context, tx *sql.Tx, field *eventstore.Field) error { +func handleSearchUpsert(ctx context.Context, tx database.Tx, field *eventstore.Field) error { value, err := json.Marshal(field.Value.Value) if err != nil { return zerrors.ThrowInvalidArgument(err, "V3-fcrW1", "unable to marshal field value") @@ -268,7 +269,7 @@ func writeUpsertField(fields []eventstore.FieldType) string { const removeSearch = `DELETE FROM eventstore.fields WHERE ` -func handleSearchDelete(ctx context.Context, tx *sql.Tx, clauses map[eventstore.FieldType]any) error { +func handleSearchDelete(ctx context.Context, tx database.Tx, clauses map[eventstore.FieldType]any) error { if len(clauses) == 0 { return zerrors.ThrowInvalidArgument(nil, "V3-oqlBZ", "no conditions") } diff --git a/internal/eventstore/v3/mock_test.go b/internal/eventstore/v3/mock_test.go index 8de5ae8e2c..89a50f8fd4 100644 --- a/internal/eventstore/v3/mock_test.go +++ b/internal/eventstore/v3/mock_test.go @@ -48,12 +48,17 @@ func (e *mockCommand) Fields() []*eventstore.FieldOperation { func mockEvent(aggregate *eventstore.Aggregate, sequence uint64, payload Payload) eventstore.Event { return &event{ - aggregate: aggregate, - creator: "creator", - revision: 1, - typ: "event.type", - sequence: sequence, - payload: payload, + command: &command{ + InstanceID: aggregate.InstanceID, + AggregateType: string(aggregate.Type), + AggregateID: aggregate.ID, + Owner: aggregate.ResourceOwner, + Creator: "creator", + Revision: 1, + CommandType: "event.type", + Payload: payload, + }, + sequence: sequence, } } @@ -66,3 +71,13 @@ func mockAggregate(id string) *eventstore.Aggregate { Version: "v1", } } + +func mockAggregateWithInstance(id, instance string) *eventstore.Aggregate { + return &eventstore.Aggregate{ + ID: id, + InstanceID: instance, + Type: "type", + ResourceOwner: "ro", + Version: "v1", + } +} diff --git a/internal/eventstore/v3/push.go b/internal/eventstore/v3/push.go index 47a4c96dca..fb597021e2 100644 --- a/internal/eventstore/v3/push.go +++ b/internal/eventstore/v3/push.go @@ -4,81 +4,67 @@ import ( "context" "database/sql" _ "embed" - "errors" - "fmt" - "strconv" - "strings" - "github.com/cockroachdb/cockroach-go/v2/crdb" - "github.com/jackc/pgx/v5/pgconn" "github.com/zitadel/logging" - "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/database/dialect" + "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/telemetry/tracing" - "github.com/zitadel/zitadel/internal/zerrors" ) -var appNamePrefix = dialect.DBPurposeEventPusher.AppName() + "_" +var pushTxOpts = &sql.TxOptions{ + Isolation: sql.LevelReadCommitted, + ReadOnly: false, +} -func (es *Eventstore) Push(ctx context.Context, commands ...eventstore.Command) (events []eventstore.Event, err error) { +func (es *Eventstore) Push(ctx context.Context, client database.ContextQueryExecuter, commands ...eventstore.Command) (events []eventstore.Event, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() - tx, err := es.client.BeginTx(ctx, nil) + events, err = es.writeCommands(ctx, client, commands) + if isSetupNotExecutedError(err) { + return es.pushWithoutFunc(ctx, client, commands...) + } + + return events, err +} + +func (es *Eventstore) writeCommands(ctx context.Context, client database.ContextQueryExecuter, commands []eventstore.Command) (_ []eventstore.Event, err error) { + var conn *sql.Conn + switch c := client.(type) { + case database.Client: + conn, err = c.Conn(ctx) + case nil: + conn, err = es.client.Conn(ctx) + client = conn + } if err != nil { return nil, err } - // tx is not closed because [crdb.ExecuteInTx] takes care of that - var ( - sequences []*latestSequence - ) + if conn != nil { + defer conn.Close() + } - // needs to be set like this because psql complains about parameters in the SET statement - _, err = tx.ExecContext(ctx, "SET application_name = '"+appNamePrefix+authz.GetInstance(ctx).InstanceID()+"'") + tx, close, err := es.pushTx(ctx, client) + if err != nil { + return nil, err + } + if close != nil { + defer func() { + err = close(err) + }() + } + + events, err := writeEvents(ctx, tx, commands) if err != nil { - logging.WithError(err).Warn("failed to set application name") return nil, err } - // needs to be set like this because psql complains about parameters in the SET statement - _, err = tx.ExecContext(ctx, "SET application_name = '"+appNamePrefix+authz.GetInstance(ctx).InstanceID()+"'") - if err != nil { - logging.WithError(err).Warn("failed to set application name") + if err = handleUniqueConstraints(ctx, tx, commands); err != nil { return nil, err } - err = crdb.ExecuteInTx(ctx, &transaction{tx}, func() (err error) { - inTxCtx, span := tracing.NewSpan(ctx) - defer func() { span.EndWithError(err) }() - - sequences, err = latestSequences(inTxCtx, tx, commands) - if err != nil { - return err - } - - events, err = insertEvents(inTxCtx, tx, sequences, commands) - if err != nil { - return err - } - - if err = handleUniqueConstraints(inTxCtx, tx, commands); err != nil { - return err - } - - // CockroachDB by default does not allow multiple modifications of the same table using ON CONFLICT - // Thats why we enable it manually - if es.client.Type() == "cockroach" { - _, err = tx.Exec("SET enable_multiple_modifications_of_table = on") - if err != nil { - return err - } - } - - return handleFieldCommands(inTxCtx, tx, commands) - }) - + err = es.handleFieldCommands(ctx, tx, commands) if err != nil { return nil, err } @@ -86,120 +72,30 @@ func (es *Eventstore) Push(ctx context.Context, commands ...eventstore.Command) return events, nil } -//go:embed push.sql -var pushStmt string +func writeEvents(ctx context.Context, tx database.Tx, commands []eventstore.Command) (_ []eventstore.Event, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() -func insertEvents(ctx context.Context, tx *sql.Tx, sequences []*latestSequence, commands []eventstore.Command) ([]eventstore.Event, error) { - events, placeholders, args, err := mapCommands(commands, sequences) + events, cmds, err := commandsToEvents(ctx, commands) if err != nil { return nil, err } - rows, err := tx.QueryContext(ctx, fmt.Sprintf(pushStmt, strings.Join(placeholders, ", ")), args...) + rows, err := tx.QueryContext(ctx, `select owner, created_at, "sequence", position from eventstore.push($1::eventstore.command[])`, cmds) if err != nil { return nil, err } defer rows.Close() for i := 0; rows.Next(); i++ { - err = rows.Scan(&events[i].(*event).createdAt, &events[i].(*event).position) + err = rows.Scan(&events[i].(*event).command.Owner, &events[i].(*event).createdAt, &events[i].(*event).sequence, &events[i].(*event).position) if err != nil { logging.WithError(err).Warn("failed to scan events") return nil, err } } - - if err := rows.Err(); err != nil { - pgErr := new(pgconn.PgError) - if errors.As(err, &pgErr) { - // Check if push tries to write an event just written - // by another transaction - if pgErr.Code == "40001" { - // TODO: @livio-a should we return the parent or not? - return nil, zerrors.ThrowInvalidArgument(err, "V3-p5xAn", "Errors.AlreadyExists") - } - } - logging.WithError(rows.Err()).Warn("failed to push events") - return nil, zerrors.ThrowInternal(err, "V3-VGnZY", "Errors.Internal") + if err = rows.Err(); err != nil { + return nil, err } - return events, nil } - -const argsPerCommand = 10 - -func mapCommands(commands []eventstore.Command, sequences []*latestSequence) (events []eventstore.Event, placeholders []string, args []any, err error) { - events = make([]eventstore.Event, len(commands)) - args = make([]any, 0, len(commands)*argsPerCommand) - placeholders = make([]string, len(commands)) - - for i, command := range commands { - sequence := searchSequenceByCommand(sequences, command) - if sequence == nil { - logging.WithFields( - "aggType", command.Aggregate().Type, - "aggID", command.Aggregate().ID, - "instance", command.Aggregate().InstanceID, - ).Panic("no sequence found") - // added return for linting - return nil, nil, nil, nil - } - sequence.sequence++ - - events[i], err = commandToEvent(sequence, command) - if err != nil { - return nil, nil, nil, err - } - - placeholders[i] = fmt.Sprintf(pushPlaceholderFmt, - i*argsPerCommand+1, - i*argsPerCommand+2, - i*argsPerCommand+3, - i*argsPerCommand+4, - i*argsPerCommand+5, - i*argsPerCommand+6, - i*argsPerCommand+7, - i*argsPerCommand+8, - i*argsPerCommand+9, - i*argsPerCommand+10, - ) - - revision, err := strconv.Atoi(strings.TrimPrefix(string(events[i].(*event).aggregate.Version), "v")) - if err != nil { - return nil, nil, nil, zerrors.ThrowInternal(err, "V3-JoZEp", "Errors.Internal") - } - args = append(args, - events[i].(*event).aggregate.InstanceID, - events[i].(*event).aggregate.ResourceOwner, - events[i].(*event).aggregate.Type, - events[i].(*event).aggregate.ID, - revision, - events[i].(*event).creator, - events[i].(*event).typ, - events[i].(*event).payload, - events[i].(*event).sequence, - i, - ) - } - - return events, placeholders, args, nil -} - -type transaction struct { - *sql.Tx -} - -var _ crdb.Tx = (*transaction)(nil) - -func (t *transaction) Exec(ctx context.Context, query string, args ...interface{}) error { - _, err := t.Tx.ExecContext(ctx, query, args...) - return err -} - -func (t *transaction) Commit(ctx context.Context) error { - return t.Tx.Commit() -} - -func (t *transaction) Rollback(ctx context.Context) error { - return t.Tx.Rollback() -} diff --git a/internal/eventstore/v3/push_test.go b/internal/eventstore/v3/push_test.go index 0f16a2ac75..a6c4f515fd 100644 --- a/internal/eventstore/v3/push_test.go +++ b/internal/eventstore/v3/push_test.go @@ -70,11 +70,11 @@ func Test_mapCommands(t *testing.T) { args: []any{ "instance", "ro", - eventstore.AggregateType("type"), + "type", "V3-VEIvq", - 1, + uint16(1), "creator", - eventstore.EventType("event.type"), + "event.type", Payload(nil), uint64(1), 0, @@ -121,22 +121,22 @@ func Test_mapCommands(t *testing.T) { // first event "instance", "ro", - eventstore.AggregateType("type"), + "type", "V3-VEIvq", - 1, + uint16(1), "creator", - eventstore.EventType("event.type"), + "event.type", Payload(nil), uint64(6), 0, // second event "instance", "ro", - eventstore.AggregateType("type"), + "type", "V3-VEIvq", - 1, + uint16(1), "creator", - eventstore.EventType("event.type"), + "event.type", Payload(nil), uint64(7), 1, @@ -187,22 +187,22 @@ func Test_mapCommands(t *testing.T) { // first event "instance", "ro", - eventstore.AggregateType("type"), + "type", "V3-VEIvq", - 1, + uint16(1), "creator", - eventstore.EventType("event.type"), + "event.type", Payload(nil), uint64(6), 0, // second event "instance", "ro", - eventstore.AggregateType("type"), + "type", "V3-IT6VN", - 1, + uint16(1), "creator", - eventstore.EventType("event.type"), + "event.type", Payload(nil), uint64(1), 1, diff --git a/internal/eventstore/v3/push_without_func.go b/internal/eventstore/v3/push_without_func.go new file mode 100644 index 0000000000..914b880204 --- /dev/null +++ b/internal/eventstore/v3/push_without_func.go @@ -0,0 +1,183 @@ +package eventstore + +import ( + "context" + _ "embed" + "errors" + "fmt" + "strings" + + "github.com/cockroachdb/cockroach-go/v2/crdb" + "github.com/jackc/pgx/v5/pgconn" + "github.com/zitadel/logging" + + "github.com/zitadel/zitadel/internal/database" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/zerrors" +) + +type transaction struct { + database.Tx +} + +var _ crdb.Tx = (*transaction)(nil) + +func (t *transaction) Exec(ctx context.Context, query string, args ...interface{}) error { + _, err := t.Tx.ExecContext(ctx, query, args...) + return err +} + +func (t *transaction) Commit(ctx context.Context) error { + return t.Tx.Commit() +} + +func (t *transaction) Rollback(ctx context.Context) error { + return t.Tx.Rollback() +} + +// checks whether the error is caused because setup step 39 was not executed +func isSetupNotExecutedError(err error) bool { + if err == nil { + return false + } + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) { + return (pgErr.Code == "42704" && strings.Contains(pgErr.Message, "eventstore.command")) || + (pgErr.Code == "42883" && strings.Contains(pgErr.Message, "eventstore.push")) + } + return errors.Is(err, errTypesNotFound) +} + +var ( + //go:embed push.sql + pushStmt string +) + +// pushWithoutFunc implements pushing events before setup step 39 was introduced. +// TODO: remove with v3 +func (es *Eventstore) pushWithoutFunc(ctx context.Context, client database.ContextQueryExecuter, commands ...eventstore.Command) (events []eventstore.Event, err error) { + tx, closeTx, err := es.pushTx(ctx, client) + if err != nil { + return nil, err + } + defer func() { + err = closeTx(err) + }() + + // tx is not closed because [crdb.ExecuteInTx] takes care of that + var ( + sequences []*latestSequence + ) + sequences, err = latestSequences(ctx, tx, commands) + if err != nil { + return nil, err + } + + events, err = es.writeEventsOld(ctx, tx, sequences, commands) + if err != nil { + return nil, err + } + + if err = handleUniqueConstraints(ctx, tx, commands); err != nil { + return nil, err + } + + err = es.handleFieldCommands(ctx, tx, commands) + if err != nil { + return nil, err + } + + return events, nil +} + +func (es *Eventstore) writeEventsOld(ctx context.Context, tx database.Tx, sequences []*latestSequence, commands []eventstore.Command) ([]eventstore.Event, error) { + events, placeholders, args, err := mapCommands(commands, sequences) + if err != nil { + return nil, err + } + + rows, err := tx.QueryContext(ctx, fmt.Sprintf(pushStmt, strings.Join(placeholders, ", ")), args...) + if err != nil { + return nil, err + } + defer rows.Close() + + for i := 0; rows.Next(); i++ { + err = rows.Scan(&events[i].(*event).createdAt, &events[i].(*event).position) + if err != nil { + logging.WithError(err).Warn("failed to scan events") + return nil, err + } + } + + if err := rows.Err(); err != nil { + pgErr := new(pgconn.PgError) + if errors.As(err, &pgErr) { + // Check if push tries to write an event just written + // by another transaction + if pgErr.Code == "40001" { + // TODO: @livio-a should we return the parent or not? + return nil, zerrors.ThrowInvalidArgument(err, "V3-p5xAn", "Errors.AlreadyExists") + } + } + logging.WithError(rows.Err()).Warn("failed to push events") + return nil, zerrors.ThrowInternal(err, "V3-VGnZY", "Errors.Internal") + } + + return events, nil +} + +const argsPerCommand = 10 + +func mapCommands(commands []eventstore.Command, sequences []*latestSequence) (events []eventstore.Event, placeholders []string, args []any, err error) { + events = make([]eventstore.Event, len(commands)) + args = make([]any, 0, len(commands)*argsPerCommand) + placeholders = make([]string, len(commands)) + + for i, command := range commands { + sequence := searchSequenceByCommand(sequences, command) + if sequence == nil { + logging.WithFields( + "aggType", command.Aggregate().Type, + "aggID", command.Aggregate().ID, + "instance", command.Aggregate().InstanceID, + ).Panic("no sequence found") + // added return for linting + return nil, nil, nil, nil + } + sequence.sequence++ + + events[i], err = commandToEventOld(sequence, command) + if err != nil { + return nil, nil, nil, err + } + + placeholders[i] = fmt.Sprintf(pushPlaceholderFmt, + i*argsPerCommand+1, + i*argsPerCommand+2, + i*argsPerCommand+3, + i*argsPerCommand+4, + i*argsPerCommand+5, + i*argsPerCommand+6, + i*argsPerCommand+7, + i*argsPerCommand+8, + i*argsPerCommand+9, + i*argsPerCommand+10, + ) + + args = append(args, + events[i].(*event).command.InstanceID, + events[i].(*event).command.Owner, + events[i].(*event).command.AggregateType, + events[i].(*event).command.AggregateID, + events[i].(*event).command.Revision, + events[i].(*event).command.Creator, + events[i].(*event).command.CommandType, + events[i].(*event).command.Payload, + events[i].(*event).sequence, + i, + ) + } + + return events, placeholders, args, nil +} diff --git a/internal/eventstore/v3/sequence.go b/internal/eventstore/v3/sequence.go index 8d84ef4755..7d97e1080d 100644 --- a/internal/eventstore/v3/sequence.go +++ b/internal/eventstore/v3/sequence.go @@ -10,6 +10,7 @@ import ( "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -22,7 +23,7 @@ type latestSequence struct { //go:embed sequences_query.sql var latestSequencesStmt string -func latestSequences(ctx context.Context, tx *sql.Tx, commands []eventstore.Command) ([]*latestSequence, error) { +func latestSequences(ctx context.Context, tx database.Tx, commands []eventstore.Command) ([]*latestSequence, error) { sequences := commandsToSequences(ctx, commands) conditions, args := sequencesToSql(sequences) diff --git a/internal/eventstore/v3/unique_constraints.go b/internal/eventstore/v3/unique_constraints.go index e3bae89805..a491ae4f5c 100644 --- a/internal/eventstore/v3/unique_constraints.go +++ b/internal/eventstore/v3/unique_constraints.go @@ -2,7 +2,6 @@ package eventstore import ( "context" - "database/sql" _ "embed" "errors" "fmt" @@ -11,7 +10,9 @@ import ( "github.com/jackc/pgx/v5/pgconn" "github.com/zitadel/logging" + "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -24,7 +25,10 @@ var ( addConstraintStmt string ) -func handleUniqueConstraints(ctx context.Context, tx *sql.Tx, commands []eventstore.Command) error { +func handleUniqueConstraints(ctx context.Context, tx database.Tx, commands []eventstore.Command) (err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + deletePlaceholders := make([]string, 0) deleteArgs := make([]any, 0) diff --git a/internal/execution/execution.go b/internal/execution/execution.go index c4756b86a2..99d7f6182f 100644 --- a/internal/execution/execution.go +++ b/internal/execution/execution.go @@ -14,6 +14,7 @@ import ( "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" + "github.com/zitadel/zitadel/pkg/actions" ) type ContextInfo interface { @@ -28,6 +29,7 @@ type Target interface { GetEndpoint() string GetTargetType() domain.TargetType GetTimeout() time.Duration + GetSigningKey() string } // CallTargets call a list of targets in order with handling of error and responses @@ -72,13 +74,13 @@ func CallTarget( switch target.GetTargetType() { // get request, ignore response and return request and error for handling in list of targets case domain.TargetTypeWebhook: - return nil, webhook(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody()) + return nil, webhook(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody(), target.GetSigningKey()) // get request, return response and error case domain.TargetTypeCall: - return Call(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody()) + return Call(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody(), target.GetSigningKey()) case domain.TargetTypeAsync: go func(target Target, info ContextInfoRequest) { - if _, err := Call(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody()); err != nil { + if _, err := Call(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody(), target.GetSigningKey()); err != nil { logging.WithFields("target", target.GetTargetID()).OnError(err).Info(err) } }(target, info) @@ -89,13 +91,13 @@ func CallTarget( } // webhook call a webhook, ignore the response but return the errror -func webhook(ctx context.Context, url string, timeout time.Duration, body []byte) error { - _, err := Call(ctx, url, timeout, body) +func webhook(ctx context.Context, url string, timeout time.Duration, body []byte, signingKey string) error { + _, err := Call(ctx, url, timeout, body, signingKey) return err } // Call function to do a post HTTP request to a desired url with timeout -func Call(ctx context.Context, url string, timeout time.Duration, body []byte) (_ []byte, err error) { +func Call(ctx context.Context, url string, timeout time.Duration, body []byte, signingKey string) (_ []byte, err error) { ctx, cancel := context.WithTimeout(ctx, timeout) ctx, span := tracing.NewSpan(ctx) defer func() { @@ -108,6 +110,9 @@ func Call(ctx context.Context, url string, timeout time.Duration, body []byte) ( return nil, err } req.Header.Set("Content-Type", "application/json") + if signingKey != "" { + req.Header.Set(actions.SigningHeader, actions.ComputeSignatureHeader(time.Now(), body, signingKey)) + } client := http.DefaultClient resp, err := client.Do(req) diff --git a/internal/execution/execution_test.go b/internal/execution/execution_test.go index 184823f9b2..5a45d96625 100644 --- a/internal/execution/execution_test.go +++ b/internal/execution/execution_test.go @@ -18,6 +18,7 @@ import ( "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/execution" "github.com/zitadel/zitadel/internal/zerrors" + "github.com/zitadel/zitadel/pkg/actions" ) func Test_Call(t *testing.T) { @@ -29,6 +30,7 @@ func Test_Call(t *testing.T) { body []byte respBody []byte statusCode int + signingKey string } type res struct { body []byte @@ -84,6 +86,22 @@ func Test_Call(t *testing.T) { body: []byte("{\"response\": \"values\"}"), }, }, + { + "ok, signed", + args{ + ctx: context.Background(), + timeout: time.Minute, + sleep: time.Second, + method: http.MethodPost, + body: []byte("{\"request\": \"values\"}"), + respBody: []byte("{\"response\": \"values\"}"), + statusCode: http.StatusOK, + signingKey: "signingkey", + }, + res{ + body: []byte("{\"response\": \"values\"}"), + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -95,7 +113,7 @@ func Test_Call(t *testing.T) { statusCode: tt.args.statusCode, respondBody: tt.args.respBody, }, - testCall(tt.args.ctx, tt.args.timeout, tt.args.body), + testCall(tt.args.ctx, tt.args.timeout, tt.args.body, tt.args.signingKey), ) if tt.res.wantErr { assert.Error(t, err) @@ -186,6 +204,29 @@ func Test_CallTarget(t *testing.T) { body: nil, }, }, + { + "webhook, signed, ok", + args{ + ctx: context.Background(), + info: requestContextInfo1, + server: &callTestServer{ + timeout: time.Second, + method: http.MethodPost, + expectBody: []byte("{\"request\":{\"request\":\"content1\"}}"), + respondBody: []byte("{\"request\":\"content2\"}"), + statusCode: http.StatusOK, + signingKey: "signingkey", + }, + target: &mockTarget{ + TargetType: domain.TargetTypeWebhook, + Timeout: time.Minute, + SigningKey: "signingkey", + }, + }, + res{ + body: nil, + }, + }, { "request response, error", args{ @@ -228,6 +269,29 @@ func Test_CallTarget(t *testing.T) { body: []byte("{\"request\":\"content2\"}"), }, }, + { + "request response, signed, ok", + args{ + ctx: context.Background(), + info: requestContextInfo1, + server: &callTestServer{ + timeout: time.Second, + method: http.MethodPost, + expectBody: []byte("{\"request\":{\"request\":\"content1\"}}"), + respondBody: []byte("{\"request\":\"content2\"}"), + statusCode: http.StatusOK, + signingKey: "signingkey", + }, + target: &mockTarget{ + TargetType: domain.TargetTypeCall, + Timeout: time.Minute, + SigningKey: "signingkey", + }, + }, + res{ + body: []byte("{\"request\":\"content2\"}"), + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -392,6 +456,7 @@ type mockTarget struct { Endpoint string Timeout time.Duration InterruptOnError bool + SigningKey string } func (e *mockTarget) GetTargetID() string { @@ -409,6 +474,9 @@ func (e *mockTarget) GetTargetType() domain.TargetType { func (e *mockTarget) GetTimeout() time.Duration { return e.Timeout } +func (e *mockTarget) GetSigningKey() string { + return e.SigningKey +} type callTestServer struct { method string @@ -416,6 +484,7 @@ type callTestServer struct { timeout time.Duration statusCode int respondBody []byte + signingKey string } func testServers( @@ -447,7 +516,7 @@ func listen( c *callTestServer, ) (url string, close func()) { handler := func(w http.ResponseWriter, r *http.Request) { - checkRequest(t, r, c.method, c.expectBody) + checkRequest(t, r, c.method, c.expectBody, c.signingKey) if c.statusCode != http.StatusOK { http.Error(w, "error", c.statusCode) @@ -466,16 +535,19 @@ func listen( return server.URL, server.Close } -func checkRequest(t *testing.T, sent *http.Request, method string, expectedBody []byte) { +func checkRequest(t *testing.T, sent *http.Request, method string, expectedBody []byte, signingKey string) { sentBody, err := io.ReadAll(sent.Body) require.NoError(t, err) require.Equal(t, expectedBody, sentBody) require.Equal(t, method, sent.Method) + if signingKey != "" { + require.NoError(t, actions.ValidatePayload(sentBody, sent.Header.Get(actions.SigningHeader), signingKey)) + } } -func testCall(ctx context.Context, timeout time.Duration, body []byte) func(string) ([]byte, error) { +func testCall(ctx context.Context, timeout time.Duration, body []byte, signingKey string) func(string) ([]byte, error) { return func(url string) ([]byte, error) { - return execution.Call(ctx, url, timeout, body) + return execution.Call(ctx, url, timeout, body, signingKey) } } diff --git a/internal/integration/config/zitadel.yaml b/internal/integration/config/zitadel.yaml index 378dc2f09b..e2642d9b8f 100644 --- a/internal/integration/config/zitadel.yaml +++ b/internal/integration/config/zitadel.yaml @@ -8,28 +8,30 @@ TLS: Caches: Connectors: + Memory: + Enabled: true Postgres: Enabled: true Redis: Enabled: true Instance: - Connector: "redis" - MaxAge: 1h - LastUsage: 10m + Connector: "memory" + MaxAge: 5m + LastUsage: 1m Log: Level: info - AddSource: true - Formatter: - Format: text Milestones: Connector: "postgres" - MaxAge: 1h - LastUsage: 10m + MaxAge: 5m + LastUsage: 1m + Log: + Level: info + Organization: + Connector: "redis" + MaxAge: 5m + LastUsage: 1m Log: Level: info - AddSource: true - Formatter: - Format: text Quotas: Access: diff --git a/internal/notification/channels/error.go b/internal/notification/channels/error.go new file mode 100644 index 0000000000..e3c3fa3c49 --- /dev/null +++ b/internal/notification/channels/error.go @@ -0,0 +1,25 @@ +package channels + +import "errors" + +type CancelError struct { + Err error +} + +func (e *CancelError) Error() string { + return e.Err.Error() +} + +func NewCancelError(err error) error { + return &CancelError{ + Err: err, + } +} + +func (e *CancelError) Is(target error) bool { + return errors.As(target, &e) +} + +func (e *CancelError) Unwrap() error { + return e.Err +} diff --git a/internal/notification/channels/twilio/channel.go b/internal/notification/channels/twilio/channel.go index e3e2767a0e..8b7f0e24f2 100644 --- a/internal/notification/channels/twilio/channel.go +++ b/internal/notification/channels/twilio/channel.go @@ -1,7 +1,10 @@ package twilio import ( - newTwilio "github.com/twilio/twilio-go" + "errors" + + "github.com/twilio/twilio-go" + twilioClient "github.com/twilio/twilio-go/client" openapi "github.com/twilio/twilio-go/rest/api/v2010" verify "github.com/twilio/twilio-go/rest/verify/v2" "github.com/zitadel/logging" @@ -12,7 +15,7 @@ import ( ) func InitChannel(config Config) channels.NotificationChannel { - client := newTwilio.NewRestClientWithParams(newTwilio.ClientParams{Username: config.SID, Password: config.Token}) + client := twilio.NewRestClientWithParams(twilio.ClientParams{Username: config.SID, Password: config.Token}) logging.Debug("successfully initialized twilio sms channel") return channels.HandleMessageFunc(func(message channels.Message) error { @@ -26,6 +29,17 @@ func InitChannel(config Config) channels.NotificationChannel { params.SetChannel("sms") resp, err := client.VerifyV2.CreateVerification(config.VerifyServiceSID, params) + + var twilioErr *twilioClient.TwilioRestError + if errors.As(err, &twilioErr) && twilioErr.Code == 60203 { + // If there were too many attempts to send a verification code (more than 5 times) + // without a verification check, even retries with backoff might not solve the problem. + // Instead, let the user initiate the verification again (e.g. using "resend code") + // https://www.twilio.com/docs/api/errors/60203 + logging.WithFields("error", twilioErr.Message, "code", twilioErr.Code).Warn("twilio create verification error") + return channels.NewCancelError(twilioErr) + } + if err != nil { return zerrors.ThrowInternal(err, "TWILI-0s9f2", "could not send verification") } diff --git a/internal/notification/handlers/commands.go b/internal/notification/handlers/commands.go index 07969a6bba..90b66bdf48 100644 --- a/internal/notification/handlers/commands.go +++ b/internal/notification/handlers/commands.go @@ -2,13 +2,19 @@ package handlers import ( "context" + "database/sql" + "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/notification/senders" "github.com/zitadel/zitadel/internal/repository/milestone" "github.com/zitadel/zitadel/internal/repository/quota" ) type Commands interface { + RequestNotification(ctx context.Context, instanceID string, request *command.NotificationRequest) error + NotificationCanceled(ctx context.Context, tx *sql.Tx, id, resourceOwner string, err error) error + NotificationRetryRequested(ctx context.Context, tx *sql.Tx, id, resourceOwner string, request *command.NotificationRetryRequest, err error) error + NotificationSent(ctx context.Context, tx *sql.Tx, id, instanceID string) error HumanInitCodeSent(ctx context.Context, orgID, userID string) error HumanEmailVerificationCodeSent(ctx context.Context, orgID, userID string) error PasswordCodeSent(ctx context.Context, orgID, userID string, generatorInfo *senders.CodeGeneratorInfo) error diff --git a/internal/notification/handlers/config_email.go b/internal/notification/handlers/config_email.go index b78540a423..3e6eaa27a1 100644 --- a/internal/notification/handlers/config_email.go +++ b/internal/notification/handlers/config_email.go @@ -23,6 +23,9 @@ func (n *NotificationQueries) GetActiveEmailConfig(ctx context.Context) (*email. Description: config.Description, } if config.SMTPConfig != nil { + if config.SMTPConfig.Password == nil { + return nil, zerrors.ThrowNotFound(err, "QUERY-Wrs3gw", "Errors.SMTPConfig.NotFound") + } password, err := crypto.DecryptString(config.SMTPConfig.Password, n.SMTPPasswordCrypto) if err != nil { return nil, err diff --git a/internal/notification/handlers/config_sms.go b/internal/notification/handlers/config_sms.go index 1962824c9a..fd733b3731 100644 --- a/internal/notification/handlers/config_sms.go +++ b/internal/notification/handlers/config_sms.go @@ -24,6 +24,9 @@ func (n *NotificationQueries) GetActiveSMSConfig(ctx context.Context) (*sms.Conf Description: config.Description, } if config.TwilioConfig != nil { + if config.TwilioConfig.Token == nil { + return nil, zerrors.ThrowNotFound(err, "QUERY-SFefsd", "Errors.SMS.Twilio.NotFound") + } token, err := crypto.DecryptString(config.TwilioConfig.Token, n.SMSTokenCrypto) if err != nil { return nil, err diff --git a/internal/notification/handlers/ctx.go b/internal/notification/handlers/ctx.go index b8fc45da68..8f499814aa 100644 --- a/internal/notification/handlers/ctx.go +++ b/internal/notification/handlers/ctx.go @@ -14,6 +14,10 @@ func HandlerContext(event *eventstore.Aggregate) context.Context { return authz.SetCtxData(ctx, authz.CtxData{UserID: NotifyUserID, OrgID: event.ResourceOwner}) } +func ContextWithNotifier(ctx context.Context, aggregate *eventstore.Aggregate) context.Context { + return authz.SetCtxData(ctx, authz.CtxData{UserID: NotifyUserID, OrgID: aggregate.ResourceOwner}) +} + func (n *NotificationQueries) HandlerContext(event *eventstore.Aggregate) (context.Context, error) { ctx := context.Background() instance, err := n.InstanceByID(ctx, event.InstanceID) diff --git a/internal/notification/handlers/integration_test/telemetry_pusher_test.go b/internal/notification/handlers/integration_test/telemetry_pusher_test.go index 6b4ac10258..0779df7b34 100644 --- a/internal/notification/handlers/integration_test/telemetry_pusher_test.go +++ b/internal/notification/handlers/integration_test/telemetry_pusher_test.go @@ -26,8 +26,6 @@ import ( ) func TestServer_TelemetryPushMilestones(t *testing.T) { - t.Parallel() - sub := sink.Subscribe(CTX, sink.ChannelMilestone) defer sub.Close() diff --git a/internal/notification/handlers/mock/commands.mock.go b/internal/notification/handlers/mock/commands.mock.go index ec327de8e8..ee6eb3c6b1 100644 --- a/internal/notification/handlers/mock/commands.mock.go +++ b/internal/notification/handlers/mock/commands.mock.go @@ -11,8 +11,10 @@ package mock import ( context "context" + sql "database/sql" reflect "reflect" + command "github.com/zitadel/zitadel/internal/command" senders "github.com/zitadel/zitadel/internal/notification/senders" milestone "github.com/zitadel/zitadel/internal/repository/milestone" quota "github.com/zitadel/zitadel/internal/repository/quota" @@ -155,6 +157,48 @@ func (mr *MockCommandsMockRecorder) MilestonePushed(ctx, instanceID, msType, end return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MilestonePushed", reflect.TypeOf((*MockCommands)(nil).MilestonePushed), ctx, instanceID, msType, endpoints) } +// NotificationCanceled mocks base method. +func (m *MockCommands) NotificationCanceled(ctx context.Context, tx *sql.Tx, id, resourceOwner string, err error) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NotificationCanceled", ctx, tx, id, resourceOwner, err) + ret0, _ := ret[0].(error) + return ret0 +} + +// NotificationCanceled indicates an expected call of NotificationCanceled. +func (mr *MockCommandsMockRecorder) NotificationCanceled(ctx, tx, id, resourceOwner, err any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NotificationCanceled", reflect.TypeOf((*MockCommands)(nil).NotificationCanceled), ctx, tx, id, resourceOwner, err) +} + +// NotificationRetryRequested mocks base method. +func (m *MockCommands) NotificationRetryRequested(ctx context.Context, tx *sql.Tx, id, resourceOwner string, request *command.NotificationRetryRequest, err error) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NotificationRetryRequested", ctx, tx, id, resourceOwner, request, err) + ret0, _ := ret[0].(error) + return ret0 +} + +// NotificationRetryRequested indicates an expected call of NotificationRetryRequested. +func (mr *MockCommandsMockRecorder) NotificationRetryRequested(ctx, tx, id, resourceOwner, request, err any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NotificationRetryRequested", reflect.TypeOf((*MockCommands)(nil).NotificationRetryRequested), ctx, tx, id, resourceOwner, request, err) +} + +// NotificationSent mocks base method. +func (m *MockCommands) NotificationSent(ctx context.Context, tx *sql.Tx, id, instanceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NotificationSent", ctx, tx, id, instanceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// NotificationSent indicates an expected call of NotificationSent. +func (mr *MockCommandsMockRecorder) NotificationSent(ctx, tx, id, instanceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NotificationSent", reflect.TypeOf((*MockCommands)(nil).NotificationSent), ctx, tx, id, instanceID) +} + // OTPEmailSent mocks base method. func (m *MockCommands) OTPEmailSent(ctx context.Context, sessionID, resourceOwner string) error { m.ctrl.T.Helper() @@ -211,6 +255,20 @@ func (mr *MockCommandsMockRecorder) PasswordCodeSent(ctx, orgID, userID, generat return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PasswordCodeSent", reflect.TypeOf((*MockCommands)(nil).PasswordCodeSent), ctx, orgID, userID, generatorInfo) } +// RequestNotification mocks base method. +func (m *MockCommands) RequestNotification(ctx context.Context, instanceID string, request *command.NotificationRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RequestNotification", ctx, instanceID, request) + ret0, _ := ret[0].(error) + return ret0 +} + +// RequestNotification indicates an expected call of RequestNotification. +func (mr *MockCommandsMockRecorder) RequestNotification(ctx, instanceID, request any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestNotification", reflect.TypeOf((*MockCommands)(nil).RequestNotification), ctx, instanceID, request) +} + // UsageNotificationSent mocks base method. func (m *MockCommands) UsageNotificationSent(ctx context.Context, dueEvent *quota.NotificationDueEvent) error { m.ctrl.T.Helper() diff --git a/internal/notification/handlers/mock/queries.mock.go b/internal/notification/handlers/mock/queries.mock.go index 5669444d4f..5ead216437 100644 --- a/internal/notification/handlers/mock/queries.mock.go +++ b/internal/notification/handlers/mock/queries.mock.go @@ -46,6 +46,20 @@ func (m *MockQueries) EXPECT() *MockQueriesMockRecorder { return m.recorder } +// ActiveInstances mocks base method. +func (m *MockQueries) ActiveInstances() []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ActiveInstances") + ret0, _ := ret[0].([]string) + return ret0 +} + +// ActiveInstances indicates an expected call of ActiveInstances. +func (mr *MockQueriesMockRecorder) ActiveInstances() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ActiveInstances", reflect.TypeOf((*MockQueries)(nil).ActiveInstances)) +} + // ActiveLabelPolicyByOrg mocks base method. func (m *MockQueries) ActiveLabelPolicyByOrg(ctx context.Context, orgID string, withOwnerRemoved bool) (*query.LabelPolicy, error) { m.ctrl.T.Helper() diff --git a/internal/notification/handlers/notification_worker.go b/internal/notification/handlers/notification_worker.go new file mode 100644 index 0000000000..dbf11a72fd --- /dev/null +++ b/internal/notification/handlers/notification_worker.go @@ -0,0 +1,498 @@ +package handlers + +import ( + "context" + "database/sql" + "errors" + "math/rand/v2" + "slices" + "strings" + "time" + + "github.com/zitadel/logging" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/api/call" + "github.com/zitadel/zitadel/internal/command" + "github.com/zitadel/zitadel/internal/crypto" + "github.com/zitadel/zitadel/internal/database" + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/notification/channels" + "github.com/zitadel/zitadel/internal/notification/senders" + "github.com/zitadel/zitadel/internal/notification/types" + "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/internal/repository/instance" + "github.com/zitadel/zitadel/internal/repository/notification" +) + +const ( + Code = "Code" + OTP = "OTP" +) + +type NotificationWorker struct { + commands Commands + queries *NotificationQueries + es *eventstore.Eventstore + client *database.DB + channels types.ChannelChains + config WorkerConfig + now nowFunc + backOff func(current time.Duration) time.Duration +} + +type WorkerConfig struct { + LegacyEnabled bool + Workers uint8 + BulkLimit uint16 + RequeueEvery time.Duration + RetryWorkers uint8 + RetryRequeueEvery time.Duration + TransactionDuration time.Duration + MaxAttempts uint8 + MaxTtl time.Duration + MinRetryDelay time.Duration + MaxRetryDelay time.Duration + RetryDelayFactor float32 +} + +// nowFunc makes [time.Now] mockable +type nowFunc func() time.Time + +type Sent func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error + +var sentHandlers map[eventstore.EventType]Sent + +func RegisterSentHandler(eventType eventstore.EventType, sent Sent) { + if sentHandlers == nil { + sentHandlers = make(map[eventstore.EventType]Sent) + } + sentHandlers[eventType] = sent +} + +func NewNotificationWorker( + config WorkerConfig, + commands Commands, + queries *NotificationQueries, + es *eventstore.Eventstore, + client *database.DB, + channels types.ChannelChains, +) *NotificationWorker { + // make sure the delay does not get less + if config.RetryDelayFactor < 1 { + config.RetryDelayFactor = 1 + } + w := &NotificationWorker{ + config: config, + commands: commands, + queries: queries, + es: es, + client: client, + channels: channels, + now: time.Now, + } + w.backOff = w.exponentialBackOff + return w +} + +func (w *NotificationWorker) Start(ctx context.Context) { + if w.config.LegacyEnabled { + return + } + for i := 0; i < int(w.config.Workers); i++ { + go w.schedule(ctx, i, false) + } + for i := 0; i < int(w.config.RetryWorkers); i++ { + go w.schedule(ctx, i, true) + } +} + +func (w *NotificationWorker) reduceNotificationRequested(ctx, txCtx context.Context, tx *sql.Tx, event *notification.RequestedEvent) (err error) { + ctx = ContextWithNotifier(ctx, event.Aggregate()) + + // if the notification is too old, we can directly cancel + if event.CreatedAt().Add(w.config.MaxTtl).Before(w.now()) { + return w.commands.NotificationCanceled(txCtx, tx, event.Aggregate().ID, event.Aggregate().ResourceOwner, nil) + } + + // Get the notify user first, so if anything fails afterward we have the current state of the user + // and can pass that to the retry request. + // We do not trigger the projection to reduce load on the database. By the time the notification is processed, + // the user should be projected anyway. If not, it will just wait for the next run. + notifyUser, err := w.queries.GetNotifyUserByID(ctx, false, event.UserID) + if err != nil { + return err + } + + // The domain claimed event requires the domain as argument, but lacks the user when creating the request event. + // Since we set it into the request arguments, it will be passed into a potential retry event. + if event.RequiresPreviousDomain && event.Request.Args != nil && event.Request.Args.Domain == "" { + index := strings.LastIndex(notifyUser.LastEmail, "@") + event.Request.Args.Domain = notifyUser.LastEmail[index+1:] + } + + err = w.sendNotification(ctx, txCtx, tx, event.Request, notifyUser, event) + if err == nil { + return nil + } + // if retries are disabled or if the error explicitly specifies, we cancel the notification + if w.config.MaxAttempts <= 1 || errors.Is(err, &channels.CancelError{}) { + return w.commands.NotificationCanceled(txCtx, tx, event.Aggregate().ID, event.Aggregate().ResourceOwner, err) + } + // otherwise we retry after a backoff delay + return w.commands.NotificationRetryRequested( + txCtx, + tx, + event.Aggregate().ID, + event.Aggregate().ResourceOwner, + notificationEventToRequest(event.Request, notifyUser, w.backOff(0)), + err, + ) +} + +func (w *NotificationWorker) reduceNotificationRetry(ctx, txCtx context.Context, tx *sql.Tx, event *notification.RetryRequestedEvent) (err error) { + ctx = ContextWithNotifier(ctx, event.Aggregate()) + + // if the notification is too old, we can directly cancel + if event.CreatedAt().Add(w.config.MaxTtl).Before(w.now()) { + return w.commands.NotificationCanceled(txCtx, tx, event.Aggregate().ID, event.Aggregate().ResourceOwner, err) + } + + if event.CreatedAt().Add(event.BackOff).After(w.now()) { + return nil + } + err = w.sendNotification(ctx, txCtx, tx, event.Request, event.NotifyUser, event) + if err == nil { + return nil + } + // if the max attempts are reached or if the error explicitly specifies, we cancel the notification + if event.Sequence() >= uint64(w.config.MaxAttempts) || errors.Is(err, &channels.CancelError{}) { + return w.commands.NotificationCanceled(txCtx, tx, event.Aggregate().ID, event.Aggregate().ResourceOwner, err) + } + // otherwise we retry after a backoff delay + return w.commands.NotificationRetryRequested(txCtx, tx, event.Aggregate().ID, event.Aggregate().ResourceOwner, notificationEventToRequest( + event.Request, + event.NotifyUser, + w.backOff(event.BackOff), + ), err) +} + +func (w *NotificationWorker) sendNotification(ctx, txCtx context.Context, tx *sql.Tx, request notification.Request, notifyUser *query.NotifyUser, e eventstore.Event) error { + ctx, err := enrichCtx(ctx, request.TriggeredAtOrigin) + if err != nil { + return channels.NewCancelError(err) + } + + // check early that a "sent" handler exists, otherwise we can cancel early + sentHandler, ok := sentHandlers[request.EventType] + if !ok { + logging.Errorf(`no "sent" handler registered for %s`, request.EventType) + return channels.NewCancelError(err) + } + + var code string + if request.Code != nil { + code, err = crypto.DecryptString(request.Code, w.queries.UserDataCrypto) + if err != nil { + return err + } + } + + colors, err := w.queries.ActiveLabelPolicyByOrg(ctx, request.UserResourceOwner, false) + if err != nil { + return err + } + + translator, err := w.queries.GetTranslatorWithOrgTexts(ctx, request.UserResourceOwner, request.MessageType) + if err != nil { + return err + } + + generatorInfo := new(senders.CodeGeneratorInfo) + var notify types.Notify + switch request.NotificationType { + case domain.NotificationTypeEmail: + template, err := w.queries.MailTemplateByOrg(ctx, notifyUser.ResourceOwner, false) + if err != nil { + return err + } + notify = types.SendEmail(ctx, w.channels, string(template.Template), translator, notifyUser, colors, e) + case domain.NotificationTypeSms: + notify = types.SendSMS(ctx, w.channels, translator, notifyUser, colors, e, generatorInfo) + } + + args := request.Args.ToMap() + args[Code] = code + // existing notifications use `OTP` as argument for the code + if request.IsOTP { + args[OTP] = code + } + + if err := notify(request.URLTemplate, args, request.MessageType, request.UnverifiedNotificationChannel); err != nil { + return err + } + err = w.commands.NotificationSent(txCtx, tx, e.Aggregate().ID, e.Aggregate().ResourceOwner) + if err != nil { + // In case the notification event cannot be pushed, we most likely cannot create a retry or cancel event. + // Therefore, we'll only log the error and also do not need to try to push to the user / session. + logging.WithFields("instanceID", authz.GetInstance(ctx).InstanceID(), "notification", e.Aggregate().ID). + OnError(err).Error("could not set sent notification event") + return nil + } + err = sentHandler(txCtx, w.commands, request.NotificationAggregateID(), request.NotificationAggregateResourceOwner(), generatorInfo, args) + logging.WithFields("instanceID", authz.GetInstance(ctx).InstanceID(), "notification", e.Aggregate().ID). + OnError(err).Error("could not set notification event on aggregate") + return nil +} + +func (w *NotificationWorker) exponentialBackOff(current time.Duration) time.Duration { + if current >= w.config.MaxRetryDelay { + return w.config.MaxRetryDelay + } + if current < w.config.MinRetryDelay { + current = w.config.MinRetryDelay + } + t := time.Duration(rand.Int64N(int64(w.config.RetryDelayFactor*float32(current.Nanoseconds()))-current.Nanoseconds()) + current.Nanoseconds()) + if t > w.config.MaxRetryDelay { + return w.config.MaxRetryDelay + } + return t +} + +func notificationEventToRequest(e notification.Request, notifyUser *query.NotifyUser, backoff time.Duration) *command.NotificationRetryRequest { + return &command.NotificationRetryRequest{ + NotificationRequest: command.NotificationRequest{ + UserID: e.UserID, + UserResourceOwner: e.UserResourceOwner, + TriggerOrigin: e.TriggeredAtOrigin, + URLTemplate: e.URLTemplate, + Code: e.Code, + CodeExpiry: e.CodeExpiry, + EventType: e.EventType, + NotificationType: e.NotificationType, + MessageType: e.MessageType, + UnverifiedNotificationChannel: e.UnverifiedNotificationChannel, + Args: e.Args, + AggregateID: e.AggregateID, + AggregateResourceOwner: e.AggregateResourceOwner, + IsOTP: e.IsOTP, + }, + BackOff: backoff, + NotifyUser: notifyUser, + } +} + +func (w *NotificationWorker) schedule(ctx context.Context, workerID int, retry bool) { + t := time.NewTimer(0) + + for { + select { + case <-ctx.Done(): + t.Stop() + w.log(workerID, retry).Info("scheduler stopped") + return + case <-t.C: + instances, err := w.queryInstances(ctx, retry) + w.log(workerID, retry).OnError(err).Error("unable to query instances") + + w.triggerInstances(call.WithTimestamp(ctx), instances, workerID, retry) + if retry { + t.Reset(w.config.RetryRequeueEvery) + continue + } + t.Reset(w.config.RequeueEvery) + } + } +} + +func (w *NotificationWorker) log(workerID int, retry bool) *logging.Entry { + return logging.WithFields("notification worker", workerID, "retries", retry) +} + +func (w *NotificationWorker) queryInstances(ctx context.Context, retry bool) ([]string, error) { + return w.queries.ActiveInstances(), nil +} + +func (w *NotificationWorker) triggerInstances(ctx context.Context, instances []string, workerID int, retry bool) { + for _, instance := range instances { + instanceCtx := authz.WithInstanceID(ctx, instance) + + err := w.trigger(instanceCtx, workerID, retry) + w.log(workerID, retry).WithField("instance", instance).OnError(err).Info("trigger failed") + } +} + +func (w *NotificationWorker) trigger(ctx context.Context, workerID int, retry bool) (err error) { + txCtx := ctx + if w.config.TransactionDuration > 0 { + var cancel, cancelTx func() + txCtx, cancelTx = context.WithCancel(ctx) + defer cancelTx() + ctx, cancel = context.WithTimeout(ctx, w.config.TransactionDuration) + defer cancel() + } + tx, err := w.client.BeginTx(txCtx, nil) + if err != nil { + return err + } + defer func() { + err = database.CloseTransaction(tx, err) + }() + + events, err := w.searchEvents(txCtx, tx, retry) + if err != nil { + return err + } + + // If there aren't any events or no unlocked event terminate early and start a new run. + if len(events) == 0 { + return nil + } + + w.log(workerID, retry). + WithField("instanceID", authz.GetInstance(ctx).InstanceID()). + WithField("events", len(events)). + Info("handling notification events") + + for _, event := range events { + var err error + switch e := event.(type) { + case *notification.RequestedEvent: + w.createSavepoint(txCtx, tx, event, workerID, retry) + err = w.reduceNotificationRequested(ctx, txCtx, tx, e) + case *notification.RetryRequestedEvent: + w.createSavepoint(txCtx, tx, event, workerID, retry) + err = w.reduceNotificationRetry(ctx, txCtx, tx, e) + } + if err != nil { + w.log(workerID, retry).OnError(err). + WithField("instanceID", authz.GetInstance(ctx).InstanceID()). + WithField("notificationID", event.Aggregate().ID). + WithField("sequence", event.Sequence()). + WithField("type", event.Type()). + Error("could not handle notification event") + // if we have an error, we rollback to the savepoint and continue with the next event + // we use the txCtx to make sure we can rollback the transaction in case the ctx is canceled + w.rollbackToSavepoint(txCtx, tx, event, workerID, retry) + } + // if the context is canceled, we stop the processing + if ctx.Err() != nil { + return nil + } + } + return nil +} + +func (w *NotificationWorker) latestRetries(events []eventstore.Event) []eventstore.Event { + for i := len(events) - 1; i > 0; i-- { + // since we delete during the iteration, we need to make sure we don't panic + if len(events) <= i { + continue + } + // delete all the previous retries of the same notification + events = slices.DeleteFunc(events, func(e eventstore.Event) bool { + return e.Aggregate().ID == events[i].Aggregate().ID && + e.Sequence() < events[i].Sequence() + }) + } + return events +} + +func (w *NotificationWorker) createSavepoint(ctx context.Context, tx *sql.Tx, event eventstore.Event, workerID int, retry bool) { + _, err := tx.ExecContext(ctx, "SAVEPOINT notification_send") + w.log(workerID, retry).OnError(err). + WithField("instanceID", authz.GetInstance(ctx).InstanceID()). + WithField("notificationID", event.Aggregate().ID). + WithField("sequence", event.Sequence()). + WithField("type", event.Type()). + Error("could not create savepoint for notification event") +} + +func (w *NotificationWorker) rollbackToSavepoint(ctx context.Context, tx *sql.Tx, event eventstore.Event, workerID int, retry bool) { + _, err := tx.ExecContext(ctx, "ROLLBACK TO SAVEPOINT notification_send") + w.log(workerID, retry).OnError(err). + WithField("instanceID", authz.GetInstance(ctx).InstanceID()). + WithField("notificationID", event.Aggregate().ID). + WithField("sequence", event.Sequence()). + WithField("type", event.Type()). + Error("could not rollback to savepoint for notification event") +} + +func (w *NotificationWorker) searchEvents(ctx context.Context, tx *sql.Tx, retry bool) ([]eventstore.Event, error) { + if retry { + return w.searchRetryEvents(ctx, tx) + } + // query events and lock them for update (with skip locked) + searchQuery := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + LockRowsDuringTx(tx, eventstore.LockOptionSkipLocked). + // Messages older than the MaxTTL, we can be ignored. + // The first attempt of a retry might still be older than the TTL and needs to be filtered out later on. + CreationDateAfter(w.now().Add(-1*w.config.MaxTtl)). + Limit(uint64(w.config.BulkLimit)). + AddQuery(). + AggregateTypes(notification.AggregateType). + EventTypes(notification.RequestedType). + Builder(). + ExcludeAggregateIDs(). + EventTypes(notification.RetryRequestedType, notification.CanceledType, notification.SentType). + Builder() + //nolint:staticcheck + return w.es.Filter(ctx, searchQuery) +} + +func (w *NotificationWorker) searchRetryEvents(ctx context.Context, tx *sql.Tx) ([]eventstore.Event, error) { + // query events and lock them for update (with skip locked) + searchQuery := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + LockRowsDuringTx(tx, eventstore.LockOptionSkipLocked). + // Messages older than the MaxTTL, we can be ignored. + // The first attempt of a retry might still be older than the TTL and needs to be filtered out later on. + CreationDateAfter(w.now().Add(-1*w.config.MaxTtl)). + AddQuery(). + AggregateTypes(notification.AggregateType). + EventTypes(notification.RetryRequestedType). + Builder(). + ExcludeAggregateIDs(). + EventTypes(notification.CanceledType, notification.SentType). + Builder() + //nolint:staticcheck + events, err := w.es.Filter(ctx, searchQuery) + if err != nil { + return nil, err + } + return w.latestRetries(events), nil +} + +type existingInstances []string + +// AppendEvents implements eventstore.QueryReducer. +func (ai *existingInstances) AppendEvents(events ...eventstore.Event) { + for _, event := range events { + switch event.Type() { + case instance.InstanceAddedEventType: + *ai = append(*ai, event.Aggregate().InstanceID) + case instance.InstanceRemovedEventType: + *ai = slices.DeleteFunc(*ai, func(s string) bool { + return s == event.Aggregate().InstanceID + }) + } + } +} + +// Query implements eventstore.QueryReducer. +func (*existingInstances) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + AddQuery(). + AggregateTypes(instance.AggregateType). + EventTypes( + instance.InstanceAddedEventType, + instance.InstanceRemovedEventType, + ). + Builder() +} + +// Reduce implements eventstore.QueryReducer. +// reduce is not used as events are reduced during AppendEvents +func (*existingInstances) Reduce() error { + return nil +} diff --git a/internal/notification/handlers/notification_worker_test.go b/internal/notification/handlers/notification_worker_test.go new file mode 100644 index 0000000000..40b3197d37 --- /dev/null +++ b/internal/notification/handlers/notification_worker_test.go @@ -0,0 +1,965 @@ +package handlers + +import ( + "context" + "database/sql" + "errors" + "fmt" + "testing" + "time" + + "github.com/muhlemmer/gu" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/api/ui/login" + "github.com/zitadel/zitadel/internal/command" + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/eventstore/repository" + es_repo_mock "github.com/zitadel/zitadel/internal/eventstore/repository/mock" + "github.com/zitadel/zitadel/internal/notification/channels/email" + channel_mock "github.com/zitadel/zitadel/internal/notification/channels/mock" + "github.com/zitadel/zitadel/internal/notification/channels/sms" + "github.com/zitadel/zitadel/internal/notification/channels/smtp" + "github.com/zitadel/zitadel/internal/notification/channels/twilio" + "github.com/zitadel/zitadel/internal/notification/handlers/mock" + "github.com/zitadel/zitadel/internal/notification/messages" + "github.com/zitadel/zitadel/internal/notification/senders" + "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/internal/repository/notification" + "github.com/zitadel/zitadel/internal/repository/session" + "github.com/zitadel/zitadel/internal/repository/user" +) + +const ( + notificationID = "notificationID" +) + +func Test_userNotifier_reduceNotificationRequested(t *testing.T) { + testNow := time.Now + testBackOff := func(current time.Duration) time.Duration { + return time.Second + } + sendError := errors.New("send error") + tests := []struct { + name string + test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fieldsWorker, argsWorker, wantWorker) + }{ + { + name: "too old", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fieldsWorker, a argsWorker, w wantWorker) { + codeAlg, code := cryptoValue(t, ctrl, "testcode") + commands.EXPECT().NotificationCanceled(gomock.Any(), gomock.Any(), notificationID, instanceID, nil).Return(nil) + return fieldsWorker{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).MockQuerier, + }), + userDataCrypto: codeAlg, + now: testNow, + }, + argsWorker{ + event: ¬ification.RequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: notificationID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now().Add(-1 * time.Hour), + Typ: notification.RequestedType, + }), + Request: notification.Request{ + UserID: userID, + UserResourceOwner: orgID, + AggregateID: "", + AggregateResourceOwner: "", + TriggeredAtOrigin: eventOrigin, + EventType: user.HumanInviteCodeAddedType, + MessageType: domain.InviteUserMessageType, + NotificationType: domain.NotificationTypeEmail, + URLTemplate: fmt.Sprintf("%s/ui/login/user/invite?userID=%s&loginname={{.LoginName}}&code={{.Code}}&orgID=%s&authRequestID=%s", eventOrigin, userID, orgID, authRequestID), + CodeExpiry: 1 * time.Hour, + Code: code, + UnverifiedNotificationChannel: true, + IsOTP: false, + RequiresPreviousDomain: false, + Args: &domain.NotificationArguments{ + ApplicationName: "APP", + }, + }, + }, + }, w + }, + }, + { + name: "send ok (email)", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fieldsWorker, a argsWorker, w wantWorker) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: "Invitation to APP", + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, "testcode") + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().NotificationSent(gomock.Any(), gomock.Any(), notificationID, instanceID).Return(nil) + commands.EXPECT().InviteCodeSent(gomock.Any(), orgID, userID).Return(nil) + return fieldsWorker{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).MockQuerier, + }), + userDataCrypto: codeAlg, + now: testNow, + }, + argsWorker{ + event: ¬ification.RequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: notificationID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now().UTC(), + Typ: notification.RequestedType, + }), + Request: notification.Request{ + UserID: userID, + UserResourceOwner: orgID, + AggregateID: "", + AggregateResourceOwner: "", + TriggeredAtOrigin: eventOrigin, + EventType: user.HumanInviteCodeAddedType, + MessageType: domain.InviteUserMessageType, + NotificationType: domain.NotificationTypeEmail, + URLTemplate: fmt.Sprintf("%s/ui/login/user/invite?userID=%s&loginname={{.LoginName}}&code={{.Code}}&orgID=%s&authRequestID=%s", eventOrigin, userID, orgID, authRequestID), + CodeExpiry: 1 * time.Hour, + Code: code, + UnverifiedNotificationChannel: true, + IsOTP: false, + RequiresPreviousDomain: false, + Args: &domain.NotificationArguments{ + ApplicationName: "APP", + }, + }, + }, + }, w + }, + }, + { + name: "send ok (sms with external provider)", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fieldsWorker, a argsWorker, w wantWorker) { + expiry := 0 * time.Hour + testCode := "" + expectContent := fmt.Sprintf(`%[1]s is your one-time password for %[2]s. Use it within the next %[3]s. +@%[2]s #%[1]s`, testCode, eventOriginDomain, expiry) + w.messageSMS = &messages.SMS{ + SenderPhoneNumber: "senderNumber", + RecipientPhoneNumber: verifiedPhone, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, testCode) + expectTemplateWithNotifyUserQueriesSMS(queries) + commands.EXPECT().NotificationSent(gomock.Any(), gomock.Any(), notificationID, instanceID).Return(nil) + commands.EXPECT().OTPSMSSent(gomock.Any(), sessionID, instanceID, &senders.CodeGeneratorInfo{ + ID: smsProviderID, + VerificationID: verificationID, + }).Return(nil) + return fieldsWorker{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).MockQuerier, + }), + userDataCrypto: codeAlg, + now: testNow, + }, + argsWorker{ + event: ¬ification.RequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: notificationID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now().UTC(), + Typ: notification.RequestedType, + }), + Request: notification.Request{ + UserID: userID, + UserResourceOwner: orgID, + AggregateID: sessionID, + AggregateResourceOwner: instanceID, + TriggeredAtOrigin: eventOrigin, + EventType: session.OTPSMSChallengedType, + MessageType: domain.VerifySMSOTPMessageType, + NotificationType: domain.NotificationTypeSms, + URLTemplate: "", + CodeExpiry: expiry, + Code: code, + UnverifiedNotificationChannel: false, + IsOTP: true, + RequiresPreviousDomain: false, + Args: &domain.NotificationArguments{ + Origin: eventOrigin, + Domain: eventOriginDomain, + Expiry: expiry, + }, + }, + }, + }, w + }, + }, + { + name: "previous domain", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fieldsWorker, a argsWorker, w wantWorker) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{verifiedEmail}, + Subject: "Domain has been claimed", + Content: expectContent, + } + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().NotificationSent(gomock.Any(), gomock.Any(), notificationID, instanceID).Return(nil) + commands.EXPECT().UserDomainClaimedSent(gomock.Any(), orgID, userID).Return(nil) + return fieldsWorker{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).MockQuerier, + }), + userDataCrypto: nil, + now: testNow, + }, + argsWorker{ + event: ¬ification.RequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: notificationID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now().UTC(), + Typ: notification.RequestedType, + }), + Request: notification.Request{ + UserID: userID, + UserResourceOwner: orgID, + AggregateID: "", + AggregateResourceOwner: "", + TriggeredAtOrigin: eventOrigin, + EventType: user.UserDomainClaimedType, + MessageType: domain.DomainClaimedMessageType, + NotificationType: domain.NotificationTypeEmail, + URLTemplate: login.LoginLink(eventOrigin, orgID), + CodeExpiry: 0, + Code: nil, + UnverifiedNotificationChannel: false, + IsOTP: false, + RequiresPreviousDomain: true, + Args: &domain.NotificationArguments{ + TempUsername: "tempUsername", + }, + }, + }, + }, w + }, + }, + { + name: "send failed, retry", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fieldsWorker, a argsWorker, w wantWorker) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: "Invitation to APP", + Content: expectContent, + } + w.sendError = sendError + codeAlg, code := cryptoValue(t, ctrl, "testcode") + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().NotificationRetryRequested(gomock.Any(), gomock.Any(), notificationID, instanceID, + &command.NotificationRetryRequest{ + NotificationRequest: command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + AggregateID: "", + AggregateResourceOwner: "", + TriggerOrigin: eventOrigin, + EventType: user.HumanInviteCodeAddedType, + MessageType: domain.InviteUserMessageType, + NotificationType: domain.NotificationTypeEmail, + URLTemplate: fmt.Sprintf("%s/ui/login/user/invite?userID=%s&loginname={{.LoginName}}&code={{.Code}}&orgID=%s&authRequestID=%s", eventOrigin, userID, orgID, authRequestID), + CodeExpiry: 1 * time.Hour, + Code: code, + UnverifiedNotificationChannel: true, + IsOTP: false, + RequiresPreviousDomain: false, + Args: &domain.NotificationArguments{ + ApplicationName: "APP", + }, + }, + BackOff: 1 * time.Second, + NotifyUser: &query.NotifyUser{ + ID: userID, + ResourceOwner: orgID, + LastEmail: lastEmail, + VerifiedEmail: verifiedEmail, + PreferredLoginName: preferredLoginName, + LastPhone: lastPhone, + VerifiedPhone: verifiedPhone, + }, + }, + sendError, + ).Return(nil) + return fieldsWorker{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).MockQuerier, + }), + userDataCrypto: codeAlg, + now: testNow, + backOff: testBackOff, + maxAttempts: 2, + }, + argsWorker{ + event: ¬ification.RequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: notificationID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now().UTC(), + Typ: notification.RequestedType, + }), + Request: notification.Request{ + UserID: userID, + UserResourceOwner: orgID, + AggregateID: "", + AggregateResourceOwner: "", + TriggeredAtOrigin: eventOrigin, + EventType: user.HumanInviteCodeAddedType, + MessageType: domain.InviteUserMessageType, + NotificationType: domain.NotificationTypeEmail, + URLTemplate: fmt.Sprintf("%s/ui/login/user/invite?userID=%s&loginname={{.LoginName}}&code={{.Code}}&orgID=%s&authRequestID=%s", eventOrigin, userID, orgID, authRequestID), + CodeExpiry: 1 * time.Hour, + Code: code, + UnverifiedNotificationChannel: true, + IsOTP: false, + RequiresPreviousDomain: false, + Args: &domain.NotificationArguments{ + ApplicationName: "APP", + }, + }, + }, + }, w + }, + }, + { + name: "send failed (max attempts), cancel", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fieldsWorker, a argsWorker, w wantWorker) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: "Invitation to APP", + Content: expectContent, + } + w.sendError = sendError + codeAlg, code := cryptoValue(t, ctrl, "testcode") + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().NotificationCanceled(gomock.Any(), gomock.Any(), notificationID, instanceID, sendError).Return(nil) + return fieldsWorker{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).MockQuerier, + }), + userDataCrypto: codeAlg, + now: testNow, + backOff: testBackOff, + maxAttempts: 1, + }, + argsWorker{ + event: ¬ification.RequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: notificationID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now().UTC(), + Seq: 1, + Typ: notification.RequestedType, + }), + Request: notification.Request{ + UserID: userID, + UserResourceOwner: orgID, + AggregateID: "", + AggregateResourceOwner: "", + TriggeredAtOrigin: eventOrigin, + EventType: user.HumanInviteCodeAddedType, + MessageType: domain.InviteUserMessageType, + NotificationType: domain.NotificationTypeEmail, + URLTemplate: fmt.Sprintf("%s/ui/login/user/invite?userID=%s&loginname={{.LoginName}}&code={{.Code}}&orgID=%s&authRequestID=%s", eventOrigin, userID, orgID, authRequestID), + CodeExpiry: 1 * time.Hour, + Code: code, + UnverifiedNotificationChannel: true, + IsOTP: false, + RequiresPreviousDomain: false, + Args: &domain.NotificationArguments{ + ApplicationName: "APP", + }, + }, + }, + }, w + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + queries := mock.NewMockQueries(ctrl) + commands := mock.NewMockCommands(ctrl) + f, a, w := tt.test(ctrl, queries, commands) + err := newNotificationWorker(t, ctrl, queries, f, a, w).reduceNotificationRequested( + authz.WithInstanceID(context.Background(), instanceID), + authz.WithInstanceID(context.Background(), instanceID), + &sql.Tx{}, + a.event.(*notification.RequestedEvent)) + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func Test_userNotifier_reduceNotificationRetry(t *testing.T) { + testNow := time.Now + testBackOff := func(current time.Duration) time.Duration { + return time.Second + } + sendError := errors.New("send error") + tests := []struct { + name string + test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fieldsWorker, argsWorker, wantWorker) + }{ + { + name: "too old", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fieldsWorker, a argsWorker, w wantWorker) { + codeAlg, code := cryptoValue(t, ctrl, "testcode") + commands.EXPECT().NotificationCanceled(gomock.Any(), gomock.Any(), notificationID, instanceID, nil).Return(nil) + return fieldsWorker{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).MockQuerier, + }), + userDataCrypto: codeAlg, + now: testNow, + }, + argsWorker{ + event: ¬ification.RetryRequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: notificationID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now().Add(-1 * time.Hour), + Typ: notification.RequestedType, + }), + Request: notification.Request{ + UserID: userID, + UserResourceOwner: orgID, + AggregateID: "", + AggregateResourceOwner: "", + TriggeredAtOrigin: eventOrigin, + EventType: user.HumanInviteCodeAddedType, + MessageType: domain.InviteUserMessageType, + NotificationType: domain.NotificationTypeEmail, + URLTemplate: fmt.Sprintf("%s/ui/login/user/invite?userID=%s&loginname={{.LoginName}}&code={{.Code}}&orgID=%s&authRequestID=%s", eventOrigin, userID, orgID, authRequestID), + CodeExpiry: 1 * time.Hour, + Code: code, + UnverifiedNotificationChannel: true, + IsOTP: false, + RequiresPreviousDomain: false, + Args: &domain.NotificationArguments{ + ApplicationName: "APP", + }, + }, + BackOff: 1 * time.Second, + NotifyUser: &query.NotifyUser{ + ID: userID, + ResourceOwner: orgID, + LastEmail: lastEmail, + VerifiedEmail: verifiedEmail, + PreferredLoginName: preferredLoginName, + LastPhone: lastPhone, + VerifiedPhone: verifiedPhone, + }, + }, + }, w + }, + }, + { + name: "backoff not done", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fieldsWorker, a argsWorker, w wantWorker) { + codeAlg, code := cryptoValue(t, ctrl, "testcode") + return fieldsWorker{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).MockQuerier, + }), + userDataCrypto: codeAlg, + now: testNow, + }, + argsWorker{ + event: ¬ification.RetryRequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: notificationID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now(), + Typ: notification.RequestedType, + Seq: 2, + }), + Request: notification.Request{ + UserID: userID, + UserResourceOwner: orgID, + AggregateID: "", + AggregateResourceOwner: "", + TriggeredAtOrigin: eventOrigin, + EventType: user.HumanInviteCodeAddedType, + MessageType: domain.InviteUserMessageType, + NotificationType: domain.NotificationTypeEmail, + URLTemplate: fmt.Sprintf("%s/ui/login/user/invite?userID=%s&loginname={{.LoginName}}&code={{.Code}}&orgID=%s&authRequestID=%s", eventOrigin, userID, orgID, authRequestID), + CodeExpiry: 1 * time.Hour, + Code: code, + UnverifiedNotificationChannel: true, + IsOTP: false, + RequiresPreviousDomain: false, + Args: &domain.NotificationArguments{ + ApplicationName: "APP", + }, + }, + BackOff: 10 * time.Second, + NotifyUser: &query.NotifyUser{ + ID: userID, + ResourceOwner: orgID, + LastEmail: lastEmail, + VerifiedEmail: verifiedEmail, + PreferredLoginName: preferredLoginName, + LastPhone: lastPhone, + VerifiedPhone: verifiedPhone, + }, + }, + }, w + }, + }, + { + name: "send ok", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fieldsWorker, a argsWorker, w wantWorker) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: "Invitation to APP", + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, "testcode") + expectTemplateQueries(queries, givenTemplate) + commands.EXPECT().NotificationSent(gomock.Any(), gomock.Any(), notificationID, instanceID).Return(nil) + commands.EXPECT().InviteCodeSent(gomock.Any(), orgID, userID).Return(nil) + return fieldsWorker{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).MockQuerier, + }), + userDataCrypto: codeAlg, + now: testNow, + maxAttempts: 3, + }, + argsWorker{ + event: ¬ification.RetryRequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: notificationID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now().Add(-2 * time.Second), + Typ: notification.RequestedType, + Seq: 2, + }), + Request: notification.Request{ + UserID: userID, + UserResourceOwner: orgID, + AggregateID: "", + AggregateResourceOwner: "", + TriggeredAtOrigin: eventOrigin, + EventType: user.HumanInviteCodeAddedType, + MessageType: domain.InviteUserMessageType, + NotificationType: domain.NotificationTypeEmail, + URLTemplate: fmt.Sprintf("%s/ui/login/user/invite?userID=%s&loginname={{.LoginName}}&code={{.Code}}&orgID=%s&authRequestID=%s", eventOrigin, userID, orgID, authRequestID), + CodeExpiry: 1 * time.Hour, + Code: code, + UnverifiedNotificationChannel: true, + IsOTP: false, + RequiresPreviousDomain: false, + Args: &domain.NotificationArguments{ + ApplicationName: "APP", + }, + }, + BackOff: 1 * time.Second, + NotifyUser: &query.NotifyUser{ + ID: userID, + ResourceOwner: orgID, + LastEmail: lastEmail, + VerifiedEmail: verifiedEmail, + PreferredLoginName: preferredLoginName, + LastPhone: lastPhone, + VerifiedPhone: verifiedPhone, + }, + }, + }, w + }, + }, + { + name: "send failed, retry", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fieldsWorker, a argsWorker, w wantWorker) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: "Invitation to APP", + Content: expectContent, + } + w.sendError = sendError + codeAlg, code := cryptoValue(t, ctrl, "testcode") + expectTemplateQueries(queries, givenTemplate) + commands.EXPECT().NotificationRetryRequested(gomock.Any(), gomock.Any(), notificationID, instanceID, + &command.NotificationRetryRequest{ + NotificationRequest: command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + AggregateID: "", + AggregateResourceOwner: "", + TriggerOrigin: eventOrigin, + EventType: user.HumanInviteCodeAddedType, + MessageType: domain.InviteUserMessageType, + NotificationType: domain.NotificationTypeEmail, + URLTemplate: fmt.Sprintf("%s/ui/login/user/invite?userID=%s&loginname={{.LoginName}}&code={{.Code}}&orgID=%s&authRequestID=%s", eventOrigin, userID, orgID, authRequestID), + CodeExpiry: 1 * time.Hour, + Code: code, + UnverifiedNotificationChannel: true, + IsOTP: false, + RequiresPreviousDomain: false, + Args: &domain.NotificationArguments{ + ApplicationName: "APP", + }, + }, + BackOff: 1 * time.Second, + NotifyUser: &query.NotifyUser{ + ID: userID, + ResourceOwner: orgID, + LastEmail: lastEmail, + VerifiedEmail: verifiedEmail, + PreferredLoginName: preferredLoginName, + LastPhone: lastPhone, + VerifiedPhone: verifiedPhone, + }, + }, + sendError, + ).Return(nil) + return fieldsWorker{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).MockQuerier, + }), + userDataCrypto: codeAlg, + now: testNow, + backOff: testBackOff, + maxAttempts: 3, + }, + argsWorker{ + event: ¬ification.RetryRequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: notificationID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now().Add(-2 * time.Second), + Typ: notification.RequestedType, + Seq: 2, + }), + Request: notification.Request{ + UserID: userID, + UserResourceOwner: orgID, + AggregateID: "", + AggregateResourceOwner: "", + TriggeredAtOrigin: eventOrigin, + EventType: user.HumanInviteCodeAddedType, + MessageType: domain.InviteUserMessageType, + NotificationType: domain.NotificationTypeEmail, + URLTemplate: fmt.Sprintf("%s/ui/login/user/invite?userID=%s&loginname={{.LoginName}}&code={{.Code}}&orgID=%s&authRequestID=%s", eventOrigin, userID, orgID, authRequestID), + CodeExpiry: 1 * time.Hour, + Code: code, + UnverifiedNotificationChannel: true, + IsOTP: false, + RequiresPreviousDomain: false, + Args: &domain.NotificationArguments{ + ApplicationName: "APP", + }, + }, + BackOff: 1 * time.Second, + NotifyUser: &query.NotifyUser{ + ID: userID, + ResourceOwner: orgID, + LastEmail: lastEmail, + VerifiedEmail: verifiedEmail, + PreferredLoginName: preferredLoginName, + LastPhone: lastPhone, + VerifiedPhone: verifiedPhone, + }, + }, + }, w + }, + }, + { + name: "send failed (max attempts), cancel", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fieldsWorker, a argsWorker, w wantWorker) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: "Invitation to APP", + Content: expectContent, + } + w.sendError = sendError + codeAlg, code := cryptoValue(t, ctrl, "testcode") + expectTemplateQueries(queries, givenTemplate) + commands.EXPECT().NotificationCanceled(gomock.Any(), gomock.Any(), notificationID, instanceID, sendError).Return(nil) + return fieldsWorker{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).MockQuerier, + }), + userDataCrypto: codeAlg, + now: testNow, + backOff: testBackOff, + maxAttempts: 2, + }, + argsWorker{ + event: ¬ification.RetryRequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: notificationID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now().Add(-2 * time.Second), + Seq: 2, + Typ: notification.RequestedType, + }), + Request: notification.Request{ + UserID: userID, + UserResourceOwner: orgID, + AggregateID: "", + AggregateResourceOwner: "", + TriggeredAtOrigin: eventOrigin, + EventType: user.HumanInviteCodeAddedType, + MessageType: domain.InviteUserMessageType, + NotificationType: domain.NotificationTypeEmail, + URLTemplate: fmt.Sprintf("%s/ui/login/user/invite?userID=%s&loginname={{.LoginName}}&code={{.Code}}&orgID=%s&authRequestID=%s", eventOrigin, userID, orgID, authRequestID), + CodeExpiry: 1 * time.Hour, + Code: code, + UnverifiedNotificationChannel: true, + IsOTP: false, + RequiresPreviousDomain: false, + Args: &domain.NotificationArguments{ + ApplicationName: "APP", + }, + }, + BackOff: 1 * time.Second, + NotifyUser: &query.NotifyUser{ + ID: userID, + ResourceOwner: orgID, + LastEmail: lastEmail, + VerifiedEmail: verifiedEmail, + PreferredLoginName: preferredLoginName, + LastPhone: lastPhone, + VerifiedPhone: verifiedPhone, + }, + }, + }, w + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + queries := mock.NewMockQueries(ctrl) + commands := mock.NewMockCommands(ctrl) + f, a, w := tt.test(ctrl, queries, commands) + err := newNotificationWorker(t, ctrl, queries, f, a, w).reduceNotificationRetry( + authz.WithInstanceID(context.Background(), instanceID), + authz.WithInstanceID(context.Background(), instanceID), + &sql.Tx{}, + a.event.(*notification.RetryRequestedEvent), + ) + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func newNotificationWorker(t *testing.T, ctrl *gomock.Controller, queries *mock.MockQueries, f fieldsWorker, a argsWorker, w wantWorker) *NotificationWorker { + queries.EXPECT().NotificationProviderByIDAndType(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&query.DebugNotificationProvider{}, nil) + smtpAlg, _ := cryptoValue(t, ctrl, "smtppw") + channel := channel_mock.NewMockNotificationChannel(ctrl) + if w.err == nil { + if w.message != nil { + w.message.TriggeringEvent = a.event + channel.EXPECT().HandleMessage(w.message).Return(w.sendError) + } + if w.messageSMS != nil { + w.messageSMS.TriggeringEvent = a.event + channel.EXPECT().HandleMessage(w.messageSMS).DoAndReturn(func(message *messages.SMS) error { + message.VerificationID = gu.Ptr(verificationID) + return w.sendError + }) + } + } + return &NotificationWorker{ + commands: f.commands, + queries: NewNotificationQueries( + f.queries, + f.es, + externalDomain, + externalPort, + externalSecure, + "", + f.userDataCrypto, + smtpAlg, + f.SMSTokenCrypto, + ), + channels: ¬ificationChannels{ + Chain: *senders.ChainChannels(channel), + EmailConfig: &email.Config{ + ProviderConfig: &email.Provider{ + ID: "emailProviderID", + Description: "description", + }, + SMTPConfig: &smtp.Config{ + SMTP: smtp.SMTP{ + Host: "host", + User: "user", + Password: "password", + }, + Tls: true, + From: "from", + FromName: "fromName", + ReplyToAddress: "replyToAddress", + }, + WebhookConfig: nil, + }, + SMSConfig: &sms.Config{ + ProviderConfig: &sms.Provider{ + ID: "smsProviderID", + Description: "description", + }, + TwilioConfig: &twilio.Config{ + SID: "sid", + Token: "token", + SenderNumber: "senderNumber", + VerifyServiceSID: "verifyServiceSID", + }, + }, + }, + config: WorkerConfig{ + Workers: 1, + BulkLimit: 10, + RequeueEvery: 2 * time.Second, + TransactionDuration: 5 * time.Second, + MaxAttempts: f.maxAttempts, + MaxTtl: 5 * time.Minute, + MinRetryDelay: 1 * time.Second, + MaxRetryDelay: 10 * time.Second, + RetryDelayFactor: 2, + }, + now: f.now, + backOff: f.backOff, + } +} + +func TestNotificationWorker_exponentialBackOff(t *testing.T) { + type fields struct { + config WorkerConfig + } + type args struct { + current time.Duration + } + tests := []struct { + name string + fields fields + args args + wantMin time.Duration + wantMax time.Duration + }{ + { + name: "less than min, min - 1.5*min", + fields: fields{ + config: WorkerConfig{ + MinRetryDelay: 1 * time.Second, + MaxRetryDelay: 5 * time.Second, + RetryDelayFactor: 1.5, + }, + }, + args: args{ + current: 0, + }, + wantMin: 1000 * time.Millisecond, + wantMax: 1500 * time.Millisecond, + }, + { + name: "current, 1.5*current - max", + fields: fields{ + config: WorkerConfig{ + MinRetryDelay: 1 * time.Second, + MaxRetryDelay: 5 * time.Second, + RetryDelayFactor: 1.5, + }, + }, + args: args{ + current: 4 * time.Second, + }, + wantMin: 4000 * time.Millisecond, + wantMax: 5000 * time.Millisecond, + }, + { + name: "max, max", + fields: fields{ + config: WorkerConfig{ + MinRetryDelay: 1 * time.Second, + MaxRetryDelay: 5 * time.Second, + RetryDelayFactor: 1.5, + }, + }, + args: args{ + current: 5 * time.Second, + }, + wantMin: 5000 * time.Millisecond, + wantMax: 5000 * time.Millisecond, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := &NotificationWorker{ + config: tt.fields.config, + } + b := w.exponentialBackOff(tt.args.current) + assert.GreaterOrEqual(t, b, tt.wantMin) + assert.LessOrEqual(t, b, tt.wantMax) + }) + } +} diff --git a/internal/notification/handlers/queries.go b/internal/notification/handlers/queries.go index 1c00460531..1c8d37598e 100644 --- a/internal/notification/handlers/queries.go +++ b/internal/notification/handlers/queries.go @@ -31,6 +31,8 @@ type Queries interface { InstanceByID(ctx context.Context, id string) (instance authz.Instance, err error) GetActiveSigningWebKey(ctx context.Context) (*jose.JSONWebKey, error) ActivePrivateSigningKey(ctx context.Context, t time.Time) (keys *query.PrivateKeys, err error) + + ActiveInstances() []string } type NotificationQueries struct { diff --git a/internal/notification/handlers/user_notifier.go b/internal/notification/handlers/user_notifier.go index 41d2f4dc8f..ec30ab476f 100644 --- a/internal/notification/handlers/user_notifier.go +++ b/internal/notification/handlers/user_notifier.go @@ -2,23 +2,85 @@ package handlers import ( "context" - "strings" "time" http_util "github.com/zitadel/zitadel/internal/api/http" + "github.com/zitadel/zitadel/internal/api/ui/console" "github.com/zitadel/zitadel/internal/api/ui/login" - "github.com/zitadel/zitadel/internal/crypto" + "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" "github.com/zitadel/zitadel/internal/notification/senders" "github.com/zitadel/zitadel/internal/notification/types" - "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/repository/session" "github.com/zitadel/zitadel/internal/repository/user" "github.com/zitadel/zitadel/internal/zerrors" ) +func init() { + RegisterSentHandler(user.HumanInitialCodeAddedType, + func(ctx context.Context, commands Commands, id, orgID string, _ *senders.CodeGeneratorInfo, args map[string]any) error { + return commands.HumanInitCodeSent(ctx, orgID, id) + }, + ) + RegisterSentHandler(user.HumanEmailCodeAddedType, + func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error { + return commands.HumanEmailVerificationCodeSent(ctx, orgID, id) + }, + ) + RegisterSentHandler(user.HumanPasswordCodeAddedType, + func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error { + return commands.PasswordCodeSent(ctx, orgID, id, generatorInfo) + }, + ) + RegisterSentHandler(user.HumanOTPSMSCodeAddedType, + func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error { + return commands.HumanOTPSMSCodeSent(ctx, id, orgID, generatorInfo) + }, + ) + RegisterSentHandler(session.OTPSMSChallengedType, + func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error { + return commands.OTPSMSSent(ctx, id, orgID, generatorInfo) + }, + ) + RegisterSentHandler(user.HumanOTPEmailCodeAddedType, + func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error { + return commands.HumanOTPEmailCodeSent(ctx, id, orgID) + }, + ) + RegisterSentHandler(session.OTPEmailChallengedType, + func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error { + return commands.OTPEmailSent(ctx, id, orgID) + }, + ) + RegisterSentHandler(user.UserDomainClaimedType, + func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error { + return commands.UserDomainClaimedSent(ctx, orgID, id) + }, + ) + RegisterSentHandler(user.HumanPasswordlessInitCodeRequestedType, + func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error { + return commands.HumanPasswordlessInitCodeSent(ctx, id, orgID, args["CodeID"].(string)) + }, + ) + RegisterSentHandler(user.HumanPasswordChangedType, + func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error { + return commands.PasswordChangeSent(ctx, orgID, id) + }, + ) + RegisterSentHandler(user.HumanPhoneCodeAddedType, + func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error { + return commands.HumanPhoneVerificationCodeSent(ctx, orgID, id, generatorInfo) + }, + ) + RegisterSentHandler(user.HumanInviteCodeAddedType, + func(ctx context.Context, commands Commands, id, orgID string, _ *senders.CodeGeneratorInfo, args map[string]any) error { + return commands.InviteCodeSent(ctx, orgID, id) + }, + ) +} + const ( UserNotificationsProjectionTable = "projections.notifications" ) @@ -26,7 +88,6 @@ const ( type userNotifier struct { commands Commands queries *NotificationQueries - channels types.ChannelChains otpEmailTmpl string } @@ -37,12 +98,15 @@ func NewUserNotifier( queries *NotificationQueries, channels types.ChannelChains, otpEmailTmpl string, + legacyMode bool, ) *handler.Handler { + if legacyMode { + return NewUserNotifierLegacy(ctx, config, commands, queries, channels, otpEmailTmpl) + } return handler.NewHandler(ctx, &config, &userNotifier{ commands: commands, queries: queries, otpEmailTmpl: otpEmailTmpl, - channels: channels, }) } @@ -146,39 +210,29 @@ func (u *userNotifier) reduceInitCodeAdded(event eventstore.Event) (*handler.Sta if alreadyHandled { return nil } - code, err := crypto.DecryptString(e.Code, u.queries.UserDataCrypto) - if err != nil { - return err - } - colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false) - if err != nil { - return err - } - - template, err := u.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner, false) - if err != nil { - return err - } - - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) - if err != nil { - return err - } - translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.InitCodeMessageType) - if err != nil { - return err - } - ctx, err = u.queries.Origin(ctx, e) if err != nil { return err } - err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e). - SendUserInitCode(ctx, notifyUser, code, e.AuthRequestID) - if err != nil { - return err - } - return u.commands.HumanInitCodeSent(ctx, e.Aggregate().ResourceOwner, e.Aggregate().ID) + origin := http_util.DomainContext(ctx).Origin() + return u.commands.RequestNotification( + ctx, + e.Aggregate().ResourceOwner, + command.NewNotificationRequest( + e.Aggregate().ID, + e.Aggregate().ResourceOwner, + origin, + e.EventType, + domain.NotificationTypeEmail, + domain.InitCodeMessageType, + ). + WithURLTemplate(login.InitUserLinkTemplate(origin, e.Aggregate().ID, e.Aggregate().ResourceOwner, e.AuthRequestID)). + WithCode(e.Code, e.Expiry). + WithArgs(&domain.NotificationArguments{ + AuthRequestID: e.AuthRequestID, + }). + WithUnverifiedChannel(), + ) }), nil } @@ -203,42 +257,39 @@ func (u *userNotifier) reduceEmailCodeAdded(event eventstore.Event) (*handler.St if alreadyHandled { return nil } - code, err := crypto.DecryptString(e.Code, u.queries.UserDataCrypto) - if err != nil { - return err - } - colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false) - if err != nil { - return err - } - - template, err := u.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner, false) - if err != nil { - return err - } - - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) - if err != nil { - return err - } - translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.VerifyEmailMessageType) - if err != nil { - return err - } - ctx, err = u.queries.Origin(ctx, e) if err != nil { return err } - err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e). - SendEmailVerificationCode(ctx, notifyUser, code, e.URLTemplate, e.AuthRequestID) - if err != nil { - return err - } - return u.commands.HumanEmailVerificationCodeSent(ctx, e.Aggregate().ResourceOwner, e.Aggregate().ID) + origin := http_util.DomainContext(ctx).Origin() + + return u.commands.RequestNotification(ctx, + e.Aggregate().ResourceOwner, + command.NewNotificationRequest( + e.Aggregate().ID, + e.Aggregate().ResourceOwner, + origin, + e.EventType, + domain.NotificationTypeEmail, + domain.VerifyEmailMessageType, + ). + WithURLTemplate(u.emailCodeTemplate(origin, e)). + WithCode(e.Code, e.Expiry). + WithArgs(&domain.NotificationArguments{ + AuthRequestID: e.AuthRequestID, + }). + WithUnverifiedChannel(), + ) }), nil } +func (u *userNotifier) emailCodeTemplate(origin string, e *user.HumanEmailCodeAddedEvent) string { + if e.URLTemplate != "" { + return e.URLTemplate + } + return login.MailVerificationLinkTemplate(origin, e.Aggregate().ID, e.Aggregate().ResourceOwner, e.AuthRequestID) +} + func (u *userNotifier) reducePasswordCodeAdded(event eventstore.Event) (*handler.Statement, error) { e, ok := event.(*user.HumanPasswordCodeAddedEvent) if !ok { @@ -259,64 +310,74 @@ func (u *userNotifier) reducePasswordCodeAdded(event eventstore.Event) (*handler if alreadyHandled { return nil } - var code string - if e.Code != nil { - code, err = crypto.DecryptString(e.Code, u.queries.UserDataCrypto) - if err != nil { - return err - } - } - colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false) - if err != nil { - return err - } - - template, err := u.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner, false) - if err != nil { - return err - } - - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) - if err != nil { - return err - } - translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.PasswordResetMessageType) - if err != nil { - return err - } - ctx, err = u.queries.Origin(ctx, e) if err != nil { return err } - generatorInfo := new(senders.CodeGeneratorInfo) - notify := types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e) - if e.NotificationType == domain.NotificationTypeSms { - notify = types.SendSMS(ctx, u.channels, translator, notifyUser, colors, e, generatorInfo) - } - err = notify.SendPasswordCode(ctx, notifyUser, code, e.URLTemplate, e.AuthRequestID) - if err != nil { - return err - } - return u.commands.PasswordCodeSent(ctx, e.Aggregate().ResourceOwner, e.Aggregate().ID, generatorInfo) + origin := http_util.DomainContext(ctx).Origin() + return u.commands.RequestNotification(ctx, + e.Aggregate().ResourceOwner, + command.NewNotificationRequest( + e.Aggregate().ID, + e.Aggregate().ResourceOwner, + origin, + e.EventType, + e.NotificationType, + domain.PasswordResetMessageType, + ). + WithURLTemplate(u.passwordCodeTemplate(origin, e)). + WithCode(e.Code, e.Expiry). + WithArgs(&domain.NotificationArguments{ + AuthRequestID: e.AuthRequestID, + }). + WithUnverifiedChannel(), + ) }), nil } +func (u *userNotifier) passwordCodeTemplate(origin string, e *user.HumanPasswordCodeAddedEvent) string { + if e.URLTemplate != "" { + return e.URLTemplate + } + return login.InitPasswordLinkTemplate(origin, e.Aggregate().ID, e.Aggregate().ResourceOwner, e.AuthRequestID) +} + func (u *userNotifier) reduceOTPSMSCodeAdded(event eventstore.Event) (*handler.Statement, error) { e, ok := event.(*user.HumanOTPSMSCodeAddedEvent) if !ok { return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-ASF3g", "reduce.wrong.event.type %s", user.HumanOTPSMSCodeAddedType) } - return u.reduceOTPSMS( - e, - e.Code, - e.Expiry, - e.Aggregate().ID, - e.Aggregate().ResourceOwner, - u.commands.HumanOTPSMSCodeSent, - user.HumanOTPSMSCodeAddedType, - user.HumanOTPSMSCodeSentType, - ) + + return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { + ctx := HandlerContext(event.Aggregate()) + alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, + user.HumanOTPSMSCodeAddedType, + user.HumanOTPSMSCodeSentType) + if err != nil { + return err + } + if alreadyHandled { + return nil + } + ctx, err = u.queries.Origin(ctx, e) + if err != nil { + return err + } + return u.commands.RequestNotification(ctx, + e.Aggregate().ResourceOwner, + command.NewNotificationRequest( + e.Aggregate().ID, + e.Aggregate().ResourceOwner, + http_util.DomainContext(ctx).Origin(), + e.EventType, + domain.NotificationTypeSms, + domain.VerifySMSOTPMessageType, + ). + WithCode(e.Code, e.Expiry). + WithArgs(otpArgs(ctx, e.Expiry)). + WithOTP(), + ) + }), nil } func (u *userNotifier) reduceSessionOTPSMSChallenged(event eventstore.Event) (*handler.Statement, error) { @@ -327,75 +388,46 @@ func (u *userNotifier) reduceSessionOTPSMSChallenged(event eventstore.Event) (*h if e.CodeReturned { return handler.NewNoOpStatement(e), nil } - ctx := HandlerContext(event.Aggregate()) - s, err := u.queries.SessionByID(ctx, true, e.Aggregate().ID, "") - if err != nil { - return nil, err - } - return u.reduceOTPSMS( - e, - e.Code, - e.Expiry, - s.UserFactor.UserID, - s.UserFactor.ResourceOwner, - u.commands.OTPSMSSent, - session.OTPSMSChallengedType, - session.OTPSMSSentType, - ) -} -func (u *userNotifier) reduceOTPSMS( - event eventstore.Event, - code *crypto.CryptoValue, - expiry time.Duration, - userID, - resourceOwner string, - sentCommand func(ctx context.Context, userID, resourceOwner string, generatorInfo *senders.CodeGeneratorInfo) (err error), - eventTypes ...eventstore.EventType, -) (*handler.Statement, error) { - ctx := HandlerContext(event.Aggregate()) - alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, expiry, nil, eventTypes...) - if err != nil { - return nil, err - } - if alreadyHandled { - return handler.NewNoOpStatement(event), nil - } - var plainCode string - if code != nil { - plainCode, err = crypto.DecryptString(code, u.queries.UserDataCrypto) + return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { + ctx := HandlerContext(event.Aggregate()) + alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, + session.OTPSMSChallengedType, + session.OTPSMSSentType) if err != nil { - return nil, err + return err + } + if alreadyHandled { + return nil + } + s, err := u.queries.SessionByID(ctx, true, e.Aggregate().ID, "") + if err != nil { + return err } - } - colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, resourceOwner, false) - if err != nil { - return nil, err - } - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, userID) - if err != nil { - return nil, err - } - translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.VerifySMSOTPMessageType) - if err != nil { - return nil, err - } - ctx, err = u.queries.Origin(ctx, event) - if err != nil { - return nil, err - } - generatorInfo := new(senders.CodeGeneratorInfo) - notify := types.SendSMS(ctx, u.channels, translator, notifyUser, colors, event, generatorInfo) - err = notify.SendOTPSMSCode(ctx, plainCode, expiry) - if err != nil { - return nil, err - } - err = sentCommand(ctx, event.Aggregate().ID, event.Aggregate().ResourceOwner, generatorInfo) - if err != nil { - return nil, err - } - return handler.NewNoOpStatement(event), nil + ctx, err = u.queries.Origin(ctx, e) + if err != nil { + return err + } + + args := otpArgs(ctx, e.Expiry) + args.SessionID = e.Aggregate().ID + return u.commands.RequestNotification(ctx, + s.UserFactor.ResourceOwner, + command.NewNotificationRequest( + s.UserFactor.UserID, + s.UserFactor.ResourceOwner, + http_util.DomainContext(ctx).Origin(), + e.EventType, + domain.NotificationTypeSms, + domain.VerifySMSOTPMessageType, + ). + WithAggregate(e.Aggregate().ID, e.Aggregate().ResourceOwner). + WithCode(e.Code, e.Expiry). + WithOTP(). + WithArgs(args), + ) + }), nil } func (u *userNotifier) reduceOTPEmailCodeAdded(event eventstore.Event) (*handler.Statement, error) { @@ -403,24 +435,46 @@ func (u *userNotifier) reduceOTPEmailCodeAdded(event eventstore.Event) (*handler if !ok { return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-JL3hw", "reduce.wrong.event.type %s", user.HumanOTPEmailCodeAddedType) } - var authRequestID string - if e.AuthRequestInfo != nil { - authRequestID = e.AuthRequestInfo.ID - } - url := func(code, origin string, _ *query.NotifyUser) (string, error) { - return login.OTPLink(origin, authRequestID, code, domain.MFATypeOTPEmail), nil - } - return u.reduceOTPEmail( - e, - e.Code, - e.Expiry, - e.Aggregate().ID, - e.Aggregate().ResourceOwner, - url, - u.commands.HumanOTPEmailCodeSent, - user.HumanOTPEmailCodeAddedType, - user.HumanOTPEmailCodeSentType, - ) + + return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { + ctx := HandlerContext(event.Aggregate()) + alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, + user.HumanOTPEmailCodeAddedType, + user.HumanOTPEmailCodeSentType) + if err != nil { + return err + } + if alreadyHandled { + return nil + } + + ctx, err = u.queries.Origin(ctx, e) + if err != nil { + return err + } + origin := http_util.DomainContext(ctx).Origin() + var authRequestID string + if e.AuthRequestInfo != nil { + authRequestID = e.AuthRequestInfo.ID + } + args := otpArgs(ctx, e.Expiry) + args.AuthRequestID = authRequestID + return u.commands.RequestNotification(ctx, + e.Aggregate().ResourceOwner, + command.NewNotificationRequest( + e.Aggregate().ID, + e.Aggregate().ResourceOwner, + origin, + e.EventType, + domain.NotificationTypeEmail, + domain.VerifyEmailOTPMessageType, + ). + WithURLTemplate(login.OTPLinkTemplate(origin, authRequestID, domain.MFATypeOTPEmail)). + WithCode(e.Code, e.Expiry). + WithOTP(). + WithArgs(args), + ) + }), nil } func (u *userNotifier) reduceSessionOTPEmailChallenged(event eventstore.Event) (*handler.Statement, error) { @@ -431,93 +485,63 @@ func (u *userNotifier) reduceSessionOTPEmailChallenged(event eventstore.Event) ( if e.ReturnCode { return handler.NewNoOpStatement(e), nil } - ctx := HandlerContext(event.Aggregate()) - s, err := u.queries.SessionByID(ctx, true, e.Aggregate().ID, "") - if err != nil { - return nil, err - } - url := func(code, origin string, user *query.NotifyUser) (string, error) { - var buf strings.Builder - urlTmpl := origin + u.otpEmailTmpl - if e.URLTmpl != "" { - urlTmpl = e.URLTmpl + return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { + ctx := HandlerContext(event.Aggregate()) + alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, + session.OTPEmailChallengedType, + session.OTPEmailSentType) + if err != nil { + return err } - if err := domain.RenderOTPEmailURLTemplate(&buf, urlTmpl, code, user.ID, user.PreferredLoginName, user.DisplayName, e.Aggregate().ID, user.PreferredLanguage); err != nil { - return "", err + if alreadyHandled { + return nil } - return buf.String(), nil - } - return u.reduceOTPEmail( - e, - e.Code, - e.Expiry, - s.UserFactor.UserID, - s.UserFactor.ResourceOwner, - url, - u.commands.OTPEmailSent, - user.HumanOTPEmailCodeAddedType, - user.HumanOTPEmailCodeSentType, - ) + s, err := u.queries.SessionByID(ctx, true, e.Aggregate().ID, "") + if err != nil { + return err + } + + ctx, err = u.queries.Origin(ctx, e) + if err != nil { + return err + } + origin := http_util.DomainContext(ctx).Origin() + + args := otpArgs(ctx, e.Expiry) + args.SessionID = e.Aggregate().ID + return u.commands.RequestNotification(ctx, + s.UserFactor.ResourceOwner, + command.NewNotificationRequest( + s.UserFactor.UserID, + s.UserFactor.ResourceOwner, + origin, + e.EventType, + domain.NotificationTypeEmail, + domain.VerifyEmailOTPMessageType, + ). + WithAggregate(e.Aggregate().ID, e.Aggregate().ResourceOwner). + WithURLTemplate(u.otpEmailTemplate(origin, e)). + WithCode(e.Code, e.Expiry). + WithOTP(). + WithArgs(args), + ) + }), nil } -func (u *userNotifier) reduceOTPEmail( - event eventstore.Event, - code *crypto.CryptoValue, - expiry time.Duration, - userID, - resourceOwner string, - urlTmpl func(code, origin string, user *query.NotifyUser) (string, error), - sentCommand func(ctx context.Context, userID string, resourceOwner string) (err error), - eventTypes ...eventstore.EventType, -) (*handler.Statement, error) { - ctx := HandlerContext(event.Aggregate()) - alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, expiry, nil, eventTypes...) - if err != nil { - return nil, err - } - if alreadyHandled { - return handler.NewNoOpStatement(event), nil - } - plainCode, err := crypto.DecryptString(code, u.queries.UserDataCrypto) - if err != nil { - return nil, err - } - colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, resourceOwner, false) - if err != nil { - return nil, err +func (u *userNotifier) otpEmailTemplate(origin string, e *session.OTPEmailChallengedEvent) string { + if e.URLTmpl != "" { + return e.URLTmpl } + return origin + u.otpEmailTmpl +} - template, err := u.queries.MailTemplateByOrg(ctx, resourceOwner, false) - if err != nil { - return nil, err +func otpArgs(ctx context.Context, expiry time.Duration) *domain.NotificationArguments { + domainCtx := http_util.DomainContext(ctx) + return &domain.NotificationArguments{ + Origin: domainCtx.Origin(), + Domain: domainCtx.RequestedDomain(), + Expiry: expiry, } - - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, userID) - if err != nil { - return nil, err - } - translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, resourceOwner, domain.VerifyEmailOTPMessageType) - if err != nil { - return nil, err - } - ctx, err = u.queries.Origin(ctx, event) - if err != nil { - return nil, err - } - url, err := urlTmpl(plainCode, http_util.DomainContext(ctx).Origin(), notifyUser) - if err != nil { - return nil, err - } - notify := types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, event) - err = notify.SendOTPEmailCode(ctx, url, plainCode, expiry) - if err != nil { - return nil, err - } - err = sentCommand(ctx, event.Aggregate().ID, event.Aggregate().ResourceOwner) - if err != nil { - return nil, err - } - return handler.NewNoOpStatement(event), nil } func (u *userNotifier) reduceDomainClaimed(event eventstore.Event) (*handler.Statement, error) { @@ -535,35 +559,28 @@ func (u *userNotifier) reduceDomainClaimed(event eventstore.Event) (*handler.Sta if alreadyHandled { return nil } - colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false) - if err != nil { - return err - } - - template, err := u.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner, false) - if err != nil { - return err - } - - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) - if err != nil { - return err - } - translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.DomainClaimedMessageType) - if err != nil { - return err - } - ctx, err = u.queries.Origin(ctx, e) if err != nil { return err } - err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e). - SendDomainClaimed(ctx, notifyUser, e.UserName) - if err != nil { - return err - } - return u.commands.UserDomainClaimedSent(ctx, e.Aggregate().ResourceOwner, e.Aggregate().ID) + origin := http_util.DomainContext(ctx).Origin() + return u.commands.RequestNotification(ctx, + e.Aggregate().ResourceOwner, + command.NewNotificationRequest( + e.Aggregate().ID, + e.Aggregate().ResourceOwner, + origin, + e.EventType, + domain.NotificationTypeEmail, + domain.DomainClaimedMessageType, + ). + WithURLTemplate(login.LoginLink(origin, e.Aggregate().ResourceOwner)). + WithUnverifiedChannel(). + WithPreviousDomain(). + WithArgs(&domain.NotificationArguments{ + TempUsername: e.UserName, + }), + ) }), nil } @@ -585,42 +602,37 @@ func (u *userNotifier) reducePasswordlessCodeRequested(event eventstore.Event) ( if alreadyHandled { return nil } - code, err := crypto.DecryptString(e.Code, u.queries.UserDataCrypto) - if err != nil { - return err - } - colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false) - if err != nil { - return err - } - - template, err := u.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner, false) - if err != nil { - return err - } - - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) - if err != nil { - return err - } - translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.PasswordlessRegistrationMessageType) - if err != nil { - return err - } - ctx, err = u.queries.Origin(ctx, e) if err != nil { return err } - err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e). - SendPasswordlessRegistrationLink(ctx, notifyUser, code, e.ID, e.URLTemplate) - if err != nil { - return err - } - return u.commands.HumanPasswordlessInitCodeSent(ctx, e.Aggregate().ID, e.Aggregate().ResourceOwner, e.ID) + origin := http_util.DomainContext(ctx).Origin() + return u.commands.RequestNotification(ctx, + e.Aggregate().ResourceOwner, + command.NewNotificationRequest( + e.Aggregate().ID, + e.Aggregate().ResourceOwner, + origin, + e.EventType, + domain.NotificationTypeEmail, + domain.PasswordlessRegistrationMessageType, + ). + WithURLTemplate(u.passwordlessCodeTemplate(origin, e)). + WithCode(e.Code, e.Expiry). + WithArgs(&domain.NotificationArguments{ + CodeID: e.ID, + }), + ) }), nil } +func (u *userNotifier) passwordlessCodeTemplate(origin string, e *user.HumanPasswordlessInitCodeRequestedEvent) string { + if e.URLTemplate != "" { + return e.URLTemplate + } + return domain.PasswordlessInitCodeLinkTemplate(origin+login.HandlerPrefix+login.EndpointPasswordlessRegistration, e.Aggregate().ID, e.Aggregate().ResourceOwner, e.ID) +} + func (u *userNotifier) reducePasswordChanged(event eventstore.Event) (*handler.Statement, error) { e, ok := event.(*user.HumanPasswordChangedEvent) if !ok { @@ -638,10 +650,7 @@ func (u *userNotifier) reducePasswordChanged(event eventstore.Event) (*handler.S } notificationPolicy, err := u.queries.NotificationPolicyByOrg(ctx, true, e.Aggregate().ResourceOwner, false) - if zerrors.IsNotFound(err) { - return nil - } - if err != nil { + if err != nil && !zerrors.IsNotFound(err) { return err } @@ -649,34 +658,25 @@ func (u *userNotifier) reducePasswordChanged(event eventstore.Event) (*handler.S return nil } - colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false) - if err != nil { - return err - } - - template, err := u.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner, false) - if err != nil { - return err - } - - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) - if err != nil { - return err - } - translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.PasswordChangeMessageType) - if err != nil { - return err - } ctx, err = u.queries.Origin(ctx, e) if err != nil { return err } - err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e). - SendPasswordChange(ctx, notifyUser) - if err != nil { - return err - } - return u.commands.PasswordChangeSent(ctx, e.Aggregate().ResourceOwner, e.Aggregate().ID) + origin := http_util.DomainContext(ctx).Origin() + + return u.commands.RequestNotification(ctx, + e.Aggregate().ResourceOwner, + command.NewNotificationRequest( + e.Aggregate().ID, + e.Aggregate().ResourceOwner, + origin, + e.EventType, + domain.NotificationTypeEmail, + domain.PasswordChangeMessageType, + ). + WithURLTemplate(console.LoginHintLink(origin, "{{.PreferredLoginName}}")). + WithUnverifiedChannel(), + ) }), nil } @@ -700,37 +700,28 @@ func (u *userNotifier) reducePhoneCodeAdded(event eventstore.Event) (*handler.St if alreadyHandled { return nil } - var code string - if e.Code != nil { - code, err = crypto.DecryptString(e.Code, u.queries.UserDataCrypto) - if err != nil { - return err - } - } - colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false) - if err != nil { - return err - } - - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) - if err != nil { - return err - } - translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.VerifyPhoneMessageType) - if err != nil { - return err - } ctx, err = u.queries.Origin(ctx, e) if err != nil { return err } - generatorInfo := new(senders.CodeGeneratorInfo) - if err = types.SendSMS(ctx, u.channels, translator, notifyUser, colors, e, generatorInfo). - SendPhoneVerificationCode(ctx, code); err != nil { - return err - } - return u.commands.HumanPhoneVerificationCodeSent(ctx, e.Aggregate().ResourceOwner, e.Aggregate().ID, generatorInfo) + + return u.commands.RequestNotification(ctx, + e.Aggregate().ResourceOwner, + command.NewNotificationRequest( + e.Aggregate().ID, + e.Aggregate().ResourceOwner, + http_util.DomainContext(ctx).Origin(), + e.EventType, + domain.NotificationTypeSms, + domain.VerifyPhoneMessageType, + ). + WithCode(e.Code, e.Expiry). + WithUnverifiedChannel(). + WithArgs(&domain.NotificationArguments{ + Domain: http_util.DomainContext(ctx).RequestedDomain(), + }), + ) }), nil } @@ -753,42 +744,45 @@ func (u *userNotifier) reduceInviteCodeAdded(event eventstore.Event) (*handler.S if alreadyHandled { return nil } - code, err := crypto.DecryptString(e.Code, u.queries.UserDataCrypto) - if err != nil { - return err - } - colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false) - if err != nil { - return err - } - - template, err := u.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner, false) - if err != nil { - return err - } - - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) - if err != nil { - return err - } - translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.InviteUserMessageType) - if err != nil { - return err - } ctx, err = u.queries.Origin(ctx, e) if err != nil { return err } - notify := types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e) - err = notify.SendInviteCode(ctx, notifyUser, code, e.ApplicationName, e.URLTemplate, e.AuthRequestID) - if err != nil { - return err + origin := http_util.DomainContext(ctx).Origin() + + applicationName := e.ApplicationName + if applicationName == "" { + applicationName = "ZITADEL" } - return u.commands.InviteCodeSent(ctx, e.Aggregate().ID, e.Aggregate().ResourceOwner) + return u.commands.RequestNotification(ctx, + e.Aggregate().ResourceOwner, + command.NewNotificationRequest( + e.Aggregate().ID, + e.Aggregate().ResourceOwner, + origin, + e.EventType, + domain.NotificationTypeEmail, + domain.InviteUserMessageType, + ). + WithURLTemplate(u.inviteCodeTemplate(origin, e)). + WithCode(e.Code, e.Expiry). + WithUnverifiedChannel(). + WithArgs(&domain.NotificationArguments{ + AuthRequestID: e.AuthRequestID, + ApplicationName: applicationName, + }), + ) }), nil } +func (u *userNotifier) inviteCodeTemplate(origin string, e *user.HumanInviteCodeAddedEvent) string { + if e.URLTemplate != "" { + return e.URLTemplate + } + return login.InviteUserLinkTemplate(origin, e.Aggregate().ID, e.Aggregate().ResourceOwner, e.AuthRequestID) +} + func (u *userNotifier) checkIfCodeAlreadyHandledOrExpired(ctx context.Context, event eventstore.Event, expiry time.Duration, data map[string]interface{}, eventTypes ...eventstore.EventType) (bool, error) { if expiry > 0 && event.CreatedAt().Add(expiry).Before(time.Now().UTC()) { return true, nil diff --git a/internal/notification/handlers/user_notifier_legacy.go b/internal/notification/handlers/user_notifier_legacy.go new file mode 100644 index 0000000000..7df31cdf91 --- /dev/null +++ b/internal/notification/handlers/user_notifier_legacy.go @@ -0,0 +1,793 @@ +package handlers + +import ( + "context" + "strings" + "time" + + http_util "github.com/zitadel/zitadel/internal/api/http" + "github.com/zitadel/zitadel/internal/api/ui/login" + "github.com/zitadel/zitadel/internal/crypto" + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/eventstore/handler/v2" + "github.com/zitadel/zitadel/internal/notification/senders" + "github.com/zitadel/zitadel/internal/notification/types" + "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/internal/repository/session" + "github.com/zitadel/zitadel/internal/repository/user" + "github.com/zitadel/zitadel/internal/zerrors" +) + +type userNotifierLegacy struct { + commands Commands + queries *NotificationQueries + channels types.ChannelChains + otpEmailTmpl string +} + +func NewUserNotifierLegacy( + ctx context.Context, + config handler.Config, + commands Commands, + queries *NotificationQueries, + channels types.ChannelChains, + otpEmailTmpl string, +) *handler.Handler { + return handler.NewHandler(ctx, &config, &userNotifierLegacy{ + commands: commands, + queries: queries, + otpEmailTmpl: otpEmailTmpl, + channels: channels, + }) +} + +func (u *userNotifierLegacy) Name() string { + return UserNotificationsProjectionTable +} + +func (u *userNotifierLegacy) Reducers() []handler.AggregateReducer { + return []handler.AggregateReducer{ + { + Aggregate: user.AggregateType, + EventReducers: []handler.EventReducer{ + { + Event: user.UserV1InitialCodeAddedType, + Reduce: u.reduceInitCodeAdded, + }, + { + Event: user.HumanInitialCodeAddedType, + Reduce: u.reduceInitCodeAdded, + }, + { + Event: user.UserV1EmailCodeAddedType, + Reduce: u.reduceEmailCodeAdded, + }, + { + Event: user.HumanEmailCodeAddedType, + Reduce: u.reduceEmailCodeAdded, + }, + { + Event: user.UserV1PasswordCodeAddedType, + Reduce: u.reducePasswordCodeAdded, + }, + { + Event: user.HumanPasswordCodeAddedType, + Reduce: u.reducePasswordCodeAdded, + }, + { + Event: user.UserDomainClaimedType, + Reduce: u.reduceDomainClaimed, + }, + { + Event: user.HumanPasswordlessInitCodeRequestedType, + Reduce: u.reducePasswordlessCodeRequested, + }, + { + Event: user.UserV1PhoneCodeAddedType, + Reduce: u.reducePhoneCodeAdded, + }, + { + Event: user.HumanPhoneCodeAddedType, + Reduce: u.reducePhoneCodeAdded, + }, + { + Event: user.HumanPasswordChangedType, + Reduce: u.reducePasswordChanged, + }, + { + Event: user.HumanOTPSMSCodeAddedType, + Reduce: u.reduceOTPSMSCodeAdded, + }, + { + Event: user.HumanOTPEmailCodeAddedType, + Reduce: u.reduceOTPEmailCodeAdded, + }, + { + Event: user.HumanInviteCodeAddedType, + Reduce: u.reduceInviteCodeAdded, + }, + }, + }, + { + Aggregate: session.AggregateType, + EventReducers: []handler.EventReducer{ + { + Event: session.OTPSMSChallengedType, + Reduce: u.reduceSessionOTPSMSChallenged, + }, + { + Event: session.OTPEmailChallengedType, + Reduce: u.reduceSessionOTPEmailChallenged, + }, + }, + }, + } +} + +func (u *userNotifierLegacy) reduceInitCodeAdded(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*user.HumanInitialCodeAddedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-EFe2f", "reduce.wrong.event.type %s", user.HumanInitialCodeAddedType) + } + + return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { + ctx := HandlerContext(event.Aggregate()) + alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, + user.UserV1InitialCodeAddedType, user.UserV1InitialCodeSentType, + user.HumanInitialCodeAddedType, user.HumanInitialCodeSentType) + if err != nil { + return err + } + if alreadyHandled { + return nil + } + code, err := crypto.DecryptString(e.Code, u.queries.UserDataCrypto) + if err != nil { + return err + } + colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false) + if err != nil { + return err + } + + template, err := u.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner, false) + if err != nil { + return err + } + + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) + if err != nil { + return err + } + translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.InitCodeMessageType) + if err != nil { + return err + } + + ctx, err = u.queries.Origin(ctx, e) + if err != nil { + return err + } + err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e). + SendUserInitCode(ctx, notifyUser, code, e.AuthRequestID) + if err != nil { + return err + } + return u.commands.HumanInitCodeSent(ctx, e.Aggregate().ResourceOwner, e.Aggregate().ID) + }), nil +} + +func (u *userNotifierLegacy) reduceEmailCodeAdded(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*user.HumanEmailCodeAddedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-SWf3g", "reduce.wrong.event.type %s", user.HumanEmailCodeAddedType) + } + + if e.CodeReturned { + return handler.NewNoOpStatement(e), nil + } + + return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { + ctx := HandlerContext(event.Aggregate()) + alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, + user.UserV1EmailCodeAddedType, user.UserV1EmailCodeSentType, + user.HumanEmailCodeAddedType, user.HumanEmailCodeSentType) + if err != nil { + return err + } + if alreadyHandled { + return nil + } + code, err := crypto.DecryptString(e.Code, u.queries.UserDataCrypto) + if err != nil { + return err + } + colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false) + if err != nil { + return err + } + + template, err := u.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner, false) + if err != nil { + return err + } + + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) + if err != nil { + return err + } + translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.VerifyEmailMessageType) + if err != nil { + return err + } + + ctx, err = u.queries.Origin(ctx, e) + if err != nil { + return err + } + err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e). + SendEmailVerificationCode(ctx, notifyUser, code, e.URLTemplate, e.AuthRequestID) + if err != nil { + return err + } + return u.commands.HumanEmailVerificationCodeSent(ctx, e.Aggregate().ResourceOwner, e.Aggregate().ID) + }), nil +} + +func (u *userNotifierLegacy) reducePasswordCodeAdded(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*user.HumanPasswordCodeAddedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Eeg3s", "reduce.wrong.event.type %s", user.HumanPasswordCodeAddedType) + } + if e.CodeReturned { + return handler.NewNoOpStatement(e), nil + } + + return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { + ctx := HandlerContext(event.Aggregate()) + alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, + user.UserV1PasswordCodeAddedType, user.UserV1PasswordCodeSentType, + user.HumanPasswordCodeAddedType, user.HumanPasswordCodeSentType) + if err != nil { + return err + } + if alreadyHandled { + return nil + } + var code string + if e.Code != nil { + code, err = crypto.DecryptString(e.Code, u.queries.UserDataCrypto) + if err != nil { + return err + } + } + colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false) + if err != nil { + return err + } + + template, err := u.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner, false) + if err != nil { + return err + } + + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) + if err != nil { + return err + } + translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.PasswordResetMessageType) + if err != nil { + return err + } + + ctx, err = u.queries.Origin(ctx, e) + if err != nil { + return err + } + generatorInfo := new(senders.CodeGeneratorInfo) + notify := types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e) + if e.NotificationType == domain.NotificationTypeSms { + notify = types.SendSMS(ctx, u.channels, translator, notifyUser, colors, e, generatorInfo) + } + err = notify.SendPasswordCode(ctx, notifyUser, code, e.URLTemplate, e.AuthRequestID) + if err != nil { + return err + } + return u.commands.PasswordCodeSent(ctx, e.Aggregate().ResourceOwner, e.Aggregate().ID, generatorInfo) + }), nil +} + +func (u *userNotifierLegacy) reduceOTPSMSCodeAdded(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*user.HumanOTPSMSCodeAddedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-ASF3g", "reduce.wrong.event.type %s", user.HumanOTPSMSCodeAddedType) + } + return u.reduceOTPSMS( + e, + e.Code, + e.Expiry, + e.Aggregate().ID, + e.Aggregate().ResourceOwner, + u.commands.HumanOTPSMSCodeSent, + user.HumanOTPSMSCodeAddedType, + user.HumanOTPSMSCodeSentType, + ) +} + +func (u *userNotifierLegacy) reduceSessionOTPSMSChallenged(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*session.OTPSMSChallengedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Sk32L", "reduce.wrong.event.type %s", session.OTPSMSChallengedType) + } + if e.CodeReturned { + return handler.NewNoOpStatement(e), nil + } + ctx := HandlerContext(event.Aggregate()) + s, err := u.queries.SessionByID(ctx, true, e.Aggregate().ID, "") + if err != nil { + return nil, err + } + return u.reduceOTPSMS( + e, + e.Code, + e.Expiry, + s.UserFactor.UserID, + s.UserFactor.ResourceOwner, + u.commands.OTPSMSSent, + session.OTPSMSChallengedType, + session.OTPSMSSentType, + ) +} + +func (u *userNotifierLegacy) reduceOTPSMS( + event eventstore.Event, + code *crypto.CryptoValue, + expiry time.Duration, + userID, + resourceOwner string, + sentCommand func(ctx context.Context, userID, resourceOwner string, generatorInfo *senders.CodeGeneratorInfo) (err error), + eventTypes ...eventstore.EventType, +) (*handler.Statement, error) { + ctx := HandlerContext(event.Aggregate()) + alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, expiry, nil, eventTypes...) + if err != nil { + return nil, err + } + if alreadyHandled { + return handler.NewNoOpStatement(event), nil + } + var plainCode string + if code != nil { + plainCode, err = crypto.DecryptString(code, u.queries.UserDataCrypto) + if err != nil { + return nil, err + } + } + colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, resourceOwner, false) + if err != nil { + return nil, err + } + + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, userID) + if err != nil { + return nil, err + } + translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.VerifySMSOTPMessageType) + if err != nil { + return nil, err + } + ctx, err = u.queries.Origin(ctx, event) + if err != nil { + return nil, err + } + generatorInfo := new(senders.CodeGeneratorInfo) + notify := types.SendSMS(ctx, u.channels, translator, notifyUser, colors, event, generatorInfo) + err = notify.SendOTPSMSCode(ctx, plainCode, expiry) + if err != nil { + return nil, err + } + err = sentCommand(ctx, event.Aggregate().ID, event.Aggregate().ResourceOwner, generatorInfo) + if err != nil { + return nil, err + } + return handler.NewNoOpStatement(event), nil +} + +func (u *userNotifierLegacy) reduceOTPEmailCodeAdded(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*user.HumanOTPEmailCodeAddedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-JL3hw", "reduce.wrong.event.type %s", user.HumanOTPEmailCodeAddedType) + } + var authRequestID string + if e.AuthRequestInfo != nil { + authRequestID = e.AuthRequestInfo.ID + } + url := func(code, origin string, _ *query.NotifyUser) (string, error) { + return login.OTPLink(origin, authRequestID, code, domain.MFATypeOTPEmail), nil + } + return u.reduceOTPEmail( + e, + e.Code, + e.Expiry, + e.Aggregate().ID, + e.Aggregate().ResourceOwner, + url, + u.commands.HumanOTPEmailCodeSent, + user.HumanOTPEmailCodeAddedType, + user.HumanOTPEmailCodeSentType, + ) +} + +func (u *userNotifierLegacy) reduceSessionOTPEmailChallenged(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*session.OTPEmailChallengedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-zbsgt", "reduce.wrong.event.type %s", session.OTPEmailChallengedType) + } + if e.ReturnCode { + return handler.NewNoOpStatement(e), nil + } + ctx := HandlerContext(event.Aggregate()) + s, err := u.queries.SessionByID(ctx, true, e.Aggregate().ID, "") + if err != nil { + return nil, err + } + url := func(code, origin string, user *query.NotifyUser) (string, error) { + var buf strings.Builder + urlTmpl := origin + u.otpEmailTmpl + if e.URLTmpl != "" { + urlTmpl = e.URLTmpl + } + if err := domain.RenderOTPEmailURLTemplate(&buf, urlTmpl, code, user.ID, user.PreferredLoginName, user.DisplayName, e.Aggregate().ID, user.PreferredLanguage); err != nil { + return "", err + } + return buf.String(), nil + } + return u.reduceOTPEmail( + e, + e.Code, + e.Expiry, + s.UserFactor.UserID, + s.UserFactor.ResourceOwner, + url, + u.commands.OTPEmailSent, + user.HumanOTPEmailCodeAddedType, + user.HumanOTPEmailCodeSentType, + ) +} + +func (u *userNotifierLegacy) reduceOTPEmail( + event eventstore.Event, + code *crypto.CryptoValue, + expiry time.Duration, + userID, + resourceOwner string, + urlTmpl func(code, origin string, user *query.NotifyUser) (string, error), + sentCommand func(ctx context.Context, userID string, resourceOwner string) (err error), + eventTypes ...eventstore.EventType, +) (*handler.Statement, error) { + ctx := HandlerContext(event.Aggregate()) + alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, expiry, nil, eventTypes...) + if err != nil { + return nil, err + } + if alreadyHandled { + return handler.NewNoOpStatement(event), nil + } + plainCode, err := crypto.DecryptString(code, u.queries.UserDataCrypto) + if err != nil { + return nil, err + } + colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, resourceOwner, false) + if err != nil { + return nil, err + } + + template, err := u.queries.MailTemplateByOrg(ctx, resourceOwner, false) + if err != nil { + return nil, err + } + + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, userID) + if err != nil { + return nil, err + } + translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, resourceOwner, domain.VerifyEmailOTPMessageType) + if err != nil { + return nil, err + } + ctx, err = u.queries.Origin(ctx, event) + if err != nil { + return nil, err + } + url, err := urlTmpl(plainCode, http_util.DomainContext(ctx).Origin(), notifyUser) + if err != nil { + return nil, err + } + notify := types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, event) + err = notify.SendOTPEmailCode(ctx, url, plainCode, expiry) + if err != nil { + return nil, err + } + err = sentCommand(ctx, event.Aggregate().ID, event.Aggregate().ResourceOwner) + if err != nil { + return nil, err + } + return handler.NewNoOpStatement(event), nil +} + +func (u *userNotifierLegacy) reduceDomainClaimed(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*user.DomainClaimedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Drh5w", "reduce.wrong.event.type %s", user.UserDomainClaimedType) + } + return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { + ctx := HandlerContext(event.Aggregate()) + alreadyHandled, err := u.queries.IsAlreadyHandled(ctx, event, nil, + user.UserDomainClaimedType, user.UserDomainClaimedSentType) + if err != nil { + return err + } + if alreadyHandled { + return nil + } + colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false) + if err != nil { + return err + } + + template, err := u.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner, false) + if err != nil { + return err + } + + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) + if err != nil { + return err + } + translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.DomainClaimedMessageType) + if err != nil { + return err + } + + ctx, err = u.queries.Origin(ctx, e) + if err != nil { + return err + } + err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e). + SendDomainClaimed(ctx, notifyUser, e.UserName) + if err != nil { + return err + } + return u.commands.UserDomainClaimedSent(ctx, e.Aggregate().ResourceOwner, e.Aggregate().ID) + }), nil +} + +func (u *userNotifierLegacy) reducePasswordlessCodeRequested(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*user.HumanPasswordlessInitCodeRequestedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-EDtjd", "reduce.wrong.event.type %s", user.HumanPasswordlessInitCodeAddedType) + } + if e.CodeReturned { + return handler.NewNoOpStatement(e), nil + } + + return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { + ctx := HandlerContext(event.Aggregate()) + alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, map[string]interface{}{"id": e.ID}, user.HumanPasswordlessInitCodeSentType) + if err != nil { + return err + } + if alreadyHandled { + return nil + } + code, err := crypto.DecryptString(e.Code, u.queries.UserDataCrypto) + if err != nil { + return err + } + colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false) + if err != nil { + return err + } + + template, err := u.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner, false) + if err != nil { + return err + } + + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) + if err != nil { + return err + } + translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.PasswordlessRegistrationMessageType) + if err != nil { + return err + } + + ctx, err = u.queries.Origin(ctx, e) + if err != nil { + return err + } + err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e). + SendPasswordlessRegistrationLink(ctx, notifyUser, code, e.ID, e.URLTemplate) + if err != nil { + return err + } + return u.commands.HumanPasswordlessInitCodeSent(ctx, e.Aggregate().ID, e.Aggregate().ResourceOwner, e.ID) + }), nil +} + +func (u *userNotifierLegacy) reducePasswordChanged(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*user.HumanPasswordChangedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Yko2z8", "reduce.wrong.event.type %s", user.HumanPasswordChangedType) + } + + return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { + ctx := HandlerContext(event.Aggregate()) + alreadyHandled, err := u.queries.IsAlreadyHandled(ctx, event, nil, user.HumanPasswordChangeSentType) + if err != nil { + return err + } + if alreadyHandled { + return nil + } + + notificationPolicy, err := u.queries.NotificationPolicyByOrg(ctx, true, e.Aggregate().ResourceOwner, false) + if zerrors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + + if !notificationPolicy.PasswordChange { + return nil + } + + colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false) + if err != nil { + return err + } + + template, err := u.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner, false) + if err != nil { + return err + } + + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) + if err != nil { + return err + } + translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.PasswordChangeMessageType) + if err != nil { + return err + } + ctx, err = u.queries.Origin(ctx, e) + if err != nil { + return err + } + err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e). + SendPasswordChange(ctx, notifyUser) + if err != nil { + return err + } + return u.commands.PasswordChangeSent(ctx, e.Aggregate().ResourceOwner, e.Aggregate().ID) + }), nil +} + +func (u *userNotifierLegacy) reducePhoneCodeAdded(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*user.HumanPhoneCodeAddedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-He83g", "reduce.wrong.event.type %s", user.HumanPhoneCodeAddedType) + } + if e.CodeReturned { + return handler.NewNoOpStatement(e), nil + } + + return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { + ctx := HandlerContext(event.Aggregate()) + alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, + user.UserV1PhoneCodeAddedType, user.UserV1PhoneCodeSentType, + user.HumanPhoneCodeAddedType, user.HumanPhoneCodeSentType) + if err != nil { + return err + } + if alreadyHandled { + return nil + } + var code string + if e.Code != nil { + code, err = crypto.DecryptString(e.Code, u.queries.UserDataCrypto) + if err != nil { + return err + } + } + colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false) + if err != nil { + return err + } + + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) + if err != nil { + return err + } + translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.VerifyPhoneMessageType) + if err != nil { + return err + } + + ctx, err = u.queries.Origin(ctx, e) + if err != nil { + return err + } + generatorInfo := new(senders.CodeGeneratorInfo) + if err = types.SendSMS(ctx, u.channels, translator, notifyUser, colors, e, generatorInfo). + SendPhoneVerificationCode(ctx, code); err != nil { + return err + } + return u.commands.HumanPhoneVerificationCodeSent(ctx, e.Aggregate().ResourceOwner, e.Aggregate().ID, generatorInfo) + }), nil +} + +func (u *userNotifierLegacy) reduceInviteCodeAdded(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*user.HumanInviteCodeAddedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Eeg3s", "reduce.wrong.event.type %s", user.HumanInviteCodeAddedType) + } + if e.CodeReturned { + return handler.NewNoOpStatement(e), nil + } + + return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { + ctx := HandlerContext(event.Aggregate()) + alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, + user.HumanInviteCodeAddedType, user.HumanInviteCodeSentType) + if err != nil { + return err + } + if alreadyHandled { + return nil + } + code, err := crypto.DecryptString(e.Code, u.queries.UserDataCrypto) + if err != nil { + return err + } + colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false) + if err != nil { + return err + } + + template, err := u.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner, false) + if err != nil { + return err + } + + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) + if err != nil { + return err + } + translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.InviteUserMessageType) + if err != nil { + return err + } + + ctx, err = u.queries.Origin(ctx, e) + if err != nil { + return err + } + notify := types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e) + err = notify.SendInviteCode(ctx, notifyUser, code, e.ApplicationName, e.URLTemplate, e.AuthRequestID) + if err != nil { + return err + } + return u.commands.InviteCodeSent(ctx, e.Aggregate().ID, e.Aggregate().ResourceOwner) + }), nil +} + +func (u *userNotifierLegacy) checkIfCodeAlreadyHandledOrExpired(ctx context.Context, event eventstore.Event, expiry time.Duration, data map[string]interface{}, eventTypes ...eventstore.EventType) (bool, error) { + if expiry > 0 && event.CreatedAt().Add(expiry).Before(time.Now().UTC()) { + return true, nil + } + return u.queries.IsAlreadyHandled(ctx, event, data, eventTypes...) +} diff --git a/internal/notification/handlers/user_notifier_legacy_test.go b/internal/notification/handlers/user_notifier_legacy_test.go new file mode 100644 index 0000000000..fe99eaa572 --- /dev/null +++ b/internal/notification/handlers/user_notifier_legacy_test.go @@ -0,0 +1,1601 @@ +package handlers + +import ( + "database/sql" + "fmt" + "testing" + "time" + + "github.com/muhlemmer/gu" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/eventstore/repository" + es_repo_mock "github.com/zitadel/zitadel/internal/eventstore/repository/mock" + "github.com/zitadel/zitadel/internal/notification/channels/email" + channel_mock "github.com/zitadel/zitadel/internal/notification/channels/mock" + "github.com/zitadel/zitadel/internal/notification/channels/sms" + "github.com/zitadel/zitadel/internal/notification/channels/smtp" + "github.com/zitadel/zitadel/internal/notification/channels/twilio" + "github.com/zitadel/zitadel/internal/notification/handlers/mock" + "github.com/zitadel/zitadel/internal/notification/messages" + "github.com/zitadel/zitadel/internal/notification/senders" + "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/internal/repository/session" + "github.com/zitadel/zitadel/internal/repository/user" +) + +func Test_userNotifierLegacy_reduceInitCodeAdded(t *testing.T) { + expectMailSubject := "Initialize User" + tests := []struct { + name string + test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, wantLegacy) + }{{ + name: "asset url with event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, "testcode") + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().HumanInitCodeSent(gomock.Any(), orgID, userID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &user.HumanInitialCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, { + name: "asset url without event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s://%s:%d%s/%s/%s", externalProtocol, instancePrimaryDomain, externalPort, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, "testcode") + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().HumanInitCodeSent(gomock.Any(), orgID, userID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &user.HumanInitialCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + }, + }, w + }, + }, { + name: "button url with event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.URL}}" + testCode := "testcode" + expectContent := fmt.Sprintf("%s/ui/login/user/init?authRequestID=%s&code=%s&loginname=%s&orgID=%s&passwordset=%t&userID=%s", eventOrigin, "", testCode, preferredLoginName, orgID, false, userID) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, testCode) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().HumanInitCodeSent(gomock.Any(), orgID, userID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &user.HumanInitialCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, { + name: "button url without event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.URL}}" + testCode := "testcode" + expectContent := fmt.Sprintf("%s://%s:%d/ui/login/user/init?authRequestID=%s&code=%s&loginname=%s&orgID=%s&passwordset=%t&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, "", testCode, preferredLoginName, orgID, false, userID) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, testCode) + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().HumanInitCodeSent(gomock.Any(), orgID, userID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &user.HumanInitialCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + }, + }, w + }, + }, { + name: "button url without event trigger url with authRequestID", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.URL}}" + testCode := "testcode" + expectContent := fmt.Sprintf("%s://%s:%d/ui/login/user/init?authRequestID=%s&code=%s&loginname=%s&orgID=%s&passwordset=%t&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, authRequestID, testCode, preferredLoginName, orgID, false, userID) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, testCode) + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().HumanInitCodeSent(gomock.Any(), orgID, userID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &user.HumanInitialCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + AuthRequestID: authRequestID, + }, + }, w + }, + }} + // TODO: Why don't we have an url template on user.HumanInitialCodeAddedEvent? + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + queries := mock.NewMockQueries(ctrl) + commands := mock.NewMockCommands(ctrl) + f, a, w := tt.test(ctrl, queries, commands) + stmt, err := newUserNotifierLegacy(t, ctrl, queries, f, a, w).reduceInitCodeAdded(a.event) + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + err = stmt.Execute(nil, "") + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func Test_userNotifierLegacy_reduceEmailCodeAdded(t *testing.T) { + expectMailSubject := "Verify email" + tests := []struct { + name string + test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, wantLegacy) + }{{ + name: "asset url with event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, "testcode") + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().HumanEmailVerificationCodeSent(gomock.Any(), orgID, userID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &user.HumanEmailCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, { + name: "asset url without event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s://%s:%d%s/%s/%s", externalProtocol, instancePrimaryDomain, externalPort, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, "testcode") + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().HumanEmailVerificationCodeSent(gomock.Any(), orgID, userID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &user.HumanEmailCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + }, + }, w + }, + }, { + name: "button url with event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.URL}}" + testCode := "testcode" + expectContent := fmt.Sprintf("%s/ui/login/mail/verification?authRequestID=%s&code=%s&orgID=%s&userID=%s", eventOrigin, "", testCode, orgID, userID) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, testCode) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().HumanEmailVerificationCodeSent(gomock.Any(), orgID, userID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + SMSTokenCrypto: nil, + }, args{ + event: &user.HumanEmailCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, { + name: "button url without event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.URL}}" + testCode := "testcode" + expectContent := fmt.Sprintf("%s://%s:%d/ui/login/mail/verification?authRequestID=%s&code=%s&orgID=%s&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, "", testCode, orgID, userID) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, testCode) + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().HumanEmailVerificationCodeSent(gomock.Any(), orgID, userID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &user.HumanEmailCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + }, + }, w + }, + }, { + name: "button url without event trigger url with authRequestID", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.URL}}" + testCode := "testcode" + expectContent := fmt.Sprintf("%s://%s:%d/ui/login/mail/verification?authRequestID=%s&code=%s&orgID=%s&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, authRequestID, testCode, orgID, userID) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, testCode) + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().HumanEmailVerificationCodeSent(gomock.Any(), orgID, userID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &user.HumanEmailCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + AuthRequestID: authRequestID, + }, + }, w + }, + }, { + name: "button url with url template and event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.URL}}" + urlTemplate := "https://my.custom.url/org/{{.OrgID}}/user/{{.UserID}}/verify/{{.Code}}" + testCode := "testcode" + expectContent := fmt.Sprintf("https://my.custom.url/org/%s/user/%s/verify/%s", orgID, userID, testCode) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, testCode) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().HumanEmailVerificationCodeSent(gomock.Any(), orgID, userID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + SMSTokenCrypto: nil, + }, args{ + event: &user.HumanEmailCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + URLTemplate: urlTemplate, + CodeReturned: false, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + queries := mock.NewMockQueries(ctrl) + commands := mock.NewMockCommands(ctrl) + f, a, w := tt.test(ctrl, queries, commands) + stmt, err := newUserNotifierLegacy(t, ctrl, queries, f, a, w).reduceEmailCodeAdded(a.event) + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + err = stmt.Execute(nil, "") + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func Test_userNotifierLegacy_reducePasswordCodeAdded(t *testing.T) { + expectMailSubject := "Reset password" + tests := []struct { + name string + test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, wantLegacy) + }{{ + name: "asset url with event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, "testcode") + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().PasswordCodeSent(gomock.Any(), orgID, userID, &senders.CodeGeneratorInfo{}).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &user.HumanPasswordCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, { + name: "asset url without event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s://%s:%d%s/%s/%s", externalProtocol, instancePrimaryDomain, externalPort, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, "testcode") + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().PasswordCodeSent(gomock.Any(), orgID, userID, &senders.CodeGeneratorInfo{}).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &user.HumanPasswordCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + }, + }, w + }, + }, { + name: "button url with event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.URL}}" + testCode := "testcode" + expectContent := fmt.Sprintf("%s/ui/login/password/init?authRequestID=%s&code=%s&orgID=%s&userID=%s", eventOrigin, "", testCode, orgID, userID) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, testCode) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().PasswordCodeSent(gomock.Any(), orgID, userID, &senders.CodeGeneratorInfo{}).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + SMSTokenCrypto: nil, + }, args{ + event: &user.HumanPasswordCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, { + name: "button url without event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.URL}}" + testCode := "testcode" + expectContent := fmt.Sprintf("%s://%s:%d/ui/login/password/init?authRequestID=%s&code=%s&orgID=%s&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, "", testCode, orgID, userID) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, testCode) + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().PasswordCodeSent(gomock.Any(), orgID, userID, &senders.CodeGeneratorInfo{}).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &user.HumanPasswordCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + }, + }, w + }, + }, { + name: "button url without event trigger url with authRequestID", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.URL}}" + testCode := "testcode" + expectContent := fmt.Sprintf("%s://%s:%d/ui/login/password/init?authRequestID=%s&code=%s&orgID=%s&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, authRequestID, testCode, orgID, userID) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, testCode) + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().PasswordCodeSent(gomock.Any(), orgID, userID, &senders.CodeGeneratorInfo{}).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &user.HumanPasswordCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + AuthRequestID: authRequestID, + }, + }, w + }, + }, { + name: "button url with url template and event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.URL}}" + urlTemplate := "https://my.custom.url/org/{{.OrgID}}/user/{{.UserID}}/verify/{{.Code}}" + testCode := "testcode" + expectContent := fmt.Sprintf("https://my.custom.url/org/%s/user/%s/verify/%s", orgID, userID, testCode) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, testCode) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().PasswordCodeSent(gomock.Any(), orgID, userID, &senders.CodeGeneratorInfo{}).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + SMSTokenCrypto: nil, + }, args{ + event: &user.HumanPasswordCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + URLTemplate: urlTemplate, + CodeReturned: false, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, { + name: "external code", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.URL}}" + expectContent := "We received a password reset request. Please use the button below to reset your password. (Code ) If you didn't ask for this mail, please ignore it." + w.messageSMS = &messages.SMS{ + SenderPhoneNumber: "senderNumber", + RecipientPhoneNumber: lastPhone, + Content: expectContent, + } + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().PasswordCodeSent(gomock.Any(), orgID, userID, &senders.CodeGeneratorInfo{ID: smsProviderID, VerificationID: verificationID}).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + SMSTokenCrypto: nil, + }, args{ + event: &user.HumanPasswordCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: nil, + Expiry: 0, + URLTemplate: "", + CodeReturned: false, + NotificationType: domain.NotificationTypeSms, + GeneratorID: smsProviderID, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + queries := mock.NewMockQueries(ctrl) + commands := mock.NewMockCommands(ctrl) + f, a, w := tt.test(ctrl, queries, commands) + stmt, err := newUserNotifierLegacy(t, ctrl, queries, f, a, w).reducePasswordCodeAdded(a.event) + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + err = stmt.Execute(nil, "") + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func Test_userNotifierLegacy_reduceDomainClaimed(t *testing.T) { + expectMailSubject := "Domain has been claimed" + tests := []struct { + name string + test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, wantLegacy) + }{{ + name: "asset url with event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().UserDomainClaimedSent(gomock.Any(), orgID, userID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + }, args{ + event: &user.DomainClaimedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, { + name: "asset url without event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s://%s:%d%s/%s/%s", externalProtocol, instancePrimaryDomain, externalPort, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().UserDomainClaimedSent(gomock.Any(), orgID, userID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + }, args{ + event: &user.DomainClaimedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + }, + }, w + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + queries := mock.NewMockQueries(ctrl) + commands := mock.NewMockCommands(ctrl) + f, a, w := tt.test(ctrl, queries, commands) + stmt, err := newUserNotifierLegacy(t, ctrl, queries, f, a, w).reduceDomainClaimed(a.event) + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + err = stmt.Execute(nil, "") + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func Test_userNotifierLegacy_reducePasswordlessCodeRequested(t *testing.T) { + expectMailSubject := "Add Passwordless Login" + tests := []struct { + name string + test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, wantLegacy) + }{{ + name: "asset url with event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, "testcode") + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().HumanPasswordlessInitCodeSent(gomock.Any(), userID, orgID, codeID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &user.HumanPasswordlessInitCodeRequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + ID: codeID, + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, { + name: "asset url without event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s://%s:%d%s/%s/%s", externalProtocol, instancePrimaryDomain, externalPort, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, "testcode") + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().HumanPasswordlessInitCodeSent(gomock.Any(), userID, orgID, codeID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &user.HumanPasswordlessInitCodeRequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + ID: codeID, + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + }, + }, w + }, + }, { + name: "button url with event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.URL}}" + testCode := "testcode" + codeAlg, code := cryptoValue(t, ctrl, testCode) + expectContent := fmt.Sprintf("%s/ui/login/login/passwordless/init?userID=%s&orgID=%s&codeID=%s&code=%s", eventOrigin, userID, orgID, codeID, testCode) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().HumanPasswordlessInitCodeSent(gomock.Any(), userID, orgID, codeID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + SMSTokenCrypto: nil, + }, args{ + event: &user.HumanPasswordlessInitCodeRequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + ID: codeID, + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, { + name: "button url without event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.URL}}" + testCode := "testcode" + codeAlg, code := cryptoValue(t, ctrl, testCode) + expectContent := fmt.Sprintf("%s://%s:%d/ui/login/login/passwordless/init?userID=%s&orgID=%s&codeID=%s&code=%s", externalProtocol, instancePrimaryDomain, externalPort, userID, orgID, codeID, testCode) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().HumanPasswordlessInitCodeSent(gomock.Any(), userID, orgID, codeID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &user.HumanPasswordlessInitCodeRequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + ID: codeID, + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + }, + }, w + }, + }, { + name: "button url with url template and event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.URL}}" + urlTemplate := "https://my.custom.url/org/{{.OrgID}}/user/{{.UserID}}/verify/{{.Code}}" + testCode := "testcode" + expectContent := fmt.Sprintf("https://my.custom.url/org/%s/user/%s/verify/%s", orgID, userID, testCode) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, testCode) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().HumanPasswordlessInitCodeSent(gomock.Any(), userID, orgID, codeID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + SMSTokenCrypto: nil, + }, args{ + event: &user.HumanPasswordlessInitCodeRequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + ID: codeID, + Code: code, + Expiry: time.Hour, + URLTemplate: urlTemplate, + CodeReturned: false, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + queries := mock.NewMockQueries(ctrl) + commands := mock.NewMockCommands(ctrl) + f, a, w := tt.test(ctrl, queries, commands) + stmt, err := newUserNotifierLegacy(t, ctrl, queries, f, a, w).reducePasswordlessCodeRequested(a.event) + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + err = stmt.Execute(nil, "") + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func Test_userNotifierLegacy_reducePasswordChanged(t *testing.T) { + expectMailSubject := "Password of user has changed" + tests := []struct { + name string + test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, wantLegacy) + }{{ + name: "asset url with event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + queries.EXPECT().NotificationPolicyByOrg(gomock.Any(), gomock.Any(), orgID, gomock.Any()).Return(&query.NotificationPolicy{ + PasswordChange: true, + }, nil) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().PasswordChangeSent(gomock.Any(), orgID, userID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + }, args{ + event: &user.HumanPasswordChangedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, { + name: "asset url without event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s://%s:%d%s/%s/%s", externalProtocol, instancePrimaryDomain, externalPort, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{lastEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + queries.EXPECT().NotificationPolicyByOrg(gomock.Any(), gomock.Any(), orgID, gomock.Any()).Return(&query.NotificationPolicy{ + PasswordChange: true, + }, nil) + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + commands.EXPECT().PasswordChangeSent(gomock.Any(), orgID, userID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + }, args{ + event: &user.HumanPasswordChangedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + }, + }, w + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + queries := mock.NewMockQueries(ctrl) + commands := mock.NewMockCommands(ctrl) + f, a, w := tt.test(ctrl, queries, commands) + stmt, err := newUserNotifierLegacy(t, ctrl, queries, f, a, w).reducePasswordChanged(a.event) + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + err = stmt.Execute(nil, "") + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func Test_userNotifierLegacy_reduceOTPEmailChallenged(t *testing.T) { + expectMailSubject := "Verify One-Time Password" + tests := []struct { + name string + test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, wantLegacy) + }{{ + name: "asset url with event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{verifiedEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, "testcode") + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), userID, gomock.Any()).Return(&query.Session{}, nil) + commands.EXPECT().OTPEmailSent(gomock.Any(), userID, orgID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &session.OTPEmailChallengedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + URLTmpl: "", + ReturnCode: false, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, { + name: "asset url without event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.LogoURL}}" + expectContent := fmt.Sprintf("%s://%s:%d%s/%s/%s", externalProtocol, instancePrimaryDomain, externalPort, assetsPath, policyID, logoURL) + w.message = &messages.Email{ + Recipients: []string{verifiedEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, "testcode") + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), userID, gomock.Any()).Return(&query.Session{}, nil) + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + commands.EXPECT().OTPEmailSent(gomock.Any(), userID, orgID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &session.OTPEmailChallengedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + URLTmpl: "", + ReturnCode: false, + }, + }, w + }, + }, { + name: "button url with event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.URL}}" + testCode := "testcode" + expectContent := fmt.Sprintf("%s/otp/verify?loginName=%s&code=%s", eventOrigin, preferredLoginName, testCode) + w.message = &messages.Email{ + Recipients: []string{verifiedEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, testCode) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), userID, gomock.Any()).Return(&query.Session{}, nil) + commands.EXPECT().OTPEmailSent(gomock.Any(), userID, orgID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + SMSTokenCrypto: nil, + }, args{ + event: &session.OTPEmailChallengedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + URLTmpl: "", + ReturnCode: false, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, { + name: "button url without event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.URL}}" + testCode := "testcode" + expectContent := fmt.Sprintf("%s://%s:%d/otp/verify?loginName=%s&code=%s", externalProtocol, instancePrimaryDomain, externalPort, preferredLoginName, testCode) + w.message = &messages.Email{ + Recipients: []string{verifiedEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, testCode) + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), userID, gomock.Any()).Return(&query.Session{}, nil) + commands.EXPECT().OTPEmailSent(gomock.Any(), userID, orgID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + }, args{ + event: &session.OTPEmailChallengedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + ReturnCode: false, + }, + }, w + }, + }, { + name: "button url with url template and event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + givenTemplate := "{{.URL}}" + urlTemplate := "https://my.custom.url/user/{{.LoginName}}/verify" + testCode := "testcode" + expectContent := fmt.Sprintf("https://my.custom.url/user/%s/verify", preferredLoginName) + w.message = &messages.Email{ + Recipients: []string{verifiedEmail}, + Subject: expectMailSubject, + Content: expectContent, + } + codeAlg, code := cryptoValue(t, ctrl, testCode) + expectTemplateWithNotifyUserQueries(queries, givenTemplate) + queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), userID, gomock.Any()).Return(&query.Session{}, nil) + commands.EXPECT().OTPEmailSent(gomock.Any(), userID, orgID).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + userDataCrypto: codeAlg, + SMSTokenCrypto: nil, + }, args{ + event: &session.OTPEmailChallengedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: code, + Expiry: time.Hour, + ReturnCode: false, + URLTmpl: urlTemplate, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + queries := mock.NewMockQueries(ctrl) + commands := mock.NewMockCommands(ctrl) + f, a, w := tt.test(ctrl, queries, commands) + _, err := newUserNotifierLegacy(t, ctrl, queries, f, a, w).reduceSessionOTPEmailChallenged(a.event) + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func Test_userNotifierLegacy_reduceOTPSMSChallenged(t *testing.T) { + tests := []struct { + name string + test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, wantLegacy) + }{{ + name: "asset url with event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + testCode := "" + expiry := 0 * time.Hour + expectContent := fmt.Sprintf(`%[1]s is your one-time password for %[2]s. Use it within the next %[3]s. +@%[2]s #%[1]s`, testCode, eventOriginDomain, expiry) + w.messageSMS = &messages.SMS{ + SenderPhoneNumber: "senderNumber", + RecipientPhoneNumber: verifiedPhone, + Content: expectContent, + } + expectTemplateWithNotifyUserQueriesSMS(queries) + queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), userID, gomock.Any()).Return(&query.Session{}, nil) + commands.EXPECT().OTPSMSSent(gomock.Any(), userID, orgID, &senders.CodeGeneratorInfo{ID: smsProviderID, VerificationID: verificationID}).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + }, args{ + event: &session.OTPSMSChallengedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: nil, + Expiry: expiry, + CodeReturned: false, + GeneratorID: smsProviderID, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, { + name: "asset url without event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w wantLegacy) { + testCode := "" + expiry := 0 * time.Hour + expectContent := fmt.Sprintf(`%[1]s is your one-time password for %[2]s. Use it within the next %[3]s. +@%[2]s #%[1]s`, testCode, instancePrimaryDomain, expiry) + w.messageSMS = &messages.SMS{ + SenderPhoneNumber: "senderNumber", + RecipientPhoneNumber: verifiedPhone, + Content: expectContent, + } + expectTemplateWithNotifyUserQueriesSMS(queries) + queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), userID, gomock.Any()).Return(&query.Session{}, nil) + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + commands.EXPECT().OTPSMSSent(gomock.Any(), userID, orgID, &senders.CodeGeneratorInfo{ID: smsProviderID, VerificationID: verificationID}).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + }, args{ + event: &session.OTPSMSChallengedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + }), + Code: nil, + Expiry: expiry, + CodeReturned: false, + GeneratorID: smsProviderID, + }, + }, w + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + queries := mock.NewMockQueries(ctrl) + commands := mock.NewMockCommands(ctrl) + f, a, w := tt.test(ctrl, queries, commands) + _, err := newUserNotifierLegacy(t, ctrl, queries, f, a, w).reduceSessionOTPSMSChallenged(a.event) + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +type wantLegacy struct { + message *messages.Email + messageSMS *messages.SMS + err assert.ErrorAssertionFunc +} + +func newUserNotifierLegacy(t *testing.T, ctrl *gomock.Controller, queries *mock.MockQueries, f fields, a args, w wantLegacy) *userNotifierLegacy { + queries.EXPECT().NotificationProviderByIDAndType(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&query.DebugNotificationProvider{}, nil) + smtpAlg, _ := cryptoValue(t, ctrl, "smtppw") + channel := channel_mock.NewMockNotificationChannel(ctrl) + if w.err == nil { + if w.message != nil { + w.message.TriggeringEvent = a.event + channel.EXPECT().HandleMessage(w.message).Return(nil) + } + if w.messageSMS != nil { + w.messageSMS.TriggeringEvent = a.event + channel.EXPECT().HandleMessage(w.messageSMS).DoAndReturn(func(message *messages.SMS) error { + message.VerificationID = gu.Ptr(verificationID) + return nil + }) + } + } + return &userNotifierLegacy{ + commands: f.commands, + queries: NewNotificationQueries( + f.queries, + f.es, + externalDomain, + externalPort, + externalSecure, + "", + f.userDataCrypto, + smtpAlg, + f.SMSTokenCrypto, + ), + otpEmailTmpl: defaultOTPEmailTemplate, + channels: ¬ificationChannels{ + Chain: *senders.ChainChannels(channel), + EmailConfig: &email.Config{ + ProviderConfig: &email.Provider{ + ID: "emailProviderID", + Description: "description", + }, + SMTPConfig: &smtp.Config{ + SMTP: smtp.SMTP{ + Host: "host", + User: "user", + Password: "password", + }, + Tls: true, + From: "from", + FromName: "fromName", + ReplyToAddress: "replyToAddress", + }, + WebhookConfig: nil, + }, + SMSConfig: &sms.Config{ + ProviderConfig: &sms.Provider{ + ID: "smsProviderID", + Description: "description", + }, + TwilioConfig: &twilio.Config{ + SID: "sid", + Token: "token", + SenderNumber: "senderNumber", + VerifyServiceSID: "verifyServiceSID", + }, + }, + }, + } +} diff --git a/internal/notification/handlers/user_notifier_test.go b/internal/notification/handlers/user_notifier_test.go index 9692832787..b57edcc57c 100644 --- a/internal/notification/handlers/user_notifier_test.go +++ b/internal/notification/handlers/user_notifier_test.go @@ -7,22 +7,19 @@ import ( "testing" "time" - "github.com/muhlemmer/gu" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" "golang.org/x/text/language" + "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/repository" es_repo_mock "github.com/zitadel/zitadel/internal/eventstore/repository/mock" "github.com/zitadel/zitadel/internal/notification/channels/email" - channel_mock "github.com/zitadel/zitadel/internal/notification/channels/mock" "github.com/zitadel/zitadel/internal/notification/channels/set" "github.com/zitadel/zitadel/internal/notification/channels/sms" - "github.com/zitadel/zitadel/internal/notification/channels/smtp" - "github.com/zitadel/zitadel/internal/notification/channels/twilio" "github.com/zitadel/zitadel/internal/notification/channels/webhook" "github.com/zitadel/zitadel/internal/notification/handlers/mock" "github.com/zitadel/zitadel/internal/notification/messages" @@ -39,6 +36,8 @@ const ( userID = "user1" codeID = "event1" logoURL = "logo.png" + instanceID = "instanceID" + sessionID = "sessionID" eventOrigin = "https://triggered.here" eventOriginDomain = "triggered.here" assetsPath = "/assets/v1" @@ -60,196 +59,106 @@ const ( ) func Test_userNotifier_reduceInitCodeAdded(t *testing.T) { - expectMailSubject := "Initialize User" tests := []struct { name string test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, want) - }{{ - name: "asset url with event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.LogoURL}}" - expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, "testcode") - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().HumanInitCodeSent(gomock.Any(), orgID, userID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &user.HumanInitialCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), + }{ + { + name: "with event trigger", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + _, code := cryptoValue(t, ctrl, "testcode") + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: eventOrigin, + URLTemplate: fmt.Sprintf("%s/ui/login/user/init?userID=%s&loginname={{.LoginName}}&code={{.Code}}&orgID=%s&passwordset={{.PasswordSet}}&authRequestID=%s", + eventOrigin, userID, orgID, authRequestID), + Code: code, + CodeExpiry: time.Hour, + EventType: user.HumanInitialCodeAddedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.InitCodeMessageType, + UnverifiedNotificationChannel: true, + Args: &domain.NotificationArguments{AuthRequestID: authRequestID}, + AggregateID: "", + AggregateResourceOwner: "", + IsOTP: false, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, }), - Code: code, - Expiry: time.Hour, - TriggeredAtOrigin: eventOrigin, - }, - }, w + }, args{ + event: &user.HumanInitialCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanInitialCodeAddedType, + }), + Code: code, + Expiry: time.Hour, + TriggeredAtOrigin: eventOrigin, + AuthRequestID: authRequestID, + }, + }, w + }, }, - }, { - name: "asset url without event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.LogoURL}}" - expectContent := fmt.Sprintf("%s://%s:%d%s/%s/%s", externalProtocol, instancePrimaryDomain, externalPort, assetsPath, policyID, logoURL) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, "testcode") - queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ - Domains: []*query.InstanceDomain{{ - Domain: instancePrimaryDomain, - IsPrimary: true, - }}, - }, nil) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().HumanInitCodeSent(gomock.Any(), orgID, userID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &user.HumanInitialCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), + { + name: "without event trigger", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + _, code := cryptoValue(t, ctrl, "testcode") + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: fmt.Sprintf("%s://%s:%d", externalProtocol, instancePrimaryDomain, externalPort), + URLTemplate: fmt.Sprintf("%s://%s:%d/ui/login/user/init?userID=%s&loginname={{.LoginName}}&code={{.Code}}&orgID=%s&passwordset={{.PasswordSet}}&authRequestID=%s", + externalProtocol, instancePrimaryDomain, externalPort, userID, orgID, authRequestID), + Code: code, + CodeExpiry: time.Hour, + EventType: user.HumanInitialCodeAddedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.InitCodeMessageType, + UnverifiedNotificationChannel: true, + Args: &domain.NotificationArguments{AuthRequestID: authRequestID}, + AggregateID: "", + AggregateResourceOwner: "", + IsOTP: false, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, }), - Code: code, - Expiry: time.Hour, - }, - }, w + }, args{ + event: &user.HumanInitialCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanInitialCodeAddedType, + }), + Code: code, + Expiry: time.Hour, + AuthRequestID: authRequestID, + }, + }, w + }, }, - }, { - name: "button url with event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.URL}}" - testCode := "testcode" - expectContent := fmt.Sprintf("%s/ui/login/user/init?authRequestID=%s&code=%s&loginname=%s&orgID=%s&passwordset=%t&userID=%s", eventOrigin, "", testCode, preferredLoginName, orgID, false, userID) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, testCode) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().HumanInitCodeSent(gomock.Any(), orgID, userID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &user.HumanInitialCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), - }), - Code: code, - Expiry: time.Hour, - TriggeredAtOrigin: eventOrigin, - }, - }, w - }, - }, { - name: "button url without event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.URL}}" - testCode := "testcode" - expectContent := fmt.Sprintf("%s://%s:%d/ui/login/user/init?authRequestID=%s&code=%s&loginname=%s&orgID=%s&passwordset=%t&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, "", testCode, preferredLoginName, orgID, false, userID) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, testCode) - queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ - Domains: []*query.InstanceDomain{{ - Domain: instancePrimaryDomain, - IsPrimary: true, - }}, - }, nil) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().HumanInitCodeSent(gomock.Any(), orgID, userID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &user.HumanInitialCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), - }), - Code: code, - Expiry: time.Hour, - }, - }, w - }, - }, { - name: "button url without event trigger url with authRequestID", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.URL}}" - testCode := "testcode" - expectContent := fmt.Sprintf("%s://%s:%d/ui/login/user/init?authRequestID=%s&code=%s&loginname=%s&orgID=%s&passwordset=%t&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, authRequestID, testCode, preferredLoginName, orgID, false, userID) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, testCode) - queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ - Domains: []*query.InstanceDomain{{ - Domain: instancePrimaryDomain, - IsPrimary: true, - }}, - }, nil) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().HumanInitCodeSent(gomock.Any(), orgID, userID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &user.HumanInitialCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), - }), - Code: code, - Expiry: time.Hour, - AuthRequestID: authRequestID, - }, - }, w - }, - }} - // TODO: Why don't we have an url template on user.HumanInitialCodeAddedEvent? + } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) @@ -273,244 +182,141 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) { } func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) { - expectMailSubject := "Verify email" tests := []struct { name string test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, want) - }{{ - name: "asset url with event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.LogoURL}}" - expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, "testcode") - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().HumanEmailVerificationCodeSent(gomock.Any(), orgID, userID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &user.HumanEmailCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), + }{ + { + name: "with event trigger", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + _, code := cryptoValue(t, ctrl, "testcode") + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: eventOrigin, + URLTemplate: fmt.Sprintf("%s/ui/login/mail/verification?userID=%s&code={{.Code}}&orgID=%s&authRequestID=%s", + eventOrigin, userID, orgID, authRequestID), + Code: code, + CodeExpiry: time.Hour, + EventType: user.HumanEmailCodeAddedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.VerifyEmailMessageType, + UnverifiedNotificationChannel: true, + Args: &domain.NotificationArguments{AuthRequestID: authRequestID}, + AggregateID: "", + AggregateResourceOwner: "", + IsOTP: false, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, }), - Code: code, - Expiry: time.Hour, - URLTemplate: "", - CodeReturned: false, - TriggeredAtOrigin: eventOrigin, - }, - }, w + }, args{ + event: &user.HumanEmailCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanEmailCodeAddedType, + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + TriggeredAtOrigin: eventOrigin, + AuthRequestID: authRequestID, + }, + }, w + }, }, - }, { - name: "asset url without event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.LogoURL}}" - expectContent := fmt.Sprintf("%s://%s:%d%s/%s/%s", externalProtocol, instancePrimaryDomain, externalPort, assetsPath, policyID, logoURL) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, "testcode") - queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ - Domains: []*query.InstanceDomain{{ - Domain: instancePrimaryDomain, - IsPrimary: true, - }}, - }, nil) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().HumanEmailVerificationCodeSent(gomock.Any(), orgID, userID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &user.HumanEmailCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), + { + name: "without event trigger", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + _, code := cryptoValue(t, ctrl, "testcode") + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: fmt.Sprintf("%s://%s:%d", externalProtocol, instancePrimaryDomain, externalPort), + URLTemplate: fmt.Sprintf("%s://%s:%d/ui/login/mail/verification?userID=%s&code={{.Code}}&orgID=%s&authRequestID=%s", + externalProtocol, instancePrimaryDomain, externalPort, userID, orgID, authRequestID), + Code: code, + CodeExpiry: time.Hour, + EventType: user.HumanEmailCodeAddedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.VerifyEmailMessageType, + UnverifiedNotificationChannel: true, + Args: &domain.NotificationArguments{AuthRequestID: authRequestID}, + AggregateID: "", + AggregateResourceOwner: "", + IsOTP: false, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, }), - Code: code, - Expiry: time.Hour, - URLTemplate: "", - CodeReturned: false, - }, - }, w + }, args{ + event: &user.HumanEmailCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanEmailCodeAddedType, + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + AuthRequestID: authRequestID, + }, + }, w + }, }, - }, { - name: "button url with event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.URL}}" - testCode := "testcode" - expectContent := fmt.Sprintf("%s/ui/login/mail/verification?authRequestID=%s&code=%s&orgID=%s&userID=%s", eventOrigin, "", testCode, orgID, userID) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, testCode) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().HumanEmailVerificationCodeSent(gomock.Any(), orgID, userID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - SMSTokenCrypto: nil, - }, args{ - event: &user.HumanEmailCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), + { + name: "return code", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + w.noOperation = true + _, code := cryptoValue(t, ctrl, "testcode") + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).MockQuerier, }), - Code: code, - Expiry: time.Hour, - URLTemplate: "", - CodeReturned: false, - TriggeredAtOrigin: eventOrigin, - }, - }, w + }, args{ + event: &user.HumanEmailCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanEmailCodeAddedType, + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: true, + TriggeredAtOrigin: eventOrigin, + AuthRequestID: authRequestID, + }, + }, w + }, }, - }, { - name: "button url without event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.URL}}" - testCode := "testcode" - expectContent := fmt.Sprintf("%s://%s:%d/ui/login/mail/verification?authRequestID=%s&code=%s&orgID=%s&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, "", testCode, orgID, userID) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, testCode) - queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ - Domains: []*query.InstanceDomain{{ - Domain: instancePrimaryDomain, - IsPrimary: true, - }}, - }, nil) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().HumanEmailVerificationCodeSent(gomock.Any(), orgID, userID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &user.HumanEmailCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), - }), - Code: code, - Expiry: time.Hour, - URLTemplate: "", - CodeReturned: false, - }, - }, w - }, - }, { - name: "button url without event trigger url with authRequestID", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.URL}}" - testCode := "testcode" - expectContent := fmt.Sprintf("%s://%s:%d/ui/login/mail/verification?authRequestID=%s&code=%s&orgID=%s&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, authRequestID, testCode, orgID, userID) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, testCode) - queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ - Domains: []*query.InstanceDomain{{ - Domain: instancePrimaryDomain, - IsPrimary: true, - }}, - }, nil) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().HumanEmailVerificationCodeSent(gomock.Any(), orgID, userID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &user.HumanEmailCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), - }), - Code: code, - Expiry: time.Hour, - URLTemplate: "", - CodeReturned: false, - AuthRequestID: authRequestID, - }, - }, w - }, - }, { - name: "button url with url template and event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.URL}}" - urlTemplate := "https://my.custom.url/org/{{.OrgID}}/user/{{.UserID}}/verify/{{.Code}}" - testCode := "testcode" - expectContent := fmt.Sprintf("https://my.custom.url/org/%s/user/%s/verify/%s", orgID, userID, testCode) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, testCode) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().HumanEmailVerificationCodeSent(gomock.Any(), orgID, userID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - SMSTokenCrypto: nil, - }, args{ - event: &user.HumanEmailCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), - }), - Code: code, - Expiry: time.Hour, - URLTemplate: urlTemplate, - CodeReturned: false, - TriggeredAtOrigin: eventOrigin, - }, - }, w - }, - }} + } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) @@ -523,6 +329,10 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) { } else { assert.NoError(t, err) } + if w.noOperation { + assert.Nil(t, stmt.Execute) + return + } err = stmt.Execute(nil, "") if w.err != nil { w.err(t, err) @@ -534,280 +344,189 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) { } func Test_userNotifier_reducePasswordCodeAdded(t *testing.T) { - expectMailSubject := "Reset password" tests := []struct { name string test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, want) - }{{ - name: "asset url with event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.LogoURL}}" - expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, "testcode") - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().PasswordCodeSent(gomock.Any(), orgID, userID, &senders.CodeGeneratorInfo{}).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &user.HumanPasswordCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), + }{ + { + name: "with event trigger", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + _, code := cryptoValue(t, ctrl, "testcode") + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: eventOrigin, + URLTemplate: fmt.Sprintf("%s/ui/login/password/init?userID=%s&code={{.Code}}&orgID=%s&authRequestID=%s", + eventOrigin, userID, orgID, authRequestID), + Code: code, + CodeExpiry: time.Hour, + EventType: user.HumanPasswordCodeAddedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.PasswordResetMessageType, + UnverifiedNotificationChannel: true, + Args: &domain.NotificationArguments{AuthRequestID: authRequestID}, + AggregateID: "", + AggregateResourceOwner: "", + IsOTP: false, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, }), - Code: code, - Expiry: time.Hour, - URLTemplate: "", - CodeReturned: false, - TriggeredAtOrigin: eventOrigin, - }, - }, w + }, args{ + event: &user.HumanPasswordCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanPasswordCodeAddedType, + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + TriggeredAtOrigin: eventOrigin, + AuthRequestID: authRequestID, + }, + }, w + }, }, - }, { - name: "asset url without event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.LogoURL}}" - expectContent := fmt.Sprintf("%s://%s:%d%s/%s/%s", externalProtocol, instancePrimaryDomain, externalPort, assetsPath, policyID, logoURL) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, "testcode") - queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ - Domains: []*query.InstanceDomain{{ - Domain: instancePrimaryDomain, - IsPrimary: true, - }}, - }, nil) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().PasswordCodeSent(gomock.Any(), orgID, userID, &senders.CodeGeneratorInfo{}).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &user.HumanPasswordCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), + { + name: "asset url without event trigger url", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + _, code := cryptoValue(t, ctrl, "testcode") + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: fmt.Sprintf("%s://%s:%d", externalProtocol, instancePrimaryDomain, externalPort), + URLTemplate: fmt.Sprintf("%s://%s:%d/ui/login/password/init?userID=%s&code={{.Code}}&orgID=%s&authRequestID=%s", + externalProtocol, instancePrimaryDomain, externalPort, userID, orgID, authRequestID), + Code: code, + CodeExpiry: time.Hour, + EventType: user.HumanPasswordCodeAddedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.PasswordResetMessageType, + UnverifiedNotificationChannel: true, + Args: &domain.NotificationArguments{AuthRequestID: authRequestID}, + AggregateID: "", + AggregateResourceOwner: "", + IsOTP: false, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, }), - Code: code, - Expiry: time.Hour, - URLTemplate: "", - CodeReturned: false, - }, - }, w + }, args{ + event: &user.HumanPasswordCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanPasswordCodeAddedType, + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + AuthRequestID: authRequestID, + }, + }, w + }, }, - }, { - name: "button url with event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.URL}}" - testCode := "testcode" - expectContent := fmt.Sprintf("%s/ui/login/password/init?authRequestID=%s&code=%s&orgID=%s&userID=%s", eventOrigin, "", testCode, orgID, userID) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, testCode) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().PasswordCodeSent(gomock.Any(), orgID, userID, &senders.CodeGeneratorInfo{}).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - SMSTokenCrypto: nil, - }, args{ - event: &user.HumanPasswordCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), + { + name: "external code", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: eventOrigin, + URLTemplate: fmt.Sprintf("%s/ui/login/password/init?userID=%s&code={{.Code}}&orgID=%s&authRequestID=%s", + eventOrigin, userID, orgID, authRequestID), + Code: nil, + CodeExpiry: 0, + EventType: user.HumanPasswordCodeAddedType, + NotificationType: domain.NotificationTypeSms, + MessageType: domain.PasswordResetMessageType, + UnverifiedNotificationChannel: true, + Args: &domain.NotificationArguments{AuthRequestID: authRequestID}, + AggregateID: "", + AggregateResourceOwner: "", + IsOTP: false, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, }), - Code: code, - Expiry: time.Hour, - URLTemplate: "", - CodeReturned: false, - TriggeredAtOrigin: eventOrigin, - }, - }, w + }, args{ + event: &user.HumanPasswordCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanPasswordCodeAddedType, + }), + Code: nil, + Expiry: 0, + URLTemplate: "", + CodeReturned: false, + NotificationType: domain.NotificationTypeSms, + GeneratorID: smsProviderID, + TriggeredAtOrigin: eventOrigin, + AuthRequestID: authRequestID, + }, + }, w + }, }, - }, { - name: "button url without event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.URL}}" - testCode := "testcode" - expectContent := fmt.Sprintf("%s://%s:%d/ui/login/password/init?authRequestID=%s&code=%s&orgID=%s&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, "", testCode, orgID, userID) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, testCode) - queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ - Domains: []*query.InstanceDomain{{ - Domain: instancePrimaryDomain, - IsPrimary: true, - }}, - }, nil) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().PasswordCodeSent(gomock.Any(), orgID, userID, &senders.CodeGeneratorInfo{}).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &user.HumanPasswordCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), + { + name: "return code", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + w.noOperation = true + _, code := cryptoValue(t, ctrl, "testcode") + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).MockQuerier, }), - Code: code, - Expiry: time.Hour, - URLTemplate: "", - CodeReturned: false, - }, - }, w + }, args{ + event: &user.HumanPasswordCodeAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanPasswordCodeAddedType, + }), + Code: code, + Expiry: 1 * time.Hour, + URLTemplate: "", + CodeReturned: true, + NotificationType: domain.NotificationTypeSms, + GeneratorID: smsProviderID, + TriggeredAtOrigin: eventOrigin, + AuthRequestID: authRequestID, + }, + }, w + }, }, - }, { - name: "button url without event trigger url with authRequestID", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.URL}}" - testCode := "testcode" - expectContent := fmt.Sprintf("%s://%s:%d/ui/login/password/init?authRequestID=%s&code=%s&orgID=%s&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, authRequestID, testCode, orgID, userID) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, testCode) - queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ - Domains: []*query.InstanceDomain{{ - Domain: instancePrimaryDomain, - IsPrimary: true, - }}, - }, nil) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().PasswordCodeSent(gomock.Any(), orgID, userID, &senders.CodeGeneratorInfo{}).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &user.HumanPasswordCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), - }), - Code: code, - Expiry: time.Hour, - URLTemplate: "", - CodeReturned: false, - AuthRequestID: authRequestID, - }, - }, w - }, - }, { - name: "button url with url template and event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.URL}}" - urlTemplate := "https://my.custom.url/org/{{.OrgID}}/user/{{.UserID}}/verify/{{.Code}}" - testCode := "testcode" - expectContent := fmt.Sprintf("https://my.custom.url/org/%s/user/%s/verify/%s", orgID, userID, testCode) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, testCode) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().PasswordCodeSent(gomock.Any(), orgID, userID, &senders.CodeGeneratorInfo{}).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - SMSTokenCrypto: nil, - }, args{ - event: &user.HumanPasswordCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), - }), - Code: code, - Expiry: time.Hour, - URLTemplate: urlTemplate, - CodeReturned: false, - TriggeredAtOrigin: eventOrigin, - }, - }, w - }, - }, { - name: "external code", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.URL}}" - expectContent := "We received a password reset request. Please use the button below to reset your password. (Code ) If you didn't ask for this mail, please ignore it." - w.messageSMS = &messages.SMS{ - SenderPhoneNumber: "senderNumber", - RecipientPhoneNumber: lastPhone, - Content: expectContent, - } - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().PasswordCodeSent(gomock.Any(), orgID, userID, &senders.CodeGeneratorInfo{ID: smsProviderID, VerificationID: verificationID}).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - SMSTokenCrypto: nil, - }, args{ - event: &user.HumanPasswordCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), - }), - Code: nil, - Expiry: 0, - URLTemplate: "", - CodeReturned: false, - NotificationType: domain.NotificationTypeSms, - GeneratorID: smsProviderID, - TriggeredAtOrigin: eventOrigin, - }, - }, w - }, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -821,6 +540,10 @@ func Test_userNotifier_reducePasswordCodeAdded(t *testing.T) { } else { assert.NoError(t, err) } + if w.noOperation { + assert.Nil(t, stmt.Execute) + return + } err = stmt.Execute(nil, "") if w.err != nil { w.err(t, err) @@ -832,22 +555,30 @@ func Test_userNotifier_reducePasswordCodeAdded(t *testing.T) { } func Test_userNotifier_reduceDomainClaimed(t *testing.T) { - expectMailSubject := "Domain has been claimed" tests := []struct { name string test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, want) }{{ - name: "asset url with event trigger url", + name: "with event trigger", test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.LogoURL}}" - expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().UserDomainClaimedSent(gomock.Any(), orgID, userID).Return(nil) + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: eventOrigin, + URLTemplate: fmt.Sprintf("%s/ui/login/login?orgID=%s", + eventOrigin, orgID), + Code: nil, + CodeExpiry: 0, + EventType: user.UserDomainClaimedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.DomainClaimedMessageType, + UnverifiedNotificationChannel: true, + Args: &domain.NotificationArguments{TempUsername: "newUsername"}, + AggregateID: "", + AggregateResourceOwner: "", + IsOTP: false, + RequiresPreviousDomain: true, + }).Return(nil) return fields{ queries: queries, commands: commands, @@ -857,32 +588,44 @@ func Test_userNotifier_reduceDomainClaimed(t *testing.T) { }, args{ event: &user.DomainClaimedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, AggregateID: userID, ResourceOwner: sql.NullString{String: orgID}, CreationDate: time.Now().UTC(), + Typ: user.UserDomainClaimedType, }), TriggeredAtOrigin: eventOrigin, + UserName: "newUsername", }, }, w }, }, { - name: "asset url without event trigger url", + name: "without event trigger", test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.LogoURL}}" - expectContent := fmt.Sprintf("%s://%s:%d%s/%s/%s", externalProtocol, instancePrimaryDomain, externalPort, assetsPath, policyID, logoURL) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ Domains: []*query.InstanceDomain{{ Domain: instancePrimaryDomain, IsPrimary: true, }}, }, nil) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().UserDomainClaimedSent(gomock.Any(), orgID, userID).Return(nil) + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: fmt.Sprintf("%s://%s:%d", externalProtocol, instancePrimaryDomain, externalPort), + URLTemplate: fmt.Sprintf("%s://%s:%d/ui/login/login?orgID=%s", + externalProtocol, instancePrimaryDomain, externalPort, orgID), + Code: nil, + CodeExpiry: 0, + EventType: user.UserDomainClaimedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.DomainClaimedMessageType, + UnverifiedNotificationChannel: true, + Args: &domain.NotificationArguments{TempUsername: "newUsername"}, + AggregateID: "", + AggregateResourceOwner: "", + IsOTP: false, + RequiresPreviousDomain: true, + }).Return(nil) return fields{ queries: queries, commands: commands, @@ -892,10 +635,13 @@ func Test_userNotifier_reduceDomainClaimed(t *testing.T) { }, args{ event: &user.DomainClaimedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, AggregateID: userID, ResourceOwner: sql.NullString{String: orgID}, CreationDate: time.Now().UTC(), + Typ: user.UserDomainClaimedType, }), + UserName: "newUsername", }, }, w }, @@ -923,207 +669,138 @@ func Test_userNotifier_reduceDomainClaimed(t *testing.T) { } func Test_userNotifier_reducePasswordlessCodeRequested(t *testing.T) { - expectMailSubject := "Add Passwordless Login" tests := []struct { name string test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, want) - }{{ - name: "asset url with event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.LogoURL}}" - expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, "testcode") - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().HumanPasswordlessInitCodeSent(gomock.Any(), userID, orgID, codeID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &user.HumanPasswordlessInitCodeRequestedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), + }{ + { + name: "with event trigger", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + _, code := cryptoValue(t, ctrl, "testcode") + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: eventOrigin, + URLTemplate: fmt.Sprintf("%s/ui/login/login/passwordless/init?userID=%s&orgID=%s&codeID=%s&code={{.Code}}", eventOrigin, userID, orgID, codeID), + Code: code, + CodeExpiry: time.Hour, + EventType: user.HumanPasswordlessInitCodeAddedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.PasswordlessRegistrationMessageType, + UnverifiedNotificationChannel: false, + Args: &domain.NotificationArguments{CodeID: codeID}, + AggregateID: "", + AggregateResourceOwner: "", + IsOTP: false, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, }), - ID: codeID, - Code: code, - Expiry: time.Hour, - URLTemplate: "", - CodeReturned: false, - TriggeredAtOrigin: eventOrigin, - }, - }, w + }, args{ + event: &user.HumanPasswordlessInitCodeRequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanPasswordlessInitCodeAddedType, + }), + ID: codeID, + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, }, - }, { - name: "asset url without event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.LogoURL}}" - expectContent := fmt.Sprintf("%s://%s:%d%s/%s/%s", externalProtocol, instancePrimaryDomain, externalPort, assetsPath, policyID, logoURL) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, "testcode") - queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ - Domains: []*query.InstanceDomain{{ - Domain: instancePrimaryDomain, - IsPrimary: true, - }}, - }, nil) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().HumanPasswordlessInitCodeSent(gomock.Any(), userID, orgID, codeID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &user.HumanPasswordlessInitCodeRequestedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), + { + name: "without event trigger", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + _, code := cryptoValue(t, ctrl, "testCode") + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: fmt.Sprintf("%s://%s:%d", externalProtocol, instancePrimaryDomain, externalPort), + URLTemplate: fmt.Sprintf("%s://%s:%d/ui/login/login/passwordless/init?userID=%s&orgID=%s&codeID=%s&code={{.Code}}", externalProtocol, instancePrimaryDomain, externalPort, userID, orgID, codeID), + Code: code, + CodeExpiry: time.Hour, + EventType: user.HumanPasswordlessInitCodeAddedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.PasswordlessRegistrationMessageType, + UnverifiedNotificationChannel: false, + Args: &domain.NotificationArguments{CodeID: codeID}, + AggregateID: "", + AggregateResourceOwner: "", + IsOTP: false, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, }), - ID: codeID, - Code: code, - Expiry: time.Hour, - URLTemplate: "", - CodeReturned: false, - }, - }, w + }, args{ + event: &user.HumanPasswordlessInitCodeRequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanPasswordlessInitCodeAddedType, + }), + ID: codeID, + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + }, + }, w + }, }, - }, { - name: "button url with event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.URL}}" - testCode := "testcode" - codeAlg, code := cryptoValue(t, ctrl, testCode) - expectContent := fmt.Sprintf("%s/ui/login/login/passwordless/init?userID=%s&orgID=%s&codeID=%s&code=%s", eventOrigin, userID, orgID, codeID, testCode) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().HumanPasswordlessInitCodeSent(gomock.Any(), userID, orgID, codeID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - SMSTokenCrypto: nil, - }, args{ - event: &user.HumanPasswordlessInitCodeRequestedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), + { + name: "return code", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + w.noOperation = true + _, code := cryptoValue(t, ctrl, "testcode") + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).MockQuerier, }), - ID: codeID, - Code: code, - Expiry: time.Hour, - URLTemplate: "", - CodeReturned: false, - TriggeredAtOrigin: eventOrigin, - }, - }, w + }, args{ + event: &user.HumanPasswordlessInitCodeRequestedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanPasswordlessInitCodeAddedType, + }), + ID: codeID, + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: true, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, }, - }, { - name: "button url without event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.URL}}" - testCode := "testcode" - codeAlg, code := cryptoValue(t, ctrl, testCode) - expectContent := fmt.Sprintf("%s://%s:%d/ui/login/login/passwordless/init?userID=%s&orgID=%s&codeID=%s&code=%s", externalProtocol, instancePrimaryDomain, externalPort, userID, orgID, codeID, testCode) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ - Domains: []*query.InstanceDomain{{ - Domain: instancePrimaryDomain, - IsPrimary: true, - }}, - }, nil) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().HumanPasswordlessInitCodeSent(gomock.Any(), userID, orgID, codeID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &user.HumanPasswordlessInitCodeRequestedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), - }), - ID: codeID, - Code: code, - Expiry: time.Hour, - URLTemplate: "", - CodeReturned: false, - }, - }, w - }, - }, { - name: "button url with url template and event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.URL}}" - urlTemplate := "https://my.custom.url/org/{{.OrgID}}/user/{{.UserID}}/verify/{{.Code}}" - testCode := "testcode" - expectContent := fmt.Sprintf("https://my.custom.url/org/%s/user/%s/verify/%s", orgID, userID, testCode) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, testCode) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().HumanPasswordlessInitCodeSent(gomock.Any(), userID, orgID, codeID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - SMSTokenCrypto: nil, - }, args{ - event: &user.HumanPasswordlessInitCodeRequestedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), - }), - ID: codeID, - Code: code, - Expiry: time.Hour, - URLTemplate: urlTemplate, - CodeReturned: false, - TriggeredAtOrigin: eventOrigin, - }, - }, w - }, - }} + } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) @@ -1136,6 +813,10 @@ func Test_userNotifier_reducePasswordlessCodeRequested(t *testing.T) { } else { assert.NoError(t, err) } + if w.noOperation { + assert.Nil(t, stmt.Execute) + return + } err = stmt.Execute(nil, "") if w.err != nil { w.err(t, err) @@ -1147,80 +828,127 @@ func Test_userNotifier_reducePasswordlessCodeRequested(t *testing.T) { } func Test_userNotifier_reducePasswordChanged(t *testing.T) { - expectMailSubject := "Password of user has changed" tests := []struct { name string test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, want) - }{{ - name: "asset url with event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.LogoURL}}" - expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - queries.EXPECT().NotificationPolicyByOrg(gomock.Any(), gomock.Any(), orgID, gomock.Any()).Return(&query.NotificationPolicy{ - PasswordChange: true, - }, nil) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().PasswordChangeSent(gomock.Any(), orgID, userID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - }, args{ - event: &user.HumanPasswordChangedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), + }{ + { + name: "with event trigger", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + queries.EXPECT().NotificationPolicyByOrg(gomock.Any(), gomock.Any(), orgID, gomock.Any()).Return(&query.NotificationPolicy{ + PasswordChange: true, + }, nil) + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: eventOrigin, + URLTemplate: fmt.Sprintf("%s/ui/console?login_hint={{.PreferredLoginName}}", eventOrigin), + Code: nil, + CodeExpiry: 0, + EventType: user.HumanPasswordChangedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.PasswordChangeMessageType, + UnverifiedNotificationChannel: true, + Args: nil, + AggregateID: "", + AggregateResourceOwner: "", + IsOTP: false, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, }), - TriggeredAtOrigin: eventOrigin, - }, - }, w + }, args{ + event: &user.HumanPasswordChangedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanPasswordChangedType, + }), + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, }, - }, { - name: "asset url without event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.LogoURL}}" - expectContent := fmt.Sprintf("%s://%s:%d%s/%s/%s", externalProtocol, instancePrimaryDomain, externalPort, assetsPath, policyID, logoURL) - w.message = &messages.Email{ - Recipients: []string{lastEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - queries.EXPECT().NotificationPolicyByOrg(gomock.Any(), gomock.Any(), orgID, gomock.Any()).Return(&query.NotificationPolicy{ - PasswordChange: true, - }, nil) - queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ - Domains: []*query.InstanceDomain{{ - Domain: instancePrimaryDomain, - IsPrimary: true, - }}, - }, nil) - expectTemplateQueries(queries, givenTemplate) - commands.EXPECT().PasswordChangeSent(gomock.Any(), orgID, userID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - }, args{ - event: &user.HumanPasswordChangedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), + { + name: "without event trigger", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + queries.EXPECT().NotificationPolicyByOrg(gomock.Any(), gomock.Any(), orgID, gomock.Any()).Return(&query.NotificationPolicy{ + PasswordChange: true, + }, nil) + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: fmt.Sprintf("%s://%s:%d", externalProtocol, instancePrimaryDomain, externalPort), + URLTemplate: fmt.Sprintf("%s://%s:%d/ui/console?login_hint={{.PreferredLoginName}}", + externalProtocol, instancePrimaryDomain, externalPort), + Code: nil, + CodeExpiry: 0, + EventType: user.HumanPasswordChangedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.PasswordChangeMessageType, + UnverifiedNotificationChannel: true, + Args: nil, + AggregateID: "", + AggregateResourceOwner: "", + IsOTP: false, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, }), - }, - }, w + }, args{ + event: &user.HumanPasswordChangedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanPasswordChangedType, + }), + }, + }, w + }, + }, { + name: "no notification", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + queries.EXPECT().NotificationPolicyByOrg(gomock.Any(), gomock.Any(), orgID, gomock.Any()).Return(&query.NotificationPolicy{ + PasswordChange: false, + }, nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + }, args{ + event: &user.HumanPasswordChangedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanPasswordChangedType, + }), + }, + }, w + }, }, - }} + } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) @@ -1244,213 +972,235 @@ func Test_userNotifier_reducePasswordChanged(t *testing.T) { } func Test_userNotifier_reduceOTPEmailChallenged(t *testing.T) { - expectMailSubject := "Verify One-Time Password" tests := []struct { name string test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, want) - }{{ - name: "asset url with event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.LogoURL}}" - expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL) - w.message = &messages.Email{ - Recipients: []string{verifiedEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, "testcode") - expectTemplateQueries(queries, givenTemplate) - queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), userID, gomock.Any()).Return(&query.Session{}, nil) - commands.EXPECT().OTPEmailSent(gomock.Any(), userID, orgID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &session.OTPEmailChallengedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), - }), - Code: code, - Expiry: time.Hour, - URLTmpl: "", - ReturnCode: false, - TriggeredAtOrigin: eventOrigin, + }{ + { + name: "url with event trigger", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + _, code := cryptoValue(t, ctrl, "testCode") + queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), sessionID, gomock.Any()).Return(&query.Session{ + ID: sessionID, + ResourceOwner: instanceID, + UserFactor: query.SessionUserFactor{ + UserID: userID, + ResourceOwner: orgID, }, - }, w - }, - }, { - name: "asset url without event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.LogoURL}}" - expectContent := fmt.Sprintf("%s://%s:%d%s/%s/%s", externalProtocol, instancePrimaryDomain, externalPort, assetsPath, policyID, logoURL) - w.message = &messages.Email{ - Recipients: []string{verifiedEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, "testcode") - expectTemplateQueries(queries, givenTemplate) - queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), userID, gomock.Any()).Return(&query.Session{}, nil) - queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ - Domains: []*query.InstanceDomain{{ - Domain: instancePrimaryDomain, - IsPrimary: true, - }}, - }, nil) - commands.EXPECT().OTPEmailSent(gomock.Any(), userID, orgID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &session.OTPEmailChallengedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), - }), - Code: code, - Expiry: time.Hour, - URLTmpl: "", - ReturnCode: false, + }, nil) + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: eventOrigin, + URLTemplate: fmt.Sprintf("%s/otp/verify?loginName={{.LoginName}}&code={{.Code}}", eventOrigin), + Code: code, + CodeExpiry: time.Hour, + EventType: session.OTPEmailChallengedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.VerifyEmailOTPMessageType, + UnverifiedNotificationChannel: false, + Args: &domain.NotificationArguments{ + Domain: eventOriginDomain, + Expiry: 1 * time.Hour, + Origin: eventOrigin, + SessionID: sessionID, }, - }, w - }, - }, { - name: "button url with event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.URL}}" - testCode := "testcode" - expectContent := fmt.Sprintf("%s/otp/verify?loginName=%s&code=%s", eventOrigin, preferredLoginName, testCode) - w.message = &messages.Email{ - Recipients: []string{verifiedEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, testCode) - expectTemplateQueries(queries, givenTemplate) - queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), userID, gomock.Any()).Return(&query.Session{}, nil) - commands.EXPECT().OTPEmailSent(gomock.Any(), userID, orgID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - SMSTokenCrypto: nil, - }, args{ - event: &session.OTPEmailChallengedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), + AggregateID: sessionID, + AggregateResourceOwner: instanceID, + IsOTP: true, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, }), - Code: code, - Expiry: time.Hour, - URLTmpl: "", - ReturnCode: false, - TriggeredAtOrigin: eventOrigin, - }, - }, w + }, args{ + event: &session.OTPEmailChallengedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: sessionID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now().UTC(), + Typ: session.OTPEmailChallengedType, + }), + Code: code, + Expiry: time.Hour, + URLTmpl: "", + ReturnCode: false, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, }, - }, { - name: "button url without event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.URL}}" - testCode := "testcode" - expectContent := fmt.Sprintf("%s://%s:%d/otp/verify?loginName=%s&code=%s", externalProtocol, instancePrimaryDomain, externalPort, preferredLoginName, testCode) - w.message = &messages.Email{ - Recipients: []string{verifiedEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, testCode) - queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ - Domains: []*query.InstanceDomain{{ - Domain: instancePrimaryDomain, - IsPrimary: true, - }}, - }, nil) - expectTemplateQueries(queries, givenTemplate) - queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), userID, gomock.Any()).Return(&query.Session{}, nil) - commands.EXPECT().OTPEmailSent(gomock.Any(), userID, orgID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - }, args{ - event: &session.OTPEmailChallengedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), + { + name: "without event trigger", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + _, code := cryptoValue(t, ctrl, "testCode") + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), sessionID, gomock.Any()).Return(&query.Session{ + ID: sessionID, + ResourceOwner: instanceID, + UserFactor: query.SessionUserFactor{ + UserID: userID, + ResourceOwner: orgID, + }, + }, nil) + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: fmt.Sprintf("%s://%s:%d", externalProtocol, instancePrimaryDomain, externalPort), + URLTemplate: fmt.Sprintf("%s://%s:%d/otp/verify?loginName={{.LoginName}}&code={{.Code}}", externalProtocol, instancePrimaryDomain, externalPort), + Code: code, + CodeExpiry: time.Hour, + EventType: session.OTPEmailChallengedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.VerifyEmailOTPMessageType, + UnverifiedNotificationChannel: false, + Args: &domain.NotificationArguments{ + Domain: instancePrimaryDomain, + Expiry: 1 * time.Hour, + Origin: fmt.Sprintf("%s://%s:%d", externalProtocol, instancePrimaryDomain, externalPort), + SessionID: sessionID, + }, + AggregateID: sessionID, + AggregateResourceOwner: instanceID, + IsOTP: true, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, }), - Code: code, - Expiry: time.Hour, - ReturnCode: false, - }, - }, w + }, args{ + event: &session.OTPEmailChallengedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: sessionID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now().UTC(), + Typ: session.OTPEmailChallengedType, + }), + Code: code, + Expiry: time.Hour, + ReturnCode: false, + }, + }, w + }, }, - }, { - name: "button url with url template and event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.URL}}" - urlTemplate := "https://my.custom.url/user/{{.LoginName}}/verify" - testCode := "testcode" - expectContent := fmt.Sprintf("https://my.custom.url/user/%s/verify", preferredLoginName) - w.message = &messages.Email{ - Recipients: []string{verifiedEmail}, - Subject: expectMailSubject, - Content: expectContent, - } - codeAlg, code := cryptoValue(t, ctrl, testCode) - expectTemplateQueries(queries, givenTemplate) - queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), userID, gomock.Any()).Return(&query.Session{}, nil) - commands.EXPECT().OTPEmailSent(gomock.Any(), userID, orgID).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - userDataCrypto: codeAlg, - SMSTokenCrypto: nil, - }, args{ - event: &session.OTPEmailChallengedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), + { + name: "return code", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + w.noOperation = true + _, code := cryptoValue(t, ctrl, "testCode") + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).MockQuerier, }), - Code: code, - Expiry: time.Hour, - ReturnCode: false, - URLTmpl: urlTemplate, - TriggeredAtOrigin: eventOrigin, - }, - }, w + }, args{ + event: &session.OTPEmailChallengedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: sessionID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now().UTC(), + Typ: session.OTPEmailChallengedType, + }), + Code: code, + Expiry: time.Hour, + URLTmpl: "", + ReturnCode: true, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, }, - }} + { + name: "url template", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + _, code := cryptoValue(t, ctrl, "testCode") + queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), sessionID, gomock.Any()).Return(&query.Session{ + ID: sessionID, + ResourceOwner: instanceID, + UserFactor: query.SessionUserFactor{ + UserID: userID, + ResourceOwner: orgID, + }, + }, nil) + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: eventOrigin, + URLTemplate: "/verify-otp?sessionID={{.SessionID}}", + Code: code, + CodeExpiry: time.Hour, + EventType: session.OTPEmailChallengedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.VerifyEmailOTPMessageType, + UnverifiedNotificationChannel: false, + Args: &domain.NotificationArguments{ + Domain: eventOriginDomain, + Expiry: 1 * time.Hour, + Origin: eventOrigin, + SessionID: sessionID, + }, + AggregateID: sessionID, + AggregateResourceOwner: instanceID, + IsOTP: true, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + }, args{ + event: &session.OTPEmailChallengedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: sessionID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now().UTC(), + Typ: session.OTPEmailChallengedType, + }), + Code: code, + Expiry: time.Hour, + URLTmpl: "/verify-otp?sessionID={{.SessionID}}", + ReturnCode: false, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, + } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) queries := mock.NewMockQueries(ctrl) commands := mock.NewMockCommands(ctrl) f, a, w := tt.test(ctrl, queries, commands) - _, err := newUserNotifier(t, ctrl, queries, f, a, w).reduceSessionOTPEmailChallenged(a.event) + stmt, err := newUserNotifier(t, ctrl, queries, f, a, w).reduceSessionOTPEmailChallenged(a.event) + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + if w.noOperation { + assert.Nil(t, stmt.Execute) + return + } + err = stmt.Execute(nil, "") if w.err != nil { w.err(t, err) } else { @@ -1464,86 +1214,212 @@ func Test_userNotifier_reduceOTPSMSChallenged(t *testing.T) { tests := []struct { name string test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, want) - }{{ - name: "asset url with event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.LogoURL}}" - testCode := "" - expiry := 0 * time.Hour - expectContent := fmt.Sprintf(`%[1]s is your one-time password for %[2]s. Use it within the next %[3]s. -@%[2]s #%[1]s`, testCode, eventOriginDomain, expiry) - w.messageSMS = &messages.SMS{ - SenderPhoneNumber: "senderNumber", - RecipientPhoneNumber: verifiedPhone, - Content: expectContent, - } - expectTemplateQueriesSMS(queries, givenTemplate) - queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), userID, gomock.Any()).Return(&query.Session{}, nil) - commands.EXPECT().OTPSMSSent(gomock.Any(), userID, orgID, &senders.CodeGeneratorInfo{ID: smsProviderID, VerificationID: verificationID}).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - }, args{ - event: &session.OTPSMSChallengedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), - }), - Code: nil, - Expiry: expiry, - CodeReturned: false, - GeneratorID: smsProviderID, - TriggeredAtOrigin: eventOrigin, + }{ + { + name: "with event trigger", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + testCode := "testcode" + _, code := cryptoValue(t, ctrl, testCode) + queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), sessionID, gomock.Any()).Return(&query.Session{ + ID: sessionID, + ResourceOwner: instanceID, + UserFactor: query.SessionUserFactor{ + UserID: userID, + ResourceOwner: orgID, }, - }, w - }, - }, { - name: "asset url without event trigger url", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { - givenTemplate := "{{.LogoURL}}" - testCode := "" - expiry := 0 * time.Hour - expectContent := fmt.Sprintf(`%[1]s is your one-time password for %[2]s. Use it within the next %[3]s. -@%[2]s #%[1]s`, testCode, instancePrimaryDomain, expiry) - w.messageSMS = &messages.SMS{ - SenderPhoneNumber: "senderNumber", - RecipientPhoneNumber: verifiedPhone, - Content: expectContent, - } - expectTemplateQueriesSMS(queries, givenTemplate) - queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), userID, gomock.Any()).Return(&query.Session{}, nil) - queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ - Domains: []*query.InstanceDomain{{ - Domain: instancePrimaryDomain, - IsPrimary: true, - }}, - }, nil) - commands.EXPECT().OTPSMSSent(gomock.Any(), userID, orgID, &senders.CodeGeneratorInfo{ID: smsProviderID, VerificationID: verificationID}).Return(nil) - return fields{ - queries: queries, - commands: commands, - es: eventstore.NewEventstore(&eventstore.Config{ - Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, - }), - }, args{ - event: &session.OTPSMSChallengedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - AggregateID: userID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), - }), - Code: nil, - Expiry: expiry, - CodeReturned: false, - GeneratorID: smsProviderID, + }, nil) + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: eventOrigin, + URLTemplate: "", + Code: code, + CodeExpiry: time.Hour, + EventType: session.OTPSMSChallengedType, + NotificationType: domain.NotificationTypeSms, + MessageType: domain.VerifySMSOTPMessageType, + UnverifiedNotificationChannel: false, + Args: &domain.NotificationArguments{ + Domain: eventOriginDomain, + Expiry: 1 * time.Hour, + Origin: eventOrigin, + SessionID: sessionID, }, - }, w + AggregateID: sessionID, + AggregateResourceOwner: instanceID, + IsOTP: true, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + }, args{ + event: &session.OTPSMSChallengedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: sessionID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now().UTC(), + Typ: session.OTPSMSChallengedType, + }), + Code: code, + Expiry: 1 * time.Hour, + CodeReturned: false, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, + { + name: "without event trigger", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + testCode := "testcode" + _, code := cryptoValue(t, ctrl, testCode) + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), sessionID, gomock.Any()).Return(&query.Session{ + ID: sessionID, + ResourceOwner: instanceID, + UserFactor: query.SessionUserFactor{ + UserID: userID, + ResourceOwner: orgID, + }, + }, nil) + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: fmt.Sprintf("%s://%s:%d", externalProtocol, instancePrimaryDomain, externalPort), + URLTemplate: "", + Code: code, + CodeExpiry: time.Hour, + EventType: session.OTPSMSChallengedType, + NotificationType: domain.NotificationTypeSms, + MessageType: domain.VerifySMSOTPMessageType, + UnverifiedNotificationChannel: false, + Args: &domain.NotificationArguments{ + Domain: instancePrimaryDomain, + Expiry: 1 * time.Hour, + Origin: fmt.Sprintf("%s://%s:%d", externalProtocol, instancePrimaryDomain, externalPort), + SessionID: sessionID, + }, + AggregateID: sessionID, + AggregateResourceOwner: instanceID, + IsOTP: true, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + }, args{ + event: &session.OTPSMSChallengedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: sessionID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now().UTC(), + Typ: session.OTPSMSChallengedType, + }), + Code: code, + Expiry: 1 * time.Hour, + CodeReturned: false, + }, + }, w + }, + }, + { + name: "external code", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + queries.EXPECT().SessionByID(gomock.Any(), gomock.Any(), sessionID, gomock.Any()).Return(&query.Session{ + ID: sessionID, + ResourceOwner: instanceID, + UserFactor: query.SessionUserFactor{ + UserID: userID, + ResourceOwner: orgID, + }, + }, nil) + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: eventOrigin, + URLTemplate: "", + Code: nil, + CodeExpiry: 0, + EventType: session.OTPSMSChallengedType, + NotificationType: domain.NotificationTypeSms, + MessageType: domain.VerifySMSOTPMessageType, + UnverifiedNotificationChannel: false, + Args: &domain.NotificationArguments{ + Domain: eventOriginDomain, + Expiry: 0 * time.Hour, + Origin: eventOrigin, + SessionID: sessionID, + }, + AggregateID: sessionID, + AggregateResourceOwner: instanceID, + IsOTP: true, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + }, args{ + event: &session.OTPSMSChallengedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: sessionID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now().UTC(), + Typ: session.OTPSMSChallengedType, + }), + Code: nil, + Expiry: 0, + CodeReturned: false, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, + }, + { + name: "return code", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + w.noOperation = true + _, code := cryptoValue(t, ctrl, "testCode") + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).MockQuerier, + }), + }, args{ + event: &session.OTPSMSChallengedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: sessionID, + ResourceOwner: sql.NullString{String: instanceID}, + CreationDate: time.Now().UTC(), + Typ: session.OTPSMSChallengedType, + }), + Code: code, + Expiry: 1 * time.Hour, + CodeReturned: true, + TriggeredAtOrigin: eventOrigin, + }, + }, w + }, }, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -1551,7 +1427,281 @@ func Test_userNotifier_reduceOTPSMSChallenged(t *testing.T) { queries := mock.NewMockQueries(ctrl) commands := mock.NewMockCommands(ctrl) f, a, w := tt.test(ctrl, queries, commands) - _, err := newUserNotifier(t, ctrl, queries, f, a, w).reduceSessionOTPSMSChallenged(a.event) + stmt, err := newUserNotifier(t, ctrl, queries, f, a, w).reduceSessionOTPSMSChallenged(a.event) + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + if w.noOperation { + assert.Nil(t, stmt.Execute) + return + } + err = stmt.Execute(nil, "") + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func Test_userNotifier_reduceInviteCodeAdded(t *testing.T) { + tests := []struct { + name string + test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, want) + }{ + { + name: "with event trigger", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + _, code := cryptoValue(t, ctrl, "testcode") + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: eventOrigin, + URLTemplate: fmt.Sprintf("%s/ui/login/user/invite?userID=%s&loginname={{.LoginName}}&code={{.Code}}&orgID=%s&authRequestID=%s", eventOrigin, userID, orgID, authRequestID), + Code: code, + CodeExpiry: time.Hour, + EventType: user.HumanInviteCodeAddedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.InviteUserMessageType, + UnverifiedNotificationChannel: true, + Args: &domain.NotificationArguments{ + ApplicationName: "ZITADEL", + AuthRequestID: authRequestID, + }, + AggregateID: "", + AggregateResourceOwner: "", + IsOTP: false, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + }, args{ + event: &user.HumanInviteCodeAddedEvent{ + BaseEvent: eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanInviteCodeAddedType, + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + TriggeredAtOrigin: eventOrigin, + AuthRequestID: authRequestID, + }, + }, w + }, + }, + { + name: "without event trigger", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + _, code := cryptoValue(t, ctrl, "testCode") + queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{ + Domains: []*query.InstanceDomain{{ + Domain: instancePrimaryDomain, + IsPrimary: true, + }}, + }, nil) + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: fmt.Sprintf("%s://%s:%d", externalProtocol, instancePrimaryDomain, externalPort), + URLTemplate: fmt.Sprintf("%s://%s:%d/ui/login/user/invite?userID=%s&loginname={{.LoginName}}&code={{.Code}}&orgID=%s&authRequestID=%s", externalProtocol, instancePrimaryDomain, externalPort, userID, orgID, authRequestID), + Code: code, + CodeExpiry: time.Hour, + EventType: user.HumanInviteCodeAddedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.InviteUserMessageType, + UnverifiedNotificationChannel: true, + Args: &domain.NotificationArguments{ + ApplicationName: "ZITADEL", + AuthRequestID: authRequestID, + }, + AggregateID: "", + AggregateResourceOwner: "", + IsOTP: false, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + }, args{ + event: &user.HumanInviteCodeAddedEvent{ + BaseEvent: eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanInviteCodeAddedType, + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + AuthRequestID: authRequestID, + }, + }, w + }, + }, + { + name: "return code", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + w.noOperation = true + _, code := cryptoValue(t, ctrl, "testcode") + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).MockQuerier, + }), + }, args{ + event: &user.HumanInviteCodeAddedEvent{ + BaseEvent: eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanInviteCodeAddedType, + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: true, + TriggeredAtOrigin: eventOrigin, + AuthRequestID: authRequestID, + }, + }, w + }, + }, + { + name: "url template", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + _, code := cryptoValue(t, ctrl, "testcode") + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: eventOrigin, + URLTemplate: "/passwordless-init?userID={{.UserID}}", + Code: code, + CodeExpiry: time.Hour, + EventType: user.HumanInviteCodeAddedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.InviteUserMessageType, + UnverifiedNotificationChannel: true, + Args: &domain.NotificationArguments{ + ApplicationName: "ZITADEL", + AuthRequestID: authRequestID, + }, + AggregateID: "", + AggregateResourceOwner: "", + IsOTP: false, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + }, args{ + event: &user.HumanInviteCodeAddedEvent{ + BaseEvent: eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanInviteCodeAddedType, + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "/passwordless-init?userID={{.UserID}}", + CodeReturned: false, + TriggeredAtOrigin: eventOrigin, + AuthRequestID: authRequestID, + }, + }, w + }, + }, + { + name: "application name", + test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { + _, code := cryptoValue(t, ctrl, "testcode") + commands.EXPECT().RequestNotification(gomock.Any(), orgID, &command.NotificationRequest{ + UserID: userID, + UserResourceOwner: orgID, + TriggerOrigin: eventOrigin, + URLTemplate: fmt.Sprintf("%s/ui/login/user/invite?userID=%s&loginname={{.LoginName}}&code={{.Code}}&orgID=%s&authRequestID=%s", eventOrigin, userID, orgID, authRequestID), + Code: code, + CodeExpiry: time.Hour, + EventType: user.HumanInviteCodeAddedType, + NotificationType: domain.NotificationTypeEmail, + MessageType: domain.InviteUserMessageType, + UnverifiedNotificationChannel: true, + Args: &domain.NotificationArguments{ + ApplicationName: "APP", + AuthRequestID: authRequestID, + }, + AggregateID: "", + AggregateResourceOwner: "", + IsOTP: false, + RequiresPreviousDomain: false, + }).Return(nil) + return fields{ + queries: queries, + commands: commands, + es: eventstore.NewEventstore(&eventstore.Config{ + Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier, + }), + }, args{ + event: &user.HumanInviteCodeAddedEvent{ + BaseEvent: eventstore.BaseEventFromRepo(&repository.Event{ + InstanceID: instanceID, + AggregateID: userID, + ResourceOwner: sql.NullString{String: orgID}, + CreationDate: time.Now().UTC(), + Typ: user.HumanInviteCodeAddedType, + }), + Code: code, + Expiry: time.Hour, + URLTemplate: "", + CodeReturned: false, + TriggeredAtOrigin: eventOrigin, + AuthRequestID: authRequestID, + ApplicationName: "APP", + }, + }, w + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + queries := mock.NewMockQueries(ctrl) + commands := mock.NewMockCommands(ctrl) + f, a, w := tt.test(ctrl, queries, commands) + stmt, err := newUserNotifier(t, ctrl, queries, f, a, w).reduceInviteCodeAdded(a.event) + if w.err != nil { + w.err(t, err) + } else { + assert.NoError(t, err) + } + if w.noOperation { + assert.Nil(t, stmt.Execute) + return + } + err = stmt.Execute(nil, "") if w.err != nil { w.err(t, err) } else { @@ -1568,32 +1718,36 @@ type fields struct { userDataCrypto crypto.EncryptionAlgorithm SMSTokenCrypto crypto.EncryptionAlgorithm } +type fieldsWorker struct { + queries *mock.MockQueries + commands *mock.MockCommands + es *eventstore.Eventstore + userDataCrypto crypto.EncryptionAlgorithm + SMSTokenCrypto crypto.EncryptionAlgorithm + now nowFunc + backOff func(current time.Duration) time.Duration + maxAttempts uint8 +} type args struct { event eventstore.Event } +type argsWorker struct { + event eventstore.Event +} type want struct { + noOperation bool + err assert.ErrorAssertionFunc +} +type wantWorker struct { message *messages.Email messageSMS *messages.SMS + sendError error err assert.ErrorAssertionFunc } func newUserNotifier(t *testing.T, ctrl *gomock.Controller, queries *mock.MockQueries, f fields, a args, w want) *userNotifier { queries.EXPECT().NotificationProviderByIDAndType(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&query.DebugNotificationProvider{}, nil) smtpAlg, _ := cryptoValue(t, ctrl, "smtppw") - channel := channel_mock.NewMockNotificationChannel(ctrl) - if w.err == nil { - if w.message != nil { - w.message.TriggeringEvent = a.event - channel.EXPECT().HandleMessage(w.message).Return(nil) - } - if w.messageSMS != nil { - w.messageSMS.TriggeringEvent = a.event - channel.EXPECT().HandleMessage(w.messageSMS).DoAndReturn(func(message *messages.SMS) error { - message.VerificationID = gu.Ptr(verificationID) - return nil - }) - } - } return &userNotifier{ commands: f.commands, queries: NewNotificationQueries( @@ -1608,63 +1762,30 @@ func newUserNotifier(t *testing.T, ctrl *gomock.Controller, queries *mock.MockQu f.SMSTokenCrypto, ), otpEmailTmpl: defaultOTPEmailTemplate, - channels: &channels{ - Chain: *senders.ChainChannels(channel), - EmailConfig: &email.Config{ - ProviderConfig: &email.Provider{ - ID: "emailProviderID", - Description: "description", - }, - SMTPConfig: &smtp.Config{ - SMTP: smtp.SMTP{ - Host: "host", - User: "user", - Password: "password", - }, - Tls: true, - From: "from", - FromName: "fromName", - ReplyToAddress: "replyToAddress", - }, - WebhookConfig: nil, - }, - SMSConfig: &sms.Config{ - ProviderConfig: &sms.Provider{ - ID: "smsProviderID", - Description: "description", - }, - TwilioConfig: &twilio.Config{ - SID: "sid", - Token: "token", - SenderNumber: "senderNumber", - VerifyServiceSID: "verifyServiceSID", - }, - }, - }, } } -var _ types.ChannelChains = (*channels)(nil) +var _ types.ChannelChains = (*notificationChannels)(nil) -type channels struct { +type notificationChannels struct { senders.Chain EmailConfig *email.Config SMSConfig *sms.Config } -func (c *channels) Email(context.Context) (*senders.Chain, *email.Config, error) { +func (c *notificationChannels) Email(context.Context) (*senders.Chain, *email.Config, error) { return &c.Chain, c.EmailConfig, nil } -func (c *channels) SMS(context.Context) (*senders.Chain, *sms.Config, error) { +func (c *notificationChannels) SMS(context.Context) (*senders.Chain, *sms.Config, error) { return &c.Chain, c.SMSConfig, nil } -func (c *channels) Webhook(context.Context, webhook.Config) (*senders.Chain, error) { +func (c *notificationChannels) Webhook(context.Context, webhook.Config) (*senders.Chain, error) { return &c.Chain, nil } -func (c *channels) SecurityTokenEvent(context.Context, set.Config) (*senders.Chain, error) { +func (c *notificationChannels) SecurityTokenEvent(context.Context, set.Config) (*senders.Chain, error) { return &c.Chain, nil } @@ -1679,6 +1800,11 @@ func expectTemplateQueries(queries *mock.MockQueries, template string) { }, }, nil) queries.EXPECT().MailTemplateByOrg(gomock.Any(), gomock.Any(), gomock.Any()).Return(&query.MailTemplate{Template: []byte(template)}, nil) + queries.EXPECT().GetDefaultLanguage(gomock.Any()).Return(language.English) + queries.EXPECT().CustomTextListByTemplate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return(&query.CustomTexts{}, nil) +} + +func expectTemplateWithNotifyUserQueries(queries *mock.MockQueries, template string) { queries.EXPECT().GetNotifyUserByID(gomock.Any(), gomock.Any(), gomock.Any()).Return(&query.NotifyUser{ ID: userID, ResourceOwner: orgID, @@ -1688,11 +1814,19 @@ func expectTemplateQueries(queries *mock.MockQueries, template string) { LastPhone: lastPhone, VerifiedPhone: verifiedPhone, }, nil) - queries.EXPECT().GetDefaultLanguage(gomock.Any()).Return(language.English) - queries.EXPECT().CustomTextListByTemplate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return(&query.CustomTexts{}, nil) + expectTemplateQueries(queries, template) } -func expectTemplateQueriesSMS(queries *mock.MockQueries, template string) { +func expectTemplateWithNotifyUserQueriesSMS(queries *mock.MockQueries) { + queries.EXPECT().GetNotifyUserByID(gomock.Any(), gomock.Any(), gomock.Any()).Return(&query.NotifyUser{ + ID: userID, + ResourceOwner: orgID, + LastEmail: lastEmail, + VerifiedEmail: verifiedEmail, + PreferredLoginName: preferredLoginName, + LastPhone: lastPhone, + VerifiedPhone: verifiedPhone, + }, nil) queries.EXPECT().GetInstanceRestrictions(gomock.Any()).Return(query.Restrictions{ AllowedLanguages: []language.Tag{language.English}, }, nil) @@ -1702,15 +1836,6 @@ func expectTemplateQueriesSMS(queries *mock.MockQueries, template string) { LogoURL: logoURL, }, }, nil) - queries.EXPECT().GetNotifyUserByID(gomock.Any(), gomock.Any(), gomock.Any()).Return(&query.NotifyUser{ - ID: userID, - ResourceOwner: orgID, - LastEmail: lastEmail, - VerifiedEmail: verifiedEmail, - PreferredLoginName: preferredLoginName, - LastPhone: lastPhone, - VerifiedPhone: verifiedPhone, - }, nil) queries.EXPECT().GetDefaultLanguage(gomock.Any()).Return(language.English) queries.EXPECT().CustomTextListByTemplate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return(&query.CustomTexts{}, nil) } diff --git a/internal/notification/projections.go b/internal/notification/projections.go index 2be95f1490..38e1f1c347 100644 --- a/internal/notification/projections.go +++ b/internal/notification/projections.go @@ -6,6 +6,7 @@ import ( "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/crypto" + "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/notification/handlers" @@ -14,11 +15,15 @@ import ( "github.com/zitadel/zitadel/internal/query/projection" ) -var projections []*handler.Handler +var ( + projections []*handler.Handler + worker *handlers.NotificationWorker +) func Register( ctx context.Context, userHandlerCustomConfig, quotaHandlerCustomConfig, telemetryHandlerCustomConfig, backChannelLogoutHandlerCustomConfig projection.CustomConfig, + notificationWorkerConfig handlers.WorkerConfig, telemetryCfg handlers.TelemetryPusherConfig, externalDomain string, externalPort uint16, @@ -29,10 +34,11 @@ func Register( otpEmailTmpl, fileSystemPath string, userEncryption, smtpEncryption, smsEncryption, keysEncryptionAlg crypto.EncryptionAlgorithm, tokenLifetime time.Duration, + client *database.DB, ) { q := handlers.NewNotificationQueries(queries, es, externalDomain, externalPort, externalSecure, fileSystemPath, userEncryption, smtpEncryption, smsEncryption) c := newChannels(q) - projections = append(projections, handlers.NewUserNotifier(ctx, projection.ApplyCustomConfig(userHandlerCustomConfig), commands, q, c, otpEmailTmpl)) + projections = append(projections, handlers.NewUserNotifier(ctx, projection.ApplyCustomConfig(userHandlerCustomConfig), commands, q, c, otpEmailTmpl, notificationWorkerConfig.LegacyEnabled)) projections = append(projections, handlers.NewQuotaNotifier(ctx, projection.ApplyCustomConfig(quotaHandlerCustomConfig), commands, q, c)) projections = append(projections, handlers.NewBackChannelLogoutNotifier( ctx, @@ -47,12 +53,14 @@ func Register( if telemetryCfg.Enabled { projections = append(projections, handlers.NewTelemetryPusher(ctx, telemetryCfg, projection.ApplyCustomConfig(telemetryHandlerCustomConfig), commands, q, c)) } + worker = handlers.NewNotificationWorker(notificationWorkerConfig, commands, q, es, client, c) } func Start(ctx context.Context) { for _, projection := range projections { projection.Start(ctx) } + worker.Start(ctx) } func ProjectInstance(ctx context.Context) error { diff --git a/internal/notification/static/i18n/ko.yaml b/internal/notification/static/i18n/ko.yaml new file mode 100644 index 0000000000..6bc160e3ec --- /dev/null +++ b/internal/notification/static/i18n/ko.yaml @@ -0,0 +1,68 @@ +InitCode: + Title: ์‚ฌ์šฉ์ž ์ดˆ๊ธฐํ™” + PreHeader: ์‚ฌ์šฉ์ž ์ดˆ๊ธฐํ™” + Subject: ์‚ฌ์šฉ์ž ์ดˆ๊ธฐํ™” + Greeting: ์•ˆ๋…•ํ•˜์„ธ์š”, {{.DisplayName}}๋‹˜, + Text: ์‚ฌ์šฉ์ž๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋กœ๊ทธ์ธํ•˜๋ ค๋ฉด ์‚ฌ์šฉ์ž ์ด๋ฆ„ {{.PreferredLoginName}}์„ ์‚ฌ์šฉํ•˜์„ธ์š”. ์ดˆ๊ธฐํ™” ํ”„๋กœ์„ธ์Šค๋ฅผ ์™„๋ฃŒํ•˜๋ ค๋ฉด ์•„๋ž˜ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”. (์ฝ”๋“œ {{.Code}}) ์ด ๋ฉ”์ผ์„ ์š”์ฒญํ•˜์ง€ ์•Š์œผ์…จ๋‹ค๋ฉด ๋ฌด์‹œํ•˜์…”๋„ ๋ฉ๋‹ˆ๋‹ค. + ButtonText: ์ดˆ๊ธฐํ™” ์™„๋ฃŒ +PasswordReset: + Title: ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • + PreHeader: ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • + Subject: ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • + Greeting: ์•ˆ๋…•ํ•˜์„ธ์š”, {{.DisplayName}}๋‹˜, + Text: ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ์š”์ฒญ์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค. ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์žฌ์„ค์ •ํ•˜๋ ค๋ฉด ์•„๋ž˜ ๋ฒ„ํŠผ์„ ์‚ฌ์šฉํ•˜์„ธ์š”. (์ฝ”๋“œ {{.Code}}) ์ด ๋ฉ”์ผ์„ ์š”์ฒญํ•˜์ง€ ์•Š์œผ์…จ๋‹ค๋ฉด ๋ฌด์‹œํ•˜์…”๋„ ๋ฉ๋‹ˆ๋‹ค. + ButtonText: ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • +VerifyEmail: + Title: ์ด๋ฉ”์ผ ์ธ์ฆ + PreHeader: ์ด๋ฉ”์ผ ์ธ์ฆ + Subject: ์ด๋ฉ”์ผ ์ธ์ฆ + Greeting: ์•ˆ๋…•ํ•˜์„ธ์š”, {{.DisplayName}}๋‹˜, + Text: ์ƒˆ ์ด๋ฉ”์ผ์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฉ”์ผ์„ ์ธ์ฆํ•˜๋ ค๋ฉด ์•„๋ž˜ ๋ฒ„ํŠผ์„ ์‚ฌ์šฉํ•˜์„ธ์š”. (์ฝ”๋“œ {{.Code}}) ์ƒˆ๋กœ์šด ์ด๋ฉ”์ผ์„ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์œผ์…จ๋‹ค๋ฉด ์ด ๋ฉ”์ผ์„ ๋ฌด์‹œํ•˜์„ธ์š”. + ButtonText: ์ด๋ฉ”์ผ ์ธ์ฆ +VerifyPhone: + Title: ์ „ํ™”๋ฒˆํ˜ธ ์ธ์ฆ + PreHeader: ์ „ํ™”๋ฒˆํ˜ธ ์ธ์ฆ + Subject: ์ „ํ™”๋ฒˆํ˜ธ ์ธ์ฆ + Greeting: ์•ˆ๋…•ํ•˜์„ธ์š”, {{.DisplayName}}๋‹˜, + Text: ์ƒˆ ์ „ํ™”๋ฒˆํ˜ธ๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆํ•˜์„ธ์š” {{.Code}} + ButtonText: ์ „ํ™”๋ฒˆํ˜ธ ์ธ์ฆ +VerifyEmailOTP: + Title: ์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ธ์ฆ + PreHeader: ์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ธ์ฆ + Subject: ์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ธ์ฆ + Greeting: ์•ˆ๋…•ํ•˜์„ธ์š”, {{.DisplayName}}๋‹˜, + Text: ์ธ์ฆํ•˜๋ ค๋ฉด ๋‹ค์Œ ์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ {{.OTP}}๋ฅผ 5๋ถ„ ์ด๋‚ด์— ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ "์ธ์ฆ" ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”. + ButtonText: ์ธ์ฆ +VerifySMSOTP: + Text: >- + {{.OTP}}๋Š” {{ .Domain }}์˜ ์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ {{.Expiry}} ์ด๋‚ด์— ์‚ฌ์šฉํ•˜์„ธ์š”. + + @{{.Domain}} #{{.OTP}} +DomainClaimed: + Title: ์กฐ์ง์—์„œ ๋„๋ฉ”์ธ์„ ์†Œ์œ ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + PreHeader: ์ด๋ฉ”์ผ / ์‚ฌ์šฉ์ž ์ด๋ฆ„ ๋ณ€๊ฒฝ + Subject: ์กฐ์ง์—์„œ ๋„๋ฉ”์ธ์„ ์†Œ์œ ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + Greeting: ์•ˆ๋…•ํ•˜์„ธ์š”, {{.DisplayName}}๋‹˜, + Text: ๋„๋ฉ”์ธ {{.Domain}} ์€(๋Š”) ์กฐ์ง์—์„œ ์†Œ์œ ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ์‚ฌ์šฉ์ž์ธ {{.Username}}์€ ์ด ์กฐ์ง์˜ ๊ตฌ์„ฑ์›์ด ์•„๋‹™๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋กœ๊ทธ์ธํ•  ๋•Œ ์ด๋ฉ”์ผ์„ ๋ณ€๊ฒฝํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋กœ๊ทธ์ธ์šฉ์œผ๋กœ ์ž„์‹œ ์‚ฌ์šฉ์ž ์ด๋ฆ„ ({{.TempUsername}})์„ ์ƒ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. + ButtonText: ๋กœ๊ทธ์ธ +PasswordlessRegistration: + Title: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ ์ถ”๊ฐ€ + PreHeader: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ ์ถ”๊ฐ€ + Subject: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ ์ถ”๊ฐ€ + Greeting: ์•ˆ๋…•ํ•˜์„ธ์š”, {{.DisplayName}}๋‹˜, + Text: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ์„ ์œ„ํ•œ ํ† ํฐ ์ถ”๊ฐ€ ์š”์ฒญ์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค. ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ์„ ์œ„ํ•ด ํ† ํฐ ๋˜๋Š” ์žฅ์น˜๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด ์•„๋ž˜ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”. + ButtonText: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ ์ถ”๊ฐ€ +PasswordChange: + Title: ์‚ฌ์šฉ์ž ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ๋จ + PreHeader: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ + Subject: ์‚ฌ์šฉ์ž ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ๋จ + Greeting: ์•ˆ๋…•ํ•˜์„ธ์š”, {{.DisplayName}}๋‹˜, + Text: ์‚ฌ์šฉ์ž์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋ณธ์ธ์ด ์ง์ ‘ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์œผ์…จ๋‹ค๋ฉด ์ฆ‰์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์žฌ์„ค์ •ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. + ButtonText: ๋กœ๊ทธ์ธ +InviteUser: + Title: "{{.ApplicationName}} ์ดˆ๋Œ€" + PreHeader: "{{.ApplicationName}} ์ดˆ๋Œ€" + Subject: "{{.ApplicationName}} ์ดˆ๋Œ€" + Greeting: ์•ˆ๋…•ํ•˜์„ธ์š”, {{.DisplayName}}๋‹˜, + Text: "{{.ApplicationName}}์— ์ดˆ๋Œ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ดˆ๋Œ€ ํ”„๋กœ์„ธ์Šค๋ฅผ ์™„๋ฃŒํ•˜๋ ค๋ฉด ์•„๋ž˜ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”. ์ด ๋ฉ”์ผ์„ ์š”์ฒญํ•˜์ง€ ์•Š์œผ์…จ๋‹ค๋ฉด ๋ฌด์‹œํ•˜์…”๋„ ๋ฉ๋‹ˆ๋‹ค." + ButtonText: ์ดˆ๋Œ€ ์ˆ˜๋ฝ diff --git a/internal/notification/types/notification.go b/internal/notification/types/notification.go index 61c4cf70de..db791851bc 100644 --- a/internal/notification/types/notification.go +++ b/internal/notification/types/notification.go @@ -3,8 +3,10 @@ package types import ( "context" "html" + "strings" "github.com/zitadel/zitadel/internal/database" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/i18n" "github.com/zitadel/zitadel/internal/notification/channels/email" @@ -40,13 +42,17 @@ func SendEmail( triggeringEvent eventstore.Event, ) Notify { return func( - url string, + urlTmpl string, args map[string]interface{}, messageType string, allowUnverifiedNotificationChannel bool, ) error { args = mapNotifyUserToArgs(user, args) sanitizeArgsForHTML(args) + url, err := urlFromTemplate(urlTmpl, args) + if err != nil { + return err + } data := GetTemplateData(ctx, translator, args, url, messageType, user.PreferredLanguage.String(), colors) template, err := templates.GetParsedTemplate(mailhtml, data) if err != nil { @@ -82,6 +88,14 @@ func sanitizeArgsForHTML(args map[string]any) { } } +func urlFromTemplate(urlTmpl string, args map[string]interface{}) (string, error) { + var buf strings.Builder + if err := domain.RenderURLTemplate(&buf, urlTmpl, args); err != nil { + return "", err + } + return buf.String(), nil +} + func SendSMS( ctx context.Context, channels ChannelChains, @@ -92,12 +106,16 @@ func SendSMS( generatorInfo *senders.CodeGeneratorInfo, ) Notify { return func( - url string, + urlTmpl string, args map[string]interface{}, messageType string, allowUnverifiedNotificationChannel bool, ) error { args = mapNotifyUserToArgs(user, args) + url, err := urlFromTemplate(urlTmpl, args) + if err != nil { + return err + } data := GetTemplateData(ctx, translator, args, url, messageType, user.PreferredLanguage.String(), colors) return generateSms( ctx, diff --git a/internal/notification/types/user_email.go b/internal/notification/types/user_email.go index 210ca14cf8..985fe81391 100644 --- a/internal/notification/types/user_email.go +++ b/internal/notification/types/user_email.go @@ -74,6 +74,8 @@ func mapNotifyUserToArgs(user *query.NotifyUser, args map[string]interface{}) ma if args == nil { args = make(map[string]interface{}) } + args["UserID"] = user.ID + args["OrgID"] = user.ResourceOwner args["UserName"] = user.Username args["FirstName"] = user.FirstName args["LastName"] = user.LastName @@ -84,6 +86,7 @@ func mapNotifyUserToArgs(user *query.NotifyUser, args map[string]interface{}) ma args["LastPhone"] = user.LastPhone args["VerifiedPhone"] = user.VerifiedPhone args["PreferredLoginName"] = user.PreferredLoginName + args["LoginName"] = user.PreferredLoginName // some endpoint promoted LoginName instead of PreferredLoginName args["LoginNames"] = user.LoginNames args["ChangeDate"] = user.ChangeDate args["CreationDate"] = user.CreationDate diff --git a/internal/query/cache.go b/internal/query/cache.go index 55f7bb3db6..7c60eca702 100644 --- a/internal/query/cache.go +++ b/internal/query/cache.go @@ -2,7 +2,9 @@ package query import ( "context" + "time" + "github.com/hashicorp/golang-lru/v2/expirable" "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/cache" @@ -12,15 +14,31 @@ import ( type Caches struct { instance cache.Cache[instanceIndex, string, *authzInstance] + org cache.Cache[orgIndex, string, *Org] + + activeInstances *expirable.LRU[string, bool] } -func startCaches(background context.Context, connectors connector.Connectors) (_ *Caches, err error) { +type ActiveInstanceConfig struct { + MaxEntries int + TTL time.Duration +} + +func startCaches(background context.Context, connectors connector.Connectors, instanceConfig ActiveInstanceConfig) (_ *Caches, err error) { caches := new(Caches) caches.instance, err = connector.StartCache[instanceIndex, string, *authzInstance](background, instanceIndexValues(), cache.PurposeAuthzInstance, connectors.Config.Instance, connectors) if err != nil { return nil, err } + caches.org, err = connector.StartCache[orgIndex, string, *Org](background, orgIndexValues(), cache.PurposeOrganization, connectors.Config.Organization, connectors) + if err != nil { + return nil, err + } + + caches.activeInstances = expirable.NewLRU[string, bool](instanceConfig.MaxEntries, nil, instanceConfig.TTL) + caches.registerInstanceInvalidation() + caches.registerOrgInvalidation() return caches, nil } diff --git a/internal/query/execution.go b/internal/query/execution.go index 5ce5e36a94..b98c680f57 100644 --- a/internal/query/execution.go +++ b/internal/query/execution.go @@ -11,6 +11,7 @@ import ( sq "github.com/Masterminds/squirrel" "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query/projection" @@ -175,6 +176,11 @@ func (q *Queries) TargetsByExecutionID(ctx context.Context, ids []string) (execu instanceID, database.TextArray[string](ids), ) + for i := range execution { + if err := execution[i].decryptSigningKey(q.targetEncryptionAlgorithm); err != nil { + return nil, err + } + } return execution, err } @@ -205,6 +211,11 @@ func (q *Queries) TargetsByExecutionIDs(ctx context.Context, ids1, ids2 []string database.TextArray[string](ids1), database.TextArray[string](ids2), ) + for i := range execution { + if err := execution[i].decryptSigningKey(q.targetEncryptionAlgorithm); err != nil { + return nil, err + } + } return execution, err } @@ -352,6 +363,8 @@ type ExecutionTarget struct { Endpoint string Timeout time.Duration InterruptOnError bool + signingKey *crypto.CryptoValue + SigningKey string } func (e *ExecutionTarget) GetExecutionID() string { @@ -372,6 +385,21 @@ func (e *ExecutionTarget) GetTargetType() domain.TargetType { func (e *ExecutionTarget) GetTimeout() time.Duration { return e.Timeout } +func (e *ExecutionTarget) GetSigningKey() string { + return e.SigningKey +} + +func (t *ExecutionTarget) decryptSigningKey(alg crypto.EncryptionAlgorithm) error { + if t.signingKey == nil { + return nil + } + keyValue, err := crypto.DecryptString(t.signingKey, alg) + if err != nil { + return zerrors.ThrowInternal(err, "QUERY-bxevy3YXwy", "Errors.Internal") + } + t.SigningKey = keyValue + return nil +} func scanExecutionTargets(rows *sql.Rows) ([]*ExecutionTarget, error) { targets := make([]*ExecutionTarget, 0) @@ -386,6 +414,7 @@ func scanExecutionTargets(rows *sql.Rows) ([]*ExecutionTarget, error) { endpoint = &sql.NullString{} timeout = &sql.NullInt64{} interruptOnError = &sql.NullBool{} + signingKey = &crypto.CryptoValue{} ) err := rows.Scan( @@ -396,6 +425,7 @@ func scanExecutionTargets(rows *sql.Rows) ([]*ExecutionTarget, error) { endpoint, timeout, interruptOnError, + signingKey, ) if err != nil { @@ -409,6 +439,7 @@ func scanExecutionTargets(rows *sql.Rows) ([]*ExecutionTarget, error) { target.Endpoint = endpoint.String target.Timeout = time.Duration(timeout.Int64) target.InterruptOnError = interruptOnError.Bool + target.signingKey = signingKey targets = append(targets, target) } diff --git a/internal/query/instance.go b/internal/query/instance.go index 549c05a233..d7d66b1607 100644 --- a/internal/query/instance.go +++ b/internal/query/instance.go @@ -143,6 +143,10 @@ func (q *InstanceSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder return query } +func (q *Queries) ActiveInstances() []string { + return q.caches.activeInstances.Keys() +} + func (q *Queries) SearchInstances(ctx context.Context, queries *InstanceSearchQueries) (instances *Instances, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -198,10 +202,13 @@ var ( ) func (q *Queries) InstanceByHost(ctx context.Context, instanceHost, publicHost string) (_ authz.Instance, err error) { + var instance *authzInstance ctx, span := tracing.NewSpan(ctx) defer func() { if err != nil { err = fmt.Errorf("unable to get instance by host: instanceHost %s, publicHost %s: %w", instanceHost, publicHost, err) + } else { + q.caches.activeInstances.Add(instance.ID, true) } span.EndWithError(err) }() @@ -225,6 +232,12 @@ func (q *Queries) InstanceByHost(ctx context.Context, instanceHost, publicHost s func (q *Queries) InstanceByID(ctx context.Context, id string) (_ authz.Instance, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() + defer func() { + if err != nil { + return + } + q.caches.activeInstances.Add(id, true) + }() instance, ok := q.caches.instance.Get(ctx, instanceIndexByID, id) if ok { @@ -522,9 +535,9 @@ func (i *authzInstance) Keys(index instanceIndex) []string { return []string{i.ID} case instanceIndexByHost: return i.ExternalDomains - default: - return nil + case instanceIndexUnspecified: } + return nil } func scanAuthzInstance() (*authzInstance, func(row *sql.Row) error) { diff --git a/internal/query/instance_by_domain.sql b/internal/query/instance_by_domain.sql index 2f3fcb3518..60896027c4 100644 --- a/internal/query/instance_by_domain.sql +++ b/internal/query/instance_by_domain.sql @@ -1,6 +1,8 @@ with domain as ( - select instance_id from projections.instance_domains - where domain = $1 + SELECT instance_id FROM eventstore.fields + WHERE object_type = 'instance_domain' + AND object_id = $1 + AND field_name = 'domain' ), instance_features as ( select i.* from domain d diff --git a/internal/query/org.go b/internal/query/org.go index 1c20255171..e5bfc5140f 100644 --- a/internal/query/org.go +++ b/internal/query/org.go @@ -107,10 +107,19 @@ func (q *OrgSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder { return query } -func (q *Queries) OrgByID(ctx context.Context, shouldTriggerBulk bool, id string) (_ *Org, err error) { +func (q *Queries) OrgByID(ctx context.Context, shouldTriggerBulk bool, id string) (org *Org, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() + if org, ok := q.caches.org.Get(ctx, orgIndexByID, id); ok { + return org, nil + } + defer func() { + if err == nil && org != nil { + q.caches.org.Set(ctx, org) + } + }() + if !authz.GetInstance(ctx).Features().ShouldUseImprovedPerformance(feature.ImprovedPerformanceTypeOrgByID) { return q.oldOrgByID(ctx, shouldTriggerBulk, id) } @@ -175,6 +184,11 @@ func (q *Queries) OrgByPrimaryDomain(ctx context.Context, domain string) (org *O ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() + org, ok := q.caches.org.Get(ctx, orgIndexByPrimaryDomain, domain) + if ok { + return org, nil + } + stmt, scan := prepareOrgQuery(ctx, q.client) query, args, err := stmt.Where(sq.Eq{ OrgColumnDomain.identifier(): domain, @@ -189,6 +203,9 @@ func (q *Queries) OrgByPrimaryDomain(ctx context.Context, domain string) (org *O org, err = scan(row) return err }, query, args...) + if err == nil { + q.caches.org.Set(ctx, org) + } return org, err } @@ -476,3 +493,30 @@ func prepareOrgUniqueQuery(ctx context.Context, db prepareDatabase) (sq.SelectBu return isUnique, err } } + +type orgIndex int + +//go:generate enumer -type orgIndex -linecomment +const ( + // Empty line comment ensures empty string for unspecified value + orgIndexUnspecified orgIndex = iota // + orgIndexByID + orgIndexByPrimaryDomain +) + +// Keys implements [cache.Entry] +func (o *Org) Keys(index orgIndex) []string { + switch index { + case orgIndexByID: + return []string{o.ID} + case orgIndexByPrimaryDomain: + return []string{o.Domain} + case orgIndexUnspecified: + } + return nil +} + +func (c *Caches) registerOrgInvalidation() { + invalidate := cacheInvalidationFunc(c.org, orgIndexByID, getAggregateID) + projection.OrgProjection.RegisterCacheInvalidation(invalidate) +} diff --git a/internal/query/orgindex_enumer.go b/internal/query/orgindex_enumer.go new file mode 100644 index 0000000000..74f7c985c9 --- /dev/null +++ b/internal/query/orgindex_enumer.go @@ -0,0 +1,82 @@ +// Code generated by "enumer -type orgIndex -linecomment"; DO NOT EDIT. + +package query + +import ( + "fmt" + "strings" +) + +const _orgIndexName = "orgIndexByIDorgIndexByPrimaryDomain" + +var _orgIndexIndex = [...]uint8{0, 0, 12, 35} + +const _orgIndexLowerName = "orgindexbyidorgindexbyprimarydomain" + +func (i orgIndex) String() string { + if i < 0 || i >= orgIndex(len(_orgIndexIndex)-1) { + return fmt.Sprintf("orgIndex(%d)", i) + } + return _orgIndexName[_orgIndexIndex[i]:_orgIndexIndex[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 _orgIndexNoOp() { + var x [1]struct{} + _ = x[orgIndexUnspecified-(0)] + _ = x[orgIndexByID-(1)] + _ = x[orgIndexByPrimaryDomain-(2)] +} + +var _orgIndexValues = []orgIndex{orgIndexUnspecified, orgIndexByID, orgIndexByPrimaryDomain} + +var _orgIndexNameToValueMap = map[string]orgIndex{ + _orgIndexName[0:0]: orgIndexUnspecified, + _orgIndexLowerName[0:0]: orgIndexUnspecified, + _orgIndexName[0:12]: orgIndexByID, + _orgIndexLowerName[0:12]: orgIndexByID, + _orgIndexName[12:35]: orgIndexByPrimaryDomain, + _orgIndexLowerName[12:35]: orgIndexByPrimaryDomain, +} + +var _orgIndexNames = []string{ + _orgIndexName[0:0], + _orgIndexName[0:12], + _orgIndexName[12:35], +} + +// orgIndexString retrieves an enum value from the enum constants string name. +// Throws an error if the param is not part of the enum. +func orgIndexString(s string) (orgIndex, error) { + if val, ok := _orgIndexNameToValueMap[s]; ok { + return val, nil + } + + if val, ok := _orgIndexNameToValueMap[strings.ToLower(s)]; ok { + return val, nil + } + return 0, fmt.Errorf("%s does not belong to orgIndex values", s) +} + +// orgIndexValues returns all values of the enum +func orgIndexValues() []orgIndex { + return _orgIndexValues +} + +// orgIndexStrings returns a slice of all String values of the enum +func orgIndexStrings() []string { + strs := make([]string, len(_orgIndexNames)) + copy(strs, _orgIndexNames) + return strs +} + +// IsAorgIndex returns "true" if the value is listed in the enum definition. "false" otherwise +func (i orgIndex) IsAorgIndex() bool { + for _, v := range _orgIndexValues { + if i == v { + return true + } + } + return false +} diff --git a/internal/query/projection/config.go b/internal/query/projection/config.go index d1a79f1c20..085f5a60cf 100644 --- a/internal/query/projection/config.go +++ b/internal/query/projection/config.go @@ -12,15 +12,18 @@ type Config struct { BulkLimit uint64 Customizations map[string]CustomConfig HandleActiveInstances time.Duration + MaxActiveInstances uint32 TransactionDuration time.Duration + ActiveInstancer interface { + ActiveInstances() []string + } } type CustomConfig struct { - RequeueEvery *time.Duration - RetryFailedAfter *time.Duration - MaxFailureCount *uint8 - ConcurrentInstances *uint - BulkLimit *uint16 - HandleActiveInstances *time.Duration - TransactionDuration *time.Duration + RequeueEvery *time.Duration + RetryFailedAfter *time.Duration + MaxFailureCount *uint8 + ConcurrentInstances *uint + BulkLimit *uint16 + TransactionDuration *time.Duration } diff --git a/internal/query/projection/eventstore_field.go b/internal/query/projection/eventstore_field.go index 647c2af83b..59dde7507d 100644 --- a/internal/query/projection/eventstore_field.go +++ b/internal/query/projection/eventstore_field.go @@ -3,6 +3,7 @@ package projection import ( "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" + "github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/org" "github.com/zitadel/zitadel/internal/repository/project" ) @@ -10,6 +11,7 @@ import ( const ( fieldsProjectGrant = "project_grant_fields" fieldsOrgDomainVerified = "org_domain_verified_fields" + fieldsInstanceDomain = "instance_domain_fields" ) func newFillProjectGrantFields(config handler.Config) *handler.FieldHandler { @@ -36,3 +38,17 @@ func newFillOrgDomainVerifiedFields(config handler.Config) *handler.FieldHandler }, ) } + +func newFillInstanceDomainFields(config handler.Config) *handler.FieldHandler { + return handler.NewFieldHandler( + &config, + fieldsInstanceDomain, + map[eventstore.AggregateType][]eventstore.EventType{ + instance.AggregateType: { + instance.InstanceDomainAddedEventType, + instance.InstanceDomainRemovedEventType, + instance.InstanceRemovedEventType, + }, + }, + ) +} diff --git a/internal/query/projection/eventstore_mock_test.go b/internal/query/projection/eventstore_mock_test.go index 202664d517..4e280dce21 100644 --- a/internal/query/projection/eventstore_mock_test.go +++ b/internal/query/projection/eventstore_mock_test.go @@ -2,7 +2,6 @@ package projection import ( "context" - "time" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" @@ -28,7 +27,7 @@ func (m *mockEventStore) appendFilterResponse(events []eventstore.Event) *mockEv return m } -func (m *mockEventStore) InstanceIDs(ctx context.Context, _ time.Duration, _ bool, query *eventstore.SearchQueryBuilder) ([]string, error) { +func (m *mockEventStore) InstanceIDs(ctx context.Context, query *eventstore.SearchQueryBuilder) ([]string, error) { m.instanceIDCounter++ return m.instanceIDsResponse[m.instanceIDCounter-1], nil } diff --git a/internal/query/projection/projection.go b/internal/query/projection/projection.go index a23ae72330..30dd46a3c6 100644 --- a/internal/query/projection/projection.go +++ b/internal/query/projection/projection.go @@ -83,6 +83,7 @@ var ( ProjectGrantFields *handler.FieldHandler OrgDomainVerifiedFields *handler.FieldHandler + InstanceDomainFields *handler.FieldHandler ) type projection interface { @@ -98,14 +99,14 @@ var ( func Create(ctx context.Context, sqlClient *database.DB, es handler.EventStore, config Config, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, certEncryptionAlgorithm crypto.EncryptionAlgorithm, systemUsers map[string]*internal_authz.SystemAPIUser) error { projectionConfig = handler.Config{ - Client: sqlClient, - Eventstore: es, - BulkLimit: uint16(config.BulkLimit), - RequeueEvery: config.RequeueEvery, - HandleActiveInstances: config.HandleActiveInstances, - MaxFailureCount: config.MaxFailureCount, - RetryFailedAfter: config.RetryFailedAfter, - TransactionDuration: config.TransactionDuration, + Client: sqlClient, + Eventstore: es, + BulkLimit: uint16(config.BulkLimit), + RequeueEvery: config.RequeueEvery, + MaxFailureCount: config.MaxFailureCount, + RetryFailedAfter: config.RetryFailedAfter, + TransactionDuration: config.TransactionDuration, + ActiveInstancer: config.ActiveInstancer, } OrgProjection = newOrgProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["orgs"])) @@ -170,6 +171,7 @@ func Create(ctx context.Context, sqlClient *database.DB, es handler.EventStore, ProjectGrantFields = newFillProjectGrantFields(applyCustomConfig(projectionConfig, config.Customizations[fieldsProjectGrant])) OrgDomainVerifiedFields = newFillOrgDomainVerifiedFields(applyCustomConfig(projectionConfig, config.Customizations[fieldsOrgDomainVerified])) + InstanceDomainFields = newFillInstanceDomainFields(applyCustomConfig(projectionConfig, config.Customizations[fieldsInstanceDomain])) newProjectionsList() return nil @@ -221,9 +223,6 @@ func applyCustomConfig(config handler.Config, customConfig CustomConfig) handler if customConfig.RetryFailedAfter != nil { config.RetryFailedAfter = *customConfig.RetryFailedAfter } - if customConfig.HandleActiveInstances != nil { - config.HandleActiveInstances = *customConfig.HandleActiveInstances - } if customConfig.TransactionDuration != nil { config.TransactionDuration = *customConfig.TransactionDuration } diff --git a/internal/query/projection/target.go b/internal/query/projection/target.go index d39a75b6dc..acc42b9604 100644 --- a/internal/query/projection/target.go +++ b/internal/query/projection/target.go @@ -11,7 +11,7 @@ import ( ) const ( - TargetTable = "projections.targets1" + TargetTable = "projections.targets2" TargetIDCol = "id" TargetCreationDateCol = "creation_date" TargetChangeDateCol = "change_date" @@ -23,6 +23,7 @@ const ( TargetEndpointCol = "endpoint" TargetTimeoutCol = "timeout" TargetInterruptOnErrorCol = "interrupt_on_error" + TargetSigningKey = "signing_key" ) type targetProjection struct{} @@ -49,6 +50,7 @@ func (*targetProjection) Init() *old_handler.Check { handler.NewColumn(TargetEndpointCol, handler.ColumnTypeText), handler.NewColumn(TargetTimeoutCol, handler.ColumnTypeInt64), handler.NewColumn(TargetInterruptOnErrorCol, handler.ColumnTypeBool), + handler.NewColumn(TargetSigningKey, handler.ColumnTypeJSONB, handler.Nullable()), }, handler.NewPrimaryKey(TargetInstanceIDCol, TargetIDCol), ), @@ -105,6 +107,7 @@ func (p *targetProjection) reduceTargetAdded(event eventstore.Event) (*handler.S handler.NewCol(TargetTargetType, e.TargetType), handler.NewCol(TargetTimeoutCol, e.Timeout), handler.NewCol(TargetInterruptOnErrorCol, e.InterruptOnError), + handler.NewCol(TargetSigningKey, e.SigningKey), }, ), nil } @@ -134,6 +137,9 @@ func (p *targetProjection) reduceTargetChanged(event eventstore.Event) (*handler if e.InterruptOnError != nil { values = append(values, handler.NewCol(TargetInterruptOnErrorCol, *e.InterruptOnError)) } + if e.SigningKey != nil { + values = append(values, handler.NewCol(TargetSigningKey, e.SigningKey)) + } return handler.NewUpdateStatement( e, values, diff --git a/internal/query/projection/target_test.go b/internal/query/projection/target_test.go index 30067c6640..6517e78f04 100644 --- a/internal/query/projection/target_test.go +++ b/internal/query/projection/target_test.go @@ -29,7 +29,7 @@ func TestTargetProjection_reduces(t *testing.T) { testEvent( target.AddedEventType, target.AggregateType, - []byte(`{"name": "name", "targetType":0, "endpoint":"https://example.com", "timeout": 3000000000, "async": true, "interruptOnError": true}`), + []byte(`{"name": "name", "targetType":0, "endpoint":"https://example.com", "timeout": 3000000000, "async": true, "interruptOnError": true, "signingKey": { "cryptoType": 0, "algorithm": "RSA-265", "keyId": "key-id" }}`), ), eventstore.GenericEventMapper[target.AddedEvent], ), @@ -41,7 +41,7 @@ func TestTargetProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.targets1 (instance_id, resource_owner, id, creation_date, change_date, sequence, name, endpoint, target_type, timeout, interrupt_on_error) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + expectedStmt: "INSERT INTO projections.targets2 (instance_id, resource_owner, id, creation_date, change_date, sequence, name, endpoint, target_type, timeout, interrupt_on_error, signing_key) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)", expectedArgs: []interface{}{ "instance-id", "ro-id", @@ -54,6 +54,7 @@ func TestTargetProjection_reduces(t *testing.T) { domain.TargetTypeWebhook, 3 * time.Second, true, + anyArg{}, }, }, }, @@ -67,7 +68,7 @@ func TestTargetProjection_reduces(t *testing.T) { testEvent( target.ChangedEventType, target.AggregateType, - []byte(`{"name": "name2", "targetType":0, "endpoint":"https://example.com", "timeout": 3000000000, "async": true, "interruptOnError": true}`), + []byte(`{"name": "name2", "targetType":0, "endpoint":"https://example.com", "timeout": 3000000000, "async": true, "interruptOnError": true, "signingKey": { "cryptoType": 0, "algorithm": "RSA-265", "keyId": "key-id" }}`), ), eventstore.GenericEventMapper[target.ChangedEvent], ), @@ -79,7 +80,7 @@ func TestTargetProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.targets1 SET (change_date, sequence, resource_owner, name, target_type, endpoint, timeout, interrupt_on_error) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (instance_id = $9) AND (id = $10)", + expectedStmt: "UPDATE projections.targets2 SET (change_date, sequence, resource_owner, name, target_type, endpoint, timeout, interrupt_on_error, signing_key) = ($1, $2, $3, $4, $5, $6, $7, $8, $9) WHERE (instance_id = $10) AND (id = $11)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -89,6 +90,7 @@ func TestTargetProjection_reduces(t *testing.T) { "https://example.com", 3 * time.Second, true, + anyArg{}, "instance-id", "agg-id", }, @@ -116,7 +118,7 @@ func TestTargetProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.targets1 WHERE (instance_id = $1) AND (id = $2)", + expectedStmt: "DELETE FROM projections.targets2 WHERE (instance_id = $1) AND (id = $2)", expectedArgs: []interface{}{ "instance-id", "agg-id", @@ -145,7 +147,7 @@ func TestTargetProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.targets1 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.targets2 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, diff --git a/internal/query/query.go b/internal/query/query.go index b39dbe9ca1..0a90e9e4f9 100644 --- a/internal/query/query.go +++ b/internal/query/query.go @@ -29,10 +29,11 @@ type Queries struct { client *database.DB caches *Caches - keyEncryptionAlgorithm crypto.EncryptionAlgorithm - idpConfigEncryption crypto.EncryptionAlgorithm - sessionTokenVerifier func(ctx context.Context, sessionToken string, sessionID string, tokenID string) (err error) - checkPermission domain.PermissionCheck + keyEncryptionAlgorithm crypto.EncryptionAlgorithm + idpConfigEncryption crypto.EncryptionAlgorithm + targetEncryptionAlgorithm crypto.EncryptionAlgorithm + sessionTokenVerifier func(ctx context.Context, sessionToken string, sessionID string, tokenID string) (err error) + checkPermission domain.PermissionCheck DefaultLanguage language.Tag mutex sync.Mutex @@ -52,7 +53,7 @@ func StartQueries( cacheConnectors connector.Connectors, projections projection.Config, defaults sd.SystemDefaults, - idpConfigEncryption, otpEncryption, keyEncryptionAlgorithm, certEncryptionAlgorithm crypto.EncryptionAlgorithm, + idpConfigEncryption, otpEncryption, keyEncryptionAlgorithm, certEncryptionAlgorithm, targetEncryptionAlgorithm crypto.EncryptionAlgorithm, zitadelRoles []authz.RoleMapping, sessionTokenVerifier func(ctx context.Context, sessionToken string, sessionID string, tokenID string) (err error), permissionCheck func(q *Queries) domain.PermissionCheck, @@ -70,6 +71,7 @@ func StartQueries( zitadelRoles: zitadelRoles, keyEncryptionAlgorithm: keyEncryptionAlgorithm, idpConfigEncryption: idpConfigEncryption, + targetEncryptionAlgorithm: targetEncryptionAlgorithm, sessionTokenVerifier: sessionTokenVerifier, multifactors: domain.MultifactorConfigs{ OTP: domain.OTPConfig{ @@ -82,6 +84,7 @@ func StartQueries( repo.checkPermission = permissionCheck(repo) + projections.ActiveInstancer = repo err = projection.Create(ctx, projectionSqlClient, es, projections, keyEncryptionAlgorithm, certEncryptionAlgorithm, systemAPIUsers) if err != nil { return nil, err @@ -89,7 +92,15 @@ func StartQueries( if startProjections { projection.Start(ctx) } - repo.caches, err = startCaches(ctx, cacheConnectors) + + repo.caches, err = startCaches( + ctx, + cacheConnectors, + ActiveInstanceConfig{ + MaxEntries: int(projections.MaxActiveInstances), + TTL: projections.HandleActiveInstances, + }, + ) if err != nil { return nil, err } diff --git a/internal/query/target.go b/internal/query/target.go index 8d926a699b..03db85236c 100644 --- a/internal/query/target.go +++ b/internal/query/target.go @@ -9,6 +9,7 @@ import ( sq "github.com/Masterminds/squirrel" "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query/projection" "github.com/zitadel/zitadel/internal/zerrors" @@ -59,6 +60,10 @@ var ( name: projection.TargetInterruptOnErrorCol, table: targetTable, } + TargetColumnSigningKey = Column{ + name: projection.TargetSigningKey, + table: targetTable, + } ) type Targets struct { @@ -78,6 +83,20 @@ type Target struct { Endpoint string Timeout time.Duration InterruptOnError bool + signingKey *crypto.CryptoValue + SigningKey string +} + +func (t *Target) decryptSigningKey(alg crypto.EncryptionAlgorithm) error { + if t.signingKey == nil { + return nil + } + keyValue, err := crypto.DecryptString(t.signingKey, alg) + if err != nil { + return zerrors.ThrowInternal(err, "QUERY-bxevy3YXwy", "Errors.Internal") + } + t.SigningKey = keyValue + return nil } type TargetSearchQueries struct { @@ -93,21 +112,37 @@ func (q *TargetSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder { return query } -func (q *Queries) SearchTargets(ctx context.Context, queries *TargetSearchQueries) (targets *Targets, err error) { +func (q *Queries) SearchTargets(ctx context.Context, queries *TargetSearchQueries) (*Targets, error) { eq := sq.Eq{ TargetColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } query, scan := prepareTargetsQuery(ctx, q.client) - return genericRowsQueryWithState[*Targets](ctx, q.client, targetTable, combineToWhereStmt(query, queries.toQuery, eq), scan) + targets, err := genericRowsQueryWithState[*Targets](ctx, q.client, targetTable, combineToWhereStmt(query, queries.toQuery, eq), scan) + if err != nil { + return nil, err + } + for i := range targets.Targets { + if err := targets.Targets[i].decryptSigningKey(q.targetEncryptionAlgorithm); err != nil { + return nil, err + } + } + return targets, nil } -func (q *Queries) GetTargetByID(ctx context.Context, id string) (target *Target, err error) { +func (q *Queries) GetTargetByID(ctx context.Context, id string) (*Target, error) { eq := sq.Eq{ TargetColumnID.identifier(): id, TargetColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } query, scan := prepareTargetQuery(ctx, q.client) - return genericRowQuery[*Target](ctx, q.client, query.Where(eq), scan) + target, err := genericRowQuery[*Target](ctx, q.client, query.Where(eq), scan) + if err != nil { + return nil, err + } + if err := target.decryptSigningKey(q.targetEncryptionAlgorithm); err != nil { + return nil, err + } + return target, nil } func NewTargetNameSearchQuery(method TextComparison, value string) (SearchQuery, error) { @@ -129,6 +164,7 @@ func prepareTargetsQuery(context.Context, prepareDatabase) (sq.SelectBuilder, fu TargetColumnTimeout.identifier(), TargetColumnURL.identifier(), TargetColumnInterruptOnError.identifier(), + TargetColumnSigningKey.identifier(), countColumn.identifier(), ).From(targetTable.identifier()). PlaceholderFormat(sq.Dollar), @@ -147,6 +183,7 @@ func prepareTargetsQuery(context.Context, prepareDatabase) (sq.SelectBuilder, fu &target.Timeout, &target.Endpoint, &target.InterruptOnError, + &target.signingKey, &count, ) if err != nil { @@ -179,6 +216,7 @@ func prepareTargetQuery(context.Context, prepareDatabase) (sq.SelectBuilder, fun TargetColumnTimeout.identifier(), TargetColumnURL.identifier(), TargetColumnInterruptOnError.identifier(), + TargetColumnSigningKey.identifier(), ).From(targetTable.identifier()). PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*Target, error) { @@ -193,6 +231,7 @@ func prepareTargetQuery(context.Context, prepareDatabase) (sq.SelectBuilder, fun &target.Timeout, &target.Endpoint, &target.InterruptOnError, + &target.signingKey, ) if err != nil { if errors.Is(err, sql.ErrNoRows) { diff --git a/internal/query/target_test.go b/internal/query/target_test.go index 1b6edd1ad7..aa1ad517b7 100644 --- a/internal/query/target_test.go +++ b/internal/query/target_test.go @@ -9,22 +9,24 @@ import ( "testing" "time" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/zerrors" ) var ( - prepareTargetsStmt = `SELECT projections.targets1.id,` + - ` projections.targets1.creation_date,` + - ` projections.targets1.change_date,` + - ` projections.targets1.resource_owner,` + - ` projections.targets1.name,` + - ` projections.targets1.target_type,` + - ` projections.targets1.timeout,` + - ` projections.targets1.endpoint,` + - ` projections.targets1.interrupt_on_error,` + + prepareTargetsStmt = `SELECT projections.targets2.id,` + + ` projections.targets2.creation_date,` + + ` projections.targets2.change_date,` + + ` projections.targets2.resource_owner,` + + ` projections.targets2.name,` + + ` projections.targets2.target_type,` + + ` projections.targets2.timeout,` + + ` projections.targets2.endpoint,` + + ` projections.targets2.interrupt_on_error,` + + ` projections.targets2.signing_key,` + ` COUNT(*) OVER ()` + - ` FROM projections.targets1` + ` FROM projections.targets2` prepareTargetsCols = []string{ "id", "creation_date", @@ -35,19 +37,21 @@ var ( "timeout", "endpoint", "interrupt_on_error", + "signing_key", "count", } - prepareTargetStmt = `SELECT projections.targets1.id,` + - ` projections.targets1.creation_date,` + - ` projections.targets1.change_date,` + - ` projections.targets1.resource_owner,` + - ` projections.targets1.name,` + - ` projections.targets1.target_type,` + - ` projections.targets1.timeout,` + - ` projections.targets1.endpoint,` + - ` projections.targets1.interrupt_on_error` + - ` FROM projections.targets1` + prepareTargetStmt = `SELECT projections.targets2.id,` + + ` projections.targets2.creation_date,` + + ` projections.targets2.change_date,` + + ` projections.targets2.resource_owner,` + + ` projections.targets2.name,` + + ` projections.targets2.target_type,` + + ` projections.targets2.timeout,` + + ` projections.targets2.endpoint,` + + ` projections.targets2.interrupt_on_error,` + + ` projections.targets2.signing_key` + + ` FROM projections.targets2` prepareTargetCols = []string{ "id", "creation_date", @@ -58,6 +62,7 @@ var ( "timeout", "endpoint", "interrupt_on_error", + "signing_key", } ) @@ -102,6 +107,12 @@ func Test_TargetPrepares(t *testing.T) { 1 * time.Second, "https://example.com", true, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "alg", + KeyID: "encKey", + Crypted: []byte("crypted"), + }, }, }, ), @@ -123,6 +134,12 @@ func Test_TargetPrepares(t *testing.T) { Timeout: 1 * time.Second, Endpoint: "https://example.com", InterruptOnError: true, + signingKey: &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "alg", + KeyID: "encKey", + Crypted: []byte("crypted"), + }, }, }, }, @@ -145,6 +162,12 @@ func Test_TargetPrepares(t *testing.T) { 1 * time.Second, "https://example.com", true, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "alg", + KeyID: "encKey", + Crypted: []byte("crypted"), + }, }, { "id-2", @@ -156,6 +179,12 @@ func Test_TargetPrepares(t *testing.T) { 1 * time.Second, "https://example.com", false, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "alg", + KeyID: "encKey", + Crypted: []byte("crypted"), + }, }, { "id-3", @@ -167,6 +196,12 @@ func Test_TargetPrepares(t *testing.T) { 1 * time.Second, "https://example.com", false, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "alg", + KeyID: "encKey", + Crypted: []byte("crypted"), + }, }, }, ), @@ -188,6 +223,12 @@ func Test_TargetPrepares(t *testing.T) { Timeout: 1 * time.Second, Endpoint: "https://example.com", InterruptOnError: true, + signingKey: &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "alg", + KeyID: "encKey", + Crypted: []byte("crypted"), + }, }, { ObjectDetails: domain.ObjectDetails{ @@ -201,6 +242,12 @@ func Test_TargetPrepares(t *testing.T) { Timeout: 1 * time.Second, Endpoint: "https://example.com", InterruptOnError: false, + signingKey: &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "alg", + KeyID: "encKey", + Crypted: []byte("crypted"), + }, }, { ObjectDetails: domain.ObjectDetails{ @@ -214,6 +261,12 @@ func Test_TargetPrepares(t *testing.T) { Timeout: 1 * time.Second, Endpoint: "https://example.com", InterruptOnError: false, + signingKey: &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "alg", + KeyID: "encKey", + Crypted: []byte("crypted"), + }, }, }, }, @@ -270,6 +323,12 @@ func Test_TargetPrepares(t *testing.T) { 1 * time.Second, "https://example.com", true, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "alg", + KeyID: "encKey", + Crypted: []byte("crypted"), + }, }, ), }, @@ -285,6 +344,12 @@ func Test_TargetPrepares(t *testing.T) { Timeout: 1 * time.Second, Endpoint: "https://example.com", InterruptOnError: true, + signingKey: &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "alg", + KeyID: "encKey", + Crypted: []byte("crypted"), + }, }, }, { diff --git a/internal/query/targets_by_execution_id.sql b/internal/query/targets_by_execution_id.sql index f8248479b0..f3ee25d675 100644 --- a/internal/query/targets_by_execution_id.sql +++ b/internal/query/targets_by_execution_id.sql @@ -31,9 +31,9 @@ WITH RECURSIVE ON e.instance_id = p.instance_id AND e.include IS NOT NULL AND e.include = p.execution_id) -select e.execution_id, e.instance_id, e.target_id, t.target_type, t.endpoint, t.timeout, t.interrupt_on_error +select e.execution_id, e.instance_id, e.target_id, t.target_type, t.endpoint, t.timeout, t.interrupt_on_error, t.signing_key FROM dissolved_execution_targets e - JOIN projections.targets1 t + JOIN projections.targets2 t ON e.instance_id = t.instance_id AND e.target_id = t.id WHERE "include" = '' diff --git a/internal/query/targets_by_execution_ids.sql b/internal/query/targets_by_execution_ids.sql index 749d9387b2..277826a81b 100644 --- a/internal/query/targets_by_execution_ids.sql +++ b/internal/query/targets_by_execution_ids.sql @@ -38,9 +38,9 @@ WITH RECURSIVE ON e.instance_id = p.instance_id AND e.include IS NOT NULL AND e.include = p.execution_id) -select e.execution_id, e.instance_id, e.target_id, t.target_type, t.endpoint, t.timeout, t.interrupt_on_error +select e.execution_id, e.instance_id, e.target_id, t.target_type, t.endpoint, t.timeout, t.interrupt_on_error, t.signing_key FROM dissolved_execution_targets e - JOIN projections.targets1 t + JOIN projections.targets2 t ON e.instance_id = t.instance_id AND e.target_id = t.id WHERE "include" = '' diff --git a/internal/repository/instance/domain.go b/internal/repository/instance/domain.go index faeb45a71f..9e9b241ad2 100644 --- a/internal/repository/instance/domain.go +++ b/internal/repository/instance/domain.go @@ -13,6 +13,10 @@ const ( InstanceDomainAddedEventType = domainEventPrefix + "added" InstanceDomainPrimarySetEventType = domainEventPrefix + "primary.set" InstanceDomainRemovedEventType = domainEventPrefix + "removed" + + InstanceDomainSearchType = "instance_domain" + InstanceDomainSearchField = "domain" + InstanceDomainObjectRevision = uint8(1) ) func NewAddInstanceDomainUniqueConstraint(domain string) *eventstore.UniqueConstraint { @@ -43,6 +47,30 @@ func (e *DomainAddedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { return []*eventstore.UniqueConstraint{NewAddInstanceDomainUniqueConstraint(e.Domain)} } +func (e *DomainAddedEvent) Fields() []*eventstore.FieldOperation { + return []*eventstore.FieldOperation{ + eventstore.SetField( + e.Aggregate(), + domainSearchObject(e.Domain), + InstanceDomainSearchField, + &eventstore.Value{ + Value: e.Domain, + // TODO: (adlerhurst) ensure uniqueness if we go with fields table: https://github.com/zitadel/zitadel/issues/9009 + MustBeUnique: false, + ShouldIndex: true, + }, + + eventstore.FieldTypeInstanceID, + eventstore.FieldTypeResourceOwner, + eventstore.FieldTypeAggregateType, + eventstore.FieldTypeAggregateID, + eventstore.FieldTypeObjectType, + eventstore.FieldTypeObjectID, + eventstore.FieldTypeFieldName, + ), + } +} + func NewDomainAddedEvent(ctx context.Context, aggregate *eventstore.Aggregate, domain string, generated bool) *DomainAddedEvent { return &DomainAddedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -118,6 +146,29 @@ func (e *DomainRemovedEvent) UniqueConstraints() []*eventstore.UniqueConstraint return []*eventstore.UniqueConstraint{NewRemoveInstanceDomainUniqueConstraint(e.Domain)} } +func (e *DomainRemovedEvent) Fields() []*eventstore.FieldOperation { + return []*eventstore.FieldOperation{ + eventstore.SetField( + e.Aggregate(), + domainSearchObject(e.Domain), + InstanceDomainSearchField, + &eventstore.Value{ + Value: e.Domain, + MustBeUnique: true, + ShouldIndex: true, + }, + + eventstore.FieldTypeInstanceID, + eventstore.FieldTypeResourceOwner, + eventstore.FieldTypeAggregateType, + eventstore.FieldTypeAggregateID, + eventstore.FieldTypeObjectType, + eventstore.FieldTypeObjectID, + eventstore.FieldTypeFieldName, + ), + } +} + func NewDomainRemovedEvent(ctx context.Context, aggregate *eventstore.Aggregate, domain string) *DomainRemovedEvent { return &DomainRemovedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -140,3 +191,11 @@ func DomainRemovedEventMapper(event eventstore.Event) (eventstore.Event, error) return domainRemoved, nil } + +func domainSearchObject(domain string) eventstore.Object { + return eventstore.Object{ + Type: InstanceDomainSearchType, + ID: domain, + Revision: InstanceDomainObjectRevision, + } +} diff --git a/internal/repository/instance/instance.go b/internal/repository/instance/instance.go index bd0214075c..761ec4d576 100644 --- a/internal/repository/instance/instance.go +++ b/internal/repository/instance/instance.go @@ -106,6 +106,14 @@ func (e *InstanceRemovedEvent) UniqueConstraints() []*eventstore.UniqueConstrain return constraints } +func (e *InstanceRemovedEvent) Fields() []*eventstore.FieldOperation { + return []*eventstore.FieldOperation{ + eventstore.RemoveSearchFields(map[eventstore.FieldType]any{ + eventstore.FieldTypeInstanceID: e.Aggregate().ID, + }), + } +} + func NewInstanceRemovedEvent(ctx context.Context, aggregate *eventstore.Aggregate, name string, domains []string) *InstanceRemovedEvent { return &InstanceRemovedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( diff --git a/internal/repository/notification/aggregate.go b/internal/repository/notification/aggregate.go new file mode 100644 index 0000000000..8370337d40 --- /dev/null +++ b/internal/repository/notification/aggregate.go @@ -0,0 +1,25 @@ +package notification + +import ( + "github.com/zitadel/zitadel/internal/eventstore" +) + +const ( + AggregateType = "notification" + AggregateVersion = "v1" +) + +type Aggregate struct { + eventstore.Aggregate +} + +func NewAggregate(id, resourceOwner string) *Aggregate { + return &Aggregate{ + Aggregate: eventstore.Aggregate{ + Type: AggregateType, + Version: AggregateVersion, + ID: id, + ResourceOwner: resourceOwner, + }, + } +} diff --git a/internal/repository/notification/eventstore.go b/internal/repository/notification/eventstore.go new file mode 100644 index 0000000000..3ef1c9c7db --- /dev/null +++ b/internal/repository/notification/eventstore.go @@ -0,0 +1,12 @@ +package notification + +import ( + "github.com/zitadel/zitadel/internal/eventstore" +) + +func init() { + eventstore.RegisterFilterEventMapper(AggregateType, RequestedType, eventstore.GenericEventMapper[RequestedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, SentType, eventstore.GenericEventMapper[SentEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, RetryRequestedType, eventstore.GenericEventMapper[RetryRequestedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, CanceledType, eventstore.GenericEventMapper[CanceledEvent]) +} diff --git a/internal/repository/notification/notification.go b/internal/repository/notification/notification.go new file mode 100644 index 0000000000..cf7090525f --- /dev/null +++ b/internal/repository/notification/notification.go @@ -0,0 +1,244 @@ +package notification + +import ( + "context" + "time" + + "github.com/zitadel/zitadel/internal/crypto" + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/query" +) + +const ( + notificationEventPrefix = "notification." + RequestedType = notificationEventPrefix + "requested" + RetryRequestedType = notificationEventPrefix + "retry.requested" + SentType = notificationEventPrefix + "sent" + CanceledType = notificationEventPrefix + "canceled" +) + +type Request struct { + UserID string `json:"userID"` + UserResourceOwner string `json:"userResourceOwner"` + AggregateID string `json:"notificationAggregateID"` + AggregateResourceOwner string `json:"notificationAggregateResourceOwner"` + TriggeredAtOrigin string `json:"triggeredAtOrigin"` + EventType eventstore.EventType `json:"eventType"` + MessageType string `json:"messageType"` + NotificationType domain.NotificationType `json:"notificationType"` + URLTemplate string `json:"urlTemplate,omitempty"` + CodeExpiry time.Duration `json:"codeExpiry,omitempty"` + Code *crypto.CryptoValue `json:"code,omitempty"` + UnverifiedNotificationChannel bool `json:"unverifiedNotificationChannel,omitempty"` + IsOTP bool `json:"isOTP,omitempty"` + RequiresPreviousDomain bool `json:"RequiresPreviousDomain,omitempty"` + Args *domain.NotificationArguments `json:"args,omitempty"` +} + +func (e *Request) NotificationAggregateID() string { + if e.AggregateID == "" { + return e.UserID + } + return e.AggregateID +} + +func (e *Request) NotificationAggregateResourceOwner() string { + if e.AggregateResourceOwner == "" { + return e.UserResourceOwner + } + return e.AggregateResourceOwner +} + +type RequestedEvent struct { + eventstore.BaseEvent `json:"-"` + + Request `json:"request"` +} + +func (e *RequestedEvent) TriggerOrigin() string { + return e.TriggeredAtOrigin +} + +func (e *RequestedEvent) Payload() interface{} { + return e +} + +func (e *RequestedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { + return nil +} + +func (e *RequestedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = *event +} + +func NewRequestedEvent(ctx context.Context, + aggregate *eventstore.Aggregate, + userID, + userResourceOwner, + aggregateID, + aggregateResourceOwner, + triggerOrigin, + urlTemplate string, + code *crypto.CryptoValue, + codeExpiry time.Duration, + eventType eventstore.EventType, + notificationType domain.NotificationType, + messageType string, + unverifiedNotificationChannel, + isOTP, + requiresPreviousDomain bool, + args *domain.NotificationArguments, +) *RequestedEvent { + return &RequestedEvent{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + aggregate, + RequestedType, + ), + Request: Request{ + UserID: userID, + UserResourceOwner: userResourceOwner, + AggregateID: aggregateID, + AggregateResourceOwner: aggregateResourceOwner, + TriggeredAtOrigin: triggerOrigin, + EventType: eventType, + MessageType: messageType, + NotificationType: notificationType, + URLTemplate: urlTemplate, + CodeExpiry: codeExpiry, + Code: code, + UnverifiedNotificationChannel: unverifiedNotificationChannel, + IsOTP: isOTP, + RequiresPreviousDomain: requiresPreviousDomain, + Args: args, + }, + } +} + +type SentEvent struct { + eventstore.BaseEvent `json:"-"` +} + +func (e *SentEvent) Payload() interface{} { + return e +} + +func (e *SentEvent) UniqueConstraints() []*eventstore.UniqueConstraint { + return nil +} + +func (e *SentEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = *event +} + +func NewSentEvent(ctx context.Context, + aggregate *eventstore.Aggregate, +) *SentEvent { + return &SentEvent{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + aggregate, + SentType, + ), + } +} + +type CanceledEvent struct { + eventstore.BaseEvent `json:"-"` + + Error string `json:"error"` +} + +func (e *CanceledEvent) Payload() interface{} { + return e +} + +func (e *CanceledEvent) UniqueConstraints() []*eventstore.UniqueConstraint { + return nil +} + +func (e *CanceledEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = *event +} + +func NewCanceledEvent(ctx context.Context, aggregate *eventstore.Aggregate, errorMessage string) *CanceledEvent { + return &CanceledEvent{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + aggregate, + CanceledType, + ), + Error: errorMessage, + } +} + +type RetryRequestedEvent struct { + eventstore.BaseEvent `json:"-"` + + Request `json:"request"` + Error string `json:"error"` + NotifyUser *query.NotifyUser `json:"notifyUser"` + BackOff time.Duration `json:"backOff"` +} + +func (e *RetryRequestedEvent) Payload() interface{} { + return e +} + +func (e *RetryRequestedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { + return nil +} + +func (e *RetryRequestedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = *event +} + +func NewRetryRequestedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + userID, + userResourceOwner, + aggregateID, + aggregateResourceOwner, + triggerOrigin, + urlTemplate string, + code *crypto.CryptoValue, + codeExpiry time.Duration, + eventType eventstore.EventType, + notificationType domain.NotificationType, + messageType string, + unverifiedNotificationChannel, + isOTP bool, + args *domain.NotificationArguments, + notifyUser *query.NotifyUser, + backoff time.Duration, + errorMessage string, +) *RetryRequestedEvent { + return &RetryRequestedEvent{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + aggregate, + RetryRequestedType, + ), + Request: Request{ + UserID: userID, + UserResourceOwner: userResourceOwner, + AggregateID: aggregateID, + AggregateResourceOwner: aggregateResourceOwner, + TriggeredAtOrigin: triggerOrigin, + EventType: eventType, + MessageType: messageType, + NotificationType: notificationType, + URLTemplate: urlTemplate, + CodeExpiry: codeExpiry, + Code: code, + UnverifiedNotificationChannel: unverifiedNotificationChannel, + IsOTP: isOTP, + Args: args, + }, + NotifyUser: notifyUser, + BackOff: backoff, + Error: errorMessage, + } +} diff --git a/internal/repository/org/org.go b/internal/repository/org/org.go index 95c9c2f3ed..cf8a3ce114 100644 --- a/internal/repository/org/org.go +++ b/internal/repository/org/org.go @@ -310,23 +310,7 @@ func (e *OrgRemovedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { func (e *OrgRemovedEvent) Fields() []*eventstore.FieldOperation { // TODO: project grants are currently not removed because we don't have the relationship between the granted org and the grant return []*eventstore.FieldOperation{ - eventstore.SetField( - e.Aggregate(), - orgSearchObject(e.Aggregate().ID), - OrgStateSearchField, - &eventstore.Value{ - Value: domain.OrgStateRemoved, - ShouldIndex: true, - }, - - eventstore.FieldTypeInstanceID, - eventstore.FieldTypeResourceOwner, - eventstore.FieldTypeAggregateType, - eventstore.FieldTypeAggregateID, - eventstore.FieldTypeObjectType, - eventstore.FieldTypeObjectID, - eventstore.FieldTypeFieldName, - ), + eventstore.RemoveSearchFieldsByAggregate(e.Aggregate()), } } diff --git a/internal/repository/target/target.go b/internal/repository/target/target.go index 85e3ae7023..3df1b31480 100644 --- a/internal/repository/target/target.go +++ b/internal/repository/target/target.go @@ -4,6 +4,7 @@ import ( "context" "time" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" ) @@ -18,11 +19,12 @@ const ( type AddedEvent struct { eventstore.BaseEvent `json:"-"` - Name string `json:"name"` - TargetType domain.TargetType `json:"targetType"` - Endpoint string `json:"endpoint"` - Timeout time.Duration `json:"timeout"` - InterruptOnError bool `json:"interruptOnError"` + Name string `json:"name"` + TargetType domain.TargetType `json:"targetType"` + Endpoint string `json:"endpoint"` + Timeout time.Duration `json:"timeout"` + InterruptOnError bool `json:"interruptOnError"` + SigningKey *crypto.CryptoValue `json:"signingKey"` } func (e *AddedEvent) SetBaseEvent(b *eventstore.BaseEvent) { @@ -45,22 +47,24 @@ func NewAddedEvent( endpoint string, timeout time.Duration, interruptOnError bool, + signingKey *crypto.CryptoValue, ) *AddedEvent { return &AddedEvent{ *eventstore.NewBaseEventForPush( ctx, aggregate, AddedEventType, ), - name, targetType, endpoint, timeout, interruptOnError} + name, targetType, endpoint, timeout, interruptOnError, signingKey} } type ChangedEvent struct { eventstore.BaseEvent `json:"-"` - Name *string `json:"name,omitempty"` - TargetType *domain.TargetType `json:"targetType,omitempty"` - Endpoint *string `json:"endpoint,omitempty"` - Timeout *time.Duration `json:"timeout,omitempty"` - InterruptOnError *bool `json:"interruptOnError,omitempty"` + Name *string `json:"name,omitempty"` + TargetType *domain.TargetType `json:"targetType,omitempty"` + Endpoint *string `json:"endpoint,omitempty"` + Timeout *time.Duration `json:"timeout,omitempty"` + InterruptOnError *bool `json:"interruptOnError,omitempty"` + SigningKey *crypto.CryptoValue `json:"signingKey,omitempty"` oldName string } @@ -134,6 +138,12 @@ func ChangeInterruptOnError(interruptOnError bool) func(event *ChangedEvent) { } } +func ChangeSigningKey(signingKey *crypto.CryptoValue) func(event *ChangedEvent) { + return func(e *ChangedEvent) { + e.SigningKey = signingKey + } +} + type RemovedEvent struct { eventstore.BaseEvent `json:"-"` diff --git a/internal/static/i18n/ko.yaml b/internal/static/i18n/ko.yaml new file mode 100644 index 0000000000..fda5171de2 --- /dev/null +++ b/internal/static/i18n/ko.yaml @@ -0,0 +1,1406 @@ +Errors: + Internal: ๋‚ด๋ถ€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค + NoChangesFound: ๋ณ€๊ฒฝ ์‚ฌํ•ญ ์—†์Œ + OriginNotAllowed: ์ด "Origin"์€ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + IDMissing: ID๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + ResourceOwnerMissing: ๋ฆฌ์†Œ์Šค ์†Œ์œ  ์กฐ์ง์ด ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + RemoveFailed: ์ œ๊ฑฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + ProjectionName: + Invalid: ์ž˜๋ชป๋œ ํˆฌ์˜ ์ด๋ฆ„์ž…๋‹ˆ๋‹ค + Assets: + EmptyKey: ์ž์‚ฐ ํ‚ค๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + Store: + NotInitialized: ์ž์‚ฐ ์ €์žฅ์†Œ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + NotConfigured: ์ž์‚ฐ ์ €์žฅ์†Œ๊ฐ€ ๊ตฌ์„ฑ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + Bucket: + Internal: ๋ฒ„ํ‚ท ์ƒ์„ฑ ์ค‘ ๋‚ด๋ถ€ ์˜ค๋ฅ˜ ๋ฐœ์ƒ + AlreadyExists: ๋ฒ„ํ‚ท์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + CreateFailed: ๋ฒ„ํ‚ท์ด ์ƒ์„ฑ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + ListFailed: ๋ฒ„ํ‚ท์„ ์ฝ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + RemoveFailed: ๋ฒ„ํ‚ท์ด ์‚ญ์ œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + SetPublicFailed: ๋ฒ„ํ‚ท์„ ๊ณต๊ฐœ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Object: + PutFailed: ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + GetFailed: ๊ฐ์ฒด๋ฅผ ์ฝ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotFound: ๊ฐ์ฒด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + PresignedTokenFailed: ์„œ๋ช…๋œ ํ† ํฐ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + ListFailed: ๊ฐ์ฒด ๋ชฉ๋ก์„ ์ฝ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + RemoveFailed: ๊ฐ์ฒด๋ฅผ ์‚ญ์ œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Limit: + ExceedsDefault: ์ œํ•œ์ด ๊ธฐ๋ณธ ์ œํ•œ์„ ์ดˆ๊ณผํ•ฉ๋‹ˆ๋‹ค + Limits: + NotFound: ์ œํ•œ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NoneSpecified: ์ง€์ •๋œ ์ œํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค + Instance: + Blocked: ์ธ์Šคํ„ด์Šค๊ฐ€ ์ฐจ๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + Restrictions: + NoneSpecified: ์ง€์ •๋œ ์ œํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค + DefaultLanguageMustBeAllowed: ๊ธฐ๋ณธ ์–ธ์–ด๋Š” ํ—ˆ์šฉ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค + Language: + NotParsed: ์–ธ์–ด๋ฅผ ๊ตฌ๋ฌธ ๋ถ„์„ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotSupported: ์ง€์›๋˜์ง€ ์•Š๋Š” ์–ธ์–ด์ž…๋‹ˆ๋‹ค + NotAllowed: ํ—ˆ์šฉ๋˜์ง€ ์•Š๋Š” ์–ธ์–ด์ž…๋‹ˆ๋‹ค + Undefined: ์–ธ์–ด๊ฐ€ ์ •์˜๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + Duplicate: ์ค‘๋ณต๋œ ์–ธ์–ด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค + OIDCSettings: + NotFound: OIDC ๊ตฌ์„ฑ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + AlreadyExists: OIDC ๊ตฌ์„ฑ์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + SecretGenerator: + AlreadyExists: ์‹œํฌ๋ฆฟ ์ƒ์„ฑ๊ธฐ๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + TypeMissing: ์‹œํฌ๋ฆฟ ์ƒ์„ฑ๊ธฐ ์œ ํ˜•์ด ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + NotFound: ์‹œํฌ๋ฆฟ ์ƒ์„ฑ๊ธฐ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + SMSConfig: + NotFound: SMS ๊ตฌ์„ฑ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + AlreadyActive: SMS ๊ตฌ์„ฑ์ด ์ด๋ฏธ ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค + AlreadyDeactivated: SMS ๊ตฌ์„ฑ์ด ์ด๋ฏธ ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค + NotExternalVerification: SMS ๊ตฌ์„ฑ์€ ์ฝ”๋“œ ๊ฒ€์ฆ์„ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + SMTP: + NotEmailMessage: ๋ฉ”์‹œ์ง€๊ฐ€ ์ด๋ฉ”์ผ ๋ฉ”์‹œ์ง€๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค + RequiredAttributes: subject, recipients ๋ฐ content๊ฐ€ ์„ค์ •๋˜์–ด์•ผ ํ•˜์ง€๋งŒ ์ผ๋ถ€ ๋˜๋Š” ๋ชจ๋‘ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + CouldNotSplit: smtp ์—ฐ๊ฒฐ์„ ์œ„ํ•ด ํ˜ธ์ŠคํŠธ์™€ ํฌํŠธ๋ฅผ ๋ถ„ํ• ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + CouldNotDial: SMTP ์„œ๋ฒ„์— ์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ํฌํŠธ ๋˜๋Š” ๋ฐฉํ™”๋ฒฝ ๋ฌธ์ œ๋ฅผ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค... + CouldNotDialTLS: TLS๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ SMTP ์„œ๋ฒ„์— ์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ํฌํŠธ ๋˜๋Š” ๋ฐฉํ™”๋ฒฝ ๋ฌธ์ œ๋ฅผ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค... + CouldNotCreateClient: smtp ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + CouldNotStartTLS: TLS๋ฅผ ์‹œ์ž‘ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + CouldNotAuth: smtp ์ธ์ฆ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ •ํ™•ํ•œ์ง€ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. ์ •ํ™•ํ•˜๋‹ค๋ฉด ์ œ๊ณต์ž๊ฐ€ ZITADEL์—์„œ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ์ธ์ฆ ๋ฐฉ๋ฒ•์„ ์š”๊ตฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค + CouldNotSetSender: ๋ฐœ์‹ ์ž๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + CouldNotSetRecipient: ์ˆ˜์‹ ์ž๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + SMTPConfig: + TestPassword: ํ…Œ์ŠคํŠธํ•  ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค + NotFound: SMTP ๊ตฌ์„ฑ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + AlreadyExists: SMTP ๊ตฌ์„ฑ์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + AlreadyDeactivated: SMTP ๊ตฌ์„ฑ์ด ์ด๋ฏธ ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค + SenderAdressNotCustomDomain: ๋ฐœ์‹ ์ž ์ฃผ์†Œ๋Š” ์ธ์Šคํ„ด์Šค์—์„œ ์‚ฌ์šฉ์ž ์ •์˜ ๋„๋ฉ”์ธ์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค + TestEmailNotFound: ํ…Œ์ŠคํŠธํ•  ์ด๋ฉ”์ผ ์ฃผ์†Œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค + Notification: + NoDomain: ๋ฉ”์‹œ์ง€์— ๋Œ€ํ•œ ๋„๋ฉ”์ธ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + User: + NotFound: ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + AlreadyExists: ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + NotFoundOnOrg: ์„ ํƒํ•œ ์กฐ์ง์—์„œ ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotAllowedOrg: ์‚ฌ์šฉ์ž๊ฐ€ ํ•„์ˆ˜ ์กฐ์ง์˜ ๊ตฌ์„ฑ์›์ด ์•„๋‹™๋‹ˆ๋‹ค + UserIDMissing: ์‚ฌ์šฉ์ž ID๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + UserIDWrong: "์š”์ฒญํ•œ ์‚ฌ์šฉ์ž์™€ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค" + DomainPolicyNil: ์กฐ์ง ์ •์ฑ…์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + EmailAsUsernameNotAllowed: ์ด๋ฉ”์ผ์„ ์‚ฌ์šฉ์ž ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Invalid: ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๊ฐ€ ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค + DomainNotAllowedAsUsername: ๋„๋ฉ”์ธ์ด ์ด๋ฏธ ์˜ˆ์•ฝ๋˜์–ด ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + AlreadyInactive: ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฏธ ๋น„ํ™œ์„ฑ ์ƒํƒœ์ž…๋‹ˆ๋‹ค + NotInactive: ์‚ฌ์šฉ์ž๊ฐ€ ๋น„ํ™œ์„ฑ ์ƒํƒœ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค + CantDeactivateInitial: ์ดˆ๊ธฐ ์ƒํƒœ์˜ ์‚ฌ์šฉ์ž๋Š” ๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์—†์œผ๋ฉฐ ์‚ญ์ œ๋งŒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค + ShouldBeActiveOrInitial: ์‚ฌ์šฉ์ž๊ฐ€ ํ™œ์„ฑ ์ƒํƒœ์ด๊ฑฐ๋‚˜ ์ดˆ๊ธฐ ์ƒํƒœ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค + AlreadyInitialised: ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฏธ ์ดˆ๊ธฐํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค + NotInitialised: ์‚ฌ์šฉ์ž๊ฐ€ ์•„์ง ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + NotLocked: ์‚ฌ์šฉ์ž๊ฐ€ ์ž ๊ฒจ ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค + NoChanges: ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ์—†์Šต๋‹ˆ๋‹ค + InitCodeNotFound: ์ดˆ๊ธฐํ™” ์ฝ”๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + UsernameNotChanged: ์‚ฌ์šฉ์ž ์ด๋ฆ„์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + InvalidURLTemplate: URL ํ…œํ”Œ๋ฆฟ์ด ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค + Profile: + NotFound: ํ”„๋กœํ•„์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotChanged: ํ”„๋กœํ•„์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + Empty: ํ”„๋กœํ•„์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + FirstNameEmpty: ํ”„๋กœํ•„์˜ ์ด๋ฆ„์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + LastNameEmpty: ํ”„๋กœํ•„์˜ ์„ฑ์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + IDMissing: ํ”„๋กœํ•„ ID๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + Email: + NotFound: ์ด๋ฉ”์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Invalid: ์ด๋ฉ”์ผ์ด ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค + AlreadyVerified: ์ด๋ฉ”์ผ์ด ์ด๋ฏธ ์ธ์ฆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + NotChanged: ์ด๋ฉ”์ผ์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + Empty: ์ด๋ฉ”์ผ์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + IDMissing: ์ด๋ฉ”์ผ ID๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + Phone: + NotFound: ์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Invalid: ์ „ํ™”๋ฒˆํ˜ธ๊ฐ€ ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค + AlreadyVerified: ์ „ํ™”๋ฒˆํ˜ธ๊ฐ€ ์ด๋ฏธ ์ธ์ฆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + Empty: ์ „ํ™”๋ฒˆํ˜ธ๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + NotChanged: ์ „ํ™”๋ฒˆํ˜ธ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + Address: + NotFound: ์ฃผ์†Œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotChanged: ์ฃผ์†Œ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + Machine: + Key: + NotFound: ๋จธ์‹  ํ‚ค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + AlreadyExisting: ๋จธ์‹  ํ‚ค๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + Invalid: ๊ณต๊ฐœ ํ‚ค๊ฐ€ PKIX ํ˜•์‹์˜ PEM ์ธ์ฝ”๋”ฉ์„ ๋”ฐ๋ฅด๋Š” ์œ ํšจํ•œ RSA ๊ณต๊ฐœ ํ‚ค๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค + Secret: + NotExisting: ์‹œํฌ๋ฆฟ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Invalid: ์‹œํฌ๋ฆฟ์ด ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค + CouldNotGenerate: ์‹œํฌ๋ฆฟ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + PAT: + NotFound: ๊ฐœ์ธ ์•ก์„ธ์Šค ํ† ํฐ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotHuman: ์‚ฌ์šฉ์ž๋Š” ๊ฐœ์ธ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค + NotMachine: ์‚ฌ์šฉ์ž๋Š” ๊ธฐ์ˆ ์ ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค + WrongType: ์ด ์‚ฌ์šฉ์ž ์œ ํ˜•์—๋Š” ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + NotAllowedToLink: ์‚ฌ์šฉ์ž๋Š” ์™ธ๋ถ€ ๋กœ๊ทธ์ธ ์ œ๊ณต์ž์™€ ๋งํฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Username: + AlreadyExists: ์‚ฌ์šฉ์ž ์ด๋ฆ„์ด ์ด๋ฏธ ์‚ฌ์šฉ ์ค‘์ž…๋‹ˆ๋‹ค + Reserved: ์‚ฌ์šฉ์ž ์ด๋ฆ„์ด ์ด๋ฏธ ์‚ฌ์šฉ ์ค‘์ž…๋‹ˆ๋‹ค + Empty: ์‚ฌ์šฉ์ž ์ด๋ฆ„์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + Code: + Empty: ์ฝ”๋“œ๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + NotFound: ์ฝ”๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Expired: ์ฝ”๋“œ๊ฐ€ ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + GeneratorAlgNotSupported: ์ง€์›๋˜์ง€ ์•Š๋Š” ์ƒ์„ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜ + Invalid: ์ฝ”๋“œ๊ฐ€ ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค + Password: + NotFound: ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Empty: ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + Invalid: ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค + NotSet: ์‚ฌ์šฉ์ž๊ฐ€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์„ค์ •ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + NotChanged: ์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ํ˜„์žฌ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ๋‹ค๋ฅด์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค + NotSupported: ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹œ ์ธ์ฝ”๋”ฉ์ด ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ https://zitadel.com/docs/concepts/architecture/secrets#hashed-secrets๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š” + PasswordComplexityPolicy: + NotFound: ๋น„๋ฐ€๋ฒˆํ˜ธ ์ •์ฑ…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + MinLength: ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋„ˆ๋ฌด ์งง์Šต๋‹ˆ๋‹ค + MinLengthNotAllowed: ์ง€์ •๋œ ์ตœ์†Œ ๊ธธ์ด๋Š” ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + HasLower: ๋น„๋ฐ€๋ฒˆํ˜ธ์—๋Š” ์†Œ๋ฌธ์ž๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค + HasUpper: ๋น„๋ฐ€๋ฒˆํ˜ธ์—๋Š” ๋Œ€๋ฌธ์ž๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค + HasNumber: ๋น„๋ฐ€๋ฒˆํ˜ธ์—๋Š” ์ˆซ์ž๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค + HasSymbol: ๋น„๋ฐ€๋ฒˆํ˜ธ์—๋Š” ๊ธฐํ˜ธ๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค + ExternalIDP: + Invalid: ์™ธ๋ถ€ IDP๊ฐ€ ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค + IDPConfigNotExisting: ์ด ์กฐ์ง์— ๋Œ€ํ•ด ์œ ํšจํ•˜์ง€ ์•Š์€ IDP ์ œ๊ณต์ž์ž…๋‹ˆ๋‹ค + NotAllowed: ์™ธ๋ถ€ IDP๊ฐ€ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + MinimumExternalIDPNeeded: ์ตœ์†Œ ํ•˜๋‚˜์˜ IDP๊ฐ€ ์ถ”๊ฐ€๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค + AlreadyExists: ์™ธ๋ถ€ IDP๊ฐ€ ์ด๋ฏธ ์‚ฌ์šฉ ์ค‘์ž…๋‹ˆ๋‹ค + NotFound: ์™ธ๋ถ€ IDP๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + LoginFailed: ์™ธ๋ถ€ IDP์—์„œ ๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค + MFA: + OTP: + AlreadyReady: ๋‹ค์ค‘ ์š”์†Œ OTP(์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ)๊ฐ€ ์ด๋ฏธ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค + NotExisting: ๋‹ค์ค‘ ์š”์†Œ OTP(์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ)๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + NotReady: ๋‹ค์ค‘ ์š”์†Œ OTP(์ผํšŒ์šฉ ๋น„๋ฐ€๋ฒˆํ˜ธ)๊ฐ€ ์ค€๋น„๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + InvalidCode: ์ฝ”๋“œ๊ฐ€ ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค + U2F: + NotExisting: U2F๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Passwordless: + NotExisting: ํŒจ์Šค์›Œ๋“œ๋ฆฌ์Šค๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + WebAuthN: + NotFound: WebAuthN ํ† ํฐ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + BeginRegisterFailed: WebAuthN ๋“ฑ๋ก ์‹œ์ž‘์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค + MarshalError: ๋ฐ์ดํ„ฐ ์ง๋ ฌํ™” ์˜ค๋ฅ˜ ๋ฐœ์ƒ + ErrorOnParseCredential: ์ž๊ฒฉ ์ฆ๋ช… ๋ฐ์ดํ„ฐ ๊ตฌ๋ฌธ ๋ถ„์„ ์˜ค๋ฅ˜ + CreateCredentialFailed: ์ž๊ฒฉ ์ฆ๋ช… ์ƒ์„ฑ ์˜ค๋ฅ˜ + BeginLoginFailed: WebAuthN ๋กœ๊ทธ์ธ ์‹œ์ž‘์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค + ValidateLoginFailed: ๋กœ๊ทธ์ธ ์ž๊ฒฉ ์ฆ๋ช… ํ™•์ธ ์˜ค๋ฅ˜ + CloneWarning: ์ž๊ฒฉ ์ฆ๋ช…์ด ๋ณต์ œ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค + RefreshToken: + Invalid: ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ด ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค + NotFound: ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Instance: + NotFound: ์ธ์Šคํ„ด์Šค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + AlreadyExists: ์ธ์Šคํ„ด์Šค๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + NotChanged: ์ธ์Šคํ„ด์Šค๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + Org: + AlreadyExists: ์กฐ์ง ์ด๋ฆ„์ด ์ด๋ฏธ ์‚ฌ์šฉ ์ค‘์ž…๋‹ˆ๋‹ค + Invalid: ์กฐ์ง์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + AlreadyDeactivated: ์กฐ์ง์ด ์ด๋ฏธ ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค + AlreadyActive: ์กฐ์ง์ด ์ด๋ฏธ ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค + Empty: ์กฐ์ง์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + NotFound: ์กฐ์ง์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotChanged: ์กฐ์ง์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + DefaultOrgNotDeletable: ๊ธฐ๋ณธ ์กฐ์ง์€ ์‚ญ์ œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + ZitadelOrgNotDeletable: ZITADEL ํ”„๋กœ์ ํŠธ๊ฐ€ ํฌํ•จ๋œ ์กฐ์ง์€ ์‚ญ์ œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + InvalidDomain: ์œ ํšจํ•˜์ง€ ์•Š์€ ๋„๋ฉ”์ธ์ž…๋‹ˆ๋‹ค + DomainMissing: ๋„๋ฉ”์ธ์ด ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + DomainNotOnOrg: ์กฐ์ง์— ๋„๋ฉ”์ธ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + DomainNotVerified: ๋„๋ฉ”์ธ์ด ์ธ์ฆ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + DomainAlreadyVerified: ๋„๋ฉ”์ธ์ด ์ด๋ฏธ ์ธ์ฆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + DomainVerificationTypeInvalid: ๋„๋ฉ”์ธ ์ธ์ฆ ์œ ํ˜•์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + DomainVerificationMissing: ๋„๋ฉ”์ธ ์ธ์ฆ์ด ์•„์ง ์‹œ์ž‘๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + DomainVerificationFailed: ๋„๋ฉ”์ธ ์ธ์ฆ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค + DomainVerificationTXTNotFound: ๋„๋ฉ”์ธ์—์„œ _zitadel-challenge TXT ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. DNS ์„œ๋ฒ„์— ์ถ”๊ฐ€ํ–ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ์ƒˆ๋กœ์šด ๋ ˆ์ฝ”๋“œ๊ฐ€ ์ „ํŒŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์„ธ์š” + DomainVerificationTXTNoMatch: ๋„๋ฉ”์ธ์—์„œ _zitadel-challenge TXT ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฐพ์•˜์œผ๋‚˜ ์˜ฌ๋ฐ”๋ฅธ ํ† ํฐ ํ…์ŠคํŠธ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. DNS ์„œ๋ฒ„์— ์˜ฌ๋ฐ”๋ฅธ ํ† ํฐ์„ ์ถ”๊ฐ€ํ–ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ์ƒˆ๋กœ์šด ๋ ˆ์ฝ”๋“œ๊ฐ€ ์ „ํŒŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์„ธ์š” + DomainVerificationHTTPNotFound: ์˜ˆ์ƒ๋˜๋Š” URL์— ์ฑŒ๋ฆฐ์ง€๋ฅผ ํฌํ•จํ•˜๋Š” ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ํŒŒ์ผ์ด ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์— ์ฝ๊ธฐ ๊ถŒํ•œ์œผ๋กœ ์—…๋กœ๋“œ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š” + DomainVerificationHTTPNoMatch: ์˜ˆ์ƒ๋˜๋Š” URL์— ์ฑŒ๋ฆฐ์ง€ ํŒŒ์ผ์ด ์žˆ์ง€๋งŒ ์˜ฌ๋ฐ”๋ฅธ ํ† ํฐ ํ…์ŠคํŠธ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํŒŒ์ผ์˜ ๋‚ด์šฉ์„ ํ™•์ธํ•˜์„ธ์š” + DomainVerificationTimeout: DNS ์„œ๋ฒ„์— ๋Œ€ํ•œ ์ฟผ๋ฆฌ์—์„œ ์‹œ๊ฐ„ ์ดˆ๊ณผ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค + PrimaryDomainNotDeletable: ๊ธฐ๋ณธ ๋„๋ฉ”์ธ์€ ์‚ญ์ œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + DomainNotFound: ๋„๋ฉ”์ธ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + MemberIDMissing: ๊ตฌ์„ฑ์› ID๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + MemberNotFound: ์กฐ์ง ๊ตฌ์„ฑ์›์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + InvalidMember: ์กฐ์ง ๊ตฌ์„ฑ์›์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + UserIDMissing: ์‚ฌ์šฉ์ž ID๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + PolicyAlreadyExists: ์ •์ฑ…์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + PolicyNotExisting: ์ •์ฑ…์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + IdpInvalid: IDP ์„ค์ •์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + IdpNotExisting: IDP ์„ค์ •์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + OIDCConfigInvalid: OIDC IDP ์„ค์ •์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + IdpIsNotOIDC: IDP ์„ค์ •์ด OIDC ์œ ํ˜•์ด ์•„๋‹™๋‹ˆ๋‹ค + Domain: + AlreadyExists: ๋„๋ฉ”์ธ์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + InvalidCharacter: ๋„๋ฉ”์ธ์—๋Š” ์˜์ˆซ์ž, ., -๋งŒ ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค + EmptyString: ์œ ํšจํ•˜์ง€ ์•Š์€ ๋ฌธ์ž๋“ค์ด ๋น„์–ด ์žˆ๋Š” ๋ฌธ์ž์—ด๋กœ ๋Œ€์ฒด๋˜์—ˆ๊ณ  ๊ฒฐ๊ณผ ๋„๋ฉ”์ธ์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + IDP: + InvalidSearchQuery: ์ž˜๋ชป๋œ ๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ์ž…๋‹ˆ๋‹ค + ClientIDMissing: ClientID๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + TeamIDMissing: TeamID๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + KeyIDMissing: KeyID๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + PrivateKeyMissing: ๊ฐœ์ธ ํ‚ค๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + LoginPolicy: + NotFound: ๋กœ๊ทธ์ธ ์ •์ฑ…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Invalid: ๋กœ๊ทธ์ธ ์ •์ฑ…์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + RedirectURIInvalid: ๊ธฐ๋ณธ ๋ฆฌ๋””๋ ‰ํŠธ URI๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + NotExisting: ๋กœ๊ทธ์ธ ์ •์ฑ…์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + AlreadyExists: ๋กœ๊ทธ์ธ ์ •์ฑ…์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + IdpProviderAlreadyExisting: IDP ์ œ๊ณต์ž๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + IdpProviderNotExisting: IDP ์ œ๊ณต์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + RegistrationNotAllowed: ๋“ฑ๋ก์ด ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + UsernamePasswordNotAllowed: ์‚ฌ์šฉ์ž ์ด๋ฆ„/๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + MFA: + AlreadyExists: ๋‹ค์ค‘ ์ธ์ฆ์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + NotExisting: ๋‹ค์ค‘ ์ธ์ฆ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Unspecified: ๋‹ค์ค‘ ์ธ์ฆ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + MailTemplate: + NotFound: ๊ธฐ๋ณธ ๋ฉ”์ผ ํ…œํ”Œ๋ฆฟ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotChanged: ๊ธฐ๋ณธ ๋ฉ”์ผ ํ…œํ”Œ๋ฆฟ์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + AlreadyExists: ๊ธฐ๋ณธ ๋ฉ”์ผ ํ…œํ”Œ๋ฆฟ์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + Invalid: ๊ธฐ๋ณธ ๋ฉ”์ผ ํ…œํ”Œ๋ฆฟ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + CustomMessageText: + NotFound: ๊ธฐ๋ณธ ๋ฉ”์‹œ์ง€ ํ…์ŠคํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotChanged: ๊ธฐ๋ณธ ๋ฉ”์‹œ์ง€ ํ…์ŠคํŠธ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + AlreadyExists: ๊ธฐ๋ณธ ๋ฉ”์‹œ์ง€ ํ…์ŠคํŠธ๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + Invalid: ๊ธฐ๋ณธ ๋ฉ”์‹œ์ง€ ํ…์ŠคํŠธ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + PasswordComplexityPolicy: + NotFound: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก์„ฑ ์ •์ฑ…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Empty: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก์„ฑ ์ •์ฑ…์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + NotExisting: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก์„ฑ ์ •์ฑ…์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + AlreadyExists: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก์„ฑ ์ •์ฑ…์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + PasswordLockoutPolicy: + NotFound: ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž ๊ธˆ ์ •์ฑ…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Empty: ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž ๊ธˆ ์ •์ฑ…์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + NotExisting: ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž ๊ธˆ ์ •์ฑ…์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + AlreadyExists: ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž ๊ธˆ ์ •์ฑ…์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + PasswordAgePolicy: + NotFound: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์ •์ฑ…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Empty: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์ •์ฑ…์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + NotExisting: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์ •์ฑ…์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + AlreadyExists: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์ •์ฑ…์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + OrgIAMPolicy: + Empty: ์กฐ์ง IAM ์ •์ฑ…์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + NotExisting: ์กฐ์ง IAM ์ •์ฑ…์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + AlreadyExists: ์กฐ์ง IAM ์ •์ฑ…์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + NotificationPolicy: + NotFound: ์•Œ๋ฆผ ์ •์ฑ…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotChanged: ์•Œ๋ฆผ ์ •์ฑ…์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + AlreadyExists: ์•Œ๋ฆผ ์ •์ฑ…์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + LabelPolicy: + NotFound: ๊ฐœ์ธ ๋ผ๋ฒจ ์ •์ฑ…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotChanged: ๊ฐœ์ธ ๋ผ๋ฒจ ์ •์ฑ…์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + Project: + ProjectIDMissing: ํ”„๋กœ์ ํŠธ ID๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + AlreadyExists: ์กฐ์ง์— ํ”„๋กœ์ ํŠธ๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + OrgNotExisting: ์กฐ์ง์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + UserNotExisting: ์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + CouldNotGenerateClientSecret: ํด๋ผ์ด์–ธํŠธ ์‹œํฌ๋ฆฟ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Invalid: ํ”„๋กœ์ ํŠธ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + NotActive: ํ”„๋กœ์ ํŠธ๊ฐ€ ํ™œ์„ฑ ์ƒํƒœ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค + NotInactive: ํ”„๋กœ์ ํŠธ๊ฐ€ ๋น„ํ™œ์„ฑํ™” ์ƒํƒœ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค + NotFound: ํ”„๋กœ์ ํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + UserIDMissing: ์‚ฌ์šฉ์ž ID๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + Member: + NotFound: ํ”„๋กœ์ ํŠธ ๊ตฌ์„ฑ์›์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Invalid: ํ”„๋กœ์ ํŠธ ๊ตฌ์„ฑ์›์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + AlreadyExists: ํ”„๋กœ์ ํŠธ ๊ตฌ์„ฑ์›์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + NotExisting: ํ”„๋กœ์ ํŠธ ๊ตฌ์„ฑ์›์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + MinimumOneRoleNeeded: ์ตœ์†Œ ํ•˜๋‚˜์˜ ์—ญํ• ์ด ์ถ”๊ฐ€๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค + Role: + AlreadyExists: ์—ญํ• ์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + Invalid: ์—ญํ• ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + NotExisting: ์—ญํ• ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + IDMissing: ID๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + App: + AlreadyExists: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + NotFound: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Invalid: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + NotExisting: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + NotActive: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ํ™œ์„ฑ ์ƒํƒœ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค + NotInactive: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋น„ํ™œ์„ฑ ์ƒํƒœ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค + OIDCConfigInvalid: OIDC ์„ค์ •์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + APIConfigInvalid: API ์„ค์ •์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + SAMLConfigInvalid: SAML ์„ค์ •์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + IsNotOIDC: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด OIDC ์œ ํ˜•์ด ์•„๋‹™๋‹ˆ๋‹ค + IsNotAPI: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด API ์œ ํ˜•์ด ์•„๋‹™๋‹ˆ๋‹ค + IsNotSAML: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด SAML ์œ ํ˜•์ด ์•„๋‹™๋‹ˆ๋‹ค + SAMLMetadataMissing: SAML ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + SAMLMetadataFormat: SAML ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํ˜•์‹ ์˜ค๋ฅ˜ + SAMLEntityIDAlreadyExisting: SAML EntityID๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + OIDCAuthMethodNoSecret: ์„ ํƒํ•œ OIDC ์ธ์ฆ ๋ฐฉ๋ฒ•์—๋Š” ์‹œํฌ๋ฆฟ์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + APIAuthMethodNoSecret: ์„ ํƒํ•œ API ์ธ์ฆ ๋ฐฉ๋ฒ•์—๋Š” ์‹œํฌ๋ฆฟ์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + AuthMethodNoPrivateKeyJWT: ์„ ํƒํ•œ ์ธ์ฆ ๋ฐฉ๋ฒ•์—๋Š” ํ‚ค๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + ClientSecretInvalid: ํด๋ผ์ด์–ธํŠธ ์‹œํฌ๋ฆฟ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Key: + AlreadyExisting: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ‚ค๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + NotFound: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ‚ค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + RequiredFieldsMissing: ํ•„์š”ํ•œ ํ•„๋“œ๊ฐ€ ์ผ๋ถ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + Grant: + AlreadyExists: ํ”„๋กœ์ ํŠธ ๊ถŒํ•œ์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + NotFound: ๊ถŒํ•œ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Invalid: ํ”„๋กœ์ ํŠธ ๊ถŒํ•œ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + NotExisting: ํ”„๋กœ์ ํŠธ ๊ถŒํ•œ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + HasNotExistingRole: ํ”„๋กœ์ ํŠธ์— ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์—ญํ• ์ด ์žˆ์Šต๋‹ˆ๋‹ค + NotActive: ํ”„๋กœ์ ํŠธ ๊ถŒํ•œ์ด ํ™œ์„ฑ ์ƒํƒœ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค + NotInactive: ํ”„๋กœ์ ํŠธ ๊ถŒํ•œ์ด ๋น„ํ™œ์„ฑ ์ƒํƒœ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค + IAM: + NotFound: ์ธ์Šคํ„ด์Šค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋„๋ฉ”์ธ์ด ์˜ฌ๋ฐ”๋ฅธ์ง€ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. https://zitadel.com/docs/apis/introduction#domains ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š” + Member: + RolesNotChanged: ์—ญํ• ์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + MemberInvalid: ๊ตฌ์„ฑ์›์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + MemberAlreadyExisting: ๊ตฌ์„ฑ์›์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + MemberNotExisting: ๊ตฌ์„ฑ์›์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + IDMissing: ID๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + IAMProjectIDMissing: IAM ํ”„๋กœ์ ํŠธ ID๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + IamProjectAlreadySet: IAM ํ”„๋กœ์ ํŠธ ID๊ฐ€ ์ด๋ฏธ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค + IdpInvalid: IDP ์„ค์ •์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + IdpNotExisting: IDP ์„ค์ •์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + OIDCConfigInvalid: OIDC IDP ์„ค์ •์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + IdpIsNotOIDC: IDP ์„ค์ •์ด OIDC ์œ ํ˜•์ด ์•„๋‹™๋‹ˆ๋‹ค + LoginPolicyInvalid: ๋กœ๊ทธ์ธ ์ •์ฑ…์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + LoginPolicyNotExisting: ๋กœ๊ทธ์ธ ์ •์ฑ…์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + IdpProviderInvalid: IDP ์ œ๊ณต์ž๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + LoginPolicy: + NotFound: ๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ์ •์ฑ…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotChanged: ๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ์ •์ฑ…์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + NotExisting: ๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ์ •์ฑ…์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + AlreadyExists: ๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ์ •์ฑ…์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + RedirectURIInvalid: ๊ธฐ๋ณธ ๋ฆฌ๋””๋ ‰ํŠธ URI๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + MFA: + AlreadyExists: ๋‹ค์ค‘ ์ธ์ฆ์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + NotExisting: ๋‹ค์ค‘ ์ธ์ฆ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Unspecified: ๋‹ค์ค‘ ์ธ์ฆ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + IDP: + AlreadyExists: IDP ์ œ๊ณต์ž๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + NotExisting: IDP ์ œ๊ณต์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Invalid: IDP ์ œ๊ณต์ž๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + IDPConfig: + AlreadyExists: IDP ์„ค์ •์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + NotInactive: IDP ์„ค์ •์ด ๋น„ํ™œ์„ฑํ™” ์ƒํƒœ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค + NotActive: IDP ์„ค์ •์ด ํ™œ์„ฑ ์ƒํƒœ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค + LabelPolicy: + NotFound: ๊ธฐ๋ณธ ๊ฐœ์ธ ๋ผ๋ฒจ ์ •์ฑ…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotChanged: ๊ธฐ๋ณธ ๊ฐœ์ธ ๋ผ๋ฒจ ์ •์ฑ…์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + MailTemplate: + NotFound: ๊ธฐ๋ณธ ๋ฉ”์ผ ํ…œํ”Œ๋ฆฟ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotChanged: ๊ธฐ๋ณธ ๋ฉ”์ผ ํ…œํ”Œ๋ฆฟ์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + AlreadyExists: ๊ธฐ๋ณธ ๋ฉ”์ผ ํ…œํ”Œ๋ฆฟ์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + Invalid: ๊ธฐ๋ณธ ๋ฉ”์ผ ํ…œํ”Œ๋ฆฟ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + CustomMessageText: + NotFound: ๊ธฐ๋ณธ ๋ฉ”์‹œ์ง€ ํ…์ŠคํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotChanged: ๊ธฐ๋ณธ ๋ฉ”์‹œ์ง€ ํ…์ŠคํŠธ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + AlreadyExists: ๊ธฐ๋ณธ ๋ฉ”์‹œ์ง€ ํ…์ŠคํŠธ๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + Invalid: ๊ธฐ๋ณธ ๋ฉ”์‹œ์ง€ ํ…์ŠคํŠธ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + PasswordComplexityPolicy: + NotFound: ๊ธฐ๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก์„ฑ ์ •์ฑ…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotExisting: ๊ธฐ๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก์„ฑ ์ •์ฑ…์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + AlreadyExists: ๊ธฐ๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก์„ฑ ์ •์ฑ…์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + Empty: ๊ธฐ๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก์„ฑ ์ •์ฑ…์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + NotChanged: ๊ธฐ๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก์„ฑ ์ •์ฑ…์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + PasswordAgePolicy: + NotFound: ๊ธฐ๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์ •์ฑ…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotExisting: ๊ธฐ๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์ •์ฑ…์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + AlreadyExists: ๊ธฐ๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์ •์ฑ…์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + Empty: ๊ธฐ๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์ •์ฑ…์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + NotChanged: ๊ธฐ๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์ •์ฑ…์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + PasswordLockoutPolicy: + NotFound: ๊ธฐ๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž ๊ธˆ ์ •์ฑ…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotExisting: ๊ธฐ๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž ๊ธˆ ์ •์ฑ…์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + AlreadyExists: ๊ธฐ๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž ๊ธˆ ์ •์ฑ…์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + Empty: ๊ธฐ๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž ๊ธˆ ์ •์ฑ…์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + NotChanged: ๊ธฐ๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž ๊ธˆ ์ •์ฑ…์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + DomainPolicy: + NotFound: ์กฐ์ง IAM ์ •์ฑ…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Empty: ์กฐ์ง IAM ์ •์ฑ…์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + NotExisting: ์กฐ์ง IAM ์ •์ฑ…์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + AlreadyExists: ์กฐ์ง IAM ์ •์ฑ…์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + NotChanged: ์กฐ์ง IAM ์ •์ฑ…์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + NotificationPolicy: + NotFound: ๊ธฐ๋ณธ ์•Œ๋ฆผ ์ •์ฑ…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotChanged: ๊ธฐ๋ณธ ์•Œ๋ฆผ ์ •์ฑ…์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + AlreadyExists: ๊ธฐ๋ณธ ์•Œ๋ฆผ ์ •์ฑ…์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + Policy: + AlreadyExists: ์ •์ฑ…์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + Label: + Invalid: + PrimaryColor: ๊ธฐ๋ณธ ์ƒ‰์ƒ์ด ์œ ํšจํ•œ 16์ง„์ˆ˜ ์ƒ‰์ƒ ๊ฐ’์ด ์•„๋‹™๋‹ˆ๋‹ค + BackgroundColor: ๋ฐฐ๊ฒฝ ์ƒ‰์ƒ์ด ์œ ํšจํ•œ 16์ง„์ˆ˜ ์ƒ‰์ƒ ๊ฐ’์ด ์•„๋‹™๋‹ˆ๋‹ค + WarnColor: ๊ฒฝ๊ณ  ์ƒ‰์ƒ์ด ์œ ํšจํ•œ 16์ง„์ˆ˜ ์ƒ‰์ƒ ๊ฐ’์ด ์•„๋‹™๋‹ˆ๋‹ค + FontColor: ๊ธ€๊ผด ์ƒ‰์ƒ์ด ์œ ํšจํ•œ 16์ง„์ˆ˜ ์ƒ‰์ƒ ๊ฐ’์ด ์•„๋‹™๋‹ˆ๋‹ค + PrimaryColorDark: ๊ธฐ๋ณธ ์ƒ‰์ƒ(๋‹คํฌ ๋ชจ๋“œ)์ด ์œ ํšจํ•œ 16์ง„์ˆ˜ ์ƒ‰์ƒ ๊ฐ’์ด ์•„๋‹™๋‹ˆ๋‹ค + BackgroundColorDark: ๋ฐฐ๊ฒฝ ์ƒ‰์ƒ(๋‹คํฌ ๋ชจ๋“œ)์ด ์œ ํšจํ•œ 16์ง„์ˆ˜ ์ƒ‰์ƒ ๊ฐ’์ด ์•„๋‹™๋‹ˆ๋‹ค + WarnColorDark: ๊ฒฝ๊ณ  ์ƒ‰์ƒ(๋‹คํฌ ๋ชจ๋“œ)์ด ์œ ํšจํ•œ 16์ง„์ˆ˜ ์ƒ‰์ƒ ๊ฐ’์ด ์•„๋‹™๋‹ˆ๋‹ค + FontColorDark: ๊ธ€๊ผด ์ƒ‰์ƒ(๋‹คํฌ ๋ชจ๋“œ)์ด ์œ ํšจํ•œ 16์ง„์ˆ˜ ์ƒ‰์ƒ ๊ฐ’์ด ์•„๋‹™๋‹ˆ๋‹ค + UserGrant: + AlreadyExists: ์‚ฌ์šฉ์ž ๊ถŒํ•œ์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + NotFound: ์‚ฌ์šฉ์ž ๊ถŒํ•œ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Invalid: ์‚ฌ์šฉ์ž ๊ถŒํ•œ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + NotChanged: ์‚ฌ์šฉ์ž ๊ถŒํ•œ์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + IDMissing: ID๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + NotActive: ์‚ฌ์šฉ์ž ๊ถŒํ•œ์ด ํ™œ์„ฑ ์ƒํƒœ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค + NotInactive: ์‚ฌ์šฉ์ž ๊ถŒํ•œ์ด ๋น„ํ™œ์„ฑ ์ƒํƒœ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค + NoPermissionForProject: ์‚ฌ์šฉ์ž๊ฐ€ ์ด ํ”„๋กœ์ ํŠธ์— ๋Œ€ํ•œ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค + RoleKeyNotFound: ์—ญํ• ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Member: + AlreadyExists: ๊ตฌ์„ฑ์›์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + IDPConfig: + AlreadyExists: ๋™์ผํ•œ ์ด๋ฆ„์˜ IDP ์„ค์ •์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + NotExisting: IDP ์„ค์ •์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Changes: + NotFound: ๊ธฐ๋ก์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + AuditRetention: ๊ธฐ๋ก์ด ๊ฐ์‚ฌ ๋กœ๊ทธ ๋ณด์กด ๊ธฐ๊ฐ„์„ ์ดˆ๊ณผํ–ˆ์Šต๋‹ˆ๋‹ค + Token: + NotFound: ํ† ํฐ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Invalid: ํ† ํฐ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + UserSession: + NotFound: ์‚ฌ์šฉ์ž ์„ธ์…˜์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Key: + NotFound: ํ‚ค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + ExpireBeforeNow: ๋งŒ๋ฃŒ์ผ์ด ์ด๋ฏธ ๊ฒฝ๊ณผํ–ˆ์Šต๋‹ˆ๋‹ค + Login: + LoginPolicy: + MFA: + ForceAndNotConfigured: ๋‹ค์ค‘ ์ธ์ฆ์ด ํ•„์ˆ˜๋กœ ์„ค์ •๋˜์—ˆ์ง€๋งŒ ๊ฐ€๋Šฅํ•œ ์ œ๊ณต์ž๊ฐ€ ๊ตฌ์„ฑ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•˜์‹ญ์‹œ์˜ค. + Step: + Started: + AlreadyExists: ์ด๋ฏธ ์‹œ์ž‘๋œ ๋‹จ๊ณ„๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + Done: + AlreadyExists: ์ด๋ฏธ ์™„๋ฃŒ๋œ ๋‹จ๊ณ„๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + CustomText: + AlreadyExists: ์‚ฌ์šฉ์ž ์ •์˜ ํ…์ŠคํŠธ๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + Invalid: ์‚ฌ์šฉ์ž ์ •์˜ ํ…์ŠคํŠธ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + NotFound: ์‚ฌ์šฉ์ž ์ •์˜ ํ…์ŠคํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + TranslationFile: + ReadError: ๋ฒˆ์—ญ ํŒŒ์ผ์„ ์ฝ๋Š” ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ + MergeError: ๋ฒˆ์—ญ ํŒŒ์ผ์„ ์‚ฌ์šฉ์ž ์ •์˜ ๋ฒˆ์—ญ๊ณผ ๋ณ‘ํ•ฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotFound: ๋ฒˆ์—ญ ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Metadata: + NotFound: ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NoData: ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ชฉ๋ก์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + Invalid: ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + KeyNotExisting: ํ•˜๋‚˜ ์ด์ƒ์˜ ํ‚ค๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Action: + Invalid: ์ž‘์—…์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + NotFound: ์ž‘์—…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NotActive: ์ž‘์—…์ด ํ™œ์„ฑํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + NotInactive: ์ž‘์—…์ด ๋น„ํ™œ์„ฑํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + MaxAllowed: ์ถ”๊ฐ€ ํ™œ์„ฑ ์ž‘์—…์ด ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + NotEnabled: "\"์ž‘์—…\" ๊ธฐ๋Šฅ์ด ํ™œ์„ฑํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค" + Flow: + FlowTypeMissing: FlowType์ด ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + Empty: ํ”Œ๋กœ์šฐ๊ฐ€ ์ด๋ฏธ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค + WrongTriggerType: TriggerType์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + NoChanges: ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ์—†์Šต๋‹ˆ๋‹ค + ActionIDsNotExist: ActionIDs๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Query: + CloseRows: SQL ๋ฌธ์„ ์™„๋ฃŒํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + SQLStatement: SQL ๋ฌธ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + InvalidRequest: ์š”์ฒญ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + TooManyNestingLevels: ์ฟผ๋ฆฌ ์ค‘์ฒฉ ์ˆ˜์ค€์ด ๋„ˆ๋ฌด ๋งŽ์Šต๋‹ˆ๋‹ค (์ตœ๋Œ€ 20) + LimitExceeded: ์ œํ•œ์„ ์ดˆ๊ณผํ–ˆ์Šต๋‹ˆ๋‹ค + Quota: + AlreadyExists: ์ด ๋‹จ์œ„์— ๋Œ€ํ•œ ํ• ๋‹น๋Ÿ‰์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + NotFound: ์ด ๋‹จ์œ„์— ๋Œ€ํ•œ ํ• ๋‹น๋Ÿ‰์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Invalid: + CallURL: ํ• ๋‹น๋Ÿ‰ ํ˜ธ์ถœ URL์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Percent: ํ• ๋‹น๋Ÿ‰ ๋ฐฑ๋ถ„์œจ์ด 1 ๋ฏธ๋งŒ์ž…๋‹ˆ๋‹ค + Unimplemented: ์ด ๋‹จ์œ„์— ๋Œ€ํ•ด ํ• ๋‹น๋Ÿ‰์ด ๊ตฌํ˜„๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + Amount: ํ• ๋‹น๋Ÿ‰ ๊ธˆ์•ก์ด 1 ๋ฏธ๋งŒ์ž…๋‹ˆ๋‹ค + ResetInterval: ํ• ๋‹น๋Ÿ‰ ์žฌ์„ค์ • ๊ฐ„๊ฒฉ์ด 1๋ถ„๋ณด๋‹ค ์งง์Šต๋‹ˆ๋‹ค + Noop: ์•Œ๋ฆผ ์—†๋Š” ๋ฌด์ œํ•œ ํ• ๋‹น๋Ÿ‰์€ ํšจ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค + Access: + Exhausted: ์ธ์ฆ๋œ ์š”์ฒญ์— ๋Œ€ํ•œ ํ• ๋‹น๋Ÿ‰์ด ์†Œ์ง„๋˜์—ˆ์Šต๋‹ˆ๋‹ค + Execution: + Exhausted: ์‹คํ–‰ ์‹œ๊ฐ„์— ๋Œ€ํ•œ ํ• ๋‹น๋Ÿ‰์ด ์†Œ์ง„๋˜์—ˆ์Šต๋‹ˆ๋‹ค + LogStore: + Access: + StorageFailed: ์•ก์„ธ์Šค ๋กœ๊ทธ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค + ScanFailed: ์ธ์ฆ๋œ ์š”์ฒญ ์‚ฌ์šฉ๋Ÿ‰ ์กฐํšŒ ์‹คํŒจ + Execution: + StorageFailed: ์ž‘์—… ์‹คํ–‰ ๋กœ๊ทธ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค + ScanFailed: ์ž‘์—… ์‹คํ–‰ ์‹œ๊ฐ„ ์‚ฌ์šฉ๋Ÿ‰ ์กฐํšŒ ์‹คํŒจ + Session: + NotExisting: ์„ธ์…˜์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Terminated: ์„ธ์…˜์ด ์ด๋ฏธ ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + Expired: ์„ธ์…˜์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + PositiveLifetime: ์„ธ์…˜ ์ˆ˜๋ช…์€ 0๋ณด๋‹ค ์ž‘์•„์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค + Token: + Invalid: ์„ธ์…˜ ํ† ํฐ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + WebAuthN: + NoChallenge: WebAuthN ์ฑŒ๋ฆฐ์ง€๊ฐ€ ์—†๋Š” ์„ธ์…˜ + Intent: + IDPMissing: ์š”์ฒญ์—์„œ IDP ID๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + IDPInvalid: ์š”์ฒญ์— ๋Œ€ํ•œ IDP๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + ResponseInvalid: IDP ์‘๋‹ต์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + MissingSingleMappingAttribute: IDP ์‘๋‹ต์— ๋งคํ•‘ ์†์„ฑ์ด ํฌํ•จ๋˜์–ด ์žˆ์ง€ ์•Š๊ฑฐ๋‚˜ ๊ฐ’์ด ํ•˜๋‚˜ ์ด์ƒ ์žˆ์Šต๋‹ˆ๋‹ค + SuccessURLMissing: ์š”์ฒญ์— ์„ฑ๊ณต URL์ด ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + FailureURLMissing: ์š”์ฒญ์— ์‹คํŒจ URL์ด ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + StateMissing: ์š”์ฒญ์— ์ƒํƒœ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + NotStarted: ์˜๋„๊ฐ€ ์‹œ์ž‘๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜ ์ด๋ฏธ ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + NotSucceeded: ์˜๋„๊ฐ€ ์„ฑ๊ณตํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + TokenCreationFailed: ํ† ํฐ ์ƒ์„ฑ ์‹คํŒจ + InvalidToken: ์˜๋„ ํ† ํฐ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + OtherUser: ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๋ฅผ ์œ„ํ•œ ์˜๋„์ž…๋‹ˆ๋‹ค + AuthRequest: + AlreadyExists: ์ธ์ฆ ์š”์ฒญ์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + NotExisting: ์ธ์ฆ ์š”์ฒญ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + WrongLoginClient: ๋‹ค๋ฅธ ๋กœ๊ทธ์ธ ํด๋ผ์ด์–ธํŠธ์— ์˜ํ•ด ์ƒ์„ฑ๋œ ์ธ์ฆ ์š”์ฒญ + OIDCSession: + RefreshTokenInvalid: ์ƒˆ๋กœ ๊ณ ์นจ ํ† ํฐ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Token: + Invalid: ํ† ํฐ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Expired: ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + InvalidClient: ํ† ํฐ์ด ์ด ํด๋ผ์ด์–ธํŠธ์— ๋Œ€ํ•ด ๋ฐœํ–‰๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค + Feature: + NotExisting: ๊ธฐ๋Šฅ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + TypeNotSupported: ๊ธฐ๋Šฅ ์œ ํ˜•์ด ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + InvalidValue: ์ด ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด ์œ ํšจํ•˜์ง€ ์•Š์€ ๊ฐ’ + Target: + Invalid: ๋Œ€์ƒ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + NoTimeout: ๋Œ€์ƒ์— ํƒ€์ž„์•„์›ƒ์ด ์—†์Šต๋‹ˆ๋‹ค + InvalidURL: ๋Œ€์ƒ URL์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + NotFound: ๋Œ€์ƒ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Execution: + ConditionInvalid: ์‹คํ–‰ ์กฐ๊ฑด์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Invalid: ์‹คํ–‰์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + NotFound: ์‹คํ–‰์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + IncludeNotFound: ํฌํ•จ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + NoTargets: ์ •์˜๋œ ๋Œ€์ƒ์ด ์—†์Šต๋‹ˆ๋‹ค + Failed: ์‹คํ–‰ ์‹คํŒจ + ResponseIsNotValidJSON: ์‘๋‹ต์ด ์œ ํšจํ•œ JSON์ด ์•„๋‹™๋‹ˆ๋‹ค + UserSchema: + NotEnabled: "\"์‚ฌ์šฉ์ž ์Šคํ‚ค๋งˆ\" ๊ธฐ๋Šฅ์ด ํ™œ์„ฑํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค" + Type: + Missing: ์‚ฌ์šฉ์ž ์Šคํ‚ค๋งˆ ์œ ํ˜•์ด ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + AlreadyExists: ์‚ฌ์šฉ์ž ์Šคํ‚ค๋งˆ ์œ ํ˜•์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค + Authenticator: + Invalid: ์ธ์ฆ๊ธฐ ์œ ํ˜•์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + NotActive: ์‚ฌ์šฉ์ž ์Šคํ‚ค๋งˆ๊ฐ€ ํ™œ์„ฑ ์ƒํƒœ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค + NotInactive: ์‚ฌ์šฉ์ž ์Šคํ‚ค๋งˆ๊ฐ€ ๋น„ํ™œ์„ฑ ์ƒํƒœ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค + NotExists: ์‚ฌ์šฉ์ž ์Šคํ‚ค๋งˆ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + ID: + Missing: ์‚ฌ์šฉ์ž ์Šคํ‚ค๋งˆ ID๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + Invalid: ์‚ฌ์šฉ์ž ์Šคํ‚ค๋งˆ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Data: + Invalid: ์‚ฌ์šฉ์ž ์Šคํ‚ค๋งˆ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + TokenExchange: + FeatureDisabled: ํ† ํฐ ๊ตํ™˜ ๊ธฐ๋Šฅ์ด ์ธ์Šคํ„ด์Šค์—์„œ ๋น„ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. https://zitadel.com/docs/apis/resources/feature_service_v2/feature-service-set-instance-features + Token: + Missing: ํ† ํฐ์ด ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + Invalid: ํ† ํฐ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + TypeMissing: ํ† ํฐ ์œ ํ˜•์ด ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + TypeNotAllowed: ํ—ˆ์šฉ๋˜์ง€ ์•Š๋Š” ํ† ํฐ ์œ ํ˜•์ž…๋‹ˆ๋‹ค + TypeNotSupported: ์ง€์›๋˜์ง€ ์•Š๋Š” ํ† ํฐ ์œ ํ˜•์ž…๋‹ˆ๋‹ค + NotForAPI: API์— ๋Œ€ํ•ด ๋Œ€๋ฆฌ ์ธ์ฆ๋œ ํ† ํฐ์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Impersonation: + PolicyDisabled: ์ธ์Šคํ„ด์Šค ๋ณด์•ˆ ์ •์ฑ…์—์„œ ๋Œ€๋ฆฌ ์ธ์ฆ์ด ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค + WebKey: + ActiveDelete: ํ™œ์„ฑ ์›น ํ‚ค๋ฅผ ์‚ญ์ œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + Config: ์›น ํ‚ค ์„ค์ •์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + Duplicate: ์›น ํ‚ค ID๊ฐ€ ๊ณ ์œ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + FeatureDisabled: ์›น ํ‚ค ๊ธฐ๋Šฅ์ด ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค + NoActive: ํ™œ์„ฑ ์›น ํ‚ค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค + NotFound: ์›น ํ‚ค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + +AggregateTypes: + action: ์ž‘์—… + instance: ์ธ์Šคํ„ด์Šค + key_pair: ํ‚ค ์Œ + org: ์กฐ์ง + project: ํ”„๋กœ์ ํŠธ + user: ์‚ฌ์šฉ์ž + usergrant: ์‚ฌ์šฉ์ž ๊ถŒํ•œ + quota: ํ• ๋‹น๋Ÿ‰ + feature: ๊ธฐ๋Šฅ + target: ๋Œ€์ƒ + execution: ์‹คํ–‰ + user_schema: ์‚ฌ์šฉ์ž ์Šคํ‚ค๋งˆ + auth_request: ์ธ์ฆ ์š”์ฒญ + device_auth: ๋””๋ฐ”์ด์Šค ์ธ์ฆ + idpintent: IDP ์˜๋„ + limits: ์ œํ•œ + milestone: ๋งˆ์ผ์Šคํ†ค + oidc_session: OIDC ์„ธ์…˜ + restrictions: ์ œํ•œ ์‚ฌํ•ญ + system: ์‹œ์Šคํ…œ + session: ์„ธ์…˜ + web_key: ์›น ํ‚ค + +EventTypes: + execution: + set: ์‹คํ–‰ ์„ค์ •๋จ + removed: ์‹คํ–‰ ์‚ญ์ œ๋จ + target: + added: ๋Œ€์ƒ ์ƒ์„ฑ๋จ + changed: ๋Œ€์ƒ ๋ณ€๊ฒฝ๋จ + removed: ๋Œ€์ƒ ์‚ญ์ œ๋จ + user: + added: ์‚ฌ์šฉ์ž ์ถ”๊ฐ€๋จ + selfregistered: ์‚ฌ์šฉ์ž๊ฐ€ ์ž์ฒด ๋“ฑ๋กํ•จ + initialization: + code: + added: ์ดˆ๊ธฐํ™” ์ฝ”๋“œ ์ƒ์„ฑ๋จ + sent: ์ดˆ๊ธฐํ™” ์ฝ”๋“œ ์ „์†ก๋จ + check: + succeeded: ์ดˆ๊ธฐํ™” ํ™•์ธ ์„ฑ๊ณต + failed: ์ดˆ๊ธฐํ™” ํ™•์ธ ์‹คํŒจ + token: + added: ์•ก์„ธ์Šค ํ† ํฐ ์ƒ์„ฑ๋จ + v2.added: ์•ก์„ธ์Šค ํ† ํฐ ์ƒ์„ฑ๋จ + removed: ์•ก์„ธ์Šค ํ† ํฐ ์‚ญ์ œ๋จ + impersonated: ์‚ฌ์šฉ์ž ๋Œ€๋ฆฌ ๋กœ๊ทธ์ธ๋จ + username: + reserved: ์‚ฌ์šฉ์ž ์ด๋ฆ„ ์˜ˆ์•ฝ๋จ + released: ์‚ฌ์šฉ์ž ์ด๋ฆ„ ํ•ด์ œ๋จ + changed: ์‚ฌ์šฉ์ž ์ด๋ฆ„ ๋ณ€๊ฒฝ๋จ + email: + reserved: ์ด๋ฉ”์ผ ์ฃผ์†Œ ์˜ˆ์•ฝ๋จ + released: ์ด๋ฉ”์ผ ์ฃผ์†Œ ํ•ด์ œ๋จ + changed: ์ด๋ฉ”์ผ ์ฃผ์†Œ ๋ณ€๊ฒฝ๋จ + verified: ์ด๋ฉ”์ผ ์ฃผ์†Œ ์ธ์ฆ๋จ + verification: + failed: ์ด๋ฉ”์ผ ์ฃผ์†Œ ์ธ์ฆ ์‹คํŒจ + code: + added: ์ด๋ฉ”์ผ ์ธ์ฆ ์ฝ”๋“œ ์ƒ์„ฑ๋จ + sent: ์ด๋ฉ”์ผ ์ธ์ฆ ์ฝ”๋“œ ์ „์†ก๋จ + machine: + added: ๊ธฐ์ˆ  ์‚ฌ์šฉ์ž ์ถ”๊ฐ€๋จ + changed: ๊ธฐ์ˆ  ์‚ฌ์šฉ์ž ๋ณ€๊ฒฝ๋จ + key: + added: ํ‚ค ์ถ”๊ฐ€๋จ + removed: ํ‚ค ์‚ญ์ œ๋จ + secret: + set: ์‹œํฌ๋ฆฟ ์„ค์ •๋จ + updated: ์‹œํฌ๋ฆฟ ํ•ด์‹œ ์—…๋ฐ์ดํŠธ๋จ + removed: ์‹œํฌ๋ฆฟ ์‚ญ์ œ๋จ + check: + succeeded: ์‹œํฌ๋ฆฟ ํ™•์ธ ์„ฑ๊ณต + failed: ์‹œํฌ๋ฆฟ ํ™•์ธ ์‹คํŒจ + human: + added: ์‚ฌ์šฉ์ž ์ถ”๊ฐ€๋จ + selfregistered: ์‚ฌ์šฉ์ž๊ฐ€ ์ž์ฒด ๋“ฑ๋กํ•จ + avatar: + added: ์•„๋ฐ”ํƒ€ ์ถ”๊ฐ€๋จ + removed: ์•„๋ฐ”ํƒ€ ์‚ญ์ œ๋จ + initialization: + code: + added: ์ดˆ๊ธฐํ™” ์ฝ”๋“œ ์ƒ์„ฑ๋จ + sent: ์ดˆ๊ธฐํ™” ์ฝ”๋“œ ์ „์†ก๋จ + check: + succeeded: ์ดˆ๊ธฐํ™” ํ™•์ธ ์„ฑ๊ณต + failed: ์ดˆ๊ธฐํ™” ํ™•์ธ ์‹คํŒจ + invite: + code: + added: ์ดˆ๋Œ€ ์ฝ”๋“œ ์ƒ์„ฑ๋จ + sent: ์ดˆ๋Œ€ ์ฝ”๋“œ ์ „์†ก๋จ + check: + succeeded: ์ดˆ๋Œ€ ํ™•์ธ ์„ฑ๊ณต + failed: ์ดˆ๋Œ€ ํ™•์ธ ์‹คํŒจ + username: + reserved: ์‚ฌ์šฉ์ž ์ด๋ฆ„ ์˜ˆ์•ฝ๋จ + released: ์‚ฌ์šฉ์ž ์ด๋ฆ„ ํ•ด์ œ๋จ + email: + changed: ์ด๋ฉ”์ผ ์ฃผ์†Œ ๋ณ€๊ฒฝ๋จ + verified: ์ด๋ฉ”์ผ ์ฃผ์†Œ ์ธ์ฆ๋จ + verification: + failed: ์ด๋ฉ”์ผ ์ฃผ์†Œ ์ธ์ฆ ์‹คํŒจ + code: + added: ์ด๋ฉ”์ผ ์ธ์ฆ ์ฝ”๋“œ ์ƒ์„ฑ๋จ + sent: ์ด๋ฉ”์ผ ์ธ์ฆ ์ฝ”๋“œ ์ „์†ก๋จ + password: + changed: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ๋จ + code: + added: ๋น„๋ฐ€๋ฒˆํ˜ธ ์ฝ”๋“œ ์ƒ์„ฑ๋จ + sent: ๋น„๋ฐ€๋ฒˆํ˜ธ ์ฝ”๋“œ ์ „์†ก๋จ + check: + succeeded: ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ ์„ฑ๊ณต + failed: ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ ์‹คํŒจ + change: + sent: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์ „์†ก๋จ + hash: + updated: ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹œ ์—…๋ฐ์ดํŠธ๋จ + externallogin: + check: + succeeded: ์™ธ๋ถ€ ๋กœ๊ทธ์ธ ์„ฑ๊ณต + externalidp: + added: ์™ธ๋ถ€ IDP ์ถ”๊ฐ€๋จ + removed: ์™ธ๋ถ€ IDP ์‚ญ์ œ๋จ + cascade: + removed: ์™ธ๋ถ€ IDP ์—ฐ์‡„ ์‚ญ์ œ๋จ + id: + migrated: ์™ธ๋ถ€ IDP์˜ ์‚ฌ์šฉ์ž ID๊ฐ€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๋จ + phone: + changed: ์ „ํ™”๋ฒˆํ˜ธ ๋ณ€๊ฒฝ๋จ + verified: ์ „ํ™”๋ฒˆํ˜ธ ์ธ์ฆ๋จ + verification: + failed: ์ „ํ™”๋ฒˆํ˜ธ ์ธ์ฆ ์‹คํŒจ + code: + added: ์ „ํ™”๋ฒˆํ˜ธ ์ฝ”๋“œ ์ƒ์„ฑ๋จ + sent: ์ „ํ™”๋ฒˆํ˜ธ ์ฝ”๋“œ ์ „์†ก๋จ + removed: ์ „ํ™”๋ฒˆํ˜ธ ์‚ญ์ œ๋จ + profile: + changed: ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ๋ณ€๊ฒฝ๋จ + address: + changed: ์‚ฌ์šฉ์ž ์ฃผ์†Œ ๋ณ€๊ฒฝ๋จ + mfa: + otp: + added: ๋‹ค์ค‘์ธ์ฆ OTP ์ถ”๊ฐ€๋จ + verified: ๋‹ค์ค‘์ธ์ฆ OTP ์ธ์ฆ๋จ + removed: ๋‹ค์ค‘์ธ์ฆ OTP ์‚ญ์ œ๋จ + check: + succeeded: ๋‹ค์ค‘์ธ์ฆ OTP ํ™•์ธ ์„ฑ๊ณต + failed: ๋‹ค์ค‘์ธ์ฆ OTP ํ™•์ธ ์‹คํŒจ + sms: + added: ๋‹ค์ค‘์ธ์ฆ OTP SMS ์ถ”๊ฐ€๋จ + removed: ๋‹ค์ค‘์ธ์ฆ OTP SMS ์‚ญ์ œ๋จ + code: + added: ๋‹ค์ค‘์ธ์ฆ OTP SMS ์ฝ”๋“œ ์ถ”๊ฐ€๋จ + sent: ๋‹ค์ค‘์ธ์ฆ OTP SMS ์ฝ”๋“œ ์ „์†ก๋จ + check: + succeeded: ๋‹ค์ค‘์ธ์ฆ OTP SMS ํ™•์ธ ์„ฑ๊ณต + failed: ๋‹ค์ค‘์ธ์ฆ OTP SMS ํ™•์ธ ์‹คํŒจ + email: + added: ๋‹ค์ค‘์ธ์ฆ OTP ์ด๋ฉ”์ผ ์ถ”๊ฐ€๋จ + removed: ๋‹ค์ค‘์ธ์ฆ OTP ์ด๋ฉ”์ผ ์‚ญ์ œ๋จ + code: + added: ๋‹ค์ค‘์ธ์ฆ OTP ์ด๋ฉ”์ผ ์ฝ”๋“œ ์ถ”๊ฐ€๋จ + sent: ๋‹ค์ค‘์ธ์ฆ OTP ์ด๋ฉ”์ผ ์ฝ”๋“œ ์ „์†ก๋จ + check: + succeeded: ๋‹ค์ค‘์ธ์ฆ OTP ์ด๋ฉ”์ผ ํ™•์ธ ์„ฑ๊ณต + failed: ๋‹ค์ค‘์ธ์ฆ OTP ์ด๋ฉ”์ผ ํ™•์ธ ์‹คํŒจ + u2f: + token: + added: ๋‹ค์ค‘์ธ์ฆ U2F ํ† ํฐ ์ถ”๊ฐ€๋จ + verified: ๋‹ค์ค‘์ธ์ฆ U2F ํ† ํฐ ์ธ์ฆ๋จ + removed: ๋‹ค์ค‘์ธ์ฆ U2F ํ† ํฐ ์‚ญ์ œ๋จ + begin: + login: ๋‹ค์ค‘์ธ์ฆ U2F ํ™•์ธ ์‹œ์ž‘๋จ + check: + succeeded: ๋‹ค์ค‘์ธ์ฆ U2F ํ™•์ธ ์„ฑ๊ณต + failed: ๋‹ค์ค‘์ธ์ฆ U2F ํ™•์ธ ์‹คํŒจ + signcount: + changed: ๋‹ค์ค‘์ธ์ฆ U2F ํ† ํฐ์˜ ์ฒดํฌ์„ฌ์ด ๋ณ€๊ฒฝ๋จ + init: + skipped: ๋‹ค์ค‘์ธ์ฆ ์ดˆ๊ธฐํ™” ๊ฑด๋„ˆ๋œ€ + passwordless: + token: + added: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ ํ† ํฐ ์ถ”๊ฐ€๋จ + verified: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ ํ† ํฐ ์ธ์ฆ๋จ + removed: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ ํ† ํฐ ์‚ญ์ œ๋จ + begin: + login: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ ํ™•์ธ ์‹œ์ž‘๋จ + check: + succeeded: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ ํ™•์ธ ์„ฑ๊ณต + failed: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ ํ™•์ธ ์‹คํŒจ + signcount: + changed: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ ํ† ํฐ์˜ ์ฒดํฌ์„ฌ์ด ๋ณ€๊ฒฝ๋จ + initialization: + code: + added: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์ดˆ๊ธฐํ™” ์ฝ”๋“œ ์ถ”๊ฐ€๋จ + sent: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์ดˆ๊ธฐํ™” ์ฝ”๋“œ ์ „์†ก๋จ + requested: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์ดˆ๊ธฐํ™” ์ฝ”๋“œ ์š”์ฒญ๋จ + check: + succeeded: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์ดˆ๊ธฐํ™” ์ฝ”๋“œ ํ™•์ธ ์„ฑ๊ณต + failed: ๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ์ดˆ๊ธฐํ™” ์ฝ”๋“œ ํ™•์ธ ์‹คํŒจ + signed: + out: ์‚ฌ์šฉ์ž ๋กœ๊ทธ์•„์›ƒ๋จ + refresh: + token: + added: ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ์ƒ์„ฑ๋จ + renewed: ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ๊ฐฑ์‹ ๋จ + removed: ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ์‚ญ์ œ๋จ + locked: ์‚ฌ์šฉ์ž ์ž ๊ธˆ๋จ + unlocked: ์‚ฌ์šฉ์ž ์ž ๊ธˆ ํ•ด์ œ๋จ + deactivated: ์‚ฌ์šฉ์ž ๋น„ํ™œ์„ฑํ™”๋จ + reactivated: ์‚ฌ์šฉ์ž ์žฌํ™œ์„ฑํ™”๋จ + removed: ์‚ฌ์šฉ์ž ์‚ญ์ œ๋จ + password: + changed: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ๋จ + code: + added: ๋น„๋ฐ€๋ฒˆํ˜ธ ์ฝ”๋“œ ์ƒ์„ฑ๋จ + sent: ๋น„๋ฐ€๋ฒˆํ˜ธ ์ฝ”๋“œ ์ „์†ก๋จ + check: + succeeded: ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ ์„ฑ๊ณต + failed: ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ ์‹คํŒจ + phone: + changed: ์ „ํ™”๋ฒˆํ˜ธ ๋ณ€๊ฒฝ๋จ + verified: ์ „ํ™”๋ฒˆํ˜ธ ์ธ์ฆ๋จ + verification: + failed: ์ „ํ™”๋ฒˆํ˜ธ ์ธ์ฆ ์‹คํŒจ + code: + added: ์ „ํ™”๋ฒˆํ˜ธ ์ฝ”๋“œ ์ƒ์„ฑ๋จ + sent: ์ „ํ™”๋ฒˆํ˜ธ ์ฝ”๋“œ ์ „์†ก๋จ + + profile: + changed: ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ๋ณ€๊ฒฝ๋จ + address: + changed: ์‚ฌ์šฉ์ž ์ฃผ์†Œ ๋ณ€๊ฒฝ๋จ + mfa: + otp: + added: ๋‹ค์ค‘์ธ์ฆ OTP ์ถ”๊ฐ€๋จ + verified: ๋‹ค์ค‘์ธ์ฆ OTP ์ธ์ฆ๋จ + removed: ๋‹ค์ค‘์ธ์ฆ OTP ์‚ญ์ œ๋จ + check: + succeeded: ๋‹ค์ค‘์ธ์ฆ OTP ํ™•์ธ ์„ฑ๊ณต + failed: ๋‹ค์ค‘์ธ์ฆ OTP ํ™•์ธ ์‹คํŒจ + init: + skipped: ๋‹ค์ค‘์ธ์ฆ OTP ์ดˆ๊ธฐํ™” ๊ฑด๋„ˆ๋œ€ + init: + skipped: ๋‹ค์ค‘์ธ์ฆ ์ดˆ๊ธฐํ™” ๊ฑด๋„ˆ๋œ€ + signed: + out: ์‚ฌ์šฉ์ž ๋กœ๊ทธ์•„์›ƒ๋จ + grant: + added: ๊ถŒํ•œ ์ถ”๊ฐ€๋จ + changed: ๊ถŒํ•œ ๋ณ€๊ฒฝ๋จ + removed: ๊ถŒํ•œ ์‚ญ์ œ๋จ + deactivated: ๊ถŒํ•œ ๋น„ํ™œ์„ฑํ™”๋จ + reactivated: ๊ถŒํ•œ ์žฌํ™œ์„ฑํ™”๋จ + reserved: ๊ถŒํ•œ ์˜ˆ์•ฝ๋จ + released: ๊ถŒํ•œ ํ•ด์ œ๋จ + cascade: + removed: ๊ถŒํ•œ ์—ฐ์‡„ ์‚ญ์ œ๋จ + changed: ๊ถŒํ•œ ๋ณ€๊ฒฝ๋จ + metadata: + set: ์‚ฌ์šฉ์ž ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์„ค์ •๋จ + removed: ์‚ฌ์šฉ์ž ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์‚ญ์ œ๋จ + removed.all: ๋ชจ๋“  ์‚ฌ์šฉ์ž ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์‚ญ์ œ๋จ + domain: + claimed: ๋„๋ฉ”์ธ ํด๋ ˆ์ž„๋จ + claimed.sent: ๋„๋ฉ”์ธ ํด๋ ˆ์ž„ ์•Œ๋ฆผ ์ „์†ก๋จ + pat: + added: ๊ฐœ์ธ ์•ก์„ธ์Šค ํ† ํฐ ์ถ”๊ฐ€๋จ + removed: ๊ฐœ์ธ ์•ก์„ธ์Šค ํ† ํฐ ์‚ญ์ œ๋จ + org: + added: ์กฐ์ง ์ถ”๊ฐ€๋จ + changed: ์กฐ์ง ๋ณ€๊ฒฝ๋จ + deactivated: ์กฐ์ง ๋น„ํ™œ์„ฑํ™”๋จ + reactivated: ์กฐ์ง ์žฌํ™œ์„ฑํ™”๋จ + removed: ์กฐ์ง ์‚ญ์ œ๋จ + domain: + added: ๋„๋ฉ”์ธ ์ถ”๊ฐ€๋จ + verification: + added: ๋„๋ฉ”์ธ ์ธ์ฆ ์ถ”๊ฐ€๋จ + failed: ๋„๋ฉ”์ธ ์ธ์ฆ ์‹คํŒจ + verified: ๋„๋ฉ”์ธ ์ธ์ฆ๋จ + removed: ๋„๋ฉ”์ธ ์‚ญ์ œ๋จ + primary: + set: ๊ธฐ๋ณธ ๋„๋ฉ”์ธ ์„ค์ •๋จ + reserved: ๋„๋ฉ”์ธ ์˜ˆ์•ฝ๋จ + released: ๋„๋ฉ”์ธ ํ•ด์ œ๋จ + name: + reserved: ์กฐ์ง ์ด๋ฆ„ ์˜ˆ์•ฝ๋จ + released: ์กฐ์ง ์ด๋ฆ„ ํ•ด์ œ๋จ + member: + added: ์กฐ์ง ๋ฉค๋ฒ„ ์ถ”๊ฐ€๋จ + changed: ์กฐ์ง ๋ฉค๋ฒ„ ๋ณ€๊ฒฝ๋จ + removed: ์กฐ์ง ๋ฉค๋ฒ„ ์‚ญ์ œ๋จ + cascade: + removed: ์กฐ์ง ๋ฉค๋ฒ„ ์—ฐ์‡„ ์‚ญ์ œ๋จ + iam: + policy: + added: ์‹œ์Šคํ…œ ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ์‹œ์Šคํ…œ ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + removed: ์‹œ์Šคํ…œ ์ •์ฑ… ์‚ญ์ œ๋จ + idp: + config: + added: IDP ์„ค์ • ์ถ”๊ฐ€๋จ + changed: IDP ์„ค์ • ๋ณ€๊ฒฝ๋จ + removed: IDP ์„ค์ • ์‚ญ์ œ๋จ + deactivated: IDP ์„ค์ • ๋น„ํ™œ์„ฑํ™”๋จ + reactivated: IDP ์„ค์ • ์žฌํ™œ์„ฑํ™”๋จ + oidc: + config: + added: OIDC IDP ์„ค์ • ์ถ”๊ฐ€๋จ + changed: OIDC IDP ์„ค์ • ๋ณ€๊ฒฝ๋จ + saml: + config: + added: SAML IDP ์„ค์ • ์ถ”๊ฐ€๋จ + changed: SAML IDP ์„ค์ • ๋ณ€๊ฒฝ๋จ + jwt: + config: + added: JWT IDP ์„ค์ • ์ถ”๊ฐ€๋จ + changed: JWT IDP ์„ค์ • ๋ณ€๊ฒฝ๋จ + customtext: + set: ์‚ฌ์šฉ์ž ์ง€์ • ํ…์ŠคํŠธ ์„ค์ •๋จ + removed: ์‚ฌ์šฉ์ž ์ง€์ • ํ…์ŠคํŠธ ์‚ญ์ œ๋จ + template: + removed: ์‚ฌ์šฉ์ž ์ง€์ • ํ…œํ”Œ๋ฆฟ ์‚ญ์ œ๋จ + policy: + login: + added: ๋กœ๊ทธ์ธ ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ๋กœ๊ทธ์ธ ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + removed: ๋กœ๊ทธ์ธ ์ •์ฑ… ์‚ญ์ œ๋จ + idpprovider: + added: ๋กœ๊ทธ์ธ ์ •์ฑ…์— IDP ์ถ”๊ฐ€๋จ + removed: ๋กœ๊ทธ์ธ ์ •์ฑ…์—์„œ IDP ์‚ญ์ œ๋จ + cascade: + removed: ๋กœ๊ทธ์ธ ์ •์ฑ…์—์„œ ์—ฐ์‡„ ์‚ญ์ œ๋จ + secondfactor: + added: ๋กœ๊ทธ์ธ ์ •์ฑ…์— 2์ฐจ ์ธ์ฆ ์ถ”๊ฐ€๋จ + removed: ๋กœ๊ทธ์ธ ์ •์ฑ…์—์„œ 2์ฐจ ์ธ์ฆ ์‚ญ์ œ๋จ + multifactor: + added: ๋กœ๊ทธ์ธ ์ •์ฑ…์— ๋‹ค์ค‘์ธ์ฆ ์ถ”๊ฐ€๋จ + removed: ๋กœ๊ทธ์ธ ์ •์ฑ…์—์„œ ๋‹ค์ค‘์ธ์ฆ ์‚ญ์ œ๋จ + password: + complexity: + added: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก๋„ ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก๋„ ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + removed: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก๋„ ์ •์ฑ… ์‚ญ์ œ๋จ + age: + added: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + removed: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์ •์ฑ… ์‚ญ์ œ๋จ + lockout: + added: ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž ๊ธˆ ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž ๊ธˆ ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + removed: ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž ๊ธˆ ์ •์ฑ… ์‚ญ์ œ๋จ + label: + added: ๋ ˆ์ด๋ธ” ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ๋ ˆ์ด๋ธ” ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + activated: ๋ ˆ์ด๋ธ” ์ •์ฑ… ํ™œ์„ฑํ™”๋จ + removed: ๋ ˆ์ด๋ธ” ์ •์ฑ… ์‚ญ์ œ๋จ + logo: + added: ๋ ˆ์ด๋ธ” ์ •์ฑ…์— ๋กœ๊ณ  ์ถ”๊ฐ€๋จ + removed: ๋ ˆ์ด๋ธ” ์ •์ฑ…์—์„œ ๋กœ๊ณ  ์‚ญ์ œ๋จ + dark: + added: ๋ ˆ์ด๋ธ” ์ •์ฑ…์— ๋‹คํฌ ๋ชจ๋“œ ๋กœ๊ณ  ์ถ”๊ฐ€๋จ + removed: ๋ ˆ์ด๋ธ” ์ •์ฑ…์—์„œ ๋‹คํฌ ๋ชจ๋“œ ๋กœ๊ณ  ์‚ญ์ œ๋จ + icon: + added: ๋ ˆ์ด๋ธ” ์ •์ฑ…์— ์•„์ด์ฝ˜ ์ถ”๊ฐ€๋จ + removed: ๋ ˆ์ด๋ธ” ์ •์ฑ…์—์„œ ์•„์ด์ฝ˜ ์‚ญ์ œ๋จ + dark: + added: ๋ ˆ์ด๋ธ” ์ •์ฑ…์— ๋‹คํฌ ๋ชจ๋“œ ์•„์ด์ฝ˜ ์ถ”๊ฐ€๋จ + removed: ๋ ˆ์ด๋ธ” ์ •์ฑ…์—์„œ ๋‹คํฌ ๋ชจ๋“œ ์•„์ด์ฝ˜ ์‚ญ์ œ๋จ + font: + added: ๋ ˆ์ด๋ธ” ์ •์ฑ…์— ํฐํŠธ ์ถ”๊ฐ€๋จ + removed: ๋ ˆ์ด๋ธ” ์ •์ฑ…์—์„œ ํฐํŠธ ์‚ญ์ œ๋จ + assets: + removed: ๋ ˆ์ด๋ธ” ์ •์ฑ…์—์„œ ์ž์‚ฐ ์‚ญ์ œ๋จ + privacy: + added: ๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ๋ฐฉ์นจ ๋ฐ ์ด์šฉ ์•ฝ๊ด€ ์ถ”๊ฐ€๋จ + changed: ๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ๋ฐฉ์นจ ๋ฐ ์ด์šฉ ์•ฝ๊ด€ ๋ณ€๊ฒฝ๋จ + removed: ๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ๋ฐฉ์นจ ๋ฐ ์ด์šฉ ์•ฝ๊ด€ ์‚ญ์ œ๋จ + domain: + added: ๋„๋ฉ”์ธ ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ๋„๋ฉ”์ธ ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + removed: ๋„๋ฉ”์ธ ์ •์ฑ… ์‚ญ์ œ๋จ + lockout: + added: ์ž ๊ธˆ ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ์ž ๊ธˆ ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + removed: ์ž ๊ธˆ ์ •์ฑ… ์‚ญ์ œ๋จ + notification: + added: ์•Œ๋ฆผ ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ์•Œ๋ฆผ ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + removed: ์•Œ๋ฆผ ์ •์ฑ… ์‚ญ์ œ๋จ + flow: + trigger_actions: + set: ์ž‘์—… ์„ค์ •๋จ + cascade: + removed: ์—ฐ์‡„ ์ž‘์—… ์‚ญ์ œ๋จ + removed: ์ž‘์—… ์‚ญ์ œ๋จ + cleared: ํ”Œ๋กœ์šฐ ์ดˆ๊ธฐํ™”๋จ + mail: + template: + added: ์ด๋ฉ”์ผ ํ…œํ”Œ๋ฆฟ ์ถ”๊ฐ€๋จ + changed: ์ด๋ฉ”์ผ ํ…œํ”Œ๋ฆฟ ๋ณ€๊ฒฝ๋จ + removed: ์ด๋ฉ”์ผ ํ…œํ”Œ๋ฆฟ ์‚ญ์ œ๋จ + text: + added: ์ด๋ฉ”์ผ ํ…์ŠคํŠธ ์ถ”๊ฐ€๋จ + changed: ์ด๋ฉ”์ผ ํ…์ŠคํŠธ ๋ณ€๊ฒฝ๋จ + removed: ์ด๋ฉ”์ผ ํ…์ŠคํŠธ ์‚ญ์ œ๋จ + metadata: + removed: ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์‚ญ์ œ๋จ + removed.all: ๋ชจ๋“  ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์‚ญ์ œ๋จ + set: ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์„ค์ •๋จ + project: + added: ํ”„๋กœ์ ํŠธ ์ถ”๊ฐ€๋จ + changed: ํ”„๋กœ์ ํŠธ ๋ณ€๊ฒฝ๋จ + deactivated: ํ”„๋กœ์ ํŠธ ๋น„ํ™œ์„ฑํ™”๋จ + reactivated: ํ”„๋กœ์ ํŠธ ์žฌํ™œ์„ฑํ™”๋จ + removed: ํ”„๋กœ์ ํŠธ ์‚ญ์ œ๋จ + member: + added: ํ”„๋กœ์ ํŠธ ๋ฉค๋ฒ„ ์ถ”๊ฐ€๋จ + changed: ํ”„๋กœ์ ํŠธ ๋ฉค๋ฒ„ ๋ณ€๊ฒฝ๋จ + removed: ํ”„๋กœ์ ํŠธ ๋ฉค๋ฒ„ ์‚ญ์ œ๋จ + cascade: + removed: ํ”„๋กœ์ ํŠธ ๋ฉค๋ฒ„ ์—ฐ์‡„ ์‚ญ์ œ๋จ + role: + added: ํ”„๋กœ์ ํŠธ ์—ญํ•  ์ถ”๊ฐ€๋จ + changed: ํ”„๋กœ์ ํŠธ ์—ญํ•  ๋ณ€๊ฒฝ๋จ + removed: ํ”„๋กœ์ ํŠธ ์—ญํ•  ์‚ญ์ œ๋จ + grant: + added: ๊ด€๋ฆฌ ์•ก์„ธ์Šค ์ถ”๊ฐ€๋จ + changed: ๊ด€๋ฆฌ ์•ก์„ธ์Šค ๋ณ€๊ฒฝ๋จ + removed: ๊ด€๋ฆฌ ์•ก์„ธ์Šค ์‚ญ์ œ๋จ + deactivated: ๊ด€๋ฆฌ ์•ก์„ธ์Šค ๋น„ํ™œ์„ฑํ™”๋จ + reactivated: ๊ด€๋ฆฌ ์•ก์„ธ์Šค ์žฌํ™œ์„ฑํ™”๋จ + cascade: + changed: ๊ด€๋ฆฌ ์•ก์„ธ์Šค ๋ณ€๊ฒฝ๋จ + member: + added: ๊ด€๋ฆฌ ์•ก์„ธ์Šค ๋ฉค๋ฒ„ ์ถ”๊ฐ€๋จ + changed: ๊ด€๋ฆฌ ์•ก์„ธ์Šค ๋ฉค๋ฒ„ ๋ณ€๊ฒฝ๋จ + removed: ๊ด€๋ฆฌ ์•ก์„ธ์Šค ๋ฉค๋ฒ„ ์‚ญ์ œ๋จ + cascade: + removed: ๊ด€๋ฆฌ ์•ก์„ธ์Šค ์—ฐ์‡„ ์‚ญ์ œ๋จ + application: + added: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ถ”๊ฐ€๋จ + changed: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ณ€๊ฒฝ๋จ + removed: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‚ญ์ œ๋จ + deactivated: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋น„ํ™œ์„ฑํ™”๋จ + reactivated: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์žฌํ™œ์„ฑํ™”๋จ + oidc: + secret: + check: + succeeded: OIDC ํด๋ผ์ด์–ธํŠธ ์‹œํฌ๋ฆฟ ํ™•์ธ ์„ฑ๊ณต + failed: OIDC ํด๋ผ์ด์–ธํŠธ ์‹œํฌ๋ฆฟ ํ™•์ธ ์‹คํŒจ + key: + added: OIDC ์•ฑ ํ‚ค ์ถ”๊ฐ€๋จ + removed: OIDC ์•ฑ ํ‚ค ์‚ญ์ œ๋จ + api: + secret: + check: + succeeded: API ์‹œํฌ๋ฆฟ ํ™•์ธ ์„ฑ๊ณต + failed: API ์‹œํฌ๋ฆฟ ํ™•์ธ ์‹คํŒจ + key: + added: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ‚ค ์ถ”๊ฐ€๋จ + removed: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ‚ค ์‚ญ์ œ๋จ + config: + saml: + added: SAML ์„ค์ • ์ถ”๊ฐ€๋จ + changed: SAML ์„ค์ • ๋ณ€๊ฒฝ๋จ + oidc: + added: OIDC ์„ค์ • ์ถ”๊ฐ€๋จ + changed: OIDC ์„ค์ • ๋ณ€๊ฒฝ๋จ + secret: + changed: OIDC ์‹œํฌ๋ฆฟ ๋ณ€๊ฒฝ๋จ + updated: OIDC ์‹œํฌ๋ฆฟ ํ•ด์‹œ ๊ฐฑ์‹ ๋จ + api: + added: API ์„ค์ • ์ถ”๊ฐ€๋จ + changed: API ์„ค์ • ๋ณ€๊ฒฝ๋จ + secret: + changed: API ์‹œํฌ๋ฆฟ ๋ณ€๊ฒฝ๋จ + updated: API ์‹œํฌ๋ฆฟ ํ•ด์‹œ ๊ฐฑ์‹ ๋จ + policy: + password: + complexity: + added: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก๋„ ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก๋„ ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + age: + added: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + lockout: + added: ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž ๊ธˆ ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž ๊ธˆ ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + iam: + setup: + started: ZITADEL ์„ค์ • ์‹œ์ž‘๋จ + done: ZITADEL ์„ค์ • ์™„๋ฃŒ๋จ + global: + org: + set: ๊ธ€๋กœ๋ฒŒ ์กฐ์ง ์„ค์ •๋จ + project: + iam: + set: ZITADEL ํ”„๋กœ์ ํŠธ ์„ค์ •๋จ + member: + added: ZITADEL ๋ฉค๋ฒ„ ์ถ”๊ฐ€๋จ + changed: ZITADEL ๋ฉค๋ฒ„ ๋ณ€๊ฒฝ๋จ + removed: ZITADEL ๋ฉค๋ฒ„ ์‚ญ์ œ๋จ + cascade: + removed: ZITADEL ๋ฉค๋ฒ„ ์—ฐ์‡„ ์‚ญ์ œ๋จ + idp: + config: + added: IDP ์„ค์ • ์ถ”๊ฐ€๋จ + changed: IDP ์„ค์ • ๋ณ€๊ฒฝ๋จ + removed: IDP ์„ค์ • ์‚ญ์ œ๋จ + deactivated: IDP ์„ค์ • ๋น„ํ™œ์„ฑํ™”๋จ + reactivated: IDP ์„ค์ • ์žฌํ™œ์„ฑํ™”๋จ + oidc: + config: + added: OIDC IDP ์„ค์ • ์ถ”๊ฐ€๋จ + changed: OIDC IDP ์„ค์ • ๋ณ€๊ฒฝ๋จ + saml: + config: + added: SAML IDP ์„ค์ • ์ถ”๊ฐ€๋จ + changed: SAML IDP ์„ค์ • ๋ณ€๊ฒฝ๋จ + jwt: + config: + added: IDP์— JWT ์„ค์ • ์ถ”๊ฐ€๋จ + changed: IDP๋กœ๋ถ€ํ„ฐ JWT ์„ค์ • ์‚ญ์ œ๋จ + customtext: + set: ํ…์ŠคํŠธ ์„ค์ •๋จ + removed: ํ…์ŠคํŠธ ์‚ญ์ œ๋จ + policy: + login: + added: ๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + idpprovider: + added: ๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ์ •์ฑ…์— IDP ์ถ”๊ฐ€๋จ + removed: ๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ์ •์ฑ…์—์„œ IDP ์‚ญ์ œ๋จ + label: + added: ๋ ˆ์ด๋ธ” ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ๋ ˆ์ด๋ธ” ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + activated: ๋ ˆ์ด๋ธ” ์ •์ฑ… ํ™œ์„ฑํ™”๋จ + logo: + added: ๋กœ๊ณ ๊ฐ€ ๋ ˆ์ด๋ธ” ์ •์ฑ…์— ์ถ”๊ฐ€๋จ + removed: ๋ ˆ์ด๋ธ” ์ •์ฑ…์—์„œ ๋กœ๊ณ ๊ฐ€ ์‚ญ์ œ๋จ + dark: + added: ๋‹คํฌ ๋ชจ๋“œ ๋กœ๊ณ ๊ฐ€ ๋ ˆ์ด๋ธ” ์ •์ฑ…์— ์ถ”๊ฐ€๋จ + removed: ๋‹คํฌ ๋ชจ๋“œ ๋กœ๊ณ ๊ฐ€ ๋ ˆ์ด๋ธ” ์ •์ฑ…์—์„œ ์‚ญ์ œ๋จ + icon: + added: ์•„์ด์ฝ˜์ด ๋ ˆ์ด๋ธ” ์ •์ฑ…์— ์ถ”๊ฐ€๋จ + removed: ๋ ˆ์ด๋ธ” ์ •์ฑ…์—์„œ ์•„์ด์ฝ˜์ด ์‚ญ์ œ๋จ + dark: + added: ๋‹คํฌ ๋ชจ๋“œ ์•„์ด์ฝ˜์ด ๋ ˆ์ด๋ธ” ์ •์ฑ…์— ์ถ”๊ฐ€๋จ + removed: ๋‹คํฌ ๋ชจ๋“œ ์•„์ด์ฝ˜์ด ๋ ˆ์ด๋ธ” ์ •์ฑ…์—์„œ ์‚ญ์ œ๋จ + font: + added: ํฐํŠธ๊ฐ€ ๋ ˆ์ด๋ธ” ์ •์ฑ…์— ์ถ”๊ฐ€๋จ + removed: ๋ ˆ์ด๋ธ” ์ •์ฑ…์—์„œ ํฐํŠธ๊ฐ€ ์‚ญ์ œ๋จ + assets: + removed: ๋ ˆ์ด๋ธ” ์ •์ฑ…์—์„œ ์ž์‚ฐ ์‚ญ์ œ๋จ + default: + language: + set: ๊ธฐ๋ณธ ์–ธ์–ด ์„ค์ •๋จ + oidc: + settings: + added: OIDC ์„ค์ • ์ถ”๊ฐ€๋จ + changed: OIDC ์„ค์ • ๋ณ€๊ฒฝ๋จ + removed: OIDC ์„ค์ • ์‚ญ์ œ๋จ + secret: + generator: + added: ์‹œํฌ๋ฆฟ ์ƒ์„ฑ๊ธฐ ์ถ”๊ฐ€๋จ + changed: ์‹œํฌ๋ฆฟ ์ƒ์„ฑ๊ธฐ ๋ณ€๊ฒฝ๋จ + removed: ์‹œํฌ๋ฆฟ ์ƒ์„ฑ๊ธฐ ์‚ญ์ œ๋จ + smtp: + config: + added: SMTP ์„ค์ • ์ถ”๊ฐ€๋จ + changed: SMTP ์„ค์ • ๋ณ€๊ฒฝ๋จ + activated: SMTP ์„ค์ • ํ™œ์„ฑํ™”๋จ + deactivated: SMTP ์„ค์ • ๋น„ํ™œ์„ฑํ™”๋จ + removed: SMTP ์„ค์ • ์‚ญ์ œ๋จ + password: + changed: SMTP ์„ค์ • ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ๋จ + sms: + config: + twilio: + added: Twilio SMS ์ œ๊ณต์ž ์ถ”๊ฐ€๋จ + changed: Twilio SMS ์ œ๊ณต์ž ๋ณ€๊ฒฝ๋จ + token: + changed: Twilio SMS ์ œ๊ณต์ž ํ† ํฐ ๋ณ€๊ฒฝ๋จ + removed: Twilio SMS ์ œ๊ณต์ž ์‚ญ์ œ๋จ + activated: Twilio SMS ์ œ๊ณต์ž ํ™œ์„ฑํ™”๋จ + deactivated: Twilio SMS ์ œ๊ณต์ž ๋น„ํ™œ์„ฑํ™”๋จ + key_pair: + added: ํ‚ค ํŽ˜์–ด ์ถ”๊ฐ€๋จ + certificate: + added: ์ธ์ฆ์„œ ์ถ”๊ฐ€๋จ + action: + added: ์ž‘์—… ์ถ”๊ฐ€๋จ + changed: ์ž‘์—… ๋ณ€๊ฒฝ๋จ + deactivated: ์ž‘์—… ๋น„ํ™œ์„ฑํ™”๋จ + reactivated: ์ž‘์—… ์žฌํ™œ์„ฑํ™”๋จ + removed: ์ž‘์—… ์‚ญ์ œ๋จ + instance: + added: ์ธ์Šคํ„ด์Šค ์ถ”๊ฐ€๋จ + changed: ์ธ์Šคํ„ด์Šค ๋ณ€๊ฒฝ๋จ + customtext: + removed: ์‚ฌ์šฉ์ž ์ •์˜ ํ…์ŠคํŠธ ์‚ญ์ œ๋จ + set: ์‚ฌ์šฉ์ž ์ •์˜ ํ…์ŠคํŠธ ์„ค์ •๋จ + template: + removed: ์‚ฌ์šฉ์ž ์ •์˜ ํ…œํ”Œ๋ฆฟ ์‚ญ์ œ๋จ + default: + language: + set: ๊ธฐ๋ณธ ์–ธ์–ด ์„ค์ •๋จ + org: + set: ๊ธฐ๋ณธ ์กฐ์ง ์„ค์ •๋จ + domain: + added: ๋„๋ฉ”์ธ ์ถ”๊ฐ€๋จ + primary: + set: ๊ธฐ๋ณธ ๋„๋ฉ”์ธ ์„ค์ •๋จ + removed: ๋„๋ฉ”์ธ ์‚ญ์ œ๋จ + iam: + console: + set: ZITADEL ์ฝ˜์†” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ค์ •๋จ + project: + set: ZITADEL ํ”„๋กœ์ ํŠธ ์„ค์ •๋จ + mail: + template: + added: ์ด๋ฉ”์ผ ํ…œํ”Œ๋ฆฟ ์ถ”๊ฐ€๋จ + changed: ์ด๋ฉ”์ผ ํ…œํ”Œ๋ฆฟ ๋ณ€๊ฒฝ๋จ + text: + added: ์ด๋ฉ”์ผ ํ…์ŠคํŠธ ์ถ”๊ฐ€๋จ + changed: ์ด๋ฉ”์ผ ํ…์ŠคํŠธ ๋ณ€๊ฒฝ๋จ + member: + added: ์ธ์Šคํ„ด์Šค ๋ฉค๋ฒ„ ์ถ”๊ฐ€๋จ + changed: ์ธ์Šคํ„ด์Šค ๋ฉค๋ฒ„ ๋ณ€๊ฒฝ๋จ + removed: ์ธ์Šคํ„ด์Šค ๋ฉค๋ฒ„ ์‚ญ์ œ๋จ + cascade: + removed: ์ธ์Šคํ„ด์Šค ๋ฉค๋ฒ„ ์—ฐ์‡„ ์‚ญ์ œ๋จ + notification: + provider: + debug: + fileadded: ํŒŒ์ผ ๋””๋ฒ„๊ทธ ์•Œ๋ฆผ ์ œ๊ณต์ž ์ถ”๊ฐ€๋จ + filechanged: ํŒŒ์ผ ๋””๋ฒ„๊ทธ ์•Œ๋ฆผ ์ œ๊ณต์ž ๋ณ€๊ฒฝ๋จ + fileremoved: ํŒŒ์ผ ๋””๋ฒ„๊ทธ ์•Œ๋ฆผ ์ œ๊ณต์ž ์‚ญ์ œ๋จ + logadded: ๋กœ๊ทธ ๋””๋ฒ„๊ทธ ์•Œ๋ฆผ ์ œ๊ณต์ž ์ถ”๊ฐ€๋จ + logchanged: ๋กœ๊ทธ ๋””๋ฒ„๊ทธ ์•Œ๋ฆผ ์ œ๊ณต์ž ๋ณ€๊ฒฝ๋จ + logremoved: ๋กœ๊ทธ ๋””๋ฒ„๊ทธ ์•Œ๋ฆผ ์ œ๊ณต์ž ์‚ญ์ œ๋จ + oidc: + settings: + added: OIDC ์„ค์ • ์ถ”๊ฐ€๋จ + changed: OIDC ์„ค์ • ๋ณ€๊ฒฝ๋จ + policy: + domain: + added: ๋„๋ฉ”์ธ ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ๋„๋ฉ”์ธ ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + label: + activated: ๋ ˆ์ด๋ธ” ์ •์ฑ… ํ™œ์„ฑํ™”๋จ + added: ๋ ˆ์ด๋ธ” ์ •์ฑ… ์ถ”๊ฐ€๋จ + assets: + removed: ๋ ˆ์ด๋ธ” ์ •์ฑ…์—์„œ ์ž์‚ฐ ์‚ญ์ œ๋จ + changed: ๋ ˆ์ด๋ธ” ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + font: + added: ๋ ˆ์ด๋ธ” ์ •์ฑ…์— ํฐํŠธ ์ถ”๊ฐ€๋จ + removed: ๋ ˆ์ด๋ธ” ์ •์ฑ…์—์„œ ํฐํŠธ ์‚ญ์ œ๋จ + icon: + added: ๋ ˆ์ด๋ธ” ์ •์ฑ…์— ์•„์ด์ฝ˜ ์ถ”๊ฐ€๋จ + removed: ๋ ˆ์ด๋ธ” ์ •์ฑ…์—์„œ ์•„์ด์ฝ˜ ์‚ญ์ œ๋จ + dark: + added: ๋ ˆ์ด๋ธ” ์ •์ฑ…์— ๋‹คํฌ ๋ชจ๋“œ ์•„์ด์ฝ˜ ์ถ”๊ฐ€๋จ + removed: ๋ ˆ์ด๋ธ” ์ •์ฑ…์—์„œ ๋‹คํฌ ๋ชจ๋“œ ์•„์ด์ฝ˜ ์‚ญ์ œ๋จ + logo: + added: ๋ ˆ์ด๋ธ” ์ •์ฑ…์— ๋กœ๊ณ  ์ถ”๊ฐ€๋จ + removed: ๋ ˆ์ด๋ธ” ์ •์ฑ…์—์„œ ๋กœ๊ณ  ์‚ญ์ œ๋จ + dark: + added: ๋ ˆ์ด๋ธ” ์ •์ฑ…์— ๋‹คํฌ ๋ชจ๋“œ ๋กœ๊ณ  ์ถ”๊ฐ€๋จ + removed: ๋ ˆ์ด๋ธ” ์ •์ฑ…์—์„œ ๋‹คํฌ ๋ชจ๋“œ ๋กœ๊ณ  ์‚ญ์ œ๋จ + lockout: + added: ์ž ๊ธˆ ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ์ž ๊ธˆ ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + login: + added: ๋กœ๊ทธ์ธ ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ๋กœ๊ทธ์ธ ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + idpprovider: + added: ๋กœ๊ทธ์ธ ์ •์ฑ…์— IDP ์ถ”๊ฐ€๋จ + cascade: + removed: ๋กœ๊ทธ์ธ ์ •์ฑ…์—์„œ ์—ฐ์‡„ IDP ์‚ญ์ œ๋จ + removed: ๋กœ๊ทธ์ธ ์ •์ฑ…์—์„œ IDP ์‚ญ์ œ๋จ + multifactor: + added: ๋‹ค์ค‘ ์ธ์ฆ ์ถ”๊ฐ€๋จ + removed: ๋‹ค์ค‘ ์ธ์ฆ ์‚ญ์ œ๋จ + secondfactor: + added: 2์ฐจ ์ธ์ฆ ์ถ”๊ฐ€๋จ + removed: 2์ฐจ ์ธ์ฆ ์‚ญ์ œ๋จ + password: + age: + added: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + complexity: + added: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก๋„ ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์žก๋„ ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + privacy: + added: ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ ์ •์ฑ… ์ถ”๊ฐ€๋จ + changed: ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ ์ •์ฑ… ๋ณ€๊ฒฝ๋จ + security: + set: ๋ณด์•ˆ ์ •์ฑ… ์„ค์ •๋จ + + removed: ์ธ์Šคํ„ด์Šค ์‚ญ์ œ๋จ + secret: + generator: + added: ์‹œํฌ๋ฆฟ ์ƒ์„ฑ๊ธฐ ์ถ”๊ฐ€๋จ + changed: ์‹œํฌ๋ฆฟ ์ƒ์„ฑ๊ธฐ ๋ณ€๊ฒฝ๋จ + removed: ์‹œํฌ๋ฆฟ ์ƒ์„ฑ๊ธฐ ์‚ญ์ œ๋จ + sms: + configtwilio: + activated: Twilio SMS ์„ค์ • ํ™œ์„ฑํ™”๋จ + added: Twilio SMS ์„ค์ • ์ถ”๊ฐ€๋จ + changed: Twilio SMS ์„ค์ • ๋ณ€๊ฒฝ๋จ + deactivated: Twilio SMS ์„ค์ • ๋น„ํ™œ์„ฑํ™”๋จ + removed: Twilio SMS ์„ค์ • ์‚ญ์ œ๋จ + token: + changed: Twilio SMS ์„ค์ • ํ† ํฐ ๋ณ€๊ฒฝ๋จ + smtp: + config: + added: SMTP ์„ค์ • ์ถ”๊ฐ€๋จ + changed: SMTP ์„ค์ • ๋ณ€๊ฒฝ๋จ + activated: SMTP ์„ค์ • ํ™œ์„ฑํ™”๋จ + deactivated: SMTP ์„ค์ • ๋น„ํ™œ์„ฑํ™”๋จ + password: + changed: SMTP ์„ค์ • ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ๋จ + removed: SMTP ์„ค์ • ์‚ญ์ œ๋จ + user_schema: + created: ์‚ฌ์šฉ์ž ์Šคํ‚ค๋งˆ ์ƒ์„ฑ๋จ + updated: ์‚ฌ์šฉ์ž ์Šคํ‚ค๋งˆ ์—…๋ฐ์ดํŠธ๋จ + deactivated: ์‚ฌ์šฉ์ž ์Šคํ‚ค๋งˆ ๋น„ํ™œ์„ฑํ™”๋จ + reactivated: ์‚ฌ์šฉ์ž ์Šคํ‚ค๋งˆ ์žฌํ™œ์„ฑํ™”๋จ + deleted: ์‚ฌ์šฉ์ž ์Šคํ‚ค๋งˆ ์‚ญ์ œ๋จ + user: + created: ์‚ฌ์šฉ์ž ์ƒ์„ฑ๋จ + updated: ์‚ฌ์šฉ์ž ์—…๋ฐ์ดํŠธ๋จ + deleted: ์‚ฌ์šฉ์ž ์‚ญ์ œ๋จ + email: + updated: ์ด๋ฉ”์ผ ์ฃผ์†Œ ๋ณ€๊ฒฝ๋จ + verified: ์ด๋ฉ”์ผ ์ฃผ์†Œ ์ธ์ฆ๋จ + verification: + failed: ์ด๋ฉ”์ผ ์ฃผ์†Œ ์ธ์ฆ ์‹คํŒจ + code: + added: ์ด๋ฉ”์ผ ์ฃผ์†Œ ์ธ์ฆ ์ฝ”๋“œ ์ƒ์„ฑ๋จ + sent: ์ด๋ฉ”์ผ ์ฃผ์†Œ ์ธ์ฆ ์ฝ”๋“œ ์ „์†ก๋จ + phone: + updated: ์ „ํ™”๋ฒˆํ˜ธ ๋ณ€๊ฒฝ๋จ + verified: ์ „ํ™”๋ฒˆํ˜ธ ์ธ์ฆ๋จ + verification: + failed: ์ „ํ™”๋ฒˆํ˜ธ ์ธ์ฆ ์‹คํŒจ + code: + added: ์ „ํ™”๋ฒˆํ˜ธ ์ธ์ฆ ์ฝ”๋“œ ์ƒ์„ฑ๋จ + sent: ์ „ํ™”๋ฒˆํ˜ธ ์ธ์ฆ ์ฝ”๋“œ ์ „์†ก๋จ + + + web_key: + added: ์›น ํ‚ค ์ถ”๊ฐ€๋จ + activated: ์›น ํ‚ค ํ™œ์„ฑํ™”๋จ + deactivated: ์›น ํ‚ค ๋น„ํ™œ์„ฑํ™”๋จ + removed: ์›น ํ‚ค ์‚ญ์ œ๋จ + +Application: + OIDC: + UnsupportedVersion: ์ง€์›๋˜์ง€ ์•Š๋Š” OIDC ๋ฒ„์ „์ž…๋‹ˆ๋‹ค + V1: + NotCompliant: ์„ค์ •์ด OIDC 1.0 ํ‘œ์ค€๊ณผ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + NoRedirectUris: ์ ์–ด๋„ ํ•˜๋‚˜์˜ ๋ฆฌ๋””๋ ‰์…˜ URI๊ฐ€ ๋“ฑ๋ก๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + NotAllCombinationsAreAllowed: ์„ค์ •์€ ์ค€์ˆ˜ํ•˜์ง€๋งŒ ๋ชจ๋“  ๊ฐ€๋Šฅํ•œ ์กฐํ•ฉ์ด ํ—ˆ์šฉ๋˜๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. + Code: + RedirectUris: + HttpOnlyForWeb: ์ฝ”๋“œ ์ธ์ฆ ๋ฐฉ์‹์—์„œ๋Š” ์›น ์•ฑ ์œ ํ˜•์— ๋Œ€ํ•ด์„œ๋งŒ HTTP ๋ฆฌ๋””๋ ‰์…˜ URI๊ฐ€ ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค. + CustomOnlyForNative: ์ฝ”๋“œ ์ธ์ฆ ๋ฐฉ์‹์—์„œ๋Š” ๋„ค์ดํ‹ฐ๋ธŒ ์•ฑ ์œ ํ˜•์— ๋Œ€ํ•ด์„œ๋งŒ ์‚ฌ์šฉ์ž ์ •์˜ ๋ฆฌ๋””๋ ‰์…˜ URI๋ฅผ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค (e.g appname:// ). + Implicit: + RedirectUris: + CustomNotAllowed: ์•”์‹œ์  ์ธ์ฆ ๋ฐฉ์‹์—์„œ๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ๋ฆฌ๋””๋ ‰์…˜ URI๋ฅผ ํ—ˆ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + HttpNotAllowed: ์•”์‹œ์  ์ธ์ฆ ๋ฐฉ์‹์—์„œ๋Š” HTTP ๋ฆฌ๋””๋ ‰์…˜ URI๋ฅผ ํ—ˆ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + HttpLocalhostOnlyForNative: http://localhost ๋ฆฌ๋””๋ ‰์…˜ URI๋Š” ๋„ค์ดํ‹ฐ๋ธŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋งŒ ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค. + Native: + AuthMethodType: + NotNone: ๋„ค์ดํ‹ฐ๋ธŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ authmethodtype์€ none์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + RedirectUris: + MustBeHttpLocalhost: ๋ฆฌ๋””๋ ‰์…˜ URI๋Š” http://127.0.0.1, http://[::1], http://localhost์™€ ๊ฐ™์€ ์ž์ฒด ํ”„๋กœํ† ์ฝœ๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + UserAgent: + AuthMethodType: + NotNone: ์‚ฌ์šฉ์ž ์—์ด์ „ํŠธ ์•ฑ์˜ authmethodtype์€ none์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + GrantType: + Refresh: + NoAuthCode: ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์€ ์ธ์ฆ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ๋•Œ๋งŒ ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค. + +Action: + Flow: + Type: + Unspecified: ๋ฏธ์ง€์ • + ExternalAuthentication: ์™ธ๋ถ€ ์ธ์ฆ + CustomiseToken: ํ† ํฐ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• + InternalAuthentication: ๋‚ด๋ถ€ ์ธ์ฆ + CustomizeSAMLResponse: SAML ์‘๋‹ต ์ปค์Šคํ„ฐ๋งˆ์ด์ง• + TriggerType: + Unspecified: ๋ฏธ์ง€์ • + PostAuthentication: ์ธ์ฆ ํ›„ + PreCreation: ์ƒ์„ฑ ์ „ + PostCreation: ์ƒ์„ฑ ํ›„ + PreUserinfoCreation: ์‚ฌ์šฉ์ž ์ •๋ณด ์ƒ์„ฑ ์ „ + PreAccessTokenCreation: ์•ก์„ธ์Šค ํ† ํฐ ์ƒ์„ฑ ์ „ + PreSAMLResponseCreation: SAML ์‘๋‹ต ์ƒ์„ฑ ์ „ diff --git a/internal/user/model/user_view.go b/internal/user/model/user_view.go index 6806d78ebd..c5f6c0da2c 100644 --- a/internal/user/model/user_view.go +++ b/internal/user/model/user_view.go @@ -40,6 +40,7 @@ type HumanView struct { Gender Gender Email string IsEmailVerified bool + VerifiedEmail string Phone string IsPhoneVerified bool Country string diff --git a/internal/user/repository/view/model/user.go b/internal/user/repository/view/model/user.go index 62c64edc0c..c7c0248924 100644 --- a/internal/user/repository/view/model/user.go +++ b/internal/user/repository/view/model/user.go @@ -79,6 +79,7 @@ type HumanView struct { AvatarKey string `json:"storeKey" gorm:"column:avatar_key"` Email string `json:"email" gorm:"column:email"` IsEmailVerified bool `json:"-" gorm:"column:is_email_verified"` + VerifiedEmail string `json:"-" gorm:"column:verified_email"` Phone string `json:"phone" gorm:"column:phone"` IsPhoneVerified bool `json:"-" gorm:"column:is_phone_verified"` Country string `json:"country" gorm:"column:country"` @@ -170,6 +171,7 @@ func UserToModel(user *UserView) *model.UserView { Gender: model.Gender(user.Gender), Email: user.Email, IsEmailVerified: user.IsEmailVerified, + VerifiedEmail: user.VerifiedEmail, Phone: user.Phone, IsPhoneVerified: user.IsPhoneVerified, Country: user.Country, diff --git a/internal/user/repository/view/user_by_id.sql b/internal/user/repository/view/user_by_id.sql index 1720ad7998..bd34f77d80 100644 --- a/internal/user/repository/view/user_by_id.sql +++ b/internal/user/repository/view/user_by_id.sql @@ -42,6 +42,7 @@ SELECT , h.gender , h.email , h.is_email_verified + , n.verified_email , h.phone , h.is_phone_verified , (SELECT COALESCE((SELECT state FROM auth_methods WHERE method_type = 1), 0)) AS otp_state @@ -77,6 +78,9 @@ FROM projections.users13 u LEFT JOIN projections.users13_humans h ON u.instance_id = h.instance_id AND u.id = h.user_id + LEFT JOIN projections.users13_notifications n + ON u.instance_id = n.instance_id + AND u.id = n.user_id LEFT JOIN projections.login_names3 l ON u.instance_id = l.instance_id AND u.id = l.user_id diff --git a/load-test/.gitignore b/load-test/.gitignore new file mode 100644 index 0000000000..30bd623c23 --- /dev/null +++ b/load-test/.gitignore @@ -0,0 +1,2 @@ +.env + diff --git a/load-test/src/use_cases/machine_jwt_profile_grant.ts b/load-test/src/use_cases/machine_jwt_profile_grant.ts index 084ac4f684..2511f9e2a5 100644 --- a/load-test/src/use_cases/machine_jwt_profile_grant.ts +++ b/load-test/src/use_cases/machine_jwt_profile_grant.ts @@ -46,9 +46,6 @@ export async function setup() { export default function (data: any) { token(new JWTProfileRequest(data.machines[__VU - 1].userId, data.machines[__VU - 1].keyId)) - .then((token) => { - userinfo(token.accessToken!) - }) } export function teardown(data: any) { diff --git a/load-test/src/use_cases/machine_jwt_profile_grant_single_user.ts b/load-test/src/use_cases/machine_jwt_profile_grant_single_user.ts index c654fb9492..95437b0c97 100644 --- a/load-test/src/use_cases/machine_jwt_profile_grant_single_user.ts +++ b/load-test/src/use_cases/machine_jwt_profile_grant_single_user.ts @@ -24,9 +24,6 @@ export async function setup() { export default function (data: any) { token(new JWTProfileRequest(data.machine.userId, data.machine.keyId)) - .then((token) => { - userinfo(token.accessToken!) - }) } export function teardown(data: any) { diff --git a/pkg/actions/signing.go b/pkg/actions/signing.go new file mode 100644 index 0000000000..0b39327450 --- /dev/null +++ b/pkg/actions/signing.go @@ -0,0 +1,115 @@ +package actions + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "strconv" + "strings" + "time" +) + +var ( + ErrNoValidSignature = errors.New("no valid signature") + ErrInvalidHeader = errors.New("webhook has invalid Zitadel-Signature header") + ErrNotSigned = errors.New("webhook has no Zitadel-Signature header") + ErrTooOld = errors.New("timestamp wasn't within tolerance") +) + +const ( + SigningHeader = "ZITADEL-Signature" + signingTimestamp = "t" + signingVersion string = "v1" + DefaultTolerance = 300 * time.Second + partSeparator = "," +) + +func ComputeSignatureHeader(t time.Time, payload []byte, signingKey ...string) string { + parts := []string{ + fmt.Sprintf("%s=%d", signingTimestamp, t.Unix()), + } + for _, k := range signingKey { + parts = append(parts, fmt.Sprintf("%s=%s", signingVersion, hex.EncodeToString(computeSignature(t, payload, k)))) + } + return strings.Join(parts, partSeparator) +} + +func computeSignature(t time.Time, payload []byte, signingKey string) []byte { + mac := hmac.New(sha256.New, []byte(signingKey)) + mac.Write([]byte(fmt.Sprintf("%d", t.Unix()))) + mac.Write([]byte(".")) + mac.Write(payload) + return mac.Sum(nil) +} + +func ValidatePayload(payload []byte, header string, signingKey string) error { + return ValidatePayloadWithTolerance(payload, header, signingKey, DefaultTolerance) +} + +func ValidatePayloadWithTolerance(payload []byte, header string, signingKey string, tolerance time.Duration) error { + return validatePayload(payload, header, signingKey, tolerance, true) +} + +func validatePayload(payload []byte, sigHeader string, signingKey string, tolerance time.Duration, enforceTolerance bool) error { + header, err := parseSignatureHeader(sigHeader) + if err != nil { + return err + } + + expectedSignature := computeSignature(header.timestamp, payload, signingKey) + expiredTimestamp := time.Since(header.timestamp) > tolerance + if enforceTolerance && expiredTimestamp { + return ErrTooOld + } + + for _, sig := range header.signatures { + if hmac.Equal(expectedSignature, sig) { + return nil + } + } + return ErrNoValidSignature +} + +type signedHeader struct { + timestamp time.Time + signatures [][]byte +} + +func parseSignatureHeader(header string) (*signedHeader, error) { + sh := &signedHeader{} + if header == "" { + return sh, ErrNotSigned + } + + pairs := strings.Split(header, ",") + for _, pair := range pairs { + parts := strings.Split(pair, "=") + if len(parts) != 2 { + return sh, ErrInvalidHeader + } + switch parts[0] { + case signingTimestamp: + timestamp, err := strconv.ParseInt(parts[1], 10, 64) + if err != nil { + return sh, ErrInvalidHeader + } + sh.timestamp = time.Unix(timestamp, 0) + + case signingVersion: + sig, err := hex.DecodeString(parts[1]) + if err != nil { + continue + } + sh.signatures = append(sh.signatures, sig) + default: + continue + } + } + + if len(sh.signatures) == 0 { + return sh, ErrNoValidSignature + } + return sh, nil +} diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index 0df07ffd4c..0ff2ad7b75 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -472,7 +472,7 @@ service ManagementService { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { summary: "Create/Import User (Human)"; - description: "Create/import a new user with the type human. The newly created user will get an initialization email if either the email address is not marked as verified or no password is set. If a password is set the user will not be requested to set a new one on the first login.\n\nDeprecated: please use user service v2 AddHumanUser" + description: "Create/import a new user with the type human. The newly created user will get an initialization email if either the email address is not marked as verified or no password is set. If a password is set the user will not be requested to set a new one on the first login.\n\nDeprecated: please use user service v2 [AddHumanUser](apis/resources/user_service_v2/user-service-add-human-user.api.mdx)" tags: "Users"; tags: "User Human" deprecated: true; diff --git a/proto/zitadel/resources/action/v3alpha/action_service.proto b/proto/zitadel/resources/action/v3alpha/action_service.proto index fa07a9f854..bc3739861d 100644 --- a/proto/zitadel/resources/action/v3alpha/action_service.proto +++ b/proto/zitadel/resources/action/v3alpha/action_service.proto @@ -408,6 +408,12 @@ message CreateTargetRequest { message CreateTargetResponse { zitadel.resources.object.v3alpha.Details details = 1; + // Key used to sign and check payload sent to the target. + string signing_key = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"98KmsU67\"" + } + ]; } message PatchTargetRequest { @@ -433,6 +439,12 @@ message PatchTargetRequest { message PatchTargetResponse { zitadel.resources.object.v3alpha.Details details = 1; + // Key used to sign and check payload sent to the target. + optional string signing_key = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"98KmsU67\"" + } + ]; } message DeleteTargetRequest { diff --git a/proto/zitadel/resources/action/v3alpha/target.proto b/proto/zitadel/resources/action/v3alpha/target.proto index cb1ff85883..8524ab3639 100644 --- a/proto/zitadel/resources/action/v3alpha/target.proto +++ b/proto/zitadel/resources/action/v3alpha/target.proto @@ -9,6 +9,7 @@ import "google/protobuf/struct.proto"; import "protoc-gen-openapiv2/options/annotations.proto"; import "validate/validate.proto"; import "zitadel/protoc_gen_zitadel/v2/options.proto"; +import "google/protobuf/timestamp.proto"; import "zitadel/resources/object/v3alpha/object.proto"; @@ -51,6 +52,11 @@ message Target { message GetTarget { zitadel.resources.object.v3alpha.Details details = 1; Target config = 2; + string signing_key = 3 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"98KmsU67\"" + } + ]; } message PatchTarget { @@ -84,6 +90,21 @@ message PatchTarget { max_length: 1000 } ]; + // Regenerate the key used for signing and checking the payload sent to the target. + // Set the graceful period for the existing key. During that time, the previous + // signing key and the new one will be used to sign the request to allow you a smooth + // transition onf your API. + // + // Note that we currently only allow an immediate rotation ("0s") and will support + // longer expirations in the future. + optional google.protobuf.Duration expiration_signing_key = 7 [ + (validate.rules).duration = {const: {seconds: 0, nanos: 0}}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"0s\"" + minimum: 0 + maximum: 0 + } + ]; } diff --git a/proto/zitadel/user/v2/user_service.proto b/proto/zitadel/user/v2/user_service.proto index 47707fef4f..83b025bf0a 100644 --- a/proto/zitadel/user/v2/user_service.proto +++ b/proto/zitadel/user/v2/user_service.proto @@ -168,7 +168,7 @@ service UserService { // Search Users // - // Search for users. By default, we will return users of your organization. Make sure to include a limit and sorting for pagination.. + // Search for users. By default, we will return all users of your instance that you have permission to read. Make sure to include a limit and sorting for pagination. rpc ListUsers(ListUsersRequest) returns (ListUsersResponse) { option (google.api.http) = { post: "/v2/users" @@ -1091,6 +1091,7 @@ service UserService { rpc CreateInviteCode (CreateInviteCodeRequest) returns (CreateInviteCodeResponse) { option (google.api.http) = { post: "/v2/users/{user_id}/invite_code" + body: "*" }; option (zitadel.protoc_gen_zitadel.v2.options) = { @@ -1140,6 +1141,7 @@ service UserService { rpc VerifyInviteCode (VerifyInviteCodeRequest) returns (VerifyInviteCodeResponse) { option (google.api.http) = { post: "/v2/users/{user_id}/invite_code/verify" + body: "*" }; option (zitadel.protoc_gen_zitadel.v2.options) = { diff --git a/proto/zitadel/user/v2beta/user_service.proto b/proto/zitadel/user/v2beta/user_service.proto index 156f961c59..9ad0a7e6eb 100644 --- a/proto/zitadel/user/v2beta/user_service.proto +++ b/proto/zitadel/user/v2beta/user_service.proto @@ -174,7 +174,7 @@ service UserService { // Search Users // - // Search for users. By default, we will return users of your organization. Make sure to include a limit and sorting for pagination. + // Search for users. By default, we will return all users of your instance that you have permission to read. Make sure to include a limit and sorting for pagination. // // Deprecated: please move to the corresponding endpoint under user service v2 (GA). rpc ListUsers(ListUsersRequest) returns (ListUsersResponse) { diff --git a/release-channels.yaml b/release-channels.yaml deleted file mode 100644 index 4d2b5bf892..0000000000 --- a/release-channels.yaml +++ /dev/null @@ -1 +0,0 @@ -stable: "v2.54.8"