0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

feat: default user role (#5872)

* feat: default user role

* chore: add tests and changeset

* refactor: show warning for deprecated env

* chore: fix tests
This commit is contained in:
Gao Sun 2024-05-17 16:02:05 +08:00 committed by GitHub
parent 13bfdbd638
commit 76fd33b7ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
89 changed files with 467 additions and 238 deletions

View file

@ -0,0 +1,8 @@
---
"@logto/console": minor
"@logto/phrases": minor
"@logto/schemas": minor
"@logto/core": minor
---
support default roles for users

View file

@ -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": {

View file

@ -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",

View file

@ -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);

View file

@ -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<LocalePhrase>;\n\n')
: fs.appendFile(targetFilePath, '};\n\n'));
await fs.appendFile(targetFilePath, `export default Object.freeze(${identifier});\n`);
};

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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",

View file

@ -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",

View file

@ -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"

View file

@ -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"

View file

@ -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",

View file

@ -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"

View file

@ -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"

View file

@ -5,7 +5,7 @@
"author": "ZR3SYSTEMS. <https://github.com/FlurryNight>",
"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"

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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"

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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"

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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",

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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"

View file

@ -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"
},

View file

@ -5,7 +5,7 @@
"author": "Kyungyoon Kim. <ruddbs5302@gmail.com>",
"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"

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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"

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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"

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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"

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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"

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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"

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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"

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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"

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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"

View file

@ -5,7 +5,7 @@
"author": "Kyungyoon Kim. <ruddbs5302@gmail.com>",
"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"

View file

@ -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",

View file

@ -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",

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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",

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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"

View file

@ -5,7 +5,7 @@
"author": "Danil Tankov <danil.tankoff@yandex.ru>",
"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"

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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",

View file

@ -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"

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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"

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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"

View file

@ -5,7 +5,7 @@
"author": "Silverhand Inc. <contact@silverhand.io>",
"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"

View file

@ -5,7 +5,7 @@
"author": "Dove<dove@feegr.cc> 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"

View file

@ -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",

View file

@ -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)}
/>
</FormField>
{role.type === RoleType.User && (
<FormField title="role_details.field_is_default">
<Switch
label={t('role_details.field_is_default_description')}
{...register('isDefault')}
/>
</FormField>
)}
</FormCard>
</DetailsForm>
<UnsavedChangesAlertModal hasUnsavedChanges={!isDeleting && isDirty} />

View file

@ -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

View file

@ -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",

View file

@ -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 = {

View file

@ -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.`);
};

View file

@ -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`
);
}
};

View file

@ -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<typeof createUserLibrary>;
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<CreateUser>, 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);

View file

@ -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),
]);

View file

@ -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 *
`;

View file

@ -95,6 +95,16 @@ export const createRolesQueries = (pool: CommonQueryMethods) => {
`)
: [];
const findDefaultRoles = async (forType: RoleType.User) =>
pool.any<Role>(
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<Role>(sql`
@ -147,6 +157,7 @@ export const createRolesQueries = (pool: CommonQueryMethods) => {
countRoles,
findRoles,
findRolesByRoleIds,
findDefaultRoles,
findRolesByRoleNames,
findRoleByRoleName,
insertRoles,

View file

@ -51,6 +51,7 @@ const mockedQueries = {
name: 'admin',
description: 'none',
type: RoleType.User,
isDefault: false,
},
]
),

View file

@ -39,6 +39,7 @@ const mockedQueries = {
name: 'admin',
description: 'none',
type: RoleType.User,
isDefault: false,
},
]
),

View file

@ -18,6 +18,7 @@ const roles = {
findRoleByRoleName: jest.fn(async (): Promise<Role | null> => null),
insertRole: jest.fn(async (data) => ({
type: mockAdminUserRole.type,
isDefault: false,
...data,
id: mockAdminUserRole.id,
tenantId: 'fake_tenant',

View file

@ -201,7 +201,7 @@ export default function roleRoutes<T extends ManagementApiRouter>(
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],

View file

@ -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`?'
);
}
};

View file

@ -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],
}}
>

View file

@ -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",

View file

@ -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",

View file

@ -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,
},

View file

@ -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<Nullable<string>> {
return this.logto.getRefreshToken();
}

View file

@ -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);
});
});

View file

@ -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"

View file

@ -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"

View file

@ -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<typeof en.translation>;
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<BuiltInLanguageTag, LocalePhrase>;
export type Resource = Record<
Exclude<BuiltInLanguageTag, DefaultLocale>,
DeepPartial<LocalePhrase>
> & {
[key in DefaultLocale]: LocalePhrase;
};
const resource: Resource = {
de,

View file

@ -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<LocalePhrase>;
export default Object.freeze(de);

View file

@ -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: {

View file

@ -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<LocalePhrase>;
export default Object.freeze(es);

View file

@ -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<LocalePhrase>;
export default Object.freeze(fr);

View file

@ -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<LocalePhrase>;
export default Object.freeze(it);

View file

@ -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<LocalePhrase>;
export default Object.freeze(ja);

View file

@ -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<LocalePhrase>;
export default Object.freeze(ko);

View file

@ -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<LocalePhrase>;
export default Object.freeze(pl_pl);

View file

@ -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<LocalePhrase>;
export default Object.freeze(pt_br);

View file

@ -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<LocalePhrase>;
export default Object.freeze(pt_pt);

View file

@ -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<LocalePhrase>;
export default Object.freeze(ru);

View file

@ -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<LocalePhrase>;
export default Object.freeze(tr_tr);

View file

@ -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<LocalePhrase>;
export default Object.freeze(zh_cn);

View file

@ -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<LocalePhrase>;
export default Object.freeze(zh_hk);

View file

@ -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<LocalePhrase>;
export default Object.freeze(zh_tw);

View file

@ -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;

View file

@ -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",

View file

@ -85,4 +85,5 @@ export const createTenantApplicationRole = (): Readonly<Role> => ({
description:
'The role for M2M applications that represent a user tenant and send requests to Logto Cloud.',
type: RoleType.MachineToMachine,
isDefault: false,
});

View file

@ -36,6 +36,7 @@ export const getMapiProxyRole = (tenantId: string): Readonly<Role> =>
name: `machine:mapi:${tenantId}`,
description: `Machine-to-machine role for accessing Management API of tenant '${tenantId}'.`,
type: RoleType.MachineToMachine,
isDefault: false,
});
/**

View file

@ -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)

View file

@ -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",

View file

@ -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'));

View file

@ -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"
},

View file

@ -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": {

View file

@ -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