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:
parent
0ea55134a9
commit
357fb8dc40
32 changed files with 277 additions and 279 deletions
|
@ -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 = {
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
53
packages/connector-aliyun-dm/src/types.ts
Normal file
53
packages/connector-aliyun-dm/src/types.ts
Normal 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;
|
||||
};
|
|
@ -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>,
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
57
packages/connector-aliyun-sms/src/types.ts
Normal file
57
packages/connector-aliyun-sms/src/types.ts
Normal 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>;
|
|
@ -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>,
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
21
packages/connector-facebook/src/types.ts
Normal file
21
packages/connector-facebook/src/types.ts
Normal 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 } };
|
||||
};
|
|
@ -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');
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
21
packages/connector-github/src/types.ts
Normal file
21
packages/connector-github/src/types.ts
Normal 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;
|
||||
};
|
|
@ -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');
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
25
packages/connector-google/src/types.ts
Normal file
25
packages/connector-google/src/types.ts
Normal 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;
|
||||
};
|
|
@ -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');
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
22
packages/connector-wechat-native/src/types.ts
Normal file
22
packages/connector-wechat-native/src/types.ts
Normal 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;
|
||||
};
|
|
@ -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');
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
22
packages/connector-wechat/src/types.ts
Normal file
22
packages/connector-wechat/src/types.ts
Normal 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;
|
||||
};
|
Loading…
Add table
Reference in a new issue