0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-10 21:58:23 -05:00

refactor(connector): fix UTs

This commit is contained in:
Darcy Ye 2022-08-08 11:34:42 +08:00
parent 0a73c837cf
commit 0185629352
No known key found for this signature in database
GPG key ID: B46F4C07EDEFC610
34 changed files with 361 additions and 291 deletions

View file

@ -9,7 +9,7 @@
* https://opendocs.alipay.com/open/204/105296/
*/
import { SocialConnectorInstance } from '@logto/connector-base-classes';
import { SocialConnector } from '@logto/connector-base-classes';
import {
AuthResponseParser,
ConnectorError,
@ -46,10 +46,7 @@ import { signingParameters } from './utils';
export type { AlipayNativeConfig } from './types';
export default class AlipayNativeConnector<T> extends SocialConnectorInstance<
AlipayNativeConfig,
T
> {
export default class AlipayNativeConnector extends SocialConnector<AlipayNativeConfig> {
private readonly signingParameters = signingParameters;
constructor(getConnectorConfig: GetConnectorConfig) {

View file

@ -4,7 +4,7 @@
* https://opendocs.alipay.com/open/263/105808
* https://opendocs.alipay.com/open/01emu5
*/
import { SocialConnectorInstance } from '@logto/connector-base-classes';
import { SocialConnector } from '@logto/connector-base-classes';
import {
AuthResponseParser,
ConnectorError,
@ -43,7 +43,7 @@ import { signingParameters } from './utils';
export type { AlipayConfig } from './types';
export default class AlipayConnector<T> extends SocialConnectorInstance<AlipayConfig, T> {
export default class AlipayConnector extends SocialConnector<AlipayConfig> {
private readonly signingParameters = signingParameters;
constructor(getConnectorConfig: GetConnectorConfig) {

View file

@ -1,4 +1,4 @@
import { EmailConnectorInstance } from '@logto/connector-base-classes';
import { EmailConnector } from '@logto/connector-base-classes';
import {
ConnectorError,
ConnectorErrorCodes,
@ -17,7 +17,7 @@ import {
sendMailErrorResponseGuard,
} from './types';
export default class AliyunDmConnector<T> extends EmailConnectorInstance<AliyunDmConfig, T> {
export default class AliyunDmConnector extends EmailConnector<AliyunDmConfig> {
constructor(getConnectorConfig: GetConnectorConfig) {
super(getConnectorConfig);
this.metadata = defaultMetadata;
@ -32,7 +32,7 @@ export default class AliyunDmConnector<T> extends EmailConnectorInstance<AliyunD
}
}
public readonly sendMessageBy: EmailSendMessageByFunction<AliyunDmConfig> = async (
protected readonly sendMessageBy: EmailSendMessageByFunction<AliyunDmConfig> = async (
config,
address,
type,

View file

@ -1,4 +1,4 @@
import { SmsConnectorInstance } from '@logto/connector-base-classes';
import { SmsConnector } from '@logto/connector-base-classes';
import {
ConnectorError,
ConnectorErrorCodes,
@ -12,7 +12,7 @@ import { defaultMetadata } from './constant';
import { sendSms } from './single-send-text';
import { aliyunSmsConfigGuard, AliyunSmsConfig, sendSmsResponseGuard } from './types';
export default class AliyunSmsConnector<T> extends SmsConnectorInstance<AliyunSmsConfig, T> {
export default class AliyunSmsConnector extends SmsConnector<AliyunSmsConfig> {
constructor(getConnectorConfig: GetConnectorConfig) {
super(getConnectorConfig);
this.metadata = defaultMetadata;
@ -27,7 +27,7 @@ export default class AliyunSmsConnector<T> extends SmsConnectorInstance<AliyunSm
}
}
public readonly sendMessageBy: SmsSendMessageByFunction<AliyunSmsConfig> = async (
protected readonly sendMessageBy: SmsSendMessageByFunction<AliyunSmsConfig> = async (
config,
phone,
type,

View file

@ -1,4 +1,4 @@
import { SocialConnectorInstance } from '@logto/connector-base-classes';
import { SocialConnector } from '@logto/connector-base-classes';
import {
AuthResponseParser,
GetAuthorizationUri,
@ -13,7 +13,7 @@ import { scope, defaultMetadata, jwksUri, issuer, authorizationEndpoint } from '
import { appleConfigGuard, authResponseGuard, AppleConfig, AuthResponse } from './types';
// TO-DO: support nonce validation
export default class AppleConnector<T> extends SocialConnectorInstance<AppleConfig, T> {
export default class AppleConnector extends SocialConnector<AppleConfig> {
constructor(getConnectorConfig: GetConnectorConfig) {
super(getConnectorConfig);
this.metadata = defaultMetadata;

View file

@ -6,7 +6,7 @@ import {
AuthorizationUrlRequest,
CryptoProvider,
} from '@azure/msal-node';
import { SocialConnectorInstance } from '@logto/connector-base-classes';
import { SocialConnector } from '@logto/connector-base-classes';
import {
ConnectorError,
ConnectorErrorCodes,
@ -27,7 +27,7 @@ import {
userInfoResponseGuard,
} from './types';
export default class AzureADConnector<T> extends SocialConnectorInstance<AzureADConfig, T> {
export default class AzureADConnector extends SocialConnector<AzureADConfig> {
public clientApplication!: ConfidentialClientApplication;
public authCodeUrlParams!: AuthorizationUrlRequest;

View file

@ -4,8 +4,6 @@ import path from 'path';
import {
ConnectorMetadata,
GetConnectorConfig,
ConnectorError,
ConnectorErrorCodes,
EmailSendMessageFunction,
EmailSendTestMessageFunction,
EmailSendMessageByFunction,
@ -17,24 +15,10 @@ import {
AuthResponseParser,
} from '@logto/connector-types';
export class BaseConnectorInstance<T, U> {
export class BaseConnector<T> {
public metadata!: ConnectorMetadata;
public getConfig: GetConnectorConfig;
private _connector?: U;
public get connector() {
if (!this._connector) {
throw new ConnectorError(ConnectorErrorCodes.General);
}
return this._connector;
}
public set connector(input: U) {
this._connector = input;
}
constructor(getConnectorConfig: GetConnectorConfig) {
this.getConfig = getConnectorConfig;
}
@ -43,7 +27,7 @@ export class BaseConnectorInstance<T, U> {
public validateConfig(config: unknown): asserts config is T {}
// eslint-disable-next-line complexity
public metadataParser = () => {
protected metadataParser = () => {
// eslint-disable-next-line unicorn/prefer-module
const currentPath = __dirname;
@ -83,8 +67,8 @@ export class BaseConnectorInstance<T, U> {
};
}
export class SmsConnectorInstance<T, U> extends BaseConnectorInstance<T, U> {
public readonly sendMessageBy!: EmailSendMessageByFunction<T>;
export class SmsConnector<T> extends BaseConnector<T> {
protected readonly sendMessageBy!: EmailSendMessageByFunction<T>;
public sendMessage: EmailSendMessageFunction = async (address, type, data) => {
const config = await this.getConfig(this.metadata.id);
@ -93,15 +77,15 @@ export class SmsConnectorInstance<T, U> extends BaseConnectorInstance<T, U> {
return this.sendMessageBy(config, address, type, data);
};
public sendTestMessage: EmailSendTestMessageFunction = async (config, address, type, data) => {
public sendTestMessage?: EmailSendTestMessageFunction = async (config, address, type, data) => {
this.validateConfig(config);
return this.sendMessageBy(config, address, type, data);
};
}
export class EmailConnectorInstance<T, U> extends BaseConnectorInstance<T, U> {
public readonly sendMessageBy!: SmsSendMessageByFunction<T>;
export class EmailConnector<T> extends BaseConnector<T> {
protected readonly sendMessageBy!: SmsSendMessageByFunction<T>;
public sendMessage: SmsSendMessageFunction = async (address, type, data) => {
const config = await this.getConfig(this.metadata.id);
@ -110,22 +94,17 @@ export class EmailConnectorInstance<T, U> extends BaseConnectorInstance<T, U> {
return this.sendMessageBy(config, address, type, data);
};
public sendTestMessage: SmsSendTestMessageFunction = async (config, address, type, data) => {
public sendTestMessage?: SmsSendTestMessageFunction = async (config, address, type, data) => {
this.validateConfig(config);
return this.sendMessageBy(config, address, type, data);
};
}
export class SocialConnectorInstance<T, U> extends BaseConnectorInstance<T, U> {
export class SocialConnector<T> extends BaseConnector<T> {
public getAuthorizationUri!: GetAuthorizationUri;
public getUserInfo!: GetUserInfo;
protected authResponseParser!: AuthResponseParser;
}
export type ConnectorInstance =
| InstanceType<typeof SmsConnectorInstance>
| InstanceType<typeof EmailConnectorInstance>
| InstanceType<typeof SocialConnectorInstance>;

View file

@ -3,7 +3,7 @@
* https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow
*/
import { SocialConnectorInstance } from '@logto/connector-base-classes';
import { SocialConnector } from '@logto/connector-base-classes';
import {
AuthResponseParser,
ConnectorError,
@ -33,7 +33,7 @@ import {
userInfoResponseGuard,
} from './types';
export default class FacebookConnector<T> extends SocialConnectorInstance<FacebookConfig, T> {
export default class FacebookConnector extends SocialConnector<FacebookConfig> {
constructor(getConnectorConfig: GetConnectorConfig) {
super(getConnectorConfig);
this.metadata = defaultMetadata;

View file

@ -1,4 +1,4 @@
import { SocialConnectorInstance } from '@logto/connector-base-classes';
import { SocialConnector } from '@logto/connector-base-classes';
import {
AuthResponseParser,
GetAuthorizationUri,
@ -29,7 +29,7 @@ import {
userInfoResponseGuard,
} from './types';
export default class GithubConnector<T> extends SocialConnectorInstance<GithubConfig, T> {
export default class GithubConnector extends SocialConnector<GithubConfig> {
constructor(getConnectorConfig: GetConnectorConfig) {
super(getConnectorConfig);
this.metadata = defaultMetadata;

View file

@ -2,7 +2,7 @@
* The Implementation of OpenID Connect of Google Identity Platform.
* https://developers.google.com/identity/protocols/oauth2/openid-connect
*/
import { SocialConnectorInstance } from '@logto/connector-base-classes';
import { SocialConnector } from '@logto/connector-base-classes';
import {
AuthResponseParser,
ConnectorError,
@ -31,7 +31,7 @@ import {
userInfoResponseGuard,
} from './types';
export default class GoogleConnector<T> extends SocialConnectorInstance<GoogleConfig, T> {
export default class GoogleConnector extends SocialConnector<GoogleConfig> {
constructor(getConnectorConfig: GetConnectorConfig) {
super(getConnectorConfig);
this.metadata = defaultMetadata;

View file

@ -1,7 +1,7 @@
import fs from 'fs/promises';
import path from 'path';
import { EmailConnectorInstance } from '@logto/connector-base-classes';
import { EmailConnector } from '@logto/connector-base-classes';
import {
ConnectorError,
ConnectorErrorCodes,
@ -13,7 +13,7 @@ import { assert } from '@silverhand/essentials';
import { defaultMetadata } from './constant';
import { mockMailConfigGuard, MockMailConfig } from './types';
export default class MockMailConnector<T> extends EmailConnectorInstance<MockMailConfig, T> {
export default class MockMailConnector extends EmailConnector<MockMailConfig> {
constructor(getConnectorConfig: GetConnectorConfig) {
super(getConnectorConfig);
this.metadata = defaultMetadata;
@ -28,7 +28,7 @@ export default class MockMailConnector<T> extends EmailConnectorInstance<MockMai
}
}
public readonly sendMessageBy: EmailSendMessageByFunction<MockMailConfig> = async (
protected readonly sendMessageBy: EmailSendMessageByFunction<MockMailConfig> = async (
config,
address,
type,

View file

@ -1,7 +1,7 @@
import fs from 'fs/promises';
import path from 'path';
import { SmsConnectorInstance } from '@logto/connector-base-classes';
import { SmsConnector } from '@logto/connector-base-classes';
import {
ConnectorError,
ConnectorErrorCodes,
@ -13,7 +13,7 @@ import { assert } from '@silverhand/essentials';
import { defaultMetadata } from './constant';
import { mockSmsConfigGuard, MockSmsConfig } from './types';
export default class MockSmsConnector<T> extends SmsConnectorInstance<MockSmsConfig, T> {
export default class MockSmsConnector extends SmsConnector<MockSmsConfig> {
constructor(getConnectorConfig: GetConnectorConfig) {
super(getConnectorConfig);
this.metadata = defaultMetadata;

View file

@ -1,6 +1,6 @@
import { randomUUID } from 'crypto';
import { SocialConnectorInstance } from '@logto/connector-base-classes';
import { SocialConnector } from '@logto/connector-base-classes';
import {
ConnectorError,
ConnectorErrorCodes,
@ -13,7 +13,7 @@ import { z } from 'zod';
import { defaultMetadata } from './constant';
import { mockSocialConfigGuard, MockSocialConfig } from './types';
export default class MockSocialConnector<T> extends SocialConnectorInstance<MockSocialConfig, T> {
export default class MockSocialConnector extends SocialConnector<MockSocialConfig> {
constructor(getConnectorConfig: GetConnectorConfig) {
super(getConnectorConfig);
this.metadata = defaultMetadata;

View file

@ -1,4 +1,4 @@
import { EmailConnectorInstance } from '@logto/connector-base-classes';
import { EmailConnector } from '@logto/connector-base-classes';
import {
ConnectorError,
ConnectorErrorCodes,
@ -18,10 +18,7 @@ import {
PublicParameters,
} from './types';
export default class SendGridMailConnector<T> extends EmailConnectorInstance<
SendGridMailConfig,
T
> {
export default class SendGridMailConnector extends EmailConnector<SendGridMailConfig> {
constructor(getConnectorConfig: GetConnectorConfig) {
super(getConnectorConfig);
this.metadata = defaultMetadata;
@ -36,7 +33,7 @@ export default class SendGridMailConnector<T> extends EmailConnectorInstance<
}
}
public readonly sendMessageBy: EmailSendMessageByFunction<SendGridMailConfig> = async (
protected readonly sendMessageBy: EmailSendMessageByFunction<SendGridMailConfig> = async (
config,
address,
type,

View file

@ -1,4 +1,4 @@
import { EmailConnectorInstance } from '@logto/connector-base-classes';
import { EmailConnector } from '@logto/connector-base-classes';
import {
ConnectorError,
ConnectorErrorCodes,
@ -12,7 +12,7 @@ import SMTPTransport from 'nodemailer/lib/smtp-transport';
import { defaultMetadata } from './constant';
import { ContextType, smtpConfigGuard, SmtpConfig } from './types';
export default class SmtpConnector<T> extends EmailConnectorInstance<SmtpConfig, T> {
export default class SmtpConnector extends EmailConnector<SmtpConfig> {
constructor(getConnectorConfig: GetConnectorConfig) {
super(getConnectorConfig);
this.metadata = defaultMetadata;
@ -27,7 +27,7 @@ export default class SmtpConnector<T> extends EmailConnectorInstance<SmtpConfig,
}
}
public readonly sendMessageBy: EmailSendMessageByFunction<SmtpConfig> = async (
protected readonly sendMessageBy: EmailSendMessageByFunction<SmtpConfig> = async (
config,
address,
type,

View file

@ -1,4 +1,4 @@
import { SmsConnectorInstance } from '@logto/connector-base-classes';
import { SmsConnector } from '@logto/connector-base-classes';
import {
ConnectorError,
ConnectorErrorCodes,
@ -11,7 +11,7 @@ import got, { HTTPError } from 'got';
import { defaultMetadata, endpoint } from './constant';
import { twilioSmsConfigGuard, TwilioSmsConfig, PublicParameters } from './types';
export default class TwilioSmsConnector<T> extends SmsConnectorInstance<TwilioSmsConfig, T> {
export default class TwilioSmsConnector extends SmsConnector<TwilioSmsConfig> {
constructor(getConnectorConfig: GetConnectorConfig) {
super(getConnectorConfig);
this.metadata = defaultMetadata;
@ -26,7 +26,7 @@ export default class TwilioSmsConnector<T> extends SmsConnectorInstance<TwilioSm
}
}
public readonly sendMessageBy: SmsSendMessageByFunction<TwilioSmsConfig> = async (
protected readonly sendMessageBy: SmsSendMessageByFunction<TwilioSmsConfig> = async (
config,
phone,
type,

View file

@ -3,7 +3,7 @@
* https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
*/
import { SocialConnectorInstance } from '@logto/connector-base-classes';
import { SocialConnector } from '@logto/connector-base-classes';
import {
GetAuthorizationUri,
GetUserInfo,
@ -35,10 +35,7 @@ import {
WechatNativeConfig,
} from './types';
export default class WechatNativeConnector<T> extends SocialConnectorInstance<
WechatNativeConfig,
T
> {
export default class WechatNativeConnector extends SocialConnector<WechatNativeConfig> {
constructor(getConnectorConfig: GetConnectorConfig) {
super(getConnectorConfig);
this.metadata = defaultMetadata;

View file

@ -3,7 +3,7 @@
* https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
*/
import { SocialConnectorInstance } from '@logto/connector-base-classes';
import { SocialConnector } from '@logto/connector-base-classes';
import {
AuthResponseParser,
GetAuthorizationUri,
@ -36,7 +36,7 @@ import {
WechatConfig,
} from './types';
export default class WechatConnector<T> extends SocialConnectorInstance<WechatConfig, T> {
export default class WechatConnector extends SocialConnector<WechatConfig> {
constructor(getConnectorConfig: GetConnectorConfig) {
super(getConnectorConfig);
this.metadata = defaultMetadata;

View file

@ -148,35 +148,35 @@ export const mockConnectorList: Connector[] = [
export const mockConnectorInstanceList: Array<{
connector: Connector;
metadata: ConnectorMetadata;
instance: { metadata: ConnectorMetadata };
}> = [
{
connector: mockConnector0,
metadata: { ...mockMetadata0, type: ConnectorType.Social },
instance: { metadata: { ...mockMetadata0, type: ConnectorType.Social } },
},
{
connector: mockConnector1,
metadata: mockMetadata1,
instance: { metadata: mockMetadata1 },
},
{
connector: mockConnector2,
metadata: mockMetadata2,
instance: { metadata: mockMetadata2 },
},
{
connector: mockConnector3,
metadata: mockMetadata3,
instance: { metadata: mockMetadata3 },
},
{
connector: mockConnector4,
metadata: { ...mockMetadata4, type: ConnectorType.Email, platform: null },
instance: { metadata: { ...mockMetadata4, type: ConnectorType.Email, platform: null } },
},
{
connector: mockConnector5,
metadata: { ...mockMetadata5, type: ConnectorType.SMS, platform: null },
instance: { metadata: { ...mockMetadata5, type: ConnectorType.SMS, platform: null } },
},
{
connector: mockConnector6,
metadata: { ...mockMetadata6, type: ConnectorType.Email, platform: null },
instance: { metadata: { ...mockMetadata6, type: ConnectorType.Email, platform: null } },
},
];
@ -185,12 +185,14 @@ export const mockAliyunDmConnectorInstance = {
...mockConnector,
id: 'aliyun-dm',
},
metadata: {
...mockMetadata,
id: 'aliyun-dm',
target: 'aliyun-dm',
type: ConnectorType.Email,
platform: null,
instance: {
metadata: {
...mockMetadata,
id: 'aliyun-dm',
target: 'aliyun-dm',
type: ConnectorType.Email,
platform: null,
},
},
};
@ -199,12 +201,14 @@ export const mockAliyunSmsConnectorInstance = {
...mockConnector,
id: 'aliyun-sms',
},
metadata: {
...mockMetadata,
id: 'aliyun-sms',
target: 'aliyun-sms',
type: ConnectorType.SMS,
platform: null,
instance: {
metadata: {
...mockMetadata,
id: 'aliyun-sms',
target: 'aliyun-sms',
type: ConnectorType.SMS,
platform: null,
},
},
};
@ -213,12 +217,14 @@ export const mockFacebookConnectorInstance = {
...mockConnector,
id: 'facebook',
},
metadata: {
...mockMetadata,
id: 'facebook',
target: 'facebook',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,
instance: {
metadata: {
...mockMetadata,
id: 'facebook',
target: 'facebook',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,
},
},
};
@ -227,12 +233,14 @@ export const mockGithubConnectorInstance = {
...mockConnector,
id: 'github',
},
metadata: {
...mockMetadata,
id: 'github',
target: 'github',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,
instance: {
metadata: {
...mockMetadata,
id: 'github',
target: 'github',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,
},
},
};
@ -241,12 +249,14 @@ export const mockWechatConnectorInstance = {
...mockConnector,
id: 'wechat-web',
},
metadata: {
...mockMetadata,
id: 'wechat-web',
target: 'wechat',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,
instance: {
metadata: {
...mockMetadata,
id: 'wechat-web',
target: 'wechat',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,
},
},
};
@ -255,12 +265,14 @@ export const mockWechatNativeConnectorInstance = {
...mockConnector,
id: 'wechat-native',
},
metadata: {
...mockMetadata,
id: 'wechat-native',
target: 'wechat',
type: ConnectorType.Social,
platform: ConnectorPlatform.Native,
instance: {
metadata: {
...mockMetadata,
id: 'wechat-native',
target: 'wechat',
type: ConnectorType.Social,
platform: ConnectorPlatform.Native,
},
},
};
@ -270,12 +282,14 @@ export const mockGoogleConnectorInstance = {
id: 'google',
enabled: false,
},
metadata: {
...mockMetadata,
id: 'google',
target: 'google',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,
instance: {
metadata: {
...mockMetadata,
id: 'google',
target: 'google',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,
},
},
};

View file

@ -1,16 +1,12 @@
import { ConnectorInstance, SocialConnectorInstance } from '@logto/connector-base-classes';
import { Connector } from '@logto/schemas';
import envSet from '@/env-set';
import RequestError from '@/errors/RequestError';
import { findAllConnectors, insertConnector } from '@/queries/connector';
import { defaultConnectorPackages } from './consts';
import { ConnectorType } from './types';
import { ConnectorType, ConnectorInstance, Instance, SocialConnectorInstance } from './types';
import { getConnectorConfig } from './utilities';
// eslint-disable-next-line @silverhand/fp/no-let
let cachedConnectors: ConnectorInstance[] | undefined;
let cachedConnectors: Instance[] | undefined;
const loadConnectors = async () => {
if (cachedConnectors) {
@ -28,8 +24,8 @@ const loadConnectors = async () => {
connectorPackages.map(async (packageName) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { default: Builder } = await import(packageName);
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment
const instance: ConnectorInstance = new Builder<Connector>(getConnectorConfig);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
const instance: Instance = new Builder(getConnectorConfig);
return instance;
})
@ -42,20 +38,17 @@ export const getConnectorInstances = async (): Promise<ConnectorInstance[]> => {
const connectors = await findAllConnectors();
const connectorMap = new Map(connectors.map((connector) => [connector.id, connector]));
const allConnectors = await loadConnectors();
const allInstances = await loadConnectors();
return allConnectors.map((element) => {
const { id } = element.metadata;
return allInstances.map((instance) => {
const { id } = instance.metadata;
const connector = connectorMap.get(id);
if (!connector) {
throw new RequestError({ code: 'entity.not_found', id, status: 404 });
}
// eslint-disable-next-line @silverhand/fp/no-mutation
element.connector = connector;
return element;
return { instance, connector };
});
};
@ -76,13 +69,13 @@ export const getConnectorInstanceById = async (id: string): Promise<ConnectorIns
const isSocialConnectorInstance = (
connector: ConnectorInstance
): connector is InstanceType<typeof SocialConnectorInstance> => {
return connector.metadata.type === ConnectorType.Social;
): connector is SocialConnectorInstance => {
return connector.instance.metadata.type === ConnectorType.Social;
};
export const getSocialConnectorInstanceById = async (
id: string
): Promise<InstanceType<typeof SocialConnectorInstance>> => {
): Promise<SocialConnectorInstance> => {
const connector = await getConnectorInstanceById(id);
if (!isSocialConnectorInstance(connector)) {

View file

@ -1,4 +1,5 @@
import { PasscodeType } from '@logto/schemas';
import { SmsConnector, EmailConnector, SocialConnector } from '@logto/connector-base-classes';
import { PasscodeType, Connector } from '@logto/schemas';
import { z } from 'zod';
export { ConnectorType } from '@logto/schemas';
@ -15,3 +16,29 @@ export const socialUserInfoGuard = z.object({
});
export type SocialUserInfo = z.infer<typeof socialUserInfoGuard>;
export type Instance =
| InstanceType<typeof SmsConnector>
| InstanceType<typeof EmailConnector>
| InstanceType<typeof SocialConnector>;
export type SmsConnectorInstance = {
instance: InstanceType<typeof SmsConnector>;
connector: Connector;
};
export type EmailConnectorInstance = {
instance: InstanceType<typeof EmailConnector>;
connector: Connector;
};
export type SocialConnectorInstance = {
instance: InstanceType<typeof SocialConnector>;
connector: Connector;
};
export type ConnectorInstance =
| SmsConnectorInstance
| EmailConnectorInstance
| SocialConnectorInstance
| { instance: Instance; connector: Connector };

View file

@ -1,8 +1,8 @@
import { ConnectorType } from '@logto/connector-types';
import { Passcode, PasscodeType } from '@logto/schemas';
import { ConnectorType, ValidateConfig, GetConnectorConfig } from '@logto/connector-types';
import { Passcode, PasscodeType, Connector } from '@logto/schemas';
import { mockConnector, mockMetadata } from '@/__mocks__';
import { getConnectorInstances } from '@/connectors';
import { ConnectorMetadata } from '@/connectors/types';
import RequestError from '@/errors/RequestError';
import {
consumePasscode,
@ -25,6 +25,17 @@ import {
jest.mock('@/queries/passcode');
jest.mock('@/connectors');
type ConnectorInstance = {
connector: Connector;
instance: {
metadata: ConnectorMetadata;
validateConfig?: ValidateConfig<unknown>;
getConfig?: GetConnectorConfig;
sendMessage?: unknown;
sendTestMessage?: unknown;
};
};
const mockedFindUnconsumedPasscodesByJtiAndType =
findUnconsumedPasscodesByJtiAndType as jest.MockedFunction<
typeof findUnconsumedPasscodesByJtiAndType
@ -37,14 +48,19 @@ const mockedDeletePasscodesByIds = deletePasscodesByIds as jest.MockedFunction<
typeof deletePasscodesByIds
>;
const mockedInsertPasscode = insertPasscode as jest.MockedFunction<typeof insertPasscode>;
const mockedGetConnectorInstances = getConnectorInstances as jest.MockedFunction<
typeof getConnectorInstances
>;
const mockedConsumePasscode = consumePasscode as jest.MockedFunction<typeof consumePasscode>;
const mockedIncreasePasscodeTryCount = increasePasscodeTryCount as jest.MockedFunction<
typeof increasePasscodeTryCount
>;
const getConnectorInstancesPlaceHolder = jest.fn() as jest.MockedFunction<
() => Promise<ConnectorInstance[]>
>;
jest.mock('@/connectors', () => ({
getConnectorInstances: async () => getConnectorInstancesPlaceHolder(),
}));
beforeAll(() => {
mockedFindUnconsumedPasscodesByJtiAndType.mockResolvedValue([]);
mockedInsertPasscode.mockImplementation(async (data): Promise<Passcode> => {
@ -125,24 +141,27 @@ describe('sendPasscode', () => {
it('should throw error when email or sms connector can not be found', async () => {
const sendMessage = jest.fn();
const validateConfig = jest.fn();
const getConfig = jest.fn();
mockedGetConnectorInstances.mockResolvedValueOnce([
const validateConfig = jest.fn() as ValidateConfig<unknown>;
const getConfig = jest.fn() as GetConnectorConfig;
const mockConnectorInstances: ConnectorInstance[] = [
{
connector: {
...mockConnector,
id: 'id1',
},
metadata: {
...mockMetadata,
type: ConnectorType.Email,
platform: null,
instance: {
metadata: {
...mockMetadata,
type: ConnectorType.Email,
platform: null,
},
sendMessage,
validateConfig,
getConfig,
},
sendMessage,
validateConfig,
getConfig,
},
]);
];
getConnectorInstancesPlaceHolder.mockResolvedValueOnce(mockConnectorInstances);
const passcode: Passcode = {
id: 'id',
interactionJti: 'jti',
@ -164,38 +183,42 @@ describe('sendPasscode', () => {
it('should call sendPasscode with params matching', async () => {
const sendMessage = jest.fn();
const validateConfig = jest.fn();
const getConfig = jest.fn();
mockedGetConnectorInstances.mockResolvedValueOnce([
const validateConfig = jest.fn() as ValidateConfig<unknown>;
const getConfig = jest.fn() as GetConnectorConfig;
const mockConnectorInstances: ConnectorInstance[] = [
{
connector: {
...mockConnector,
id: 'id0',
},
metadata: {
...mockMetadata,
type: ConnectorType.SMS,
platform: null,
instance: {
metadata: {
...mockMetadata,
type: ConnectorType.SMS,
platform: null,
},
sendMessage,
validateConfig,
getConfig,
},
sendMessage,
validateConfig,
getConfig,
},
{
connector: {
...mockConnector,
id: 'id1',
},
metadata: {
...mockMetadata,
type: ConnectorType.Email,
platform: null,
instance: {
metadata: {
...mockMetadata,
type: ConnectorType.Email,
platform: null,
},
validateConfig,
getConfig,
},
sendMessage,
validateConfig,
getConfig,
},
]);
];
getConnectorInstancesPlaceHolder.mockResolvedValueOnce(mockConnectorInstances);
const passcode: Passcode = {
id: 'passcode_id',
interactionJti: 'jti',

View file

@ -1,9 +1,8 @@
import { EmailConnectorInstance, SmsConnectorInstance } from '@logto/connector-base-classes';
import { Passcode, PasscodeType } from '@logto/schemas';
import { customAlphabet, nanoid } from 'nanoid';
import { getConnectorInstances } from '@/connectors';
import { ConnectorType } from '@/connectors/types';
import { ConnectorType, EmailConnectorInstance, SmsConnectorInstance } from '@/connectors/types';
import RequestError from '@/errors/RequestError';
import {
consumePasscode,
@ -49,12 +48,12 @@ export const sendPasscode = async (passcode: Passcode) => {
const connectorInstances = await getConnectorInstances();
const emailConnectorInstance = connectorInstances.find(
(connector): connector is InstanceType<typeof EmailConnectorInstance> =>
connector.connector.enabled && connector.metadata.type === ConnectorType.Email
(connector): connector is EmailConnectorInstance =>
connector.connector.enabled && connector.instance.metadata.type === ConnectorType.Email
);
const smsConnectorInstance = connectorInstances.find(
(connector): connector is InstanceType<typeof SmsConnectorInstance> =>
connector.connector.enabled && connector.metadata.type === ConnectorType.SMS
(connector): connector is SmsConnectorInstance =>
connector.connector.enabled && connector.instance.metadata.type === ConnectorType.SMS
);
const connectorInstance = passcode.email ? emailConnectorInstance : smsConnectorInstance;
@ -67,7 +66,10 @@ export const sendPasscode = async (passcode: Passcode) => {
})
);
const { connector, metadata, sendMessage } = connectorInstance;
const {
connector,
instance: { metadata, sendMessage },
} = connectorInstance;
const response = await sendMessage(emailOrPhone, passcode.type, {
code: passcode.code,

View file

@ -1,4 +1,3 @@
import { ConnectorInstance } from '@logto/connector-types';
import { BrandingStyle, SignInMethodState, ConnectorType } from '@logto/schemas';
import {
@ -8,6 +7,7 @@ import {
mockBranding,
mockSignInMethods,
} from '@/__mocks__';
import { ConnectorInstance } from '@/connectors/types';
import RequestError from '@/errors/RequestError';
import {
isEnabled,

View file

@ -1,4 +1,3 @@
import { ConnectorInstance } from '@logto/connector-base-classes';
import {
Branding,
BrandingStyle,
@ -8,7 +7,7 @@ import {
} from '@logto/schemas';
import { Optional } from '@silverhand/essentials';
import { ConnectorType } from '@/connectors/types';
import { ConnectorInstance, ConnectorType } from '@/connectors/types';
import RequestError from '@/errors/RequestError';
import assertThat from '@/utils/assert-that';
@ -42,7 +41,7 @@ export const validateSignInMethods = (
if (isEnabled(signInMethods.email)) {
assertThat(
enabledConnectorInstances.some((item) => item.metadata.type === ConnectorType.Email),
enabledConnectorInstances.some((item) => item.instance.metadata.type === ConnectorType.Email),
new RequestError({
code: 'sign_in_experiences.enabled_connector_not_found',
type: ConnectorType.Email,
@ -52,7 +51,7 @@ export const validateSignInMethods = (
if (isEnabled(signInMethods.sms)) {
assertThat(
enabledConnectorInstances.some((item) => item.metadata.type === ConnectorType.SMS),
enabledConnectorInstances.some((item) => item.instance.metadata.type === ConnectorType.SMS),
new RequestError({
code: 'sign_in_experiences.enabled_connector_not_found',
type: ConnectorType.SMS,
@ -62,7 +61,9 @@ export const validateSignInMethods = (
if (isEnabled(signInMethods.social)) {
assertThat(
enabledConnectorInstances.some((item) => item.metadata.type === ConnectorType.Social),
enabledConnectorInstances.some(
(item) => item.instance.metadata.type === ConnectorType.Social
),
new RequestError({
code: 'sign_in_experiences.enabled_connector_not_found',
type: ConnectorType.Social,

View file

@ -41,7 +41,7 @@ export const getUserInfoByAuthCode = async (
): Promise<SocialUserInfo> => {
const connector = await getConnector(connectorId);
return connector.getUserInfo(data);
return connector.instance.getUserInfo(data);
};
export const getUserInfoFromInteractionResult = async (

View file

@ -1,8 +1,4 @@
import {
ValidateConfig,
EmailConnectorInstance,
SmsConnectorInstance,
} from '@logto/connector-types';
import { GetConnectorConfig, ValidateConfig } from '@logto/connector-types';
import { Connector, ConnectorType } from '@logto/schemas';
import { mockConnectorInstanceList, mockMetadata, mockConnector } from '@/__mocks__';
@ -15,9 +11,13 @@ import connectorRoutes from './connector';
type ConnectorInstance = {
connector: Connector;
metadata: ConnectorMetadata;
validateConfig?: ValidateConfig;
sendMessage?: unknown;
instance: {
metadata: ConnectorMetadata;
validateConfig?: ValidateConfig<unknown>;
getConfig?: GetConnectorConfig;
sendMessage?: unknown;
sendTestMessage?: unknown;
};
};
const getConnectorInstancesPlaceHolder = jest.fn() as jest.MockedFunction<
@ -59,7 +59,7 @@ describe('connector route', () => {
it('throws if more than one SMS connector is enabled', async () => {
getConnectorInstancesPlaceHolder.mockResolvedValueOnce(
mockConnectorInstanceList.filter(
(connectorInstance) => connectorInstance.metadata.type !== ConnectorType.Email
(connectorInstance) => connectorInstance.instance.metadata.type !== ConnectorType.Email
)
);
const response = await connectorRequest.get('/connectors').send({});
@ -69,7 +69,7 @@ describe('connector route', () => {
it('shows all connectors', async () => {
getConnectorInstancesPlaceHolder.mockResolvedValueOnce(
mockConnectorInstanceList.filter(
(connectorInstance) => connectorInstance.metadata.type === ConnectorType.Social
(connectorInstance) => connectorInstance.instance.metadata.type === ConnectorType.Social
)
);
const response = await connectorRequest.get('/connectors').send({});
@ -107,46 +107,50 @@ describe('connector route', () => {
});
it('should get SMS connector and send test message', async () => {
const mockSendTestMessage = jest.fn();
const mockedMetadata = {
...mockMetadata,
type: ConnectorType.SMS,
};
const mockedSmsConnectorInstance: SmsConnectorInstance = {
const mockedSmsConnectorInstance: ConnectorInstance = {
connector: mockConnector,
metadata: mockedMetadata,
validateConfig: jest.fn(),
getConfig: jest.fn(),
sendMessage: jest.fn(),
sendTestMessage: jest.fn(),
instance: {
metadata: mockedMetadata,
validateConfig: jest.fn(),
getConfig: jest.fn(),
sendMessage: jest.fn(),
sendTestMessage: mockSendTestMessage,
},
};
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([mockedSmsConnectorInstance]);
const sendMessageSpy = jest.spyOn(mockedSmsConnectorInstance, 'sendTestMessage');
const response = await connectorRequest
.post('/connectors/id/test')
.send({ phone: '12345678901', config: { test: 123 } });
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
expect(sendMessageSpy).toHaveBeenCalledWith({ test: 123 }, '12345678901', 'Test', {
expect(mockSendTestMessage).toHaveBeenCalledTimes(1);
expect(mockSendTestMessage).toHaveBeenCalledWith({ test: 123 }, '12345678901', 'Test', {
code: '123456',
});
expect(response).toHaveProperty('statusCode', 204);
});
it('should get email connector and send test message', async () => {
const mockedEmailConnector: EmailConnectorInstance = {
const sendTestMessage = jest.fn();
const mockedEmailConnector: ConnectorInstance = {
connector: mockConnector,
metadata: mockMetadata,
validateConfig: jest.fn(),
getConfig: jest.fn(),
sendMessage: jest.fn(),
sendTestMessage: jest.fn(),
instance: {
metadata: mockMetadata,
validateConfig: jest.fn(),
getConfig: jest.fn(),
sendMessage: jest.fn(),
sendTestMessage,
},
};
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([mockedEmailConnector]);
const sendMessageSpy = jest.spyOn(mockedEmailConnector, 'sendTestMessage');
const response = await connectorRequest
.post('/connectors/id/test')
.send({ email: 'test@email.com', config: { test: 123 } });
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
expect(sendMessageSpy).toHaveBeenCalledWith({ test: 123 }, 'test@email.com', 'Test', {
expect(sendTestMessage).toHaveBeenCalledTimes(1);
expect(sendTestMessage).toHaveBeenCalledWith({ test: 123 }, 'test@email.com', 'Test', {
code: 'email-test',
});
expect(response).toHaveProperty('statusCode', 204);

View file

@ -1,13 +1,13 @@
import {
ConnectorInstance,
EmailConnectorInstance,
SmsConnectorInstance,
} from '@logto/connector-base-classes';
import { arbitraryObjectGuard, ConnectorDto, Connectors, ConnectorType } from '@logto/schemas';
import { emailRegEx, phoneRegEx } from '@logto/shared';
import { object, string } from 'zod';
import { getConnectorInstances, getConnectorInstanceById } from '@/connectors';
import {
ConnectorInstance,
EmailConnectorInstance,
SmsConnectorInstance,
} from '@/connectors/types';
import RequestError from '@/errors/RequestError';
import koaGuard from '@/middleware/koa-guard';
import { updateConnector } from '@/queries/connector';
@ -15,7 +15,10 @@ import assertThat from '@/utils/assert-that';
import { AuthedRouter } from './types';
const transpileConnectorInstance = ({ connector, metadata }: ConnectorInstance): ConnectorDto => ({
const transpileConnectorInstance = ({
connector,
instance: { metadata },
}: ConnectorInstance): ConnectorDto => ({
...connector,
...metadata,
});
@ -35,20 +38,26 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
assertThat(
connectorInstances.filter(
(connector) =>
connector.connector.enabled && connector.metadata.type === ConnectorType.Email
connector.connector.enabled && connector.instance.metadata.type === ConnectorType.Email
).length <= 1,
'connector.more_than_one_email'
);
assertThat(
connectorInstances.filter(
(connector) =>
connector.connector.enabled && connector.metadata.type === ConnectorType.SMS
connector.connector.enabled && connector.instance.metadata.type === ConnectorType.SMS
).length <= 1,
'connector.more_than_one_sms'
);
const filteredInstances = filterTarget
? connectorInstances.filter(({ metadata: { target } }) => target === filterTarget)
? connectorInstances.filter(
({
instance: {
metadata: { target },
},
}) => target === filterTarget
)
: connectorInstances;
ctx.body = filteredInstances.map((connectorInstance) =>
@ -88,8 +97,7 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
const connectorInstance = await getConnectorInstanceById(id);
const {
connector: { config },
validateConfig,
metadata,
instance: { validateConfig, metadata },
} = connectorInstance;
/**
@ -113,7 +121,7 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
connectors
.filter(
(connector) =>
connector.metadata.type === metadata.type && connector.connector.enabled
connector.instance.metadata.type === metadata.type && connector.connector.enabled
)
.map(async ({ connector: { id } }) =>
updateConnector({ set: { enabled: false }, where: { id }, jsonbMode: 'merge' })
@ -144,7 +152,9 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
body,
} = ctx.guard;
const { metadata, validateConfig } = await getConnectorInstanceById(id);
const {
instance: { metadata, validateConfig },
} = await getConnectorInstanceById(id);
/**
* Assertion functions always need explicit annotations.
@ -184,17 +194,16 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
const subject = phone ?? email;
assertThat(subject, new RequestError({ code: 'guard.invalid_input' }));
const connector:
| InstanceType<typeof SmsConnectorInstance>
| InstanceType<typeof EmailConnectorInstance>
| undefined = phone
const connector: SmsConnectorInstance | EmailConnectorInstance | undefined = phone
? connectorInstances.find(
(connector): connector is InstanceType<typeof SmsConnectorInstance> =>
connector.metadata.id === id && connector.metadata.type === ConnectorType.SMS
(connector): connector is SmsConnectorInstance =>
connector.instance.metadata.id === id &&
connector.instance.metadata.type === ConnectorType.SMS
)
: connectorInstances.find(
(connector): connector is InstanceType<typeof EmailConnectorInstance> =>
connector.metadata.id === id && connector.metadata.type === ConnectorType.Email
(connector): connector is EmailConnectorInstance =>
connector.instance.metadata.id === id &&
connector.instance.metadata.type === ConnectorType.Email
);
assertThat(
@ -205,7 +214,9 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
})
);
const { sendTestMessage } = connector;
const {
instance: { sendTestMessage },
} = connector;
assertThat(
sendTestMessage,
new RequestError({

View file

@ -12,9 +12,11 @@ import connectorRoutes from './connector';
type ConnectorInstance = {
connector: Connector;
metadata: ConnectorMetadata;
validateConfig?: ValidateConfig;
sendMessage?: unknown;
instance: {
metadata: ConnectorMetadata;
validateConfig?: ValidateConfig<unknown>;
sendMessage?: unknown;
};
};
const getConnectorInstancesPlaceHolder = jest.fn() as jest.MockedFunction<
@ -22,9 +24,11 @@ const getConnectorInstancesPlaceHolder = jest.fn() as jest.MockedFunction<
>;
const getConnectorInstanceByIdPlaceHolder = jest.fn(async (connectorId: string) => {
const connectorInstances = await getConnectorInstancesPlaceHolder();
const connector = connectorInstances.find(({ connector }) => connector.id === connectorId);
const connectorInstance = connectorInstances.find(
({ connector }) => connector.id === connectorId
);
assertThat(
connector,
connectorInstance,
new RequestError({
code: 'entity.not_found',
connectorId,
@ -32,13 +36,18 @@ const getConnectorInstanceByIdPlaceHolder = jest.fn(async (connectorId: string)
})
);
const { instance, connector } = connectorInstance;
return {
...connector,
validateConfig: validateConfigPlaceHolder,
sendMessage: sendMessagePlaceHolder,
connector,
instance: {
...instance,
validateConfig: validateConfigPlaceHolder,
sendMessage: sendMessagePlaceHolder,
},
};
});
const validateConfigPlaceHolder = jest.fn() as jest.MockedFunction<ValidateConfig>;
const validateConfigPlaceHolder = jest.fn() as jest.MockedFunction<ValidateConfig<unknown>>;
const sendMessagePlaceHolder = jest.fn();
jest.mock('@/queries/connector', () => ({
@ -78,7 +87,7 @@ describe('connector PATCH routes', () => {
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([
{
connector: mockConnector,
metadata: { ...mockMetadata, type: ConnectorType.Social },
instance: { metadata: { ...mockMetadata, type: ConnectorType.Social } },
},
]);
const response = await connectorRequest
@ -104,7 +113,7 @@ describe('connector PATCH routes', () => {
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([
{
connector: mockConnector,
metadata: mockMetadata,
instance: { metadata: mockMetadata },
},
]);
const response = await connectorRequest
@ -117,7 +126,7 @@ describe('connector PATCH routes', () => {
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([
{
connector: mockConnector,
metadata: mockMetadata,
instance: { metadata: mockMetadata },
},
]);
const response = await connectorRequest
@ -149,9 +158,7 @@ describe('connector PATCH routes', () => {
};
getConnectorInstanceByIdPlaceHolder.mockResolvedValueOnce({
connector: mockedConnector,
metadata: mockedMetadata,
validateConfig: jest.fn(),
sendMessage: jest.fn(),
instance: { metadata: mockedMetadata, validateConfig: jest.fn(), sendMessage: jest.fn() },
});
const response = await connectorRequest
.patch('/connectors/id1/enabled')
@ -193,9 +200,11 @@ describe('connector PATCH routes', () => {
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([
{
connector: mockConnector,
metadata: {
...mockMetadata,
type: ConnectorType.SMS,
instance: {
metadata: {
...mockMetadata,
type: ConnectorType.SMS,
},
},
},
]);
@ -209,7 +218,7 @@ describe('connector PATCH routes', () => {
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([
{
connector: mockConnector,
metadata: mockMetadata,
instance: { metadata: mockMetadata },
},
]);
const response = await connectorRequest
@ -253,7 +262,7 @@ describe('connector PATCH routes', () => {
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([
{
connector: mockConnector,
metadata: mockMetadata,
instance: { metadata: mockMetadata },
},
]);
const response = await connectorRequest
@ -266,7 +275,7 @@ describe('connector PATCH routes', () => {
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([
{
connector: mockConnector,
metadata: mockMetadata,
instance: { metadata: mockMetadata },
},
]);
const response = await connectorRequest

View file

@ -61,13 +61,13 @@ const getConnectorInstanceByIdHelper = jest.fn(async (connectorId: string) => {
type: connectorId.startsWith('social') ? ConnectorType.Social : ConnectorType.SMS,
};
return { connector, metadata, getAuthorizationUri: jest.fn(async () => '') };
return { connector, instance: { metadata, getAuthorizationUri: jest.fn(async () => '') } };
});
jest.mock('@/connectors', () => ({
getSocialConnectorInstanceById: async (connectorId: string) => {
const connectorInstance = await getConnectorInstanceByIdHelper(connectorId);
if (connectorInstance.metadata.type !== ConnectorType.Social) {
if (connectorInstance.instance.metadata.type !== ConnectorType.Social) {
throw new RequestError({
code: 'entity.not_found',
status: 404,
@ -170,7 +170,7 @@ describe('sessionSocialRoutes', () => {
it('throw error when auth code is wrong', async () => {
(getConnectorInstanceById as jest.Mock).mockResolvedValueOnce({
metadata: { target: connectorTarget },
instance: { metadata: { target: connectorTarget } },
});
const response = await sessionRequest.post('/session/sign-in/social/auth').send({
connectorId: 'connectorId',
@ -183,7 +183,7 @@ describe('sessionSocialRoutes', () => {
it('throw error when code is provided but connector can not be found', async () => {
(getConnectorInstanceById as jest.Mock).mockResolvedValueOnce({
metadata: { target: connectorTarget },
instance: { metadata: { target: connectorTarget } },
});
const response = await sessionRequest.post('/session/sign-in/social/auth').send({
connectorId: '_connectorId',
@ -196,7 +196,7 @@ describe('sessionSocialRoutes', () => {
it('get and add user info with auth code, as well as assign result and redirect', async () => {
(getConnectorInstanceById as jest.Mock).mockResolvedValueOnce({
metadata: { target: connectorTarget },
instance: { metadata: { target: connectorTarget } },
});
const response = await sessionRequest.post('/session/sign-in/social/auth').send({
connectorId: 'connectorId',
@ -224,7 +224,7 @@ describe('sessionSocialRoutes', () => {
it('throw error when identity exists', async () => {
const wrongConnectorTarget = 'wrongConnectorTarget';
(getConnectorInstanceById as jest.Mock).mockResolvedValueOnce({
metadata: { target: wrongConnectorTarget },
instance: { metadata: { target: wrongConnectorTarget } },
});
const response = await sessionRequest.post('/session/sign-in/social/auth').send({
connectorId: '_connectorId_',
@ -250,7 +250,7 @@ describe('sessionSocialRoutes', () => {
beforeEach(() => {
const mockGetConnectorInstanceById = getConnectorInstanceById as jest.Mock;
mockGetConnectorInstanceById.mockResolvedValueOnce({
metadata: { target: 'connectorTarget' },
instance: { metadata: { target: 'connectorTarget' } },
});
});
afterEach(() => {
@ -312,7 +312,7 @@ describe('sessionSocialRoutes', () => {
beforeEach(() => {
const mockGetConnectorInstanceById = getConnectorInstanceById as jest.Mock;
mockGetConnectorInstanceById.mockResolvedValueOnce({
metadata: { target: 'connectorTarget' },
instance: { metadata: { target: 'connectorTarget' } },
});
});
afterEach(() => {
@ -378,7 +378,7 @@ describe('sessionSocialRoutes', () => {
beforeEach(() => {
const mockGetConnectorInstanceById = getConnectorInstanceById as jest.Mock;
mockGetConnectorInstanceById.mockResolvedValueOnce({
metadata: { target: 'connectorTarget' },
instance: { metadata: { target: 'connectorTarget' } },
});
});
afterEach(() => {

View file

@ -45,7 +45,7 @@ export default function sessionSocialRoutes<T extends AnonymousRouter>(
assertThat(state && redirectUri, 'session.insufficient_info');
const connector = await getSocialConnectorInstanceById(connectorId);
assertThat(connector.connector.enabled, 'connector.not_enabled');
const redirectTo = await connector.getAuthorizationUri({ state, redirectUri });
const redirectTo = await connector.instance.getAuthorizationUri({ state, redirectUri });
ctx.body = { redirectTo };
return next();
@ -67,7 +67,9 @@ export default function sessionSocialRoutes<T extends AnonymousRouter>(
const type = 'SignInSocial';
ctx.log(type, { connectorId, data });
const {
metadata: { target },
instance: {
metadata: { target },
},
} = await getConnectorInstanceById(connectorId);
const userInfo = await getUserInfoByAuthCode(connectorId, data);
@ -118,7 +120,9 @@ export default function sessionSocialRoutes<T extends AnonymousRouter>(
const type = 'SignInSocialBind';
ctx.log(type, { connectorId });
const {
metadata: { target },
instance: {
metadata: { target },
},
} = await getConnectorInstanceById(connectorId);
const userInfo = await getUserInfoFromInteractionResult(connectorId, result);
@ -158,7 +162,9 @@ export default function sessionSocialRoutes<T extends AnonymousRouter>(
const type = 'RegisterSocial';
ctx.log(type, { connectorId });
const {
metadata: { target },
instance: {
metadata: { target },
},
} = await getConnectorInstanceById(connectorId);
const userInfo = await getUserInfoFromInteractionResult(connectorId, result);
@ -203,7 +209,9 @@ export default function sessionSocialRoutes<T extends AnonymousRouter>(
const type = 'RegisterSocialBind';
ctx.log(type, { connectorId, userId });
const {
metadata: { target },
instance: {
metadata: { target },
},
} = await getConnectorInstanceById(connectorId);
const userInfo = await getUserInfoFromInteractionResult(connectorId, result);

View file

@ -52,7 +52,8 @@ export default function signInExperiencesRoutes<T extends AuthedRouter>(router:
const filteredSocialSignInConnectorTargets = socialSignInConnectorTargets?.filter((target) =>
enabledConnectorInstances.some(
(connector) =>
connector.metadata.target === target && connector.metadata.type === ConnectorType.Social
connector.instance.metadata.target === target &&
connector.instance.metadata.type === ConnectorType.Social
)
);

View file

@ -32,7 +32,7 @@ jest.mock('@/connectors', () => ({
getSocialConnectorInstanceById: async (connectorId: string) => {
const connectorInstance = await getConnectorInstanceById(connectorId);
if (connectorInstance.metadata.type !== ConnectorType.Social) {
if (connectorInstance.instance.metadata.type !== ConnectorType.Social) {
throw new RequestError({
code: 'entity.not_found',
status: 404,
@ -92,19 +92,19 @@ describe('GET /.well-known/sign-in-exp', () => {
...mockSignInExperience,
socialConnectors: [
{
...mockGithubConnectorInstance.metadata,
...mockGithubConnectorInstance.instance.metadata,
id: mockGithubConnectorInstance.connector.id,
},
{
...mockFacebookConnectorInstance.metadata,
...mockFacebookConnectorInstance.instance.metadata,
id: mockFacebookConnectorInstance.connector.id,
},
{
...mockWechatConnectorInstance.metadata,
...mockWechatConnectorInstance.instance.metadata,
id: mockWechatConnectorInstance.connector.id,
},
{
...mockWechatNativeConnectorInstance.metadata,
...mockWechatNativeConnectorInstance.instance.metadata,
id: mockWechatNativeConnectorInstance.connector.id,
},
],

View file

@ -55,13 +55,20 @@ export default function wellKnownRoutes<T extends AnonymousRouter>(router: T, pr
Array<ConnectorMetadata & { id: string }>
>((previous, connectorTarget) => {
const connectors = connectorInstances.filter(
({ metadata: { target }, connector: { enabled } }) =>
target === connectorTarget && enabled
({
instance: {
metadata: { target },
},
connector: { enabled },
}) => target === connectorTarget && enabled
);
return [
...previous,
...connectors.map(({ metadata, connector: { id } }) => ({ ...metadata, id })),
...connectors.map(({ instance: { metadata }, connector: { id } }) => ({
...metadata,
id,
})),
];
}, []);