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:
parent
fa92f4960d
commit
220ba58364
7 changed files with 249 additions and 308 deletions
|
@ -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",
|
||||
|
|
23
packages/integration-tests/src/client/dummy-storage.ts
Normal file
23
packages/integration-tests/src/client/dummy-storage.ts
Normal 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;
|
||||
}
|
||||
}
|
20
packages/integration-tests/src/client/logto-client.ts
Normal file
20
packages/integration-tests/src/client/logto-client.ts
Normal 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(),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
139
packages/integration-tests/src/ui-actions.ts
Normal file
139
packages/integration-tests/src/ui-actions.ts
Normal 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;
|
||||
};
|
|
@ -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
61
pnpm-lock.yaml
generated
|
@ -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==}
|
||||
|
|
Loading…
Add table
Reference in a new issue