mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
feat(core): guard password by policy
This commit is contained in:
parent
ed7d842517
commit
b8a7b900e1
44 changed files with 429 additions and 204 deletions
|
@ -60,7 +60,7 @@
|
||||||
"@microsoft/applicationinsights-clickanalytics-js": "^3.0.2",
|
"@microsoft/applicationinsights-clickanalytics-js": "^3.0.2",
|
||||||
"@microsoft/applicationinsights-react-js": "^17.0.0",
|
"@microsoft/applicationinsights-react-js": "^17.0.0",
|
||||||
"@microsoft/applicationinsights-web": "^3.0.2",
|
"@microsoft/applicationinsights-web": "^3.0.2",
|
||||||
"@silverhand/essentials": "^2.5.0",
|
"@silverhand/essentials": "^2.8.4",
|
||||||
"applicationinsights": "^2.7.0"
|
"applicationinsights": "^2.7.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
"@logto/phrases-ui": "workspace:^1.2.0",
|
"@logto/phrases-ui": "workspace:^1.2.0",
|
||||||
"@logto/schemas": "workspace:1.8.0",
|
"@logto/schemas": "workspace:1.8.0",
|
||||||
"@logto/shared": "workspace:^2.0.0",
|
"@logto/shared": "workspace:^2.0.0",
|
||||||
"@silverhand/essentials": "^2.5.0",
|
"@silverhand/essentials": "^2.8.4",
|
||||||
"chalk": "^5.0.0",
|
"chalk": "^5.0.0",
|
||||||
"decamelize": "^6.0.0",
|
"decamelize": "^6.0.0",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
"prepublishOnly": "pnpm build"
|
"prepublishOnly": "pnpm build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@silverhand/essentials": "^2.5.0",
|
"@silverhand/essentials": "^2.8.4",
|
||||||
"got": "^13.0.0",
|
"got": "^13.0.0",
|
||||||
"snakecase-keys": "^5.4.4",
|
"snakecase-keys": "^5.4.4",
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.20.2"
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
"@parcel/transformer-svg-react": "2.9.3",
|
"@parcel/transformer-svg-react": "2.9.3",
|
||||||
"@silverhand/eslint-config": "4.0.1",
|
"@silverhand/eslint-config": "4.0.1",
|
||||||
"@silverhand/eslint-config-react": "4.0.1",
|
"@silverhand/eslint-config-react": "4.0.1",
|
||||||
"@silverhand/essentials": "^2.5.0",
|
"@silverhand/essentials": "^2.8.4",
|
||||||
"@silverhand/ts-config": "4.0.0",
|
"@silverhand/ts-config": "4.0.0",
|
||||||
"@silverhand/ts-config-react": "4.0.0",
|
"@silverhand/ts-config-react": "4.0.0",
|
||||||
"@swc/core": "^1.3.52",
|
"@swc/core": "^1.3.52",
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
"@logto/schemas": "workspace:^1.8.0",
|
"@logto/schemas": "workspace:^1.8.0",
|
||||||
"@logto/shared": "workspace:^2.0.0",
|
"@logto/shared": "workspace:^2.0.0",
|
||||||
"@logto/ui": "workspace:*",
|
"@logto/ui": "workspace:*",
|
||||||
"@silverhand/essentials": "^2.5.0",
|
"@silverhand/essentials": "^2.8.4",
|
||||||
"@withtyped/client": "^0.7.22",
|
"@withtyped/client": "^0.7.22",
|
||||||
"chalk": "^5.0.0",
|
"chalk": "^5.0.0",
|
||||||
"clean-deep": "^3.4.0",
|
"clean-deep": "^3.4.0",
|
||||||
|
|
|
@ -91,4 +91,5 @@ export const mockSignInExperience: SignInExperience = {
|
||||||
signInMode: SignInMode.SignInAndRegister,
|
signInMode: SignInMode.SignInAndRegister,
|
||||||
customCss: null,
|
customCss: null,
|
||||||
customContent: {},
|
customContent: {},
|
||||||
|
passwordPolicy: {},
|
||||||
};
|
};
|
||||||
|
|
|
@ -32,12 +32,13 @@ describe('sign-in-experience query', () => {
|
||||||
signUp: JSON.stringify(mockSignInExperience.signUp),
|
signUp: JSON.stringify(mockSignInExperience.signUp),
|
||||||
socialSignInConnectorTargets: JSON.stringify(mockSignInExperience.socialSignInConnectorTargets),
|
socialSignInConnectorTargets: JSON.stringify(mockSignInExperience.socialSignInConnectorTargets),
|
||||||
customContent: JSON.stringify(mockSignInExperience.customContent),
|
customContent: JSON.stringify(mockSignInExperience.customContent),
|
||||||
|
passwordPolicy: JSON.stringify(mockSignInExperience.passwordPolicy),
|
||||||
};
|
};
|
||||||
|
|
||||||
it('findDefaultSignInExperience', async () => {
|
it('findDefaultSignInExperience', async () => {
|
||||||
/* eslint-disable sql/no-unsafe-query */
|
/* eslint-disable sql/no-unsafe-query */
|
||||||
const expectSql = `
|
const expectSql = `
|
||||||
select "tenant_id", "id", "color", "branding", "language_info", "terms_of_use_url", "privacy_policy_url", "sign_in", "sign_up", "social_sign_in_connector_targets", "sign_in_mode", "custom_css", "custom_content"
|
select "tenant_id", "id", "color", "branding", "language_info", "terms_of_use_url", "privacy_policy_url", "sign_in", "sign_up", "social_sign_in_connector_targets", "sign_in_mode", "custom_css", "custom_content", "password_policy"
|
||||||
from "sign_in_experiences"
|
from "sign_in_experiences"
|
||||||
where "id"=$1
|
where "id"=$1
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -76,6 +76,10 @@ const { sendVerificationCodeToIdentifier } = await mockEsmWithActual(
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { validatePassword } = await mockEsmWithActual('./utils/validate-password.js', () => ({
|
||||||
|
validatePassword: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
const { createLog, prependAllLogEntries } = createMockLogContext();
|
const { createLog, prependAllLogEntries } = createMockLogContext();
|
||||||
|
|
||||||
await mockEsmWithActual(
|
await mockEsmWithActual(
|
||||||
|
@ -151,6 +155,7 @@ describe('interaction routes', () => {
|
||||||
expect(verifyIdentifierSettings).toBeCalled();
|
expect(verifyIdentifierSettings).toBeCalled();
|
||||||
expect(verifyProfileSettings).toBeCalled();
|
expect(verifyProfileSettings).toBeCalled();
|
||||||
expect(verifyIdentifierPayload).toBeCalled();
|
expect(verifyIdentifierPayload).toBeCalled();
|
||||||
|
expect(validatePassword).toBeCalled();
|
||||||
expect(storeInteractionResult).toBeCalled();
|
expect(storeInteractionResult).toBeCalled();
|
||||||
expect(response.status).toEqual(204);
|
expect(response.status).toEqual(204);
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,6 +37,7 @@ import {
|
||||||
verifyProfileSettings,
|
verifyProfileSettings,
|
||||||
} from './utils/sign-in-experience-validation.js';
|
} from './utils/sign-in-experience-validation.js';
|
||||||
import { createSocialAuthorizationUrl } from './utils/social-verification.js';
|
import { createSocialAuthorizationUrl } from './utils/social-verification.js';
|
||||||
|
import { validatePassword } from './utils/validate-password.js';
|
||||||
import { sendVerificationCodeToIdentifier } from './utils/verification-code-validation.js';
|
import { sendVerificationCodeToIdentifier } from './utils/verification-code-validation.js';
|
||||||
import {
|
import {
|
||||||
verifyIdentifierPayload,
|
verifyIdentifierPayload,
|
||||||
|
@ -71,6 +72,10 @@ export default function interactionRoutes<T extends AnonymousRouter>(
|
||||||
status: [204, 400, 401, 403, 422],
|
status: [204, 400, 401, 403, 422],
|
||||||
}),
|
}),
|
||||||
koaInteractionSie(queries),
|
koaInteractionSie(queries),
|
||||||
|
async ({ guard: { body }, passwordPolicyChecker }, next) => {
|
||||||
|
await validatePassword(body.profile?.password, passwordPolicyChecker);
|
||||||
|
return next();
|
||||||
|
},
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
const { event, identifier, profile } = ctx.guard.body;
|
const { event, identifier, profile } = ctx.guard.body;
|
||||||
const { signInExperience, createLog } = ctx;
|
const { signInExperience, createLog } = ctx;
|
||||||
|
@ -205,6 +210,10 @@ export default function interactionRoutes<T extends AnonymousRouter>(
|
||||||
status: [204, 400, 404],
|
status: [204, 400, 404],
|
||||||
}),
|
}),
|
||||||
koaInteractionSie(queries),
|
koaInteractionSie(queries),
|
||||||
|
async ({ guard: { body }, passwordPolicyChecker }, next) => {
|
||||||
|
await validatePassword(body.password, passwordPolicyChecker);
|
||||||
|
return next();
|
||||||
|
},
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
const profilePayload = ctx.guard.body;
|
const profilePayload = ctx.guard.body;
|
||||||
const { signInExperience, interactionDetails, createLog } = ctx;
|
const { signInExperience, interactionDetails, createLog } = ctx;
|
||||||
|
@ -243,6 +252,10 @@ export default function interactionRoutes<T extends AnonymousRouter>(
|
||||||
status: [204, 400, 404],
|
status: [204, 400, 404],
|
||||||
}),
|
}),
|
||||||
koaInteractionSie(queries),
|
koaInteractionSie(queries),
|
||||||
|
async ({ guard: { body }, passwordPolicyChecker }, next) => {
|
||||||
|
await validatePassword(body.password, passwordPolicyChecker);
|
||||||
|
return next();
|
||||||
|
},
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
const profilePayload = ctx.guard.body;
|
const profilePayload = ctx.guard.body;
|
||||||
const { signInExperience, interactionDetails, createLog } = ctx;
|
const { signInExperience, interactionDetails, createLog } = ctx;
|
||||||
|
@ -373,6 +386,8 @@ export default function interactionRoutes<T extends AnonymousRouter>(
|
||||||
// Check interaction exists
|
// Check interaction exists
|
||||||
const { event } = getInteractionStorage(interactionDetails.result);
|
const { event } = getInteractionStorage(interactionDetails.result);
|
||||||
|
|
||||||
|
// This file needs refactor
|
||||||
|
// eslint-disable-next-line max-lines
|
||||||
await sendVerificationCodeToIdentifier(
|
await sendVerificationCodeToIdentifier(
|
||||||
{ event, ...guard.body },
|
{ event, ...guard.body },
|
||||||
interactionDetails.jti,
|
interactionDetails.jti,
|
||||||
|
|
|
@ -1,12 +1,23 @@
|
||||||
|
import crypto from 'node:crypto';
|
||||||
|
|
||||||
|
import { PasswordPolicyChecker } from '@logto/core-kit';
|
||||||
import type { SignInExperience } from '@logto/schemas';
|
import type { SignInExperience } from '@logto/schemas';
|
||||||
import type { MiddlewareType } from 'koa';
|
import type { MiddlewareType } from 'koa';
|
||||||
import { type IRouterParamContext } from 'koa-router';
|
import { type IRouterParamContext } from 'koa-router';
|
||||||
|
|
||||||
import type Queries from '#src/tenants/Queries.js';
|
import type Queries from '#src/tenants/Queries.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend the context with the default sign-in experience and the corresponding
|
||||||
|
* password policy checker.
|
||||||
|
*/
|
||||||
export type WithInteractionSieContext<ContextT extends IRouterParamContext = IRouterParamContext> =
|
export type WithInteractionSieContext<ContextT extends IRouterParamContext = IRouterParamContext> =
|
||||||
ContextT & { signInExperience: SignInExperience };
|
ContextT & { signInExperience: SignInExperience; passwordPolicyChecker: PasswordPolicyChecker };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a middleware that injects the default sign-in experience and the
|
||||||
|
* corresponding password policy checker into the context.
|
||||||
|
*/
|
||||||
export default function koaInteractionSie<StateT, ContextT extends IRouterParamContext, ResponseT>({
|
export default function koaInteractionSie<StateT, ContextT extends IRouterParamContext, ResponseT>({
|
||||||
signInExperiences: { findDefaultSignInExperience },
|
signInExperiences: { findDefaultSignInExperience },
|
||||||
}: Queries): MiddlewareType<StateT, WithInteractionSieContext<ContextT>, ResponseT> {
|
}: Queries): MiddlewareType<StateT, WithInteractionSieContext<ContextT>, ResponseT> {
|
||||||
|
@ -14,6 +25,10 @@ export default function koaInteractionSie<StateT, ContextT extends IRouterParamC
|
||||||
const signInExperience = await findDefaultSignInExperience();
|
const signInExperience = await findDefaultSignInExperience();
|
||||||
|
|
||||||
ctx.signInExperience = signInExperience;
|
ctx.signInExperience = signInExperience;
|
||||||
|
ctx.passwordPolicyChecker = new PasswordPolicyChecker(
|
||||||
|
signInExperience.passwordPolicy,
|
||||||
|
crypto.subtle
|
||||||
|
);
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { type PasswordPolicyChecker } from '@logto/core-kit';
|
||||||
|
import { type Optional } from '@silverhand/essentials';
|
||||||
|
|
||||||
|
import RequestError from '#src/errors/RequestError/index.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate password against the given password policy if the password is not undefined,
|
||||||
|
* throw a {@link RequestError} if the password is invalid; otherwise, do nothing.
|
||||||
|
*/
|
||||||
|
export const validatePassword = async (
|
||||||
|
password: Optional<string>,
|
||||||
|
checker: PasswordPolicyChecker
|
||||||
|
) => {
|
||||||
|
if (password === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const issues = await checker.check(password, {});
|
||||||
|
if (issues.length > 0) {
|
||||||
|
throw new RequestError('password.password_rejected', issues);
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,3 +1,6 @@
|
||||||
|
import crypto from 'node:crypto';
|
||||||
|
|
||||||
|
import { PasswordPolicyChecker } from '@logto/core-kit';
|
||||||
import { InteractionEvent, MissingProfile, SignInIdentifier } from '@logto/schemas';
|
import { InteractionEvent, MissingProfile, SignInIdentifier } from '@logto/schemas';
|
||||||
import { createMockUtils, pickDefault } from '@logto/shared/esm';
|
import { createMockUtils, pickDefault } from '@logto/shared/esm';
|
||||||
import type Provider from 'oidc-provider';
|
import type Provider from 'oidc-provider';
|
||||||
|
@ -32,6 +35,10 @@ describe('validateMandatoryUserProfile', () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
interactionDetails: {} as Awaited<ReturnType<Provider['interactionDetails']>>,
|
interactionDetails: {} as Awaited<ReturnType<Provider['interactionDetails']>>,
|
||||||
signInExperience: mockSignInExperience,
|
signInExperience: mockSignInExperience,
|
||||||
|
passwordPolicyChecker: new PasswordPolicyChecker(
|
||||||
|
mockSignInExperience.passwordPolicy,
|
||||||
|
crypto.subtle
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const interaction: IdentifierVerifiedInteractionResult = {
|
const interaction: IdentifierVerifiedInteractionResult = {
|
||||||
|
@ -274,7 +281,7 @@ describe('validateMandatoryUserProfile', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('email or Phone required', () => {
|
describe('email or phone required', () => {
|
||||||
const ctx = {
|
const ctx = {
|
||||||
...baseCtx,
|
...baseCtx,
|
||||||
signInExperience: {
|
signInExperience: {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
ZodString,
|
ZodString,
|
||||||
ZodUnion,
|
ZodUnion,
|
||||||
ZodUnknown,
|
ZodUnknown,
|
||||||
|
ZodDefault,
|
||||||
} from 'zod';
|
} from 'zod';
|
||||||
|
|
||||||
import RequestError from '#src/errors/RequestError/index.js';
|
import RequestError from '#src/errors/RequestError/index.js';
|
||||||
|
@ -266,5 +267,13 @@ export const zodTypeToSwagger = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config instanceof ZodDefault) {
|
||||||
|
return {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
default: config._def.defaultValue(),
|
||||||
|
...zodTypeToSwagger(config._def.innerType),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
throw new RequestError('swagger.invalid_zod_type', config);
|
throw new RequestError('swagger.invalid_zod_type', config);
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
"@logto/schemas": "workspace:^1.6.0",
|
"@logto/schemas": "workspace:^1.6.0",
|
||||||
"@logto/shared": "workspace:^2.0.0",
|
"@logto/shared": "workspace:^2.0.0",
|
||||||
"@silverhand/eslint-config": "4.0.1",
|
"@silverhand/eslint-config": "4.0.1",
|
||||||
"@silverhand/essentials": "^2.5.0",
|
"@silverhand/essentials": "^2.8.4",
|
||||||
"@silverhand/ts-config": "4.0.0",
|
"@silverhand/ts-config": "4.0.0",
|
||||||
"@types/expect-puppeteer": "^5.0.3",
|
"@types/expect-puppeteer": "^5.0.3",
|
||||||
"@types/jest": "^29.4.0",
|
"@types/jest": "^29.4.0",
|
||||||
|
|
|
@ -33,8 +33,9 @@
|
||||||
"url": "https://github.com/logto-io/logto/issues"
|
"url": "https://github.com/logto-io/logto/issues"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@logto/core-kit": "workspace:^2.0.1",
|
||||||
"@logto/language-kit": "workspace:^1.0.0",
|
"@logto/language-kit": "workspace:^1.0.0",
|
||||||
"@silverhand/essentials": "^2.5.0"
|
"@silverhand/essentials": "^2.8.4"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.20.2"
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@logto/language-kit": "workspace:^1.0.0",
|
"@logto/language-kit": "workspace:^1.0.0",
|
||||||
"@silverhand/essentials": "^2.5.0"
|
"@silverhand/essentials": "^2.8.4"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.20.2"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const password = {
|
const password = {
|
||||||
unsupported_encryption_method: 'Die Verschlüsselungsmethode {{name}} wird nicht unterstützt.',
|
unsupported_encryption_method: 'Die Verschlüsselungsmethode {{name}} wird nicht unterstützt.',
|
||||||
pepper_not_found: 'Password pepper not found. Please check your core envs.',
|
pepper_not_found: 'Password pepper not found. Please check your core envs.',
|
||||||
|
password_rejected: 'Password rejected. Please check if your password meets the requirements.', // UNTRANSLATED
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(password);
|
export default Object.freeze(password);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const password = {
|
const password = {
|
||||||
unsupported_encryption_method: 'The encryption method {{name}} is not supported.',
|
unsupported_encryption_method: 'The encryption method {{name}} is not supported.',
|
||||||
pepper_not_found: 'Password pepper not found. Please check your core envs.',
|
pepper_not_found: 'Password pepper not found. Please check your core envs.',
|
||||||
|
password_rejected: 'Password rejected. Please check if your password meets the requirements.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(password);
|
export default Object.freeze(password);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const password = {
|
const password = {
|
||||||
unsupported_encryption_method: 'El método de encriptación {{name}} no es compatible.',
|
unsupported_encryption_method: 'El método de encriptación {{name}} no es compatible.',
|
||||||
pepper_not_found: 'No se encontró el password pepper. Por favor revisa tus variables de entorno.',
|
pepper_not_found: 'No se encontró el password pepper. Por favor revisa tus variables de entorno.',
|
||||||
|
password_rejected: 'Password rejected. Please check if your password meets the requirements.', // UNTRANSLATED
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(password);
|
export default Object.freeze(password);
|
||||||
|
|
|
@ -2,6 +2,7 @@ const password = {
|
||||||
unsupported_encryption_method: "La méthode de cryptage {{name}} n'est pas prise en charge.",
|
unsupported_encryption_method: "La méthode de cryptage {{name}} n'est pas prise en charge.",
|
||||||
pepper_not_found:
|
pepper_not_found:
|
||||||
'Mot de passe pepper non trouvé. Veuillez vérifier votre environnement de base.',
|
'Mot de passe pepper non trouvé. Veuillez vérifier votre environnement de base.',
|
||||||
|
password_rejected: 'Password rejected. Please check if your password meets the requirements.', // UNTRANSLATED
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(password);
|
export default Object.freeze(password);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const password = {
|
const password = {
|
||||||
unsupported_encryption_method: 'Il metodo di crittografia {{name}} non è supportato.',
|
unsupported_encryption_method: 'Il metodo di crittografia {{name}} non è supportato.',
|
||||||
pepper_not_found: 'Pepper password non trovato. Per favore controlla le tue env di core.',
|
pepper_not_found: 'Pepper password non trovato. Per favore controlla le tue env di core.',
|
||||||
|
password_rejected: 'Password rejected. Please check if your password meets the requirements.', // UNTRANSLATED
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(password);
|
export default Object.freeze(password);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const password = {
|
const password = {
|
||||||
unsupported_encryption_method: '暗号化方式 {{name}} はサポートされていません。',
|
unsupported_encryption_method: '暗号化方式 {{name}} はサポートされていません。',
|
||||||
pepper_not_found: 'パスワードペッパーが見つかりません。コアの環境を確認してください。',
|
pepper_not_found: 'パスワードペッパーが見つかりません。コアの環境を確認してください。',
|
||||||
|
password_rejected: 'Password rejected. Please check if your password meets the requirements.', // UNTRANSLATED
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(password);
|
export default Object.freeze(password);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const password = {
|
const password = {
|
||||||
unsupported_encryption_method: '{{name}} 암호화 방법을 지원하지 않아요.',
|
unsupported_encryption_method: '{{name}} 암호화 방법을 지원하지 않아요.',
|
||||||
pepper_not_found: '비밀번호 Pepper를 찾을 수 없어요. Core 환경설정을 확인해 주세요.',
|
pepper_not_found: '비밀번호 Pepper를 찾을 수 없어요. Core 환경설정을 확인해 주세요.',
|
||||||
|
password_rejected: 'Password rejected. Please check if your password meets the requirements.', // UNTRANSLATED
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(password);
|
export default Object.freeze(password);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const password = {
|
const password = {
|
||||||
unsupported_encryption_method: 'Metoda szyfrowania {{name}} nie jest obsługiwana.',
|
unsupported_encryption_method: 'Metoda szyfrowania {{name}} nie jest obsługiwana.',
|
||||||
pepper_not_found: 'Nie znaleziono wartości pepper dla hasła. Sprawdź swoje zmienne środowiskowe.',
|
pepper_not_found: 'Nie znaleziono wartości pepper dla hasła. Sprawdź swoje zmienne środowiskowe.',
|
||||||
|
password_rejected: 'Password rejected. Please check if your password meets the requirements.', // UNTRANSLATED
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(password);
|
export default Object.freeze(password);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const password = {
|
const password = {
|
||||||
unsupported_encryption_method: 'O método de criptografia {{name}} não é suportado.',
|
unsupported_encryption_method: 'O método de criptografia {{name}} não é suportado.',
|
||||||
pepper_not_found: 'Password pepper não encontrada. Por favor, verifique seus envs principais.',
|
pepper_not_found: 'Password pepper não encontrada. Por favor, verifique seus envs principais.',
|
||||||
|
password_rejected: 'Password rejected. Please check if your password meets the requirements.', // UNTRANSLATED
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(password);
|
export default Object.freeze(password);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const password = {
|
const password = {
|
||||||
unsupported_encryption_method: 'O método de enncriptação {{name}} não é suportado.',
|
unsupported_encryption_method: 'O método de enncriptação {{name}} não é suportado.',
|
||||||
pepper_not_found: 'pepper da Password não encontrada. Por favor, verifique os envs.',
|
pepper_not_found: 'pepper da Password não encontrada. Por favor, verifique os envs.',
|
||||||
|
password_rejected: 'Password rejected. Please check if your password meets the requirements.', // UNTRANSLATED
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(password);
|
export default Object.freeze(password);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const password = {
|
const password = {
|
||||||
unsupported_encryption_method: 'Метод шифрования {{name}} не поддерживается.',
|
unsupported_encryption_method: 'Метод шифрования {{name}} не поддерживается.',
|
||||||
pepper_not_found: 'Не найден пепер пароля. Пожалуйста, проверьте ваши основные envs.',
|
pepper_not_found: 'Не найден пепер пароля. Пожалуйста, проверьте ваши основные envs.',
|
||||||
|
password_rejected: 'Password rejected. Please check if your password meets the requirements.', // UNTRANSLATED
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(password);
|
export default Object.freeze(password);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const password = {
|
const password = {
|
||||||
unsupported_encryption_method: '{{name}} şifreleme metodu desteklenmiyor.',
|
unsupported_encryption_method: '{{name}} şifreleme metodu desteklenmiyor.',
|
||||||
pepper_not_found: 'Şifre pepperı bulunamadı. Lütfen core envs.i kontrol edin.',
|
pepper_not_found: 'Şifre pepperı bulunamadı. Lütfen core envs.i kontrol edin.',
|
||||||
|
password_rejected: 'Password rejected. Please check if your password meets the requirements.', // UNTRANSLATED
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(password);
|
export default Object.freeze(password);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const password = {
|
const password = {
|
||||||
unsupported_encryption_method: '不支持的加密方法 {{name}}',
|
unsupported_encryption_method: '不支持的加密方法 {{name}}',
|
||||||
pepper_not_found: '密码 pepper 未找到。请检查 core 的环境变量。',
|
pepper_not_found: '密码 pepper 未找到。请检查 core 的环境变量。',
|
||||||
|
password_rejected: 'Password rejected. Please check if your password meets the requirements.', // UNTRANSLATED
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(password);
|
export default Object.freeze(password);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const password = {
|
const password = {
|
||||||
unsupported_encryption_method: '不支持的加密方法 {{name}}',
|
unsupported_encryption_method: '不支持的加密方法 {{name}}',
|
||||||
pepper_not_found: '密碼 pepper 未找到。請檢查 core 的環境變量。',
|
pepper_not_found: '密碼 pepper 未找到。請檢查 core 的環境變量。',
|
||||||
|
password_rejected: 'Password rejected. Please check if your password meets the requirements.', // UNTRANSLATED
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(password);
|
export default Object.freeze(password);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const password = {
|
const password = {
|
||||||
unsupported_encryption_method: '不支持的加密方法 {{name}}',
|
unsupported_encryption_method: '不支持的加密方法 {{name}}',
|
||||||
pepper_not_found: '密碼 pepper 未找到。請檢查 core 的環境變數。',
|
pepper_not_found: '密碼 pepper 未找到。請檢查 core 的環境變數。',
|
||||||
|
password_rejected: 'Password rejected. Please check if your password meets the requirements.', // UNTRANSLATED
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(password);
|
export default Object.freeze(password);
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { sql } from 'slonik';
|
||||||
|
|
||||||
|
import type { AlterationScript } from '../lib/types/alteration.js';
|
||||||
|
|
||||||
|
/** The alteration script for adding `password_policy` column to the sign-in experience table. */
|
||||||
|
const alteration: AlterationScript = {
|
||||||
|
up: async (pool) => {
|
||||||
|
await pool.query(sql`
|
||||||
|
alter table sign_in_experiences
|
||||||
|
add column password_policy jsonb not null default '{}';
|
||||||
|
`);
|
||||||
|
},
|
||||||
|
down: async (pool) => {
|
||||||
|
await pool.query(sql`
|
||||||
|
alter table sign_in_experiences
|
||||||
|
drop column password_policy;
|
||||||
|
`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default alteration;
|
|
@ -41,7 +41,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@silverhand/eslint-config": "4.0.1",
|
"@silverhand/eslint-config": "4.0.1",
|
||||||
"@silverhand/essentials": "^2.5.0",
|
"@silverhand/essentials": "^2.8.4",
|
||||||
"@silverhand/ts-config": "4.0.0",
|
"@silverhand/ts-config": "4.0.0",
|
||||||
"@types/inquirer": "^9.0.0",
|
"@types/inquirer": "^9.0.0",
|
||||||
"@types/jest": "^29.4.0",
|
"@types/jest": "^29.4.0",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { hexColorRegEx } from '@logto/core-kit';
|
import { type PasswordPolicy, hexColorRegEx, passwordPolicyGuard } from '@logto/core-kit';
|
||||||
import { languageTagGuard } from '@logto/language-kit';
|
import { languageTagGuard } from '@logto/language-kit';
|
||||||
|
import { type DeepPartial } from '@silverhand/essentials';
|
||||||
import type { Json } from '@withtyped/server';
|
import type { Json } from '@withtyped/server';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
@ -204,6 +205,10 @@ export const logContextPayloadGuard = z
|
||||||
})
|
})
|
||||||
.catchall(z.unknown());
|
.catchall(z.unknown());
|
||||||
|
|
||||||
|
export type PartialPasswordPolicy = DeepPartial<PasswordPolicy>;
|
||||||
|
|
||||||
|
export const partialPasswordPolicyGuard = passwordPolicyGuard.deepPartial();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The basic log context type. It's more about a type hint instead of forcing the log shape.
|
* The basic log context type. It's more about a type hint instead of forcing the log shape.
|
||||||
*
|
*
|
||||||
|
|
|
@ -49,6 +49,7 @@ export const createDefaultSignInExperience = (
|
||||||
signInMode: SignInMode.SignInAndRegister,
|
signInMode: SignInMode.SignInAndRegister,
|
||||||
customCss: null,
|
customCss: null,
|
||||||
customContent: {},
|
customContent: {},
|
||||||
|
passwordPolicy: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
/** @deprecated Use `createDefaultSignInExperience()` instead. */
|
/** @deprecated Use `createDefaultSignInExperience()` instead. */
|
||||||
|
|
|
@ -15,5 +15,6 @@ create table sign_in_experiences (
|
||||||
sign_in_mode sign_in_mode not null default 'SignInAndRegister',
|
sign_in_mode sign_in_mode not null default 'SignInAndRegister',
|
||||||
custom_css text,
|
custom_css text,
|
||||||
custom_content jsonb /* @use CustomContent */ not null default '{}'::jsonb,
|
custom_content jsonb /* @use CustomContent */ not null default '{}'::jsonb,
|
||||||
|
password_policy jsonb /* @use PartialPasswordPolicy */ not null default '{}'::jsonb,
|
||||||
primary key (tenant_id, id)
|
primary key (tenant_id, id)
|
||||||
);
|
);
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
},
|
},
|
||||||
"prettier": "@silverhand/eslint-config/.prettierrc",
|
"prettier": "@silverhand/eslint-config/.prettierrc",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@silverhand/essentials": "^2.5.0",
|
"@silverhand/essentials": "^2.8.4",
|
||||||
"chalk": "^5.0.0",
|
"chalk": "^5.0.0",
|
||||||
"find-up": "^6.3.0",
|
"find-up": "^6.3.0",
|
||||||
"nanoid": "^4.0.0",
|
"nanoid": "^4.0.0",
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@logto/language-kit": "workspace:^1.0.0",
|
"@logto/language-kit": "workspace:^1.0.0",
|
||||||
"@silverhand/essentials": "^2.5.0",
|
"@silverhand/essentials": "^2.8.4",
|
||||||
"@withtyped/client": "^0.7.22"
|
"@withtyped/client": "^0.7.22"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/types": "^29.0.3",
|
"@jest/types": "^29.0.3",
|
||||||
"@silverhand/eslint-config": "4.0.1",
|
"@silverhand/eslint-config": "4.0.1",
|
||||||
"@silverhand/essentials": "^2.5.0",
|
"@silverhand/essentials": "^2.8.4",
|
||||||
"@silverhand/ts-config": "4.0.0",
|
"@silverhand/ts-config": "4.0.0",
|
||||||
"@silverhand/ts-config-react": "4.0.0",
|
"@silverhand/ts-config-react": "4.0.0",
|
||||||
"@types/color": "^3.0.3",
|
"@types/color": "^3.0.3",
|
||||||
|
|
|
@ -25,7 +25,7 @@ describe('PasswordPolicyChecker', () => {
|
||||||
it('should reject malformed policy', () => {
|
it('should reject malformed policy', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
return new PasswordPolicyChecker({ length: { min: 1, max: 2 } });
|
return new PasswordPolicyChecker({ length: { min: 1, max: '2' } });
|
||||||
}).toThrowError(ZodError);
|
}).toThrowError(ZodError);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -39,35 +39,37 @@ describe('PasswordPolicyChecker -> check()', () => {
|
||||||
rejects: {
|
rejects: {
|
||||||
pwned: true,
|
pwned: true,
|
||||||
repetitionAndSequence: true,
|
repetitionAndSequence: true,
|
||||||
words: [{ type: 'custom', value: 'test' }],
|
personalInfo: true,
|
||||||
|
words: ['test', 'aaaa'],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should accept valid password', async () => {
|
it('should accept valid password', async () => {
|
||||||
expect(await checker.check('aL1!aL1!')).toEqual([]);
|
expect(await checker.check('aL1!aL1!', {})).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject with all failed checks', async () => {
|
it('should reject with all failed checks', async () => {
|
||||||
expect(await checker.check('aaa')).toEqual([
|
expect(await checker.check('aaa', {})).toEqual([
|
||||||
{ code: 'password_rejected.too_short' },
|
{ code: 'password_rejected.too_short', interpolation: { min: 7 } },
|
||||||
{ code: 'password_rejected.character_types', interpolation: { min: 2 } },
|
{ code: 'password_rejected.character_types', interpolation: { min: 2 } },
|
||||||
{ code: 'password_rejected.repetition' },
|
{ code: 'password_rejected.repetition' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(await checker.check('123456')).toEqual([
|
expect(await checker.check('123456', { phoneNumber: '12345' })).toEqual([
|
||||||
{ code: 'password_rejected.too_short' },
|
{ code: 'password_rejected.too_short', interpolation: { min: 7 } },
|
||||||
{ code: 'password_rejected.character_types', interpolation: { min: 2 } },
|
{ code: 'password_rejected.character_types', interpolation: { min: 2 } },
|
||||||
{ code: 'password_rejected.pwned' },
|
|
||||||
{ code: 'password_rejected.sequence' },
|
{ code: 'password_rejected.sequence' },
|
||||||
|
{ code: 'password_rejected.pwned' },
|
||||||
|
{ code: 'password_rejected.personal_info' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(await checker.check('aaaaaatest😀')).toEqual([
|
expect(await checker.check('aaaaaatest😀', {})).toEqual([
|
||||||
{ code: 'password_rejected.too_long' },
|
{ code: 'password_rejected.too_long', interpolation: { max: 8 } },
|
||||||
{ code: 'password_rejected.unsupported_characters' },
|
{ code: 'password_rejected.unsupported_characters' },
|
||||||
{ code: 'password_rejected.repetition' },
|
{ code: 'password_rejected.repetition' },
|
||||||
{
|
{
|
||||||
code: 'password_rejected.restricted_words',
|
code: 'password_rejected.restricted_words',
|
||||||
interpolation: { type: 'custom', value: 'test' },
|
interpolation: { words: 'test\naaaa', count: 2 },
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
@ -77,12 +79,12 @@ describe('PasswordPolicyChecker -> checkCharTypes()', () => {
|
||||||
const checker1 = new PasswordPolicyChecker({
|
const checker1 = new PasswordPolicyChecker({
|
||||||
length: { min: 1, max: 256 },
|
length: { min: 1, max: 256 },
|
||||||
characterTypes: { min: 2 },
|
characterTypes: { min: 2 },
|
||||||
rejects: { pwned: false, repetitionAndSequence: false, words: [] },
|
rejects: { pwned: false, repetitionAndSequence: false, personalInfo: false, words: [] },
|
||||||
});
|
});
|
||||||
const checker2 = new PasswordPolicyChecker({
|
const checker2 = new PasswordPolicyChecker({
|
||||||
length: { min: 1, max: 256 },
|
length: { min: 1, max: 256 },
|
||||||
characterTypes: { min: 4 },
|
characterTypes: { min: 4 },
|
||||||
rejects: { pwned: false, repetitionAndSequence: false, words: [] },
|
rejects: { pwned: false, repetitionAndSequence: false, personalInfo: false, words: [] },
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject unsupported characters', () => {
|
it('should reject unsupported characters', () => {
|
||||||
|
@ -106,7 +108,7 @@ describe('PasswordPolicyChecker -> hasBeenPwned()', () => {
|
||||||
const checker = new PasswordPolicyChecker({
|
const checker = new PasswordPolicyChecker({
|
||||||
length: { min: 1, max: 256 },
|
length: { min: 1, max: 256 },
|
||||||
characterTypes: { min: 2 },
|
characterTypes: { min: 2 },
|
||||||
rejects: { pwned: true, repetitionAndSequence: false, words: [] },
|
rejects: { pwned: true, repetitionAndSequence: false, personalInfo: false, words: [] },
|
||||||
});
|
});
|
||||||
|
|
||||||
mockPwnResponse();
|
mockPwnResponse();
|
||||||
|
@ -139,6 +141,36 @@ describe('PasswordPolicyChecker -> hasRepetition()', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('PasswordPolicyChecker -> hasPersonalInfo()', () => {
|
||||||
|
const checker = new PasswordPolicyChecker({
|
||||||
|
rejects: { pwned: false, repetitionAndSequence: false, personalInfo: true, words: [] },
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject password with name', () => {
|
||||||
|
expect(checker.hasPersonalInfo('test', { name: 'test' })).toBe(true);
|
||||||
|
expect(checker.hasPersonalInfo('test', { name: 'test2' })).toBe(false);
|
||||||
|
expect(checker.hasPersonalInfo('FOO', { name: 'Foo bar' })).toBe(true);
|
||||||
|
expect(checker.hasPersonalInfo('Foo', { name: 'bar fOo' })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject password with username', () => {
|
||||||
|
expect(checker.hasPersonalInfo('123.456!test', { username: 'teST' })).toBe(true);
|
||||||
|
expect(checker.hasPersonalInfo('test', { username: 'test2' })).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject password with email', () => {
|
||||||
|
expect(checker.hasPersonalInfo('teST', { email: 'test@foo.com' })).toBe(true);
|
||||||
|
expect(checker.hasPersonalInfo('TEST', { email: 'test1@foo.com' })).toBe(false);
|
||||||
|
expect(checker.hasPersonalInfo('FOO', { email: 'test@foo.com' })).toBe(false);
|
||||||
|
expect(checker.hasPersonalInfo('Foo', { email: 'fOO@foo.com' })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject password with phone number', () => {
|
||||||
|
expect(checker.hasPersonalInfo('teST1234567890.', { phoneNumber: '123456789' })).toBe(true);
|
||||||
|
expect(checker.hasPersonalInfo('TEST12345678', { phoneNumber: '123456789' })).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('PasswordPolicyChecker -> hasWords()', () => {
|
describe('PasswordPolicyChecker -> hasWords()', () => {
|
||||||
const checker = new PasswordPolicyChecker({
|
const checker = new PasswordPolicyChecker({
|
||||||
length: { min: 1, max: 256 },
|
length: { min: 1, max: 256 },
|
||||||
|
@ -146,25 +178,15 @@ describe('PasswordPolicyChecker -> hasWords()', () => {
|
||||||
rejects: {
|
rejects: {
|
||||||
pwned: false,
|
pwned: false,
|
||||||
repetitionAndSequence: false,
|
repetitionAndSequence: false,
|
||||||
words: [
|
personalInfo: false,
|
||||||
{ type: 'custom', value: 'test' },
|
words: ['test', 'teSt2', 'TesT3'],
|
||||||
{ type: 'custom', value: 'teSt2' },
|
|
||||||
{ type: 'personal', value: 'TesT3' },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject password with blacklisted words (case insensitive)', () => {
|
it('should reject password with blacklisted words (case insensitive)', () => {
|
||||||
expect(checker.hasWords('test')).toEqual([{ type: 'custom', value: 'test' }]);
|
expect(checker.hasWords('test')).toEqual(['test']);
|
||||||
expect(checker.hasWords('tEst2')).toEqual([
|
expect(checker.hasWords('tEst2')).toEqual(['test', 'test2']);
|
||||||
{ type: 'custom', value: 'test' },
|
expect(checker.hasWords('tEST TEst2 teSt3')).toEqual(['test', 'test2', 'test3']);
|
||||||
{ type: 'custom', value: 'test2' },
|
|
||||||
]);
|
|
||||||
expect(checker.hasWords('tEST TEst2 teSt3')).toEqual([
|
|
||||||
{ type: 'custom', value: 'test' },
|
|
||||||
{ type: 'custom', value: 'test2' },
|
|
||||||
{ type: 'personal', value: 'test3' },
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should accept password without blacklisted words', () => {
|
it('should accept password without blacklisted words', () => {
|
||||||
|
@ -177,7 +199,7 @@ describe('PasswordPolicyChecker -> hasSequentialChars()', () => {
|
||||||
const checker = new PasswordPolicyChecker({
|
const checker = new PasswordPolicyChecker({
|
||||||
length: { min: 1, max: 256 },
|
length: { min: 1, max: 256 },
|
||||||
characterTypes: { min: 2 },
|
characterTypes: { min: 2 },
|
||||||
rejects: { pwned: false, repetitionAndSequence: true, words: [] },
|
rejects: { pwned: false, repetitionAndSequence: true, personalInfo: false, words: [] },
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject password with too many sequential characters', () => {
|
it('should reject password with too many sequential characters', () => {
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
|
import { type DeepPartial } from '@silverhand/essentials';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
/** A word that used for password policy. */
|
|
||||||
type Word = {
|
|
||||||
type: 'custom' | 'personal';
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Password policy configuration type. */
|
/** Password policy configuration type. */
|
||||||
type PasswordPolicy = {
|
export type PasswordPolicy = {
|
||||||
/** Policy about password length. */
|
/** Policy about password length. */
|
||||||
length: {
|
length: {
|
||||||
/** Minimum password length. */
|
/** Minimum password length. */
|
||||||
|
@ -33,29 +28,38 @@ type PasswordPolicy = {
|
||||||
pwned: boolean;
|
pwned: boolean;
|
||||||
/** Whether to reject passwords that like '123456' or 'aaaaaa'. */
|
/** Whether to reject passwords that like '123456' or 'aaaaaa'. */
|
||||||
repetitionAndSequence: boolean;
|
repetitionAndSequence: boolean;
|
||||||
|
/** Whether to reject passwords that include personal information. */
|
||||||
|
personalInfo: boolean;
|
||||||
/** Whether to reject passwords that include specific words. */
|
/** Whether to reject passwords that include specific words. */
|
||||||
words: Word[];
|
words: string[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Password policy configuration guard. */
|
/** Password policy configuration guard. */
|
||||||
const passwordPolicyGuard: z.ZodType<PasswordPolicy> = z.object({
|
export const passwordPolicyGuard = z.object({
|
||||||
length: z.object({
|
length: z
|
||||||
min: z.number().int().min(1),
|
.object({
|
||||||
max: z.number().int().min(1),
|
min: z.number().int().min(1).default(8),
|
||||||
}),
|
max: z.number().int().min(1).default(256),
|
||||||
characterTypes: z.object({
|
})
|
||||||
min: z.number().int().min(1).max(4),
|
.default({}),
|
||||||
}),
|
characterTypes: z
|
||||||
rejects: z.object({
|
.object({
|
||||||
pwned: z.boolean(),
|
min: z.number().int().min(1).max(4).optional().default(2),
|
||||||
repetitionAndSequence: z.boolean(),
|
})
|
||||||
words: z.array(z.object({ type: z.enum(['custom', 'personal']), value: z.string() })),
|
.default({}),
|
||||||
}),
|
rejects: z
|
||||||
});
|
.object({
|
||||||
|
pwned: z.boolean().default(true),
|
||||||
|
repetitionAndSequence: z.boolean().default(true),
|
||||||
|
personalInfo: z.boolean().default(true),
|
||||||
|
words: z.string().array().default([]),
|
||||||
|
})
|
||||||
|
.default({}),
|
||||||
|
}) satisfies z.ZodType<PasswordPolicy, z.ZodTypeDef, DeepPartial<PasswordPolicy>>;
|
||||||
|
|
||||||
/** The code of why a password is rejected. */
|
/** The code of why a password is rejected. */
|
||||||
type PasswordRejectionCode =
|
export type PasswordRejectionCode =
|
||||||
| 'too_short'
|
| 'too_short'
|
||||||
| 'too_long'
|
| 'too_long'
|
||||||
| 'character_types'
|
| 'character_types'
|
||||||
|
@ -63,16 +67,25 @@ type PasswordRejectionCode =
|
||||||
| 'pwned'
|
| 'pwned'
|
||||||
| 'repetition'
|
| 'repetition'
|
||||||
| 'sequence'
|
| 'sequence'
|
||||||
|
| 'personal_info'
|
||||||
| 'restricted_words';
|
| 'restricted_words';
|
||||||
|
|
||||||
/** A password issue that does not meet the policy. */
|
/** A password issue that does not meet the policy. */
|
||||||
type PasswordIssue = {
|
export type PasswordIssue = {
|
||||||
/** Issue code. */
|
/** Issue code. */
|
||||||
code: `password_rejected.${PasswordRejectionCode}`;
|
code: `password_rejected.${PasswordRejectionCode}`;
|
||||||
/** Interpolation data for the issue message. */
|
/** Interpolation data for the issue message. */
|
||||||
interpolation?: Record<string, unknown>;
|
interpolation?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Personal information to check. */
|
||||||
|
export type PersonalInfo = Partial<{
|
||||||
|
name: string;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The class for checking if a password meets the policy. The policy is defined as
|
* The class for checking if a password meets the policy. The policy is defined as
|
||||||
* {@link PasswordPolicy}.
|
* {@link PasswordPolicy}.
|
||||||
|
@ -98,28 +111,73 @@ type PasswordIssue = {
|
||||||
export class PasswordPolicyChecker {
|
export class PasswordPolicyChecker {
|
||||||
static symbols = Object.freeze('!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' as const);
|
static symbols = Object.freeze('!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' as const);
|
||||||
|
|
||||||
constructor(public readonly policy: PasswordPolicy) {
|
public readonly policy: PasswordPolicy;
|
||||||
// Validate policy.
|
|
||||||
passwordPolicyGuard.parse(policy);
|
constructor(
|
||||||
|
policy: DeepPartial<PasswordPolicy>,
|
||||||
|
/** The Web Crypto API to use. By default, the global `crypto.subtle` will be used. */
|
||||||
|
protected readonly subtle: SubtleCrypto = crypto.subtle
|
||||||
|
) {
|
||||||
|
this.policy = passwordPolicyGuard.parse(policy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a password meets the policy.
|
* Check if a password meets all the policy requirements.
|
||||||
*
|
*
|
||||||
* @param password - Password to check.
|
* @param password - Password to check.
|
||||||
|
* @param personalInfo - Personal information to check. Required if the policy
|
||||||
|
* requires to reject passwords that include personal information.
|
||||||
* @returns An array of issues. If the password meets the policy, an empty array will be returned.
|
* @returns An array of issues. If the password meets the policy, an empty array will be returned.
|
||||||
|
* @throws TypeError - If the policy requires to reject passwords that include personal information
|
||||||
|
* but the personal information is not provided.
|
||||||
*/
|
*/
|
||||||
/* eslint-disable @silverhand/fp/no-mutating-methods */
|
/* eslint-disable @silverhand/fp/no-mutating-methods */
|
||||||
async check(password: string): Promise<PasswordIssue[]> {
|
|
||||||
|
async check(password: string, personalInfo?: PersonalInfo): Promise<PasswordIssue[]> {
|
||||||
|
const issues: PasswordIssue[] = this.fastCheck(password);
|
||||||
|
|
||||||
|
if (this.policy.rejects.pwned && (await this.hasBeenPwned(password))) {
|
||||||
|
issues.push({
|
||||||
|
code: 'password_rejected.pwned',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.policy.rejects.personalInfo) {
|
||||||
|
if (!personalInfo) {
|
||||||
|
throw new TypeError('Personal information is required to check personal information.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasPersonalInfo(password, personalInfo)) {
|
||||||
|
issues.push({
|
||||||
|
code: 'password_rejected.personal_info',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a fast check to see if the password passes the basic requirements.
|
||||||
|
* No pwned password and personal information check will be performed.
|
||||||
|
*
|
||||||
|
* This method is used for frontend validation.
|
||||||
|
*
|
||||||
|
* @param password - Password to check.
|
||||||
|
* @returns Whether the password passes the basic requirements.
|
||||||
|
*/
|
||||||
|
fastCheck(password: string) {
|
||||||
const issues: PasswordIssue[] = [];
|
const issues: PasswordIssue[] = [];
|
||||||
|
|
||||||
if (password.length < this.policy.length.min) {
|
if (password.length < this.policy.length.min) {
|
||||||
issues.push({
|
issues.push({
|
||||||
code: 'password_rejected.too_short',
|
code: 'password_rejected.too_short',
|
||||||
|
interpolation: { min: this.policy.length.min },
|
||||||
});
|
});
|
||||||
} else if (password.length > this.policy.length.max) {
|
} else if (password.length > this.policy.length.max) {
|
||||||
issues.push({
|
issues.push({
|
||||||
code: 'password_rejected.too_long',
|
code: 'password_rejected.too_long',
|
||||||
|
interpolation: { max: this.policy.length.max },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,12 +193,6 @@ export class PasswordPolicyChecker {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.policy.rejects.pwned && (await this.hasBeenPwned(password))) {
|
|
||||||
issues.push({
|
|
||||||
code: 'password_rejected.pwned',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.policy.rejects.repetitionAndSequence) {
|
if (this.policy.rejects.repetitionAndSequence) {
|
||||||
if (this.hasRepetition(password)) {
|
if (this.hasRepetition(password)) {
|
||||||
issues.push({
|
issues.push({
|
||||||
|
@ -155,12 +207,14 @@ export class PasswordPolicyChecker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
issues.push(
|
const words = this.hasWords(password);
|
||||||
...this.hasWords(password).map<PasswordIssue>(({ type, value }) => ({
|
|
||||||
|
if (words.length > 0) {
|
||||||
|
issues.push({
|
||||||
code: 'password_rejected.restricted_words',
|
code: 'password_rejected.restricted_words',
|
||||||
interpolation: { type, value },
|
interpolation: { words: words.join('\n'), count: words.length },
|
||||||
}))
|
});
|
||||||
);
|
}
|
||||||
|
|
||||||
return issues;
|
return issues;
|
||||||
}
|
}
|
||||||
|
@ -199,13 +253,13 @@ export class PasswordPolicyChecker {
|
||||||
* @returns Whether the password has been pwned.
|
* @returns Whether the password has been pwned.
|
||||||
*/
|
*/
|
||||||
async hasBeenPwned(password: string): Promise<boolean> {
|
async hasBeenPwned(password: string): Promise<boolean> {
|
||||||
const hash = await crypto.subtle.digest('SHA-1', new TextEncoder().encode(password));
|
const hash = await this.subtle.digest('SHA-1', new TextEncoder().encode(password));
|
||||||
const hashHex = Array.from(new Uint8Array(hash))
|
const hashHex = Array.from(new Uint8Array(hash))
|
||||||
.map((binary) => binary.toString(16).padStart(2, '0'))
|
.map((binary) => binary.toString(16).padStart(2, '0'))
|
||||||
.join('');
|
.join('');
|
||||||
const hashPrefix = hashHex.slice(0, 5);
|
const hashPrefix = hashHex.slice(0, 5);
|
||||||
const hashSuffix = hashHex.slice(5);
|
const hashSuffix = hashHex.slice(5);
|
||||||
const response = await fetch(`https://api.haveibeenpwned.com/range/${hashPrefix}`);
|
const response = await fetch(`https://api.pwnedpasswords.com/range/${hashPrefix}`);
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
const hashes = text.split('\n');
|
const hashes = text.split('\n');
|
||||||
const found = hashes.some((hex) => hex.toLowerCase().startsWith(hashSuffix));
|
const found = hashes.some((hex) => hex.toLowerCase().startsWith(hashSuffix));
|
||||||
|
@ -228,20 +282,53 @@ export class PasswordPolicyChecker {
|
||||||
return Boolean(matchedRepetition);
|
return Boolean(matchedRepetition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given password contains personal information.
|
||||||
|
*
|
||||||
|
* @param password - Password to check.
|
||||||
|
* @param personalInfo - Personal information to check.
|
||||||
|
* @returns Whether the password contains personal information.
|
||||||
|
*/
|
||||||
|
hasPersonalInfo(password: string, personalInfo: PersonalInfo): boolean {
|
||||||
|
const lowercasedPassword = password.toLowerCase();
|
||||||
|
const { name, username, email, phoneNumber } = personalInfo;
|
||||||
|
|
||||||
|
if (
|
||||||
|
name
|
||||||
|
?.toLowerCase()
|
||||||
|
.split(' ')
|
||||||
|
.some((word) => lowercasedPassword.includes(word))
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username && lowercasedPassword.includes(username.toLowerCase())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailPrefix = email?.split('@')[0];
|
||||||
|
if (emailPrefix && lowercasedPassword.includes(emailPrefix.toLowerCase())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (phoneNumber && lowercasedPassword.includes(phoneNumber)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the given password contains specific words.
|
* Check if the given password contains specific words.
|
||||||
*
|
*
|
||||||
* @param password - Password to check.
|
* @param password - Password to check.
|
||||||
* @returns An array of matched words.
|
* @returns An array of matched words.
|
||||||
*/
|
*/
|
||||||
hasWords(password: string): Word[] {
|
hasWords(password: string): string[] {
|
||||||
const words = this.policy.rejects.words.map(({ value, ...rest }) => ({
|
const words = this.policy.rejects.words.map((word) => word.toLowerCase());
|
||||||
...rest,
|
|
||||||
value: value.toLowerCase(),
|
|
||||||
}));
|
|
||||||
const lowercasedPassword = password.toLowerCase();
|
const lowercasedPassword = password.toLowerCase();
|
||||||
|
|
||||||
return words.filter(({ value }) => lowercasedPassword.includes(value));
|
return words.filter((word) => lowercasedPassword.includes(word));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
"@react-spring/web": "^9.6.1",
|
"@react-spring/web": "^9.6.1",
|
||||||
"@silverhand/eslint-config": "4.0.1",
|
"@silverhand/eslint-config": "4.0.1",
|
||||||
"@silverhand/eslint-config-react": "4.0.1",
|
"@silverhand/eslint-config-react": "4.0.1",
|
||||||
"@silverhand/essentials": "^2.5.0",
|
"@silverhand/essentials": "^2.8.4",
|
||||||
"@silverhand/ts-config": "4.0.0",
|
"@silverhand/ts-config": "4.0.0",
|
||||||
"@silverhand/ts-config-react": "4.0.0",
|
"@silverhand/ts-config-react": "4.0.0",
|
||||||
"@swc/core": "^1.3.52",
|
"@swc/core": "^1.3.52",
|
||||||
|
|
|
@ -204,6 +204,7 @@ export const mockSignInExperience: SignInExperience = {
|
||||||
signInMode: SignInMode.SignInAndRegister,
|
signInMode: SignInMode.SignInAndRegister,
|
||||||
customCss: null,
|
customCss: null,
|
||||||
customContent: {},
|
customContent: {},
|
||||||
|
passwordPolicy: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockSignInExperienceSettings: SignInExperienceResponse = {
|
export const mockSignInExperienceSettings: SignInExperienceResponse = {
|
||||||
|
@ -228,6 +229,7 @@ export const mockSignInExperienceSettings: SignInExperienceResponse = {
|
||||||
},
|
},
|
||||||
customCss: null,
|
customCss: null,
|
||||||
customContent: {},
|
customContent: {},
|
||||||
|
passwordPolicy: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const usernameSettings = {
|
const usernameSettings = {
|
||||||
|
|
213
pnpm-lock.yaml
213
pnpm-lock.yaml
|
@ -49,8 +49,8 @@ importers:
|
||||||
specifier: ^3.0.2
|
specifier: ^3.0.2
|
||||||
version: 3.0.2(tslib@2.4.1)(typescript@5.0.2)
|
version: 3.0.2(tslib@2.4.1)(typescript@5.0.2)
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
applicationinsights:
|
applicationinsights:
|
||||||
specifier: ^2.7.0
|
specifier: ^2.7.0
|
||||||
version: 2.7.0
|
version: 2.7.0
|
||||||
|
@ -125,8 +125,8 @@ importers:
|
||||||
specifier: workspace:^2.0.0
|
specifier: workspace:^2.0.0
|
||||||
version: link:../shared
|
version: link:../shared
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
chalk:
|
chalk:
|
||||||
specifier: ^5.0.0
|
specifier: ^5.0.0
|
||||||
version: 5.1.2
|
version: 5.1.2
|
||||||
|
@ -240,8 +240,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.10.5
|
specifier: ^1.10.5
|
||||||
version: 1.11.6
|
version: 1.11.6
|
||||||
|
@ -328,8 +328,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.10.5
|
specifier: ^1.10.5
|
||||||
version: 1.11.6
|
version: 1.11.6
|
||||||
|
@ -416,8 +416,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -495,8 +495,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -577,8 +577,8 @@ importers:
|
||||||
specifier: workspace:^2.0.0
|
specifier: workspace:^2.0.0
|
||||||
version: link:../../shared
|
version: link:../../shared
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -665,8 +665,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -747,8 +747,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -826,8 +826,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -905,8 +905,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -984,8 +984,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -1063,8 +1063,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -1145,8 +1145,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -1224,8 +1224,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -1303,8 +1303,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -1382,8 +1382,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -1461,8 +1461,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -1540,8 +1540,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.7.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -1619,8 +1619,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -1698,8 +1698,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -1777,8 +1777,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -1856,8 +1856,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -1935,8 +1935,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -2014,8 +2014,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -2099,8 +2099,8 @@ importers:
|
||||||
specifier: workspace:^2.0.0
|
specifier: workspace:^2.0.0
|
||||||
version: link:../../shared
|
version: link:../../shared
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -2184,8 +2184,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
fast-xml-parser:
|
fast-xml-parser:
|
||||||
specifier: ^4.2.5
|
specifier: ^4.2.5
|
||||||
version: 4.2.5
|
version: 4.2.5
|
||||||
|
@ -2269,8 +2269,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -2348,8 +2348,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -2427,8 +2427,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -2512,8 +2512,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -2591,8 +2591,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -2670,8 +2670,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -2749,8 +2749,8 @@ importers:
|
||||||
specifier: workspace:^1.1.0
|
specifier: workspace:^1.1.0
|
||||||
version: link:../../toolkit/connector-kit
|
version: link:../../toolkit/connector-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
got:
|
got:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -2888,8 +2888,8 @@ importers:
|
||||||
specifier: 4.0.1
|
specifier: 4.0.1
|
||||||
version: 4.0.1(eslint@8.44.0)(postcss@8.4.6)(prettier@3.0.0)(stylelint@15.0.0)(typescript@5.0.2)
|
version: 4.0.1(eslint@8.44.0)(postcss@8.4.6)(prettier@3.0.0)(stylelint@15.0.0)(typescript@5.0.2)
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
'@silverhand/ts-config':
|
'@silverhand/ts-config':
|
||||||
specifier: 4.0.0
|
specifier: 4.0.0
|
||||||
version: 4.0.0(typescript@5.0.2)
|
version: 4.0.0(typescript@5.0.2)
|
||||||
|
@ -3164,8 +3164,8 @@ importers:
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../ui
|
version: link:../ui
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
'@withtyped/client':
|
'@withtyped/client':
|
||||||
specifier: ^0.7.22
|
specifier: ^0.7.22
|
||||||
version: 0.7.22(zod@3.20.2)
|
version: 0.7.22(zod@3.20.2)
|
||||||
|
@ -3495,8 +3495,8 @@ importers:
|
||||||
specifier: 4.0.1
|
specifier: 4.0.1
|
||||||
version: 4.0.1(eslint@8.44.0)(prettier@3.0.0)(typescript@5.0.2)
|
version: 4.0.1(eslint@8.44.0)(prettier@3.0.0)(typescript@5.0.2)
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
'@silverhand/ts-config':
|
'@silverhand/ts-config':
|
||||||
specifier: 4.0.0
|
specifier: 4.0.0
|
||||||
version: 4.0.0(typescript@5.0.2)
|
version: 4.0.0(typescript@5.0.2)
|
||||||
|
@ -3555,8 +3555,8 @@ importers:
|
||||||
specifier: workspace:^1.0.0
|
specifier: workspace:^1.0.0
|
||||||
version: link:../toolkit/language-kit
|
version: link:../toolkit/language-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
zod:
|
zod:
|
||||||
specifier: ^3.20.2
|
specifier: ^3.20.2
|
||||||
version: 3.20.2
|
version: 3.20.2
|
||||||
|
@ -3582,12 +3582,15 @@ importers:
|
||||||
|
|
||||||
packages/phrases-ui:
|
packages/phrases-ui:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@logto/core-kit':
|
||||||
|
specifier: workspace:^2.0.1
|
||||||
|
version: link:../toolkit/core-kit
|
||||||
'@logto/language-kit':
|
'@logto/language-kit':
|
||||||
specifier: workspace:^1.0.0
|
specifier: workspace:^1.0.0
|
||||||
version: link:../toolkit/language-kit
|
version: link:../toolkit/language-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
zod:
|
zod:
|
||||||
specifier: ^3.20.2
|
specifier: ^3.20.2
|
||||||
version: 3.20.2
|
version: 3.20.2
|
||||||
|
@ -3645,8 +3648,8 @@ importers:
|
||||||
specifier: 4.0.1
|
specifier: 4.0.1
|
||||||
version: 4.0.1(eslint@8.44.0)(prettier@3.0.0)(typescript@5.0.2)
|
version: 4.0.1(eslint@8.44.0)(prettier@3.0.0)(typescript@5.0.2)
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
'@silverhand/ts-config':
|
'@silverhand/ts-config':
|
||||||
specifier: 4.0.0
|
specifier: 4.0.0
|
||||||
version: 4.0.0(typescript@5.0.2)
|
version: 4.0.0(typescript@5.0.2)
|
||||||
|
@ -3699,8 +3702,8 @@ importers:
|
||||||
packages/shared:
|
packages/shared:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
chalk:
|
chalk:
|
||||||
specifier: ^5.0.0
|
specifier: ^5.0.0
|
||||||
version: 5.1.2
|
version: 5.1.2
|
||||||
|
@ -3751,8 +3754,8 @@ importers:
|
||||||
specifier: workspace:^1.0.0
|
specifier: workspace:^1.0.0
|
||||||
version: link:../language-kit
|
version: link:../language-kit
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
'@withtyped/client':
|
'@withtyped/client':
|
||||||
specifier: ^0.7.22
|
specifier: ^0.7.22
|
||||||
version: 0.7.22(zod@3.20.2)
|
version: 0.7.22(zod@3.20.2)
|
||||||
|
@ -3821,8 +3824,8 @@ importers:
|
||||||
specifier: 4.0.1
|
specifier: 4.0.1
|
||||||
version: 4.0.1(eslint@8.44.0)(prettier@3.0.0)(typescript@5.0.2)
|
version: 4.0.1(eslint@8.44.0)(prettier@3.0.0)(typescript@5.0.2)
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
'@silverhand/ts-config':
|
'@silverhand/ts-config':
|
||||||
specifier: 4.0.0
|
specifier: 4.0.0
|
||||||
version: 4.0.0(typescript@5.0.2)
|
version: 4.0.0(typescript@5.0.2)
|
||||||
|
@ -3960,8 +3963,8 @@ importers:
|
||||||
specifier: 4.0.1
|
specifier: 4.0.1
|
||||||
version: 4.0.1(eslint@8.44.0)(postcss@8.4.6)(prettier@3.0.0)(stylelint@15.0.0)(typescript@5.0.2)
|
version: 4.0.1(eslint@8.44.0)(postcss@8.4.6)(prettier@3.0.0)(stylelint@15.0.0)(typescript@5.0.2)
|
||||||
'@silverhand/essentials':
|
'@silverhand/essentials':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.8.4
|
||||||
version: 2.5.0
|
version: 2.8.4
|
||||||
'@silverhand/ts-config':
|
'@silverhand/ts-config':
|
||||||
specifier: 4.0.0
|
specifier: 4.0.0
|
||||||
version: 4.0.0(typescript@5.0.2)
|
version: 4.0.0(typescript@5.0.2)
|
||||||
|
@ -7297,7 +7300,7 @@ packages:
|
||||||
resolution: {integrity: sha512-yDWSZMI2Qo/xoYU92tnwSP/gnSvq8+CLK5DqD/4brO42QJa7xjt7eA+HSyuMmSUrKffY2nP3riU81gs+nR8DkA==}
|
resolution: {integrity: sha512-yDWSZMI2Qo/xoYU92tnwSP/gnSvq8+CLK5DqD/4brO42QJa7xjt7eA+HSyuMmSUrKffY2nP3riU81gs+nR8DkA==}
|
||||||
engines: {node: ^18.12.0}
|
engines: {node: ^18.12.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@silverhand/essentials': 2.8.2
|
'@silverhand/essentials': 2.8.4
|
||||||
tiny-cookie: 2.4.1
|
tiny-cookie: 2.4.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
@ -7305,7 +7308,7 @@ packages:
|
||||||
resolution: {integrity: sha512-4XsXlCC0uZHcfazV09/4YKo4koqvSzQlkPUAToTp/WHpb6h2XDOJh5/hi55LXL4zp0PCcgpErKRxFCtgXCc6WQ==}
|
resolution: {integrity: sha512-4XsXlCC0uZHcfazV09/4YKo4koqvSzQlkPUAToTp/WHpb6h2XDOJh5/hi55LXL4zp0PCcgpErKRxFCtgXCc6WQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@logto/client': 2.2.0
|
'@logto/client': 2.2.0
|
||||||
'@silverhand/essentials': 2.7.0
|
'@silverhand/essentials': 2.8.4
|
||||||
js-base64: 3.7.5
|
js-base64: 3.7.5
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
@ -7313,7 +7316,7 @@ packages:
|
||||||
resolution: {integrity: sha512-vw8xDW8k38/58Q1r592z/9JdsmUh4+LMmoVm/Nu7LbWKlT32eD3H9hZDkFK9XEHpriifhI0hP7asGWEmhrEUuQ==}
|
resolution: {integrity: sha512-vw8xDW8k38/58Q1r592z/9JdsmUh4+LMmoVm/Nu7LbWKlT32eD3H9hZDkFK9XEHpriifhI0hP7asGWEmhrEUuQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@logto/js': 2.1.1
|
'@logto/js': 2.1.1
|
||||||
'@silverhand/essentials': 2.7.0
|
'@silverhand/essentials': 2.8.4
|
||||||
camelcase-keys: 7.0.2
|
camelcase-keys: 7.0.2
|
||||||
jose: 4.14.4
|
jose: 4.14.4
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -7322,7 +7325,7 @@ packages:
|
||||||
resolution: {integrity: sha512-7I2ELo5UWIJsFCYK/gX465l0+QhXTdyYWkgb2CcdPu5KbaPBNpASedm+fEV2NREYe2svbNODFhog6UMA/xGQnQ==}
|
resolution: {integrity: sha512-7I2ELo5UWIJsFCYK/gX465l0+QhXTdyYWkgb2CcdPu5KbaPBNpASedm+fEV2NREYe2svbNODFhog6UMA/xGQnQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@logto/js': 2.1.1
|
'@logto/js': 2.1.1
|
||||||
'@silverhand/essentials': 2.8.2
|
'@silverhand/essentials': 2.8.4
|
||||||
camelcase-keys: 7.0.2
|
camelcase-keys: 7.0.2
|
||||||
jose: 4.14.4
|
jose: 4.14.4
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -7331,7 +7334,7 @@ packages:
|
||||||
resolution: {integrity: sha512-zxy9zr5swOxbzYJNYtKXofj2tSIS9565d+1pT6RSbmx3Hn+JG6uzsb75PZXW9vlmmm7p1sGZeTQ+xVzKNFPsMg==}
|
resolution: {integrity: sha512-zxy9zr5swOxbzYJNYtKXofj2tSIS9565d+1pT6RSbmx3Hn+JG6uzsb75PZXW9vlmmm7p1sGZeTQ+xVzKNFPsMg==}
|
||||||
engines: {node: ^18.12.0}
|
engines: {node: ^18.12.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@silverhand/essentials': 2.7.0
|
'@silverhand/essentials': 2.8.4
|
||||||
'@withtyped/server': 0.12.8(zod@3.20.2)
|
'@withtyped/server': 0.12.8(zod@3.20.2)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- zod
|
- zod
|
||||||
|
@ -7341,7 +7344,7 @@ packages:
|
||||||
resolution: {integrity: sha512-dIrEUW7gi477HQpNsq/HT1gdvPK2ZmVuV73u2rH9LXGEIFIVGqmmIaaK3IcOPG110jKCBhTzF0+hKsW9Y3Pjmw==}
|
resolution: {integrity: sha512-dIrEUW7gi477HQpNsq/HT1gdvPK2ZmVuV73u2rH9LXGEIFIVGqmmIaaK3IcOPG110jKCBhTzF0+hKsW9Y3Pjmw==}
|
||||||
engines: {node: ^18.12.0}
|
engines: {node: ^18.12.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@silverhand/essentials': 2.8.2
|
'@silverhand/essentials': 2.8.4
|
||||||
'@withtyped/server': 0.12.9(zod@3.20.2)
|
'@withtyped/server': 0.12.9(zod@3.20.2)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- zod
|
- zod
|
||||||
|
@ -7350,7 +7353,7 @@ packages:
|
||||||
/@logto/js@2.1.1:
|
/@logto/js@2.1.1:
|
||||||
resolution: {integrity: sha512-PHikheavVK+l4ivgtzi14p184hEPgXjqQEAom1Gme1MZoopx+WlwxvHSEQBsmyvVqRtI0oiojhoU5tgYi1FKJw==}
|
resolution: {integrity: sha512-PHikheavVK+l4ivgtzi14p184hEPgXjqQEAom1Gme1MZoopx+WlwxvHSEQBsmyvVqRtI0oiojhoU5tgYi1FKJw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@silverhand/essentials': 2.7.0
|
'@silverhand/essentials': 2.8.4
|
||||||
camelcase-keys: 7.0.2
|
camelcase-keys: 7.0.2
|
||||||
jose: 4.14.2
|
jose: 4.14.2
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -7359,7 +7362,7 @@ packages:
|
||||||
resolution: {integrity: sha512-joSzzAqaRKeEquRenoFrIXXkNxkJci5zSkk4afywz1P8tTcTysnV4eXaBmwXNpmDfQdtHBwRdSACZPLgeF8JiQ==}
|
resolution: {integrity: sha512-joSzzAqaRKeEquRenoFrIXXkNxkJci5zSkk4afywz1P8tTcTysnV4eXaBmwXNpmDfQdtHBwRdSACZPLgeF8JiQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@logto/client': 2.1.0
|
'@logto/client': 2.1.0
|
||||||
'@silverhand/essentials': 2.7.0
|
'@silverhand/essentials': 2.8.4
|
||||||
js-base64: 3.7.5
|
js-base64: 3.7.5
|
||||||
node-fetch: 2.6.7
|
node-fetch: 2.6.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
@ -7372,7 +7375,7 @@ packages:
|
||||||
react: '>=16.8.0 || ^18.0.0'
|
react: '>=16.8.0 || ^18.0.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@logto/browser': 2.1.0
|
'@logto/browser': 2.1.0
|
||||||
'@silverhand/essentials': 2.7.0
|
'@silverhand/essentials': 2.8.4
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
@ -8955,16 +8958,8 @@ packages:
|
||||||
lodash: 4.17.21
|
lodash: 4.17.21
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@silverhand/essentials@2.5.0:
|
/@silverhand/essentials@2.8.4:
|
||||||
resolution: {integrity: sha512-8GgVFAmbo6S0EgsjYXH4aH8a69O7SzEtPFPDpVZmJuGEt8e3ODVx0F2V4rXyC3/SzFbcb2md2gRbA+Z6aTad6g==}
|
resolution: {integrity: sha512-VaI00QyD2trA7n7/wHNcGNGRXoSr8dUGs/hQCu4Rju4Edl3vso7CeCXdfGU2aNDuT2uMs75of6Ph8gqVJhWlYQ==}
|
||||||
engines: {node: ^16.13.0 || ^18.12.0 || ^19.2.0, pnpm: ^7}
|
|
||||||
|
|
||||||
/@silverhand/essentials@2.7.0:
|
|
||||||
resolution: {integrity: sha512-F5Qo5ZNnERUURK/9F1ZIi4FBDM22aeD59Zv0VtkgIhUL9tYK9svA2Jz88NNdYBwqCPrh8ExZlpFNi+pNmXKNlQ==}
|
|
||||||
engines: {node: ^16.13.0 || ^18.12.0 || ^19.2.0, pnpm: ^8.0.0}
|
|
||||||
|
|
||||||
/@silverhand/essentials@2.8.2:
|
|
||||||
resolution: {integrity: sha512-mrXzAQ6ZGyLoKQpfEOr/LmbQL7FIurVsBaymnsQyKNV56bFYCv5M+2irsAKROtmLMgSunAU/10XiDIaEYW9tbA==}
|
|
||||||
engines: {node: ^16.13.0 || ^18.12.0 || ^19.2.0, pnpm: ^8.0.0}
|
engines: {node: ^16.13.0 || ^18.12.0 || ^19.2.0, pnpm: ^8.0.0}
|
||||||
|
|
||||||
/@silverhand/ts-config-react@4.0.0(typescript@5.0.2):
|
/@silverhand/ts-config-react@4.0.0(typescript@5.0.2):
|
||||||
|
@ -10076,7 +10071,7 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
zod: ^3.19.1
|
zod: ^3.19.1
|
||||||
dependencies:
|
dependencies:
|
||||||
'@silverhand/essentials': 2.8.2
|
'@silverhand/essentials': 2.8.4
|
||||||
'@withtyped/shared': 0.2.2
|
'@withtyped/shared': 0.2.2
|
||||||
zod: 3.20.2
|
zod: 3.20.2
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -10086,7 +10081,7 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
zod: ^3.19.1
|
zod: ^3.19.1
|
||||||
dependencies:
|
dependencies:
|
||||||
'@silverhand/essentials': 2.8.2
|
'@silverhand/essentials': 2.8.4
|
||||||
'@withtyped/shared': 0.2.2
|
'@withtyped/shared': 0.2.2
|
||||||
zod: 3.20.2
|
zod: 3.20.2
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue