mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(connector): google one tap
This commit is contained in:
parent
dc6fbe212e
commit
6308ee1857
10 changed files with 269 additions and 83 deletions
9
.changeset/breezy-bags-help.md
Normal file
9
.changeset/breezy-bags-help.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
"@logto/connector-kit": major
|
||||||
|
---
|
||||||
|
|
||||||
|
remove `.catchall()` for `connectorMetadataGuard`
|
||||||
|
|
||||||
|
`.catchall()` allows unknown keys to be parsed as metadata. This is troublesome when we want to strip out unknown keys (Zod provides `.strip()` for this purpose but somehow it doesn't work with `.catchall()`).
|
||||||
|
|
||||||
|
For data extensibility, we added `customData` field to `ConnectorMetadata` type to store unknown keys. For example, the `fromEmail` field in `connector-logto-email` is not part of the standard metadata, so it should be stored in `customData` in the future.
|
9
.changeset/cold-masks-film.md
Normal file
9
.changeset/cold-masks-film.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
"@logto/connector-google": minor
|
||||||
|
"@logto/connector-kit": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
support Google One Tap
|
||||||
|
|
||||||
|
- support parsing and validating Google One Tap data in `connector-google`
|
||||||
|
- add Google connector constants in `connector-kit` for reuse
|
|
@ -7,6 +7,7 @@
|
||||||
"@logto/connector-kit": "workspace:^3.0.0",
|
"@logto/connector-kit": "workspace:^3.0.0",
|
||||||
"@silverhand/essentials": "^2.9.1",
|
"@silverhand/essentials": "^2.9.1",
|
||||||
"got": "^14.0.0",
|
"got": "^14.0.0",
|
||||||
|
"jose": "^5.0.0",
|
||||||
"snakecase-keys": "^8.0.0",
|
"snakecase-keys": "^8.0.0",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import type { ConnectorMetadata } from '@logto/connector-kit';
|
import type { ConnectorMetadata } from '@logto/connector-kit';
|
||||||
import { ConnectorConfigFormItemType, ConnectorPlatform } from '@logto/connector-kit';
|
import {
|
||||||
|
ConnectorConfigFormItemType,
|
||||||
|
ConnectorPlatform,
|
||||||
|
GoogleConnector,
|
||||||
|
} from '@logto/connector-kit';
|
||||||
|
|
||||||
export const authorizationEndpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
|
export const authorizationEndpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
|
||||||
export const accessTokenEndpoint = 'https://oauth2.googleapis.com/token';
|
export const accessTokenEndpoint = 'https://oauth2.googleapis.com/token';
|
||||||
|
@ -7,8 +11,8 @@ export const userInfoEndpoint = 'https://openidconnect.googleapis.com/v1/userinf
|
||||||
export const scope = 'openid profile email';
|
export const scope = 'openid profile email';
|
||||||
|
|
||||||
export const defaultMetadata: ConnectorMetadata = {
|
export const defaultMetadata: ConnectorMetadata = {
|
||||||
id: 'google-universal',
|
id: GoogleConnector.factoryId,
|
||||||
target: 'google',
|
target: GoogleConnector.target,
|
||||||
platform: ConnectorPlatform.Universal,
|
platform: ConnectorPlatform.Universal,
|
||||||
name: {
|
name: {
|
||||||
en: 'Google',
|
en: 'Google',
|
||||||
|
@ -53,3 +57,6 @@ export const defaultMetadata: ConnectorMetadata = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultTimeout = 5000;
|
export const defaultTimeout = 5000;
|
||||||
|
|
||||||
|
// https://developers.google.com/identity/gsi/web/guides/verify-google-id-token
|
||||||
|
export const jwksUri = 'https://www.googleapis.com/oauth2/v3/certs';
|
||||||
|
|
|
@ -11,6 +11,7 @@ import type {
|
||||||
GetConnectorConfig,
|
GetConnectorConfig,
|
||||||
CreateConnector,
|
CreateConnector,
|
||||||
SocialConnector,
|
SocialConnector,
|
||||||
|
GoogleConnectorConfig,
|
||||||
} from '@logto/connector-kit';
|
} from '@logto/connector-kit';
|
||||||
import {
|
import {
|
||||||
ConnectorError,
|
ConnectorError,
|
||||||
|
@ -18,7 +19,9 @@ import {
|
||||||
validateConfig,
|
validateConfig,
|
||||||
ConnectorType,
|
ConnectorType,
|
||||||
parseJson,
|
parseJson,
|
||||||
|
GoogleConnector,
|
||||||
} from '@logto/connector-kit';
|
} from '@logto/connector-kit';
|
||||||
|
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
accessTokenEndpoint,
|
accessTokenEndpoint,
|
||||||
|
@ -27,20 +30,20 @@ import {
|
||||||
userInfoEndpoint,
|
userInfoEndpoint,
|
||||||
defaultMetadata,
|
defaultMetadata,
|
||||||
defaultTimeout,
|
defaultTimeout,
|
||||||
|
jwksUri,
|
||||||
} from './constant.js';
|
} from './constant.js';
|
||||||
import type { GoogleConfig } from './types.js';
|
|
||||||
import {
|
import {
|
||||||
googleConfigGuard,
|
|
||||||
accessTokenResponseGuard,
|
accessTokenResponseGuard,
|
||||||
userInfoResponseGuard,
|
userInfoResponseGuard,
|
||||||
authResponseGuard,
|
authResponseGuard,
|
||||||
|
googleOneTapDataGuard,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
|
|
||||||
const getAuthorizationUri =
|
const getAuthorizationUri =
|
||||||
(getConfig: GetConnectorConfig): GetAuthorizationUri =>
|
(getConfig: GetConnectorConfig): GetAuthorizationUri =>
|
||||||
async ({ state, redirectUri }) => {
|
async ({ state, redirectUri }) => {
|
||||||
const config = await getConfig(defaultMetadata.id);
|
const config = await getConfig(defaultMetadata.id);
|
||||||
validateConfig(config, googleConfigGuard);
|
validateConfig(config, GoogleConnector.configGuard);
|
||||||
|
|
||||||
const queryParameters = new URLSearchParams({
|
const queryParameters = new URLSearchParams({
|
||||||
client_id: config.clientId,
|
client_id: config.clientId,
|
||||||
|
@ -54,7 +57,7 @@ const getAuthorizationUri =
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAccessToken = async (
|
export const getAccessToken = async (
|
||||||
config: GoogleConfig,
|
config: GoogleConnectorConfig,
|
||||||
codeObject: { code: string; redirectUri: string }
|
codeObject: { code: string; redirectUri: string }
|
||||||
) => {
|
) => {
|
||||||
const { code, redirectUri } = codeObject;
|
const { code, redirectUri } = codeObject;
|
||||||
|
@ -86,22 +89,58 @@ export const getAccessToken = async (
|
||||||
return { accessToken };
|
return { accessToken };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUserInfo =
|
type Json = ReturnType<typeof parseJson>;
|
||||||
(getConfig: GetConnectorConfig): GetUserInfo =>
|
|
||||||
async (data) => {
|
/**
|
||||||
|
* Get user information JSON from Google Identity Platform. It will use the following order to
|
||||||
|
* retrieve user information:
|
||||||
|
*
|
||||||
|
* 1. Google One Tap: https://developers.google.com/identity/gsi/web/guides/verify-google-id-token
|
||||||
|
* 2. Normal Google OAuth: https://developers.google.com/identity/protocols/oauth2/openid-connect
|
||||||
|
*
|
||||||
|
* @param data The data from the client.
|
||||||
|
* @param config The configuration of the connector.
|
||||||
|
* @returns A Promise that resolves to the user information JSON.
|
||||||
|
*/
|
||||||
|
const getUserInfoJson = async (data: unknown, config: GoogleConnectorConfig): Promise<Json> => {
|
||||||
|
// Google One Tap
|
||||||
|
const oneTapResult = googleOneTapDataGuard.safeParse(data);
|
||||||
|
|
||||||
|
if (oneTapResult.success) {
|
||||||
|
const { payload } = await jwtVerify<Json>(
|
||||||
|
oneTapResult.data.credential,
|
||||||
|
createRemoteJWKSet(new URL(jwksUri)),
|
||||||
|
{
|
||||||
|
// https://developers.google.com/identity/gsi/web/guides/verify-google-id-token
|
||||||
|
issuer: ['https://accounts.google.com', 'accounts.google.com'],
|
||||||
|
audience: config.clientId,
|
||||||
|
clockTolerance: 10,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal Google OAuth
|
||||||
const { code, redirectUri } = await authorizationCallbackHandler(data);
|
const { code, redirectUri } = await authorizationCallbackHandler(data);
|
||||||
const config = await getConfig(defaultMetadata.id);
|
|
||||||
validateConfig(config, googleConfigGuard);
|
|
||||||
const { accessToken } = await getAccessToken(config, { code, redirectUri });
|
const { accessToken } = await getAccessToken(config, { code, redirectUri });
|
||||||
|
|
||||||
try {
|
|
||||||
const httpResponse = await got.post(userInfoEndpoint, {
|
const httpResponse = await got.post(userInfoEndpoint, {
|
||||||
headers: {
|
headers: {
|
||||||
authorization: `Bearer ${accessToken}`,
|
authorization: `Bearer ${accessToken}`,
|
||||||
},
|
},
|
||||||
timeout: { request: defaultTimeout },
|
timeout: { request: defaultTimeout },
|
||||||
});
|
});
|
||||||
const rawData = parseJson(httpResponse.body);
|
return parseJson(httpResponse.body);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserInfo =
|
||||||
|
(getConfig: GetConnectorConfig): GetUserInfo =>
|
||||||
|
async (data) => {
|
||||||
|
const config = await getConfig(defaultMetadata.id);
|
||||||
|
validateConfig(config, GoogleConnector.configGuard);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const rawData = await getUserInfoJson(data, config);
|
||||||
const result = userInfoResponseGuard.safeParse(rawData);
|
const result = userInfoResponseGuard.safeParse(rawData);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
|
@ -150,7 +189,7 @@ const createGoogleConnector: CreateConnector<SocialConnector> = async ({ getConf
|
||||||
return {
|
return {
|
||||||
metadata: defaultMetadata,
|
metadata: defaultMetadata,
|
||||||
type: ConnectorType.Social,
|
type: ConnectorType.Social,
|
||||||
configGuard: googleConfigGuard,
|
configGuard: GoogleConnector.configGuard,
|
||||||
getAuthorizationUri: getAuthorizationUri(getConfig),
|
getAuthorizationUri: getAuthorizationUri(getConfig),
|
||||||
getUserInfo: getUserInfo(getConfig),
|
getUserInfo: getUserInfo(getConfig),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const googleConfigGuard = z.object({
|
import { GoogleConnector } from '@logto/connector-kit';
|
||||||
clientId: z.string(),
|
|
||||||
clientSecret: z.string(),
|
|
||||||
scope: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type GoogleConfig = z.infer<typeof googleConfigGuard>;
|
|
||||||
|
|
||||||
export const accessTokenResponseGuard = z.object({
|
export const accessTokenResponseGuard = z.object({
|
||||||
access_token: z.string(),
|
access_token: z.string(),
|
||||||
|
@ -33,3 +27,11 @@ export const authResponseGuard = z.object({
|
||||||
code: z.string(),
|
code: z.string(),
|
||||||
redirectUri: z.string(),
|
redirectUri: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response payload from Google One Tap. Note the CSRF token is not included since it should be
|
||||||
|
* verified by the web server.
|
||||||
|
*/
|
||||||
|
export const googleOneTapDataGuard = z.object({
|
||||||
|
[GoogleConnector.oneTapParams.credential]: z.string(),
|
||||||
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { ZodType } from 'zod';
|
import type { ZodType, z } from 'zod';
|
||||||
|
|
||||||
import { type ConnectorMetadata } from './metadata.js';
|
import { type ConnectorMetadata } from './metadata.js';
|
||||||
|
|
||||||
|
@ -17,3 +17,7 @@ export type BaseConnector<Type extends ConnectorType> = {
|
||||||
metadata: ConnectorMetadata;
|
metadata: ConnectorMetadata;
|
||||||
configGuard: ZodType;
|
configGuard: ZodType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ToZodObject<T> = z.ZodObject<{
|
||||||
|
[K in keyof T]-?: z.ZodType<T[K]>;
|
||||||
|
}>;
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import type { LanguageTag } from '@logto/language-kit';
|
import type { LanguageTag } from '@logto/language-kit';
|
||||||
import { isLanguageTag } from '@logto/language-kit';
|
import { isLanguageTag } from '@logto/language-kit';
|
||||||
|
import { type Nullable } from '@silverhand/essentials';
|
||||||
import type { ZodType } from 'zod';
|
import type { ZodType } from 'zod';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { connectorConfigFormItemGuard } from './config-form.js';
|
import { connectorConfigFormItemGuard } from './config-form.js';
|
||||||
|
import { type ToZodObject } from './foundation.js';
|
||||||
|
|
||||||
export enum ConnectorPlatform {
|
export enum ConnectorPlatform {
|
||||||
Native = 'Native',
|
Native = 'Native',
|
||||||
|
@ -34,12 +36,32 @@ export type I18nPhrases = { en: string } & {
|
||||||
[K in Exclude<LanguageTag, 'en'>]?: string;
|
[K in Exclude<LanguageTag, 'en'>]?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SocialConnectorMetadata = {
|
||||||
|
platform: Nullable<ConnectorPlatform>;
|
||||||
|
isStandard?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export const socialConnectorMetadataGuard = z.object({
|
export const socialConnectorMetadataGuard = z.object({
|
||||||
// Social connector platform. TODO: @darcyYe considering remove the nullable and make all the social connector field optional
|
// Social connector platform. TODO: @darcyYe considering remove the nullable and make all the social connector field optional
|
||||||
platform: z.nativeEnum(ConnectorPlatform).nullable(),
|
platform: z.nativeEnum(ConnectorPlatform).nullable(),
|
||||||
// Indicates custom connector that follows standard protocol. Currently supported standard connectors are OIDC, OAuth2, and SAML2
|
// Indicates custom connector that follows standard protocol. Currently supported standard connectors are OIDC, OAuth2, and SAML2
|
||||||
isStandard: z.boolean().optional(),
|
isStandard: z.boolean().optional(),
|
||||||
});
|
}) satisfies ToZodObject<SocialConnectorMetadata>;
|
||||||
|
|
||||||
|
export type ConnectorMetadata = {
|
||||||
|
id: string;
|
||||||
|
target: string;
|
||||||
|
name: I18nPhrases;
|
||||||
|
description: I18nPhrases;
|
||||||
|
logo: string;
|
||||||
|
logoDark: Nullable<string>;
|
||||||
|
readme: string;
|
||||||
|
configTemplate?: string;
|
||||||
|
formItems?: Array<z.infer<typeof connectorConfigFormItemGuard>>;
|
||||||
|
customData?: Record<string, unknown>;
|
||||||
|
/** @deprecated Use `customData` instead. */
|
||||||
|
fromEmail?: string;
|
||||||
|
} & SocialConnectorMetadata;
|
||||||
|
|
||||||
export const connectorMetadataGuard = z
|
export const connectorMetadataGuard = z
|
||||||
.object({
|
.object({
|
||||||
|
@ -57,11 +79,10 @@ export const connectorMetadataGuard = z
|
||||||
readme: z.string(),
|
readme: z.string(),
|
||||||
configTemplate: z.string().optional(), // Connector config template
|
configTemplate: z.string().optional(), // Connector config template
|
||||||
formItems: connectorConfigFormItemGuard.array().optional(),
|
formItems: connectorConfigFormItemGuard.array().optional(),
|
||||||
|
customData: z.record(z.unknown()).optional(),
|
||||||
|
fromEmail: z.string().optional(),
|
||||||
})
|
})
|
||||||
.merge(socialConnectorMetadataGuard)
|
.merge(socialConnectorMetadataGuard) satisfies ToZodObject<ConnectorMetadata>;
|
||||||
.catchall(z.unknown());
|
|
||||||
|
|
||||||
export type ConnectorMetadata = z.infer<typeof connectorMetadataGuard>;
|
|
||||||
|
|
||||||
// Configurable connector metadata guard. Stored in DB metadata field
|
// Configurable connector metadata guard. Stored in DB metadata field
|
||||||
export const configurableConnectorMetadataGuard = connectorMetadataGuard
|
export const configurableConnectorMetadataGuard = connectorMetadataGuard
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { type Json } from '@withtyped/server';
|
import { type Json } from '@withtyped/server';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { type BaseConnector, type ConnectorType } from './foundation.js';
|
import { type ToZodObject, type BaseConnector, type ConnectorType } from './foundation.js';
|
||||||
|
|
||||||
// This type definition is for SAML connector
|
// This type definition is for SAML connector
|
||||||
export type ValidateSamlAssertion = (
|
export type ValidateSamlAssertion = (
|
||||||
|
@ -80,3 +80,44 @@ export type SocialConnector = BaseConnector<ConnectorType.Social> & {
|
||||||
getUserInfo: GetUserInfo;
|
getUserInfo: GetUserInfo;
|
||||||
validateSamlAssertion?: ValidateSamlAssertion;
|
validateSamlAssertion?: ValidateSamlAssertion;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GoogleOneTapConfig = {
|
||||||
|
isEnabled?: boolean;
|
||||||
|
autoSelect?: boolean;
|
||||||
|
closeOnTapOutside?: boolean;
|
||||||
|
itpSupport?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const googleOneTapConfigGuard = z.object({
|
||||||
|
isEnabled: z.boolean().optional(),
|
||||||
|
autoSelect: z.boolean().optional(),
|
||||||
|
closeOnTapOutside: z.boolean().optional(),
|
||||||
|
itpSupport: z.boolean().optional(),
|
||||||
|
}) satisfies ToZodObject<GoogleOneTapConfig>;
|
||||||
|
|
||||||
|
/** An object that contains the configuration for the official Google connector. */
|
||||||
|
export const GoogleConnector = Object.freeze({
|
||||||
|
/** The target of Google connectors. */
|
||||||
|
target: 'google',
|
||||||
|
/** The factory ID of the official Google connector. */
|
||||||
|
factoryId: 'google-universal',
|
||||||
|
oneTapParams: Object.freeze({
|
||||||
|
/** The parameter Google One Tap uses to prevent CSRF attacks. */
|
||||||
|
csrfToken: 'g_csrf_token',
|
||||||
|
/** The parameter Google One Tap uses to carry the ID token. */
|
||||||
|
credential: 'credential',
|
||||||
|
}),
|
||||||
|
configGuard: z.object({
|
||||||
|
clientId: z.string(),
|
||||||
|
clientSecret: z.string(),
|
||||||
|
scope: z.string().optional(),
|
||||||
|
oneTap: googleOneTapConfigGuard.optional(),
|
||||||
|
}) satisfies ToZodObject<GoogleConnectorConfig>,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type GoogleConnectorConfig = {
|
||||||
|
clientId: string;
|
||||||
|
clientSecret: string;
|
||||||
|
scope?: string;
|
||||||
|
oneTap?: GoogleOneTapConfig;
|
||||||
|
};
|
||||||
|
|
155
pnpm-lock.yaml
155
pnpm-lock.yaml
|
@ -1142,6 +1142,9 @@ importers:
|
||||||
got:
|
got:
|
||||||
specifier: ^14.0.0
|
specifier: ^14.0.0
|
||||||
version: 14.0.0
|
version: 14.0.0
|
||||||
|
jose:
|
||||||
|
specifier: ^5.0.0
|
||||||
|
version: 5.2.4
|
||||||
snakecase-keys:
|
snakecase-keys:
|
||||||
specifier: ^8.0.0
|
specifier: ^8.0.0
|
||||||
version: 8.0.0
|
version: 8.0.0
|
||||||
|
@ -3415,7 +3418,7 @@ importers:
|
||||||
version: 8.57.0
|
version: 8.57.0
|
||||||
jest:
|
jest:
|
||||||
specifier: ^29.7.0
|
specifier: ^29.7.0
|
||||||
version: 29.7.0(@types/node@20.10.4)
|
version: 29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))
|
||||||
jest-matcher-specific-error:
|
jest-matcher-specific-error:
|
||||||
specifier: ^1.0.0
|
specifier: ^1.0.0
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
@ -3667,7 +3670,7 @@ importers:
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
jest:
|
jest:
|
||||||
specifier: ^29.7.0
|
specifier: ^29.7.0
|
||||||
version: 29.7.0(@types/node@20.12.7)
|
version: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3))
|
||||||
jest-environment-jsdom:
|
jest-environment-jsdom:
|
||||||
specifier: ^29.0.0
|
specifier: ^29.0.0
|
||||||
version: 29.2.2
|
version: 29.2.2
|
||||||
|
@ -3676,7 +3679,7 @@ importers:
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
jest-transformer-svg:
|
jest-transformer-svg:
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: 2.0.0(jest@29.7.0(@types/node@20.12.7))(react@18.2.0)
|
version: 2.0.0(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3)))(react@18.2.0)
|
||||||
js-base64:
|
js-base64:
|
||||||
specifier: ^3.7.5
|
specifier: ^3.7.5
|
||||||
version: 3.7.5
|
version: 3.7.5
|
||||||
|
@ -3815,7 +3818,7 @@ importers:
|
||||||
version: 10.0.0
|
version: 10.0.0
|
||||||
jest:
|
jest:
|
||||||
specifier: ^29.7.0
|
specifier: ^29.7.0
|
||||||
version: 29.7.0(@types/node@20.10.4)
|
version: 29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))
|
||||||
jest-matcher-specific-error:
|
jest-matcher-specific-error:
|
||||||
specifier: ^1.0.0
|
specifier: ^1.0.0
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
@ -14482,6 +14485,41 @@ snapshots:
|
||||||
- supports-color
|
- supports-color
|
||||||
- ts-node
|
- ts-node
|
||||||
|
|
||||||
|
'@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))':
|
||||||
|
dependencies:
|
||||||
|
'@jest/console': 29.7.0
|
||||||
|
'@jest/reporters': 29.7.0
|
||||||
|
'@jest/test-result': 29.7.0
|
||||||
|
'@jest/transform': 29.7.0
|
||||||
|
'@jest/types': 29.6.3
|
||||||
|
'@types/node': 20.12.7
|
||||||
|
ansi-escapes: 4.3.2
|
||||||
|
chalk: 4.1.2
|
||||||
|
ci-info: 3.8.0
|
||||||
|
exit: 0.1.2
|
||||||
|
graceful-fs: 4.2.11
|
||||||
|
jest-changed-files: 29.7.0
|
||||||
|
jest-config: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))
|
||||||
|
jest-haste-map: 29.7.0
|
||||||
|
jest-message-util: 29.7.0
|
||||||
|
jest-regex-util: 29.6.3
|
||||||
|
jest-resolve: 29.7.0
|
||||||
|
jest-resolve-dependencies: 29.7.0
|
||||||
|
jest-runner: 29.7.0
|
||||||
|
jest-runtime: 29.7.0
|
||||||
|
jest-snapshot: 29.7.0
|
||||||
|
jest-util: 29.7.0
|
||||||
|
jest-validate: 29.7.0
|
||||||
|
jest-watcher: 29.7.0
|
||||||
|
micromatch: 4.0.5
|
||||||
|
pretty-format: 29.7.0
|
||||||
|
slash: 3.0.0
|
||||||
|
strip-ansi: 6.0.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- babel-plugin-macros
|
||||||
|
- supports-color
|
||||||
|
- ts-node
|
||||||
|
|
||||||
'@jest/create-cache-key-function@27.5.1':
|
'@jest/create-cache-key-function@27.5.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/types': 27.5.1
|
'@jest/types': 27.5.1
|
||||||
|
@ -14755,7 +14793,7 @@ snapshots:
|
||||||
'@logto/js': 4.1.1
|
'@logto/js': 4.1.1
|
||||||
'@silverhand/essentials': 2.9.1
|
'@silverhand/essentials': 2.9.1
|
||||||
camelcase-keys: 7.0.2
|
camelcase-keys: 7.0.2
|
||||||
jose: 5.2.2
|
jose: 5.2.4
|
||||||
|
|
||||||
'@logto/cloud@0.2.5-a7eedce(zod@3.22.4)':
|
'@logto/cloud@0.2.5-a7eedce(zod@3.22.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -17852,13 +17890,13 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash.get: 4.4.2
|
lodash.get: 4.4.2
|
||||||
|
|
||||||
create-jest@29.7.0(@types/node@20.10.4):
|
create-jest@29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/types': 29.6.3
|
'@jest/types': 29.6.3
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
exit: 0.1.2
|
exit: 0.1.2
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
jest-config: 29.7.0(@types/node@20.10.4)
|
jest-config: 29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))
|
||||||
jest-util: 29.7.0
|
jest-util: 29.7.0
|
||||||
prompts: 2.4.2
|
prompts: 2.4.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
@ -19994,35 +20032,16 @@ snapshots:
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
jest-cli@29.7.0(@types/node@20.10.4):
|
jest-cli@29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3))
|
'@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))
|
||||||
'@jest/test-result': 29.7.0
|
'@jest/test-result': 29.7.0
|
||||||
'@jest/types': 29.6.3
|
'@jest/types': 29.6.3
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
create-jest: 29.7.0(@types/node@20.10.4)
|
create-jest: 29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))
|
||||||
exit: 0.1.2
|
exit: 0.1.2
|
||||||
import-local: 3.1.0
|
import-local: 3.1.0
|
||||||
jest-config: 29.7.0(@types/node@20.10.4)
|
jest-config: 29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))
|
||||||
jest-util: 29.7.0
|
|
||||||
jest-validate: 29.7.0
|
|
||||||
yargs: 17.7.2
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/node'
|
|
||||||
- babel-plugin-macros
|
|
||||||
- supports-color
|
|
||||||
- ts-node
|
|
||||||
|
|
||||||
jest-cli@29.7.0(@types/node@20.12.7):
|
|
||||||
dependencies:
|
|
||||||
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3))
|
|
||||||
'@jest/test-result': 29.7.0
|
|
||||||
'@jest/types': 29.6.3
|
|
||||||
chalk: 4.1.2
|
|
||||||
create-jest: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3))
|
|
||||||
exit: 0.1.2
|
|
||||||
import-local: 3.1.0
|
|
||||||
jest-config: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3))
|
|
||||||
jest-util: 29.7.0
|
jest-util: 29.7.0
|
||||||
jest-validate: 29.7.0
|
jest-validate: 29.7.0
|
||||||
yargs: 17.7.2
|
yargs: 17.7.2
|
||||||
|
@ -20051,7 +20070,7 @@ snapshots:
|
||||||
- supports-color
|
- supports-color
|
||||||
- ts-node
|
- ts-node
|
||||||
|
|
||||||
jest-config@29.7.0(@types/node@20.10.4):
|
jest-config@29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.24.4
|
'@babel/core': 7.24.4
|
||||||
'@jest/test-sequencer': 29.7.0
|
'@jest/test-sequencer': 29.7.0
|
||||||
|
@ -20077,6 +20096,7 @@ snapshots:
|
||||||
strip-json-comments: 3.1.1
|
strip-json-comments: 3.1.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 20.10.4
|
'@types/node': 20.10.4
|
||||||
|
ts-node: 10.9.2(@types/node@20.10.4)(typescript@5.3.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
- supports-color
|
- supports-color
|
||||||
|
@ -20112,6 +20132,37 @@ snapshots:
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
jest-config@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3)):
|
||||||
|
dependencies:
|
||||||
|
'@babel/core': 7.24.4
|
||||||
|
'@jest/test-sequencer': 29.7.0
|
||||||
|
'@jest/types': 29.6.3
|
||||||
|
babel-jest: 29.7.0(@babel/core@7.24.4)
|
||||||
|
chalk: 4.1.2
|
||||||
|
ci-info: 3.8.0
|
||||||
|
deepmerge: 4.3.1
|
||||||
|
glob: 7.2.3
|
||||||
|
graceful-fs: 4.2.11
|
||||||
|
jest-circus: 29.7.0
|
||||||
|
jest-environment-node: 29.7.0
|
||||||
|
jest-get-type: 29.6.3
|
||||||
|
jest-regex-util: 29.6.3
|
||||||
|
jest-resolve: 29.7.0
|
||||||
|
jest-runner: 29.7.0
|
||||||
|
jest-util: 29.7.0
|
||||||
|
jest-validate: 29.7.0
|
||||||
|
micromatch: 4.0.5
|
||||||
|
parse-json: 5.2.0
|
||||||
|
pretty-format: 29.7.0
|
||||||
|
slash: 3.0.0
|
||||||
|
strip-json-comments: 3.1.1
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 20.12.7
|
||||||
|
ts-node: 10.9.2(@types/node@20.10.4)(typescript@5.3.3)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- babel-plugin-macros
|
||||||
|
- supports-color
|
||||||
|
|
||||||
jest-dev-server@10.0.0:
|
jest-dev-server@10.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
|
@ -20386,11 +20437,6 @@ snapshots:
|
||||||
jest: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3))
|
jest: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3))
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
|
|
||||||
jest-transformer-svg@2.0.0(jest@29.7.0(@types/node@20.12.7))(react@18.2.0):
|
|
||||||
dependencies:
|
|
||||||
jest: 29.7.0(@types/node@20.12.7)
|
|
||||||
react: 18.2.0
|
|
||||||
|
|
||||||
jest-util@29.5.0:
|
jest-util@29.5.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/types': 29.6.3
|
'@jest/types': 29.6.3
|
||||||
|
@ -20443,24 +20489,12 @@ snapshots:
|
||||||
merge-stream: 2.0.0
|
merge-stream: 2.0.0
|
||||||
supports-color: 8.1.1
|
supports-color: 8.1.1
|
||||||
|
|
||||||
jest@29.7.0(@types/node@20.10.4):
|
jest@29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3))
|
'@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))
|
||||||
'@jest/types': 29.6.3
|
'@jest/types': 29.6.3
|
||||||
import-local: 3.1.0
|
import-local: 3.1.0
|
||||||
jest-cli: 29.7.0(@types/node@20.10.4)
|
jest-cli: 29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/node'
|
|
||||||
- babel-plugin-macros
|
|
||||||
- supports-color
|
|
||||||
- ts-node
|
|
||||||
|
|
||||||
jest@29.7.0(@types/node@20.12.7):
|
|
||||||
dependencies:
|
|
||||||
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3))
|
|
||||||
'@jest/types': 29.6.3
|
|
||||||
import-local: 3.1.0
|
|
||||||
jest-cli: 29.7.0(@types/node@20.12.7)
|
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
|
@ -23680,6 +23714,25 @@ snapshots:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@swc/core': 1.3.52(@swc/helpers@0.5.1)
|
'@swc/core': 1.3.52(@swc/helpers@0.5.1)
|
||||||
|
|
||||||
|
ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3):
|
||||||
|
dependencies:
|
||||||
|
'@cspotcode/source-map-support': 0.8.1
|
||||||
|
'@tsconfig/node10': 1.0.9
|
||||||
|
'@tsconfig/node12': 1.0.11
|
||||||
|
'@tsconfig/node14': 1.0.3
|
||||||
|
'@tsconfig/node16': 1.0.4
|
||||||
|
'@types/node': 20.10.4
|
||||||
|
acorn: 8.10.0
|
||||||
|
acorn-walk: 8.2.0
|
||||||
|
arg: 4.1.3
|
||||||
|
create-require: 1.1.1
|
||||||
|
diff: 4.0.2
|
||||||
|
make-error: 1.3.6
|
||||||
|
typescript: 5.3.3
|
||||||
|
v8-compile-cache-lib: 3.0.1
|
||||||
|
yn: 3.1.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
tsconfig-paths@3.15.0:
|
tsconfig-paths@3.15.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/json5': 0.0.29
|
'@types/json5': 0.0.29
|
||||||
|
|
Loading…
Reference in a new issue