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

refactor(connectors): reorganize connectors (#807)

This commit is contained in:
Darcy Ye 2022-05-13 14:22:37 +08:00 committed by GitHub
parent 0ea55134a9
commit 357fb8dc40
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 277 additions and 279 deletions

View file

@ -3,22 +3,6 @@ import path from 'path';
import { ConnectorType, ConnectorMetadata } from '@logto/connector-types';
import { getFileContents } from '@logto/shared';
/**
* @doc https://help.aliyun.com/document_detail/29444.html
*/
export interface SingleSendMail {
AccountName: string;
AddressType: '0' | '1';
ClickTrace?: '0' | '1';
FromAlias?: string;
HtmlBody?: string;
ReplyToAddress: 'true' | 'false';
Subject: string;
TagName?: string;
TextBody?: string;
ToAddress: string;
}
export const endpoint = 'https://dm.aliyuncs.com/';
export const staticConfigs = {

View file

@ -1,8 +1,9 @@
import { GetConnectorConfig } from '@logto/connector-types';
import { AliyunDmConnector, AliyunDmConfig } from '.';
import { AliyunDmConnector } from '.';
import { mockedConfig } from './mock';
import { singleSendMail } from './single-send-mail';
import { AliyunDmConfig } from './types';
const getConnectorConfig = jest.fn() as GetConnectorConfig<AliyunDmConfig>;

View file

@ -9,31 +9,10 @@ import {
} from '@logto/connector-types';
import { assert } from '@silverhand/essentials';
import { Response } from 'got';
import { z } from 'zod';
import { defaultMetadata } from './constant';
import { singleSendMail } from './single-send-mail';
import { SendEmailResponse } from './utils';
/**
* UsageType here is used to specify the use case of the template, can be either
* 'Register', 'SignIn', 'ForgotPassword' or 'Test'.
*/
const templateGuard = z.object({
usageType: z.string(),
subject: z.string(),
content: z.string(), // With variable {{code}}, support HTML
});
const configGuard = z.object({
accessKeyId: z.string(),
accessKeySecret: z.string(),
accountName: z.string(),
fromAlias: z.string().optional(),
templates: z.array(templateGuard),
});
export type AliyunDmConfig = z.infer<typeof configGuard>;
import { SendEmailResponse, AliyunDmConfig, aliyunDmConfigGuard } from './types';
export class AliyunDmConnector implements EmailConnector {
public metadata: ConnectorMetadata = defaultMetadata;
@ -45,7 +24,7 @@ export class AliyunDmConnector implements EmailConnector {
}
public validateConfig: ValidateConfig = async (config: unknown) => {
const result = configGuard.safeParse(config);
const result = aliyunDmConfigGuard.safeParse(config);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig, result.error.message);

View file

@ -1,7 +1,8 @@
import { Response } from 'got';
import { SingleSendMail, endpoint, staticConfigs } from './constant';
import { PublicParameters, request, SendEmailResponse } from './utils';
import { endpoint, staticConfigs } from './constant';
import { PublicParameters, SendEmailResponse, SingleSendMail } from './types';
import { request } from './utils';
/**
* @doc https://help.aliyun.com/document_detail/29444.html

View file

@ -0,0 +1,53 @@
import { z } from 'zod';
export type { Response } from 'got';
export type SendEmailResponse = { EnvId: string; RequestId: string };
/**
* UsageType here is used to specify the use case of the template, can be either
* 'Register', 'SignIn', 'ForgotPassword' or 'Test'.
*/
const templateGuard = z.object({
usageType: z.string(),
subject: z.string(),
content: z.string(), // With variable {{code}}, support HTML
});
export const aliyunDmConfigGuard = z.object({
accessKeyId: z.string(),
accessKeySecret: z.string(),
accountName: z.string(),
fromAlias: z.string().optional(),
templates: z.array(templateGuard),
});
export type AliyunDmConfig = z.infer<typeof aliyunDmConfigGuard>;
/**
* @doc https://help.aliyun.com/document_detail/29444.html
*/
export type SingleSendMail = {
AccountName: string;
AddressType: '0' | '1';
ClickTrace?: '0' | '1';
FromAlias?: string;
HtmlBody?: string;
ReplyToAddress: 'true' | 'false';
Subject: string;
TagName?: string;
TextBody?: string;
ToAddress: string;
};
export type PublicParameters = {
AccessKeyId: string;
Format?: string; // 'json' or 'xml', default: 'json'
RegionId?: string; // 'cn-hangzhou' | 'ap-southeast-1' | 'ap-southeast-2'
Signature?: string;
SignatureMethod?: string;
SignatureNonce?: string;
SignatureVersion?: string;
Timestamp?: string;
Version?: string;
};

View file

@ -2,8 +2,7 @@ import { createHmac } from 'crypto';
import got from 'got';
export type { Response } from 'got';
export type SendEmailResponse = { EnvId: string; RequestId: string };
import { PublicParameters } from './types';
// Aliyun has special escape rules.
// https://help.aliyun.com/document_detail/29442.html
@ -38,18 +37,6 @@ export const getSignature = (
return createHmac('sha1', `${secret}&`).update(stringToSign).digest('base64');
};
export interface PublicParameters {
AccessKeyId: string;
Format?: string; // 'json' or 'xml', default: 'json'
RegionId?: string; // 'cn-hangzhou' | 'ap-southeast-1' | 'ap-southeast-2'
Signature?: string;
SignatureMethod?: string;
SignatureNonce?: string;
SignatureVersion?: string;
Timestamp?: string;
Version?: string;
}
export const request = async <T>(
url: string,
parameters: PublicParameters & Record<string, string>,

View file

@ -3,18 +3,6 @@ import path from 'path';
import { ConnectorMetadata, ConnectorType } from '@logto/connector-types';
import { getFileContents } from '@logto/shared';
/**
* @doc https://help.aliyun.com/document_detail/101414.html
*/
export interface SendSms {
OutId?: string;
PhoneNumbers: string; // 11 digits w/o prefix (can be multiple phone numbers with separator `,`)
SignName: string; // Name of SMS signature
SmsUpExtendCode?: string;
TemplateCode: string; // Text message template ID
TemplateParam?: string; // Stringified JSON (used to fill in text template)
}
export const endpoint = 'https://dysmsapi.aliyuncs.com/';
export const staticConfigs = {
@ -25,6 +13,21 @@ export const staticConfigs = {
Version: '2017-05-25',
};
/**
* Details of SmsTemplateType can be found at:
* https://next.api.aliyun.com/document/Dysmsapi/2017-05-25/QuerySmsTemplateList.
*
* For our use case is to send passcode sms for passwordless sign-in/up as well as
* reset password, the default value of type code is set to be 2.
*/
export enum SmsTemplateType {
Notification = 0,
Promotion = 1,
Passcode = 2,
InternationalMessage = 6,
PureNumber = 7,
}
// eslint-disable-next-line unicorn/prefer-module
const currentPath = __dirname;
const pathToReadmeFile = path.join(currentPath, '..', 'README.md');

View file

@ -1,8 +1,9 @@
import { GetConnectorConfig } from '@logto/connector-types';
import { AliyunSmsConnector, AliyunSmsConfig } from '.';
import { AliyunSmsConnector } from '.';
import { mockedConnectorConfig, mockedValidConnectorConfig, phoneTest, codeTest } from './mock';
import { sendSms } from './single-send-text';
import { AliyunSmsConfig } from './types';
const getConnectorConfig = jest.fn() as GetConnectorConfig<AliyunSmsConfig>;

View file

@ -9,51 +9,10 @@ import {
} from '@logto/connector-types';
import { assert } from '@silverhand/essentials';
import { Response } from 'got';
import { z } from 'zod';
import { defaultMetadata } from './constant';
import { sendSms } from './single-send-text';
import { SendSmsResponse } from './utils';
/**
* Details of SmsTemplateType can be found at:
* https://next.api.aliyun.com/document/Dysmsapi/2017-05-25/QuerySmsTemplateList.
*
* For our use case is to send passcode sms for passwordless sign-in/up as well as
* reset password, the default value of type code is set to be 2.
*/
enum SmsTemplateType {
Notification = 0,
Promotion = 1,
Passcode = 2,
InternationalMessage = 6,
PureNumber = 7,
}
/**
* UsageType here is used to specify the use case of the template, can be either
* 'Register', 'SignIn', 'ForgotPassword' or 'Test'.
*
* Type here in the template is used to specify the purpose of sending the sms,
* can be either item in SmsTemplateType.
* As the SMS is applied for sending passcode, the value should always be 2 in our case.
*/
const templateGuard = z.object({
type: z.nativeEnum(SmsTemplateType).default(2),
usageType: z.string(),
code: z.string(),
name: z.string().min(1).max(30),
content: z.string().min(1).max(500),
remark: z.string(),
});
const configGuard = z.object({
accessKeyId: z.string(),
accessKeySecret: z.string(),
signName: z.string(),
templates: z.array(templateGuard),
});
export type AliyunSmsConfig = z.infer<typeof configGuard>;
import { aliyunSmsConfigGuard, AliyunSmsConfig, SendSmsResponse } from './types';
export class AliyunSmsConnector implements SmsConnector {
public metadata: ConnectorMetadata = defaultMetadata;
@ -65,7 +24,7 @@ export class AliyunSmsConnector implements SmsConnector {
}
public validateConfig: ValidateConfig = async (config: unknown) => {
const result = configGuard.safeParse(config);
const result = aliyunSmsConfigGuard.safeParse(config);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig, result.error.message);

View file

@ -1,7 +1,8 @@
import { Response } from 'got';
import { SendSms, endpoint, staticConfigs } from './constant';
import { PublicParameters, request, SendSmsResponse } from './utils';
import { endpoint, staticConfigs } from './constant';
import { PublicParameters, SendSms, SendSmsResponse } from './types';
import { request } from './utils';
/**
* @doc https://help.aliyun.com/document_detail/101414.html

View file

@ -0,0 +1,57 @@
import { z } from 'zod';
import { SmsTemplateType } from './constant';
export type { Response } from 'got';
export type SendSmsResponse = { BizId: string; Code: string; Message: string; RequestId: string };
/**
* @doc https://help.aliyun.com/document_detail/101414.html
*/
export type SendSms = {
OutId?: string;
PhoneNumbers: string; // 11 digits w/o prefix (can be multiple phone numbers with separator `,`)
SignName: string; // Name of SMS signature
SmsUpExtendCode?: string;
TemplateCode: string; // Text message template ID
TemplateParam?: string; // Stringified JSON (used to fill in text template)
};
export type PublicParameters = {
AccessKeyId: string;
Format?: string; // 'json' or 'xml', default: 'json'
RegionId?: string; // 'cn-hangzhou' | 'ap-southeast-1' | 'ap-southeast-2'
Signature?: string;
SignatureMethod?: string;
SignatureNonce?: string;
SignatureVersion?: string;
Timestamp?: string;
Version?: string;
};
/**
* UsageType here is used to specify the use case of the template, can be either
* 'Register', 'SignIn', 'ForgotPassword' or 'Test'.
*
* Type here in the template is used to specify the purpose of sending the sms,
* can be either item in SmsTemplateType.
* As the SMS is applied for sending passcode, the value should always be 2 in our case.
*/
const templateGuard = z.object({
type: z.nativeEnum(SmsTemplateType).default(2),
usageType: z.string(),
code: z.string(),
name: z.string().min(1).max(30),
content: z.string().min(1).max(500),
remark: z.string(),
});
export const aliyunSmsConfigGuard = z.object({
accessKeyId: z.string(),
accessKeySecret: z.string(),
signName: z.string(),
templates: z.array(templateGuard),
});
export type AliyunSmsConfig = z.infer<typeof aliyunSmsConfigGuard>;

View file

@ -2,8 +2,7 @@ import { createHmac } from 'crypto';
import got from 'got';
export type { Response } from 'got';
export type SendSmsResponse = { BizId: string; Code: string; Message: string; RequestId: string };
import { PublicParameters } from './types';
// Aliyun has special escape rules.
// https://help.aliyun.com/document_detail/29442.html
@ -38,18 +37,6 @@ export const getSignature = (
return createHmac('sha1', `${secret}&`).update(stringToSign).digest('base64');
};
export interface PublicParameters {
AccessKeyId: string;
Format?: string; // 'json' or 'xml', default: 'json'
RegionId?: string; // 'cn-hangzhou' | 'ap-southeast-1' | 'ap-southeast-2'
Signature?: string;
SignatureMethod?: string;
SignatureNonce?: string;
SignatureVersion?: string;
Timestamp?: string;
Version?: string;
}
export const request = async <T>(
url: string,
parameters: PublicParameters & Record<string, string>,

View file

@ -2,7 +2,6 @@ import path from 'path';
import { ConnectorMetadata, ConnectorType, ConnectorPlatform } from '@logto/connector-types';
import { getFileContents } from '@logto/shared';
import { z } from 'zod';
/**
* Note: If you do not include a version number we will default to the oldest available version, so it's recommended to include the version number in your requests.
@ -18,13 +17,6 @@ export const accessTokenEndpoint = 'https://graph.facebook.com/v13.0/oauth/acces
export const userInfoEndpoint = 'https://graph.facebook.com/v13.0/me';
export const scope = 'email,public_profile';
export const facebookConfigGuard = z.object({
clientId: z.string(),
clientSecret: z.string(),
});
export type FacebookConfig = z.infer<typeof facebookConfigGuard>;
// eslint-disable-next-line unicorn/prefer-module
const currentPath = __dirname;
const pathToReadmeFile = path.join(currentPath, '..', 'README.md');

View file

@ -2,13 +2,9 @@ import { ConnectorError, ConnectorErrorCodes, GetConnectorConfig } from '@logto/
import nock from 'nock';
import { FacebookConnector } from '.';
import {
FacebookConfig,
accessTokenEndpoint,
authorizationEndpoint,
userInfoEndpoint,
} from './constant';
import { accessTokenEndpoint, authorizationEndpoint, userInfoEndpoint } from './constant';
import { clientId, clientSecret, code, dummyRedirectUri, fields, mockedConfig } from './mock';
import { FacebookConfig } from './types';
const getConnectorConfig = jest.fn() as GetConnectorConfig<FacebookConfig>;

View file

@ -24,9 +24,13 @@ import {
userInfoEndpoint,
defaultMetadata,
defaultTimeout,
facebookConfigGuard,
FacebookConfig,
} from './constant';
import {
facebookConfigGuard,
AccessTokenResponse,
FacebookConfig,
UserInfoResponse,
} from './types';
export class FacebookConnector implements SocialConnector {
public metadata: ConnectorMetadata = defaultMetadata;
@ -60,12 +64,6 @@ export class FacebookConnector implements SocialConnector {
};
public getAccessToken: GetAccessToken = async (code, redirectUri) => {
type AccessTokenResponse = {
access_token: string;
token_type: string;
expires_in: number;
};
const { clientId: client_id, clientSecret: client_secret } = await this.getConfig(
this.metadata.target,
this.metadata.platform
@ -89,13 +87,6 @@ export class FacebookConnector implements SocialConnector {
};
public getUserInfo: GetUserInfo = async (accessTokenObject) => {
type UserInfoResponse = {
id: string;
email?: string;
name?: string;
picture?: { data: { url: string } };
};
const { accessToken } = accessTokenObject;
try {

View file

@ -0,0 +1,21 @@
import { z } from 'zod';
export const facebookConfigGuard = z.object({
clientId: z.string(),
clientSecret: z.string(),
});
export type FacebookConfig = z.infer<typeof facebookConfigGuard>;
export type AccessTokenResponse = {
access_token: string;
token_type: string;
expires_in: number;
};
export type UserInfoResponse = {
id: string;
email?: string;
name?: string;
picture?: { data: { url: string } };
};

View file

@ -2,20 +2,12 @@ import path from 'path';
import { ConnectorMetadata, ConnectorType, ConnectorPlatform } from '@logto/connector-types';
import { getFileContents } from '@logto/shared';
import { z } from 'zod';
export const authorizationEndpoint = 'https://github.com/login/oauth/authorize';
export const scope = 'read:user';
export const accessTokenEndpoint = 'https://github.com/login/oauth/access_token';
export const userInfoEndpoint = 'https://api.github.com/user';
export const githubConfigGuard = z.object({
clientId: z.string(),
clientSecret: z.string(),
});
export type GithubConfig = z.infer<typeof githubConfigGuard>;
// eslint-disable-next-line unicorn/prefer-module
const currentPath = __dirname;
const pathToReadmeFile = path.join(currentPath, '..', 'README.md');

View file

@ -2,13 +2,9 @@ import { ConnectorError, ConnectorErrorCodes, GetConnectorConfig } from '@logto/
import nock from 'nock';
import { GithubConnector } from '.';
import {
GithubConfig,
accessTokenEndpoint,
authorizationEndpoint,
userInfoEndpoint,
} from './constant';
import { accessTokenEndpoint, authorizationEndpoint, userInfoEndpoint } from './constant';
import { mockedConfig } from './mock';
import { GithubConfig } from './types';
const getConnectorConfig = jest.fn() as GetConnectorConfig<GithubConfig>;

View file

@ -17,11 +17,10 @@ import {
accessTokenEndpoint,
scope,
userInfoEndpoint,
githubConfigGuard,
GithubConfig,
defaultMetadata,
defaultTimeout,
} from './constant';
import { githubConfigGuard, AccessTokenResponse, GithubConfig, UserInfoResponse } from './types';
export class GithubConnector implements SocialConnector {
public metadata: ConnectorMetadata = defaultMetadata;
@ -54,12 +53,6 @@ export class GithubConnector implements SocialConnector {
};
getAccessToken: GetAccessToken = async (code) => {
type AccessTokenResponse = {
access_token: string;
scope: string;
token_type: string;
};
const { clientId: client_id, clientSecret: client_secret } = await this.getConfig(
this.metadata.target,
this.metadata.platform
@ -83,13 +76,6 @@ export class GithubConnector implements SocialConnector {
};
public getUserInfo: GetUserInfo = async (accessTokenObject) => {
type UserInfoResponse = {
id: number;
avatar_url?: string;
email?: string;
name?: string;
};
const { accessToken } = accessTokenObject;
try {

View file

@ -0,0 +1,21 @@
import { z } from 'zod';
export const githubConfigGuard = z.object({
clientId: z.string(),
clientSecret: z.string(),
});
export type GithubConfig = z.infer<typeof githubConfigGuard>;
export type AccessTokenResponse = {
access_token: string;
scope: string;
token_type: string;
};
export type UserInfoResponse = {
id: number;
avatar_url?: string;
email?: string;
name?: string;
};

View file

@ -2,20 +2,12 @@ import path from 'path';
import { ConnectorMetadata, ConnectorType, ConnectorPlatform } from '@logto/connector-types';
import { getFileContents } from '@logto/shared';
import { z } from 'zod';
export const authorizationEndpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
export const accessTokenEndpoint = 'https://oauth2.googleapis.com/token';
export const userInfoEndpoint = 'https://openidconnect.googleapis.com/v1/userinfo';
export const scope = 'openid profile email';
export const googleConfigGuard = z.object({
clientId: z.string(),
clientSecret: z.string(),
});
export type GoogleConfig = z.infer<typeof googleConfigGuard>;
// eslint-disable-next-line unicorn/prefer-module
const currentPath = __dirname;
const pathToReadmeFile = path.join(currentPath, '..', 'README.md');

View file

@ -2,13 +2,9 @@ import { ConnectorError, ConnectorErrorCodes, GetConnectorConfig } from '@logto/
import nock from 'nock';
import { GoogleConnector } from '.';
import {
GoogleConfig,
accessTokenEndpoint,
authorizationEndpoint,
userInfoEndpoint,
} from './constant';
import { accessTokenEndpoint, authorizationEndpoint, userInfoEndpoint } from './constant';
import { mockedConfig } from './mock';
import { GoogleConfig } from './types';
const getConnectorConfig = jest.fn() as GetConnectorConfig<GoogleConfig>;

View file

@ -21,11 +21,10 @@ import {
authorizationEndpoint,
scope,
userInfoEndpoint,
googleConfigGuard,
GoogleConfig,
defaultMetadata,
defaultTimeout,
} from './constant';
import { googleConfigGuard, AccessTokenResponse, GoogleConfig, UserInfoResponse } from './types';
export class GoogleConnector implements SocialConnector {
public metadata: ConnectorMetadata = defaultMetadata;
@ -59,12 +58,6 @@ export class GoogleConnector implements SocialConnector {
};
public getAccessToken: GetAccessToken = async (code, redirectUri) => {
type AccessTokenResponse = {
access_token: string;
scope: string;
token_type: string;
};
const { clientId, clientSecret } = await this.getConfig(
this.metadata.target,
this.metadata.platform
@ -92,17 +85,6 @@ export class GoogleConnector implements SocialConnector {
};
public getUserInfo: GetUserInfo = async (accessTokenObject) => {
type UserInfoResponse = {
sub: string;
name?: string;
given_name?: string;
family_name?: string;
picture?: string;
email?: string;
email_verified?: boolean;
locale?: string;
};
const { accessToken } = accessTokenObject;
try {

View file

@ -0,0 +1,25 @@
import { z } from 'zod';
export const googleConfigGuard = z.object({
clientId: z.string(),
clientSecret: z.string(),
});
export type GoogleConfig = z.infer<typeof googleConfigGuard>;
export type AccessTokenResponse = {
access_token: string;
scope: string;
token_type: string;
};
export type UserInfoResponse = {
sub: string;
name?: string;
given_name?: string;
family_name?: string;
picture?: string;
email?: string;
email_verified?: boolean;
locale?: string;
};

View file

@ -2,17 +2,12 @@ import path from 'path';
import { ConnectorMetadata, ConnectorType, ConnectorPlatform } from '@logto/connector-types';
import { getFileContents } from '@logto/shared';
import { z } from 'zod';
export const authorizationEndpoint = 'https://wechat.native/'; // This is used to arouse the native WeChat App
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_userinfo';
export const weChatNativeConfigGuard = z.object({ appId: z.string(), appSecret: z.string() });
export type WeChatNativeConfig = z.infer<typeof weChatNativeConfigGuard>;
// eslint-disable-next-line unicorn/prefer-module
const currentPath = __dirname;
const pathToReadmeFile = path.join(currentPath, '..', 'README.md');

View file

@ -2,13 +2,9 @@ import { ConnectorError, ConnectorErrorCodes, GetConnectorConfig } from '@logto/
import nock from 'nock';
import { WeChatNativeConnector } from '.';
import {
WeChatNativeConfig,
accessTokenEndpoint,
authorizationEndpoint,
userInfoEndpoint,
} from './constant';
import { accessTokenEndpoint, authorizationEndpoint, userInfoEndpoint } from './constant';
import { mockedConfig } from './mock';
import { WeChatNativeConfig } from './types';
const getConnectorConfig = jest.fn() as GetConnectorConfig<WeChatNativeConfig>;

View file

@ -24,9 +24,13 @@ import {
scope,
defaultMetadata,
defaultTimeout,
weChatNativeConfigGuard,
WeChatNativeConfig,
} from './constant';
import {
weChatNativeConfigGuard,
AccessTokenResponse,
UserInfoResponse,
WeChatNativeConfig,
} from './types';
// 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
@ -62,15 +66,6 @@ export class WeChatNativeConnector implements SocialConnector {
};
public getAccessToken: GetAccessToken = async (code) => {
type AccessTokenResponse = {
access_token?: string;
openid?: string;
expires_in?: number; // In seconds
refresh_token?: string;
scope?: string;
errcode?: number;
};
const { appId: appid, appSecret: secret } = await this.getConfig(
this.metadata.target,
this.metadata.platform
@ -98,14 +93,6 @@ export class WeChatNativeConnector implements SocialConnector {
// FIXME:
// eslint-disable-next-line complexity
public getUserInfo: GetUserInfo = async (accessTokenObject) => {
type UserInfoResponse = {
unionid?: string;
headimgurl?: string;
nickname?: string;
errcode?: number;
errmsg?: string;
};
const { accessToken, openid } = accessTokenObject;
try {

View file

@ -0,0 +1,22 @@
import { z } from 'zod';
export const weChatNativeConfigGuard = z.object({ appId: z.string(), appSecret: z.string() });
export type WeChatNativeConfig = z.infer<typeof weChatNativeConfigGuard>;
export type AccessTokenResponse = {
access_token?: string;
openid?: string;
expires_in?: number; // In seconds
refresh_token?: string;
scope?: string;
errcode?: number;
};
export type UserInfoResponse = {
unionid?: string;
headimgurl?: string;
nickname?: string;
errcode?: number;
errmsg?: string;
};

View file

@ -2,17 +2,12 @@ import path from 'path';
import { ConnectorMetadata, ConnectorType, ConnectorPlatform } from '@logto/connector-types';
import { getFileContents } from '@logto/shared';
import { z } from 'zod';
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';
export const weChatConfigGuard = z.object({ appId: z.string(), appSecret: z.string() });
export type WeChatConfig = z.infer<typeof weChatConfigGuard>;
// eslint-disable-next-line unicorn/prefer-module
const currentPath = __dirname;
const pathToReadmeFile = path.join(currentPath, '..', 'README.md');

View file

@ -2,13 +2,9 @@ import { ConnectorError, ConnectorErrorCodes, GetConnectorConfig } from '@logto/
import nock from 'nock';
import { WeChatConnector } from '.';
import {
WeChatConfig,
accessTokenEndpoint,
authorizationEndpoint,
userInfoEndpoint,
} from './constant';
import { accessTokenEndpoint, authorizationEndpoint, userInfoEndpoint } from './constant';
import { mockedConfig } from './mock';
import { WeChatConfig } from './types';
const getConnectorConfig = jest.fn() as GetConnectorConfig<WeChatConfig>;

View file

@ -24,9 +24,8 @@ import {
scope,
defaultMetadata,
defaultTimeout,
weChatConfigGuard,
WeChatConfig,
} from './constant';
import { weChatConfigGuard, AccessTokenResponse, UserInfoResponse, WeChatConfig } from './types';
// 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
@ -63,15 +62,6 @@ export class WeChatConnector implements SocialConnector {
};
public getAccessToken: GetAccessToken = async (code) => {
type AccessTokenResponse = {
access_token?: string;
openid?: string;
expires_in?: number; // In seconds
refresh_token?: string;
scope?: string;
errcode?: number;
};
const { appId: appid, appSecret: secret } = await this.getConfig(
this.metadata.target,
this.metadata.platform
@ -99,14 +89,6 @@ export class WeChatConnector implements SocialConnector {
// FIXME:
// eslint-disable-next-line complexity
public getUserInfo: GetUserInfo = async (accessTokenObject) => {
type UserInfoResponse = {
unionid?: string;
headimgurl?: string;
nickname?: string;
errcode?: number;
errmsg?: string;
};
const { accessToken, openid } = accessTokenObject;
try {

View file

@ -0,0 +1,22 @@
import { z } from 'zod';
export const weChatConfigGuard = z.object({ appId: z.string(), appSecret: z.string() });
export type WeChatConfig = z.infer<typeof weChatConfigGuard>;
export type AccessTokenResponse = {
access_token?: string;
openid?: string;
expires_in?: number; // In seconds
refresh_token?: string;
scope?: string;
errcode?: number;
};
export type UserInfoResponse = {
unionid?: string;
headimgurl?: string;
nickname?: string;
errcode?: number;
errmsg?: string;
};