From 153df2e12f634d706eab6ee1e76c05d4eec6d7f2 Mon Sep 17 00:00:00 2001 From: Livio Spring Date: Wed, 10 Apr 2024 11:14:55 +0200 Subject: [PATCH] feat: provide option to limit (T)OTP checks (#7693) * feat: provide option to limit (T)OTP checks * fix requests in console * update errors pkg * cleanup * cleanup * improve naming of existing config --- cmd/defaults.yaml | 3 +- .../password-lockout-policy.component.html | 38 +- .../password-lockout-policy.component.ts | 22 +- console/src/app/services/admin.service.ts | 8 +- console/src/app/services/mgmt.service.ts | 16 +- console/src/assets/i18n/bg.json | 3 +- console/src/assets/i18n/cs.json | 3 +- console/src/assets/i18n/de.json | 3 +- console/src/assets/i18n/en.json | 3 +- console/src/assets/i18n/es.json | 3 +- console/src/assets/i18n/fr.json | 3 +- console/src/assets/i18n/it.json | 3 +- console/src/assets/i18n/ja.json | 3 +- console/src/assets/i18n/mk.json | 3 +- console/src/assets/i18n/nl.json | 3 +- console/src/assets/i18n/pl.json | 3 +- console/src/assets/i18n/pt.json | 3 +- console/src/assets/i18n/ru.json | 3 +- console/src/assets/i18n/zh.json | 3 +- console/yarn.lock | 661 +----------------- .../manage/console/default-settings.mdx | 3 +- .../guides/manage/console/organizations.mdx | 2 +- docs/static/img/guides/console/lockout.png | Bin 54253 -> 14766 bytes internal/api/grpc/admin/export.go | 3 +- internal/api/grpc/admin/lockout_converter.go | 1 + .../api/grpc/management/policy_lockout.go | 2 +- .../management/policy_lockout_converter.go | 2 + .../grpc/policy/password_lockout_policy.go | 1 + internal/api/grpc/settings/v2/settings.go | 2 +- .../grpc/settings/v2/settings_converter.go | 1 + .../settings/v2/settings_converter_test.go | 2 + .../eventsourcing/eventstore/auth_request.go | 5 +- .../eventstore/auth_request_test.go | 2 +- internal/command/instance.go | 5 +- internal/command/instance_converter.go | 1 + .../instance_policy_password_lockout.go | 23 +- .../instance_policy_password_lockout_model.go | 10 +- .../instance_policy_password_lockout_test.go | 19 +- internal/command/org_policy_lockout.go | 27 +- internal/command/org_policy_lockout_model.go | 10 +- internal/command/org_policy_lockout_test.go | 21 +- .../command/policy_password_lockout_model.go | 5 + internal/command/user_human_otp.go | 62 +- internal/command/user_human_otp_model.go | 77 +- internal/command/user_human_otp_test.go | 298 ++++++++ internal/command/user_human_password_test.go | 1 + internal/domain/policy_password_lockout.go | 1 + internal/query/lockout_policy.go | 16 +- internal/query/lockout_policy_test.go | 24 +- internal/query/projection/lockout_policy.go | 11 +- .../query/projection/lockout_policy_test.go | 22 +- .../instance/policy_password_lockout.go | 6 +- .../repository/org/policy_password_lockout.go | 6 +- .../policy/policy_password_lockout.go | 16 +- proto/zitadel/admin.proto | 6 + proto/zitadel/management.proto | 12 + proto/zitadel/policy.proto | 6 + .../settings/v2beta/lockout_settings.proto | 6 + 58 files changed, 752 insertions(+), 755 deletions(-) diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index f85e185ecd..baca5175e1 100644 --- a/cmd/defaults.yaml +++ b/cmd/defaults.yaml @@ -710,7 +710,8 @@ DefaultInstance: ErrorMsgPopup: false # ZITADEL_DEFAULTINSTANCE_LABELPOLICY_ERRORMSGPOPUP DisableWatermark: false # ZITADEL_DEFAULTINSTANCE_LABELPOLICY_DISABLEWATERMARK LockoutPolicy: - MaxAttempts: 0 # ZITADEL_DEFAULTINSTANCE_LOCKOUTPOLICY_MAXATTEMPTS + MaxPasswordAttempts: 0 # ZITADEL_DEFAULTINSTANCE_LOCKOUTPOLICY_MAXPASSWORDATTEMPTS + MaxOTPAttempts: 0 # ZITADEL_DEFAULTINSTANCE_LOCKOUTPOLICY_MAXOTPATTEMPTS ShouldShowLockoutFailure: true # ZITADEL_DEFAULTINSTANCE_LOCKOUTPOLICY_SHOULDSHOWLOCKOUTFAILURE EmailTemplate: CjwhZG9jdHlwZSBodG1sPgo8aHRtbCB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwiIHhtbG5zOm89InVybjpzY2hlbWFzLW1pY3Jvc29mdC1jb206b2ZmaWNlOm9mZmljZSI+CjxoZWFkPgogIDx0aXRsZT4KCiAgPC90aXRsZT4KICA8IS0tW2lmICFtc29dPjwhLS0+CiAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJJRT1lZGdlIj4KICA8IS0tPCFbZW5kaWZdLS0+CiAgPG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9VVRGLTgiPgogIDxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MSI+CiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KICAgICNvdXRsb29rIGEgeyBwYWRkaW5nOjA7IH0KICAgIGJvZHkgeyBtYXJnaW46MDtwYWRkaW5nOjA7LXdlYmtpdC10ZXh0LXNpemUtYWRqdXN0OjEwMCU7LW1zLXRleHQtc2l6ZS1hZGp1c3Q6MTAwJTsgfQogICAgdGFibGUsIHRkIHsgYm9yZGVyLWNvbGxhcHNlOmNvbGxhcHNlO21zby10YWJsZS1sc3BhY2U6MHB0O21zby10YWJsZS1yc3BhY2U6MHB0OyB9CiAgICBpbWcgeyBib3JkZXI6MDtoZWlnaHQ6YXV0bztsaW5lLWhlaWdodDoxMDAlOyBvdXRsaW5lOm5vbmU7dGV4dC1kZWNvcmF0aW9uOm5vbmU7LW1zLWludGVycG9sYXRpb24tbW9kZTpiaWN1YmljOyB9CiAgICBwIHsgZGlzcGxheTpibG9jazttYXJnaW46MTNweCAwOyB9CiAgPC9zdHlsZT4KICA8IS0tW2lmIG1zb10+CiAgPHhtbD4KICAgIDxvOk9mZmljZURvY3VtZW50U2V0dGluZ3M+CiAgICAgIDxvOkFsbG93UE5HLz4KICAgICAgPG86UGl4ZWxzUGVySW5jaD45NjwvbzpQaXhlbHNQZXJJbmNoPgogICAgPC9vOk9mZmljZURvY3VtZW50U2V0dGluZ3M+CiAgPC94bWw+CiAgPCFbZW5kaWZdLS0+CiAgPCEtLVtpZiBsdGUgbXNvIDExXT4KICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgLm1qLW91dGxvb2stZ3JvdXAtZml4IHsgd2lkdGg6MTAwJSAhaW1wb3J0YW50OyB9CiAgPC9zdHlsZT4KICA8IVtlbmRpZl0tLT4KCgogIDxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CiAgICBAbWVkaWEgb25seSBzY3JlZW4gYW5kIChtaW4td2lkdGg6NDgwcHgpIHsKICAgICAgLm1qLWNvbHVtbi1wZXItMTAwIHsgd2lkdGg6MTAwJSAhaW1wb3J0YW50OyBtYXgtd2lkdGg6IDEwMCU7IH0KICAgICAgLm1qLWNvbHVtbi1wZXItNjAgeyB3aWR0aDo2MCUgIWltcG9ydGFudDsgbWF4LXdpZHRoOiA2MCU7IH0KICAgIH0KICA8L3N0eWxlPgoKCiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCgoKICAgIEBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKG1heC13aWR0aDo0ODBweCkgewogICAgICB0YWJsZS5tai1mdWxsLXdpZHRoLW1vYmlsZSB7IHdpZHRoOiAxMDAlICFpbXBvcnRhbnQ7IH0KICAgICAgdGQubWotZnVsbC13aWR0aC1tb2JpbGUgeyB3aWR0aDogYXV0byAhaW1wb3J0YW50OyB9CiAgICB9CgogIDwvc3R5bGU+CiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc2hhZG93IGEgewogICAgYm94LXNoYWRvdzogMHB4IDNweCAxcHggLTJweCByZ2JhKDAsIDAsIDAsIDAuMiksIDBweCAycHggMnB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLCAwcHggMXB4IDVweCAwcHggcmdiYSgwLCAwLCAwLCAwLjEyKTsKICB9PC9zdHlsZT4KCiAge3tpZiAuRm9udFVSTH19CiAgPHN0eWxlPgogICAgQGZvbnQtZmFjZSB7CiAgICAgIGZvbnQtZmFtaWx5OiAne3suRm9udEZhY2VGYW1pbHl9fSc7CiAgICAgIGZvbnQtc3R5bGU6IG5vcm1hbDsKICAgICAgZm9udC1kaXNwbGF5OiBzd2FwOwogICAgICBzcmM6IHVybCh7ey5Gb250VVJMfX0pOwogICAgfQogIDwvc3R5bGU+CiAge3tlbmR9fQoKPC9oZWFkPgo8Ym9keSBzdHlsZT0id29yZC1zcGFjaW5nOm5vcm1hbDsiPgoKCjxkaXYKICAgICAgICBzdHlsZT0iIgo+CgogIDx0YWJsZQogICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9ImJhY2tncm91bmQ6e3suQmFja2dyb3VuZENvbG9yfX07YmFja2dyb3VuZC1jb2xvcjp7ey5CYWNrZ3JvdW5kQ29sb3J9fTt3aWR0aDoxMDAlO2JvcmRlci1yYWRpdXM6MTZweDsiCiAgPgogICAgPHRib2R5PgogICAgPHRyPgogICAgICA8dGQ+CgoKICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIGNsYXNzPSIiIHN0eWxlPSJ3aWR0aDo4MDBweDsiIHdpZHRoPSI4MDAiID48dHI+PHRkIHN0eWxlPSJsaW5lLWhlaWdodDowcHg7Zm9udC1zaXplOjBweDttc28tbGluZS1oZWlnaHQtcnVsZTpleGFjdGx5OyI+PCFbZW5kaWZdLS0+CgoKICAgICAgICA8ZGl2ICBzdHlsZT0ibWFyZ2luOjBweCBhdXRvO2JvcmRlci1yYWRpdXM6MTZweDttYXgtd2lkdGg6ODAwcHg7Ij4KCiAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7Ym9yZGVyLXJhZGl1czoxNnB4OyIKICAgICAgICAgID4KICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iZGlyZWN0aW9uOmx0cjtmb250LXNpemU6MHB4O3BhZGRpbmc6MjBweCAwO3BhZGRpbmctbGVmdDowO3RleHQtYWxpZ246Y2VudGVyOyIKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgcm9sZT0icHJlc2VudGF0aW9uIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+PHRyPjx0ZCBjbGFzcz0iIiB3aWR0aD0iODAwcHgiID48IVtlbmRpZl0tLT4KCiAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7IgogICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+CgoKICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgY2xhc3M9IiIgc3R5bGU9IndpZHRoOjgwMHB4OyIgd2lkdGg9IjgwMCIgPjx0cj48dGQgc3R5bGU9ImxpbmUtaGVpZ2h0OjBweDtmb250LXNpemU6MHB4O21zby1saW5lLWhlaWdodC1ydWxlOmV4YWN0bHk7Ij48IVtlbmRpZl0tLT4KCgogICAgICAgICAgICAgICAgICAgICAgPGRpdiAgc3R5bGU9Im1hcmdpbjowcHggYXV0bzttYXgtd2lkdGg6ODAwcHg7Ij4KCiAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSJ3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImRpcmVjdGlvbjpsdHI7Zm9udC1zaXplOjBweDtwYWRkaW5nOjA7dGV4dC1hbGlnbjpjZW50ZXI7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgcm9sZT0icHJlc2VudGF0aW9uIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+PHRyPjx0ZCBjbGFzcz0iIiBzdHlsZT0id2lkdGg6ODAwcHg7IiA+PCFbZW5kaWZdLS0+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3M9Im1qLWNvbHVtbi1wZXItMTAwIG1qLW91dGxvb2stZ3JvdXAtZml4IiBzdHlsZT0iZm9udC1zaXplOjA7bGluZS1oZWlnaHQ6MDt0ZXh0LWFsaWduOmxlZnQ7ZGlzcGxheTppbmxpbmUtYmxvY2s7d2lkdGg6MTAwJTtkaXJlY3Rpb246bHRyOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiA+PHRyPjx0ZCBzdHlsZT0idmVydGljYWwtYWxpZ246dG9wO3dpZHRoOjgwMHB4OyIgPjwhW2VuZGlmXS0tPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzcz0ibWotY29sdW1uLXBlci0xMDAgbWotb3V0bG9vay1ncm91cC1maXgiIHN0eWxlPSJmb250LXNpemU6MHB4O3RleHQtYWxpZ246bGVmdDtkaXJlY3Rpb246bHRyO2Rpc3BsYXk6aW5saW5lLWJsb2NrO3ZlcnRpY2FsLWFsaWduOnRvcDt3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHdpZHRoPSIxMDAlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjp0b3A7cGFkZGluZzowOyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7e2lmIC5Mb2dvVVJMfX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iIiB3aWR0aD0iMTAwJSIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzo1MHB4IDAgMzBweCAwO3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iYm9yZGVyLWNvbGxhcHNlOmNvbGxhcHNlO2JvcmRlci1zcGFjaW5nOjBweDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCAgc3R5bGU9IndpZHRoOjE4MHB4OyI+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGltZwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHQ9ImF1dG8iIHNyYz0ie3suTG9nb1VSTH19IiBzdHlsZT0iYm9yZGVyOjA7Ym9yZGVyLXJhZGl1czo4cHg7ZGlzcGxheTpibG9jaztvdXRsaW5lOm5vbmU7dGV4dC1kZWNvcmF0aW9uOm5vbmU7aGVpZ2h0OmF1dG87d2lkdGg6MTAwJTtmb250LXNpemU6MTNweDsiIHdpZHRoPSIxODAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge3tlbmR9fQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PC90ZD48L3RyPjwvdGFibGU+PCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PC90ZD48L3RyPjwvdGFibGU+PCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KCgogICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PC90ZD48L3RyPjwvdGFibGU+PCFbZW5kaWZdLS0+CgoKICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PHRyPjx0ZCBjbGFzcz0iIiB3aWR0aD0iODAwcHgiID48IVtlbmRpZl0tLT4KCiAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7IgogICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+CgoKICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgY2xhc3M9IiIgc3R5bGU9IndpZHRoOjgwMHB4OyIgd2lkdGg9IjgwMCIgPjx0cj48dGQgc3R5bGU9ImxpbmUtaGVpZ2h0OjBweDtmb250LXNpemU6MHB4O21zby1saW5lLWhlaWdodC1ydWxlOmV4YWN0bHk7Ij48IVtlbmRpZl0tLT4KCgogICAgICAgICAgICAgICAgICAgICAgPGRpdiAgc3R5bGU9Im1hcmdpbjowcHggYXV0bzttYXgtd2lkdGg6ODAwcHg7Ij4KCiAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSJ3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImRpcmVjdGlvbjpsdHI7Zm9udC1zaXplOjBweDtwYWRkaW5nOjA7dGV4dC1hbGlnbjpjZW50ZXI7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgcm9sZT0icHJlc2VudGF0aW9uIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+PHRyPjx0ZCBjbGFzcz0iIiBzdHlsZT0idmVydGljYWwtYWxpZ246dG9wO3dpZHRoOjQ4MHB4OyIgPjwhW2VuZGlmXS0tPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsYXNzPSJtai1jb2x1bW4tcGVyLTYwIG1qLW91dGxvb2stZ3JvdXAtZml4IiBzdHlsZT0iZm9udC1zaXplOjBweDt0ZXh0LWFsaWduOmxlZnQ7ZGlyZWN0aW9uOmx0cjtkaXNwbGF5OmlubGluZS1ibG9jazt2ZXJ0aWNhbC1hbGlnbjp0b3A7d2lkdGg6MTAwJTsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiB3aWR0aD0iMTAwJSIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCAgc3R5bGU9InZlcnRpY2FsLWFsaWduOnRvcDtwYWRkaW5nOjA7Ij4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iIiB3aWR0aD0iMTAwJSIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBzdHlsZT0iZm9udC1zaXplOjBweDtwYWRkaW5nOjEwcHggMjVweDt3b3JkLWJyZWFrOmJyZWFrLXdvcmQ7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0eWxlPSJmb250LWZhbWlseTp7ey5Gb250RmFtaWx5fX07Zm9udC1zaXplOjI0cHg7Zm9udC13ZWlnaHQ6NTAwO2xpbmUtaGVpZ2h0OjE7dGV4dC1hbGlnbjpjZW50ZXI7Y29sb3I6e3suRm9udENvbG9yfX07IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID57ey5HcmVldGluZ319PC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIHN0eWxlPSJmb250LXNpemU6MHB4O3BhZGRpbmc6MTBweCAyNXB4O3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImZvbnQtZmFtaWx5Ont7LkZvbnRGYW1pbHl9fTtmb250LXNpemU6MTZweDtmb250LXdlaWdodDpsaWdodDtsaW5lLWhlaWdodDoxLjU7dGV4dC1hbGlnbjpjZW50ZXI7Y29sb3I6e3suRm9udENvbG9yfX07IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID57ey5UZXh0fX08L2Rpdj4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgoKCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIHZlcnRpY2FsLWFsaWduPSJtaWRkbGUiIGNsYXNzPSJzaGFkb3ciIHN0eWxlPSJmb250LXNpemU6MHB4O3BhZGRpbmc6MTBweCAyNXB4O3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iYm9yZGVyLWNvbGxhcHNlOnNlcGFyYXRlO2xpbmUtaGVpZ2h0OjEwMCU7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYmdjb2xvcj0ie3suUHJpbWFyeUNvbG9yfX0iIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9ImJvcmRlcjpub25lO2JvcmRlci1yYWRpdXM6NnB4O2N1cnNvcjphdXRvO21zby1wYWRkaW5nLWFsdDoxMHB4IDI1cHg7YmFja2dyb3VuZDp7ey5QcmltYXJ5Q29sb3J9fTsiIHZhbGlnbj0ibWlkZGxlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhyZWY9Int7LlVSTH19IiByZWw9Im5vb3BlbmVyIG5vcmVmZXJyZXIgbm90cmFjayIgc3R5bGU9ImRpc3BsYXk6aW5saW5lLWJsb2NrO2JhY2tncm91bmQ6e3suUHJpbWFyeUNvbG9yfX07Y29sb3I6I2ZmZmZmZjtmb250LWZhbWlseTp7ey5Gb250RmFtaWx5fX07Zm9udC1zaXplOjE0cHg7Zm9udC13ZWlnaHQ6NTAwO2xpbmUtaGVpZ2h0OjEyMCU7bWFyZ2luOjA7dGV4dC1kZWNvcmF0aW9uOm5vbmU7dGV4dC10cmFuc2Zvcm06bm9uZTtwYWRkaW5nOjEwcHggMjVweDttc28tcGFkZGluZy1hbHQ6MHB4O2JvcmRlci1yYWRpdXM6NnB4OyIgdGFyZ2V0PSJfYmxhbmsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge3suQnV0dG9uVGV4dH19CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9hPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7e2lmIC5JbmNsdWRlRm9vdGVyfX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxMHB4IDI1cHg7cGFkZGluZy10b3A6MjBweDtwYWRkaW5nLXJpZ2h0OjIwcHg7cGFkZGluZy1ib3R0b206MjBweDtwYWRkaW5nLWxlZnQ6MjBweDt3b3JkLWJyZWFrOmJyZWFrLXdvcmQ7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iYm9yZGVyLXRvcDpzb2xpZCAycHggI2RiZGJkYjtmb250LXNpemU6MXB4O21hcmdpbjowcHggYXV0bzt3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9wPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHN0eWxlPSJib3JkZXItdG9wOnNvbGlkIDJweCAjZGJkYmRiO2ZvbnQtc2l6ZToxcHg7bWFyZ2luOjBweCBhdXRvO3dpZHRoOjQ0MHB4OyIgcm9sZT0icHJlc2VudGF0aW9uIiB3aWR0aD0iNDQwcHgiID48dHI+PHRkIHN0eWxlPSJoZWlnaHQ6MDtsaW5lLWhlaWdodDowOyI+ICZuYnNwOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgoKCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxNnB4O3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImZvbnQtZmFtaWx5Ont7LkZvbnRGYW1pbHl9fTtmb250LXNpemU6MTNweDtsaW5lLWhlaWdodDoxO3RleHQtYWxpZ246Y2VudGVyO2NvbG9yOnt7LkZvbnRDb2xvcn19OyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+e3suRm9vdGVyVGV4dH19PC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHt7ZW5kfX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKCiAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KCgogICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgogICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICA8L2Rpdj4KCgogICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgoKCiAgICAgIDwvdGQ+CiAgICA8L3RyPgogICAgPC90Ym9keT4KICA8L3RhYmxlPgoKPC9kaXY+Cgo8L2JvZHk+CjwvaHRtbD4K # ZITADEL_DEFAULTINSTANCE_EMAILTEMPLATE # Sets the default values for lifetime and expiration for OIDC in each newly created instance diff --git a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.html b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.html index 9a515d1890..6591d4613b 100644 --- a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.html +++ b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.html @@ -17,17 +17,49 @@
- {{ lockoutData.maxPasswordAttempts }} -
- {{ 'POLICY.DATA.MAXATTEMPTS' | translate }} + {{ 'POLICY.DATA.MAXPASSWORDATTEMPTS' | translate }} + +
+
+
+
+ + {{ lockoutData.maxOtpAttempts }} + +
+ +
+ {{ 'POLICY.DATA.MAXOTPATTEMPTS' | translate }}
diff --git a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.ts b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.ts index ad4ff29b4f..e56857770d 100644 --- a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.ts +++ b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.ts @@ -91,24 +91,36 @@ export class PasswordLockoutPolicyComponent implements OnInit { } } - public incrementMaxAttempts(): void { + public incrementPasswordMaxAttempts(): void { if (this.lockoutData?.maxPasswordAttempts !== undefined) { this.lockoutData.maxPasswordAttempts++; } } - public decrementMaxAttempts(): void { + public decrementPasswordMaxAttempts(): void { if (this.lockoutData?.maxPasswordAttempts && this.lockoutData?.maxPasswordAttempts > 0) { this.lockoutData.maxPasswordAttempts--; } } + public incrementOTPMaxAttempts(): void { + if (this.lockoutData?.maxOtpAttempts !== undefined) { + this.lockoutData.maxOtpAttempts++; + } + } + + public decrementOTPMaxAttempts(): void { + if (this.lockoutData?.maxOtpAttempts && this.lockoutData?.maxOtpAttempts > 0) { + this.lockoutData.maxOtpAttempts--; + } + } + public savePolicy(): void { let promise: Promise; if (this.lockoutData) { if (this.service instanceof AdminService) { promise = this.service - .updateLockoutPolicy(this.lockoutData.maxPasswordAttempts) + .updateLockoutPolicy(this.lockoutData.maxPasswordAttempts, this.lockoutData.maxOtpAttempts) .then(() => { this.toast.showInfo('POLICY.TOAST.SET', true); this.fetchData(); @@ -119,7 +131,7 @@ export class PasswordLockoutPolicyComponent implements OnInit { } else { if ((this.lockoutData as LockoutPolicy.AsObject).isDefault) { promise = (this.service as ManagementService) - .addCustomLockoutPolicy(this.lockoutData.maxPasswordAttempts) + .addCustomLockoutPolicy(this.lockoutData.maxPasswordAttempts, this.lockoutData.maxOtpAttempts) .then(() => { this.toast.showInfo('POLICY.TOAST.SET', true); this.fetchData(); @@ -129,7 +141,7 @@ export class PasswordLockoutPolicyComponent implements OnInit { }); } else { promise = (this.service as ManagementService) - .updateCustomLockoutPolicy(this.lockoutData.maxPasswordAttempts) + .updateCustomLockoutPolicy(this.lockoutData.maxPasswordAttempts, this.lockoutData.maxOtpAttempts) .then(() => { this.toast.showInfo('POLICY.TOAST.SET', true); this.fetchData(); diff --git a/console/src/app/services/admin.service.ts b/console/src/app/services/admin.service.ts index 17c8f1b323..7149317ab4 100644 --- a/console/src/app/services/admin.service.ts +++ b/console/src/app/services/admin.service.ts @@ -957,9 +957,13 @@ export class AdminService { return this.grpcService.admin.getLockoutPolicy(req, null).then((resp) => resp.toObject()); } - public updateLockoutPolicy(maxAttempts: number): Promise { + public updateLockoutPolicy( + maxPasswordAttempts: number, + maxOTPAttempts: number, + ): Promise { const req = new UpdateLockoutPolicyRequest(); - req.setMaxPasswordAttempts(maxAttempts); + req.setMaxPasswordAttempts(maxPasswordAttempts); + req.setMaxOtpAttempts(maxOTPAttempts); return this.grpcService.admin.updateLockoutPolicy(req, null).then((resp) => resp.toObject()); } diff --git a/console/src/app/services/mgmt.service.ts b/console/src/app/services/mgmt.service.ts index ae975a228d..0e3c0b3062 100644 --- a/console/src/app/services/mgmt.service.ts +++ b/console/src/app/services/mgmt.service.ts @@ -1587,9 +1587,13 @@ export class ManagementService { return this.grpcService.mgmt.getLockoutPolicy(req, null).then((resp) => resp.toObject()); } - public addCustomLockoutPolicy(maxAttempts: number): Promise { + public addCustomLockoutPolicy( + maxPasswordAttempts: number, + maxOTPAttempts: number, + ): Promise { const req = new AddCustomLockoutPolicyRequest(); - req.setMaxPasswordAttempts(maxAttempts); + req.setMaxPasswordAttempts(maxPasswordAttempts); + req.setMaxOtpAttempts(maxOTPAttempts); return this.grpcService.mgmt.addCustomLockoutPolicy(req, null).then((resp) => resp.toObject()); } @@ -1599,9 +1603,13 @@ export class ManagementService { return this.grpcService.mgmt.resetLockoutPolicyToDefault(req, null).then((resp) => resp.toObject()); } - public updateCustomLockoutPolicy(maxAttempts: number): Promise { + public updateCustomLockoutPolicy( + maxPasswordAttempts: number, + maxOTPAttempts: number, + ): Promise { const req = new UpdateCustomLockoutPolicyRequest(); - req.setMaxPasswordAttempts(maxAttempts); + req.setMaxPasswordAttempts(maxPasswordAttempts); + req.setMaxOtpAttempts(maxOTPAttempts); return this.grpcService.mgmt.updateCustomLockoutPolicy(req, null).then((resp) => resp.toObject()); } diff --git a/console/src/assets/i18n/bg.json b/console/src/assets/i18n/bg.json index 2ddcec25c4..6add8de104 100644 --- a/console/src/assets/i18n/bg.json +++ b/console/src/assets/i18n/bg.json @@ -1653,7 +1653,8 @@ "HASLOWERCASE": "има малки букви", "HASUPPERCASE": "има главни букви", "SHOWLOCKOUTFAILURES": "показва грешки при блокиране", - "MAXATTEMPTS": "Максимален брой опити за парола", + "MAXPASSWORDATTEMPTS": "Максимален брой опити за парола", + "MAXOTPATTEMPTS": "Максимален брой опити за OTP", "EXPIREWARNDAYS": "Предупреждение за изтичане след ден", "MAXAGEDAYS": "Максимална възраст в дни", "USERLOGINMUSTBEDOMAIN": "Добавяне на домейн на организация като суфикс към имената за вход", diff --git a/console/src/assets/i18n/cs.json b/console/src/assets/i18n/cs.json index c893c5d8c3..c2c875c78c 100644 --- a/console/src/assets/i18n/cs.json +++ b/console/src/assets/i18n/cs.json @@ -1660,7 +1660,8 @@ "HASLOWERCASE": "obsahuje malá písmena", "HASUPPERCASE": "obsahuje velká písmena", "SHOWLOCKOUTFAILURES": "zobrazit neúspěšné pokusy o uzamčení", - "MAXATTEMPTS": "Maximální počet pokusů o heslo", + "MAXPASSWORDATTEMPTS": "Maximální počet pokusů o heslo", + "MAXOTPATTEMPTS": "Maximální počet pokusů o OTP", "EXPIREWARNDAYS": "Upozornění na expiraci po dni", "MAXAGEDAYS": "Maximální stáří v dnech", "USERLOGINMUSTBEDOMAIN": "Přidat doménu organizace jako příponu k přihlašovacím jménům", diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index e0f403b07b..f4d1cf8e6c 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -1659,7 +1659,8 @@ "HASLOWERCASE": "erfordert Kleinbuchstaben", "HASUPPERCASE": "erfordert Grossbuchstaben", "SHOWLOCKOUTFAILURES": "Zeige Anzahl Anmeldeversuche", - "MAXATTEMPTS": "Maximale Anzahl an Versuchen", + "MAXPASSWORDATTEMPTS": "Maximale Anzahl an Passwort Versuchen", + "MAXOTPATTEMPTS": "Maximale Anzahl an OTP Versuchen", "EXPIREWARNDAYS": "Ablauf Warnung nach Tagen", "MAXAGEDAYS": "Maximale Gültigkeit in Tagen", "USERLOGINMUSTBEDOMAIN": "Organisationsdomain dem Loginname hinzufügen", diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index 72cee6c0f3..0427a4af5b 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -1660,7 +1660,8 @@ "HASLOWERCASE": "must include a lowercase letter", "HASUPPERCASE": "must include an uppercase letter", "SHOWLOCKOUTFAILURES": "show lockout failures", - "MAXATTEMPTS": "Password maximum Attempts", + "MAXPASSWORDATTEMPTS": "Password maximum attempts", + "MAXOTPATTEMPTS": "OTP maximum attempts", "EXPIREWARNDAYS": "Expiration Warning after day", "MAXAGEDAYS": "Max Age in days", "USERLOGINMUSTBEDOMAIN": "Add organization domain as suffix to loginnames", diff --git a/console/src/assets/i18n/es.json b/console/src/assets/i18n/es.json index 819c078905..e5630cd0a8 100644 --- a/console/src/assets/i18n/es.json +++ b/console/src/assets/i18n/es.json @@ -1661,7 +1661,8 @@ "HASLOWERCASE": "tiene minúsculas", "HASUPPERCASE": "tiene mayúsculas", "SHOWLOCKOUTFAILURES": "mostrar fallos de bloqueo", - "MAXATTEMPTS": "Intentos máximos", + "MAXPASSWORDATTEMPTS": "Intentos máximos de contraseña", + "MAXOTPATTEMPTS": "Intentos máximos de OTP", "EXPIREWARNDAYS": "Aviso de expiración después de estos días: ", "MAXAGEDAYS": "Antigüedad máxima en días", "USERLOGINMUSTBEDOMAIN": "Añadir el dominio de la organización como sufijo de los nombres de inicio de sesión", diff --git a/console/src/assets/i18n/fr.json b/console/src/assets/i18n/fr.json index b88bed51b4..929dce791a 100644 --- a/console/src/assets/i18n/fr.json +++ b/console/src/assets/i18n/fr.json @@ -1659,7 +1659,8 @@ "HASLOWERCASE": "a minuscule", "HASUPPERCASE": "a majuscule", "SHOWLOCKOUTFAILURES": "montrer les échecs de verrouillage", - "MAXATTEMPTS": "Mot de passe maximum Tentatives", + "MAXPASSWORDATTEMPTS": "Mot de passe maximum tentatives", + "MAXOTPATTEMPTS": "Maximal de tentatives OTP", "EXPIREWARNDAYS": "Expiration Avertissement après le jour", "MAXAGEDAYS": "Âge maximum en jours", "USERLOGINMUSTBEDOMAIN": "Le nom de connexion de l'utilisateur doit contenir le nom de domaine de l'organisation", diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json index 5d1cc463ea..747e857157 100644 --- a/console/src/assets/i18n/it.json +++ b/console/src/assets/i18n/it.json @@ -1659,7 +1659,8 @@ "HASLOWERCASE": "ha la minuscola", "HASUPPERCASE": "ha la maiuscola", "SHOWLOCKOUTFAILURES": "mostra i fallimenti del blocco", - "MAXATTEMPTS": "Massimo numero di tentativi di password", + "MAXPASSWORDATTEMPTS": "Massimo numero di tentativi di password", + "MAXOTPATTEMPTS": "Massimo numero di tentativi di OTP", "EXPIREWARNDAYS": "Avviso scadenza dopo il giorno", "MAXAGEDAYS": "Lunghezza massima in giorni", "USERLOGINMUSTBEDOMAIN": "Nome utente deve contenere il dominio dell' organizzazione", diff --git a/console/src/assets/i18n/ja.json b/console/src/assets/i18n/ja.json index 6036a62fe6..dd8130b6dd 100644 --- a/console/src/assets/i18n/ja.json +++ b/console/src/assets/i18n/ja.json @@ -1656,7 +1656,8 @@ "HASLOWERCASE": "小文字を含める", "HASUPPERCASE": "大文字を含める", "SHOWLOCKOUTFAILURES": "ロックアウトの失敗を表示する", - "MAXATTEMPTS": "パスワードの最大試行", + "MAXPASSWORDATTEMPTS": "パスワードの最大試行", + "MAXOTPATTEMPTS": "最大OTP試行回数", "EXPIREWARNDAYS": "有効期限の翌日以降の警告", "MAXAGEDAYS": "最大有効期限", "USERLOGINMUSTBEDOMAIN": "ログイン名の接尾辞として組織ドメインを追加する", diff --git a/console/src/assets/i18n/mk.json b/console/src/assets/i18n/mk.json index c7e68f3dad..f12c16f09b 100644 --- a/console/src/assets/i18n/mk.json +++ b/console/src/assets/i18n/mk.json @@ -1661,7 +1661,8 @@ "HASLOWERCASE": "има мали букви", "HASUPPERCASE": "има големи букви", "SHOWLOCKOUTFAILURES": "прикажи неуспешни заклучувања", - "MAXATTEMPTS": "Максимален број на обиди за лозинка", + "MAXPASSWORDATTEMPTS": "Максимален број на обиди за лозинка", + "MAXOTPATTEMPTS": "Максимални обиди за OTP", "EXPIREWARNDAYS": "Предупредување за истекување по ден", "MAXAGEDAYS": "Максимална возраст во денови", "USERLOGINMUSTBEDOMAIN": "Додади организациски домен како суфикс на корисничките имиња", diff --git a/console/src/assets/i18n/nl.json b/console/src/assets/i18n/nl.json index 1bfe7214b2..1da10f8e57 100644 --- a/console/src/assets/i18n/nl.json +++ b/console/src/assets/i18n/nl.json @@ -1660,7 +1660,8 @@ "HASLOWERCASE": "heeft kleine letters", "HASUPPERCASE": "heeft hoofdletters", "SHOWLOCKOUTFAILURES": "toon lockout mislukkingen", - "MAXATTEMPTS": "Maximum pogingen voor wachtwoord", + "MAXPASSWORDATTEMPTS": "Maximum pogingen voor wachtwoord", + "MAXOTPATTEMPTS": "Maximale OTP-pogingen", "EXPIREWARNDAYS": "Vervaldatum Waarschuwing na dag", "MAXAGEDAYS": "Maximale Leeftijd in dagen", "USERLOGINMUSTBEDOMAIN": "Voeg organisatie domein toe als achtervoegsel aan inlognamen", diff --git a/console/src/assets/i18n/pl.json b/console/src/assets/i18n/pl.json index e1b632cd3e..ef572b7beb 100644 --- a/console/src/assets/i18n/pl.json +++ b/console/src/assets/i18n/pl.json @@ -1659,7 +1659,8 @@ "HASLOWERCASE": "zawiera małe litery", "HASUPPERCASE": "zawiera duże litery", "SHOWLOCKOUTFAILURES": "pokaż blokady nieudanych prób", - "MAXATTEMPTS": "Maksymalna liczba prób wprowadzenia hasła", + "MAXPASSWORDATTEMPTS": "Maksymalna liczba prób wprowadzenia hasła", + "MAXOTPATTEMPTS": "Maksymalna liczba prób OTP", "EXPIREWARNDAYS": "Ostrzeżenie o wygaśnięciu po dniu", "MAXAGEDAYS": "Maksymalny wiek w dniach", "USERLOGINMUSTBEDOMAIN": "Dodaj domenę organizacji jako przyrostek do nazw logowania", diff --git a/console/src/assets/i18n/pt.json b/console/src/assets/i18n/pt.json index 4cbcfe4e16..9fdb634ab6 100644 --- a/console/src/assets/i18n/pt.json +++ b/console/src/assets/i18n/pt.json @@ -1661,7 +1661,8 @@ "HASLOWERCASE": "tem letra minúscula", "HASUPPERCASE": "tem letra maiúscula", "SHOWLOCKOUTFAILURES": "mostrar falhas de bloqueio", - "MAXATTEMPTS": "Máximo de tentativas de senha", + "MAXPASSWORDATTEMPTS": "Máximo de tentativas de senha", + "MAXOTPATTEMPTS": "Máximo de tentativas de OTP", "EXPIREWARNDAYS": "Aviso de expiração após dias", "MAXAGEDAYS": "Idade máxima em dias", "USERLOGINMUSTBEDOMAIN": "Adicionar domínio da organização como sufixo aos nomes de login", diff --git a/console/src/assets/i18n/ru.json b/console/src/assets/i18n/ru.json index 2f2c63e0e5..ff38f488d4 100644 --- a/console/src/assets/i18n/ru.json +++ b/console/src/assets/i18n/ru.json @@ -1729,7 +1729,8 @@ "HASLOWERCASE": "Содержит нижний регистр", "HASUPPERCASE": "Содержит верхний регистр", "SHOWLOCKOUTFAILURES": "Показать ошибки блокировки", - "MAXATTEMPTS": "Максимальное количество попыток пароля", + "MAXPASSWORDATTEMPTS": "Максимальное количество попыток пароля", + "MAXOTPATTEMPTS": "Максимальное количество попыток OTP", "EXPIREWARNDAYS": "Предупреждение об истечении срока действия после дня", "MAXAGEDAYS": "Максимальный возраст в днях", "USERLOGINMUSTBEDOMAIN": "Добавить домен организации в качестве суффикса к именам логина", diff --git a/console/src/assets/i18n/zh.json b/console/src/assets/i18n/zh.json index d4a8c7ead4..57d5ba2490 100644 --- a/console/src/assets/i18n/zh.json +++ b/console/src/assets/i18n/zh.json @@ -1658,7 +1658,8 @@ "HASLOWERCASE": "包含小写字母", "HASUPPERCASE": "包含大写字母", "SHOWLOCKOUTFAILURES": "显示锁定失败", - "MAXATTEMPTS": "密码最大尝试次数", + "MAXPASSWORDATTEMPTS": "密码最大尝试次数", + "MAXOTPATTEMPTS": "最多尝试 OTP 次数", "EXPIREWARNDAYS": "密码过期警告", "MAXAGEDAYS": "Max Age in days", "USERLOGINMUSTBEDOMAIN": "用户名必须包含组织域名", diff --git a/console/yarn.lock b/console/yarn.lock index 7b6584a842..de6a28e268 100644 --- a/console/yarn.lock +++ b/console/yarn.lock @@ -2103,18 +2103,6 @@ protobufjs "^7.2.4" yargs "^17.7.2" -"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": - version "9.3.0" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" - integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== - -"@hapi/topo@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" - integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== - dependencies: - "@hapi/hoek" "^9.0.0" - "@humanwhocodes/config-array@^0.11.11": version "0.11.11" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844" @@ -3193,23 +3181,6 @@ "@angular-devkit/schematics" "16.2.2" jsonc-parser "3.2.0" -"@sideway/address@^4.1.5": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" - integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== - dependencies: - "@hapi/hoek" "^9.0.0" - -"@sideway/formula@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" - integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== - -"@sideway/pinpoint@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" - integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== - "@sigstore/protobuf-specs@^0.1.0": version "0.1.0" resolved "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.1.0.tgz" @@ -4246,30 +4217,11 @@ big.js@^5.2.2: resolved "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== -bin-build@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bin-build/-/bin-build-3.0.0.tgz#c5780a25a8a9f966d8244217e6c1f5082a143861" - integrity sha512-jcUOof71/TNAI2uM5uoUaDq2ePcVBQ3R/qhxAz1rX7UfvduAL/RXD3jXzvn8cVcDJdGVkiR1shal3OH0ImpuhA== - dependencies: - decompress "^4.0.0" - download "^6.2.2" - execa "^0.7.0" - p-map-series "^1.0.0" - tempfile "^2.0.0" - binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -bl@^1.0.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7" - integrity sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww== - dependencies: - readable-stream "^2.3.5" - safe-buffer "^5.1.1" - bl@^4.0.3, bl@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" @@ -4391,35 +4343,12 @@ browserstack@^1.5.1: dependencies: https-proxy-agent "^2.2.1" -buffer-alloc-unsafe@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" - integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== - -buffer-alloc@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" - integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== - dependencies: - buffer-alloc-unsafe "^1.1.0" - buffer-fill "^1.0.0" - -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== - -buffer-fill@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" - integrity sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ== - buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer@^5.2.1, buffer@^5.5.0: +buffer@^5.5.0: version "5.7.1" resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -4527,16 +4456,6 @@ caseless@~0.12.0: resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz" integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== -caw@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/caw/-/caw-2.0.1.tgz#6c3ca071fc194720883c2dc5da9b074bfc7e9e95" - integrity sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA== - dependencies: - get-proxy "^2.0.0" - isurl "^1.0.0-alpha5" - tunnel-agent "^0.6.0" - url-to-options "^1.0.1" - chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" @@ -4741,7 +4660,7 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@^2.11.0, commander@^2.20.0, commander@^2.8.1: +commander@^2.11.0, commander@^2.20.0: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -4776,14 +4695,6 @@ concat-map@0.0.1: resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -config-chain@^1.1.11: - version "1.1.13" - resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" - integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== - dependencies: - ini "^1.3.4" - proto-list "~1.2.1" - connect-history-api-fallback@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz" @@ -4804,7 +4715,7 @@ console-control-strings@^1.1.0: resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== -content-disposition@0.5.4, content-disposition@^0.5.2: +content-disposition@0.5.4: version "0.5.4" resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== @@ -4903,15 +4814,6 @@ critters@0.0.20: postcss "^8.4.23" pretty-bytes "^5.3.0" -cross-spawn@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - integrity sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A== - dependencies: - lru-cache "^4.0.1" - shebang-command "^1.2.0" - which "^1.2.9" - cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" @@ -5050,66 +4952,6 @@ decimal.js@^10.2.1: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== -decompress-response@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA== - dependencies: - mimic-response "^1.0.0" - -decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1" - integrity sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ== - dependencies: - file-type "^5.2.0" - is-stream "^1.1.0" - tar-stream "^1.5.2" - -decompress-tarbz2@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b" - integrity sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A== - dependencies: - decompress-tar "^4.1.0" - file-type "^6.1.0" - is-stream "^1.1.0" - seek-bzip "^1.0.5" - unbzip2-stream "^1.0.9" - -decompress-targz@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee" - integrity sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w== - dependencies: - decompress-tar "^4.1.1" - file-type "^5.2.0" - is-stream "^1.1.0" - -decompress-unzip@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69" - integrity sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw== - dependencies: - file-type "^3.8.0" - get-stream "^2.2.0" - pify "^2.3.0" - yauzl "^2.4.2" - -decompress@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.1.tgz#007f55cc6a62c055afa37c07eb6a4ee1b773f118" - integrity sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ== - dependencies: - decompress-tar "^4.0.0" - decompress-tarbz2 "^4.0.0" - decompress-targz "^4.0.0" - decompress-unzip "^4.0.1" - graceful-fs "^4.1.10" - make-dir "^1.0.0" - pify "^2.3.0" - strip-dirs "^2.0.0" - deep-is@^0.1.3: version "0.1.4" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" @@ -5275,28 +5117,6 @@ dotenv@~10.0.0: resolved "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== -download@^6.2.2: - version "6.2.5" - resolved "https://registry.yarnpkg.com/download/-/download-6.2.5.tgz#acd6a542e4cd0bb42ca70cfc98c9e43b07039714" - integrity sha512-DpO9K1sXAST8Cpzb7kmEhogJxymyVUd5qz/vCOSyvwtp2Klj2XcDt5YUuasgxka44SxF0q5RriKIwJmQHG2AuA== - dependencies: - caw "^2.0.0" - content-disposition "^0.5.2" - decompress "^4.0.0" - ext-name "^5.0.0" - file-type "5.2.0" - filenamify "^2.0.0" - get-stream "^3.0.0" - got "^7.0.0" - make-dir "^1.0.0" - p-event "^1.0.0" - pify "^3.0.0" - -duplexer3@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e" - integrity sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA== - duplexer@^0.1.1: version "0.1.2" resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz" @@ -5369,7 +5189,7 @@ encoding@^0.1.13: dependencies: iconv-lite "^0.6.2" -end-of-stream@^1.0.0, end-of-stream@^1.4.1: +end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -5687,19 +5507,6 @@ events@^3.2.0: resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -execa@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" - integrity sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw== - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - execa@^5.0.0: version "5.1.1" resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" @@ -5757,21 +5564,6 @@ express@^4.17.3: utils-merge "1.0.1" vary "~1.1.2" -ext-list@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" - integrity sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA== - dependencies: - mime-db "^1.28.0" - -ext-name@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ext-name/-/ext-name-5.0.0.tgz#70781981d183ee15d13993c8822045c506c8f0a6" - integrity sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ== - dependencies: - ext-list "^2.0.0" - sort-keys-length "^1.0.0" - extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" @@ -5863,13 +5655,6 @@ faye-websocket@^0.11.3: dependencies: websocket-driver ">=0.5.1" -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== - dependencies: - pend "~1.2.0" - figures@3.2.0, figures@^3.0.0: version "3.2.0" resolved "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz" @@ -5889,21 +5674,6 @@ file-saver@^2.0.5: resolved "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz" integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== -file-type@5.2.0, file-type@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6" - integrity sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ== - -file-type@^3.8.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" - integrity sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA== - -file-type@^6.1.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919" - integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg== - filelist@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz" @@ -5911,20 +5681,6 @@ filelist@^1.0.4: dependencies: minimatch "^5.0.1" -filename-reserved-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" - integrity sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ== - -filenamify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-2.1.0.tgz#88faf495fb1b47abfd612300002a16228c677ee9" - integrity sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA== - dependencies: - filename-reserved-regex "^2.0.0" - strip-outer "^1.0.0" - trim-repeated "^1.0.0" - fill-range@^7.0.1: version "7.0.1" resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" @@ -6179,26 +5935,6 @@ get-package-type@^0.1.0: resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-proxy@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/get-proxy/-/get-proxy-2.1.0.tgz#349f2b4d91d44c4d4d4e9cba2ad90143fac5ef93" - integrity sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw== - dependencies: - npm-conf "^1.1.0" - -get-stream@^2.2.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" - integrity sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA== - dependencies: - object-assign "^4.0.1" - pinkie-promise "^2.0.0" - -get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ== - get-stream@^6.0.0: version "6.0.1" resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" @@ -6336,27 +6072,7 @@ google-protobuf@^3.21.2: resolved "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.2.tgz" integrity sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA== -got@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a" - integrity sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw== - dependencies: - decompress-response "^3.2.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - is-plain-obj "^1.1.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - isurl "^1.0.0-alpha5" - lowercase-keys "^1.0.0" - p-cancelable "^0.3.0" - p-timeout "^1.1.1" - safe-buffer "^5.0.1" - timed-out "^4.0.0" - url-parse-lax "^1.0.0" - url-to-options "^1.0.1" - -graceful-fs@^4.1.10, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -6418,23 +6134,11 @@ has-proto@^1.0.1: resolved "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz" integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== -has-symbol-support-x@^1.4.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" - integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== - has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -has-to-string-tag-x@^1.2.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" - integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw== - dependencies: - has-symbol-support-x "^1.4.1" - has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" @@ -6811,11 +6515,6 @@ is-docker@^2.0.0, is-docker@^2.1.1: resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== -is-extglob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" - integrity sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww== - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" @@ -6826,13 +6525,6 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-glob@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" - integrity sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg== - dependencies: - is-extglob "^1.0.0" - is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" @@ -6845,33 +6537,16 @@ is-interactive@^1.0.0: resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== -is-invalid-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-invalid-path/-/is-invalid-path-0.1.0.tgz#307a855b3cf1a938b44ea70d2c61106053714f34" - integrity sha512-aZMG0T3F34mTg4eTdszcGXx54oiZ4NtHSft3hWNJMGJXUUqdIj3cOZuHcU0nCWWcY3jd7yRe/3AEm3vSNTpBGQ== - dependencies: - is-glob "^2.0.0" - is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz" integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== -is-natural-number@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" - integrity sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ== - is-number@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf" - integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA== - is-path-cwd@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz" @@ -6896,11 +6571,6 @@ is-path-inside@^3.0.3: resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== -is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== - is-plain-obj@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz" @@ -6923,16 +6593,6 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== -is-retry-allowed@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" - integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== - -is-stream@^1.0.0, is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== - is-stream@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" @@ -6948,13 +6608,6 @@ is-unicode-supported@^0.1.0: resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -is-valid-path@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-valid-path/-/is-valid-path-0.1.1.tgz#110f9ff74c37f663e1ec7915eb451f2db93ac9df" - integrity sha512-+kwPrVDu9Ms03L90Qaml+79+6DZHqHyRoANI6IsZJ/g8frhnfchDOBCa0RbQ6/kdHt5CS5OeIEyrYznNuVN+8A== - dependencies: - is-invalid-path "^0.1.0" - is-what@^3.14.1: version "3.14.1" resolved "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz" @@ -7041,14 +6694,6 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -isurl@^1.0.0-alpha5: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" - integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w== - dependencies: - has-to-string-tag-x "^1.2.0" - is-object "^1.0.1" - jackspeak@^2.0.3: version "2.2.0" resolved "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.0.tgz" @@ -7113,17 +6758,6 @@ jiti@^1.18.2: resolved "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz" integrity sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg== -joi@^17.4.0: - version "17.12.2" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.12.2.tgz#283a664dabb80c7e52943c557aab82faea09f521" - integrity sha512-RonXAIzCiHLc8ss3Ibuz45u28GOsWE1UpfDXLbN/9NKbL4tCJf8TWYVKsoYuuh+sAUt7fsSNpA+r2+TBA6Wjmw== - dependencies: - "@hapi/hoek" "^9.3.0" - "@hapi/topo" "^5.1.0" - "@sideway/address" "^4.1.5" - "@sideway/formula" "^3.0.1" - "@sideway/pinpoint" "^2.0.0" - js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" @@ -7508,19 +7142,6 @@ long@^5.0.0: resolved "https://registry.npmjs.org/long/-/long-5.2.3.tgz" integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== -lowercase-keys@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -lru-cache@^4.0.1: - version "4.1.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" - integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" @@ -7552,13 +7173,6 @@ magic-string@0.30.1: dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" -make-dir@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" - integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== - dependencies: - pify "^3.0.0" - make-dir@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz" @@ -7667,7 +7281,7 @@ micromatch@^4.0.2, micromatch@^4.0.4: braces "^3.0.2" picomatch "^2.3.1" -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2", mime-db@^1.28.0: +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": version "1.52.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== @@ -7694,11 +7308,6 @@ mimic-fn@^2.1.0: resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-response@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - mini-css-extract-plugin@2.7.6: version "2.7.6" resolved "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz" @@ -7933,11 +7542,6 @@ node-addon-api@^3.0.0, node-addon-api@^3.2.1: resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz" integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== -node-downloader-helper@^2.1.6: - version "2.1.9" - resolved "https://registry.yarnpkg.com/node-downloader-helper/-/node-downloader-helper-2.1.9.tgz#a59ee7276b2bf708bbac2cc5872ad28fc7cd1b0e" - integrity sha512-FSvAol2Z8UP191sZtsUZwHIN0eGoGue3uEXGdWIH5228e9KH1YHXT7fN8Oa33UGf+FbqGTQg3sJfrRGzmVCaJA== - node-forge@^1: version "1.3.1" resolved "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz" @@ -7964,18 +7568,6 @@ node-gyp@^9.0.0: tar "^6.1.2" which "^2.0.2" -node-jq@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/node-jq/-/node-jq-4.3.1.tgz#c2a082210745fd1c54df5965b9ba5d046fce9d68" - integrity sha512-5iU9L/7j8ZNHwhxDRJXgyza6JnEKqdkNcJ9+ul5HZnhConhg/v9JdvA9agJ8XA+qBgGr1MK/MeHDrdK1tL2QAA== - dependencies: - bin-build "^3.0.0" - is-valid-path "^0.1.1" - joi "^17.4.0" - node-downloader-helper "^2.1.6" - strip-final-newline "^2.0.0" - tempfile "^3.0.0" - node-releases@^2.0.12: version "2.0.13" resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz" @@ -8030,14 +7622,6 @@ npm-bundled@^3.0.0: dependencies: npm-normalize-package-bin "^3.0.0" -npm-conf@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" - integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw== - dependencies: - config-chain "^1.1.11" - pify "^3.0.0" - npm-install-checks@^6.0.0: version "6.1.1" resolved "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.1.1.tgz" @@ -8090,13 +7674,6 @@ npm-registry-fetch@^14.0.0: npm-package-arg "^10.0.0" proc-log "^3.0.0" -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== - dependencies: - path-key "^2.0.0" - npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" @@ -8284,18 +7861,6 @@ os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== -p-cancelable@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" - integrity sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw== - -p-event@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/p-event/-/p-event-1.3.0.tgz#8e6b4f4f65c72bc5b6fe28b75eda874f96a4a085" - integrity sha512-hV1zbA7gwqPVFcapfeATaNjQ3J0NuzorHPyG8GPL9g/Y/TplWVBVoCKCXL6Ej2zscrCEv195QNWJXuBH6XZuzA== - dependencies: - p-timeout "^1.1.1" - p-filter@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-filter/-/p-filter-3.0.0.tgz#ce50e03b24b23930e11679ab8694bd09a2d7ed35" @@ -8303,11 +7868,6 @@ p-filter@^3.0.0: dependencies: p-map "^5.1.0" -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - p-limit@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" @@ -8350,13 +7910,6 @@ p-locate@^6.0.0: dependencies: p-limit "^4.0.0" -p-map-series@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-1.0.0.tgz#bf98fe575705658a9e1351befb85ae4c1f07bdca" - integrity sha512-4k9LlvY6Bo/1FcIdV33wqZQES0Py+iKISU9Uc8p8AjWoZPnFKMpVIVD3s0EYn4jzLh1I+WeUZkJ0Yoa4Qfw3Kg== - dependencies: - p-reduce "^1.0.0" - p-map@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz" @@ -8371,11 +7924,6 @@ p-map@^5.1.0: dependencies: aggregate-error "^4.0.0" -p-reduce@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" - integrity sha512-3Tx1T3oM1xO/Y8Gj0sWyE78EIJZ+t+aEmXUdvQgvGmSMri7aPTHoovbXEreWKkL5j21Er60XAWLTzKbAKYOujQ== - p-retry@^4.5.0: version "4.6.2" resolved "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz" @@ -8384,13 +7932,6 @@ p-retry@^4.5.0: "@types/retry" "0.12.0" retry "^0.13.1" -p-timeout@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386" - integrity sha512-gb0ryzr+K2qFqFv6qi3khoeqMZF/+ajxQipEF6NteZVnvz9tzdsfAVj3lYtn1gAXvH5lfLwfxEII799gt/mRIA== - dependencies: - p-finally "^1.0.0" - p-try@^2.0.0: version "2.2.0" resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" @@ -8500,11 +8041,6 @@ path-is-inside@^1.0.1: resolved "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz" integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== -path-key@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== - path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" @@ -8533,11 +8069,6 @@ path-type@^4.0.0: resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== - performance-now@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" @@ -8553,16 +8084,11 @@ picomatch@2.3.1, picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pify@^2.0.0, pify@^2.3.0: +pify@^2.0.0: version "2.3.0" resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== - pify@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz" @@ -8685,11 +8211,6 @@ prelude-ls@^1.2.1: resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prepend-http@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - integrity sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg== - prettier-plugin-organize-imports@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz#77967f69d335e9c8e6e5d224074609309c62845e" @@ -8733,11 +8254,6 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" -proto-list@~1.2.1: - version "1.2.4" - resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" - integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== - protobufjs@^7.0.0, protobufjs@^7.2.4: version "7.2.5" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.5.tgz#45d5c57387a6d29a17aab6846dcc283f9b8e7f2d" @@ -8795,11 +8311,6 @@ prr@~1.0.1: resolved "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz" integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== - psl@^1.1.28, psl@^1.1.33: version "1.9.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" @@ -8926,7 +8437,7 @@ read-pkg@^7.1.0: parse-json "^5.2.0" type-fest "^2.0.0" -readable-stream@^2.0.1, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@~2.3.6: +readable-stream@^2.0.1, readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== @@ -9164,7 +8675,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -9242,13 +8753,6 @@ schema-utils@^4.0.0: ajv-formats "^2.1.1" ajv-keywords "^5.1.0" -seek-bzip@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.6.tgz#35c4171f55a680916b52a07859ecf3b5857f21c4" - integrity sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ== - dependencies: - commander "^2.8.1" - select-hose@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz" @@ -9385,13 +8889,6 @@ shallow-clone@^3.0.0: dependencies: kind-of "^6.0.2" -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== - dependencies: - shebang-regex "^1.0.0" - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" @@ -9399,11 +8896,6 @@ shebang-command@^2.0.0: dependencies: shebang-regex "^3.0.0" -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== - shebang-regex@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" @@ -9423,7 +8915,7 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -9510,20 +9002,6 @@ socks@^2.6.2: ip "^2.0.0" smart-buffer "^4.2.0" -sort-keys-length@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188" - integrity sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw== - dependencies: - sort-keys "^1.0.0" - -sort-keys@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" - integrity sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg== - dependencies: - is-plain-obj "^1.0.0" - "source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" @@ -9738,18 +9216,6 @@ strip-bom@^3.0.0: resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== -strip-dirs@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5" - integrity sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g== - dependencies: - is-natural-number "^4.0.1" - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== - strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" @@ -9760,13 +9226,6 @@ strip-json-comments@3.1.1, strip-json-comments@^3.1.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strip-outer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" - integrity sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg== - dependencies: - escape-string-regexp "^1.0.2" - strong-log-transformer@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz" @@ -9822,19 +9281,6 @@ tapable@^2.1.1, tapable@^2.2.0: resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -tar-stream@^1.5.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" - integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A== - dependencies: - bl "^1.0.0" - buffer-alloc "^1.2.0" - end-of-stream "^1.0.0" - fs-constants "^1.0.0" - readable-stream "^2.3.0" - to-buffer "^1.1.1" - xtend "^4.0.0" - tar-stream@~2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz" @@ -9858,32 +9304,6 @@ tar@^6.1.11, tar@^6.1.2: mkdirp "^1.0.3" yallist "^4.0.0" -temp-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" - integrity sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ== - -temp-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" - integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== - -tempfile@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-2.0.0.tgz#6b0446856a9b1114d1856ffcbe509cccb0977265" - integrity sha512-ZOn6nJUgvgC09+doCEF3oB+r3ag7kUvlsXEGX069QRD60p+P3uP7XG9N2/at+EyIRGSN//ZY3LyEotA1YpmjuA== - dependencies: - temp-dir "^1.0.0" - uuid "^3.0.1" - -tempfile@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-3.0.0.tgz#5376a3492de7c54150d0cc0612c3f00e2cdaf76c" - integrity sha512-uNFCg478XovRi85iD42egu+eSFUmmka750Jy7L5tfHI5hQKKtbPnxaSaXAbBqCDYrw3wx4tXjKwci4/QmsZJxw== - dependencies: - temp-dir "^2.0.0" - uuid "^3.3.2" - terser-webpack-plugin@^5.3.7: version "5.3.8" resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.8.tgz" @@ -9929,7 +9349,7 @@ text-table@0.2.0, text-table@^0.2.0: resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== -through@X.X.X, through@^2.3.4, through@^2.3.6, through@^2.3.8: +through@X.X.X, through@^2.3.4, through@^2.3.6: version "2.3.8" resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== @@ -9939,11 +9359,6 @@ thunky@^1.0.2: resolved "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz" integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== -timed-out@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - integrity sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA== - tiny-inflate@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz" @@ -9975,11 +9390,6 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" -to-buffer@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" - integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== - to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" @@ -10027,13 +9437,6 @@ tree-kill@1.2.2: resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== -trim-repeated@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21" - integrity sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg== - dependencies: - escape-string-regexp "^1.0.2" - tsconfig-paths@^4.1.2: version "4.2.0" resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz" @@ -10131,14 +9534,6 @@ ua-parser-js@^0.7.30: resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.35.tgz" integrity sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g== -unbzip2-stream@^1.0.9: - version "1.4.3" - resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" - integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== - dependencies: - buffer "^5.2.1" - through "^2.3.8" - unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" @@ -10225,13 +9620,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -url-parse-lax@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" - integrity sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA== - dependencies: - prepend-http "^1.0.1" - url-parse@^1.5.3: version "1.5.10" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" @@ -10240,11 +9628,6 @@ url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" -url-to-options@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" - integrity sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A== - util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" @@ -10255,7 +9638,7 @@ utils-merge@1.0.1: resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@^3.0.1, uuid@^3.3.2: +uuid@^3.3.2: version "3.4.0" resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== @@ -10544,7 +9927,7 @@ which-module@^2.0.0: resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz" integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== -which@^1.2.1, which@^1.2.9: +which@^1.2.1: version "1.3.1" resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -10647,11 +10030,6 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xtend@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - y18n@^4.0.0: version "4.0.3" resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz" @@ -10662,11 +10040,6 @@ y18n@^5.0.5: resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== - yallist@^3.0.2: version "3.1.1" resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" @@ -10738,14 +10111,6 @@ yargs@^16.1.1: y18n "^5.0.5" yargs-parser "^20.2.2" -yauzl@^2.4.2: - version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" - yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" diff --git a/docs/docs/guides/manage/console/default-settings.mdx b/docs/docs/guides/manage/console/default-settings.mdx index c29bb8d53d..a715f8a4db 100644 --- a/docs/docs/guides/manage/console/default-settings.mdx +++ b/docs/docs/guides/manage/console/default-settings.mdx @@ -20,7 +20,7 @@ When you configure your default settings, you can set the following: - [**Login Behavior and Access**](#login-behavior-and-access): Multifactor Authentication Options and Enforcement, Define whether Passwordless authentication methods are allowed or not, Set Login Lifetimes and advanced behavour for the login interface. - [**Identity Providers**](#identity-providers): Define IDPs which are available for all organizations - [**Password Complexity**](#password-complexity): Requirements for Passwords ex. Symbols, Numbers, min length and more. -- [**Lockout**](#lockout): Set the maximum attempts a user can try to enter the password. When the number is exceeded, the user gets locked out and has to be unlocked. +- [**Lockout**](#lockout): Set the maximum attempts a user can try to enter the password or any (T)OTP method. When the number is exceeded, the user gets locked out and has to be unlocked. - [**Domain settings**](#domain-settings): Whether users use their email or the generated username to login. Other Validation, SMTP settings - [**Branding**](#branding): Appearance of the login interface. - [**Message Texts**](#message-texts): Text and internationalization for emails @@ -189,6 +189,7 @@ Define when an account should be locked. The following settings are available: - Maximum Password Attempts: When the user has reached the maximum password attempts the account will be locked, If this is set to 0 the lockout will not trigger. +- Maximum OTP Attempts: When the user has reached the maximum (T)OTP attempts the account will be locked, If this is set to 0 the lockout will not trigger. If an account is locked, the administrator has to unlock it in the ZITADEL console diff --git a/docs/docs/guides/manage/console/organizations.mdx b/docs/docs/guides/manage/console/organizations.mdx index 99092c304e..6763a09670 100644 --- a/docs/docs/guides/manage/console/organizations.mdx +++ b/docs/docs/guides/manage/console/organizations.mdx @@ -108,7 +108,7 @@ Those settings are the same as on your instance. - [**Login Behavior and Access**](./default-settings#login-behaviour-and-access): Multifactor Authentication Options and Enforcement, Define whether Passwordless authentication methods are allowed or not, Set Login Lifetimes and advanced behavour for the login interface. - [**Identity Providers**](./default-settings#identity-providers): Define IDPs which are available for all organizations - [**Password Complexity**](./default-settings#password-complexity): Requirements for Passwords ex. Symbols, Numbers, min length and more. -- [**Lockout**](./default-settings#lockout): Set the maximum attempts a user can try to enter the password. When the number is exceeded, the user gets locked out and has to be unlocked. +- [**Lockout**](./default-settings#lockout): Set the maximum attempts a user can try to enter the password or any (T)OTP method. When the number is exceeded, the user gets locked out and has to be unlocked. - [**Verified domains**](/docs/guides/manage/console/organizations#verify-your-domain-name): This is where you manage your organization specific domains which can be used to build usernames - [**Domain settings**](./default-settings#domain-settings): Whether users use their email or the generated username to login. Other Validation, SMTP settings - [**Branding**](./default-settings#branding): Appearance of the login interface. diff --git a/docs/static/img/guides/console/lockout.png b/docs/static/img/guides/console/lockout.png index 8f6c170d3b1f902d70565a87c0c528d8badbcab6..8b87718fc995eb82e91d4f94e8f5226633310bff 100644 GIT binary patch literal 14766 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 E0q(RZl>h($ literal 54253 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

@#$vdf|m|_ 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 diff --git a/internal/api/grpc/admin/export.go b/internal/api/grpc/admin/export.go index e47d1f04f7..679ae0271c 100644 --- a/internal/api/grpc/admin/export.go +++ b/internal/api/grpc/admin/export.go @@ -491,13 +491,14 @@ func (s *Server) getLockoutPolicy(ctx context.Context, orgID string) (_ *managem ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() - queriedLockout, err := s.query.LockoutPolicyByOrg(ctx, false, orgID, false) + queriedLockout, err := s.query.LockoutPolicyByOrg(ctx, false, orgID) if err != nil { return nil, err } if !queriedLockout.IsDefault { return &management_pb.AddCustomLockoutPolicyRequest{ MaxPasswordAttempts: uint32(queriedLockout.MaxPasswordAttempts), + MaxOtpAttempts: uint32(queriedLockout.MaxOTPAttempts), }, nil } return nil, nil diff --git a/internal/api/grpc/admin/lockout_converter.go b/internal/api/grpc/admin/lockout_converter.go index 555e0c2e89..eabd5104e1 100644 --- a/internal/api/grpc/admin/lockout_converter.go +++ b/internal/api/grpc/admin/lockout_converter.go @@ -8,5 +8,6 @@ import ( func UpdateLockoutPolicyToDomain(p *admin.UpdateLockoutPolicyRequest) *domain.LockoutPolicy { return &domain.LockoutPolicy{ MaxPasswordAttempts: uint64(p.MaxPasswordAttempts), + MaxOTPAttempts: uint64(p.MaxOtpAttempts), } } diff --git a/internal/api/grpc/management/policy_lockout.go b/internal/api/grpc/management/policy_lockout.go index a36ee862d8..740a34e3a2 100644 --- a/internal/api/grpc/management/policy_lockout.go +++ b/internal/api/grpc/management/policy_lockout.go @@ -10,7 +10,7 @@ import ( ) func (s *Server) GetLockoutPolicy(ctx context.Context, req *mgmt_pb.GetLockoutPolicyRequest) (*mgmt_pb.GetLockoutPolicyResponse, error) { - policy, err := s.query.LockoutPolicyByOrg(ctx, true, authz.GetCtxData(ctx).OrgID, false) + policy, err := s.query.LockoutPolicyByOrg(ctx, true, authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/policy_lockout_converter.go b/internal/api/grpc/management/policy_lockout_converter.go index 57910c4aba..83f5648103 100644 --- a/internal/api/grpc/management/policy_lockout_converter.go +++ b/internal/api/grpc/management/policy_lockout_converter.go @@ -8,11 +8,13 @@ import ( func AddLockoutPolicyToDomain(p *mgmt.AddCustomLockoutPolicyRequest) *domain.LockoutPolicy { return &domain.LockoutPolicy{ MaxPasswordAttempts: uint64(p.MaxPasswordAttempts), + MaxOTPAttempts: uint64(p.MaxOtpAttempts), } } func UpdateLockoutPolicyToDomain(p *mgmt.UpdateCustomLockoutPolicyRequest) *domain.LockoutPolicy { return &domain.LockoutPolicy{ MaxPasswordAttempts: uint64(p.MaxPasswordAttempts), + MaxOTPAttempts: uint64(p.MaxOtpAttempts), } } diff --git a/internal/api/grpc/policy/password_lockout_policy.go b/internal/api/grpc/policy/password_lockout_policy.go index af73f1bcef..c33076234d 100644 --- a/internal/api/grpc/policy/password_lockout_policy.go +++ b/internal/api/grpc/policy/password_lockout_policy.go @@ -10,6 +10,7 @@ func ModelLockoutPolicyToPb(policy *query.LockoutPolicy) *policy_pb.LockoutPolic return &policy_pb.LockoutPolicy{ IsDefault: policy.IsDefault, MaxPasswordAttempts: policy.MaxPasswordAttempts, + MaxOtpAttempts: policy.MaxOTPAttempts, Details: object.ToViewDetailsPb( policy.Sequence, policy.CreationDate, diff --git a/internal/api/grpc/settings/v2/settings.go b/internal/api/grpc/settings/v2/settings.go index c16178c370..11f41f26b6 100644 --- a/internal/api/grpc/settings/v2/settings.go +++ b/internal/api/grpc/settings/v2/settings.go @@ -90,7 +90,7 @@ func (s *Server) GetLegalAndSupportSettings(ctx context.Context, req *settings.G } func (s *Server) GetLockoutSettings(ctx context.Context, req *settings.GetLockoutSettingsRequest) (*settings.GetLockoutSettingsResponse, error) { - current, err := s.query.LockoutPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.GetCtx()), false) + current, err := s.query.LockoutPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.GetCtx())) if err != nil { return nil, err } diff --git a/internal/api/grpc/settings/v2/settings_converter.go b/internal/api/grpc/settings/v2/settings_converter.go index 1bd9837127..f00bbf0f9d 100644 --- a/internal/api/grpc/settings/v2/settings_converter.go +++ b/internal/api/grpc/settings/v2/settings_converter.go @@ -160,6 +160,7 @@ func legalAndSupportSettingsToPb(current *query.PrivacyPolicy) *settings.LegalAn func lockoutSettingsToPb(current *query.LockoutPolicy) *settings.LockoutSettings { return &settings.LockoutSettings{ MaxPasswordAttempts: current.MaxPasswordAttempts, + MaxOtpAttempts: current.MaxOTPAttempts, ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault), } } diff --git a/internal/api/grpc/settings/v2/settings_converter_test.go b/internal/api/grpc/settings/v2/settings_converter_test.go index 7fcd5d96ce..d1cde07b87 100644 --- a/internal/api/grpc/settings/v2/settings_converter_test.go +++ b/internal/api/grpc/settings/v2/settings_converter_test.go @@ -339,10 +339,12 @@ func Test_legalSettingsToPb(t *testing.T) { func Test_lockoutSettingsToPb(t *testing.T) { arg := &query.LockoutPolicy{ MaxPasswordAttempts: 22, + MaxOTPAttempts: 22, IsDefault: true, } want := &settings.LockoutSettings{ MaxPasswordAttempts: 22, + MaxOtpAttempts: 22, ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE, } got := lockoutSettingsToPb(arg) diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 82a60b641f..98912c9309 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -75,7 +75,7 @@ type loginPolicyViewProvider interface { } type lockoutPolicyViewProvider interface { - LockoutPolicyByOrg(context.Context, bool, string, bool) (*query.LockoutPolicy, error) + LockoutPolicyByOrg(context.Context, bool, string) (*query.LockoutPolicy, error) } type idpProviderViewProvider interface { @@ -366,6 +366,7 @@ func lockoutPolicyToDomain(policy *query.LockoutPolicy) *domain.LockoutPolicy { }, Default: policy.IsDefault, MaxPasswordAttempts: policy.MaxPasswordAttempts, + MaxOTPAttempts: policy.MaxOTPAttempts, ShowLockOutFailures: policy.ShowFailures, } } @@ -1281,7 +1282,7 @@ func privacyPolicyToDomain(p *query.PrivacyPolicy) *domain.PrivacyPolicy { } func (repo *AuthRequestRepo) getLockoutPolicy(ctx context.Context, orgID string) (*query.LockoutPolicy, error) { - policy, err := repo.LockoutPolicyViewProvider.LockoutPolicyByOrg(ctx, false, orgID, false) + policy, err := repo.LockoutPolicyViewProvider.LockoutPolicyByOrg(ctx, false, orgID) 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 55bcf2e56d..99e0c78ec6 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go @@ -184,7 +184,7 @@ type mockLockoutPolicy struct { policy *query.LockoutPolicy } -func (m *mockLockoutPolicy) LockoutPolicyByOrg(context.Context, bool, string, bool) (*query.LockoutPolicy, error) { +func (m *mockLockoutPolicy) LockoutPolicyByOrg(context.Context, bool, string) (*query.LockoutPolicy, error) { return m.policy, nil } diff --git a/internal/command/instance.go b/internal/command/instance.go index 7f379d976b..f9f9121889 100644 --- a/internal/command/instance.go +++ b/internal/command/instance.go @@ -101,7 +101,8 @@ type InstanceSetup struct { ThemeMode domain.LabelPolicyThemeMode } LockoutPolicy struct { - MaxAttempts uint64 + MaxPasswordAttempts uint64 + MaxOTPAttempts uint64 ShouldShowLockoutFailure bool } EmailTemplate []byte @@ -271,7 +272,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str prepareAddDefaultPrivacyPolicy(instanceAgg, setup.PrivacyPolicy.TOSLink, setup.PrivacyPolicy.PrivacyLink, setup.PrivacyPolicy.HelpLink, setup.PrivacyPolicy.SupportEmail), prepareAddDefaultNotificationPolicy(instanceAgg, setup.NotificationPolicy.PasswordChange), - prepareAddDefaultLockoutPolicy(instanceAgg, setup.LockoutPolicy.MaxAttempts, setup.LockoutPolicy.ShouldShowLockoutFailure), + prepareAddDefaultLockoutPolicy(instanceAgg, setup.LockoutPolicy.MaxPasswordAttempts, setup.LockoutPolicy.MaxOTPAttempts, setup.LockoutPolicy.ShouldShowLockoutFailure), prepareAddDefaultLabelPolicy( instanceAgg, diff --git a/internal/command/instance_converter.go b/internal/command/instance_converter.go index cf1a10b199..1ed1cc123a 100644 --- a/internal/command/instance_converter.go +++ b/internal/command/instance_converter.go @@ -109,6 +109,7 @@ func writeModelToLockoutPolicy(wm *LockoutPolicyWriteModel) *domain.LockoutPolic return &domain.LockoutPolicy{ ObjectRoot: writeModelToObjectRoot(wm.WriteModel), MaxPasswordAttempts: wm.MaxPasswordAttempts, + MaxOTPAttempts: wm.MaxOTPAttempts, ShowLockOutFailures: wm.ShowLockOutFailures, } } diff --git a/internal/command/instance_policy_password_lockout.go b/internal/command/instance_policy_password_lockout.go index 2ed88b997f..59766ae38f 100644 --- a/internal/command/instance_policy_password_lockout.go +++ b/internal/command/instance_policy_password_lockout.go @@ -12,9 +12,15 @@ import ( "github.com/zitadel/zitadel/internal/zerrors" ) -func (c *Commands) AddDefaultLockoutPolicy(ctx context.Context, maxAttempts uint64, showLockoutFailure bool) (*domain.ObjectDetails, error) { +func (c *Commands) AddDefaultLockoutPolicy(ctx context.Context, maxPasswordAttempts, maxOTPAttempts uint64, showLockoutFailure bool) (*domain.ObjectDetails, error) { instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) - cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareAddDefaultLockoutPolicy(instanceAgg, maxAttempts, showLockoutFailure)) + //nolint:staticcheck + cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareAddDefaultLockoutPolicy( + instanceAgg, + maxPasswordAttempts, + maxOTPAttempts, + showLockoutFailure, + )) if err != nil { return nil, err } @@ -35,7 +41,13 @@ func (c *Commands) ChangeDefaultLockoutPolicy(ctx context.Context, policy *domai } instanceAgg := InstanceAggregateFromWriteModel(&existingPolicy.LockoutPolicyWriteModel.WriteModel) - changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, instanceAgg, policy.MaxPasswordAttempts, policy.ShowLockOutFailures) + changedEvent, hasChanged := existingPolicy.NewChangedEvent( + ctx, + instanceAgg, + policy.MaxPasswordAttempts, + policy.MaxOTPAttempts, + policy.ShowLockOutFailures, + ) if !hasChanged { return nil, zerrors.ThrowPreconditionFailed(nil, "INSTANCE-0psjF", "Errors.IAM.LockoutPolicy.NotChanged") } @@ -65,7 +77,8 @@ func (c *Commands) defaultLockoutPolicyWriteModelByID(ctx context.Context) (poli func prepareAddDefaultLockoutPolicy( a *instance.Aggregate, - maxAttempts uint64, + maxPasswordAttempts, + maxOTPAttempts uint64, showLockoutFailure bool, ) preparation.Validation { return func() (preparation.CreateCommands, error) { @@ -83,7 +96,7 @@ func prepareAddDefaultLockoutPolicy( return nil, zerrors.ThrowAlreadyExists(nil, "INSTANCE-0olDf", "Errors.Instance.LockoutPolicy.AlreadyExists") } return []eventstore.Command{ - instance.NewLockoutPolicyAddedEvent(ctx, &a.Aggregate, maxAttempts, showLockoutFailure), + instance.NewLockoutPolicyAddedEvent(ctx, &a.Aggregate, maxPasswordAttempts, maxOTPAttempts, showLockoutFailure), }, nil }, nil } diff --git a/internal/command/instance_policy_password_lockout_model.go b/internal/command/instance_policy_password_lockout_model.go index dcfa0b62ca..4d529169bb 100644 --- a/internal/command/instance_policy_password_lockout_model.go +++ b/internal/command/instance_policy_password_lockout_model.go @@ -54,11 +54,15 @@ func (wm *InstanceLockoutPolicyWriteModel) Query() *eventstore.SearchQueryBuilde func (wm *InstanceLockoutPolicyWriteModel) NewChangedEvent( ctx context.Context, aggregate *eventstore.Aggregate, - maxAttempts uint64, + maxPasswordAttempts, + maxOTPAttempts uint64, showLockoutFailure bool) (*instance.LockoutPolicyChangedEvent, bool) { changes := make([]policy.LockoutPolicyChanges, 0) - if wm.MaxPasswordAttempts != maxAttempts { - changes = append(changes, policy.ChangeMaxAttempts(maxAttempts)) + if wm.MaxPasswordAttempts != maxPasswordAttempts { + changes = append(changes, policy.ChangeMaxPasswordAttempts(maxPasswordAttempts)) + } + if wm.MaxOTPAttempts != maxOTPAttempts { + changes = append(changes, policy.ChangeMaxOTPAttempts(maxOTPAttempts)) } if wm.ShowLockOutFailures != showLockoutFailure { changes = append(changes, policy.ChangeShowLockOutFailures(showLockoutFailure)) diff --git a/internal/command/instance_policy_password_lockout_test.go b/internal/command/instance_policy_password_lockout_test.go index 02d5ab488d..866399caec 100644 --- a/internal/command/instance_policy_password_lockout_test.go +++ b/internal/command/instance_policy_password_lockout_test.go @@ -22,6 +22,7 @@ func TestCommandSide_AddDefaultLockoutPolicy(t *testing.T) { type args struct { ctx context.Context maxPasswordAttempts uint64 + maxOTPAttempts uint64 showLockOutFailures bool } type res struct { @@ -44,6 +45,7 @@ func TestCommandSide_AddDefaultLockoutPolicy(t *testing.T) { instance.NewLockoutPolicyAddedEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, 10, + 10, true, ), ), @@ -69,6 +71,7 @@ func TestCommandSide_AddDefaultLockoutPolicy(t *testing.T) { instance.NewLockoutPolicyAddedEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, 10, + 10, true, ), ), @@ -77,6 +80,7 @@ func TestCommandSide_AddDefaultLockoutPolicy(t *testing.T) { args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), maxPasswordAttempts: 10, + maxOTPAttempts: 10, showLockOutFailures: true, }, res: res{ @@ -91,7 +95,7 @@ func TestCommandSide_AddDefaultLockoutPolicy(t *testing.T) { r := &Commands{ eventstore: tt.fields.eventstore, } - got, err := r.AddDefaultLockoutPolicy(tt.args.ctx, tt.args.maxPasswordAttempts, tt.args.showLockOutFailures) + got, err := r.AddDefaultLockoutPolicy(tt.args.ctx, tt.args.maxPasswordAttempts, tt.args.maxOTPAttempts, tt.args.showLockOutFailures) if tt.res.err == nil { assert.NoError(t, err) } @@ -135,6 +139,7 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) { ctx: context.Background(), policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 10, + MaxOTPAttempts: 10, ShowLockOutFailures: true, }, }, @@ -152,6 +157,7 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) { instance.NewLockoutPolicyAddedEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, 10, + 10, true, ), ), @@ -162,6 +168,7 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) { ctx: context.Background(), policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 10, + MaxOTPAttempts: 10, ShowLockOutFailures: true, }, }, @@ -179,12 +186,13 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) { instance.NewLockoutPolicyAddedEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, 10, + 10, true, ), ), ), expectPush( - newDefaultLockoutPolicyChangedEvent(context.Background(), 20, false), + newDefaultLockoutPolicyChangedEvent(context.Background(), 20, 20, false), ), ), }, @@ -192,6 +200,7 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) { ctx: context.Background(), policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 20, + MaxOTPAttempts: 20, ShowLockOutFailures: false, }, }, @@ -203,6 +212,7 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) { InstanceID: "INSTANCE", }, MaxPasswordAttempts: 20, + MaxOTPAttempts: 20, ShowLockOutFailures: false, }, }, @@ -227,11 +237,12 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) { } } -func newDefaultLockoutPolicyChangedEvent(ctx context.Context, maxAttempts uint64, showLockoutFailure bool) *instance.LockoutPolicyChangedEvent { +func newDefaultLockoutPolicyChangedEvent(ctx context.Context, maxPasswordAttempts, maxOTPAttempts uint64, showLockoutFailure bool) *instance.LockoutPolicyChangedEvent { event, _ := instance.NewLockoutPolicyChangedEvent(ctx, &instance.NewAggregate("INSTANCE").Aggregate, []policy.LockoutPolicyChanges{ - policy.ChangeMaxAttempts(maxAttempts), + policy.ChangeMaxPasswordAttempts(maxPasswordAttempts), + policy.ChangeMaxOTPAttempts(maxOTPAttempts), policy.ChangeShowLockOutFailures(showLockoutFailure), }, ) diff --git a/internal/command/org_policy_lockout.go b/internal/command/org_policy_lockout.go index 47f98d9770..052fd9e239 100644 --- a/internal/command/org_policy_lockout.go +++ b/internal/command/org_policy_lockout.go @@ -21,7 +21,13 @@ func (c *Commands) AddLockoutPolicy(ctx context.Context, resourceOwner string, p } orgAgg := OrgAggregateFromWriteModel(&addedPolicy.WriteModel) - pushedEvents, err := c.eventstore.Push(ctx, org.NewLockoutPolicyAddedEvent(ctx, orgAgg, policy.MaxPasswordAttempts, policy.ShowLockOutFailures)) + pushedEvents, err := c.eventstore.Push(ctx, org.NewLockoutPolicyAddedEvent( + ctx, + orgAgg, + policy.MaxPasswordAttempts, + policy.MaxOTPAttempts, + policy.ShowLockOutFailures, + )) if err != nil { return nil, err } @@ -45,7 +51,7 @@ func (c *Commands) ChangeLockoutPolicy(ctx context.Context, resourceOwner string } orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LockoutPolicyWriteModel.WriteModel) - changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, orgAgg, policy.MaxPasswordAttempts, policy.ShowLockOutFailures) + changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, orgAgg, policy.MaxPasswordAttempts, policy.MaxOTPAttempts, policy.ShowLockOutFailures) if !hasChanged { return nil, zerrors.ThrowPreconditionFailed(nil, "ORG-0JFSr", "Errors.Org.LockoutPolicy.NotChanged") } @@ -106,3 +112,20 @@ func (c *Commands) orgLockoutPolicyWriteModelByID(ctx context.Context, orgID str } return policy, nil } + +func (c *Commands) getLockoutPolicy(ctx context.Context, orgID string) (*domain.LockoutPolicy, error) { + orgWm, err := c.orgLockoutPolicyWriteModelByID(ctx, orgID) + if err != nil { + return nil, err + } + if orgWm.State == domain.PolicyStateActive { + return writeModelToLockoutPolicy(&orgWm.LockoutPolicyWriteModel), nil + } + instanceWm, err := c.defaultLockoutPolicyWriteModelByID(ctx) + if err != nil { + return nil, err + } + policy := writeModelToLockoutPolicy(&instanceWm.LockoutPolicyWriteModel) + policy.Default = true + return policy, nil +} diff --git a/internal/command/org_policy_lockout_model.go b/internal/command/org_policy_lockout_model.go index abf893fece..9749183e5a 100644 --- a/internal/command/org_policy_lockout_model.go +++ b/internal/command/org_policy_lockout_model.go @@ -55,11 +55,15 @@ func (wm *OrgLockoutPolicyWriteModel) Query() *eventstore.SearchQueryBuilder { func (wm *OrgLockoutPolicyWriteModel) NewChangedEvent( ctx context.Context, aggregate *eventstore.Aggregate, - maxAttempts uint64, + maxPasswordAttempts, + maxOTPAttempts uint64, showLockoutFailure bool) (*org.LockoutPolicyChangedEvent, bool) { changes := make([]policy.LockoutPolicyChanges, 0) - if wm.MaxPasswordAttempts != maxAttempts { - changes = append(changes, policy.ChangeMaxAttempts(maxAttempts)) + if wm.MaxPasswordAttempts != maxPasswordAttempts { + changes = append(changes, policy.ChangeMaxPasswordAttempts(maxPasswordAttempts)) + } + if wm.MaxOTPAttempts != maxOTPAttempts { + changes = append(changes, policy.ChangeMaxOTPAttempts(maxOTPAttempts)) } if wm.ShowLockOutFailures != showLockoutFailure { changes = append(changes, policy.ChangeShowLockOutFailures(showLockoutFailure)) diff --git a/internal/command/org_policy_lockout_test.go b/internal/command/org_policy_lockout_test.go index 1eda5f348c..89444d8dc2 100644 --- a/internal/command/org_policy_lockout_test.go +++ b/internal/command/org_policy_lockout_test.go @@ -44,6 +44,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) { ctx: context.Background(), policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 10, + MaxOTPAttempts: 10, ShowLockOutFailures: true, }, }, @@ -61,6 +62,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) { org.NewLockoutPolicyAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, 10, + 10, true, ), ), @@ -72,6 +74,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) { orgID: "org1", policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 10, + MaxOTPAttempts: 10, ShowLockOutFailures: true, }, }, @@ -89,6 +92,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) { org.NewLockoutPolicyAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, 10, + 10, true, ), ), @@ -99,6 +103,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) { orgID: "org1", policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 10, + MaxOTPAttempts: 10, ShowLockOutFailures: true, }, }, @@ -109,6 +114,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) { ResourceOwner: "org1", }, MaxPasswordAttempts: 10, + MaxOTPAttempts: 10, ShowLockOutFailures: true, }, }, @@ -163,6 +169,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) { ctx: context.Background(), policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 10, + MaxOTPAttempts: 10, ShowLockOutFailures: true, }, }, @@ -183,6 +190,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) { orgID: "org1", policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 10, + MaxOTPAttempts: 10, ShowLockOutFailures: true, }, }, @@ -200,6 +208,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) { org.NewLockoutPolicyAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, 10, + 10, true, ), ), @@ -211,6 +220,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) { orgID: "org1", policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 10, + MaxOTPAttempts: 10, ShowLockOutFailures: true, }, }, @@ -228,12 +238,13 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) { org.NewLockoutPolicyAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, 10, + 10, true, ), ), ), expectPush( - newPasswordLockoutPolicyChangedEvent(context.Background(), "org1", 5, false), + newPasswordLockoutPolicyChangedEvent(context.Background(), "org1", 5, 5, false), ), ), }, @@ -242,6 +253,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) { orgID: "org1", policy: &domain.LockoutPolicy{ MaxPasswordAttempts: 5, + MaxOTPAttempts: 5, ShowLockOutFailures: false, }, }, @@ -252,6 +264,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) { ResourceOwner: "org1", }, MaxPasswordAttempts: 5, + MaxOTPAttempts: 5, ShowLockOutFailures: false, }, }, @@ -334,6 +347,7 @@ func TestCommandSide_RemovePasswordLockoutPolicy(t *testing.T) { org.NewLockoutPolicyAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, 10, + 10, true, ), ), @@ -371,11 +385,12 @@ func TestCommandSide_RemovePasswordLockoutPolicy(t *testing.T) { } } -func newPasswordLockoutPolicyChangedEvent(ctx context.Context, orgID string, maxAttempts uint64, showLockoutFailure bool) *org.LockoutPolicyChangedEvent { +func newPasswordLockoutPolicyChangedEvent(ctx context.Context, orgID string, maxPasswordAttempts, maxOTPAttempts uint64, showLockoutFailure bool) *org.LockoutPolicyChangedEvent { event, _ := org.NewLockoutPolicyChangedEvent(ctx, &org.NewAggregate(orgID).Aggregate, []policy.LockoutPolicyChanges{ - policy.ChangeMaxAttempts(maxAttempts), + policy.ChangeMaxPasswordAttempts(maxPasswordAttempts), + policy.ChangeMaxOTPAttempts(maxOTPAttempts), policy.ChangeShowLockOutFailures(showLockoutFailure), }, ) diff --git a/internal/command/policy_password_lockout_model.go b/internal/command/policy_password_lockout_model.go index a931b63b65..a772cd9828 100644 --- a/internal/command/policy_password_lockout_model.go +++ b/internal/command/policy_password_lockout_model.go @@ -10,6 +10,7 @@ type LockoutPolicyWriteModel struct { eventstore.WriteModel MaxPasswordAttempts uint64 + MaxOTPAttempts uint64 ShowLockOutFailures bool State domain.PolicyState } @@ -19,12 +20,16 @@ func (wm *LockoutPolicyWriteModel) Reduce() error { switch e := event.(type) { case *policy.LockoutPolicyAddedEvent: wm.MaxPasswordAttempts = e.MaxPasswordAttempts + wm.MaxOTPAttempts = e.MaxOTPAttempts wm.ShowLockOutFailures = e.ShowLockOutFailures wm.State = domain.PolicyStateActive case *policy.LockoutPolicyChangedEvent: if e.MaxPasswordAttempts != nil { wm.MaxPasswordAttempts = *e.MaxPasswordAttempts } + if e.MaxOTPAttempts != nil { + wm.MaxOTPAttempts = *e.MaxOTPAttempts + } if e.ShowLockOutFailures != nil { wm.ShowLockOutFailures = *e.ShowLockOutFailures } diff --git a/internal/command/user_human_otp.go b/internal/command/user_human_otp.go index 21e0021d44..fd717c0de4 100644 --- a/internal/command/user_human_otp.go +++ b/internal/command/user_human_otp.go @@ -158,14 +158,37 @@ func (c *Commands) HumanCheckMFATOTP(ctx context.Context, userID, code, resource return zerrors.ThrowPreconditionFailed(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotReady") } userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel) - err = domain.VerifyTOTP(code, existingOTP.Secret, c.multifactors.OTP.CryptoMFA) - if err == nil { + verifyErr := domain.VerifyTOTP(code, existingOTP.Secret, c.multifactors.OTP.CryptoMFA) + + // recheck for additional events (failed OTP checks or locks) + recheckErr := c.eventstore.FilterToQueryReducer(ctx, existingOTP) + if recheckErr != nil { + return recheckErr + } + if existingOTP.UserLocked { + return zerrors.ThrowPreconditionFailed(nil, "COMMAND-SF3fg", "Errors.User.Locked") + } + + // the OTP check succeeded and the user was not locked in the meantime + if verifyErr == nil { _, err = c.eventstore.Push(ctx, user.NewHumanOTPCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))) return err } - _, pushErr := c.eventstore.Push(ctx, user.NewHumanOTPCheckFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))) + + // the OTP check failed, therefore check if the limit was reached and the user must additionally be locked + commands := make([]eventstore.Command, 0, 2) + commands = append(commands, user.NewHumanOTPCheckFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))) + lockoutPolicy, err := c.getLockoutPolicy(ctx, resourceOwner) + if err != nil { + return err + } + if lockoutPolicy.MaxOTPAttempts > 0 && existingOTP.CheckFailedCount+1 >= lockoutPolicy.MaxOTPAttempts { + commands = append(commands, user.NewUserLockedEvent(ctx, userAgg)) + } + + _, pushErr := c.eventstore.Push(ctx, commands...) logging.OnError(pushErr).Error("error create password check failed event") - return err + return verifyErr } func (c *Commands) HumanRemoveTOTP(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) { @@ -515,14 +538,37 @@ func (c *Commands) humanCheckOTP( return zerrors.ThrowPreconditionFailed(nil, "COMMAND-S34gh", "Errors.User.Code.NotFound") } userAgg := &user.NewAggregate(userID, existingOTP.ResourceOwner()).Aggregate - err = crypto.VerifyCode(existingOTP.CodeCreationDate(), existingOTP.CodeExpiry(), existingOTP.Code(), code, c.userEncryption) - if err == nil { + verifyErr := crypto.VerifyCode(existingOTP.CodeCreationDate(), existingOTP.CodeExpiry(), existingOTP.Code(), code, c.userEncryption) + + // recheck for additional events (failed OTP checks or locks) + recheckErr := c.eventstore.FilterToQueryReducer(ctx, existingOTP) + if recheckErr != nil { + return recheckErr + } + if existingOTP.UserLocked() { + return zerrors.ThrowPreconditionFailed(nil, "COMMAND-S6h4R", "Errors.User.Locked") + } + + // the OTP check succeeded and the user was not locked in the meantime + if verifyErr == nil { _, err = c.eventstore.Push(ctx, checkSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))) return err } - _, pushErr := c.eventstore.Push(ctx, checkFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))) + + // the OTP check failed, therefore check if the limit was reached and the user must additionally be locked + commands := make([]eventstore.Command, 0, 2) + commands = append(commands, checkFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))) + lockoutPolicy, err := c.getLockoutPolicy(ctx, resourceOwner) + if err != nil { + return err + } + if lockoutPolicy.MaxOTPAttempts > 0 && existingOTP.CheckFailedCount()+1 >= lockoutPolicy.MaxOTPAttempts { + commands = append(commands, user.NewUserLockedEvent(ctx, userAgg)) + } + + _, pushErr := c.eventstore.Push(ctx, commands...) logging.WithFields("userID", userID).OnError(pushErr).Error("otp failure check push failed") - return err + return verifyErr } func (c *Commands) totpWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanTOTPWriteModel, err error) { diff --git a/internal/command/user_human_otp_model.go b/internal/command/user_human_otp_model.go index 25bd4c9ef5..43abc14349 100644 --- a/internal/command/user_human_otp_model.go +++ b/internal/command/user_human_otp_model.go @@ -12,8 +12,10 @@ import ( type HumanTOTPWriteModel struct { eventstore.WriteModel - State domain.MFAState - Secret *crypto.CryptoValue + State domain.MFAState + Secret *crypto.CryptoValue + CheckFailedCount uint64 + UserLocked bool } func NewHumanTOTPWriteModel(userID, resourceOwner string) *HumanTOTPWriteModel { @@ -33,6 +35,16 @@ func (wm *HumanTOTPWriteModel) Reduce() error { wm.State = domain.MFAStateNotReady case *user.HumanOTPVerifiedEvent: wm.State = domain.MFAStateReady + wm.CheckFailedCount = 0 + case *user.HumanOTPCheckSucceededEvent: + wm.CheckFailedCount = 0 + case *user.HumanOTPCheckFailedEvent: + wm.CheckFailedCount++ + case *user.UserLockedEvent: + wm.UserLocked = true + case *user.UserUnlockedEvent: + wm.CheckFailedCount = 0 + wm.UserLocked = false case *user.HumanOTPRemovedEvent: wm.State = domain.MFAStateRemoved case *user.UserRemovedEvent: @@ -50,6 +62,10 @@ func (wm *HumanTOTPWriteModel) Query() *eventstore.SearchQueryBuilder { EventTypes(user.HumanMFAOTPAddedType, user.HumanMFAOTPVerifiedType, user.HumanMFAOTPRemovedType, + user.HumanMFAOTPCheckSucceededType, + user.HumanMFAOTPCheckFailedType, + user.UserLockedType, + user.UserUnlockedType, user.UserRemovedType, user.UserV1MFAOTPAddedType, user.UserV1MFAOTPVerifiedType, @@ -72,6 +88,9 @@ type OTPCodeWriteModel interface { CodeCreationDate() time.Time CodeExpiry() time.Duration Code() *crypto.CryptoValue + CheckFailedCount() uint64 + UserLocked() bool + eventstore.QueryReducer } type HumanOTPSMSWriteModel struct { @@ -141,6 +160,9 @@ type HumanOTPSMSCodeWriteModel struct { code *crypto.CryptoValue codeCreationDate time.Time codeExpiry time.Duration + + checkFailedCount uint64 + userLocked bool } func (wm *HumanOTPSMSCodeWriteModel) CodeCreationDate() time.Time { @@ -155,6 +177,14 @@ func (wm *HumanOTPSMSCodeWriteModel) Code() *crypto.CryptoValue { return wm.code } +func (wm *HumanOTPSMSCodeWriteModel) CheckFailedCount() uint64 { + return wm.checkFailedCount +} + +func (wm *HumanOTPSMSCodeWriteModel) UserLocked() bool { + return wm.userLocked +} + func NewHumanOTPSMSCodeWriteModel(userID, resourceOwner string) *HumanOTPSMSCodeWriteModel { return &HumanOTPSMSCodeWriteModel{ HumanOTPSMSWriteModel: NewHumanOTPSMSWriteModel(userID, resourceOwner), @@ -163,10 +193,20 @@ func NewHumanOTPSMSCodeWriteModel(userID, resourceOwner string) *HumanOTPSMSCode func (wm *HumanOTPSMSCodeWriteModel) Reduce() error { for _, event := range wm.Events { - if e, ok := event.(*user.HumanOTPSMSCodeAddedEvent); ok { + switch e := event.(type) { + case *user.HumanOTPSMSCodeAddedEvent: wm.code = e.Code wm.codeCreationDate = e.CreationDate() wm.codeExpiry = e.Expiry + case *user.HumanOTPSMSCheckSucceededEvent: + wm.checkFailedCount = 0 + case *user.HumanOTPSMSCheckFailedEvent: + wm.checkFailedCount++ + case *user.UserLockedEvent: + wm.userLocked = true + case *user.UserUnlockedEvent: + wm.checkFailedCount = 0 + wm.userLocked = false } } return wm.HumanOTPSMSWriteModel.Reduce() @@ -179,6 +219,10 @@ func (wm *HumanOTPSMSCodeWriteModel) Query() *eventstore.SearchQueryBuilder { AggregateIDs(wm.AggregateID). EventTypes( user.HumanOTPSMSCodeAddedType, + user.HumanOTPSMSCheckSucceededType, + user.HumanOTPSMSCheckFailedType, + user.UserLockedType, + user.UserUnlockedType, user.HumanPhoneVerifiedType, user.HumanOTPSMSAddedType, user.HumanOTPSMSRemovedType, @@ -259,6 +303,9 @@ type HumanOTPEmailCodeWriteModel struct { code *crypto.CryptoValue codeCreationDate time.Time codeExpiry time.Duration + + checkFailedCount uint64 + userLocked bool } func (wm *HumanOTPEmailCodeWriteModel) CodeCreationDate() time.Time { @@ -273,6 +320,14 @@ func (wm *HumanOTPEmailCodeWriteModel) Code() *crypto.CryptoValue { return wm.code } +func (wm *HumanOTPEmailCodeWriteModel) CheckFailedCount() uint64 { + return wm.checkFailedCount +} + +func (wm *HumanOTPEmailCodeWriteModel) UserLocked() bool { + return wm.userLocked +} + func NewHumanOTPEmailCodeWriteModel(userID, resourceOwner string) *HumanOTPEmailCodeWriteModel { return &HumanOTPEmailCodeWriteModel{ HumanOTPEmailWriteModel: NewHumanOTPEmailWriteModel(userID, resourceOwner), @@ -281,10 +336,20 @@ func NewHumanOTPEmailCodeWriteModel(userID, resourceOwner string) *HumanOTPEmail func (wm *HumanOTPEmailCodeWriteModel) Reduce() error { for _, event := range wm.Events { - if e, ok := event.(*user.HumanOTPEmailCodeAddedEvent); ok { + switch e := event.(type) { + case *user.HumanOTPEmailCodeAddedEvent: wm.code = e.Code wm.codeCreationDate = e.CreationDate() wm.codeExpiry = e.Expiry + case *user.HumanOTPEmailCheckSucceededEvent: + wm.checkFailedCount = 0 + case *user.HumanOTPEmailCheckFailedEvent: + wm.checkFailedCount++ + case *user.UserLockedEvent: + wm.userLocked = true + case *user.UserUnlockedEvent: + wm.checkFailedCount = 0 + wm.userLocked = false } } return wm.HumanOTPEmailWriteModel.Reduce() @@ -297,6 +362,10 @@ func (wm *HumanOTPEmailCodeWriteModel) Query() *eventstore.SearchQueryBuilder { AggregateIDs(wm.AggregateID). EventTypes( user.HumanOTPEmailCodeAddedType, + user.HumanOTPEmailCheckSucceededType, + user.HumanOTPEmailCheckFailedType, + user.UserLockedType, + user.UserUnlockedType, user.HumanEmailVerifiedType, user.HumanOTPEmailAddedType, user.HumanOTPEmailRemovedType, diff --git a/internal/command/user_human_otp_test.go b/internal/command/user_human_otp_test.go index 490751610d..838e2357c6 100644 --- a/internal/command/user_human_otp_test.go +++ b/internal/command/user_human_otp_test.go @@ -1671,6 +1671,15 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) { ), ), ), + expectFilter(), // recheck + expectFilter( + eventFromEventPusher( + org.NewLockoutPolicyAddedEvent(ctx, + &org.NewAggregate("orgID").Aggregate, + 3, 3, true, + ), + ), + ), expectPush( user.NewHumanOTPSMSCheckFailedEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, @@ -1707,6 +1716,86 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) { err: zerrors.ThrowInvalidArgument(nil, "CODE-woT0xc", "Errors.User.Code.Invalid"), }, }, + { + name: "invalid code, max attempts reached, error", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + user.NewHumanOTPSMSAddedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + ), + ), + eventFromEventPusherWithCreationDateNow( + user.NewHumanOTPSMSCodeAddedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("other-code"), + }, + time.Hour, + &user.AuthRequestInfo{ + ID: "authRequestID", + UserAgentID: "userAgentID", + BrowserInfo: &user.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + ), + ), + ), + expectFilter(), // recheck + expectFilter( + eventFromEventPusher( + org.NewLockoutPolicyAddedEvent(ctx, + &org.NewAggregate("orgID").Aggregate, + 1, 1, true, + ), + ), + ), + expectPush( + user.NewHumanOTPSMSCheckFailedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + &user.AuthRequestInfo{ + ID: "authRequestID", + UserAgentID: "userAgentID", + BrowserInfo: &user.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + ), + user.NewUserLockedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: ctx, + userID: "user1", + code: "code", + resourceOwner: "org1", + authRequest: &domain.AuthRequest{ + ID: "authRequestID", + AgentID: "userAgentID", + BrowserInfo: &domain.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + }, + res: res{ + err: zerrors.ThrowInvalidArgument(nil, "CODE-woT0xc", "Errors.User.Code.Invalid"), + }, + }, { name: "code ok", fields: fields{ @@ -1739,6 +1828,7 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) { ), ), ), + expectFilter(), // recheck expectPush( user.NewHumanOTPSMSCheckSucceededEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, @@ -1777,6 +1867,65 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) { }, }, }, + { + name: "code ok, locked in the meantime", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + user.NewHumanOTPSMSAddedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + ), + ), + eventFromEventPusherWithCreationDateNow( + user.NewHumanOTPSMSCodeAddedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("code"), + }, + time.Hour, + &user.AuthRequestInfo{ + ID: "authRequestID", + UserAgentID: "userAgentID", + BrowserInfo: &user.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + ), + ), + ), + expectFilter( // recheck + user.NewUserLockedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: ctx, + userID: "user1", + code: "code", + resourceOwner: "org1", + authRequest: &domain.AuthRequest{ + ID: "authRequestID", + AgentID: "userAgentID", + BrowserInfo: &domain.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + }, + res: res{ + err: zerrors.ThrowPreconditionFailed(nil, "COMMAND-S6h4R", "Errors.User.Locked"), + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -2616,6 +2765,15 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) { ), ), ), + expectFilter(), // recheck + expectFilter( + eventFromEventPusher( + org.NewLockoutPolicyAddedEvent(ctx, + &org.NewAggregate("orgID").Aggregate, + 3, 3, true, + ), + ), + ), expectPush( user.NewHumanOTPEmailCheckFailedEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, @@ -2652,6 +2810,86 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) { err: zerrors.ThrowInvalidArgument(nil, "CODE-woT0xc", "Errors.User.Code.Invalid"), }, }, + { + name: "invalid code, max attempts reached, error", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + user.NewHumanOTPEmailAddedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + ), + ), + eventFromEventPusherWithCreationDateNow( + user.NewHumanOTPEmailCodeAddedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("other-code"), + }, + time.Hour, + &user.AuthRequestInfo{ + ID: "authRequestID", + UserAgentID: "userAgentID", + BrowserInfo: &user.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + ), + ), + ), + expectFilter(), // recheck + expectFilter( + eventFromEventPusher( + org.NewLockoutPolicyAddedEvent(ctx, + &org.NewAggregate("orgID").Aggregate, + 1, 1, true, + ), + ), + ), + expectPush( + user.NewHumanOTPEmailCheckFailedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + &user.AuthRequestInfo{ + ID: "authRequestID", + UserAgentID: "userAgentID", + BrowserInfo: &user.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + ), + user.NewUserLockedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: ctx, + userID: "user1", + code: "code", + resourceOwner: "org1", + authRequest: &domain.AuthRequest{ + ID: "authRequestID", + AgentID: "userAgentID", + BrowserInfo: &domain.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + }, + res: res{ + err: zerrors.ThrowInvalidArgument(nil, "CODE-woT0xc", "Errors.User.Code.Invalid"), + }, + }, { name: "code ok", fields: fields{ @@ -2684,6 +2922,7 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) { ), ), ), + expectFilter(), // recheck expectPush( user.NewHumanOTPEmailCheckSucceededEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, @@ -2722,6 +2961,65 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) { }, }, }, + { + name: "code ok, locked in the meantime", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + user.NewHumanOTPEmailAddedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + ), + ), + eventFromEventPusherWithCreationDateNow( + user.NewHumanOTPEmailCodeAddedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("code"), + }, + time.Hour, + &user.AuthRequestInfo{ + ID: "authRequestID", + UserAgentID: "userAgentID", + BrowserInfo: &user.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + ), + ), + ), + expectFilter( // recheck + user.NewUserLockedEvent(ctx, + &user.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: ctx, + userID: "user1", + code: "code", + resourceOwner: "org1", + authRequest: &domain.AuthRequest{ + ID: "authRequestID", + AgentID: "userAgentID", + BrowserInfo: &domain.BrowserInfo{ + UserAgent: "user-agent", + AcceptLanguage: "en", + RemoteIP: net.IP{192, 0, 2, 1}, + }, + }, + }, + res: res{ + err: zerrors.ThrowPreconditionFailed(nil, "COMMAND-S6h4R", "Errors.User.Locked"), + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/command/user_human_password_test.go b/internal/command/user_human_password_test.go index bb0fa9b26f..0c2b21b3d8 100644 --- a/internal/command/user_human_password_test.go +++ b/internal/command/user_human_password_test.go @@ -1643,6 +1643,7 @@ func TestCommandSide_CheckPassword(t *testing.T) { }, lockoutPolicy: &domain.LockoutPolicy{ MaxPasswordAttempts: 1, + MaxOTPAttempts: 1, }, }, res: res{ diff --git a/internal/domain/policy_password_lockout.go b/internal/domain/policy_password_lockout.go index ddac4de427..a5c304cd69 100644 --- a/internal/domain/policy_password_lockout.go +++ b/internal/domain/policy_password_lockout.go @@ -9,5 +9,6 @@ type LockoutPolicy struct { Default bool MaxPasswordAttempts uint64 + MaxOTPAttempts uint64 ShowLockOutFailures bool } diff --git a/internal/query/lockout_policy.go b/internal/query/lockout_policy.go index 7aa650b191..64be9b0b76 100644 --- a/internal/query/lockout_policy.go +++ b/internal/query/lockout_policy.go @@ -27,6 +27,7 @@ type LockoutPolicy struct { State domain.PolicyState MaxPasswordAttempts uint64 + MaxOTPAttempts uint64 ShowFailures bool IsDefault bool @@ -69,6 +70,10 @@ var ( name: projection.LockoutPolicyMaxPasswordAttemptsCol, table: lockoutTable, } + LockoutColMaxOTPAttempts = Column{ + name: projection.LockoutPolicyMaxOTPAttemptsCol, + table: lockoutTable, + } LockoutColIsDefault = Column{ name: projection.LockoutPolicyIsDefaultCol, table: lockoutTable, @@ -77,13 +82,9 @@ var ( name: projection.LockoutPolicyStateCol, table: lockoutTable, } - LockoutPolicyOwnerRemoved = Column{ - name: projection.LockoutPolicyOwnerRemovedCol, - table: lockoutTable, - } ) -func (q *Queries) LockoutPolicyByOrg(ctx context.Context, shouldTriggerBulk bool, orgID string, withOwnerRemoved bool) (policy *LockoutPolicy, err error) { +func (q *Queries) LockoutPolicyByOrg(ctx context.Context, shouldTriggerBulk bool, orgID string) (policy *LockoutPolicy, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -96,9 +97,6 @@ func (q *Queries) LockoutPolicyByOrg(ctx context.Context, shouldTriggerBulk bool eq := sq.Eq{ LockoutColInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - eq[LockoutPolicyOwnerRemoved.identifier()] = false - } stmt, scan := prepareLockoutPolicyQuery(ctx, q.client) query, args, err := stmt.Where( @@ -153,6 +151,7 @@ func prepareLockoutPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Sele LockoutColResourceOwner.identifier(), LockoutColShowFailures.identifier(), LockoutColMaxPasswordAttempts.identifier(), + LockoutColMaxOTPAttempts.identifier(), LockoutColIsDefault.identifier(), LockoutColState.identifier(), ). @@ -168,6 +167,7 @@ func prepareLockoutPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Sele &policy.ResourceOwner, &policy.ShowFailures, &policy.MaxPasswordAttempts, + &policy.MaxOTPAttempts, &policy.IsDefault, &policy.State, ) diff --git a/internal/query/lockout_policy_test.go b/internal/query/lockout_policy_test.go index 044f6c291b..2805ef8fdc 100644 --- a/internal/query/lockout_policy_test.go +++ b/internal/query/lockout_policy_test.go @@ -13,16 +13,17 @@ import ( ) var ( - prepareLockoutPolicyStmt = `SELECT projections.lockout_policies2.id,` + - ` projections.lockout_policies2.sequence,` + - ` projections.lockout_policies2.creation_date,` + - ` projections.lockout_policies2.change_date,` + - ` projections.lockout_policies2.resource_owner,` + - ` projections.lockout_policies2.show_failure,` + - ` projections.lockout_policies2.max_password_attempts,` + - ` projections.lockout_policies2.is_default,` + - ` projections.lockout_policies2.state` + - ` FROM projections.lockout_policies2` + + prepareLockoutPolicyStmt = `SELECT projections.lockout_policies3.id,` + + ` projections.lockout_policies3.sequence,` + + ` projections.lockout_policies3.creation_date,` + + ` projections.lockout_policies3.change_date,` + + ` projections.lockout_policies3.resource_owner,` + + ` projections.lockout_policies3.show_failure,` + + ` projections.lockout_policies3.max_password_attempts,` + + ` projections.lockout_policies3.max_otp_attempts,` + + ` projections.lockout_policies3.is_default,` + + ` projections.lockout_policies3.state` + + ` FROM projections.lockout_policies3` + ` AS OF SYSTEM TIME '-1 ms'` prepareLockoutPolicyCols = []string{ @@ -33,6 +34,7 @@ var ( "resource_owner", "show_failure", "max_password_attempts", + "max_otp_attempts", "is_default", "state", } @@ -82,6 +84,7 @@ func Test_LockoutPolicyPrepares(t *testing.T) { "ro", true, 20, + 20, true, domain.PolicyStateActive, }, @@ -96,6 +99,7 @@ func Test_LockoutPolicyPrepares(t *testing.T) { State: domain.PolicyStateActive, ShowFailures: true, MaxPasswordAttempts: 20, + MaxOTPAttempts: 20, IsDefault: true, }, }, diff --git a/internal/query/projection/lockout_policy.go b/internal/query/projection/lockout_policy.go index ceb99c2aa0..4412308b89 100644 --- a/internal/query/projection/lockout_policy.go +++ b/internal/query/projection/lockout_policy.go @@ -14,7 +14,7 @@ import ( ) const ( - LockoutPolicyTable = "projections.lockout_policies2" + LockoutPolicyTable = "projections.lockout_policies3" LockoutPolicyIDCol = "id" LockoutPolicyCreationDateCol = "creation_date" @@ -25,8 +25,8 @@ const ( LockoutPolicyResourceOwnerCol = "resource_owner" LockoutPolicyInstanceIDCol = "instance_id" LockoutPolicyMaxPasswordAttemptsCol = "max_password_attempts" + LockoutPolicyMaxOTPAttemptsCol = "max_otp_attempts" LockoutPolicyShowLockOutFailuresCol = "show_failure" - LockoutPolicyOwnerRemovedCol = "owner_removed" ) type lockoutPolicyProjection struct{} @@ -51,11 +51,10 @@ func (*lockoutPolicyProjection) Init() *old_handler.Check { handler.NewColumn(LockoutPolicyResourceOwnerCol, handler.ColumnTypeText), handler.NewColumn(LockoutPolicyInstanceIDCol, handler.ColumnTypeText), handler.NewColumn(LockoutPolicyMaxPasswordAttemptsCol, handler.ColumnTypeInt64), + handler.NewColumn(LockoutPolicyMaxOTPAttemptsCol, handler.ColumnTypeInt64, handler.Default(0)), handler.NewColumn(LockoutPolicyShowLockOutFailuresCol, handler.ColumnTypeBool), - handler.NewColumn(LockoutPolicyOwnerRemovedCol, handler.ColumnTypeBool, handler.Default(false)), }, handler.NewPrimaryKey(LockoutPolicyInstanceIDCol, LockoutPolicyIDCol), - handler.WithIndex(handler.NewIndex("owner_removed", []string{LockoutPolicyOwnerRemovedCol})), ), ) } @@ -125,6 +124,7 @@ func (p *lockoutPolicyProjection) reduceAdded(event eventstore.Event) (*handler. handler.NewCol(LockoutPolicyIDCol, policyEvent.Aggregate().ID), handler.NewCol(LockoutPolicyStateCol, domain.PolicyStateActive), handler.NewCol(LockoutPolicyMaxPasswordAttemptsCol, policyEvent.MaxPasswordAttempts), + handler.NewCol(LockoutPolicyMaxOTPAttemptsCol, policyEvent.MaxOTPAttempts), handler.NewCol(LockoutPolicyShowLockOutFailuresCol, policyEvent.ShowLockOutFailures), handler.NewCol(LockoutPolicyIsDefaultCol, isDefault), handler.NewCol(LockoutPolicyResourceOwnerCol, policyEvent.Aggregate().ResourceOwner), @@ -149,6 +149,9 @@ func (p *lockoutPolicyProjection) reduceChanged(event eventstore.Event) (*handle if policyEvent.MaxPasswordAttempts != nil { cols = append(cols, handler.NewCol(LockoutPolicyMaxPasswordAttemptsCol, *policyEvent.MaxPasswordAttempts)) } + if policyEvent.MaxOTPAttempts != nil { + cols = append(cols, handler.NewCol(LockoutPolicyMaxOTPAttemptsCol, *policyEvent.MaxOTPAttempts)) + } if policyEvent.ShowLockOutFailures != nil { cols = append(cols, handler.NewCol(LockoutPolicyShowLockOutFailuresCol, *policyEvent.ShowLockOutFailures)) } diff --git a/internal/query/projection/lockout_policy_test.go b/internal/query/projection/lockout_policy_test.go index 781f3a48c4..f44e1f4f0b 100644 --- a/internal/query/projection/lockout_policy_test.go +++ b/internal/query/projection/lockout_policy_test.go @@ -30,6 +30,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { org.AggregateType, []byte(`{ "maxPasswordAttempts": 10, + "maxOTPAttempts": 10, "showLockOutFailures": true }`), ), org.LockoutPolicyAddedEventMapper), @@ -41,7 +42,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.lockout_policies2 (creation_date, change_date, sequence, id, state, max_password_attempts, show_failure, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", + expectedStmt: "INSERT INTO projections.lockout_policies3 (creation_date, change_date, sequence, id, state, max_password_attempts, max_otp_attempts, show_failure, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", expectedArgs: []interface{}{ anyArg{}, anyArg{}, @@ -49,6 +50,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { "agg-id", domain.PolicyStateActive, uint64(10), + uint64(10), true, false, "ro-id", @@ -69,6 +71,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { org.AggregateType, []byte(`{ "maxPasswordAttempts": 10, + "maxOTPAttempts": 10, "showLockOutFailures": true }`), ), org.LockoutPolicyChangedEventMapper), @@ -79,11 +82,12 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.lockout_policies2 SET (change_date, sequence, max_password_attempts, show_failure) = ($1, $2, $3, $4) WHERE (id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.lockout_policies3 SET (change_date, sequence, max_password_attempts, max_otp_attempts, show_failure) = ($1, $2, $3, $4, $5) WHERE (id = $6) AND (instance_id = $7)", expectedArgs: []interface{}{ anyArg{}, uint64(15), uint64(10), + uint64(10), true, "agg-id", "instance-id", @@ -110,7 +114,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.lockout_policies2 WHERE (id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.lockout_policies3 WHERE (id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -137,7 +141,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.lockout_policies2 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.lockout_policies3 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, @@ -156,6 +160,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { instance.AggregateType, []byte(`{ "maxPasswordAttempts": 10, + "maxOTPAttempts": 10, "showLockOutFailures": true }`), ), instance.LockoutPolicyAddedEventMapper), @@ -166,7 +171,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.lockout_policies2 (creation_date, change_date, sequence, id, state, max_password_attempts, show_failure, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", + expectedStmt: "INSERT INTO projections.lockout_policies3 (creation_date, change_date, sequence, id, state, max_password_attempts, max_otp_attempts, show_failure, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", expectedArgs: []interface{}{ anyArg{}, anyArg{}, @@ -174,6 +179,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { "agg-id", domain.PolicyStateActive, uint64(10), + uint64(10), true, true, "ro-id", @@ -194,6 +200,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { instance.AggregateType, []byte(`{ "maxPasswordAttempts": 10, + "maxOTPAttempts": 10, "showLockOutFailures": true }`), ), instance.LockoutPolicyChangedEventMapper), @@ -204,11 +211,12 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.lockout_policies2 SET (change_date, sequence, max_password_attempts, show_failure) = ($1, $2, $3, $4) WHERE (id = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.lockout_policies3 SET (change_date, sequence, max_password_attempts, max_otp_attempts, show_failure) = ($1, $2, $3, $4, $5) WHERE (id = $6) AND (instance_id = $7)", expectedArgs: []interface{}{ anyArg{}, uint64(15), uint64(10), + uint64(10), true, "agg-id", "instance-id", @@ -235,7 +243,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.lockout_policies2 WHERE (instance_id = $1) AND (resource_owner = $2)", + expectedStmt: "DELETE FROM projections.lockout_policies3 WHERE (instance_id = $1) AND (resource_owner = $2)", expectedArgs: []interface{}{ "instance-id", "agg-id", diff --git a/internal/repository/instance/policy_password_lockout.go b/internal/repository/instance/policy_password_lockout.go index 671b539610..b05f992668 100644 --- a/internal/repository/instance/policy_password_lockout.go +++ b/internal/repository/instance/policy_password_lockout.go @@ -19,7 +19,8 @@ type LockoutPolicyAddedEvent struct { func NewLockoutPolicyAddedEvent( ctx context.Context, aggregate *eventstore.Aggregate, - maxAttempts uint64, + maxPasswordAttempts, + maxOTPAttempts uint64, showLockoutFailure bool, ) *LockoutPolicyAddedEvent { return &LockoutPolicyAddedEvent{ @@ -28,7 +29,8 @@ func NewLockoutPolicyAddedEvent( ctx, aggregate, LockoutPolicyAddedEventType), - maxAttempts, + maxPasswordAttempts, + maxOTPAttempts, showLockoutFailure), } } diff --git a/internal/repository/org/policy_password_lockout.go b/internal/repository/org/policy_password_lockout.go index e95d86eb79..8ada7f2320 100644 --- a/internal/repository/org/policy_password_lockout.go +++ b/internal/repository/org/policy_password_lockout.go @@ -20,7 +20,8 @@ type LockoutPolicyAddedEvent struct { func NewLockoutPolicyAddedEvent( ctx context.Context, aggregate *eventstore.Aggregate, - maxAttempts uint64, + maxPasswordAttempts, + maxOTPAttempts uint64, showLockoutFailure bool, ) *LockoutPolicyAddedEvent { return &LockoutPolicyAddedEvent{ @@ -29,7 +30,8 @@ func NewLockoutPolicyAddedEvent( ctx, aggregate, LockoutPolicyAddedEventType), - maxAttempts, + maxPasswordAttempts, + maxOTPAttempts, showLockoutFailure), } } diff --git a/internal/repository/policy/policy_password_lockout.go b/internal/repository/policy/policy_password_lockout.go index 8ca0737674..ec500144ef 100644 --- a/internal/repository/policy/policy_password_lockout.go +++ b/internal/repository/policy/policy_password_lockout.go @@ -15,6 +15,7 @@ type LockoutPolicyAddedEvent struct { eventstore.BaseEvent `json:"-"` MaxPasswordAttempts uint64 `json:"maxPasswordAttempts,omitempty"` + MaxOTPAttempts uint64 `json:"maxOTPAttempts,omitempty"` ShowLockOutFailures bool `json:"showLockOutFailures,omitempty"` } @@ -28,13 +29,15 @@ func (e *LockoutPolicyAddedEvent) UniqueConstraints() []*eventstore.UniqueConstr func NewLockoutPolicyAddedEvent( base *eventstore.BaseEvent, - maxAttempts uint64, + maxPasswordAttempts, + maxOTPAttempts uint64, showLockOutFailures bool, ) *LockoutPolicyAddedEvent { return &LockoutPolicyAddedEvent{ BaseEvent: *base, - MaxPasswordAttempts: maxAttempts, + MaxPasswordAttempts: maxPasswordAttempts, + MaxOTPAttempts: maxOTPAttempts, ShowLockOutFailures: showLockOutFailures, } } @@ -56,6 +59,7 @@ type LockoutPolicyChangedEvent struct { eventstore.BaseEvent `json:"-"` MaxPasswordAttempts *uint64 `json:"maxPasswordAttempts,omitempty"` + MaxOTPAttempts *uint64 `json:"maxOTPAttempts,omitempty"` ShowLockOutFailures *bool `json:"showLockOutFailures,omitempty"` } @@ -85,12 +89,18 @@ func NewLockoutPolicyChangedEvent( type LockoutPolicyChanges func(*LockoutPolicyChangedEvent) -func ChangeMaxAttempts(maxAttempts uint64) func(*LockoutPolicyChangedEvent) { +func ChangeMaxPasswordAttempts(maxAttempts uint64) func(*LockoutPolicyChangedEvent) { return func(e *LockoutPolicyChangedEvent) { e.MaxPasswordAttempts = &maxAttempts } } +func ChangeMaxOTPAttempts(maxAttempts uint64) func(*LockoutPolicyChangedEvent) { + return func(e *LockoutPolicyChangedEvent) { + e.MaxOTPAttempts = &maxAttempts + } +} + func ChangeShowLockOutFailures(showLockOutFailures bool) func(*LockoutPolicyChangedEvent) { return func(e *LockoutPolicyChangedEvent) { e.ShowLockOutFailures = &showLockOutFailures diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index 3e42109619..5211995f23 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -6650,6 +6650,12 @@ message UpdateLockoutPolicyRequest { example: "\"10\"" } ]; + uint32 max_otp_attempts = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Maximum failed attempts for a single OTP type (TOTP, SMS, Email) before the account gets locked. Attempts are reset as soon as the OTP is entered correctly. If set to 0 the account will never be locked." + example: "\"10\"" + } + ]; } message UpdateLockoutPolicyResponse { diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index 284b070d5d..aca7ae17ca 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -10412,6 +10412,12 @@ message AddCustomLockoutPolicyRequest { description: "When the user has reached the maximum password attempts the account will be locked, If this is set to 0 the lockout will not trigger." } ]; + uint32 max_otp_attempts = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Maximum failed attempts for a single OTP type (TOTP, SMS, Email) before the account gets locked. Attempts are reset as soon as the OTP is entered correctly. If set to 0 the account will never be locked." + example: "\"10\"" + } + ]; } message AddCustomLockoutPolicyResponse { @@ -10424,6 +10430,12 @@ message UpdateCustomLockoutPolicyRequest { description: "When the user has reached the maximum password attempts the account will be locked, If this is set to 0 the lockout will not trigger." } ]; + uint32 max_otp_attempts = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Maximum failed attempts for a single OTP type (TOTP, SMS, Email) before the account gets locked. Attempts are reset as soon as the OTP is entered correctly. If set to 0 the account will never be locked." + example: "\"10\"" + } + ]; } message UpdateCustomLockoutPolicyResponse { diff --git a/proto/zitadel/policy.proto b/proto/zitadel/policy.proto index 8fcb976d44..75d8472103 100644 --- a/proto/zitadel/policy.proto +++ b/proto/zitadel/policy.proto @@ -337,6 +337,12 @@ message LockoutPolicy { example: "\"10\"" } ]; + uint64 max_otp_attempts = 3 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Maximum failed attempts for a single OTP type (TOTP, SMS, Email) before the account gets locked. Attempts are reset as soon as the OTP is entered correctly. If set to 0 the account will never be locked." + example: "\"10\"" + } + ]; bool is_default = 4 [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { description: "defines if the organization's admin changed the policy" diff --git a/proto/zitadel/settings/v2beta/lockout_settings.proto b/proto/zitadel/settings/v2beta/lockout_settings.proto index 10786c9282..1ff10c65f0 100644 --- a/proto/zitadel/settings/v2beta/lockout_settings.proto +++ b/proto/zitadel/settings/v2beta/lockout_settings.proto @@ -20,4 +20,10 @@ message LockoutSettings { description: "resource_owner_type returns if the settings is managed on the organization or on the instance"; } ]; + uint64 max_otp_attempts = 3 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Maximum failed attempts for a single OTP type (TOTP, SMS, Email) before the account gets locked. Attempts are reset as soon as the OTP is entered correctly. If set to 0 the account will never be locked." + example: "\"10\"" + } + ]; }