From 76fd33b7ed460a1a633f594810f4859dd834304d Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Fri, 17 May 2024 16:02:05 +0800 Subject: [PATCH] feat: default user role (#5872) * feat: default user role * chore: add tests and changeset * refactor: show warning for deprecated env * chore: fix tests --- .changeset/spicy-drinks-camp.md | 8 + packages/app-insights/package.json | 2 +- packages/cli/package.json | 2 +- .../src/commands/database/alteration/index.ts | 4 +- .../src/commands/translate/sync-keys/utils.ts | 2 +- .../connector-alipay-native/package.json | 2 +- .../connector-alipay-web/package.json | 2 +- .../connector-aliyun-dm/package.json | 2 +- .../connector-aliyun-sms/package.json | 2 +- .../connectors/connector-apple/package.json | 2 +- .../connectors/connector-aws-ses/package.json | 2 +- .../connectors/connector-azuread/package.json | 2 +- .../connectors/connector-discord/package.json | 2 +- .../connector-facebook/package.json | 2 +- .../connector-feishu-web/package.json | 2 +- .../connectors/connector-github/package.json | 2 +- .../connectors/connector-google/package.json | 2 +- .../connector-huggingface/package.json | 2 +- .../connectors/connector-kakao/package.json | 2 +- .../connector-logto-email/package.json | 2 +- .../connector-logto-sms/package.json | 2 +- .../connector-logto-social-demo/package.json | 2 +- .../connectors/connector-mailgun/package.json | 2 +- .../package.json | 2 +- .../connector-mock-email/package.json | 2 +- .../connector-mock-sms/package.json | 2 +- .../connector-mock-social/package.json | 2 +- .../connectors/connector-naver/package.json | 2 +- .../connectors/connector-oauth2/package.json | 2 +- .../connectors/connector-oidc/package.json | 2 +- .../connectors/connector-saml/package.json | 2 +- .../connector-sendgrid-email/package.json | 2 +- .../connectors/connector-smsaero/package.json | 2 +- .../connectors/connector-smtp/package.json | 2 +- .../connector-tencent-sms/package.json | 2 +- .../connector-twilio-sms/package.json | 2 +- .../connector-wechat-native/package.json | 2 +- .../connector-wechat-web/package.json | 2 +- .../connectors/connector-wecom/package.json | 2 +- packages/console/package.json | 2 +- .../pages/RoleDetails/RoleSettings/index.tsx | 11 +- packages/core/jest.setup.js | 4 +- packages/core/package.json | 2 +- packages/core/src/__mocks__/index.ts | 4 + .../src/env-set/check-alteration-state.ts | 25 --- packages/core/src/env-set/preconditions.ts | 69 ++++++ packages/core/src/libraries/user.ts | 17 +- packages/core/src/main.ts | 7 +- packages/core/src/queries/roles.test.ts | 2 +- packages/core/src/queries/roles.ts | 11 + .../core/src/routes/admin-user/basics.test.ts | 1 + .../core/src/routes/admin-user/search.test.ts | 1 + packages/core/src/routes/role.test.ts | 1 + packages/core/src/routes/role.ts | 2 +- packages/core/src/tenants/utils.ts | 28 +-- packages/demo-app/src/App.tsx | 2 +- packages/experience/package.json | 2 +- packages/integration-tests/package.json | 2 +- packages/integration-tests/src/api/role.ts | 3 + .../integration-tests/src/client/index.ts | 4 + .../src/tests/api/role.is-default.test.ts | 107 +++++++++ packages/phrases-experience/package.json | 2 +- packages/phrases/package.json | 2 +- packages/phrases/src/index.ts | 10 +- packages/phrases/src/locales/de/index.ts | 4 +- .../translation/admin-console/role-details.ts | 3 + packages/phrases/src/locales/es/index.ts | 4 +- packages/phrases/src/locales/fr/index.ts | 4 +- packages/phrases/src/locales/it/index.ts | 4 +- packages/phrases/src/locales/ja/index.ts | 4 +- packages/phrases/src/locales/ko/index.ts | 4 +- packages/phrases/src/locales/pl-pl/index.ts | 4 +- packages/phrases/src/locales/pt-br/index.ts | 4 +- packages/phrases/src/locales/pt-pt/index.ts | 4 +- packages/phrases/src/locales/ru/index.ts | 4 +- packages/phrases/src/locales/tr-tr/index.ts | 4 +- packages/phrases/src/locales/zh-cn/index.ts | 4 +- packages/phrases/src/locales/zh-hk/index.ts | 4 +- packages/phrases/src/locales/zh-tw/index.ts | 4 +- ...1715826336-add-default-user-role-config.ts | 18 ++ packages/schemas/package.json | 2 +- packages/schemas/src/seeds/cloud-api.ts | 1 + packages/schemas/src/types/mapi-proxy.ts | 1 + packages/schemas/tables/roles.sql | 2 + packages/shared/package.json | 2 +- packages/shared/src/node/env/GlobalValues.ts | 1 + packages/toolkit/connector-kit/package.json | 2 +- packages/toolkit/core-kit/package.json | 2 +- pnpm-lock.yaml | 208 +++++++++--------- 89 files changed, 467 insertions(+), 238 deletions(-) create mode 100644 .changeset/spicy-drinks-camp.md delete mode 100644 packages/core/src/env-set/check-alteration-state.ts create mode 100644 packages/core/src/env-set/preconditions.ts create mode 100644 packages/integration-tests/src/tests/api/role.is-default.test.ts create mode 100644 packages/schemas/alterations/next-1715826336-add-default-user-role-config.ts diff --git a/.changeset/spicy-drinks-camp.md b/.changeset/spicy-drinks-camp.md new file mode 100644 index 000000000..badd048b8 --- /dev/null +++ b/.changeset/spicy-drinks-camp.md @@ -0,0 +1,8 @@ +--- +"@logto/console": minor +"@logto/phrases": minor +"@logto/schemas": minor +"@logto/core": minor +--- + +support default roles for users diff --git a/packages/app-insights/package.json b/packages/app-insights/package.json index d229f4ae4..8016dc027 100644 --- a/packages/app-insights/package.json +++ b/packages/app-insights/package.json @@ -47,7 +47,7 @@ }, "prettier": "@silverhand/eslint-config/.prettierrc", "dependencies": { - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "applicationinsights": "^2.9.5" }, "peerDependencies": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 98060e6b7..4bf0c115f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -49,7 +49,7 @@ "@logto/phrases-experience": "workspace:^1.6.1", "@logto/schemas": "workspace:1.16.0", "@logto/shared": "workspace:^3.1.1", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "@silverhand/slonik": "31.0.0-beta.2", "chalk": "^5.0.0", "decamelize": "^6.0.0", diff --git a/packages/cli/src/commands/database/alteration/index.ts b/packages/cli/src/commands/database/alteration/index.ts index 16b47bd53..a9567948f 100644 --- a/packages/cli/src/commands/database/alteration/index.ts +++ b/packages/cli/src/commands/database/alteration/index.ts @@ -1,6 +1,6 @@ import type { AlterationScript } from '@logto/schemas/lib/types/alteration.js'; import { conditionalString } from '@silverhand/essentials'; -import type { DatabasePool } from '@silverhand/slonik'; +import type { CommonQueryMethods, DatabasePool } from '@silverhand/slonik'; import chalk from 'chalk'; import type { CommandModule } from 'yargs'; @@ -39,7 +39,7 @@ export const getLatestAlterationTimestamp = async () => { }; export const getAvailableAlterations = async ( - pool: DatabasePool, + pool: CommonQueryMethods, compareMode: 'gt' | 'lte' = 'gt' ) => { const databaseTimestamp = await getCurrentDatabaseAlterationTimestamp(pool); diff --git a/packages/cli/src/commands/translate/sync-keys/utils.ts b/packages/cli/src/commands/translate/sync-keys/utils.ts index 8644334e8..c2e7afe15 100644 --- a/packages/cli/src/commands/translate/sync-keys/utils.ts +++ b/packages/cli/src/commands/translate/sync-keys/utils.ts @@ -355,7 +355,7 @@ const traverseNode = async ( await traverseObject(baseline, targetObject, 2); await (isRoot - ? fs.appendFile(targetFilePath, '} satisfies LocalePhrase;\n\n') + ? fs.appendFile(targetFilePath, '} satisfies DeepPartial;\n\n') : fs.appendFile(targetFilePath, '};\n\n')); await fs.appendFile(targetFilePath, `export default Object.freeze(${identifier});\n`); }; diff --git a/packages/connectors/connector-alipay-native/package.json b/packages/connectors/connector-alipay-native/package.json index 84227e3f2..6cbf3aa52 100644 --- a/packages/connectors/connector-alipay-native/package.json +++ b/packages/connectors/connector-alipay-native/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "dayjs": "^1.10.5", "got": "^14.0.0", "iconv-lite": "^0.6.3", diff --git a/packages/connectors/connector-alipay-web/package.json b/packages/connectors/connector-alipay-web/package.json index b65a156b7..6a787a15f 100644 --- a/packages/connectors/connector-alipay-web/package.json +++ b/packages/connectors/connector-alipay-web/package.json @@ -4,7 +4,7 @@ "description": "Alipay implementation.", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "dayjs": "^1.10.5", "got": "^14.0.0", "iconv-lite": "^0.6.3", diff --git a/packages/connectors/connector-aliyun-dm/package.json b/packages/connectors/connector-aliyun-dm/package.json index 9f3ee0b47..0aac03039 100644 --- a/packages/connectors/connector-aliyun-dm/package.json +++ b/packages/connectors/connector-aliyun-dm/package.json @@ -4,7 +4,7 @@ "description": "Aliyun DM connector implementation.", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-aliyun-sms/package.json b/packages/connectors/connector-aliyun-sms/package.json index f8bd26449..bf35fd641 100644 --- a/packages/connectors/connector-aliyun-sms/package.json +++ b/packages/connectors/connector-aliyun-sms/package.json @@ -4,7 +4,7 @@ "description": "Aliyun SMS connector implementation.", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-apple/package.json b/packages/connectors/connector-apple/package.json index 64da97b4b..8aa1e61eb 100644 --- a/packages/connectors/connector-apple/package.json +++ b/packages/connectors/connector-apple/package.json @@ -5,7 +5,7 @@ "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", "@logto/shared": "workspace:^3.1.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "jose": "^5.0.0", "snakecase-keys": "^8.0.0", diff --git a/packages/connectors/connector-aws-ses/package.json b/packages/connectors/connector-aws-ses/package.json index 397fd55d5..a9563d24b 100644 --- a/packages/connectors/connector-aws-ses/package.json +++ b/packages/connectors/connector-aws-ses/package.json @@ -7,7 +7,7 @@ "@aws-sdk/client-sesv2": "^3.556.0", "@aws-sdk/types": "^3.535.0", "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-azuread/package.json b/packages/connectors/connector-azuread/package.json index ec941edc8..ceb0c74cf 100644 --- a/packages/connectors/connector-azuread/package.json +++ b/packages/connectors/connector-azuread/package.json @@ -6,7 +6,7 @@ "dependencies": { "@azure/msal-node": "^2.0.0", "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-discord/package.json b/packages/connectors/connector-discord/package.json index 0d585c35e..f5abedaa2 100644 --- a/packages/connectors/connector-discord/package.json +++ b/packages/connectors/connector-discord/package.json @@ -5,7 +5,7 @@ "author": "ZR3SYSTEMS. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-facebook/package.json b/packages/connectors/connector-facebook/package.json index a9adc5627..3cc19cc84 100644 --- a/packages/connectors/connector-facebook/package.json +++ b/packages/connectors/connector-facebook/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-feishu-web/package.json b/packages/connectors/connector-feishu-web/package.json index 50bc45c13..4d5920230 100644 --- a/packages/connectors/connector-feishu-web/package.json +++ b/packages/connectors/connector-feishu-web/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-github/package.json b/packages/connectors/connector-github/package.json index 233070e6f..16846d954 100644 --- a/packages/connectors/connector-github/package.json +++ b/packages/connectors/connector-github/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "ky": "^1.2.3", "query-string": "^9.0.0", "snakecase-keys": "^8.0.0", diff --git a/packages/connectors/connector-google/package.json b/packages/connectors/connector-google/package.json index a837908f0..06479432d 100644 --- a/packages/connectors/connector-google/package.json +++ b/packages/connectors/connector-google/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-huggingface/package.json b/packages/connectors/connector-huggingface/package.json index ef45e49e5..ed197e5dc 100644 --- a/packages/connectors/connector-huggingface/package.json +++ b/packages/connectors/connector-huggingface/package.json @@ -6,7 +6,7 @@ "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", "@logto/connector-oauth": "workspace:^1.3.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "ky": "^1.2.3", "zod": "^3.22.4" }, diff --git a/packages/connectors/connector-kakao/package.json b/packages/connectors/connector-kakao/package.json index af1be48d8..4d41b6b81 100644 --- a/packages/connectors/connector-kakao/package.json +++ b/packages/connectors/connector-kakao/package.json @@ -5,7 +5,7 @@ "author": "Kyungyoon Kim. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-logto-email/package.json b/packages/connectors/connector-logto-email/package.json index 8ecae3af3..b3fb38956 100644 --- a/packages/connectors/connector-logto-email/package.json +++ b/packages/connectors/connector-logto-email/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-logto-sms/package.json b/packages/connectors/connector-logto-sms/package.json index aa97290af..4b3b2ecc5 100644 --- a/packages/connectors/connector-logto-sms/package.json +++ b/packages/connectors/connector-logto-sms/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-logto-social-demo/package.json b/packages/connectors/connector-logto-social-demo/package.json index 3434cd239..666c617ff 100644 --- a/packages/connectors/connector-logto-social-demo/package.json +++ b/packages/connectors/connector-logto-social-demo/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-mailgun/package.json b/packages/connectors/connector-mailgun/package.json index 4ae0879ad..446d52b28 100644 --- a/packages/connectors/connector-mailgun/package.json +++ b/packages/connectors/connector-mailgun/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-mock-email-alternative/package.json b/packages/connectors/connector-mock-email-alternative/package.json index 665d563c5..d42079609 100644 --- a/packages/connectors/connector-mock-email-alternative/package.json +++ b/packages/connectors/connector-mock-email-alternative/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-mock-email/package.json b/packages/connectors/connector-mock-email/package.json index e51158635..66344682f 100644 --- a/packages/connectors/connector-mock-email/package.json +++ b/packages/connectors/connector-mock-email/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-mock-sms/package.json b/packages/connectors/connector-mock-sms/package.json index b495e3a8c..09f82cbce 100644 --- a/packages/connectors/connector-mock-sms/package.json +++ b/packages/connectors/connector-mock-sms/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-mock-social/package.json b/packages/connectors/connector-mock-social/package.json index 3ed7a26b1..4a687efb7 100644 --- a/packages/connectors/connector-mock-social/package.json +++ b/packages/connectors/connector-mock-social/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-naver/package.json b/packages/connectors/connector-naver/package.json index 0de8c44c9..9202e4b1b 100644 --- a/packages/connectors/connector-naver/package.json +++ b/packages/connectors/connector-naver/package.json @@ -5,7 +5,7 @@ "author": "Kyungyoon Kim. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-oauth2/package.json b/packages/connectors/connector-oauth2/package.json index 104971865..7847233d9 100644 --- a/packages/connectors/connector-oauth2/package.json +++ b/packages/connectors/connector-oauth2/package.json @@ -6,7 +6,7 @@ "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", "@logto/shared": "workspace:^3.1.1", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "jose": "^5.0.0", "ky": "^1.2.3", "query-string": "^9.0.0", diff --git a/packages/connectors/connector-oidc/package.json b/packages/connectors/connector-oidc/package.json index 501993454..4df496a2b 100644 --- a/packages/connectors/connector-oidc/package.json +++ b/packages/connectors/connector-oidc/package.json @@ -6,7 +6,7 @@ "@logto/connector-kit": "workspace:^3.0.0", "@logto/connector-oauth": "workspace:^1.3.0", "@logto/shared": "workspace:^3.1.1", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "jose": "^5.0.0", "ky": "^1.2.3", "nanoid": "^5.0.1", diff --git a/packages/connectors/connector-saml/package.json b/packages/connectors/connector-saml/package.json index 3161db135..440defa1d 100644 --- a/packages/connectors/connector-saml/package.json +++ b/packages/connectors/connector-saml/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "fast-xml-parser": "^4.3.6", "got": "^14.0.0", "samlify": "2.8.11", diff --git a/packages/connectors/connector-sendgrid-email/package.json b/packages/connectors/connector-sendgrid-email/package.json index 393fe2045..f57a58a1a 100644 --- a/packages/connectors/connector-sendgrid-email/package.json +++ b/packages/connectors/connector-sendgrid-email/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-smsaero/package.json b/packages/connectors/connector-smsaero/package.json index 65477b45b..fd87e00c5 100644 --- a/packages/connectors/connector-smsaero/package.json +++ b/packages/connectors/connector-smsaero/package.json @@ -5,7 +5,7 @@ "author": "Danil Tankov ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-smtp/package.json b/packages/connectors/connector-smtp/package.json index a9364a079..24c028f8a 100644 --- a/packages/connectors/connector-smtp/package.json +++ b/packages/connectors/connector-smtp/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "nodemailer": "^6.9.9", "snakecase-keys": "^8.0.0", diff --git a/packages/connectors/connector-tencent-sms/package.json b/packages/connectors/connector-tencent-sms/package.json index 02bcb8b95..e089a1c22 100644 --- a/packages/connectors/connector-tencent-sms/package.json +++ b/packages/connectors/connector-tencent-sms/package.json @@ -5,7 +5,7 @@ "author": "StringKe", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-twilio-sms/package.json b/packages/connectors/connector-twilio-sms/package.json index 8a1bcfc60..c42f2ce37 100644 --- a/packages/connectors/connector-twilio-sms/package.json +++ b/packages/connectors/connector-twilio-sms/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-wechat-native/package.json b/packages/connectors/connector-wechat-native/package.json index 4a1a31989..b249d3330 100644 --- a/packages/connectors/connector-wechat-native/package.json +++ b/packages/connectors/connector-wechat-native/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-wechat-web/package.json b/packages/connectors/connector-wechat-web/package.json index 50c54bf2a..4af7a6031 100644 --- a/packages/connectors/connector-wechat-web/package.json +++ b/packages/connectors/connector-wechat-web/package.json @@ -5,7 +5,7 @@ "author": "Silverhand Inc. ", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/connectors/connector-wecom/package.json b/packages/connectors/connector-wecom/package.json index 4510fcc46..61ec7c224 100644 --- a/packages/connectors/connector-wecom/package.json +++ b/packages/connectors/connector-wecom/package.json @@ -5,7 +5,7 @@ "author": "Dove fork from Wechat Web connector", "dependencies": { "@logto/connector-kit": "workspace:^3.0.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "got": "^14.0.0", "snakecase-keys": "^8.0.0", "zod": "^3.22.4" diff --git a/packages/console/package.json b/packages/console/package.json index 5a9e24c6c..3c3fae0ea 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -46,7 +46,7 @@ "@parcel/transformer-svg-react": "2.9.3", "@silverhand/eslint-config": "6.0.1", "@silverhand/eslint-config-react": "6.0.2", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "@silverhand/ts-config": "6.0.0", "@silverhand/ts-config-react": "6.0.0", "@swc/core": "^1.3.52", diff --git a/packages/console/src/pages/RoleDetails/RoleSettings/index.tsx b/packages/console/src/pages/RoleDetails/RoleSettings/index.tsx index 765f222f6..5caf889a6 100644 --- a/packages/console/src/pages/RoleDetails/RoleSettings/index.tsx +++ b/packages/console/src/pages/RoleDetails/RoleSettings/index.tsx @@ -1,4 +1,4 @@ -import type { Role } from '@logto/schemas'; +import { RoleType, type Role } from '@logto/schemas'; import { useForm } from 'react-hook-form'; import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; @@ -8,6 +8,7 @@ import DetailsForm from '@/components/DetailsForm'; import FormCard from '@/components/FormCard'; import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal'; import FormField from '@/ds-components/FormField'; +import Switch from '@/ds-components/Switch'; import TextInput from '@/ds-components/TextInput'; import useApi from '@/hooks/use-api'; import { trySubmitSafe } from '@/utils/form'; @@ -66,6 +67,14 @@ function RoleSettings() { error={Boolean(errors.description)} /> + {role.type === RoleType.User && ( + + + + )} diff --git a/packages/core/jest.setup.js b/packages/core/jest.setup.js index a38acc38c..dd90ab885 100644 --- a/packages/core/jest.setup.js +++ b/packages/core/jest.setup.js @@ -18,8 +18,8 @@ mockEsm('#src/libraries/logto-config.js', () => ({ createLogtoConfigLibrary: () => ({ getOidcConfigs: () => ({}) }), })); -mockEsm('#src/env-set/check-alteration-state.js', () => ({ - checkAlterationState: () => true, +mockEsm('#src/env-set/preconditions.js', () => ({ + checkPreconditions: () => true, })); // eslint-disable-next-line unicorn/consistent-function-scoping diff --git a/packages/core/package.json b/packages/core/package.json index b26d677b6..d7aeb639a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -43,7 +43,7 @@ "@logto/phrases-experience": "workspace:^1.6.1", "@logto/schemas": "workspace:^1.16.0", "@logto/shared": "workspace:^3.1.1", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "@silverhand/slonik": "31.0.0-beta.2", "@simplewebauthn/server": "^10.0.0", "@withtyped/client": "^0.8.7", diff --git a/packages/core/src/__mocks__/index.ts b/packages/core/src/__mocks__/index.ts index 119ef647e..603483c97 100644 --- a/packages/core/src/__mocks__/index.ts +++ b/packages/core/src/__mocks__/index.ts @@ -122,6 +122,7 @@ export const mockAdminApplicationRole: Role = { name: 'admin', description: 'admin application', type: RoleType.MachineToMachine, + isDefault: false, }; export const mockAdminUserRole: Role = { @@ -130,6 +131,7 @@ export const mockAdminUserRole: Role = { name: 'admin', description: 'admin', type: RoleType.User, + isDefault: false, }; export const mockAdminUserRole2: Role = { @@ -138,6 +140,7 @@ export const mockAdminUserRole2: Role = { name: 'admin2', description: 'admin2', type: RoleType.User, + isDefault: false, }; export const mockAdminUserRole3: Role = { @@ -146,6 +149,7 @@ export const mockAdminUserRole3: Role = { name: 'admin3', description: 'admin3', type: RoleType.MachineToMachine, + isDefault: false, }; export const mockAdminConsoleData: AdminConsoleData = { diff --git a/packages/core/src/env-set/check-alteration-state.ts b/packages/core/src/env-set/check-alteration-state.ts deleted file mode 100644 index 4fc8d3c73..000000000 --- a/packages/core/src/env-set/check-alteration-state.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { getAvailableAlterations } from '@logto/cli/lib/commands/database/alteration/index.js'; -import { ConsoleLog } from '@logto/shared'; -import type { DatabasePool } from '@silverhand/slonik'; -import chalk from 'chalk'; - -const consoleLog = new ConsoleLog(chalk.magenta('db-alt')); - -export const checkAlterationState = async (pool: DatabasePool) => { - const alterations = await getAvailableAlterations(pool); - - if (alterations.length === 0) { - return; - } - - consoleLog.error( - `Found undeployed database alterations, you must deploy them first by ${chalk.green( - 'npm run alteration deploy' - )} command.\n\n` + - ` See ${chalk.blue( - 'https://docs.logto.io/docs/tutorials/using-cli/database-alteration' - )} for reference.\n` - ); - - throw new Error(`Undeployed database alterations found.`); -}; diff --git a/packages/core/src/env-set/preconditions.ts b/packages/core/src/env-set/preconditions.ts new file mode 100644 index 000000000..e2df97e3d --- /dev/null +++ b/packages/core/src/env-set/preconditions.ts @@ -0,0 +1,69 @@ +import { getAvailableAlterations } from '@logto/cli/lib/commands/database/alteration/index.js'; +import { ServiceLogs, Systems } from '@logto/schemas'; +import { ConsoleLog, isKeyInObject } from '@logto/shared'; +import { conditionalString } from '@silverhand/essentials'; +import { sql, type CommonQueryMethods, type DatabasePool } from '@silverhand/slonik'; +import chalk from 'chalk'; + +import { EnvSet } from './index.js'; + +const consoleLog = new ConsoleLog(chalk.magenta('pre')); + +export const checkPreconditions = async (pool: DatabasePool) => { + checkDeprecations(); + await Promise.all([checkAlterationState(pool), checkRowLevelSecurity(pool)]); +}; + +const checkRowLevelSecurity = async (client: CommonQueryMethods) => { + const { rows } = await client.query(sql` + select tablename + from pg_catalog.pg_tables + where schemaname = current_schema() + and rowsecurity=false + `); + + const rlsDisabled = rows.filter( + ({ tablename }) => tablename !== Systems.table && tablename !== ServiceLogs.table + ); + + if (rlsDisabled.length > 0) { + throw new Error( + 'Row-level security has to be enforced on EVERY business table when starting Logto.\n' + + `Found following table(s) without RLS: ${rlsDisabled + .map((row) => conditionalString(isKeyInObject(row, 'tablename') && String(row.tablename))) + .join(', ')}\n\n` + + 'Did you forget to run `npm cli db alteration deploy`?' + ); + } +}; + +const checkAlterationState = async (pool: CommonQueryMethods) => { + const alterations = await getAvailableAlterations(pool); + + if (alterations.length === 0) { + return; + } + + consoleLog.error( + `Found undeployed database alterations, you must deploy them first by ${chalk.green( + 'npm run alteration deploy' + )} command.\n\n` + + ` See ${chalk.blue( + 'https://docs.logto.io/docs/tutorials/using-cli/database-alteration' + )} for reference.\n` + ); + + throw new Error(`Undeployed database alterations found.`); +}; + +const checkDeprecations = () => { + if (EnvSet.values.userDefaultRoleNames.length > 0) { + consoleLog.warn( + `The environment variable ${chalk.green( + 'USER_DEFAULT_ROLE_NAMES' + )} is deprecated and will be removed in the next major version. Please use the built-in user default role configuration (${chalk.green( + 'Roles.isDefault' + )}) instead.\n` + ); + } +}; diff --git a/packages/core/src/libraries/user.ts b/packages/core/src/libraries/user.ts index 66a4560c6..0de3a0b1c 100644 --- a/packages/core/src/libraries/user.ts +++ b/packages/core/src/libraries/user.ts @@ -1,8 +1,7 @@ import type { User, CreateUser, Scope, BindMfa, MfaVerification } from '@logto/schemas'; -import { MfaFactor, Users, UsersPasswordEncryptionMethod } from '@logto/schemas'; +import { MfaFactor, RoleType, Users, UsersPasswordEncryptionMethod } from '@logto/schemas'; import { generateStandardShortId, generateStandardId } from '@logto/shared'; -import type { Nullable } from '@silverhand/essentials'; -import { deduplicate } from '@silverhand/essentials'; +import { deduplicateByKey, type Nullable } from '@silverhand/essentials'; import { argon2Verify, bcryptVerify, md5, sha1, sha256 } from 'hash-wasm'; import pRetry from 'p-retry'; @@ -75,7 +74,7 @@ export type UserLibrary = ReturnType; export const createUserLibrary = (queries: Queries) => { const { pool, - roles: { findRolesByRoleNames, findRoleByRoleName, findRolesByRoleIds }, + roles: { findDefaultRoles, findRolesByRoleNames, findRoleByRoleName, findRolesByRoleIds }, users: { hasUser, hasUserWithEmail, @@ -107,10 +106,13 @@ export const createUserLibrary = (queries: Queries) => { ); const insertUser = async (data: OmitAutoSetFields, additionalRoleNames: string[]) => { - const roleNames = deduplicate([...EnvSet.values.userDefaultRoleNames, ...additionalRoleNames]); - const roles = await findRolesByRoleNames(roleNames); + const roleNames = [...EnvSet.values.userDefaultRoleNames, ...additionalRoleNames]; + const [parameterRoles, defaultRoles] = await Promise.all([ + findRolesByRoleNames(roleNames), + findDefaultRoles(RoleType.User), + ]); - assertThat(roles.length === roleNames.length, 'role.default_role_missing'); + assertThat(parameterRoles.length === roleNames.length, 'role.default_role_missing'); return pool.transaction(async (connection) => { const insertUserQuery = buildInsertIntoWithPool(connection)(Users, { @@ -118,6 +120,7 @@ export const createUserLibrary = (queries: Queries) => { }); const user = await insertUserQuery(data); + const roles = deduplicateByKey([...parameterRoles, ...defaultRoles], 'id'); if (roles.length > 0) { const { insertUsersRoles } = createUsersRolesQueries(connection); diff --git a/packages/core/src/main.ts b/packages/core/src/main.ts index 3ead694a2..1092b7726 100644 --- a/packages/core/src/main.ts +++ b/packages/core/src/main.ts @@ -6,11 +6,11 @@ import Koa from 'koa'; import initApp from './app/init.js'; import { redisCache } from './caches/index.js'; -import { checkAlterationState } from './env-set/check-alteration-state.js'; import { EnvSet } from './env-set/index.js'; +import { checkPreconditions } from './env-set/preconditions.js'; import initI18n from './i18n/init.js'; import SystemContext from './tenants/SystemContext.js'; -import { checkRowLevelSecurity, tenantPool } from './tenants/index.js'; +import { tenantPool } from './tenants/index.js'; import { loadConnectorFactories } from './utils/connectors/index.js'; const consoleLog = new ConsoleLog(chalk.magenta('index')); @@ -29,8 +29,7 @@ try { initI18n(), redisCache.connect(), loadConnectorFactories(), - checkRowLevelSecurity(sharedAdminPool), - checkAlterationState(sharedAdminPool), + checkPreconditions(sharedAdminPool), SystemContext.shared.loadProviderConfigs(sharedAdminPool), ]); diff --git a/packages/core/src/queries/roles.test.ts b/packages/core/src/queries/roles.test.ts index 86e66de43..4ddf82382 100644 --- a/packages/core/src/queries/roles.test.ts +++ b/packages/core/src/queries/roles.test.ts @@ -135,7 +135,7 @@ describe('roles query', () => { const keys = excludeAutoSetFields(Roles.fieldKeys); const expectSql = ` - insert into "roles" ("id", "name", "description", "type") + insert into "roles" ("id", "name", "description", "type", "is_default") values (${keys.map((_, index) => `$${index + 1}`).join(', ')}) returning * `; diff --git a/packages/core/src/queries/roles.ts b/packages/core/src/queries/roles.ts index ac6e076be..38f85b492 100644 --- a/packages/core/src/queries/roles.ts +++ b/packages/core/src/queries/roles.ts @@ -95,6 +95,16 @@ export const createRolesQueries = (pool: CommonQueryMethods) => { `) : []; + const findDefaultRoles = async (forType: RoleType.User) => + pool.any( + sql` + select ${sql.join(Object.values(fields), sql`, `)} + from ${table} + where ${fields.isDefault} is true + and ${fields.type}=${forType} + ` + ); + const findRolesByRoleNames = async (roleNames: string[]) => roleNames.length > 0 ? pool.any(sql` @@ -147,6 +157,7 @@ export const createRolesQueries = (pool: CommonQueryMethods) => { countRoles, findRoles, findRolesByRoleIds, + findDefaultRoles, findRolesByRoleNames, findRoleByRoleName, insertRoles, diff --git a/packages/core/src/routes/admin-user/basics.test.ts b/packages/core/src/routes/admin-user/basics.test.ts index 68f1d29e3..9ca8a668d 100644 --- a/packages/core/src/routes/admin-user/basics.test.ts +++ b/packages/core/src/routes/admin-user/basics.test.ts @@ -51,6 +51,7 @@ const mockedQueries = { name: 'admin', description: 'none', type: RoleType.User, + isDefault: false, }, ] ), diff --git a/packages/core/src/routes/admin-user/search.test.ts b/packages/core/src/routes/admin-user/search.test.ts index 6ea88f740..8ff623024 100644 --- a/packages/core/src/routes/admin-user/search.test.ts +++ b/packages/core/src/routes/admin-user/search.test.ts @@ -39,6 +39,7 @@ const mockedQueries = { name: 'admin', description: 'none', type: RoleType.User, + isDefault: false, }, ] ), diff --git a/packages/core/src/routes/role.test.ts b/packages/core/src/routes/role.test.ts index 933e6b77f..1b79812e3 100644 --- a/packages/core/src/routes/role.test.ts +++ b/packages/core/src/routes/role.test.ts @@ -18,6 +18,7 @@ const roles = { findRoleByRoleName: jest.fn(async (): Promise => null), insertRole: jest.fn(async (data) => ({ type: mockAdminUserRole.type, + isDefault: false, ...data, id: mockAdminUserRole.id, tenantId: 'fake_tenant', diff --git a/packages/core/src/routes/role.ts b/packages/core/src/routes/role.ts index 1cbcfdf8e..521d84eb4 100644 --- a/packages/core/src/routes/role.ts +++ b/packages/core/src/routes/role.ts @@ -201,7 +201,7 @@ export default function roleRoutes( router.patch( '/roles/:id', koaGuard({ - body: Roles.createGuard.pick({ name: true, description: true }).partial(), + body: Roles.createGuard.pick({ name: true, description: true, isDefault: true }).partial(), params: object({ id: string().min(1) }), response: Roles.guard, status: [200, 404, 422], diff --git a/packages/core/src/tenants/utils.ts b/packages/core/src/tenants/utils.ts index f5491da36..856ee332d 100644 --- a/packages/core/src/tenants/utils.ts +++ b/packages/core/src/tenants/utils.ts @@ -1,8 +1,5 @@ -import { ServiceLogs, Systems } from '@logto/schemas'; import { Tenants } from '@logto/schemas/models'; -import { isKeyInObject } from '@logto/shared'; -import { conditional, conditionalString } from '@silverhand/essentials'; -import type { CommonQueryMethods } from '@silverhand/slonik'; +import { conditional } from '@silverhand/essentials'; import { parseDsn, sql, stringifyDsn } from '@silverhand/slonik'; import { z } from 'zod'; @@ -46,26 +43,3 @@ export const getTenantDatabaseDsn = async (tenantId: string) => { password: conditional(typeof password === 'string' && password), }); }; - -export const checkRowLevelSecurity = async (client: CommonQueryMethods) => { - const { rows } = await client.query(sql` - select tablename - from pg_catalog.pg_tables - where schemaname = current_schema() - and rowsecurity=false - `); - - const rlsDisabled = rows.filter( - ({ tablename }) => tablename !== Systems.table && tablename !== ServiceLogs.table - ); - - if (rlsDisabled.length > 0) { - throw new Error( - 'Row-level security has to be enforced on EVERY business table when starting Logto.\n' + - `Found following table(s) without RLS: ${rlsDisabled - .map((row) => conditionalString(isKeyInObject(row, 'tablename') && String(row.tablename))) - .join(', ')}\n\n` + - 'Did you forget to run `npm cli db alteration deploy`?' - ); - } -}; diff --git a/packages/demo-app/src/App.tsx b/packages/demo-app/src/App.tsx index 50c2b8055..b5e4b8768 100644 --- a/packages/demo-app/src/App.tsx +++ b/packages/demo-app/src/App.tsx @@ -110,7 +110,7 @@ const App = () => { config={{ endpoint: window.location.origin, appId: demoAppApplicationId, - prompt: Prompt.Login, + prompt: [Prompt.Login, Prompt.Consent], scopes: [UserScope.Organizations, UserScope.OrganizationRoles], }} > diff --git a/packages/experience/package.json b/packages/experience/package.json index 72a4d5455..01fcc2fd1 100644 --- a/packages/experience/package.json +++ b/packages/experience/package.json @@ -36,7 +36,7 @@ "@react-spring/web": "^9.6.1", "@silverhand/eslint-config": "6.0.1", "@silverhand/eslint-config-react": "6.0.2", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "@silverhand/ts-config": "6.0.0", "@silverhand/ts-config-react": "6.0.0", "@simplewebauthn/browser": "^10.0.0", diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 606ed82df..bbb9d55ac 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -31,7 +31,7 @@ "@logto/schemas": "workspace:^1.16.0", "@logto/shared": "workspace:^3.1.1", "@silverhand/eslint-config": "6.0.1", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "@silverhand/ts-config": "6.0.0", "@types/jest": "^29.4.0", "@types/node": "^20.9.5", diff --git a/packages/integration-tests/src/api/role.ts b/packages/integration-tests/src/api/role.ts index 6b6c467f0..569ef3ba7 100644 --- a/packages/integration-tests/src/api/role.ts +++ b/packages/integration-tests/src/api/role.ts @@ -16,11 +16,13 @@ export const createRole = async ({ name, description, type, + isDefault, scopeIds, }: { name?: string; description?: string; type?: RoleType; + isDefault?: boolean; scopeIds?: string[]; }) => authedAdminApi @@ -28,6 +30,7 @@ export const createRole = async ({ json: { name: name ?? generateRoleName(), description: description ?? generateRoleName(), + isDefault, type: type ?? RoleType.User, scopeIds, }, diff --git a/packages/integration-tests/src/client/index.ts b/packages/integration-tests/src/client/index.ts index d487f71e2..d10a9bdd7 100644 --- a/packages/integration-tests/src/client/index.ts +++ b/packages/integration-tests/src/client/index.ts @@ -148,6 +148,10 @@ export default class MockClient { return this.logto.getAccessToken(resource, organizationId); } + public async getAccessTokenClaims(resource?: string) { + return this.logto.getAccessTokenClaims(resource); + } + public async getRefreshToken(): Promise> { return this.logto.getRefreshToken(); } diff --git a/packages/integration-tests/src/tests/api/role.is-default.test.ts b/packages/integration-tests/src/tests/api/role.is-default.test.ts new file mode 100644 index 000000000..82354e183 --- /dev/null +++ b/packages/integration-tests/src/tests/api/role.is-default.test.ts @@ -0,0 +1,107 @@ +import { + InteractionEvent, + SignInIdentifier, + type Resource, + type Role, + type Scope, +} from '@logto/schemas'; +import { noop } from '@silverhand/essentials'; + +import { createUser, deleteUser, getUserRoles } from '#src/api/admin-user.js'; +import { putInteraction, updateSignInExperience } from '#src/api/index.js'; +import { createResource, deleteResource } from '#src/api/resource.js'; +import { assignScopesToRole, createRole, deleteRole } from '#src/api/role.js'; +import { createScope } from '#src/api/scope.js'; +import { initClient, processSession } from '#src/helpers/client.js'; +import { enableAllPasswordSignInMethods } from '#src/helpers/sign-in-experience.js'; +import { generatePassword, generateUsername } from '#src/utils.js'; + +class TestContext { + resource?: Resource; + scopes: Scope[] = []; + roles: Role[] = []; +} + +describe('default roles', () => { + const context = new TestContext(); + + beforeAll(async () => { + await enableAllPasswordSignInMethods({ + identifiers: [SignInIdentifier.Username], + password: true, + verify: false, + }); + await updateSignInExperience({ passwordPolicy: { length: { max: 256 } } }); + + // Set up a resource with two scopes and two default roles, each with one of the scopes + const resource = await createResource(); + const scopes = await Promise.all([createScope(resource.id), createScope(resource.id)]); + const roles = await Promise.all([ + createRole({ isDefault: true }), + createRole({ isDefault: true }), + ]); + await Promise.all([ + assignScopesToRole([scopes[0].id], roles[0].id), + assignScopesToRole([scopes[1].id], roles[1].id), + ]); + + /* eslint-disable @silverhand/fp/no-mutation */ + context.resource = resource; + context.scopes = scopes; + context.roles = roles; + /* eslint-enable @silverhand/fp/no-mutation */ + }); + + afterAll(async () => { + await Promise.all( + [ + ...context.roles.map(async (role) => deleteRole(role.id)), + deleteResource(context.resource!.id), + ].map(async (promise) => promise.catch(noop)) + ); + }); + + it('should automatically assign default roles to new users created via Management API', async () => { + // Create a new user + const user = await createUser(); + + // Check that the user has the default roles + const userRoles = await getUserRoles(user.id); + expect(userRoles.map((role) => role.id)).toEqual( + expect.arrayContaining(context.roles.map((role) => role.id)) + ); + + await deleteUser(user.id); + }); + + it('should automatically assign default roles to new users via sign-in experience', async () => { + const username = generateUsername(); + const password = generatePassword(); + + // Process the sign-in flow + const client = await initClient({ + resources: [context.resource!.indicator], + scopes: context.scopes.map((scope) => scope.name), + }); + await client.successSend(putInteraction, { + event: InteractionEvent.Register, + profile: { username, password }, + }); + const { redirectTo } = await client.submitInteraction(); + const userId = await processSession(client, redirectTo); + + // Check claims and roles + const claims = await client.getAccessTokenClaims(context.resource!.indicator); + expect(claims.scope?.split(' ')).toEqual( + expect.arrayContaining(context.scopes.map((scope) => scope.name)) + ); + + const roles = await getUserRoles(userId); + expect(roles.map((role) => role.id)).toEqual( + expect.arrayContaining(context.roles.map((role) => role.id)) + ); + + // Clean up + await deleteUser(userId); + }); +}); diff --git a/packages/phrases-experience/package.json b/packages/phrases-experience/package.json index c88ac7e1f..1b9d08365 100644 --- a/packages/phrases-experience/package.json +++ b/packages/phrases-experience/package.json @@ -35,7 +35,7 @@ "dependencies": { "@logto/core-kit": "workspace:^2.4.0", "@logto/language-kit": "workspace:^1.1.0", - "@silverhand/essentials": "^2.9.0" + "@silverhand/essentials": "^2.9.1" }, "peerDependencies": { "zod": "^3.22.4" diff --git a/packages/phrases/package.json b/packages/phrases/package.json index 6e867bc6f..7c28ea1b3 100644 --- a/packages/phrases/package.json +++ b/packages/phrases/package.json @@ -34,7 +34,7 @@ }, "dependencies": { "@logto/language-kit": "workspace:^1.1.0", - "@silverhand/essentials": "^2.9.0" + "@silverhand/essentials": "^2.9.1" }, "peerDependencies": { "zod": "^3.22.4" diff --git a/packages/phrases/src/index.ts b/packages/phrases/src/index.ts index 5e5e142e4..96adfb509 100644 --- a/packages/phrases/src/index.ts +++ b/packages/phrases/src/index.ts @@ -1,6 +1,6 @@ import type { LanguageTag } from '@logto/language-kit'; import { languages, fallback } from '@logto/language-kit'; -import type { NormalizeKeyPaths } from '@silverhand/essentials'; +import type { DeepPartial, NormalizeKeyPaths } from '@silverhand/essentials'; import { z } from 'zod'; import de from './locales/de/index.js'; @@ -22,6 +22,7 @@ import type { LocalePhrase } from './types.js'; export type { LocalePhrase } from './types.js'; +export type DefaultLocale = 'en'; export type I18nKey = NormalizeKeyPaths; export const builtInLanguages = [ @@ -63,7 +64,12 @@ export const getDefaultLanguageTag = (languages: string): LanguageTag => export const isBuiltInLanguageTag = (language: string): language is BuiltInLanguageTag => builtInLanguageTagGuard.safeParse(language).success; -export type Resource = Record; +export type Resource = Record< + Exclude, + DeepPartial +> & { + [key in DefaultLocale]: LocalePhrase; +}; const resource: Resource = { de, diff --git a/packages/phrases/src/locales/de/index.ts b/packages/phrases/src/locales/de/index.ts index f470681cb..453094c82 100644 --- a/packages/phrases/src/locales/de/index.ts +++ b/packages/phrases/src/locales/de/index.ts @@ -1,3 +1,5 @@ +import { type DeepPartial } from '@silverhand/essentials'; + import type { LocalePhrase } from '../../types.js'; import errors from './errors/index.js'; @@ -6,6 +8,6 @@ import translation from './translation/index.js'; const de = { translation, errors, -} satisfies LocalePhrase; +} satisfies DeepPartial; export default Object.freeze(de); diff --git a/packages/phrases/src/locales/en/translation/admin-console/role-details.ts b/packages/phrases/src/locales/en/translation/admin-console/role-details.ts index ed2c7a5e1..022f552e2 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/role-details.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/role-details.ts @@ -13,6 +13,9 @@ const role_details = { 'Roles are a grouping of permissions that can be assigned to users. They also provide a way to aggregate permissions defined for different APIs, making it more efficient to add, remove, or adjust permissions compared to assigning them individually to users.', field_name: 'Name', field_description: 'Description', + field_is_default: 'Default role', + field_is_default_description: + 'Set this role as a default role for new users. Multiple default roles can be set. This will also affect the default roles for users created via Management API.', type_m2m_role_tag: 'Machine-to-machine app role', type_user_role_tag: 'User role', permission: { diff --git a/packages/phrases/src/locales/es/index.ts b/packages/phrases/src/locales/es/index.ts index 8b3d7bf33..e7134e1f7 100644 --- a/packages/phrases/src/locales/es/index.ts +++ b/packages/phrases/src/locales/es/index.ts @@ -1,3 +1,5 @@ +import { type DeepPartial } from '@silverhand/essentials'; + import type { LocalePhrase } from '../../types.js'; import errors from './errors/index.js'; @@ -6,6 +8,6 @@ import translation from './translation/index.js'; const es = { translation, errors, -} satisfies LocalePhrase; +} satisfies DeepPartial; export default Object.freeze(es); diff --git a/packages/phrases/src/locales/fr/index.ts b/packages/phrases/src/locales/fr/index.ts index 5f235fa3f..93bbce9c0 100644 --- a/packages/phrases/src/locales/fr/index.ts +++ b/packages/phrases/src/locales/fr/index.ts @@ -1,3 +1,5 @@ +import { type DeepPartial } from '@silverhand/essentials'; + import type { LocalePhrase } from '../../types.js'; import errors from './errors/index.js'; @@ -6,6 +8,6 @@ import translation from './translation/index.js'; const fr = { translation, errors, -} satisfies LocalePhrase; +} satisfies DeepPartial; export default Object.freeze(fr); diff --git a/packages/phrases/src/locales/it/index.ts b/packages/phrases/src/locales/it/index.ts index 9e8f98453..41d5a2dc5 100644 --- a/packages/phrases/src/locales/it/index.ts +++ b/packages/phrases/src/locales/it/index.ts @@ -1,3 +1,5 @@ +import { type DeepPartial } from '@silverhand/essentials'; + import type { LocalePhrase } from '../../types.js'; import errors from './errors/index.js'; @@ -6,6 +8,6 @@ import translation from './translation/index.js'; const it = { translation, errors, -} satisfies LocalePhrase; +} satisfies DeepPartial; export default Object.freeze(it); diff --git a/packages/phrases/src/locales/ja/index.ts b/packages/phrases/src/locales/ja/index.ts index 5f5f635ec..4af9e3e08 100644 --- a/packages/phrases/src/locales/ja/index.ts +++ b/packages/phrases/src/locales/ja/index.ts @@ -1,3 +1,5 @@ +import { type DeepPartial } from '@silverhand/essentials'; + import type { LocalePhrase } from '../../types.js'; import errors from './errors/index.js'; @@ -6,6 +8,6 @@ import translation from './translation/index.js'; const ja = { translation, errors, -} satisfies LocalePhrase; +} satisfies DeepPartial; export default Object.freeze(ja); diff --git a/packages/phrases/src/locales/ko/index.ts b/packages/phrases/src/locales/ko/index.ts index 71cede199..e5d9dfbd4 100644 --- a/packages/phrases/src/locales/ko/index.ts +++ b/packages/phrases/src/locales/ko/index.ts @@ -1,3 +1,5 @@ +import { type DeepPartial } from '@silverhand/essentials'; + import type { LocalePhrase } from '../../types.js'; import errors from './errors/index.js'; @@ -6,6 +8,6 @@ import translation from './translation/index.js'; const ko = { translation, errors, -} satisfies LocalePhrase; +} satisfies DeepPartial; export default Object.freeze(ko); diff --git a/packages/phrases/src/locales/pl-pl/index.ts b/packages/phrases/src/locales/pl-pl/index.ts index 821846144..510e4f529 100644 --- a/packages/phrases/src/locales/pl-pl/index.ts +++ b/packages/phrases/src/locales/pl-pl/index.ts @@ -1,3 +1,5 @@ +import { type DeepPartial } from '@silverhand/essentials'; + import type { LocalePhrase } from '../../types.js'; import errors from './errors/index.js'; @@ -6,6 +8,6 @@ import translation from './translation/index.js'; const pl_pl = { translation, errors, -} satisfies LocalePhrase; +} satisfies DeepPartial; export default Object.freeze(pl_pl); diff --git a/packages/phrases/src/locales/pt-br/index.ts b/packages/phrases/src/locales/pt-br/index.ts index ecb6d55bf..2a8b7d5dc 100644 --- a/packages/phrases/src/locales/pt-br/index.ts +++ b/packages/phrases/src/locales/pt-br/index.ts @@ -1,3 +1,5 @@ +import { type DeepPartial } from '@silverhand/essentials'; + import type { LocalePhrase } from '../../types.js'; import errors from './errors/index.js'; @@ -6,6 +8,6 @@ import translation from './translation/index.js'; const pt_br = { translation, errors, -} satisfies LocalePhrase; +} satisfies DeepPartial; export default Object.freeze(pt_br); diff --git a/packages/phrases/src/locales/pt-pt/index.ts b/packages/phrases/src/locales/pt-pt/index.ts index 01a0f6ff4..2647dbda5 100644 --- a/packages/phrases/src/locales/pt-pt/index.ts +++ b/packages/phrases/src/locales/pt-pt/index.ts @@ -1,3 +1,5 @@ +import { type DeepPartial } from '@silverhand/essentials'; + import type { LocalePhrase } from '../../types.js'; import errors from './errors/index.js'; @@ -6,6 +8,6 @@ import translation from './translation/index.js'; const pt_pt = { translation, errors, -} satisfies LocalePhrase; +} satisfies DeepPartial; export default Object.freeze(pt_pt); diff --git a/packages/phrases/src/locales/ru/index.ts b/packages/phrases/src/locales/ru/index.ts index feaa29bb6..ec089c9a2 100644 --- a/packages/phrases/src/locales/ru/index.ts +++ b/packages/phrases/src/locales/ru/index.ts @@ -1,3 +1,5 @@ +import { type DeepPartial } from '@silverhand/essentials'; + import type { LocalePhrase } from '../../types.js'; import errors from './errors/index.js'; @@ -6,6 +8,6 @@ import translation from './translation/index.js'; const ru = { translation, errors, -} satisfies LocalePhrase; +} satisfies DeepPartial; export default Object.freeze(ru); diff --git a/packages/phrases/src/locales/tr-tr/index.ts b/packages/phrases/src/locales/tr-tr/index.ts index 2ddad2d50..2cfb82169 100644 --- a/packages/phrases/src/locales/tr-tr/index.ts +++ b/packages/phrases/src/locales/tr-tr/index.ts @@ -1,3 +1,5 @@ +import { type DeepPartial } from '@silverhand/essentials'; + import type { LocalePhrase } from '../../types.js'; import errors from './errors/index.js'; @@ -6,6 +8,6 @@ import translation from './translation/index.js'; const tr_tr = { translation, errors, -} satisfies LocalePhrase; +} satisfies DeepPartial; export default Object.freeze(tr_tr); diff --git a/packages/phrases/src/locales/zh-cn/index.ts b/packages/phrases/src/locales/zh-cn/index.ts index 851447a09..93e09a51e 100644 --- a/packages/phrases/src/locales/zh-cn/index.ts +++ b/packages/phrases/src/locales/zh-cn/index.ts @@ -1,3 +1,5 @@ +import { type DeepPartial } from '@silverhand/essentials'; + import type { LocalePhrase } from '../../types.js'; import errors from './errors/index.js'; @@ -6,6 +8,6 @@ import translation from './translation/index.js'; const zh_cn = { translation, errors, -} satisfies LocalePhrase; +} satisfies DeepPartial; export default Object.freeze(zh_cn); diff --git a/packages/phrases/src/locales/zh-hk/index.ts b/packages/phrases/src/locales/zh-hk/index.ts index 6b35bd4f4..9b712555e 100644 --- a/packages/phrases/src/locales/zh-hk/index.ts +++ b/packages/phrases/src/locales/zh-hk/index.ts @@ -1,3 +1,5 @@ +import { type DeepPartial } from '@silverhand/essentials'; + import type { LocalePhrase } from '../../types.js'; import errors from './errors/index.js'; @@ -6,6 +8,6 @@ import translation from './translation/index.js'; const zh_hk = { translation, errors, -} satisfies LocalePhrase; +} satisfies DeepPartial; export default Object.freeze(zh_hk); diff --git a/packages/phrases/src/locales/zh-tw/index.ts b/packages/phrases/src/locales/zh-tw/index.ts index 6a9a88378..f0fc7f0a0 100644 --- a/packages/phrases/src/locales/zh-tw/index.ts +++ b/packages/phrases/src/locales/zh-tw/index.ts @@ -1,3 +1,5 @@ +import { type DeepPartial } from '@silverhand/essentials'; + import type { LocalePhrase } from '../../types.js'; import errors from './errors/index.js'; @@ -6,6 +8,6 @@ import translation from './translation/index.js'; const zh_tw = { translation, errors, -} satisfies LocalePhrase; +} satisfies DeepPartial; export default Object.freeze(zh_tw); diff --git a/packages/schemas/alterations/next-1715826336-add-default-user-role-config.ts b/packages/schemas/alterations/next-1715826336-add-default-user-role-config.ts new file mode 100644 index 000000000..41d8e9cfb --- /dev/null +++ b/packages/schemas/alterations/next-1715826336-add-default-user-role-config.ts @@ -0,0 +1,18 @@ +import { sql } from '@silverhand/slonik'; + +import type { AlterationScript } from '../lib/types/alteration.js'; + +const alteration: AlterationScript = { + up: async (pool) => { + await pool.query(sql` + alter table roles add column is_default boolean not null default false; + `); + }, + down: async (pool) => { + await pool.query(sql` + alter table roles drop column is_default; + `); + }, +}; + +export default alteration; diff --git a/packages/schemas/package.json b/packages/schemas/package.json index 481cfb4f5..bd2a8a1fa 100644 --- a/packages/schemas/package.json +++ b/packages/schemas/package.json @@ -40,7 +40,7 @@ }, "devDependencies": { "@silverhand/eslint-config": "6.0.1", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "@silverhand/slonik": "31.0.0-beta.2", "@silverhand/ts-config": "6.0.0", "@types/inquirer": "^9.0.0", diff --git a/packages/schemas/src/seeds/cloud-api.ts b/packages/schemas/src/seeds/cloud-api.ts index 30782d563..f98474c54 100644 --- a/packages/schemas/src/seeds/cloud-api.ts +++ b/packages/schemas/src/seeds/cloud-api.ts @@ -85,4 +85,5 @@ export const createTenantApplicationRole = (): Readonly => ({ description: 'The role for M2M applications that represent a user tenant and send requests to Logto Cloud.', type: RoleType.MachineToMachine, + isDefault: false, }); diff --git a/packages/schemas/src/types/mapi-proxy.ts b/packages/schemas/src/types/mapi-proxy.ts index 1c7135cb0..e6651ac2b 100644 --- a/packages/schemas/src/types/mapi-proxy.ts +++ b/packages/schemas/src/types/mapi-proxy.ts @@ -36,6 +36,7 @@ export const getMapiProxyRole = (tenantId: string): Readonly => name: `machine:mapi:${tenantId}`, description: `Machine-to-machine role for accessing Management API of tenant '${tenantId}'.`, type: RoleType.MachineToMachine, + isDefault: false, }); /** diff --git a/packages/schemas/tables/roles.sql b/packages/schemas/tables/roles.sql index a87add248..269dfa86d 100644 --- a/packages/schemas/tables/roles.sql +++ b/packages/schemas/tables/roles.sql @@ -9,6 +9,8 @@ create table roles ( name varchar(128) not null, description varchar(128) not null, type role_type not null default 'User', + /** If the role is the default role for a new user. Should be ignored for `MachineToMachine` roles. */ + is_default boolean not null default false, primary key (id), constraint roles__name unique (tenant_id, name) diff --git a/packages/shared/package.json b/packages/shared/package.json index 431bbbd24..5f8035307 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -59,7 +59,7 @@ }, "prettier": "@silverhand/eslint-config/.prettierrc", "dependencies": { - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "chalk": "^5.0.0", "find-up": "^7.0.0", "libphonenumber-js": "^1.9.49", diff --git a/packages/shared/src/node/env/GlobalValues.ts b/packages/shared/src/node/env/GlobalValues.ts index 34fa359e0..4ad50bb34 100644 --- a/packages/shared/src/node/env/GlobalValues.ts +++ b/packages/shared/src/node/env/GlobalValues.ts @@ -94,6 +94,7 @@ export default class GlobalValues { // eslint-disable-next-line unicorn/consistent-function-scoping public readonly databaseUrl = tryThat(() => assertEnv('DB_URL'), throwErrorWithDsnMessage); public readonly developmentTenantId = getEnv('DEVELOPMENT_TENANT_ID'); + /** @deprecated Use the built-in user default role configuration (`Roles.isDefault`) instead. */ public readonly userDefaultRoleNames = getEnvAsStringArray('USER_DEFAULT_ROLE_NAMES'); public readonly developmentUserId = getEnv('DEVELOPMENT_USER_ID'); public readonly trustProxyHeader = yes(getEnv('TRUST_PROXY_HEADER')); diff --git a/packages/toolkit/connector-kit/package.json b/packages/toolkit/connector-kit/package.json index 8dc10d92d..41d9cb8d8 100644 --- a/packages/toolkit/connector-kit/package.json +++ b/packages/toolkit/connector-kit/package.json @@ -35,7 +35,7 @@ }, "dependencies": { "@logto/language-kit": "workspace:^1.1.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "@withtyped/client": "^0.8.7", "@withtyped/server": "^0.13.6" }, diff --git a/packages/toolkit/core-kit/package.json b/packages/toolkit/core-kit/package.json index 034e3b079..91307cbf9 100644 --- a/packages/toolkit/core-kit/package.json +++ b/packages/toolkit/core-kit/package.json @@ -47,7 +47,7 @@ "dependencies": { "@logto/language-kit": "workspace:^1.1.0", "@logto/shared": "workspace:^3.1.0", - "@silverhand/essentials": "^2.9.0", + "@silverhand/essentials": "^2.9.1", "color": "^4.2.3" }, "optionalDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ebb2531a3..2328e08bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,8 +43,8 @@ importers: packages/app-insights: dependencies: '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 applicationinsights: specifier: ^2.9.5 version: 2.9.5 @@ -104,8 +104,8 @@ importers: specifier: workspace:^3.1.1 version: link:../shared '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 '@silverhand/slonik': specifier: 31.0.0-beta.2 version: 31.0.0-beta.2 @@ -216,8 +216,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 dayjs: specifier: ^1.10.5 version: 1.11.6 @@ -298,8 +298,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 dayjs: specifier: ^1.10.5 version: 1.11.6 @@ -380,8 +380,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -453,8 +453,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -529,8 +529,8 @@ importers: specifier: workspace:^3.1.0 version: link:../../shared '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -611,8 +611,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -687,8 +687,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -760,8 +760,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -833,8 +833,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -906,8 +906,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -979,8 +979,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 ky: specifier: ^1.2.3 version: 1.2.3 @@ -1055,8 +1055,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -1131,8 +1131,8 @@ importers: specifier: workspace:^1.3.0 version: link:../connector-oauth2 '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 ky: specifier: ^1.2.3 version: 1.2.3 @@ -1201,8 +1201,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -1274,8 +1274,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -1350,8 +1350,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -1423,8 +1423,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -1496,8 +1496,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -1569,8 +1569,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -1642,8 +1642,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -1715,8 +1715,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -1788,8 +1788,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -1861,8 +1861,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -1937,8 +1937,8 @@ importers: specifier: workspace:^3.1.1 version: link:../../shared '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 jose: specifier: ^5.0.0 version: 5.2.2 @@ -2022,8 +2022,8 @@ importers: specifier: workspace:^3.1.1 version: link:../../shared '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 jose: specifier: ^5.0.0 version: 5.0.1 @@ -2101,8 +2101,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 fast-xml-parser: specifier: ^4.3.6 version: 4.3.6 @@ -2180,8 +2180,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -2253,8 +2253,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -2326,8 +2326,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -2405,8 +2405,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -2478,8 +2478,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -2551,8 +2551,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -2624,8 +2624,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -2697,8 +2697,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 got: specifier: ^14.0.0 version: 14.0.0 @@ -2830,8 +2830,8 @@ importers: specifier: 6.0.2 version: 6.0.2(eslint@8.57.0)(postcss@8.4.31)(prettier@3.0.0)(stylelint@15.11.0(typescript@5.3.3))(typescript@5.3.3) '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 '@silverhand/ts-config': specifier: 6.0.0 version: 6.0.0(typescript@5.3.3) @@ -3127,8 +3127,8 @@ importers: specifier: workspace:^3.1.1 version: link:../shared '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 '@silverhand/slonik': specifier: 31.0.0-beta.2 version: 31.0.0-beta.2 @@ -3512,8 +3512,8 @@ importers: specifier: 6.0.2 version: 6.0.2(eslint@8.57.0)(postcss@8.4.31)(prettier@3.0.0)(stylelint@15.11.0(typescript@5.3.3))(typescript@5.3.3) '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 '@silverhand/ts-config': specifier: 6.0.0 version: 6.0.0(typescript@5.3.3) @@ -3714,8 +3714,8 @@ importers: specifier: 6.0.1 version: 6.0.1(eslint@8.57.0)(prettier@3.0.0)(typescript@5.3.3) '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 '@silverhand/ts-config': specifier: 6.0.0 version: 6.0.0(typescript@5.3.3) @@ -3777,8 +3777,8 @@ importers: specifier: workspace:^1.1.0 version: link:../toolkit/language-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 zod: specifier: ^3.22.4 version: 3.22.4 @@ -3811,8 +3811,8 @@ importers: specifier: workspace:^1.1.0 version: link:../toolkit/language-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 zod: specifier: ^3.22.4 version: 3.22.4 @@ -3870,8 +3870,8 @@ importers: specifier: 6.0.1 version: 6.0.1(eslint@8.57.0)(prettier@3.0.0)(typescript@5.3.3) '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 '@silverhand/slonik': specifier: 31.0.0-beta.2 version: 31.0.0-beta.2 @@ -3921,8 +3921,8 @@ importers: packages/shared: dependencies: '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 chalk: specifier: ^5.0.0 version: 5.1.2 @@ -3973,8 +3973,8 @@ importers: specifier: workspace:^1.1.0 version: link:../language-kit '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 '@withtyped/client': specifier: ^0.8.7 version: 0.8.7(zod@3.22.4) @@ -4023,8 +4023,8 @@ importers: specifier: workspace:^3.1.0 version: link:../../shared '@silverhand/essentials': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 color: specifier: ^4.2.3 version: 4.2.3 @@ -5890,9 +5890,9 @@ packages: peerDependencies: eslint: ^8.1.0 - '@silverhand/essentials@2.9.0': - resolution: {integrity: sha512-n9mSO/gsLj0GRFXBRNhaQLRK6qbn6pBnKjMQdFwweKgT12ODBXpgkpXohpOBqSofnoaCQWqiDAT6xpCy/5dMIg==} - engines: {node: ^18.12.0 || ^20.9.0, pnpm: ^8.0.0} + '@silverhand/essentials@2.9.1': + resolution: {integrity: sha512-rsql/ZxXMqVvt7ySDHd7xjCog4oCYg+dexxlj3veolajwjKiy/08ZtFyEtFjSEAaKbXwkWZ4TDtiNSxb7HS0yA==} + engines: {node: ^18.12.0 || ^20.9.0, pnpm: ^9.0.0} '@silverhand/slonik@31.0.0-beta.2': resolution: {integrity: sha512-4IM57Er5We8+hT8IY9z5La1JAGNRFZ63tp3N0XYUYTNV9fLfUXF78yT+PoW4arnf4qc+4n498bMmKgFmt/mo9Q==} @@ -14691,44 +14691,44 @@ snapshots: '@logto/affiliate@0.1.0': dependencies: - '@silverhand/essentials': 2.9.0 + '@silverhand/essentials': 2.9.1 tiny-cookie: 2.4.1 '@logto/browser@2.2.10': dependencies: '@logto/client': 2.6.6 - '@silverhand/essentials': 2.9.0 + '@silverhand/essentials': 2.9.1 js-base64: 3.7.5 '@logto/client@2.6.6': dependencies: '@logto/js': 4.1.1 - '@silverhand/essentials': 2.9.0 + '@silverhand/essentials': 2.9.1 camelcase-keys: 7.0.2 jose: 5.2.2 '@logto/cloud@0.2.5-e5d8200(zod@3.22.4)': dependencies: - '@silverhand/essentials': 2.9.0 + '@silverhand/essentials': 2.9.1 '@withtyped/server': 0.13.6(zod@3.22.4) transitivePeerDependencies: - zod '@logto/js@4.1.1': dependencies: - '@silverhand/essentials': 2.9.0 + '@silverhand/essentials': 2.9.1 camelcase-keys: 7.0.2 '@logto/node@2.4.7': dependencies: '@logto/client': 2.6.6 - '@silverhand/essentials': 2.9.0 + '@silverhand/essentials': 2.9.1 js-base64: 3.7.5 '@logto/react@3.0.8(react@18.2.0)': dependencies: '@logto/browser': 2.2.10 - '@silverhand/essentials': 2.9.0 + '@silverhand/essentials': 2.9.1 react: 18.2.0 '@manypkg/find-root@1.1.0': @@ -15801,7 +15801,7 @@ snapshots: import-modules: 2.1.0 lodash: 4.17.21 - '@silverhand/essentials@2.9.0': {} + '@silverhand/essentials@2.9.1': {} '@silverhand/slonik@31.0.0-beta.2': dependencies: @@ -16965,7 +16965,7 @@ snapshots: '@withtyped/server@0.13.6(zod@3.22.4)': dependencies: - '@silverhand/essentials': 2.9.0 + '@silverhand/essentials': 2.9.1 '@withtyped/shared': 0.2.2 nanoid: 4.0.2 zod: 3.22.4