mirror of
https://github.com/logto-io/logto.git
synced 2025-03-31 22:51:25 -05:00
refactor(shared)!: standardize id and secret generators (#4550)
* refactor(shared)!: standardize id and secret generators * refactor: fix tests
This commit is contained in:
parent
d254dae5ff
commit
18181f892e
33 changed files with 148 additions and 111 deletions
15
.changeset/proud-donuts-check.md
Normal file
15
.changeset/proud-donuts-check.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
"@logto/shared": major
|
||||
"@logto/console": patch
|
||||
"@logto/schemas": patch
|
||||
"@logto/core": patch
|
||||
"@logto/cli": patch
|
||||
---
|
||||
|
||||
standardize id and secret generators
|
||||
|
||||
- Remove `buildIdGenerator` export from `@logto/shared`
|
||||
- Add `generateStandardSecret` and `generateStandardShortId` exports to `@logto/shared`
|
||||
- Align comment and implementation of `buildIdGenerator` in `@logto/shared`
|
||||
- The comment stated the function will include uppercase letters by default, but it did not; Now it does.
|
||||
- Use `generateStandardSecret` for all secret generation
|
|
@ -1,7 +1,7 @@
|
|||
import { generateKeyPair } from 'node:crypto';
|
||||
import { promisify } from 'node:util';
|
||||
|
||||
import { generateStandardId } from '@logto/shared';
|
||||
import { generateStandardSecret } from '@logto/shared';
|
||||
|
||||
export enum PrivateKeyType {
|
||||
RSA = 'rsa',
|
||||
|
@ -46,4 +46,4 @@ export const generateOidcPrivateKey = async (type: PrivateKeyType = PrivateKeyTy
|
|||
throw new Error(`Unsupported private key ${String(type)}`);
|
||||
};
|
||||
|
||||
export const generateOidcCookieKey = () => generateStandardId();
|
||||
export const generateOidcCookieKey = () => generateStandardSecret();
|
||||
|
|
|
@ -2,7 +2,6 @@ import UriInputField from '@/mdx-components/UriInputField';
|
|||
import Tabs from '@mdx/components/Tabs';
|
||||
import TabItem from '@mdx/components/TabItem';
|
||||
import InlineNotification from '@/ds-components/InlineNotification';
|
||||
import { buildIdGenerator } from '@logto/shared/universal';
|
||||
import Steps from '@/mdx-components/Steps';
|
||||
import Step from '@/mdx-components/Step';
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ import UriInputField from '@/mdx-components/UriInputField';
|
|||
import Tabs from '@mdx/components/Tabs';
|
||||
import TabItem from '@mdx/components/TabItem';
|
||||
import InlineNotification from '@/ds-components/InlineNotification';
|
||||
import { buildIdGenerator } from '@logto/shared/universal';
|
||||
import Steps from '@/mdx-components/Steps';
|
||||
import Step from '@/mdx-components/Step';
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import UriInputField from '@/mdx-components/UriInputField';
|
|||
import Tabs from '@mdx/components/Tabs';
|
||||
import TabItem from '@mdx/components/TabItem';
|
||||
import InlineNotification from '@/ds-components/InlineNotification';
|
||||
import { buildIdGenerator } from '@logto/shared/universal';
|
||||
import { generateStandardSecret } from '@logto/shared/universal';
|
||||
import Steps from '@/mdx-components/Steps';
|
||||
import Step from '@/mdx-components/Step';
|
||||
|
||||
|
@ -77,7 +77,7 @@ The SDK requires [express-session](https://www.npmjs.com/package/express-session
|
|||
import session from 'express-session';
|
||||
|
||||
app.use(cookieParser());
|
||||
app.use(session({ secret: '${buildIdGenerator(32)()}', cookie: { maxAge: 14 * 24 * 60 * 60 } }));`}
|
||||
app.use(session({ secret: '${generateStandardSecret()}', cookie: { maxAge: 14 * 24 * 60 * 60 } }));`}
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import UriInputField from '@/mdx-components/UriInputField';
|
|||
import Tabs from '@mdx/components/Tabs';
|
||||
import TabItem from '@mdx/components/TabItem';
|
||||
import InlineNotification from '@/ds-components/InlineNotification';
|
||||
import { buildIdGenerator } from '@logto/shared/universal';
|
||||
import { generateStandardSecret } from '@logto/shared/universal';
|
||||
import Steps from '@/mdx-components/Steps';
|
||||
import Step from '@/mdx-components/Step';
|
||||
|
||||
|
@ -58,7 +58,7 @@ export const logtoClient = new LogtoClient({
|
|||
appId: '${props.app.id}',
|
||||
appSecret: '${props.app.secret}',
|
||||
baseUrl: 'http://localhost:3000', // Change to your own base URL
|
||||
cookieSecret: '${buildIdGenerator(32)()}', // Auto-generated 32 digit secret
|
||||
cookieSecret: '${generateStandardSecret()}', // Auto-generated 32 digit secret
|
||||
cookieSecure: process.env.NODE_ENV === 'production',
|
||||
});`}
|
||||
</code>
|
||||
|
|
|
@ -2,7 +2,7 @@ import UriInputField from '@/mdx-components/UriInputField';
|
|||
import Tabs from '@mdx/components/Tabs';
|
||||
import TabItem from '@mdx/components/TabItem';
|
||||
import InlineNotification from '@/ds-components/InlineNotification';
|
||||
import { buildIdGenerator } from '@logto/shared/universal';
|
||||
import { generateStandardSecret } from '@logto/shared/universal';
|
||||
import Steps from '@/mdx-components/Steps';
|
||||
import Step from '@/mdx-components/Step';
|
||||
|
||||
|
@ -58,7 +58,7 @@ export const logtoClient = new LogtoClient({
|
|||
appId: '${props.app.id}',
|
||||
appSecret: '${props.app.secret}',
|
||||
baseUrl: 'http://localhost:3000', // Change to your own base URL
|
||||
cookieSecret: '${buildIdGenerator(32)()}', // Auto-generated 32 digit secret
|
||||
cookieSecret: '${generateStandardSecret()}', // Auto-generated 32 digit secret
|
||||
cookieSecure: process.env.NODE_ENV === 'production',
|
||||
});`}
|
||||
</code>
|
||||
|
|
|
@ -2,7 +2,6 @@ import UriInputField from '@/mdx-components/UriInputField';
|
|||
import Tabs from '@mdx/components/Tabs';
|
||||
import TabItem from '@mdx/components/TabItem';
|
||||
import InlineNotification from '@/ds-components/InlineNotification';
|
||||
import { buildIdGenerator } from '@logto/shared/universal';
|
||||
import Steps from '@/mdx-components/Steps';
|
||||
import Step from '@/mdx-components/Step';
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ import UriInputField from '@/mdx-components/UriInputField';
|
|||
import Tabs from '@mdx/components/Tabs';
|
||||
import TabItem from '@mdx/components/TabItem';
|
||||
import InlineNotification from '@/ds-components/InlineNotification';
|
||||
import { buildIdGenerator } from '@logto/shared/universal';
|
||||
import Steps from '@/mdx-components/Steps';
|
||||
import Step from '@/mdx-components/Step';
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import UriInputField from '@/mdx-components/UriInputField';
|
|||
import Tabs from '@mdx/components/Tabs';
|
||||
import TabItem from '@mdx/components/TabItem';
|
||||
import InlineNotification from '@/ds-components/InlineNotification';
|
||||
import { buildIdGenerator } from '@logto/shared/universal';
|
||||
import { generateStandardSecret } from '@logto/shared/universal';
|
||||
import Steps from '@/mdx-components/Steps';
|
||||
import Step from '@/mdx-components/Step';
|
||||
|
||||
|
@ -55,7 +55,7 @@ const sessionStorage = createCookieSessionStorage({
|
|||
cookie: {
|
||||
name: "logto-session",
|
||||
maxAge: 14 * 24 * 60 * 60,
|
||||
secrets: '${buildIdGenerator(12)()}', // Auto-generated secret
|
||||
secrets: '${generateStandardSecret()}', // Auto-generated secret
|
||||
},
|
||||
});`}
|
||||
</code>
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { SocialUserInfo } from '@logto/connector-kit';
|
|||
import { socialUserInfoGuard } from '@logto/connector-kit';
|
||||
import { Theme } from '@logto/schemas';
|
||||
import type { ConnectorResponse, UserProfileResponse } from '@logto/schemas';
|
||||
import { buildIdGenerator } from '@logto/shared/universal';
|
||||
import { generateStandardId } from '@logto/shared/universal';
|
||||
import type { Optional } from '@silverhand/essentials';
|
||||
import { appendPath, conditional } from '@silverhand/essentials';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
@ -42,7 +42,7 @@ function LinkAccountSection({ user, connectors, onUpdate }: Props) {
|
|||
const getSocialAuthorizationUri = useCallback(
|
||||
async (connectorId: string) => {
|
||||
const adminTenantEndpointUrl = new URL(adminTenantEndpoint);
|
||||
const state = buildIdGenerator(8)();
|
||||
const state = generateStandardId(8);
|
||||
const redirectUri = new URL(`/callback/${connectorId}`, adminTenantEndpointUrl).href;
|
||||
const { redirectTo } = await api
|
||||
.post('me/social/authorization-uri', { json: { connectorId, state, redirectUri } })
|
||||
|
|
|
@ -11,6 +11,8 @@ import type {
|
|||
} from '@logto/schemas';
|
||||
import { RoleType, ApplicationType } from '@logto/schemas';
|
||||
|
||||
import { mockId } from '#src/test-utils/nanoid.js';
|
||||
|
||||
export * from './connector.js';
|
||||
export * from './sign-in-experience.js';
|
||||
export * from './cloud-connection.js';
|
||||
|
@ -20,7 +22,7 @@ export * from './domain.js';
|
|||
export const mockApplication: Application = {
|
||||
tenantId: 'fake_tenant',
|
||||
id: 'foo',
|
||||
secret: 'randomId',
|
||||
secret: mockId,
|
||||
name: 'foo',
|
||||
type: ApplicationType.SPA,
|
||||
description: null,
|
||||
|
|
|
@ -3,17 +3,14 @@ import { HookEvent, InteractionEvent, LogResult } from '@logto/schemas';
|
|||
import { createMockUtils } from '@logto/shared/esm';
|
||||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { mockId, mockIdGenerators } from '#src/test-utils/nanoid.js';
|
||||
|
||||
import { generateHookTestPayload, parseResponse } from './utils.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
const { mockEsmWithActual, mockEsm } = createMockUtils(jest);
|
||||
const { mockEsm } = createMockUtils(jest);
|
||||
|
||||
const nanoIdMock = 'mockId';
|
||||
await mockEsmWithActual('@logto/shared', () => ({
|
||||
buildIdGenerator: jest.fn().mockReturnValue(nanoIdMock),
|
||||
generateStandardId: jest.fn().mockReturnValue(nanoIdMock),
|
||||
}));
|
||||
await mockIdGenerators();
|
||||
|
||||
const mockSignature = 'mockSignature';
|
||||
mockEsm('#src/utils/sign.js', () => ({
|
||||
|
@ -95,7 +92,7 @@ describe('triggerInteractionHooks()', () => {
|
|||
});
|
||||
|
||||
const calledPayload: unknown = insertLog.mock.calls[0][0];
|
||||
expect(calledPayload).toHaveProperty('id', nanoIdMock);
|
||||
expect(calledPayload).toHaveProperty('id', mockId);
|
||||
expect(calledPayload).toHaveProperty('key', 'TriggerHook.' + HookEvent.PostSignIn);
|
||||
expect(calledPayload).toHaveProperty('payload.result', LogResult.Success);
|
||||
expect(calledPayload).toHaveProperty('payload.hookId', 'foo');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { User, CreateUser, Scope } from '@logto/schemas';
|
||||
import { Users, UsersPasswordEncryptionMethod } from '@logto/schemas';
|
||||
import { buildIdGenerator, generateStandardId } from '@logto/shared';
|
||||
import { generateStandardShortId, generateStandardId } from '@logto/shared';
|
||||
import type { OmitAutoSetFields } from '@logto/shared';
|
||||
import type { Nullable } from '@silverhand/essentials';
|
||||
import { deduplicate } from '@silverhand/essentials';
|
||||
|
@ -15,8 +15,6 @@ import type Queries from '#src/tenants/Queries.js';
|
|||
import assertThat from '#src/utils/assert-that.js';
|
||||
import { encryptPassword } from '#src/utils/password.js';
|
||||
|
||||
const userId = buildIdGenerator(12);
|
||||
|
||||
export const encryptUserPassword = async (
|
||||
password: string
|
||||
): Promise<{
|
||||
|
@ -64,7 +62,7 @@ export const createUserLibrary = (queries: Queries) => {
|
|||
const generateUserId = async (retries = 500) =>
|
||||
pRetry(
|
||||
async () => {
|
||||
const id = userId();
|
||||
const id = generateStandardShortId();
|
||||
|
||||
if (!(await hasUserWithId(id))) {
|
||||
return id;
|
||||
|
|
|
@ -3,18 +3,15 @@ import { LogResult } from '@logto/schemas';
|
|||
import { pickDefault, createMockUtils } from '@logto/shared/esm';
|
||||
import i18next from 'i18next';
|
||||
|
||||
import { mockId, mockIdGenerators } from '#src/test-utils/nanoid.js';
|
||||
|
||||
import type { WithLogContext, LogPayload } from './koa-audit-log.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
const { mockEsmWithActual } = createMockUtils(jest);
|
||||
|
||||
const nanoIdMock = 'mockId';
|
||||
await mockEsmWithActual('@logto/shared', () => ({
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
buildIdGenerator: () => () => nanoIdMock,
|
||||
generateStandardId: () => nanoIdMock,
|
||||
}));
|
||||
await mockIdGenerators();
|
||||
|
||||
const { default: RequestError } = await import('#src/errors/RequestError/index.js');
|
||||
const { MockQueries } = await import('#src/test-utils/tenant.js');
|
||||
|
@ -56,7 +53,7 @@ describe('koaAuditLog middleware', () => {
|
|||
await koaLog(queries)(ctx, next);
|
||||
|
||||
expect(insertLog).toBeCalledWith({
|
||||
id: nanoIdMock,
|
||||
id: mockId,
|
||||
key: logKey,
|
||||
payload: {
|
||||
...mockPayload,
|
||||
|
@ -95,12 +92,12 @@ describe('koaAuditLog middleware', () => {
|
|||
};
|
||||
|
||||
expect(insertLog).toHaveBeenCalledWith({
|
||||
id: nanoIdMock,
|
||||
id: mockId,
|
||||
key: logKey,
|
||||
payload: basePayload,
|
||||
});
|
||||
expect(insertLog).toHaveBeenCalledWith({
|
||||
id: nanoIdMock,
|
||||
id: mockId,
|
||||
key: logKey,
|
||||
payload: {
|
||||
...basePayload,
|
||||
|
@ -147,7 +144,7 @@ describe('koaAuditLog middleware', () => {
|
|||
await koaLog(queries)(ctx, next);
|
||||
|
||||
expect(insertLog).toBeCalledWith({
|
||||
id: nanoIdMock,
|
||||
id: mockId,
|
||||
key: logKey,
|
||||
payload: {
|
||||
...mockPayload,
|
||||
|
@ -179,7 +176,7 @@ describe('koaAuditLog middleware', () => {
|
|||
await expect(koaLog(queries)(ctx, next)).rejects.toMatchError(error);
|
||||
|
||||
expect(insertLog).toBeCalledWith({
|
||||
id: nanoIdMock,
|
||||
id: mockId,
|
||||
key: logKey,
|
||||
payload: {
|
||||
...mockPayload,
|
||||
|
@ -216,7 +213,7 @@ describe('koaAuditLog middleware', () => {
|
|||
|
||||
expect(insertLog).toHaveBeenCalledTimes(2);
|
||||
expect(insertLog).toBeCalledWith({
|
||||
id: nanoIdMock,
|
||||
id: mockId,
|
||||
key: logKey,
|
||||
payload: {
|
||||
...mockPayload,
|
||||
|
|
|
@ -8,13 +8,13 @@ import {
|
|||
mockAdminUserRole3,
|
||||
mockUserRole,
|
||||
} from '#src/__mocks__/index.js';
|
||||
import { mockId, mockStandardId } from '#src/test-utils/nanoid.js';
|
||||
import { mockId, mockIdGenerators } from '#src/test-utils/nanoid.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
await mockStandardId();
|
||||
await mockIdGenerators();
|
||||
|
||||
const users = { findUserById: jest.fn() };
|
||||
|
||||
|
|
|
@ -7,13 +7,13 @@ import {
|
|||
mockAdminUserRole2,
|
||||
mockApplicationRole,
|
||||
} from '#src/__mocks__/index.js';
|
||||
import { mockId, mockStandardId } from '#src/test-utils/nanoid.js';
|
||||
import { mockId, mockIdGenerators } from '#src/test-utils/nanoid.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
await mockStandardId();
|
||||
await mockIdGenerators();
|
||||
|
||||
const mockM2mApplication = { ...mockApplication, type: ApplicationType.MachineToMachine };
|
||||
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
import type { Application, CreateApplication } from '@logto/schemas';
|
||||
import { ApplicationType } from '@logto/schemas';
|
||||
import { pickDefault, createMockUtils } from '@logto/shared/esm';
|
||||
import { pickDefault } from '@logto/shared/esm';
|
||||
|
||||
import { mockApplication } from '#src/__mocks__/index.js';
|
||||
import { mockId, mockIdGenerators } from '#src/test-utils/nanoid.js';
|
||||
import { createMockQuotaLibrary } from '#src/test-utils/quota.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
const { mockEsmWithActual } = createMockUtils(jest);
|
||||
|
||||
const findApplicationById = jest.fn(async () => mockApplication);
|
||||
const deleteApplicationById = jest.fn();
|
||||
|
||||
await mockEsmWithActual('@logto/shared', () => ({
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
buildIdGenerator: () => () => 'randomId',
|
||||
generateStandardId: () => 'randomId',
|
||||
}));
|
||||
await mockIdGenerators();
|
||||
|
||||
const tenantContext = new MockTenant(
|
||||
undefined,
|
||||
|
@ -83,8 +79,8 @@ describe('application route', () => {
|
|||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({
|
||||
...mockApplication,
|
||||
id: 'randomId',
|
||||
secret: 'randomId',
|
||||
id: mockId,
|
||||
secret: mockId,
|
||||
name,
|
||||
description,
|
||||
type,
|
||||
|
@ -101,7 +97,7 @@ describe('application route', () => {
|
|||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({
|
||||
...mockApplication,
|
||||
id: 'randomId',
|
||||
id: mockId,
|
||||
name,
|
||||
type,
|
||||
customClientMetadata,
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
InternalRole,
|
||||
ApplicationType,
|
||||
} from '@logto/schemas';
|
||||
import { generateStandardId, buildIdGenerator } from '@logto/shared';
|
||||
import { generateStandardId, generateStandardSecret } from '@logto/shared';
|
||||
import { boolean, object, string, z } from 'zod';
|
||||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
|
@ -18,7 +18,6 @@ import { parseSearchParamsForSearch } from '#src/utils/search.js';
|
|||
|
||||
import type { AuthedRouter, RouterInitArgs } from './types.js';
|
||||
|
||||
const applicationId = buildIdGenerator(21);
|
||||
const includesInternalAdminRole = (roles: Readonly<Array<{ role: Role }>>) =>
|
||||
roles.some(({ role: { name } }) => name === InternalRole.Admin);
|
||||
|
||||
|
@ -108,8 +107,8 @@ export default function applicationRoutes<T extends AuthedRouter>(
|
|||
);
|
||||
|
||||
ctx.body = await insertApplication({
|
||||
id: applicationId(),
|
||||
secret: generateStandardId(),
|
||||
id: generateStandardId(),
|
||||
secret: generateStandardSecret(),
|
||||
oidcClientMetadata: buildOidcClientMetadata(oidcClientMetadata),
|
||||
...rest,
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { type ConnectorFactory, buildRawConnector } from '@logto/cli/lib/connector/index.js';
|
||||
import { demoConnectorIds, validateConfig } from '@logto/connector-kit';
|
||||
import { Connectors, ConnectorType, connectorResponseGuard, type JsonObject } from '@logto/schemas';
|
||||
import { buildIdGenerator } from '@logto/shared';
|
||||
import { generateStandardShortId } from '@logto/shared';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import cleanDeep from 'clean-deep';
|
||||
import { string, object } from 'zod';
|
||||
|
@ -20,8 +20,6 @@ import connectorAuthorizationUriRoutes from './authorization-uri.js';
|
|||
import connectorConfigTestingRoutes from './config-testing.js';
|
||||
import connectorFactoryRoutes from './factory.js';
|
||||
|
||||
const generateConnectorId = buildIdGenerator(12);
|
||||
|
||||
const guardConnectorsQuota = async (factory: ConnectorFactory, quota: QuotaLibrary) => {
|
||||
if (factory.metadata.isStandard) {
|
||||
await quota.guardKey('standardConnectorsLimit');
|
||||
|
@ -130,7 +128,7 @@ export default function connectorRoutes<T extends AuthedRouter>(
|
|||
validateConfig(config, rawConnector.configGuard);
|
||||
}
|
||||
|
||||
const insertConnectorId = proposedId ?? generateConnectorId();
|
||||
const insertConnectorId = proposedId ?? generateStandardShortId();
|
||||
await insertConnector({
|
||||
id: insertConnectorId,
|
||||
connectorId,
|
||||
|
|
|
@ -5,14 +5,14 @@ import { pickDefault, createMockUtils } from '@logto/shared/esm';
|
|||
import { mockZhCnCustomPhrase, trTrTag, zhCnTag } from '#src/__mocks__/custom-phrase.js';
|
||||
import { mockSignInExperience } from '#src/__mocks__/index.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { mockStandardId } from '#src/test-utils/nanoid.js';
|
||||
import { mockIdGenerators } from '#src/test-utils/nanoid.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
const { mockEsm } = createMockUtils(jest);
|
||||
|
||||
await mockStandardId();
|
||||
await mockIdGenerators();
|
||||
|
||||
const mockLanguageTag = zhCnTag;
|
||||
const mockPhrase = mockZhCnCustomPhrase;
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
type HookResponse,
|
||||
type Hook,
|
||||
} from '@logto/schemas';
|
||||
import { generateStandardId } from '@logto/shared';
|
||||
import { generateStandardId, generateStandardSecret } from '@logto/shared';
|
||||
import { conditional, deduplicate, yes } from '@silverhand/essentials';
|
||||
import { subDays } from 'date-fns';
|
||||
import { z } from 'zod';
|
||||
|
@ -171,7 +171,7 @@ export default function hookRoutes<T extends AuthedRouter>(
|
|||
ctx.body = await insertHook({
|
||||
...rest,
|
||||
id: generateStandardId(),
|
||||
signingKey: generateStandardId(),
|
||||
signingKey: generateStandardSecret(),
|
||||
events: events ?? [],
|
||||
enabled: enabled ?? true,
|
||||
...conditional(event && { event }),
|
||||
|
@ -242,7 +242,7 @@ export default function hookRoutes<T extends AuthedRouter>(
|
|||
} = ctx.guard;
|
||||
|
||||
ctx.body = await updateHookById(id, {
|
||||
signingKey: generateStandardId(),
|
||||
signingKey: generateStandardSecret(),
|
||||
});
|
||||
|
||||
return next();
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import type { Resource, CreateResource } from '@logto/schemas';
|
||||
import { pickDefault, createMockUtils } from '@logto/shared/esm';
|
||||
import { pickDefault } from '@logto/shared/esm';
|
||||
import { type Nullable } from '@silverhand/essentials';
|
||||
|
||||
import { mockResource, mockScope } from '#src/__mocks__/index.js';
|
||||
import { mockId, mockIdGenerators } from '#src/test-utils/nanoid.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
const { mockEsm } = createMockUtils(jest);
|
||||
|
||||
const resources = {
|
||||
findTotalNumberOfResources: async () => ({ count: 10 }),
|
||||
findAllResources: async (): Promise<Resource[]> => [mockResource],
|
||||
|
@ -45,10 +44,7 @@ const scopes = {
|
|||
};
|
||||
const { insertScope, updateScopeById } = scopes;
|
||||
|
||||
mockEsm('@logto/shared', () => ({
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
buildIdGenerator: () => () => 'randomId',
|
||||
}));
|
||||
await mockIdGenerators();
|
||||
|
||||
const tenantContext = new MockTenant(undefined, { scopes, resources }, undefined);
|
||||
|
||||
|
@ -89,7 +85,7 @@ describe('resource routes', () => {
|
|||
expect(response.status).toEqual(201);
|
||||
expect(response.body).toEqual({
|
||||
tenantId: 'fake_tenant',
|
||||
id: 'randomId',
|
||||
id: mockId,
|
||||
name,
|
||||
indicator,
|
||||
isDefault: false,
|
||||
|
@ -183,7 +179,7 @@ describe('resource routes', () => {
|
|||
expect(response.status).toEqual(201);
|
||||
expect(findResourceById).toHaveBeenCalledWith('foo');
|
||||
expect(insertScope).toHaveBeenCalledWith({
|
||||
id: 'randomId',
|
||||
id: mockId,
|
||||
name,
|
||||
description,
|
||||
resourceId: 'foo',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Resources, Scopes } from '@logto/schemas';
|
||||
import { buildIdGenerator } from '@logto/shared';
|
||||
import { generateStandardId } from '@logto/shared';
|
||||
import { tryThat, yes } from '@silverhand/essentials';
|
||||
import { boolean, object, string } from 'zod';
|
||||
|
||||
|
@ -13,9 +13,6 @@ import { parseSearchParamsForSearch } from '#src/utils/search.js';
|
|||
|
||||
import type { AuthedRouter, RouterInitArgs } from './types.js';
|
||||
|
||||
const resourceId = buildIdGenerator(21);
|
||||
const scopeId = resourceId;
|
||||
|
||||
export default function resourceRoutes<T extends AuthedRouter>(
|
||||
...[
|
||||
router,
|
||||
|
@ -110,7 +107,7 @@ export default function resourceRoutes<T extends AuthedRouter>(
|
|||
);
|
||||
|
||||
const resource = await insertResource({
|
||||
id: resourceId(),
|
||||
id: generateStandardId(),
|
||||
...body,
|
||||
});
|
||||
|
||||
|
@ -280,7 +277,7 @@ export default function resourceRoutes<T extends AuthedRouter>(
|
|||
ctx.status = 201;
|
||||
ctx.body = await insertScope({
|
||||
...body,
|
||||
id: scopeId(),
|
||||
id: generateStandardId(),
|
||||
resourceId,
|
||||
});
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { pickDefault } from '@logto/shared/esm';
|
||||
|
||||
import { mockAdminApplicationRole, mockApplication } from '#src/__mocks__/index.js';
|
||||
import { mockId, mockStandardId } from '#src/test-utils/nanoid.js';
|
||||
import { mockId, mockIdGenerators } from '#src/test-utils/nanoid.js';
|
||||
import { createMockQuotaLibrary } from '#src/test-utils/quota.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
await mockStandardId();
|
||||
await mockIdGenerators();
|
||||
|
||||
const roles = {
|
||||
findRoleById: jest.fn(),
|
||||
|
|
|
@ -7,14 +7,14 @@ import {
|
|||
mockResource,
|
||||
mockScopeWithResource,
|
||||
} from '#src/__mocks__/index.js';
|
||||
import { mockId, mockStandardId } from '#src/test-utils/nanoid.js';
|
||||
import { mockId, mockIdGenerators } from '#src/test-utils/nanoid.js';
|
||||
import { createMockQuotaLibrary } from '#src/test-utils/quota.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
await mockStandardId();
|
||||
await mockIdGenerators();
|
||||
|
||||
const roles = {
|
||||
findRoles: jest.fn(async (): Promise<Role[]> => [mockAdminUserRole]),
|
||||
|
|
|
@ -2,14 +2,14 @@ import type { Role } from '@logto/schemas';
|
|||
import { pickDefault } from '@logto/shared/esm';
|
||||
|
||||
import { mockAdminUserRole, mockScope, mockUser } from '#src/__mocks__/index.js';
|
||||
import { mockStandardId } from '#src/test-utils/nanoid.js';
|
||||
import { mockIdGenerators } from '#src/test-utils/nanoid.js';
|
||||
import { createMockQuotaLibrary } from '#src/test-utils/quota.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
await mockStandardId();
|
||||
await mockIdGenerators();
|
||||
|
||||
const roles = {
|
||||
findRoles: jest.fn(async (): Promise<Role[]> => [mockAdminUserRole]),
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { pickDefault } from '@logto/shared/esm';
|
||||
|
||||
import { mockAdminUserRole, mockUser } from '#src/__mocks__/index.js';
|
||||
import { mockId, mockStandardId } from '#src/test-utils/nanoid.js';
|
||||
import { mockId, mockIdGenerators } from '#src/test-utils/nanoid.js';
|
||||
import { createMockQuotaLibrary } from '#src/test-utils/quota.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
await mockStandardId();
|
||||
await mockIdGenerators();
|
||||
|
||||
const roles = {
|
||||
findRoleById: jest.fn(),
|
||||
|
|
|
@ -2,8 +2,19 @@ import { createMockUtils } from '@logto/shared/esm';
|
|||
|
||||
const { mockEsmWithActual } = createMockUtils(import.meta.jest);
|
||||
|
||||
/** The mock id generated by all id generators. */
|
||||
export const mockId = 'mockId';
|
||||
export const mockStandardId = async () =>
|
||||
|
||||
/**
|
||||
* Mock all id generators to return the same {@link mockId}. List of id generators:
|
||||
*
|
||||
* - generateStandardId
|
||||
* - generateStandardShortId
|
||||
* - generateStandardSecret
|
||||
*/
|
||||
export const mockIdGenerators = async () =>
|
||||
mockEsmWithActual('@logto/shared', () => ({
|
||||
generateStandardId: () => mockId,
|
||||
generateStandardShortId: () => mockId,
|
||||
generateStandardSecret: () => mockId,
|
||||
}));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { generateStandardId } from '@logto/shared/universal';
|
||||
import { generateStandardId, generateStandardSecret } from '@logto/shared/universal';
|
||||
|
||||
import type {
|
||||
Application,
|
||||
|
@ -35,7 +35,7 @@ export const createDefaultAdminConsoleApplication = (): Readonly<CreateApplicati
|
|||
tenantId: adminTenantId,
|
||||
id: adminConsoleApplicationId,
|
||||
name: 'Admin Console',
|
||||
secret: generateStandardId(),
|
||||
secret: generateStandardSecret(),
|
||||
description: 'Logto Admin Console.',
|
||||
type: ApplicationType.SPA,
|
||||
oidcClientMetadata: { redirectUris: [], postLogoutRedirectUris: [] },
|
||||
|
@ -49,7 +49,7 @@ export const createTenantMachineToMachineApplication = (
|
|||
id: generateStandardId(),
|
||||
name: 'Cloud Service',
|
||||
description: `Machine to machine application for tenant ${tenantId}`,
|
||||
secret: generateStandardId(),
|
||||
secret: generateStandardSecret(),
|
||||
type: ApplicationType.MachineToMachine,
|
||||
oidcClientMetadata: {
|
||||
redirectUris: [],
|
||||
|
|
|
@ -1,15 +1,29 @@
|
|||
import { buildIdGenerator } from './id.js';
|
||||
import { generateStandardId, generateStandardSecret, generateStandardShortId } from './id.js';
|
||||
|
||||
describe('id generator', () => {
|
||||
describe('standard id generator', () => {
|
||||
it('should match the input length', () => {
|
||||
const id = buildIdGenerator(10)();
|
||||
expect(id.length).toEqual(10);
|
||||
});
|
||||
|
||||
it('to random id should not equal', () => {
|
||||
const id_1 = buildIdGenerator(10)();
|
||||
const id_2 = buildIdGenerator(10)();
|
||||
|
||||
expect(id_1).not.toEqual(id_2);
|
||||
const id = generateStandardId();
|
||||
expect(id.length).toEqual(21);
|
||||
});
|
||||
});
|
||||
|
||||
describe('standard short id generator', () => {
|
||||
it('should match the input length', () => {
|
||||
const id = generateStandardShortId();
|
||||
expect(id.length).toEqual(12);
|
||||
});
|
||||
});
|
||||
|
||||
describe('standard secret generator', () => {
|
||||
it('should match the input length', () => {
|
||||
const id = generateStandardSecret();
|
||||
expect(id.length).toEqual(32);
|
||||
});
|
||||
|
||||
it('should generate id with uppercase', () => {
|
||||
// If it can't generate uppercase, it will timeout
|
||||
while (!/[A-Z]/.test(generateStandardSecret())) {
|
||||
// Do nothing
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { customAlphabet } from 'nanoid';
|
||||
|
||||
const lowercaseAlphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||
const lowercaseAlphabet = '0123456789abcdefghijklmnopqrstuvwxyz' as const;
|
||||
const alphabet = `${lowercaseAlphabet}ABCDEFGHIJKLMNOPQRSTUVWXYZ` as const;
|
||||
|
||||
type BuildIdGenerator = {
|
||||
|
@ -17,6 +17,27 @@ type BuildIdGenerator = {
|
|||
(size: number, includingUppercase: false): ReturnType<typeof customAlphabet>;
|
||||
};
|
||||
|
||||
export const buildIdGenerator: BuildIdGenerator = (size: number, includingUppercase = false) =>
|
||||
const buildIdGenerator: BuildIdGenerator = (size: number, includingUppercase = true) =>
|
||||
customAlphabet(includingUppercase ? alphabet : lowercaseAlphabet, size);
|
||||
export const generateStandardId = buildIdGenerator(21);
|
||||
|
||||
/**
|
||||
* Generate a standard id with 21 characters, including lowercase letters and numbers.
|
||||
*
|
||||
* @see {@link lowercaseAlphabet}
|
||||
*/
|
||||
export const generateStandardId = buildIdGenerator(21, false);
|
||||
|
||||
/**
|
||||
* Generate a standard short id with 12 characters, including lowercase letters and numbers.
|
||||
*
|
||||
* @see {@link lowercaseAlphabet}
|
||||
*/
|
||||
export const generateStandardShortId = buildIdGenerator(12, false);
|
||||
|
||||
/**
|
||||
* Generate a standard secret with 32 characters, including uppercase letters, lowercase
|
||||
* letters, and numbers.
|
||||
*
|
||||
* @see {@link alphabet}
|
||||
*/
|
||||
export const generateStandardSecret = buildIdGenerator(32);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { generateStandardId, buildIdGenerator } from '@logto/shared/universal';
|
||||
import { generateStandardId } from '@logto/shared/universal';
|
||||
|
||||
// Use lowercase letters for tenant IDs to improve compatibility
|
||||
const generateTenantId = buildIdGenerator(6, false);
|
||||
const generateTenantId = () => generateStandardId(6);
|
||||
|
||||
export type TenantMetadata = {
|
||||
id: string;
|
||||
|
@ -12,7 +12,7 @@ export type TenantMetadata = {
|
|||
|
||||
export const createTenantMetadata = (
|
||||
databaseName: string,
|
||||
tenantId = generateTenantId(6)
|
||||
tenantId = generateTenantId()
|
||||
): TenantMetadata => {
|
||||
const parentRole = `logto_tenant_${databaseName}`;
|
||||
const role = `logto_tenant_${databaseName}_${tenantId}`;
|
||||
|
|
Loading…
Add table
Reference in a new issue