mirror of
https://github.com/logto-io/logto.git
synced 2025-03-10 22:22:45 -05:00
feat(core): add wechat-web connector (#394)
* feat(core): add WeChat connector as well as corresponding UTs * feat(core): move TODO from comment block
This commit is contained in:
parent
b196d21ca2
commit
fa757c3d12
14 changed files with 317 additions and 23 deletions
|
@ -62,7 +62,7 @@ describe('facebook connector', () => {
|
|||
token_type: 'token_type',
|
||||
});
|
||||
|
||||
const accessToken = await getAccessToken(code, dummyRedirectUri);
|
||||
const { accessToken } = await getAccessToken(code, dummyRedirectUri);
|
||||
expect(accessToken).toEqual('access_token');
|
||||
});
|
||||
|
||||
|
@ -96,7 +96,7 @@ describe('facebook connector', () => {
|
|||
picture: { data: { url: avatar } },
|
||||
});
|
||||
|
||||
const socialUserInfo = await getUserInfo(code);
|
||||
const socialUserInfo = await getUserInfo({ accessToken: code });
|
||||
expect(socialUserInfo).toMatchObject({
|
||||
id: '1234567890',
|
||||
avatar,
|
||||
|
@ -107,14 +107,14 @@ describe('facebook connector', () => {
|
|||
|
||||
it('throws SocialAccessTokenInvalid error if remote response code is 401', async () => {
|
||||
nock(userInfoEndpoint).get('').query({ fields }).reply(400);
|
||||
await expect(getUserInfo(code)).rejects.toMatchError(
|
||||
await expect(getUserInfo({ accessToken: code })).rejects.toMatchError(
|
||||
new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid)
|
||||
);
|
||||
});
|
||||
|
||||
it('throws unrecognized error', async () => {
|
||||
nock(userInfoEndpoint).get('').reply(500);
|
||||
await expect(getUserInfo(code)).rejects.toThrow();
|
||||
await expect(getUserInfo({ accessToken: code })).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -100,10 +100,10 @@ export const getAccessToken: GetAccessToken = async (code, redirectUri) => {
|
|||
|
||||
assertThat(accessToken, new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));
|
||||
|
||||
return accessToken;
|
||||
return { accessToken };
|
||||
};
|
||||
|
||||
export const getUserInfo: GetUserInfo = async (accessToken: string) => {
|
||||
export const getUserInfo: GetUserInfo = async (accessTokenObject) => {
|
||||
type UserInfoResponse = {
|
||||
id: string;
|
||||
email?: string;
|
||||
|
@ -111,6 +111,8 @@ export const getUserInfo: GetUserInfo = async (accessToken: string) => {
|
|||
picture?: { data: { url: string } };
|
||||
};
|
||||
|
||||
const { accessToken } = accessTokenObject;
|
||||
|
||||
try {
|
||||
const { id, email, name, picture } = await got
|
||||
.get(userInfoEndpoint, {
|
||||
|
|
|
@ -33,7 +33,7 @@ describe('getAccessToken', () => {
|
|||
scope: 'scope',
|
||||
token_type: 'token_type',
|
||||
});
|
||||
const accessToken = await getAccessToken('code', 'dummyRedirectUri');
|
||||
const { accessToken } = await getAccessToken('code', 'dummyRedirectUri');
|
||||
expect(accessToken).toEqual('access_token');
|
||||
});
|
||||
it('throws SocialAuthCodeInvalid error if accessToken not found in response', async () => {
|
||||
|
@ -66,7 +66,7 @@ describe('getUserInfo', () => {
|
|||
name: 'monalisa octocat',
|
||||
email: 'octocat@github.com',
|
||||
});
|
||||
const socialUserInfo = await getUserInfo('code');
|
||||
const socialUserInfo = await getUserInfo({ accessToken: 'code' });
|
||||
expect(socialUserInfo).toMatchObject({
|
||||
id: '1',
|
||||
avatar: 'https://github.com/images/error/octocat_happy.gif',
|
||||
|
@ -76,12 +76,12 @@ describe('getUserInfo', () => {
|
|||
});
|
||||
it('throws SocialAccessTokenInvalid error if remote response code is 401', async () => {
|
||||
nock(userInfoEndpoint).get('').reply(401);
|
||||
await expect(getUserInfo('code')).rejects.toMatchError(
|
||||
await expect(getUserInfo({ accessToken: 'code' })).rejects.toMatchError(
|
||||
new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid)
|
||||
);
|
||||
});
|
||||
it('throws unrecognized error', async () => {
|
||||
nock(userInfoEndpoint).get('').reply(500);
|
||||
await expect(getUserInfo('code')).rejects.toThrow();
|
||||
await expect(getUserInfo({ accessToken: 'code' })).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -90,10 +90,10 @@ export const getAccessToken: GetAccessToken = async (code) => {
|
|||
|
||||
assertThat(accessToken, new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));
|
||||
|
||||
return accessToken;
|
||||
return { accessToken };
|
||||
};
|
||||
|
||||
export const getUserInfo: GetUserInfo = async (accessToken: string) => {
|
||||
export const getUserInfo: GetUserInfo = async (accessTokenObject) => {
|
||||
type UserInfoResponse = {
|
||||
id: number;
|
||||
avatar_url?: string;
|
||||
|
@ -101,6 +101,8 @@ export const getUserInfo: GetUserInfo = async (accessToken: string) => {
|
|||
name?: string;
|
||||
};
|
||||
|
||||
const { accessToken } = accessTokenObject;
|
||||
|
||||
try {
|
||||
const {
|
||||
id,
|
||||
|
|
|
@ -48,7 +48,7 @@ describe('google connector', () => {
|
|||
scope: 'scope',
|
||||
token_type: 'token_type',
|
||||
});
|
||||
const accessToken = await getAccessToken('code', 'dummyRedirectUri');
|
||||
const { accessToken } = await getAccessToken('code', 'dummyRedirectUri');
|
||||
expect(accessToken).toEqual('access_token');
|
||||
});
|
||||
|
||||
|
@ -72,7 +72,7 @@ describe('google connector', () => {
|
|||
email_verified: true,
|
||||
locale: 'en',
|
||||
});
|
||||
const socialUserInfo = await getUserInfo('code');
|
||||
const socialUserInfo = await getUserInfo({ accessToken: 'code' });
|
||||
expect(socialUserInfo).toMatchObject({
|
||||
id: '1234567890',
|
||||
avatar: 'https://github.com/images/error/octocat_happy.gif',
|
||||
|
@ -83,14 +83,14 @@ describe('google connector', () => {
|
|||
|
||||
it('throws SocialAccessTokenInvalid error if remote response code is 401', async () => {
|
||||
nock(userInfoEndpoint).post('').reply(401);
|
||||
await expect(getUserInfo('code')).rejects.toMatchError(
|
||||
await expect(getUserInfo({ accessToken: 'code' })).rejects.toMatchError(
|
||||
new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid)
|
||||
);
|
||||
});
|
||||
|
||||
it('throws unrecognized error', async () => {
|
||||
nock(userInfoEndpoint).post('').reply(500);
|
||||
await expect(getUserInfo('code')).rejects.toThrow();
|
||||
await expect(getUserInfo({ accessToken: 'code' })).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -100,10 +100,10 @@ export const getAccessToken: GetAccessToken = async (code, redirectUri) => {
|
|||
|
||||
assertThat(accessToken, new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));
|
||||
|
||||
return accessToken;
|
||||
return { accessToken };
|
||||
};
|
||||
|
||||
export const getUserInfo: GetUserInfo = async (accessToken: string) => {
|
||||
export const getUserInfo: GetUserInfo = async (accessTokenObject) => {
|
||||
type UserInfoResponse = {
|
||||
sub: string;
|
||||
name?: string;
|
||||
|
@ -115,6 +115,8 @@ export const getUserInfo: GetUserInfo = async (accessToken: string) => {
|
|||
locale?: string;
|
||||
};
|
||||
|
||||
const { accessToken } = accessTokenObject;
|
||||
|
||||
try {
|
||||
const {
|
||||
sub: id,
|
||||
|
|
|
@ -40,6 +40,12 @@ const googleConnector = {
|
|||
config: {},
|
||||
createdAt: 1_646_382_233_000,
|
||||
};
|
||||
const wechatConnector = {
|
||||
id: 'wechat',
|
||||
enabled: false,
|
||||
config: {},
|
||||
createdAt: 1_646_382_233_000,
|
||||
};
|
||||
|
||||
const connectors = [
|
||||
aliyunDmConnector,
|
||||
|
@ -47,6 +53,7 @@ const connectors = [
|
|||
facebookConnector,
|
||||
githubConnector,
|
||||
googleConnector,
|
||||
wechatConnector,
|
||||
];
|
||||
const connectorMap = new Map(connectors.map((connector) => [connector.id, connector]));
|
||||
|
||||
|
@ -79,6 +86,7 @@ describe('getConnectorInstances', () => {
|
|||
expect(connectorInstances[2]).toHaveProperty('connector', facebookConnector);
|
||||
expect(connectorInstances[3]).toHaveProperty('connector', githubConnector);
|
||||
expect(connectorInstances[4]).toHaveProperty('connector', googleConnector);
|
||||
expect(connectorInstances[5]).toHaveProperty('connector', wechatConnector);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -7,8 +7,9 @@ import * as Facebook from './facebook';
|
|||
import * as GitHub from './github';
|
||||
import * as Google from './google';
|
||||
import { ConnectorInstance, ConnectorType, IConnector, SocialConnectorInstance } from './types';
|
||||
import * as WeChat from './wechat';
|
||||
|
||||
const allConnectors: IConnector[] = [AliyunDM, AliyunSMS, Facebook, GitHub, Google];
|
||||
const allConnectors: IConnector[] = [AliyunDM, AliyunSMS, Facebook, GitHub, Google, WeChat];
|
||||
|
||||
export const getConnectorInstances = async (): Promise<ConnectorInstance[]> => {
|
||||
return Promise.all(
|
||||
|
|
|
@ -87,9 +87,11 @@ export type ValidateConfig<T extends ArbitraryObject = ArbitraryObject> = (
|
|||
|
||||
export type GetAuthorizationUri = (redirectUri: string, state: string) => Promise<string>;
|
||||
|
||||
export type GetAccessToken = (code: string, redirectUri: string) => Promise<string>;
|
||||
type AccessTokenObject = { accessToken: string } & Record<string, string>;
|
||||
|
||||
export type GetUserInfo = (accessToken: string) => Promise<SocialUserInfo>;
|
||||
export type GetAccessToken = (code: string, redirectUri?: string) => Promise<AccessTokenObject>;
|
||||
|
||||
export type GetUserInfo = (accessTokenObject: AccessTokenObject) => Promise<SocialUserInfo>;
|
||||
|
||||
export const socialUserInfoGuard = z.object({
|
||||
id: z.string(),
|
||||
|
|
2
packages/core/src/connectors/wechat/README.md
Normal file
2
packages/core/src/connectors/wechat/README.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
### WeChat Social Connector README
|
||||
placeholder
|
4
packages/core/src/connectors/wechat/constant.ts
Normal file
4
packages/core/src/connectors/wechat/constant.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export const authorizationEndpoint = 'https://open.weixin.qq.com/connect/qrconnect';
|
||||
export const accessTokenEndpoint = 'https://api.weixin.qq.com/sns/oauth2/access_token';
|
||||
export const userInfoEndpoint = 'https://api.weixin.qq.com/sns/userinfo';
|
||||
export const scope = 'snsapi_login';
|
133
packages/core/src/connectors/wechat/index.test.ts
Normal file
133
packages/core/src/connectors/wechat/index.test.ts
Normal file
|
@ -0,0 +1,133 @@
|
|||
import nock from 'nock';
|
||||
|
||||
import { getAccessToken, getAuthorizationUri, validateConfig, getUserInfo } from '.';
|
||||
import { ConnectorError, ConnectorErrorCodes } from '../types';
|
||||
import { getConnectorConfig } from '../utilities';
|
||||
import { accessTokenEndpoint, authorizationEndpoint, userInfoEndpoint } from './constant';
|
||||
|
||||
jest.mock('../utilities');
|
||||
|
||||
beforeAll(() => {
|
||||
(getConnectorConfig as jest.MockedFunction<typeof getConnectorConfig>).mockResolvedValue({
|
||||
appId: '<app-id>',
|
||||
appSecret: '<app-secret>',
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAuthorizationUri', () => {
|
||||
it('should get a valid uri by redirectUri and state', async () => {
|
||||
const authorizationUri = await getAuthorizationUri(
|
||||
'http://localhost:3001/callback',
|
||||
'some_state'
|
||||
);
|
||||
expect(authorizationUri).toEqual(
|
||||
`${authorizationEndpoint}?appid=%3Capp-id%3E&redirect_uri=http%3A%2F%2Flocalhost%3A3001%2Fcallback&response_type=code&scope=snsapi_login&state=some_state`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAccessToken', () => {
|
||||
afterEach(() => {
|
||||
nock.cleanAll();
|
||||
});
|
||||
|
||||
const accessTokenEndpointUrl = new URL(accessTokenEndpoint);
|
||||
const parameters = new URLSearchParams({
|
||||
appid: '<app-id>',
|
||||
secret: '<app-secret>',
|
||||
code: 'code',
|
||||
grant_type: 'authorization_code',
|
||||
});
|
||||
|
||||
it('should get an accessToken by exchanging with code', async () => {
|
||||
nock(accessTokenEndpointUrl.origin)
|
||||
.get(accessTokenEndpointUrl.pathname)
|
||||
.query(parameters)
|
||||
.reply(200, {
|
||||
access_token: 'access_token',
|
||||
openid: 'openid',
|
||||
});
|
||||
const { accessToken, openid } = await getAccessToken('code');
|
||||
expect(accessToken).toEqual('access_token');
|
||||
expect(openid).toEqual('openid');
|
||||
});
|
||||
|
||||
it('throws SocialAuthCodeInvalid error if accessToken not found in response', async () => {
|
||||
nock(accessTokenEndpointUrl.origin)
|
||||
.get(accessTokenEndpointUrl.pathname)
|
||||
.query(parameters)
|
||||
.reply(200, {});
|
||||
await expect(getAccessToken('code')).rejects.toMatchError(
|
||||
new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateConfig', () => {
|
||||
it('should pass on valid config', async () => {
|
||||
await expect(validateConfig({ appId: 'appId', appSecret: 'appSecret' })).resolves.not.toThrow();
|
||||
});
|
||||
it('should throw on empty config', async () => {
|
||||
await expect(validateConfig({})).rejects.toThrowError();
|
||||
});
|
||||
it('should throw when missing appSecret', async () => {
|
||||
await expect(validateConfig({ appId: 'appId' })).rejects.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserInfo', () => {
|
||||
afterEach(() => {
|
||||
nock.cleanAll();
|
||||
});
|
||||
|
||||
const userInfoEndpointUrl = new URL(userInfoEndpoint);
|
||||
const parameters = new URLSearchParams({ access_token: 'accessToken', openid: 'openid' });
|
||||
|
||||
it('should get valid SocialUserInfo', async () => {
|
||||
nock(userInfoEndpointUrl.origin)
|
||||
.get(userInfoEndpointUrl.pathname)
|
||||
.query(parameters)
|
||||
.reply(200, {
|
||||
unionid: 'this_is_an_arbitrary_wechat_union_id',
|
||||
headimgurl: 'https://github.com/images/error/octocat_happy.gif',
|
||||
nickname: 'wechat bot',
|
||||
});
|
||||
const socialUserInfo = await getUserInfo({ accessToken: 'accessToken', openid: 'openid' });
|
||||
expect(socialUserInfo).toMatchObject({
|
||||
id: 'this_is_an_arbitrary_wechat_union_id',
|
||||
avatar: 'https://github.com/images/error/octocat_happy.gif',
|
||||
name: 'wechat bot',
|
||||
});
|
||||
});
|
||||
|
||||
it('throws SocialAccessTokenInvalid error if remote response code is 40001', async () => {
|
||||
nock(userInfoEndpointUrl.origin)
|
||||
.get(userInfoEndpointUrl.pathname)
|
||||
.query(parameters)
|
||||
.reply(40_001);
|
||||
await expect(
|
||||
getUserInfo({ accessToken: 'accessToken', openid: 'openid' })
|
||||
).rejects.toMatchError(new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid));
|
||||
});
|
||||
|
||||
it('throws unrecognized error', async () => {
|
||||
nock(userInfoEndpointUrl.origin).get(userInfoEndpointUrl.pathname).query(parameters).reply(500);
|
||||
await expect(getUserInfo({ accessToken: 'accessToken', openid: 'openid' })).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('throws Error if request failed and errcode is not 40001', async () => {
|
||||
nock(userInfoEndpointUrl.origin)
|
||||
.get(userInfoEndpointUrl.pathname)
|
||||
.query(parameters)
|
||||
.reply(200, { errcode: 40_003, errmsg: 'invalid openid' });
|
||||
await expect(
|
||||
getUserInfo({ accessToken: 'accessToken', openid: 'openid' })
|
||||
).rejects.toMatchError(new Error('invalid openid'));
|
||||
});
|
||||
|
||||
it('throws SocialAccessTokenInvalid error if openid is missing', async () => {
|
||||
await expect(getUserInfo({ accessToken: 'accessToken' })).rejects.toMatchError(
|
||||
new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid)
|
||||
);
|
||||
});
|
||||
});
|
138
packages/core/src/connectors/wechat/index.ts
Normal file
138
packages/core/src/connectors/wechat/index.ts
Normal file
|
@ -0,0 +1,138 @@
|
|||
/**
|
||||
* The Implementation of OpenID Connect of WeChat Web Open Platform.
|
||||
* https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
|
||||
*/
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import got, { RequestError as GotRequestError } from 'got';
|
||||
import { stringify } from 'query-string';
|
||||
import { z } from 'zod';
|
||||
|
||||
import assertThat from '@/utils/assert-that';
|
||||
|
||||
import {
|
||||
ConnectorMetadata,
|
||||
GetAccessToken,
|
||||
GetAuthorizationUri,
|
||||
ValidateConfig,
|
||||
GetUserInfo,
|
||||
ConnectorType,
|
||||
ConnectorError,
|
||||
ConnectorErrorCodes,
|
||||
} from '../types';
|
||||
import { getConnectorConfig, getConnectorRequestTimeout } from '../utilities';
|
||||
import { authorizationEndpoint, accessTokenEndpoint, userInfoEndpoint, scope } from './constant';
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-module
|
||||
const pathToReadmeFile = path.join(__dirname, 'README.md');
|
||||
const readmeContentFallback = 'Please check README.md file directory.';
|
||||
export const metadata: ConnectorMetadata = {
|
||||
id: 'wechat',
|
||||
type: ConnectorType.Social,
|
||||
name: {
|
||||
en: 'Sign In with WeChat',
|
||||
'zh-CN': '微信登录',
|
||||
},
|
||||
// TODO: add the real logo URL (LOG-1823)
|
||||
logo: './logo.png',
|
||||
description: {
|
||||
en: 'Sign In with WeChat',
|
||||
'zh-CN': '微信登录',
|
||||
},
|
||||
readme: existsSync(pathToReadmeFile)
|
||||
? readFileSync(pathToReadmeFile, 'utf8')
|
||||
: readmeContentFallback,
|
||||
};
|
||||
|
||||
// As creating a WeChat Web/Mobile application needs a real App or Website record, the real test is temporarily not finished.
|
||||
// TODO: test with our own wechat mobile/web application (LOG-1910), already tested with other verified wechat web application
|
||||
|
||||
const weChatConfigGuard = z.object({ appId: z.string(), appSecret: z.string() });
|
||||
|
||||
type WeChatConfig = z.infer<typeof weChatConfigGuard>;
|
||||
|
||||
export const validateConfig: ValidateConfig = async (config: unknown) => {
|
||||
const result = weChatConfigGuard.safeParse(config);
|
||||
|
||||
if (!result.success) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig, result.error.message);
|
||||
}
|
||||
};
|
||||
|
||||
export const getAuthorizationUri: GetAuthorizationUri = async (redirectUri, state) => {
|
||||
const { appId } = await getConnectorConfig<WeChatConfig>(metadata.id);
|
||||
|
||||
return `${authorizationEndpoint}?${stringify({
|
||||
appid: appId,
|
||||
redirect_uri: encodeURI(redirectUri), // The variable `redirectUri` should match {appId, appSecret}
|
||||
response_type: 'code',
|
||||
scope,
|
||||
state,
|
||||
})}`;
|
||||
};
|
||||
|
||||
export const getAccessToken: GetAccessToken = async (code) => {
|
||||
type AccessTokenResponse = {
|
||||
access_token: string;
|
||||
openid: string;
|
||||
expires_in: number; // In seconds
|
||||
refresh_token: string;
|
||||
scope: string;
|
||||
};
|
||||
|
||||
const config = await getConnectorConfig<WeChatConfig>(metadata.id);
|
||||
const { appId: appid, appSecret: secret } = config;
|
||||
|
||||
const { access_token: accessToken, openid } = await got
|
||||
.get(accessTokenEndpoint, {
|
||||
searchParams: { appid, secret, code, grant_type: 'authorization_code' },
|
||||
timeout: await getConnectorRequestTimeout(),
|
||||
})
|
||||
.json<AccessTokenResponse>();
|
||||
|
||||
assertThat(accessToken && openid, new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));
|
||||
|
||||
return { accessToken, openid };
|
||||
};
|
||||
|
||||
export const getUserInfo: GetUserInfo = async (accessTokenObject) => {
|
||||
type UserInfoResponse = {
|
||||
unionid?: string;
|
||||
headimgurl?: string;
|
||||
nickname?: string;
|
||||
errcode?: number;
|
||||
errmsg?: string;
|
||||
};
|
||||
|
||||
const { accessToken, openid } = accessTokenObject;
|
||||
|
||||
try {
|
||||
const { unionid, headimgurl, nickname, errcode, errmsg } = await got
|
||||
.get(userInfoEndpoint, {
|
||||
searchParams: { access_token: accessToken, openid },
|
||||
timeout: await getConnectorRequestTimeout(),
|
||||
})
|
||||
.json<UserInfoResponse>();
|
||||
|
||||
if (!openid || errcode || errmsg) {
|
||||
// 'openid' is defined as a required input argument in WeChat API doc, but it does not necessarily to
|
||||
// be the return value from getAccessToken per testing.
|
||||
// In another word, 'openid' is required but the response of getUserInfo is consistent as long as
|
||||
// access_token is valid.
|
||||
if (errcode === 40_001) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid);
|
||||
}
|
||||
|
||||
throw new Error(errmsg);
|
||||
}
|
||||
|
||||
return { id: unionid ?? openid, avatar: headimgurl, name: nickname };
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof GotRequestError && error.response?.statusCode === 40_001) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
|
@ -40,9 +40,9 @@ export const getUserInfoByAuthCode = async (
|
|||
redirectUri: string
|
||||
): Promise<SocialUserInfo> => {
|
||||
const connector = await getConnector(connectorId);
|
||||
const accessToken = await connector.getAccessToken(authCode, redirectUri);
|
||||
const accessTokenObject = await connector.getAccessToken(authCode, redirectUri);
|
||||
|
||||
return connector.getUserInfo(accessToken);
|
||||
return connector.getUserInfo(accessTokenObject);
|
||||
};
|
||||
|
||||
export const getUserInfoFromInteractionResult = async (
|
||||
|
|
Loading…
Add table
Reference in a new issue