mirror of
https://github.com/logto-io/logto.git
synced 2025-03-31 22:51:25 -05:00
refactor(core): migrate social library to factory mode
This commit is contained in:
parent
1b998b7e62
commit
9bec890e6f
12 changed files with 155 additions and 122 deletions
|
@ -7,8 +7,8 @@ import type { InteractionResults } from 'oidc-provider';
|
|||
import { z } from 'zod';
|
||||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { getLogtoConnectorById } from '#src/libraries/connector.js';
|
||||
import { findUserByEmail, findUserByPhone } from '#src/queries/user.js';
|
||||
import type { ConnectorLibrary } from '#src/libraries/connector.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
|
||||
export type SocialUserInfoSession = {
|
||||
|
@ -16,42 +16,7 @@ export type SocialUserInfoSession = {
|
|||
userInfo: SocialUserInfo;
|
||||
};
|
||||
|
||||
const getConnector = async (connectorId: string) => {
|
||||
try {
|
||||
return await getLogtoConnectorById(connectorId);
|
||||
} catch (error: unknown) {
|
||||
// Throw a new error with status 422 when connector not found.
|
||||
if (error instanceof RequestError && error.code === 'entity.not_found') {
|
||||
throw new RequestError({
|
||||
code: 'session.invalid_connector_id',
|
||||
status: 422,
|
||||
connectorId,
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getUserInfoByAuthCode = async (
|
||||
connectorId: string,
|
||||
data: unknown,
|
||||
getConnectorSession?: GetSession
|
||||
): Promise<SocialUserInfo> => {
|
||||
const connector = await getConnector(connectorId);
|
||||
|
||||
assertThat(
|
||||
connector.type === ConnectorType.Social,
|
||||
new RequestError({
|
||||
code: 'session.invalid_connector_id',
|
||||
status: 422,
|
||||
connectorId,
|
||||
})
|
||||
);
|
||||
|
||||
return connector.getUserInfo(data, getConnectorSession);
|
||||
};
|
||||
|
||||
export const getUserInfoFromInteractionResult = async (
|
||||
const getUserInfoFromInteractionResult = async (
|
||||
connectorId: string,
|
||||
interactionResult: InteractionResults
|
||||
): Promise<SocialUserInfo> => {
|
||||
|
@ -74,31 +39,75 @@ export const getUserInfoFromInteractionResult = async (
|
|||
return result.socialUserInfo.userInfo;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find user by phone/email from social user info.
|
||||
* if both phone and email exist, take phone for priority.
|
||||
*
|
||||
* @param info SocialUserInfo
|
||||
* @returns null | [string, User] the first string indicating phone or email
|
||||
*/
|
||||
export const findSocialRelatedUser = async (
|
||||
info: SocialUserInfo
|
||||
): Promise<Nullable<[{ type: 'email' | 'phone'; value: string }, User]>> => {
|
||||
if (info.phone) {
|
||||
const user = await findUserByPhone(info.phone);
|
||||
export type SocialLibrary = ReturnType<typeof createSocialLibrary>;
|
||||
|
||||
if (user) {
|
||||
return [{ type: 'phone', value: info.phone }, user];
|
||||
export const createSocialLibrary = (queries: Queries, connectorLibrary: ConnectorLibrary) => {
|
||||
const { findUserByEmail, findUserByPhone } = queries.users;
|
||||
const { getLogtoConnectorById } = connectorLibrary;
|
||||
|
||||
const getConnector = async (connectorId: string) => {
|
||||
try {
|
||||
return await getLogtoConnectorById(connectorId);
|
||||
} catch (error: unknown) {
|
||||
// Throw a new error with status 422 when connector not found.
|
||||
if (error instanceof RequestError && error.code === 'entity.not_found') {
|
||||
throw new RequestError({
|
||||
code: 'session.invalid_connector_id',
|
||||
status: 422,
|
||||
connectorId,
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (info.email) {
|
||||
const user = await findUserByEmail(info.email);
|
||||
const getUserInfoByAuthCode = async (
|
||||
connectorId: string,
|
||||
data: unknown,
|
||||
getConnectorSession?: GetSession
|
||||
): Promise<SocialUserInfo> => {
|
||||
const connector = await getConnector(connectorId);
|
||||
|
||||
if (user) {
|
||||
return [{ type: 'email', value: info.email }, user];
|
||||
assertThat(
|
||||
connector.type === ConnectorType.Social,
|
||||
new RequestError({
|
||||
code: 'session.invalid_connector_id',
|
||||
status: 422,
|
||||
connectorId,
|
||||
})
|
||||
);
|
||||
|
||||
return connector.getUserInfo(data, getConnectorSession);
|
||||
};
|
||||
|
||||
/**
|
||||
* Find user by phone/email from social user info.
|
||||
* if both phone and email exist, take phone for priority.
|
||||
*
|
||||
* @param info SocialUserInfo
|
||||
* @returns null | [string, User] the first string indicating phone or email
|
||||
*/
|
||||
const findSocialRelatedUser = async (
|
||||
info: SocialUserInfo
|
||||
): Promise<Nullable<[{ type: 'email' | 'phone'; value: string }, User]>> => {
|
||||
if (info.phone) {
|
||||
const user = await findUserByPhone(info.phone);
|
||||
|
||||
if (user) {
|
||||
return [{ type: 'phone', value: info.phone }, user];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
if (info.email) {
|
||||
const user = await findUserByEmail(info.email);
|
||||
|
||||
if (user) {
|
||||
return [{ type: 'email', value: info.email }, user];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return { getUserInfoByAuthCode, getUserInfoFromInteractionResult, findSocialRelatedUser };
|
||||
};
|
||||
|
|
|
@ -6,7 +6,9 @@ import { mockSignInExperience } from '#src/__mocks__/sign-in-experience.js';
|
|||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import type koaAuditLog from '#src/middleware/koa-audit-log.js';
|
||||
import { createMockLogContext } from '#src/test-utils/koa-audit-log.js';
|
||||
import { createMockTenantWithInteraction } from '#src/test-utils/tenant.js';
|
||||
import { createMockProvider } from '#src/test-utils/oidc-provider.js';
|
||||
import { createMockTenantWithInteraction, MockTenant } from '#src/test-utils/tenant.js';
|
||||
import type { LogtoConnector } from '#src/utils/connectors/types.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
import { verificationPath, interactionPrefix } from './const.js';
|
||||
|
@ -33,21 +35,6 @@ const getLogtoConnectorByIdHelper = jest.fn(async (connectorId: string) => {
|
|||
};
|
||||
});
|
||||
|
||||
await mockEsmWithActual('#src/libraries/connector.js', () => ({
|
||||
getLogtoConnectorById: jest.fn(async (connectorId: string) => {
|
||||
const connector = await getLogtoConnectorByIdHelper(connectorId);
|
||||
|
||||
if (connector.type !== ConnectorType.Social) {
|
||||
throw new RequestError({
|
||||
code: 'entity.not_found',
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
|
||||
return connector;
|
||||
}),
|
||||
}));
|
||||
|
||||
await mockEsmWithActual('#src/libraries/sign-in-experience/index.js', () => ({
|
||||
getSignInExperienceForApplication: jest.fn().mockResolvedValue(mockSignInExperience),
|
||||
}));
|
||||
|
@ -119,7 +106,27 @@ describe('interaction routes', () => {
|
|||
|
||||
const sessionRequest = createRequester({
|
||||
anonymousRoutes: interactionRoutes,
|
||||
tenantContext: createMockTenantWithInteraction(jest.fn().mockResolvedValue(baseProviderMock)),
|
||||
tenantContext: new MockTenant(
|
||||
createMockProvider(jest.fn().mockResolvedValue(baseProviderMock)),
|
||||
undefined,
|
||||
{
|
||||
connectors: {
|
||||
getLogtoConnectorById: async (connectorId: string) => {
|
||||
const connector = await getLogtoConnectorByIdHelper(connectorId);
|
||||
|
||||
if (connector.type !== ConnectorType.Social) {
|
||||
throw new RequestError({
|
||||
code: 'entity.not_found',
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
return connector as LogtoConnector;
|
||||
},
|
||||
},
|
||||
}
|
||||
),
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
@ -289,7 +289,7 @@ export default function interactionRoutes<T extends AnonymousRouter>(
|
|||
const log = createLog(`Interaction.${event}.Submit`);
|
||||
log.append({ interaction: interactionStorage });
|
||||
|
||||
const accountVerifiedInteraction = await verifyIdentifier(ctx, provider, interactionStorage);
|
||||
const accountVerifiedInteraction = await verifyIdentifier(ctx, tenant, interactionStorage);
|
||||
|
||||
const verifiedInteraction = await verifyProfile(accountVerifiedInteraction);
|
||||
|
||||
|
@ -316,7 +316,7 @@ export default function interactionRoutes<T extends AnonymousRouter>(
|
|||
|
||||
log.append(payload);
|
||||
|
||||
const redirectTo = await createSocialAuthorizationUrl(ctx, provider, payload);
|
||||
const redirectTo = await createSocialAuthorizationUrl(ctx, tenant, payload);
|
||||
|
||||
ctx.body = { redirectTo };
|
||||
|
||||
|
|
|
@ -4,14 +4,14 @@ import { createMockUtils } from '@logto/shared/esm';
|
|||
import type { WithLogContext } from '#src/middleware/koa-audit-log.js';
|
||||
import createMockContext from '#src/test-utils/jest-koa-mocks/create-mock-context.js';
|
||||
import { createMockLogContext } from '#src/test-utils/koa-audit-log.js';
|
||||
import { createMockProvider } from '#src/test-utils/oidc-provider.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
const { mockEsm } = createMockUtils(jest);
|
||||
|
||||
const { getUserInfoByAuthCode } = mockEsm('#src/libraries/social.js', () => ({
|
||||
getUserInfoByAuthCode: jest.fn().mockResolvedValue({ id: 'foo' }),
|
||||
}));
|
||||
const getUserInfoByAuthCode = jest.fn().mockResolvedValue({ id: 'foo' });
|
||||
|
||||
const tenant = new MockTenant(undefined, undefined, { socials: { getUserInfoByAuthCode } });
|
||||
|
||||
mockEsm('#src/libraries/connector.js', () => ({
|
||||
getLogtoConnectorById: jest.fn().mockResolvedValue({
|
||||
|
@ -27,7 +27,6 @@ const { verifySocialIdentity } = await import('./social-verification.js');
|
|||
|
||||
describe('social-verification', () => {
|
||||
it('verifySocialIdentity', async () => {
|
||||
const provider = createMockProvider();
|
||||
// @ts-expect-error test mock context
|
||||
const ctx: WithLogContext = {
|
||||
...createMockContext(),
|
||||
|
@ -35,7 +34,7 @@ describe('social-verification', () => {
|
|||
};
|
||||
const connectorId = 'connector';
|
||||
const connectorData = { authCode: 'code' };
|
||||
const userInfo = await verifySocialIdentity({ connectorId, connectorData }, ctx, provider);
|
||||
const userInfo = await verifySocialIdentity({ connectorId, connectorData }, ctx, tenant);
|
||||
|
||||
expect(getUserInfoByAuthCode).toBeCalledWith(connectorId, connectorData, expect.anything());
|
||||
expect(userInfo).toEqual({ id: 'foo' });
|
||||
|
|
|
@ -1,24 +1,26 @@
|
|||
import type { ConnectorSession, SocialUserInfo } from '@logto/connector-kit';
|
||||
import type { SocialConnectorPayload } from '@logto/schemas';
|
||||
import { ConnectorType } from '@logto/schemas';
|
||||
import type Provider from 'oidc-provider';
|
||||
|
||||
import { getLogtoConnectorById } from '#src/libraries/connector.js';
|
||||
import { getUserInfoByAuthCode } from '#src/libraries/social.js';
|
||||
import type { WithLogContext } from '#src/middleware/koa-audit-log.js';
|
||||
import {
|
||||
getConnectorSessionResult,
|
||||
assignConnectorSessionResult,
|
||||
} from '#src/routes/interaction/utils/interaction.js';
|
||||
import type TenantContext from '#src/tenants/TenantContext.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
|
||||
import type { SocialAuthorizationUrlPayload } from '../types/index.js';
|
||||
|
||||
export const createSocialAuthorizationUrl = async (
|
||||
ctx: WithLogContext,
|
||||
provider: Provider,
|
||||
{ provider, libraries }: TenantContext,
|
||||
payload: SocialAuthorizationUrlPayload
|
||||
) => {
|
||||
const {
|
||||
connectors: { getLogtoConnectorById },
|
||||
} = libraries;
|
||||
|
||||
const { connectorId, state, redirectUri } = payload;
|
||||
assertThat(state && redirectUri, 'session.insufficient_info');
|
||||
|
||||
|
@ -40,8 +42,12 @@ export const createSocialAuthorizationUrl = async (
|
|||
export const verifySocialIdentity = async (
|
||||
{ connectorId, connectorData }: SocialConnectorPayload,
|
||||
ctx: WithLogContext,
|
||||
provider: Provider
|
||||
{ provider, libraries }: TenantContext
|
||||
): Promise<SocialUserInfo> => {
|
||||
const {
|
||||
socials: { getUserInfoByAuthCode },
|
||||
} = libraries;
|
||||
|
||||
const log = ctx.createLog('Interaction.SignIn.Identifier.Social.Submit');
|
||||
log.append({ connectorId, connectorData });
|
||||
|
||||
|
|
|
@ -163,7 +163,7 @@ describe('identifier verification', () => {
|
|||
interactionStorage
|
||||
);
|
||||
|
||||
expect(verifySocialIdentity).toBeCalledWith(identifier, baseCtx, tenant.provider);
|
||||
expect(verifySocialIdentity).toBeCalledWith(identifier, baseCtx, tenant);
|
||||
expect(findUserByIdentifier).not.toBeCalled();
|
||||
|
||||
expect(result).toEqual({
|
||||
|
|
|
@ -68,9 +68,9 @@ const verifyVerificationCodeIdentifier = async (
|
|||
const verifySocialIdentifier = async (
|
||||
identifier: SocialConnectorPayload,
|
||||
ctx: WithLogContext,
|
||||
{ provider }: TenantContext
|
||||
tenant: TenantContext
|
||||
): Promise<SocialIdentifier> => {
|
||||
const userInfo = await verifySocialIdentity(identifier, ctx, provider);
|
||||
const userInfo = await verifySocialIdentity(identifier, ctx, tenant);
|
||||
|
||||
return { key: 'social', connectorId: identifier.connectorId, userInfo };
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@ import { InteractionEvent } from '@logto/schemas';
|
|||
import { createMockUtils, pickDefault } from '@logto/shared/esm';
|
||||
|
||||
import { createMockLogContext } from '#src/test-utils/koa-audit-log.js';
|
||||
import { createMockProvider } from '#src/test-utils/oidc-provider.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
|
||||
|
||||
import type { SignInInteractionResult } from '../types/index.js';
|
||||
|
@ -22,7 +22,7 @@ describe('verifyIdentifier', () => {
|
|||
...createContextWithRouteParameters(),
|
||||
...createMockLogContext(),
|
||||
};
|
||||
const provider = createMockProvider();
|
||||
const tenant = new MockTenant();
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
@ -33,7 +33,7 @@ describe('verifyIdentifier', () => {
|
|||
event: InteractionEvent.Register,
|
||||
};
|
||||
|
||||
const result = await verifyIdentifier(ctx, provider, interactionRecord);
|
||||
const result = await verifyIdentifier(ctx, tenant, interactionRecord);
|
||||
|
||||
expect(result).toBe(interactionRecord);
|
||||
expect(verifyUserAccount).not.toBeCalled();
|
||||
|
@ -53,10 +53,10 @@ describe('verifyIdentifier', () => {
|
|||
|
||||
verifyUserAccount.mockResolvedValue(verifiedRecord);
|
||||
|
||||
const result = await verifyIdentifier(ctx, provider, interactionRecord);
|
||||
const result = await verifyIdentifier(ctx, tenant, interactionRecord);
|
||||
|
||||
expect(result).toBe(verifiedRecord);
|
||||
expect(verifyUserAccount).toBeCalledWith(interactionRecord);
|
||||
expect(storeInteractionResult).toBeCalledWith(verifiedRecord, ctx, provider);
|
||||
expect(verifyUserAccount).toBeCalledWith(interactionRecord, tenant.libraries.socials);
|
||||
expect(storeInteractionResult).toBeCalledWith(verifiedRecord, ctx, tenant.provider);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { InteractionEvent } from '@logto/schemas';
|
||||
import type { Context } from 'koa';
|
||||
import type Provider from 'oidc-provider';
|
||||
|
||||
import type TenantContext from '#src/tenants/TenantContext.js';
|
||||
|
||||
import type {
|
||||
RegisterInteractionResult,
|
||||
|
@ -18,7 +19,7 @@ type InteractionResult =
|
|||
|
||||
export default async function verifyIdentifier(
|
||||
ctx: Context,
|
||||
provider: Provider,
|
||||
{ provider, libraries }: TenantContext,
|
||||
interactionRecord: InteractionResult
|
||||
): Promise<RegisterInteractionResult | AccountVerifiedInteractionResult> {
|
||||
if (interactionRecord.event === InteractionEvent.Register) {
|
||||
|
@ -26,7 +27,10 @@ export default async function verifyIdentifier(
|
|||
}
|
||||
|
||||
// Verify the user account and assign the verified result to the interaction record
|
||||
const accountVerifiedInteractionResult = await verifyUserAccount(interactionRecord);
|
||||
const accountVerifiedInteractionResult = await verifyUserAccount(
|
||||
interactionRecord,
|
||||
libraries.socials
|
||||
);
|
||||
await storeInteractionResult(accountVerifiedInteractionResult, ctx, provider);
|
||||
|
||||
return accountVerifiedInteractionResult;
|
||||
|
|
|
@ -2,6 +2,7 @@ import { InteractionEvent } from '@logto/schemas';
|
|||
import { createMockUtils, pickDefault } from '@logto/shared/esm';
|
||||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import type { SocialLibrary } from '#src/libraries/social.js';
|
||||
|
||||
import type { SignInInteractionResult } from '../types/index.js';
|
||||
|
||||
|
@ -10,9 +11,10 @@ const { mockEsm, mockEsmDefault } = createMockUtils(jest);
|
|||
|
||||
const findUserByIdentifier = mockEsmDefault('../utils/find-user-by-identifier.js', () => jest.fn());
|
||||
|
||||
mockEsm('#src/libraries/social.js', () => ({
|
||||
// @ts-expect-error
|
||||
const socialLibrary: SocialLibrary = {
|
||||
findSocialRelatedUser: jest.fn().mockResolvedValue(null),
|
||||
}));
|
||||
};
|
||||
|
||||
const verifyUserAccount = await pickDefault(import('./user-identity-verification.js'));
|
||||
|
||||
|
@ -28,7 +30,7 @@ describe('verifyUserAccount', () => {
|
|||
event: InteractionEvent.SignIn,
|
||||
};
|
||||
|
||||
await expect(verifyUserAccount(interaction)).rejects.toMatchError(
|
||||
await expect(verifyUserAccount(interaction, socialLibrary)).rejects.toMatchError(
|
||||
new RequestError({ code: 'session.identifier_not_found', status: 404 })
|
||||
);
|
||||
});
|
||||
|
@ -39,7 +41,7 @@ describe('verifyUserAccount', () => {
|
|||
identifiers: [{ key: 'accountId', value: 'foo' }],
|
||||
};
|
||||
|
||||
const result = await verifyUserAccount(interaction);
|
||||
const result = await verifyUserAccount(interaction, socialLibrary);
|
||||
|
||||
expect(result).toEqual({ ...interaction, accountId: 'foo' });
|
||||
});
|
||||
|
@ -52,7 +54,7 @@ describe('verifyUserAccount', () => {
|
|||
identifiers: [{ key: 'emailVerified', value: 'email' }],
|
||||
};
|
||||
|
||||
const result = await verifyUserAccount(interaction);
|
||||
const result = await verifyUserAccount(interaction, socialLibrary);
|
||||
expect(findUserByIdentifierMock).toBeCalledWith({ email: 'email' });
|
||||
|
||||
expect(result).toEqual({ ...interaction, accountId: 'foo' });
|
||||
|
@ -66,7 +68,7 @@ describe('verifyUserAccount', () => {
|
|||
identifiers: [{ key: 'phoneVerified', value: '123456' }],
|
||||
};
|
||||
|
||||
const result = await verifyUserAccount(interaction);
|
||||
const result = await verifyUserAccount(interaction, socialLibrary);
|
||||
expect(findUserByIdentifierMock).toBeCalledWith({ phone: '123456' });
|
||||
|
||||
expect(result).toEqual({ ...interaction, accountId: 'foo' });
|
||||
|
@ -80,7 +82,7 @@ describe('verifyUserAccount', () => {
|
|||
identifiers: [{ key: 'social', connectorId: 'connectorId', userInfo: { id: 'foo' } }],
|
||||
};
|
||||
|
||||
const result = await verifyUserAccount(interaction);
|
||||
const result = await verifyUserAccount(interaction, socialLibrary);
|
||||
expect(findUserByIdentifierMock).toBeCalledWith({
|
||||
connectorId: 'connectorId',
|
||||
userInfo: { id: 'foo' },
|
||||
|
@ -97,7 +99,7 @@ describe('verifyUserAccount', () => {
|
|||
identifiers: [{ key: 'social', connectorId: 'connectorId', userInfo: { id: 'foo' } }],
|
||||
};
|
||||
|
||||
await expect(verifyUserAccount(interaction)).rejects.toMatchError(
|
||||
await expect(verifyUserAccount(interaction, socialLibrary)).rejects.toMatchError(
|
||||
new RequestError(
|
||||
{
|
||||
code: 'user.identity_not_exist',
|
||||
|
@ -124,7 +126,7 @@ describe('verifyUserAccount', () => {
|
|||
],
|
||||
};
|
||||
|
||||
const result = await verifyUserAccount(interaction);
|
||||
const result = await verifyUserAccount(interaction, socialLibrary);
|
||||
expect(findUserByIdentifierMock).toBeCalledWith({ email: 'email' });
|
||||
|
||||
expect(result).toEqual({ ...interaction, accountId: 'foo' });
|
||||
|
@ -141,7 +143,7 @@ describe('verifyUserAccount', () => {
|
|||
],
|
||||
};
|
||||
|
||||
await expect(verifyUserAccount(interaction)).rejects.toMatchError(
|
||||
await expect(verifyUserAccount(interaction, socialLibrary)).rejects.toMatchError(
|
||||
new RequestError({ code: 'user.user_not_exist', status: 404 }, { identifier: 'email' })
|
||||
);
|
||||
|
||||
|
@ -161,7 +163,7 @@ describe('verifyUserAccount', () => {
|
|||
],
|
||||
};
|
||||
|
||||
await expect(verifyUserAccount(interaction)).rejects.toMatchError(
|
||||
await expect(verifyUserAccount(interaction, socialLibrary)).rejects.toMatchError(
|
||||
new RequestError({ code: 'user.suspended', status: 401 })
|
||||
);
|
||||
|
||||
|
@ -182,7 +184,7 @@ describe('verifyUserAccount', () => {
|
|||
],
|
||||
};
|
||||
|
||||
await expect(verifyUserAccount(interaction)).rejects.toMatchError(
|
||||
await expect(verifyUserAccount(interaction, socialLibrary)).rejects.toMatchError(
|
||||
new RequestError('session.verification_failed')
|
||||
);
|
||||
expect(findUserByIdentifierMock).toHaveBeenNthCalledWith(1, { email: 'email' });
|
||||
|
@ -198,7 +200,7 @@ describe('verifyUserAccount', () => {
|
|||
identifiers: [{ key: 'emailVerified', value: 'email' }],
|
||||
};
|
||||
|
||||
await expect(verifyUserAccount(interaction)).rejects.toMatchError(
|
||||
await expect(verifyUserAccount(interaction, socialLibrary)).rejects.toMatchError(
|
||||
new RequestError('session.verification_failed')
|
||||
);
|
||||
expect(findUserByIdentifierMock).toBeCalledWith({ email: 'email' });
|
||||
|
@ -220,7 +222,7 @@ describe('verifyUserAccount', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const result = await verifyUserAccount(interaction);
|
||||
const result = await verifyUserAccount(interaction, socialLibrary);
|
||||
expect(findUserByIdentifierMock).toBeCalledWith({ email: 'email' });
|
||||
|
||||
expect(result).toEqual({ ...interaction, accountId: 'foo' });
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { deduplicate } from '@silverhand/essentials';
|
||||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { findSocialRelatedUser } from '#src/libraries/social.js';
|
||||
import type { SocialLibrary } from '#src/libraries/social.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import { maskUserInfo } from '#src/utils/format.js';
|
||||
|
||||
|
@ -36,13 +36,16 @@ const identifyUserByVerifiedEmailOrPhone = async (
|
|||
return id;
|
||||
};
|
||||
|
||||
const identifyUserBySocialIdentifier = async (identifier: SocialIdentifier) => {
|
||||
const identifyUserBySocialIdentifier = async (
|
||||
identifier: SocialIdentifier,
|
||||
socialLibrary: SocialLibrary
|
||||
) => {
|
||||
const { connectorId, userInfo } = identifier;
|
||||
|
||||
const user = await findUserByIdentifier({ connectorId, userInfo });
|
||||
|
||||
if (!user) {
|
||||
const relatedInfo = await findSocialRelatedUser(userInfo);
|
||||
const relatedInfo = await socialLibrary.findSocialRelatedUser(userInfo);
|
||||
|
||||
throw new RequestError(
|
||||
{
|
||||
|
@ -60,9 +63,9 @@ const identifyUserBySocialIdentifier = async (identifier: SocialIdentifier) => {
|
|||
return id;
|
||||
};
|
||||
|
||||
const identifyUser = async (identifier: Identifier) => {
|
||||
const identifyUser = async (identifier: Identifier, socialLibrary: SocialLibrary) => {
|
||||
if (identifier.key === 'social') {
|
||||
return identifyUserBySocialIdentifier(identifier);
|
||||
return identifyUserBySocialIdentifier(identifier, socialLibrary);
|
||||
}
|
||||
|
||||
if (identifier.key === 'accountId') {
|
||||
|
@ -73,7 +76,8 @@ const identifyUser = async (identifier: Identifier) => {
|
|||
};
|
||||
|
||||
export default async function verifyUserAccount(
|
||||
interaction: SignInInteractionResult | ForgotPasswordInteractionResult
|
||||
interaction: SignInInteractionResult | ForgotPasswordInteractionResult,
|
||||
socialLibrary: SocialLibrary
|
||||
): Promise<AccountVerifiedInteractionResult> {
|
||||
const { identifiers = [], accountId, profile } = interaction;
|
||||
|
||||
|
@ -91,7 +95,7 @@ export default async function verifyUserAccount(
|
|||
|
||||
// Verify authIdentifiers
|
||||
const accountIds = await Promise.all(
|
||||
authIdentifiers.map(async (identifier) => identifyUser(identifier))
|
||||
authIdentifiers.map(async (identifier) => identifyUser(identifier, socialLibrary))
|
||||
);
|
||||
const deduplicateAccountIds = deduplicate(accountIds);
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { createHookLibrary } from '#src/libraries/hook.js';
|
|||
import { createPhraseLibrary } from '#src/libraries/phrase.js';
|
||||
import { createResourceLibrary } from '#src/libraries/resource.js';
|
||||
import { createSignInExperienceLibrary } from '#src/libraries/sign-in-experience/index.js';
|
||||
import { createSocialLibrary } from '#src/libraries/social.js';
|
||||
import { createUserLibrary } from '#src/libraries/user.js';
|
||||
import type { ModelRouters } from '#src/model-routers/index.js';
|
||||
|
||||
|
@ -15,6 +16,7 @@ export default class Libraries {
|
|||
phrases = createPhraseLibrary(this.queries);
|
||||
resources = createResourceLibrary(this.queries);
|
||||
hooks = createHookLibrary(this.queries, this.modelRouters);
|
||||
socials = createSocialLibrary(this.queries, this.connectors);
|
||||
|
||||
constructor(private readonly queries: Queries, private readonly modelRouters: ModelRouters) {}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue