0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-31 22:51:25 -05:00

refactor(test): user sign in (#1657)

This commit is contained in:
Xiao Yijun 2022-07-25 17:39:09 +08:00 committed by GitHub
parent fa92f4960d
commit 220ba58364
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 249 additions and 308 deletions

View file

@ -13,7 +13,7 @@
},
"devDependencies": {
"@jest/types": "^27.5.1",
"@logto/js": "^0.2.0",
"@logto/node": "1.0.0-beta.0",
"@logto/schemas": "^1.0.0-beta.1",
"@peculiar/webcrypto": "^1.3.3",
"@silverhand/eslint-config": "^0.17.0",

View file

@ -0,0 +1,23 @@
import { Storage, StorageKey } from '@logto/node';
import { Nullable } from '@silverhand/essentials';
export class DummyStorage implements Storage {
private storage: { [key in StorageKey]: Nullable<string> } = {
idToken: null,
refreshToken: null,
accessToken: null,
signInSession: null,
};
getItem(key: StorageKey): Nullable<string> {
return this.storage[key];
}
setItem(key: StorageKey, value: string): void {
this.storage[key] = value;
}
removeItem(key: StorageKey): void {
this.storage[key] = null;
}
}

View file

@ -0,0 +1,20 @@
import BaseClient, { LogtoConfig } from '@logto/node';
import { DummyStorage } from './dummy-storage';
export default class LogtoClient extends BaseClient {
public navigateUrl = '';
constructor(config: LogtoConfig) {
super(
// Note: Disable persisting access token in integration tests
{ ...config, persistAccessToken: false },
{
navigate: (url: string) => {
this.navigateUrl = url;
},
storage: new DummyStorage(),
}
);
}
}

View file

@ -1,111 +0,0 @@
import { generateCodeVerifier, generateState, generateCodeChallenge } from '@logto/js';
import { generatePassword, generateUsername } from './utils';
type Account = {
username: string;
password: string;
};
type ContextData = {
account: Account;
codeVerifier: string;
codeChallenge: string;
state: string;
authorizationEndpoint: string;
tokenEndpoint: string;
authorizationCode: string;
interactionCookie: string;
nextRedirectTo: string;
};
type ContextDataKey = keyof ContextData;
type ContextStore = {
getData: <T extends ContextDataKey>(key: T) => ContextData[T];
setData: <T extends ContextDataKey>(key: T, value: ContextData[T]) => void;
};
const createContextStore = (): ContextStore => {
const data: ContextData = {
account: { username: '', password: '' },
codeVerifier: '',
codeChallenge: '',
state: '',
interactionCookie: '',
authorizationCode: '',
authorizationEndpoint: '',
tokenEndpoint: '',
nextRedirectTo: '',
};
return {
getData: <T extends ContextDataKey>(key: T) => data[key],
setData: <T extends ContextDataKey>(key: T, value: ContextData[T]) => {
// eslint-disable-next-line @silverhand/fp/no-mutation
data[key] = value;
},
};
};
export class LogtoContext {
private readonly contextData: ContextStore = createContextStore();
public async init() {
const account = {
username: generateUsername(),
password: generatePassword(),
};
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
this.setData('account', account);
this.setData('codeVerifier', codeVerifier);
this.setData('codeChallenge', codeChallenge);
this.setData('state', generateState());
}
public get account(): Account {
return this.getData('account');
}
public get codeVerifier(): string {
return this.getData('codeVerifier');
}
public get codeChallenge(): string {
return this.getData('codeChallenge');
}
public get state(): string {
return this.getData('state');
}
public get authorizationCode(): string {
return this.getData('authorizationCode');
}
public get interactionCookie(): string {
return this.getData('interactionCookie');
}
public get authorizationEndpoint(): string {
return this.getData('authorizationEndpoint');
}
public get tokenEndpoint(): string {
return this.getData('tokenEndpoint');
}
public get nextRedirectTo(): string {
return this.getData('nextRedirectTo');
}
public setData<T extends ContextDataKey>(key: T, value: ContextData[T]): void {
this.contextData.setData(key, value);
}
private getData<T extends ContextDataKey>(key: T): ContextData[T] {
return this.contextData.getData(key);
}
}

View file

@ -0,0 +1,139 @@
import { assert } from '@silverhand/essentials';
import got from 'got/dist/source';
import api from './api';
import { logtoUrl } from './constants';
import { extractCookie } from './utils';
type RegisterResponse = {
redirectTo: string;
};
type SignInResponse = {
redirectTo: string;
};
type ConsentResponse = {
redirectTo: string;
};
export const visitSignInUri = async (signInUri: string) => {
const response = await got(signInUri, {
followRedirect: false,
});
// Note: After visit the sign in uri successfully, it will redirect the user to the ui sign in page.
assert(
response.statusCode === 303 && response.headers.location === '/sign-in',
new Error('Visit sign in uri failed')
);
const cookie = extractCookie(response);
assert(cookie, new Error('Get cookie from authorization endpoint failed'));
return cookie;
};
export const registerUserWithUsernameAndPassword = async (
username: string,
password: string,
interactionCookie: string
) => {
const { redirectTo } = await api
.post('session/register/username-password', {
headers: {
cookie: interactionCookie,
},
json: {
username,
password,
},
})
.json<RegisterResponse>();
// Note: If register successfully, it will redirect the user to the auth endpoint.
assert(
redirectTo.startsWith(`${logtoUrl}/oidc/auth`),
new Error('Register with username and password failed')
);
};
export const signInWithUsernameAndPassword = async (
username: string,
password: string,
interactionCookie: string
) => {
const { redirectTo: completeSignInActionUri } = await api
.post('session/sign-in/username-password', {
headers: {
cookie: interactionCookie,
},
json: {
username,
password,
},
followRedirect: false,
})
.json<SignInResponse>();
// Note: If sign in successfully, it will redirect the user to the auth endpoint
assert(
completeSignInActionUri.startsWith(`${logtoUrl}/oidc/auth`),
new Error('Sign in with username and password failed')
);
// Note: visit the completeSignInActionUri to get a new interaction cookie with session.
const completeSignInActionResponse = await got.get(completeSignInActionUri, {
headers: {
cookie: interactionCookie,
},
followRedirect: false,
});
// Note: If sign in action completed successfully, it will redirect the user to the consent page.
assert(
completeSignInActionResponse.statusCode === 303 &&
completeSignInActionResponse.headers.location === '/sign-in/consent',
new Error('Invoke auth before consent failed')
);
const cookieWithSession = extractCookie(completeSignInActionResponse);
// Note: If sign in action completed successfully, we will get `_session.sig` in the cookie.
assert(
Boolean(cookieWithSession) && cookieWithSession.includes('_session.sig'),
new Error('Invoke auth before consent failed')
);
return cookieWithSession;
};
export const consentUserAndGetSignInCallbackUri = async (interactionCookie: string) => {
const { redirectTo: completeAuthUri } = await api
.post('session/consent', {
headers: {
cookie: interactionCookie,
},
followRedirect: false,
})
.json<ConsentResponse>();
// Note: If consent successfully, it will redirect the user to the auth endpoint.
assert(completeAuthUri.startsWith(`${logtoUrl}/oidc/auth`), new Error('Consent failed'));
// Note: complete the auth process to get the sign in callback uri.
const authCodeResponse = await got.get(completeAuthUri, {
headers: {
cookie: interactionCookie,
},
followRedirect: false,
});
// Note: If complete auth successfully, it will redirect the user to the redirect uri.
assert(authCodeResponse.statusCode === 303, new Error('Complete auth failed'));
const signInCallbackUri = authCodeResponse.headers.location;
assert(signInCallbackUri, new Error('Get sign in callback uri failed'));
return signInCallbackUri;
};

View file

@ -1,184 +1,49 @@
import {
createRequester,
fetchOidcConfig,
fetchTokenByAuthorizationCode,
generateSignInUri,
verifyAndParseCodeFromCallbackUri,
} from '@logto/js';
import { LogtoConfig } from '@logto/node';
import { demoAppApplicationId } from '@logto/schemas/lib/seeds';
import got from 'got/dist/source';
import api from '@/api';
import { discoveryUrl, logtoUrl, demoAppRedirectUri } from '../src/constants';
import { LogtoContext } from '../src/logto-context';
import { extractCookie } from '../src/utils';
import LogtoClient from '@/client/logto-client';
import { demoAppRedirectUri, logtoUrl } from '@/constants';
import {
consentUserAndGetSignInCallbackUri,
registerUserWithUsernameAndPassword,
signInWithUsernameAndPassword,
visitSignInUri,
} from '@/ui-actions';
import { generatePassword, generateUsername } from '@/utils';
describe('username and password flow', () => {
const logtoContext = new LogtoContext();
beforeAll(async () => {
await logtoContext.init();
});
it('should fetch OIDC configuration', async () => {
const oidcConfig = await fetchOidcConfig(discoveryUrl, createRequester());
const { authorizationEndpoint, tokenEndpoint } = oidcConfig;
expect(authorizationEndpoint).toBeTruthy();
expect(tokenEndpoint).toBeTruthy();
logtoContext.setData('authorizationEndpoint', authorizationEndpoint);
logtoContext.setData('tokenEndpoint', tokenEndpoint);
});
it('should visit authorization endpoint and get interaction cookie', async () => {
const signInUri = generateSignInUri({
authorizationEndpoint: logtoContext.authorizationEndpoint,
clientId: demoAppApplicationId,
redirectUri: demoAppRedirectUri,
codeChallenge: logtoContext.codeChallenge,
state: logtoContext.state,
});
const response = await got(signInUri, {
followRedirect: false,
});
// Note: this will redirect to the ui sign in page
expect(response.statusCode).toBe(303);
expect(response.headers.location).toBe('/sign-in');
const cookie = extractCookie(response);
expect(cookie).toBeTruthy();
logtoContext.setData('interactionCookie', cookie);
});
it('should register with username and password and redirect to oidc/auth endpoint to start an auth process', async () => {
type RegisterResponse = {
redirectTo: string;
it('should register and sign in with username and password successfully', async () => {
const logtoConfig: LogtoConfig = {
endpoint: logtoUrl,
appId: demoAppApplicationId,
persistAccessToken: false,
};
const registerResponse = await api
.post('session/register/username-password', {
headers: {
cookie: logtoContext.interactionCookie,
},
json: logtoContext.account,
})
.json<RegisterResponse>();
const logtoClient = new LogtoClient(logtoConfig);
const { redirectTo: invokeAuthUrl } = registerResponse;
await logtoClient.signIn(demoAppRedirectUri);
expect(invokeAuthUrl.startsWith(`${logtoUrl}/oidc/auth`)).toBeTruthy();
});
expect(logtoClient.navigateUrl).toBeTruthy();
it('should sign in with username and password and redirect to oidc/auth endpoint to start an auth process', async () => {
type SignInResponse = {
redirectTo: string;
};
const interactionCookie = await visitSignInUri(logtoClient.navigateUrl);
const signInResponse = await api
.post('session/sign-in/username-password', {
headers: {
cookie: logtoContext.interactionCookie,
},
json: logtoContext.account,
followRedirect: false,
})
.json<SignInResponse>();
const username = generateUsername();
const password = generatePassword();
const { redirectTo: invokeAuthUrl } = signInResponse;
await registerUserWithUsernameAndPassword(username, password, interactionCookie);
expect(invokeAuthUrl.startsWith(`${logtoUrl}/oidc/auth`)).toBeTruthy();
logtoContext.setData('nextRedirectTo', invokeAuthUrl);
});
it('should invoke the auth process and redirect to the consent page with session cookie', async () => {
const invokeAuthUrl = logtoContext.nextRedirectTo;
const invokeAuthResponse = await got.get(invokeAuthUrl, {
headers: {
cookie: logtoContext.interactionCookie,
},
followRedirect: false,
});
// Note: Redirect to consent page
expect(invokeAuthResponse).toHaveProperty('statusCode', 303);
expect(invokeAuthResponse.headers.location).toBe('/sign-in/consent');
const cookie = extractCookie(invokeAuthResponse);
expect(cookie).toBeTruthy();
expect(cookie.includes('_session.sig')).toBeTruthy();
logtoContext.setData('interactionCookie', cookie);
});
it('should redirect to oidc/auth endpoint to complete the auth process after consent', async () => {
type ConsentResponse = {
redirectTo: string;
};
const consentResponse = await api
.post('session/consent', {
headers: {
cookie: logtoContext.interactionCookie,
},
followRedirect: false,
})
.json<ConsentResponse>();
const { redirectTo: completeAuthUrl } = consentResponse;
expect(completeAuthUrl.startsWith(`${logtoUrl}/oidc/auth`)).toBeTruthy();
logtoContext.setData('nextRedirectTo', completeAuthUrl);
});
it('should get the authorization code from the callback uri when the auth process is completed', async () => {
const completeAuthUrl = logtoContext.nextRedirectTo;
const authCodeResponse = await got.get(completeAuthUrl, {
headers: {
cookie: logtoContext.interactionCookie,
},
});
expect(authCodeResponse).toHaveProperty('statusCode', 200);
const callbackUri = authCodeResponse.redirectUrls[0];
expect(callbackUri).toBeTruthy();
if (!callbackUri) {
throw new Error('No redirect uri');
}
const authorizationCode = verifyAndParseCodeFromCallbackUri(
callbackUri,
demoAppRedirectUri,
logtoContext.state
);
expect(authorizationCode).toBeTruthy();
logtoContext.setData('authorizationCode', authorizationCode);
});
it('should fetch token by authorization code', async () => {
const token = await fetchTokenByAuthorizationCode(
{
clientId: demoAppApplicationId,
tokenEndpoint: logtoContext.tokenEndpoint,
redirectUri: demoAppRedirectUri,
codeVerifier: logtoContext.codeVerifier,
code: logtoContext.authorizationCode,
},
createRequester()
const interactionCookieWithSession = await signInWithUsernameAndPassword(
username,
password,
interactionCookie
);
expect(token).toHaveProperty('accessToken');
expect(token).toHaveProperty('expiresIn');
expect(token).toHaveProperty('idToken');
expect(token).toHaveProperty('refreshToken');
expect(token).toHaveProperty('scope');
expect(token).toHaveProperty('tokenType');
const signInCallbackUri = await consentUserAndGetSignInCallbackUri(
interactionCookieWithSession
);
await logtoClient.handleSignInCallback(signInCallbackUri);
expect(logtoClient.isAuthenticated).toBeTruthy();
});
});

61
pnpm-lock.yaml generated
View file

@ -1022,7 +1022,7 @@ importers:
packages/integration-tests:
specifiers:
'@jest/types': ^27.5.1
'@logto/js': ^0.2.0
'@logto/node': 1.0.0-beta.0
'@logto/schemas': ^1.0.0-beta.1
'@peculiar/webcrypto': ^1.3.3
'@silverhand/eslint-config': ^0.17.0
@ -1043,7 +1043,7 @@ importers:
typescript: ^4.6.4
devDependencies:
'@jest/types': 27.5.1
'@logto/js': 0.2.0
'@logto/node': 1.0.0-beta.0
'@logto/schemas': link:../schemas
'@peculiar/webcrypto': 1.3.3
'@silverhand/eslint-config': 0.17.0_odhppvbqvvm7sc3xnvl7b6rwuy
@ -1869,7 +1869,6 @@ packages:
engines: {node: '>=12'}
dependencies:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@eslint/eslintrc/1.3.0:
resolution: {integrity: sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==}
@ -2032,7 +2031,6 @@ packages:
- supports-color
- ts-node
- utf-8-validate
dev: true
/@jest/environment/27.5.1:
resolution: {integrity: sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==}
@ -2177,7 +2175,6 @@ packages:
dependencies:
'@jridgewell/resolve-uri': 3.0.5
'@jridgewell/sourcemap-codec': 1.4.11
dev: true
/@koa/cors/3.1.0:
resolution: {integrity: sha512-7ulRC1da/rBa6kj6P4g2aJfnET3z8Uf3SWu60cjbtxTA5g8lxRdX/Bd2P92EagGwwAhANeNw8T8if99rJliR6Q==}
@ -2969,14 +2966,15 @@ packages:
superstruct: 0.16.0
dev: true
/@logto/js/0.2.0:
resolution: {integrity: sha512-hKbx0pqN8hPGB/G4/OTnmcuKk+MlU9xkGTbXzc4pSCvuFq4eMEOeQ8uOaGxoetisD7dZ36lMkfAZ57cFlcwE8w==}
/@logto/client/1.0.0-beta.0:
resolution: {integrity: sha512-FHprVzEATJuRxdsSPxOD+1lpRcpgmnfA3JKHI4o1yce7yXrcqYx1nAw/4acW1vxLv67NQ0IQf/U2RAPWSpdj4g==}
dependencies:
'@logto/js': 1.0.0-beta.0
'@silverhand/essentials': 1.1.7
camelcase-keys: 7.0.2
jose: 4.6.0
js-base64: 3.7.2
lodash.get: 4.4.2
lodash.once: 4.1.1
superstruct: 0.16.0
dev: true
@ -2991,6 +2989,27 @@ packages:
superstruct: 0.16.0
dev: true
/@logto/js/1.0.0-beta.0:
resolution: {integrity: sha512-IYgfcs6Pc+fx2Y84kFyzAI1lNBT8NmJVZnP6Xuii6xBc1eTQFLpxffLPEB3/CtgXuyzlGyL5ZM9BO8mnhWYWAQ==}
dependencies:
'@silverhand/essentials': 1.1.7
camelcase-keys: 7.0.2
jose: 4.6.0
lodash.get: 4.4.2
superstruct: 0.16.0
dev: true
/@logto/node/1.0.0-beta.0:
resolution: {integrity: sha512-wQLBp8xslgqFMG7vm+/+2drkGlLql91hB2eElx9M6+iJj/OI2c8didSy6eltcYKgM5Wp4xH8peF2XX6RpOOyxQ==}
dependencies:
'@logto/client': 1.0.0-beta.0
'@silverhand/essentials': 1.1.7
js-base64: 3.7.2
node-fetch: 2.6.7
transitivePeerDependencies:
- encoding
dev: true
/@logto/react/1.0.0-alpha.2_react@17.0.2:
resolution: {integrity: sha512-52nFUrOrMuGJFY9eJG0BIawJ3pn72HvhEg4Of3/Jtrr3d1THYSIg1wEdKfNjfbuABJ85qxdi/H2HD2rn5Q6TdQ==}
peerDependencies:
@ -4497,7 +4516,7 @@ packages:
'@jest/types': 27.5.1
deepmerge: 4.2.2
identity-obj-proxy: 3.0.0
jest: 27.5.1
jest: 27.5.1_ts-node@10.9.1
jest-matcher-specific-error: 1.0.0
jest-transform-stub: 2.0.0
ts-jest: 27.1.1_53ggqi2i4rbcfjtktmjua6zili
@ -4548,6 +4567,7 @@ packages:
- babel-jest
- esbuild
- typescript
dev: false
/@silverhand/jest-config/0.17.0_u4suh3umvg724wu2nufptihvny:
resolution: {integrity: sha512-Syb9S2Hqbg3UcuGq+qByv+SJKWC6jrFSQp6JeGbsqLWlGTDjXX3QpHMaq5p9XYldlns6ZT6QQ7Q19CtbqUFU+A==}
@ -4568,6 +4588,7 @@ packages:
- babel-jest
- esbuild
- typescript
dev: false
/@silverhand/ts-config-react/0.17.0_typescript@4.6.2:
resolution: {integrity: sha512-vsZMcWVzaQT2Qb2b46jbmbcXPHxEKz3snQzQhHHV4IRJbLWnTtswOnNwfPRdXsW7/xcYfF+EM9Bw4Q7Cmib2nA==}
@ -4844,19 +4865,15 @@ packages:
/@tsconfig/node10/1.0.8:
resolution: {integrity: sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==}
dev: true
/@tsconfig/node12/1.0.9:
resolution: {integrity: sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==}
dev: true
/@tsconfig/node14/1.0.1:
resolution: {integrity: sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==}
dev: true
/@tsconfig/node16/1.0.2:
resolution: {integrity: sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==}
dev: true
/@types/accepts/1.3.5:
resolution: {integrity: sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==}
@ -5160,7 +5177,6 @@ packages:
/@types/node/16.11.12:
resolution: {integrity: sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==}
dev: true
/@types/node/17.0.23:
resolution: {integrity: sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==}
@ -5801,7 +5817,6 @@ packages:
/acorn-walk/8.2.0:
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
engines: {node: '>=0.4.0'}
dev: true
/acorn/7.4.1:
resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
@ -5817,7 +5832,6 @@ packages:
resolution: {integrity: sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/add-stream/1.0.0:
resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==}
@ -5961,7 +5975,6 @@ packages:
/arg/4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
dev: true
/argon2/0.28.5:
resolution: {integrity: sha512-kGFCctzc3VWmR1aCOYjNgvoTmVF5uVBUtWlXCKKO54d1K+31zRz45KAcDIqMo2746ozv/52d25nfEekitaXP0w==}
@ -6976,7 +6989,6 @@ packages:
/create-require/1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true
/cross-env/7.0.3:
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
@ -7324,7 +7336,6 @@ packages:
/diff/4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
dev: true
/diff/5.0.0:
resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==}
@ -7733,7 +7744,7 @@ packages:
eslint-import-resolver-webpack:
optional: true
dependencies:
'@typescript-eslint/parser': 5.14.0_cm24urgmefwiki325v54og4qfa
'@typescript-eslint/parser': 5.14.0_g4cxuhevh5o54harssx6h7xjim
debug: 3.2.7
eslint-import-resolver-node: 0.3.6
eslint-import-resolver-typescript: 2.5.0_p6hsegxeddyw6tkhd66xuhpt6y
@ -7784,7 +7795,7 @@ packages:
'@typescript-eslint/parser':
optional: true
dependencies:
'@typescript-eslint/parser': 5.14.0_cm24urgmefwiki325v54og4qfa
'@typescript-eslint/parser': 5.14.0_g4cxuhevh5o54harssx6h7xjim
array-includes: 3.1.4
array.prototype.flat: 1.2.5
debug: 2.6.9
@ -9743,7 +9754,6 @@ packages:
- supports-color
- ts-node
- utf-8-validate
dev: true
/jest-config/27.5.1:
resolution: {integrity: sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==}
@ -9823,7 +9833,6 @@ packages:
- canvas
- supports-color
- utf-8-validate
dev: true
/jest-diff/27.5.1:
resolution: {integrity: sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==}
@ -10195,7 +10204,6 @@ packages:
- supports-color
- ts-node
- utf-8-validate
dev: true
/jose/4.6.0:
resolution: {integrity: sha512-0hNAkhMBNi4soKSAX4zYOFV+aqJlEz/4j4fregvasJzEVtjDChvWqRjPvHwLqr5hx28Ayr6bsOs1Kuj87V0O8w==}
@ -15040,7 +15048,7 @@ packages:
'@types/jest': 27.4.1
bs-logger: 0.2.6
fast-json-stable-stringify: 2.1.0
jest: 27.5.1
jest: 27.5.1_ts-node@10.9.1
jest-util: 27.5.1
json5: 2.2.1
lodash.memoize: 4.1.2
@ -15209,7 +15217,6 @@ packages:
typescript: 4.6.4
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
/ts-node/10.9.1_mtczhn2fdutewshpiexgzmf2mq:
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
@ -15703,7 +15710,6 @@ packages:
/v8-compile-cache-lib/3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
dev: true
/v8-compile-cache/2.3.0:
resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
@ -16075,7 +16081,6 @@ packages:
/yn/3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
dev: true
/yocto-queue/0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}