mirror of
https://github.com/logto-io/logto.git
synced 2025-03-17 22:31:28 -05:00
feat(connector-alipay-native): add Alipay Native connector (#873)
* feat(connector-alipay-native): add Alipay Native connector * feat(connector-alipay-native): add Alipay Native connector to initialization
This commit is contained in:
parent
3daf574946
commit
9589aeafec
19 changed files with 822 additions and 103 deletions
2
packages/connector-alipay-native/README.md
Normal file
2
packages/connector-alipay-native/README.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
### Alipay Native Social Connector README
|
||||
placeholder
|
7
packages/connector-alipay-native/docs/config-template.md
Normal file
7
packages/connector-alipay-native/docs/config-template.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
```json
|
||||
{
|
||||
"appId": "<app-id>",
|
||||
"signType": "<signing-algorithm>",
|
||||
"privateKey": "<private-key>"
|
||||
}
|
||||
```
|
8
packages/connector-alipay-native/jest.config.ts
Normal file
8
packages/connector-alipay-native/jest.config.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { Config, merge } from '@silverhand/jest-config';
|
||||
|
||||
const config: Config.InitialOptions = merge({
|
||||
testEnvironment: 'node',
|
||||
setupFilesAfterEnv: ['jest-matcher-specific-error'],
|
||||
});
|
||||
|
||||
export default config;
|
63
packages/connector-alipay-native/package.json
Normal file
63
packages/connector-alipay-native/package.json
Normal file
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"name": "@logto/connector-alipay-native",
|
||||
"version": "0.1.0",
|
||||
"description": "Alipay Native implementation.",
|
||||
"main": "./lib/index.js",
|
||||
"exports": "./lib/index.js",
|
||||
"author": "Silverhand Inc. <contact@silverhand.io>",
|
||||
"license": "MPL-2.0",
|
||||
"files": [
|
||||
"lib",
|
||||
"docs"
|
||||
],
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"precommit": "lint-staged",
|
||||
"build": "rm -rf lib/ && tsc -p tsconfig.build.json",
|
||||
"lint": "eslint --ext .ts src",
|
||||
"lint:report": "pnpm lint --format json --output-file report.json",
|
||||
"dev": "rm -rf lib/ && tsc-watch -p tsconfig.build.json --preserveWatchOutput --onSuccess \"node ./lib/index.js\"",
|
||||
"test": "jest",
|
||||
"test:coverage": "jest --coverage --silent",
|
||||
"prepack": "pnpm build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@logto/connector-types": "^0.1.0",
|
||||
"@silverhand/jest-config": "^0.14.0",
|
||||
"@logto/shared": "^0.1.0",
|
||||
"@silverhand/essentials": "^1.1.0",
|
||||
"dayjs": "^1.10.5",
|
||||
"got": "^11.8.2",
|
||||
"iconv-lite": "0.6.3",
|
||||
"snakecase-keys": "^5.1.0",
|
||||
"zod": "^3.14.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/types": "^27.5.1",
|
||||
"@shopify/jest-koa-mocks": "^3.0.8",
|
||||
"@silverhand/eslint-config": "^0.14.0",
|
||||
"@silverhand/ts-config": "^0.14.0",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@types/lodash.pick": "^4.4.6",
|
||||
"@types/node": "^16.3.1",
|
||||
"@types/supertest": "^2.0.11",
|
||||
"eslint": "^8.10.0",
|
||||
"jest": "^27.5.1",
|
||||
"jest-matcher-specific-error": "^1.0.0",
|
||||
"lint-staged": "^12.0.0",
|
||||
"nock": "^13.2.2",
|
||||
"prettier": "^2.3.2",
|
||||
"supertest": "^6.2.2",
|
||||
"ts-jest": "^27.1.1",
|
||||
"tsc-watch": "^5.0.0",
|
||||
"typescript": "^4.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.0.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "@silverhand"
|
||||
},
|
||||
"prettier": "@silverhand/eslint-config/.prettierrc"
|
||||
}
|
41
packages/connector-alipay-native/src/constant.ts
Normal file
41
packages/connector-alipay-native/src/constant.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import path from 'path';
|
||||
|
||||
import { ConnectorType, ConnectorMetadata, ConnectorPlatform } from '@logto/connector-types';
|
||||
import { getFileContents } from '@logto/shared';
|
||||
|
||||
export const authorizationEndpoint = 'alipay://'; // This is used to arouse the native Alipay App
|
||||
export const alipayEndpoint = 'https://openapi.alipay.com/gateway.do';
|
||||
export const methodForAccessToken = 'alipay.system.oauth.token';
|
||||
export const methodForUserInfo = 'alipay.user.info.share';
|
||||
|
||||
export const alipaySigningAlgorithmMapping = {
|
||||
RSA: 'RSA-SHA1',
|
||||
RSA2: 'RSA-SHA256',
|
||||
} as const;
|
||||
export const alipaySigningAlgorithms = ['RSA', 'RSA2'] as const;
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-module
|
||||
const currentPath = __dirname;
|
||||
const pathToReadmeFile = path.join(currentPath, '..', 'README.md');
|
||||
const pathToConfigTemplate = path.join(currentPath, '..', 'docs', 'config-template.md');
|
||||
const readmeContentFallback = 'Please check README.md file directory.';
|
||||
const configTemplateFallback = 'Please check config-template.md file directory.';
|
||||
|
||||
export const defaultMetadata: ConnectorMetadata = {
|
||||
target: 'alipay',
|
||||
type: ConnectorType.Social,
|
||||
platform: ConnectorPlatform.Native,
|
||||
name: {
|
||||
en: 'Alipay',
|
||||
'zh-CN': '支付宝',
|
||||
},
|
||||
logo: './logo.png',
|
||||
description: {
|
||||
en: 'Sign In with Alipay',
|
||||
'zh-CN': '支付宝登录',
|
||||
},
|
||||
readme: getFileContents(pathToReadmeFile, readmeContentFallback),
|
||||
configTemplate: getFileContents(pathToConfigTemplate, configTemplateFallback),
|
||||
};
|
||||
|
||||
export const defaultTimeout = 5000;
|
237
packages/connector-alipay-native/src/index.test.ts
Normal file
237
packages/connector-alipay-native/src/index.test.ts
Normal file
|
@ -0,0 +1,237 @@
|
|||
import { ConnectorError, ConnectorErrorCodes, GetConnectorConfig } from '@logto/connector-types';
|
||||
import nock from 'nock';
|
||||
|
||||
import { AlipayNativeConnector } from '.';
|
||||
import { alipayEndpoint } from './constant';
|
||||
import { mockedAlipayNativeConfig, mockedAlipayNativeConfigWithValidPrivateKey } from './mock';
|
||||
import { AlipayNativeConfig } from './types';
|
||||
|
||||
const getConnectorConfig = jest.fn() as GetConnectorConfig<AlipayNativeConfig>;
|
||||
|
||||
const alipayNativeMethods = new AlipayNativeConnector(getConnectorConfig);
|
||||
|
||||
describe('validateConfig', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should pass on valid config', async () => {
|
||||
await expect(
|
||||
alipayNativeMethods.validateConfig(mockedAlipayNativeConfig)
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw on empty config', async () => {
|
||||
await expect(alipayNativeMethods.validateConfig({})).rejects.toThrowError();
|
||||
});
|
||||
|
||||
it('should throw when missing required properties', async () => {
|
||||
await expect(alipayNativeMethods.validateConfig({ appId: 'appId' })).rejects.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAuthorizationUri', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should get a valid uri by state', async () => {
|
||||
jest
|
||||
.spyOn(alipayNativeMethods, 'getConfig')
|
||||
.mockResolvedValueOnce(mockedAlipayNativeConfigWithValidPrivateKey);
|
||||
const authorizationUri = await alipayNativeMethods.getAuthorizationUri(
|
||||
'dummy-redirectUri',
|
||||
'dummy-state'
|
||||
);
|
||||
expect(authorizationUri).toBe('alipay://?app_id=2021000000000000');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAccessToken', () => {
|
||||
afterEach(() => {
|
||||
nock.cleanAll();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const alipayEndpointUrl = new URL(alipayEndpoint);
|
||||
|
||||
it('should get an accessToken by exchanging with code', async () => {
|
||||
jest
|
||||
.spyOn(alipayNativeMethods, 'getConfig')
|
||||
.mockResolvedValueOnce(mockedAlipayNativeConfigWithValidPrivateKey);
|
||||
nock(alipayEndpointUrl.origin)
|
||||
.post(alipayEndpointUrl.pathname)
|
||||
.query(true)
|
||||
.reply(200, {
|
||||
alipay_system_oauth_token_response: {
|
||||
user_id: '2088000000000000',
|
||||
access_token: 'access_token',
|
||||
expires_in: '3600',
|
||||
refresh_token: 'refresh_token',
|
||||
re_expires_in: '7200', // Expiring time of refresh token, in seconds
|
||||
},
|
||||
sign: '<signature>',
|
||||
});
|
||||
|
||||
const response = await alipayNativeMethods.getAccessToken('code');
|
||||
const { accessToken } = response;
|
||||
expect(accessToken).toEqual('access_token');
|
||||
});
|
||||
|
||||
it('should throw when accessToken is empty', async () => {
|
||||
jest
|
||||
.spyOn(alipayNativeMethods, 'getConfig')
|
||||
.mockResolvedValueOnce(mockedAlipayNativeConfigWithValidPrivateKey);
|
||||
nock(alipayEndpointUrl.origin)
|
||||
.post(alipayEndpointUrl.pathname)
|
||||
.query(true)
|
||||
.reply(200, {
|
||||
alipay_system_oauth_token_response: {
|
||||
user_id: '2088000000000000',
|
||||
access_token: undefined,
|
||||
expires_in: '3600',
|
||||
refresh_token: 'refresh_token',
|
||||
re_expires_in: '7200', // Expiring time of refresh token, in seconds
|
||||
},
|
||||
sign: '<signature>',
|
||||
});
|
||||
|
||||
await expect(alipayNativeMethods.getAccessToken('code')).rejects.toMatchError(
|
||||
new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid)
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail with wrong code', async () => {
|
||||
jest
|
||||
.spyOn(alipayNativeMethods, 'getConfig')
|
||||
.mockResolvedValueOnce(mockedAlipayNativeConfigWithValidPrivateKey);
|
||||
nock(alipayEndpointUrl.origin)
|
||||
.post(alipayEndpointUrl.pathname)
|
||||
.query(true)
|
||||
.reply(200, {
|
||||
error_response: {
|
||||
code: '20001',
|
||||
msg: 'Invalid code',
|
||||
sub_code: 'isv.code-invalid ',
|
||||
},
|
||||
sign: '<signature>',
|
||||
});
|
||||
|
||||
await expect(alipayNativeMethods.getAccessToken('wrong_code')).rejects.toMatchError(
|
||||
new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid, 'Invalid code')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserInfo', () => {
|
||||
afterEach(() => {
|
||||
nock.cleanAll();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const alipayEndpointUrl = new URL(alipayEndpoint);
|
||||
|
||||
it('should get userInfo with accessToken', async () => {
|
||||
jest
|
||||
.spyOn(alipayNativeMethods, 'getConfig')
|
||||
.mockResolvedValueOnce(mockedAlipayNativeConfigWithValidPrivateKey);
|
||||
nock(alipayEndpointUrl.origin)
|
||||
.post(alipayEndpointUrl.pathname)
|
||||
.query(true)
|
||||
.reply(200, {
|
||||
alipay_user_info_share_response: {
|
||||
code: '10000',
|
||||
msg: 'Success',
|
||||
user_id: '2088000000000000',
|
||||
nick_name: 'PlayboyEric',
|
||||
avatar: 'https://www.alipay.com/xxx.jpg',
|
||||
},
|
||||
sign: '<signature>',
|
||||
});
|
||||
|
||||
const { id, name, avatar } = await alipayNativeMethods.getUserInfo({
|
||||
accessToken: 'access_token',
|
||||
});
|
||||
expect(id).toEqual('2088000000000000');
|
||||
expect(name).toEqual('PlayboyEric');
|
||||
expect(avatar).toEqual('https://www.alipay.com/xxx.jpg');
|
||||
});
|
||||
|
||||
it('should throw with wrong accessToken', async () => {
|
||||
jest
|
||||
.spyOn(alipayNativeMethods, 'getConfig')
|
||||
.mockResolvedValueOnce(mockedAlipayNativeConfigWithValidPrivateKey);
|
||||
nock(alipayEndpointUrl.origin)
|
||||
.post(alipayEndpointUrl.pathname)
|
||||
.query(true)
|
||||
.reply(200, {
|
||||
alipay_user_info_share_response: {
|
||||
code: '20001',
|
||||
msg: 'Invalid auth token',
|
||||
sub_code: 'aop.invalid-auth-token',
|
||||
},
|
||||
sign: '<signature>',
|
||||
});
|
||||
|
||||
await expect(
|
||||
alipayNativeMethods.getUserInfo({ accessToken: 'wrong_access_token' })
|
||||
).rejects.toMatchError(
|
||||
new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid, 'Invalid auth token')
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw General error with other response error codes', async () => {
|
||||
jest
|
||||
.spyOn(alipayNativeMethods, 'getConfig')
|
||||
.mockResolvedValueOnce(mockedAlipayNativeConfigWithValidPrivateKey);
|
||||
nock(alipayEndpointUrl.origin)
|
||||
.post(alipayEndpointUrl.pathname)
|
||||
.query(true)
|
||||
.reply(200, {
|
||||
alipay_user_info_share_response: {
|
||||
code: '40002',
|
||||
msg: 'Invalid parameter',
|
||||
sub_code: 'isv.invalid-parameter',
|
||||
},
|
||||
sign: '<signature>',
|
||||
});
|
||||
|
||||
await expect(
|
||||
alipayNativeMethods.getUserInfo({ accessToken: 'wrong_access_token' })
|
||||
).rejects.toMatchError(new ConnectorError(ConnectorErrorCodes.General));
|
||||
});
|
||||
|
||||
it('should throw with right accessToken but empty userInfo', async () => {
|
||||
jest
|
||||
.spyOn(alipayNativeMethods, 'getConfig')
|
||||
.mockResolvedValueOnce(mockedAlipayNativeConfigWithValidPrivateKey);
|
||||
nock(alipayEndpointUrl.origin)
|
||||
.post(alipayEndpointUrl.pathname)
|
||||
.query(true)
|
||||
.reply(200, {
|
||||
alipay_user_info_share_response: {
|
||||
code: '10000',
|
||||
msg: 'Success',
|
||||
user_id: undefined,
|
||||
nick_name: 'PlayboyEric',
|
||||
avatar: 'https://www.alipay.com/xxx.jpg',
|
||||
},
|
||||
sign: '<signature>',
|
||||
});
|
||||
|
||||
await expect(
|
||||
alipayNativeMethods.getUserInfo({ accessToken: 'access_token' })
|
||||
).rejects.toMatchError(new ConnectorError(ConnectorErrorCodes.InvalidResponse));
|
||||
});
|
||||
|
||||
it('should throw with other request errors', async () => {
|
||||
jest
|
||||
.spyOn(alipayNativeMethods, 'getConfig')
|
||||
.mockResolvedValueOnce(mockedAlipayNativeConfigWithValidPrivateKey);
|
||||
nock(alipayEndpointUrl.origin).post(alipayEndpointUrl.pathname).query(true).reply(500);
|
||||
|
||||
await expect(
|
||||
alipayNativeMethods.getUserInfo({ accessToken: 'access_token' })
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
155
packages/connector-alipay-native/src/index.ts
Normal file
155
packages/connector-alipay-native/src/index.ts
Normal file
|
@ -0,0 +1,155 @@
|
|||
/**
|
||||
* The Implementation of OpenID Connect of Alipay Web Open Platform.
|
||||
* https://opendocs.alipay.com/open/218/105325
|
||||
* https://opendocs.alipay.com/open/218/105327
|
||||
*
|
||||
* https://opendocs.alipay.com/open/204/105295/
|
||||
* https://opendocs.alipay.com/open/204/105296/
|
||||
*/
|
||||
|
||||
import {
|
||||
AccessTokenObject,
|
||||
ConnectorError,
|
||||
ConnectorErrorCodes,
|
||||
ConnectorMetadata,
|
||||
GetAccessToken,
|
||||
GetAuthorizationUri,
|
||||
GetUserInfo,
|
||||
ValidateConfig,
|
||||
SocialConnector,
|
||||
GetConnectorConfig,
|
||||
} from '@logto/connector-types';
|
||||
import { assert } from '@silverhand/essentials';
|
||||
import dayjs from 'dayjs';
|
||||
import got from 'got';
|
||||
|
||||
import {
|
||||
alipayEndpoint,
|
||||
authorizationEndpoint,
|
||||
methodForAccessToken,
|
||||
methodForUserInfo,
|
||||
defaultMetadata,
|
||||
defaultTimeout,
|
||||
} from './constant';
|
||||
import {
|
||||
alipayNativeConfigGuard,
|
||||
AlipayNativeConfig,
|
||||
AccessTokenResponse,
|
||||
UserInfoResponse,
|
||||
} from './types';
|
||||
import { signingPamameters } from './utils';
|
||||
import type { SigningPamameters } from './utils';
|
||||
|
||||
export type { AlipayNativeConfig } from './types';
|
||||
|
||||
export class AlipayNativeConnector implements SocialConnector {
|
||||
public metadata: ConnectorMetadata = defaultMetadata;
|
||||
|
||||
public getConfig: GetConnectorConfig<AlipayNativeConfig>;
|
||||
|
||||
private readonly signingPamameters: SigningPamameters = signingPamameters;
|
||||
|
||||
constructor(getConnectorConfig: GetConnectorConfig<AlipayNativeConfig>) {
|
||||
this.getConfig = getConnectorConfig;
|
||||
}
|
||||
|
||||
public validateConfig: ValidateConfig = async (config: unknown) => {
|
||||
const result = alipayNativeConfigGuard.safeParse(config);
|
||||
|
||||
if (!result.success) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig, result.error.message);
|
||||
}
|
||||
};
|
||||
|
||||
public getAuthorizationUri: GetAuthorizationUri = async () => {
|
||||
const { appId } = await this.getConfig(this.metadata.target, this.metadata.platform);
|
||||
|
||||
const queryParameters = new URLSearchParams({ app_id: appId });
|
||||
|
||||
return `${authorizationEndpoint}?${queryParameters.toString()}`;
|
||||
};
|
||||
|
||||
public getAccessToken: GetAccessToken = async (code): Promise<AccessTokenObject> => {
|
||||
const config = await this.getConfig(this.metadata.target, this.metadata.platform);
|
||||
const initSearchParameters = {
|
||||
method: methodForAccessToken,
|
||||
format: 'JSON',
|
||||
timestamp: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
version: '1.0',
|
||||
grant_type: 'authorization_code',
|
||||
code,
|
||||
charset: 'UTF8',
|
||||
...config,
|
||||
};
|
||||
const signedSearchParameters = this.signingPamameters(initSearchParameters);
|
||||
|
||||
const response = await got
|
||||
.post(alipayEndpoint, {
|
||||
searchParams: signedSearchParameters,
|
||||
timeout: defaultTimeout,
|
||||
})
|
||||
.json<AccessTokenResponse>();
|
||||
|
||||
const { msg, sub_msg } = response.error_response ?? {};
|
||||
assert(
|
||||
response.alipay_system_oauth_token_response,
|
||||
new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid, msg ?? sub_msg)
|
||||
);
|
||||
const { access_token: accessToken } = response.alipay_system_oauth_token_response;
|
||||
|
||||
assert(accessToken, new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));
|
||||
|
||||
return { accessToken };
|
||||
};
|
||||
|
||||
public getUserInfo: GetUserInfo = async (accessTokenObject) => {
|
||||
const config = await this.getConfig(this.metadata.target, this.metadata.platform);
|
||||
const { accessToken } = accessTokenObject;
|
||||
assert(
|
||||
accessToken && config,
|
||||
new ConnectorError(ConnectorErrorCodes.InsufficientRequestParameters)
|
||||
);
|
||||
|
||||
const initSearchParameters = {
|
||||
method: methodForUserInfo,
|
||||
format: 'JSON',
|
||||
timestamp: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
version: '1.0',
|
||||
grant_type: 'authorization_code',
|
||||
auth_token: accessToken,
|
||||
biz_content: JSON.stringify({}),
|
||||
charset: 'UTF8',
|
||||
...config,
|
||||
};
|
||||
const signedSearchParameters = this.signingPamameters(initSearchParameters);
|
||||
|
||||
const response = await got
|
||||
.post(alipayEndpoint, {
|
||||
searchParams: signedSearchParameters,
|
||||
timeout: defaultTimeout,
|
||||
})
|
||||
.json<UserInfoResponse>();
|
||||
|
||||
const {
|
||||
user_id: id,
|
||||
avatar,
|
||||
nick_name: name,
|
||||
sub_msg,
|
||||
sub_code,
|
||||
msg,
|
||||
code,
|
||||
} = response.alipay_user_info_share_response;
|
||||
|
||||
if (sub_msg || sub_code) {
|
||||
if (code === '20001') {
|
||||
throw new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid, msg);
|
||||
}
|
||||
throw new ConnectorError(ConnectorErrorCodes.General);
|
||||
}
|
||||
// TODO: elaborate on the error messages for all social connectors (see LOG-2157)
|
||||
|
||||
assert(id, new ConnectorError(ConnectorErrorCodes.InvalidResponse));
|
||||
|
||||
return { id, avatar, name };
|
||||
};
|
||||
}
|
25
packages/connector-alipay-native/src/mock.ts
Normal file
25
packages/connector-alipay-native/src/mock.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { AlipayNativeConfig } from './types';
|
||||
|
||||
export const mockedTimestamp = '2022-02-22 22:22:22';
|
||||
|
||||
export const mockedAlipayNativeConfig: AlipayNativeConfig = {
|
||||
appId: '2021000000000000',
|
||||
signType: 'RSA2',
|
||||
privateKey: '<private-key>',
|
||||
};
|
||||
|
||||
export const mockedAlipayNativeConfigWithValidPrivateKey: AlipayNativeConfig = {
|
||||
appId: '2021000000000000',
|
||||
signType: 'RSA2',
|
||||
privateKey:
|
||||
'-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC52SvnlRfzJJDR\nA1h4MX2JWV7Yt1j+1gvtQuLh0RYbE0AgRyz8CXFcJegO8gNyUQ05vrc1RMVzvNh8\njfjLpIX8an88KE4FyoG5P8NWrwPw5ZXOnzdvNxAV8QWOU+rT4WAdCsx4++mLlb5v\nGL18R77f3WLgY23bFtcGr9q7/qOaLzNxEe4idX1eLf7Ba/gQRY0awA55/Epd1Mi7\nLqTfxTd11PoBZQPe0vnuChp3P2l1MNpIJ5G1eQ4RXgI4UMClEbGRlBN7GUlXy5p7\ng6RtvOcwmBNoE4i0/HbvaanY3u7oenST3iSzEXa2hXMjnZPvg0G4Y5mq/V6XJPTh\nJrFc9XzFAgMBAAECggEAXfmNtN10LdN4kugBLU3BL9mMF0Om8b1kbIXc2djzN5+l\nVm0HNy7DLphQXnZL/ds0N9XTKFFtEpgUU+8qNjcsNTXYvp+WzGDY9cZjTQrUkFRX\nSxLBYjBSpvWoHI8ceCVHh4f1Wtvu/VEr6Vt2PUi+IM7+d35vh1BmTJBRp6wcKBMH\nXdfjWIi5z37pTXD3OTfUjBCtzA2DX0vY6UTsmD9UI0Mb6IJdT6qugiGODFdlsduA\nWJoZlXV1VbHcvGt7DoeQgzA45sr5siUnm+ntTVBHOR/hoZQrr0DY/O/MLKYUj/+r\nZMKKpx/7VHnWfMia2EOHfjW8vUlnraUzI+5E2/FzIQKBgQDgi7S7pfRux8YONGP2\nRtHPkF8d0YllsfKedhqF3cQlJ1dhxzVqHOi1IFn6ttuuYy5UsP5apYa2kj2UUPCa\nZGGi19Vnc+RHThpR4K6/OGFrpbINAgiVJLj7F8GXzqeA7W2ZHMp1R+oB+oTxih6t\nU0dbeTP01kbBV1/7+ZUKPhLE6QKBgQDT4cMgq01F/WIGGd1GUHZQjH5bqtNiJpIf\n2Q2OTw/gn1DVnwDXpHuXPxtC3NRoaRW/dTqsF6AAkMja3voPM3sYJurGBdU8pZPC\nquc9mqqu6TR5gX3KL1lSESvMBEgfLUy/f0gI3JNw1mG17pIhnXmOB2be3HfZPcj3\nwKWlluY/fQKBgDLll97c3A3sPGll2K6vGMmqmNTCdRlW/36JmLN1NAuT4kuoguP9\nj4XWwm6A2kSp+It73vue/20MsuaWfiMQ08y8jYO4kirTekXK3vE7D2H+GeC28EkW\nHNPVa61ES1V++9Oz4fQ5i8JNDatOOmvhL5B9ZZh+pWUXsAsGZJEAxvJZAoGAMPHO\n5GYN1KQil6wz3EFMA3Fg4wYEDIFCcg7uvbfvwACtaJtxU18QmbCfOIPQoUndFzwa\nUJSohljrvPuTIh3PSpX618GTL45EIszd2/I1iXAfig3qo+DqLjX/OwKmMmWBfB8H\n4dwqRv+O1LsGkLNS2AdHsSWWnd1S5kBfQ3AnQfUCgYACM8ldXZv7uGt9uZBmxile\nB0Hg5w7F1v9VD/m9ko+INAISz8OVkD83pCEoyHwlr20JjiF+yzAakOuq6rBi+l/V\n1veSiTDUcZhciuq1G178dFYepJqisFBu7bAM+WBS4agTTtxdSLZkHeS4VX+H3DOc\ntri43NXw6QS7uQ5/+2TsEw==\n-----END PRIVATE KEY-----',
|
||||
};
|
||||
|
||||
export const mockedAlipayNativePublicParameters = {
|
||||
format: 'JSON',
|
||||
grantType: 'authorization_code',
|
||||
timestamp: mockedTimestamp,
|
||||
version: '1.0',
|
||||
charset: 'UTF8',
|
||||
method: '<method-placeholder>',
|
||||
};
|
45
packages/connector-alipay-native/src/types.ts
Normal file
45
packages/connector-alipay-native/src/types.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { alipaySigningAlgorithms } from './constant';
|
||||
|
||||
export const alipayNativeConfigGuard = z.object({
|
||||
appId: z.string().max(16),
|
||||
privateKey: z.string(),
|
||||
signType: z.enum(alipaySigningAlgorithms),
|
||||
});
|
||||
|
||||
export type AlipayNativeConfig = z.infer<typeof alipayNativeConfigGuard>;
|
||||
|
||||
// `error_response` and `alipay_system_oauth_token_response` are mutually exclusive.
|
||||
export type AccessTokenResponse = {
|
||||
error_response?: {
|
||||
code: string;
|
||||
msg: string; // To know `code` and `msg` details, see: https://opendocs.alipay.com/common/02km9f
|
||||
sub_code?: string;
|
||||
sub_msg?: string;
|
||||
};
|
||||
sign: string; // To know `sign` details, see: https://opendocs.alipay.com/common/02kf5q
|
||||
alipay_system_oauth_token_response?: {
|
||||
user_id: string; // Unique Alipay ID, 16 digits starts with '2088'
|
||||
access_token: string;
|
||||
expires_in: string; // In seconds
|
||||
refresh_token: string;
|
||||
re_expires_in: string; // Expiring time of refresh token, in seconds
|
||||
};
|
||||
};
|
||||
|
||||
export type UserInfoResponse = {
|
||||
sign: string; // To know `sign` details, see: https://opendocs.alipay.com/common/02kf5q
|
||||
alipay_user_info_share_response: {
|
||||
user_id?: string; // String of digits with max length of 16
|
||||
avatar?: string; // URL of avatar
|
||||
province?: string;
|
||||
city?: string;
|
||||
nick_name?: string;
|
||||
gender?: string; // Enum type: 'F' for female, 'M' for male
|
||||
code: string;
|
||||
msg: string; // To know `code` and `msg` details, see: https://opendocs.alipay.com/common/02km9f
|
||||
sub_code?: string;
|
||||
sub_msg?: string;
|
||||
};
|
||||
};
|
60
packages/connector-alipay-native/src/utils.test.ts
Normal file
60
packages/connector-alipay-native/src/utils.test.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { methodForAccessToken } from './constant';
|
||||
import {
|
||||
mockedAlipayNativeConfigWithValidPrivateKey,
|
||||
mockedAlipayNativePublicParameters,
|
||||
} from './mock';
|
||||
import { signingPamameters } from './utils';
|
||||
|
||||
const listenJSONParse = jest.spyOn(JSON, 'parse');
|
||||
const listenJSONStringify = jest.spyOn(JSON, 'stringify');
|
||||
|
||||
describe('signingParameters', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const testingParameters = {
|
||||
...mockedAlipayNativePublicParameters,
|
||||
...mockedAlipayNativeConfigWithValidPrivateKey,
|
||||
method: methodForAccessToken,
|
||||
code: '7ffeb112fbb6495c9e7dfb720380DD39',
|
||||
};
|
||||
|
||||
it('should return exact signature with the given parameters (functionality check)', () => {
|
||||
const decamelizedParameters = signingPamameters(testingParameters);
|
||||
expect(decamelizedParameters.sign).toBe(
|
||||
'td9+u0puul3HgbwLGL1X6z/vKKB/K25K5pjtLT/snQOp292RX3Y5j+FQUVuazTI2l65GpoSgA83LWNT9htQgtmdBmkCQ3bO6RWs38+2ZmBmH7MvpHx4ebUDhtebLUmHNuRFaNcpAZW92b0ZSuuJuahpLK8VNBgXljq+x0aD7WCRudPxc9fikR65NGxr5bwepl/9IqgMxwtajh1+PEJyhGGJhJxS1dCktGN0EiWXWNiogYT8NlFVCmw7epByKzCBWu4sPflU52gJMFHTdbav/0Tk/ZBs8RyP8Z8kcJA0jom2iT+dHqDpgkdzEmsR360UVNKCu5X7ltIiiObsAWmfluQ=='
|
||||
);
|
||||
});
|
||||
|
||||
it('should return exact signature with the given parameters (with empty property in testingParameters)', () => {
|
||||
const decamelizedParameters = signingPamameters({
|
||||
...testingParameters,
|
||||
emptyProperty: '',
|
||||
});
|
||||
expect(decamelizedParameters.sign).toBe(
|
||||
'td9+u0puul3HgbwLGL1X6z/vKKB/K25K5pjtLT/snQOp292RX3Y5j+FQUVuazTI2l65GpoSgA83LWNT9htQgtmdBmkCQ3bO6RWs38+2ZmBmH7MvpHx4ebUDhtebLUmHNuRFaNcpAZW92b0ZSuuJuahpLK8VNBgXljq+x0aD7WCRudPxc9fikR65NGxr5bwepl/9IqgMxwtajh1+PEJyhGGJhJxS1dCktGN0EiWXWNiogYT8NlFVCmw7epByKzCBWu4sPflU52gJMFHTdbav/0Tk/ZBs8RyP8Z8kcJA0jom2iT+dHqDpgkdzEmsR360UVNKCu5X7ltIiiObsAWmfluQ=='
|
||||
);
|
||||
});
|
||||
|
||||
it('should not call JSON.parse() when biz_content is empty', () => {
|
||||
signingPamameters(testingParameters);
|
||||
expect(listenJSONParse).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call JSON.parse() when biz_content is not empty', () => {
|
||||
signingPamameters({
|
||||
...testingParameters,
|
||||
biz_content: JSON.stringify({ AB: 'AB' }),
|
||||
});
|
||||
expect(listenJSONParse).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call JSON.stringify() when some value is object string', () => {
|
||||
signingPamameters({
|
||||
...testingParameters,
|
||||
testObject: JSON.stringify({ AB: 'AB' }),
|
||||
});
|
||||
expect(listenJSONStringify).toHaveBeenCalled();
|
||||
});
|
||||
});
|
51
packages/connector-alipay-native/src/utils.ts
Normal file
51
packages/connector-alipay-native/src/utils.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import * as crypto from 'crypto';
|
||||
|
||||
import * as iconv from 'iconv-lite';
|
||||
import snakeCaseKeys from 'snakecase-keys';
|
||||
|
||||
import { alipaySigningAlgorithmMapping } from './constant';
|
||||
import { AlipayNativeConfig } from './types';
|
||||
|
||||
export type SigningPamameters = (
|
||||
parameters: AlipayNativeConfig & Record<string, string | undefined>
|
||||
) => Record<string, string>;
|
||||
|
||||
// Reference: https://github.com/alipay/alipay-sdk-nodejs-all/blob/10d78e0adc7f310d5b07567ce7e4c13a3f6c768f/lib/util.ts
|
||||
export const signingPamameters: SigningPamameters = (
|
||||
parameters: AlipayNativeConfig & Record<string, string | undefined>
|
||||
): Record<string, string> => {
|
||||
const { biz_content, privateKey, ...rest } = parameters;
|
||||
const signParameters = snakeCaseKeys(
|
||||
biz_content
|
||||
? {
|
||||
...rest,
|
||||
bizContent: JSON.stringify(snakeCaseKeys(JSON.parse(biz_content))),
|
||||
}
|
||||
: rest
|
||||
);
|
||||
|
||||
const decamelizeParameters = snakeCaseKeys(signParameters);
|
||||
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutating-methods
|
||||
const sortedParametersAsString = Object.entries(decamelizeParameters)
|
||||
.map(([key, value]) => {
|
||||
// Supported Encodings can be found at https://github.com/ashtuchkin/iconv-lite/wiki/Supported-Encodings
|
||||
|
||||
if (value) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
return `${key}=${iconv.encode(value, rest.charset ?? 'UTF8')}`;
|
||||
}
|
||||
|
||||
return '';
|
||||
})
|
||||
.filter((keyValueString) => keyValueString)
|
||||
.sort()
|
||||
.join('&');
|
||||
|
||||
const sign = crypto
|
||||
.createSign(alipaySigningAlgorithmMapping[rest.signType])
|
||||
.update(sortedParametersAsString, 'utf8')
|
||||
.sign(privateKey, 'base64');
|
||||
|
||||
return { ...decamelizeParameters, sign };
|
||||
};
|
10
packages/connector-alipay-native/tsconfig.base.json
Normal file
10
packages/connector-alipay-native/tsconfig.base.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "@silverhand/ts-config/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
}
|
||||
}
|
5
packages/connector-alipay-native/tsconfig.build.json
Normal file
5
packages/connector-alipay-native/tsconfig.build.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "./tsconfig.base",
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
7
packages/connector-alipay-native/tsconfig.json
Normal file
7
packages/connector-alipay-native/tsconfig.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"extends": "./tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"types": ["node", "jest", "jest-matcher-specific-error"]
|
||||
},
|
||||
"include": ["src", "jest.config.ts"]
|
||||
}
|
6
packages/connector-alipay-native/tsconfig.test.json
Normal file
6
packages/connector-alipay-native/tsconfig.test.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"extends": "./tsconfig",
|
||||
"compilerOptions": {
|
||||
"isolatedModules": false
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@logto/connector-alipay": "^0.1.0",
|
||||
"@logto/connector-alipay-native": "^0.1.0",
|
||||
"@logto/connector-aliyun-dm": "^0.1.0",
|
||||
"@logto/connector-aliyun-sms": "^0.1.0",
|
||||
"@logto/connector-facebook": "^0.1.0",
|
||||
|
|
|
@ -19,6 +19,14 @@ const alipayConnector = {
|
|||
config: {},
|
||||
createdAt: 1_646_382_233_911,
|
||||
};
|
||||
const alipayNativeConnector = {
|
||||
id: 'alipay-native',
|
||||
target: 'alipay',
|
||||
platform: ConnectorPlatform.Native,
|
||||
enabled: false,
|
||||
config: {},
|
||||
createdAt: 1_646_382_233_911,
|
||||
};
|
||||
const aliyunDmConnector = {
|
||||
id: 'aliyun-dm',
|
||||
target: 'aliyun-dm',
|
||||
|
@ -94,6 +102,7 @@ const wechatNativeConnector = {
|
|||
|
||||
const connectors = [
|
||||
alipayConnector,
|
||||
alipayNativeConnector,
|
||||
aliyunDmConnector,
|
||||
aliyunSmsConnector,
|
||||
facebookConnector,
|
||||
|
@ -119,15 +128,16 @@ describe('getConnectorInstances', () => {
|
|||
const connectorInstances = await getConnectorInstances();
|
||||
expect(connectorInstances).toHaveLength(connectorInstances.length);
|
||||
expect(connectorInstances[0]).toHaveProperty('connector', alipayConnector);
|
||||
expect(connectorInstances[1]).toHaveProperty('connector', aliyunDmConnector);
|
||||
expect(connectorInstances[2]).toHaveProperty('connector', aliyunSmsConnector);
|
||||
expect(connectorInstances[3]).toHaveProperty('connector', facebookConnector);
|
||||
expect(connectorInstances[4]).toHaveProperty('connector', githubConnector);
|
||||
expect(connectorInstances[5]).toHaveProperty('connector', googleConnector);
|
||||
expect(connectorInstances[6]).toHaveProperty('connector', sendGridMailConnector);
|
||||
expect(connectorInstances[7]).toHaveProperty('connector', twilioSmsConnector);
|
||||
expect(connectorInstances[8]).toHaveProperty('connector', wechatConnector);
|
||||
expect(connectorInstances[9]).toHaveProperty('connector', wechatNativeConnector);
|
||||
expect(connectorInstances[1]).toHaveProperty('connector', alipayNativeConnector);
|
||||
expect(connectorInstances[2]).toHaveProperty('connector', aliyunDmConnector);
|
||||
expect(connectorInstances[3]).toHaveProperty('connector', aliyunSmsConnector);
|
||||
expect(connectorInstances[4]).toHaveProperty('connector', facebookConnector);
|
||||
expect(connectorInstances[5]).toHaveProperty('connector', githubConnector);
|
||||
expect(connectorInstances[6]).toHaveProperty('connector', googleConnector);
|
||||
expect(connectorInstances[7]).toHaveProperty('connector', sendGridMailConnector);
|
||||
expect(connectorInstances[8]).toHaveProperty('connector', twilioSmsConnector);
|
||||
expect(connectorInstances[9]).toHaveProperty('connector', wechatConnector);
|
||||
expect(connectorInstances[10]).toHaveProperty('connector', wechatNativeConnector);
|
||||
});
|
||||
|
||||
test('should throw if any required connector does not exist in DB', async () => {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { AlipayConnector } from '@logto/connector-alipay';
|
||||
import { AlipayNativeConnector } from '@logto/connector-alipay-native';
|
||||
import { AliyunDmConnector } from '@logto/connector-aliyun-dm';
|
||||
import { AliyunSmsConnector } from '@logto/connector-aliyun-sms';
|
||||
import { FacebookConnector } from '@logto/connector-facebook';
|
||||
|
@ -18,6 +19,7 @@ import { buildIndexWithTargetAndPlatform, getConnectorConfig } from './utilities
|
|||
|
||||
const allConnectors: IConnector[] = [
|
||||
new AlipayConnector(getConnectorConfig),
|
||||
new AlipayNativeConnector(getConnectorConfig),
|
||||
new AliyunDmConnector(getConnectorConfig),
|
||||
new AliyunSmsConnector(getConnectorConfig),
|
||||
new FacebookConnector(getConnectorConfig),
|
||||
|
|
172
pnpm-lock.yaml
generated
172
pnpm-lock.yaml
generated
|
@ -77,6 +77,65 @@ importers:
|
|||
tsc-watch: 5.0.3_typescript@4.6.3
|
||||
typescript: 4.6.3
|
||||
|
||||
packages/connector-alipay-native:
|
||||
specifiers:
|
||||
'@jest/types': ^27.5.1
|
||||
'@logto/connector-types': ^0.1.0
|
||||
'@logto/shared': ^0.1.0
|
||||
'@shopify/jest-koa-mocks': ^3.0.8
|
||||
'@silverhand/eslint-config': ^0.14.0
|
||||
'@silverhand/essentials': ^1.1.0
|
||||
'@silverhand/jest-config': ^0.14.0
|
||||
'@silverhand/ts-config': ^0.14.0
|
||||
'@types/jest': ^27.4.1
|
||||
'@types/lodash.pick': ^4.4.6
|
||||
'@types/node': ^16.3.1
|
||||
'@types/supertest': ^2.0.11
|
||||
dayjs: ^1.10.5
|
||||
eslint: ^8.10.0
|
||||
got: ^11.8.2
|
||||
iconv-lite: 0.6.3
|
||||
jest: ^27.5.1
|
||||
jest-matcher-specific-error: ^1.0.0
|
||||
lint-staged: ^12.0.0
|
||||
nock: ^13.2.2
|
||||
prettier: ^2.3.2
|
||||
snakecase-keys: ^5.1.0
|
||||
supertest: ^6.2.2
|
||||
ts-jest: ^27.1.1
|
||||
tsc-watch: ^5.0.0
|
||||
typescript: ^4.6.2
|
||||
zod: ^3.14.3
|
||||
dependencies:
|
||||
'@logto/connector-types': link:../connector-types
|
||||
'@logto/shared': link:../shared
|
||||
'@silverhand/essentials': 1.1.7
|
||||
'@silverhand/jest-config': 0.14.0_53ggqi2i4rbcfjtktmjua6zili
|
||||
dayjs: 1.10.7
|
||||
got: 11.8.3
|
||||
iconv-lite: 0.6.3
|
||||
snakecase-keys: 5.1.2
|
||||
zod: 3.14.3
|
||||
devDependencies:
|
||||
'@jest/types': 27.5.1
|
||||
'@shopify/jest-koa-mocks': 3.1.5
|
||||
'@silverhand/eslint-config': 0.14.0_rqoong6vegs374egqglqjbgiwm
|
||||
'@silverhand/ts-config': 0.14.0_typescript@4.6.4
|
||||
'@types/jest': 27.4.1
|
||||
'@types/lodash.pick': 4.4.6
|
||||
'@types/node': 16.11.12
|
||||
'@types/supertest': 2.0.11
|
||||
eslint: 8.10.0
|
||||
jest: 27.5.1
|
||||
jest-matcher-specific-error: 1.0.0
|
||||
lint-staged: 12.4.0
|
||||
nock: 13.2.2
|
||||
prettier: 2.5.1
|
||||
supertest: 6.2.2
|
||||
ts-jest: 27.1.1_53ggqi2i4rbcfjtktmjua6zili
|
||||
tsc-watch: 5.0.3_typescript@4.6.4
|
||||
typescript: 4.6.4
|
||||
|
||||
packages/connector-aliyun-dm:
|
||||
specifiers:
|
||||
'@jest/types': ^27.5.1
|
||||
|
@ -636,6 +695,7 @@ importers:
|
|||
packages/core:
|
||||
specifiers:
|
||||
'@logto/connector-alipay': ^0.1.0
|
||||
'@logto/connector-alipay-native': ^0.1.0
|
||||
'@logto/connector-aliyun-dm': ^0.1.0
|
||||
'@logto/connector-aliyun-sms': ^0.1.0
|
||||
'@logto/connector-facebook': ^0.1.0
|
||||
|
@ -709,6 +769,7 @@ importers:
|
|||
zod: ^3.14.3
|
||||
dependencies:
|
||||
'@logto/connector-alipay': link:../connector-alipay
|
||||
'@logto/connector-alipay-native': link:../connector-alipay-native
|
||||
'@logto/connector-aliyun-dm': link:../connector-aliyun-dm
|
||||
'@logto/connector-aliyun-sms': link:../connector-aliyun-sms
|
||||
'@logto/connector-facebook': link:../connector-facebook
|
||||
|
@ -3765,7 +3826,6 @@ packages:
|
|||
pacote: 11.3.5
|
||||
semver: 7.3.7
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -3934,7 +3994,6 @@ packages:
|
|||
whatwg-url: 8.7.0
|
||||
yargs-parser: 20.2.4
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -4132,7 +4191,6 @@ packages:
|
|||
npm-registry-fetch: 9.0.0
|
||||
npmlog: 4.1.2
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -4162,7 +4220,6 @@ packages:
|
|||
pify: 5.0.0
|
||||
read-package-json: 3.0.1
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -4297,7 +4354,6 @@ packages:
|
|||
pacote: 11.3.5
|
||||
semver: 7.3.7
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- encoding
|
||||
- supports-color
|
||||
dev: true
|
||||
|
@ -4596,8 +4652,6 @@ packages:
|
|||
promise-retry: 2.0.1
|
||||
semver: 7.3.7
|
||||
which: 2.0.2
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
dev: true
|
||||
|
||||
/@npmcli/installed-package-contents/1.0.7:
|
||||
|
@ -5575,6 +5629,16 @@ packages:
|
|||
resolution: {integrity: sha512-Yykovind6xzqAqd0t5umrdAGPlGLTE80cy80UkEnbt8Zv5zEYTFzJSNPQ81TY8BSpRreubu1oE54iHBv2UVnTQ==}
|
||||
dev: true
|
||||
|
||||
/@shopify/jest-koa-mocks/3.1.5:
|
||||
resolution: {integrity: sha512-gQ3/7ELerv00TWO37AGFX5mT9CsFCS+3/UbKMuoIlKEU0QH2OX8BV9WBf/EKw7adCDNlxss0lqV6J8kf5pgr4A==}
|
||||
engines: {node: '>=12.14.0'}
|
||||
dependencies:
|
||||
koa: 2.13.4
|
||||
node-mocks-http: 1.11.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@shopify/jest-koa-mocks/4.0.0:
|
||||
resolution: {integrity: sha512-vzj95s/xPcASmhdXGW9MMzNlcx0Wdn6WTmcuCUgJbB1fm8rGepf+QYOZF6dtLMYC2asLROX/jygsE57q90DAsA==}
|
||||
engines: {node: ^14.17.0 || >=16.0.0}
|
||||
|
@ -5612,7 +5676,6 @@ packages:
|
|||
stylelint-config-xo-scss: 0.15.0_zhymizk4kfitko2u2d4p3qwyee
|
||||
transitivePeerDependencies:
|
||||
- eslint
|
||||
- eslint-import-resolver-webpack
|
||||
- postcss
|
||||
- prettier
|
||||
- supports-color
|
||||
|
@ -5636,7 +5699,7 @@ packages:
|
|||
eslint-import-resolver-typescript: 2.5.0_rnagsyfcubvpoxo2ynj23pim7u
|
||||
eslint-plugin-consistent-default-export-name: 0.0.7
|
||||
eslint-plugin-eslint-comments: 3.2.0_eslint@8.10.0
|
||||
eslint-plugin-import: 2.25.4_sidoke6kqbkbdht6nlmwbfnany
|
||||
eslint-plugin-import: 2.25.4_eslint@8.10.0
|
||||
eslint-plugin-no-use-extend-native: 0.5.0
|
||||
eslint-plugin-node: 11.1.0_eslint@8.10.0
|
||||
eslint-plugin-prettier: 3.4.1_6pitu4b2tqihty6rv5qeiyb35m
|
||||
|
@ -5646,7 +5709,6 @@ packages:
|
|||
pkg-dir: 4.2.0
|
||||
prettier: 2.5.1
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
- typescript
|
||||
dev: true
|
||||
|
@ -5668,7 +5730,7 @@ packages:
|
|||
eslint-import-resolver-typescript: 2.5.0_rnagsyfcubvpoxo2ynj23pim7u
|
||||
eslint-plugin-consistent-default-export-name: 0.0.7
|
||||
eslint-plugin-eslint-comments: 3.2.0_eslint@8.10.0
|
||||
eslint-plugin-import: 2.25.4_sidoke6kqbkbdht6nlmwbfnany
|
||||
eslint-plugin-import: 2.25.4_eslint@8.10.0
|
||||
eslint-plugin-no-use-extend-native: 0.5.0
|
||||
eslint-plugin-node: 11.1.0_eslint@8.10.0
|
||||
eslint-plugin-prettier: 3.4.1_6pitu4b2tqihty6rv5qeiyb35m
|
||||
|
@ -5678,7 +5740,6 @@ packages:
|
|||
pkg-dir: 4.2.0
|
||||
prettier: 2.5.1
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
- typescript
|
||||
dev: true
|
||||
|
@ -5700,7 +5761,7 @@ packages:
|
|||
eslint-import-resolver-typescript: 2.5.0_rnagsyfcubvpoxo2ynj23pim7u
|
||||
eslint-plugin-consistent-default-export-name: 0.0.7
|
||||
eslint-plugin-eslint-comments: 3.2.0_eslint@8.10.0
|
||||
eslint-plugin-import: 2.25.4_sidoke6kqbkbdht6nlmwbfnany
|
||||
eslint-plugin-import: 2.25.4_eslint@8.10.0
|
||||
eslint-plugin-no-use-extend-native: 0.5.0
|
||||
eslint-plugin-node: 11.1.0_eslint@8.10.0
|
||||
eslint-plugin-prettier: 3.4.1_6pitu4b2tqihty6rv5qeiyb35m
|
||||
|
@ -5710,7 +5771,6 @@ packages:
|
|||
pkg-dir: 4.2.0
|
||||
prettier: 2.5.1
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
- typescript
|
||||
dev: true
|
||||
|
@ -7762,8 +7822,6 @@ packages:
|
|||
qs: 6.9.7
|
||||
raw-body: 2.4.3
|
||||
type-is: 1.6.18
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/bonjour-service/1.0.11:
|
||||
|
@ -7939,8 +7997,6 @@ packages:
|
|||
ssri: 8.0.1
|
||||
tar: 6.1.11
|
||||
unique-filename: 1.1.1
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
dev: true
|
||||
|
||||
/cache-content-type/1.0.1:
|
||||
|
@ -8456,8 +8512,6 @@ packages:
|
|||
on-headers: 1.0.2
|
||||
safe-buffer: 5.1.2
|
||||
vary: 1.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/concat-map/0.0.1:
|
||||
|
@ -9033,22 +9087,12 @@ packages:
|
|||
|
||||
/debug/2.6.9:
|
||||
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.0.0
|
||||
dev: true
|
||||
|
||||
/debug/3.2.7:
|
||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
|
@ -9277,8 +9321,6 @@ packages:
|
|||
dependencies:
|
||||
address: 1.1.2
|
||||
debug: 2.6.9
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/detect-port/1.3.0:
|
||||
|
@ -9288,8 +9330,6 @@ packages:
|
|||
dependencies:
|
||||
address: 1.1.2
|
||||
debug: 2.6.9
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/dezalgo/1.0.3:
|
||||
|
@ -9768,8 +9808,6 @@ packages:
|
|||
dependencies:
|
||||
debug: 3.2.7
|
||||
resolve: 1.22.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-import-resolver-typescript/2.5.0_rnagsyfcubvpoxo2ynj23pim7u:
|
||||
|
@ -9781,7 +9819,7 @@ packages:
|
|||
dependencies:
|
||||
debug: 4.3.3
|
||||
eslint: 8.10.0
|
||||
eslint-plugin-import: 2.25.4_sidoke6kqbkbdht6nlmwbfnany
|
||||
eslint-plugin-import: 2.25.4_eslint@8.10.0
|
||||
glob: 7.2.0
|
||||
is-glob: 4.0.3
|
||||
resolve: 1.22.0
|
||||
|
@ -9790,31 +9828,12 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-module-utils/2.7.3_l62aq42yiamaj3cnpuf6avthf4:
|
||||
/eslint-module-utils/2.7.3:
|
||||
resolution: {integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==}
|
||||
engines: {node: '>=4'}
|
||||
peerDependencies:
|
||||
'@typescript-eslint/parser': '*'
|
||||
eslint-import-resolver-node: '*'
|
||||
eslint-import-resolver-typescript: '*'
|
||||
eslint-import-resolver-webpack: '*'
|
||||
peerDependenciesMeta:
|
||||
'@typescript-eslint/parser':
|
||||
optional: true
|
||||
eslint-import-resolver-node:
|
||||
optional: true
|
||||
eslint-import-resolver-typescript:
|
||||
optional: true
|
||||
eslint-import-resolver-webpack:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/parser': 5.14.0_fo4uz55zgcu432252zy2gvpvcu
|
||||
debug: 3.2.7
|
||||
eslint-import-resolver-node: 0.3.6
|
||||
eslint-import-resolver-typescript: 2.5.0_rnagsyfcubvpoxo2ynj23pim7u
|
||||
find-up: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-consistent-default-export-name/0.0.7:
|
||||
|
@ -9849,24 +9868,19 @@ packages:
|
|||
ignore: 5.2.0
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-import/2.25.4_sidoke6kqbkbdht6nlmwbfnany:
|
||||
/eslint-plugin-import/2.25.4_eslint@8.10.0:
|
||||
resolution: {integrity: sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==}
|
||||
engines: {node: '>=4'}
|
||||
peerDependencies:
|
||||
'@typescript-eslint/parser': '*'
|
||||
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
|
||||
peerDependenciesMeta:
|
||||
'@typescript-eslint/parser':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/parser': 5.14.0_fo4uz55zgcu432252zy2gvpvcu
|
||||
array-includes: 3.1.4
|
||||
array.prototype.flat: 1.2.5
|
||||
debug: 2.6.9
|
||||
doctrine: 2.1.0
|
||||
eslint: 8.10.0
|
||||
eslint-import-resolver-node: 0.3.6
|
||||
eslint-module-utils: 2.7.3_l62aq42yiamaj3cnpuf6avthf4
|
||||
eslint-module-utils: 2.7.3
|
||||
has: 1.0.3
|
||||
is-core-module: 2.8.1
|
||||
is-glob: 4.0.3
|
||||
|
@ -9874,10 +9888,6 @@ packages:
|
|||
object.values: 1.1.5
|
||||
resolve: 1.22.0
|
||||
tsconfig-paths: 3.13.0
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-typescript
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-no-use-extend-native/0.5.0:
|
||||
|
@ -10269,8 +10279,6 @@ packages:
|
|||
type-is: 1.6.18
|
||||
utils-merge: 1.0.1
|
||||
vary: 1.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/extend-shallow/2.0.1:
|
||||
|
@ -10467,8 +10475,6 @@ packages:
|
|||
parseurl: 1.3.3
|
||||
statuses: 1.5.0
|
||||
unpipe: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/find-cache-dir/3.3.2:
|
||||
|
@ -13179,7 +13185,6 @@ packages:
|
|||
import-local: 3.1.0
|
||||
npmlog: 4.1.2
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- encoding
|
||||
- supports-color
|
||||
dev: true
|
||||
|
@ -13214,7 +13219,6 @@ packages:
|
|||
npm-package-arg: 8.1.5
|
||||
npm-registry-fetch: 11.0.0
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -13228,7 +13232,6 @@ packages:
|
|||
semver: 7.3.7
|
||||
ssri: 8.0.1
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -13597,7 +13600,6 @@ packages:
|
|||
socks-proxy-agent: 5.0.1
|
||||
ssri: 8.0.1
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -13622,7 +13624,6 @@ packages:
|
|||
socks-proxy-agent: 6.1.1
|
||||
ssri: 8.0.1
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -14695,7 +14696,6 @@ packages:
|
|||
minizlib: 2.1.2
|
||||
npm-package-arg: 8.1.5
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -14712,7 +14712,6 @@ packages:
|
|||
minizlib: 2.1.2
|
||||
npm-package-arg: 8.1.5
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -15151,7 +15150,6 @@ packages:
|
|||
ssri: 8.0.1
|
||||
tar: 6.1.11
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
@ -15544,8 +15542,6 @@ packages:
|
|||
async: 2.6.3
|
||||
debug: 3.2.7
|
||||
mkdirp: 0.5.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/postcss-calc/8.2.4_postcss@8.4.12:
|
||||
|
@ -16232,11 +16228,6 @@ packages:
|
|||
|
||||
/promise-inflight/1.0.1:
|
||||
resolution: {integrity: sha1-mEcocL8igTL8vdhoEputEsPAKeM=}
|
||||
peerDependencies:
|
||||
bluebird: '*'
|
||||
peerDependenciesMeta:
|
||||
bluebird:
|
||||
optional: true
|
||||
dev: true
|
||||
|
||||
/promise-retry/2.0.1:
|
||||
|
@ -16514,7 +16505,6 @@ packages:
|
|||
text-table: 0.2.0
|
||||
transitivePeerDependencies:
|
||||
- eslint
|
||||
- supports-color
|
||||
- typescript
|
||||
- vue-template-compiler
|
||||
- webpack
|
||||
|
@ -17619,8 +17609,6 @@ packages:
|
|||
on-finished: 2.3.0
|
||||
range-parser: 1.2.1
|
||||
statuses: 1.5.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/serialize-error/7.0.1:
|
||||
|
@ -17667,8 +17655,6 @@ packages:
|
|||
http-errors: 1.6.3
|
||||
mime-types: 2.1.35
|
||||
parseurl: 1.3.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/serve-static/1.14.2:
|
||||
|
@ -17679,8 +17665,6 @@ packages:
|
|||
escape-html: 1.0.3
|
||||
parseurl: 1.3.3
|
||||
send: 0.17.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/set-blocking/2.0.0:
|
||||
|
|
Loading…
Add table
Reference in a new issue