mirror of
https://github.com/logto-io/logto.git
synced 2025-01-20 21:32:31 -05:00
feat(core): add socialConnectors details for get sign-in-settings (#804)
* feat(core,ui): add social connector info to get-settings api * refactor(connectors): rename api
This commit is contained in:
parent
357fb8dc40
commit
7a922cbd33
9 changed files with 116 additions and 32 deletions
|
@ -2,7 +2,15 @@
|
|||
import { User } from '@logto/schemas';
|
||||
import { Provider } from 'oidc-provider';
|
||||
|
||||
import { mockSignInExperience, mockUser } from '@/__mocks__';
|
||||
import {
|
||||
mockSignInExperience,
|
||||
mockUser,
|
||||
mockAliyunDmConnectorInstance,
|
||||
mockAliyunSmsConnectorInstance,
|
||||
mockFacebookConnectorInstance,
|
||||
mockGithubConnectorInstance,
|
||||
mockGoogleConnectorInstance,
|
||||
} from '@/__mocks__';
|
||||
import { ConnectorType } from '@/connectors/types';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import * as signInExperienceQueries from '@/queries/sign-in-experience';
|
||||
|
@ -99,6 +107,13 @@ const getConnectorInstanceById = jest.fn(async (connectorId: string) => {
|
|||
|
||||
return { connector, metadata, getAuthorizationUri };
|
||||
});
|
||||
const getConnectorInstances = jest.fn(async () => [
|
||||
mockAliyunDmConnectorInstance,
|
||||
mockAliyunSmsConnectorInstance,
|
||||
mockFacebookConnectorInstance,
|
||||
mockGithubConnectorInstance,
|
||||
mockGoogleConnectorInstance,
|
||||
]);
|
||||
jest.mock('@/connectors', () => ({
|
||||
getSocialConnectorInstanceById: async (connectorId: string) => {
|
||||
const connectorInstance = await getConnectorInstanceById(connectorId);
|
||||
|
@ -112,6 +127,7 @@ jest.mock('@/connectors', () => ({
|
|||
|
||||
return connectorInstance;
|
||||
},
|
||||
getConnectorInstances: async () => getConnectorInstances(),
|
||||
}));
|
||||
|
||||
const grantSave = jest.fn(async () => 'finalGrantId');
|
||||
|
@ -431,11 +447,11 @@ describe('sessionRoutes', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('POST /session/bind-social-related-user', () => {
|
||||
describe('POST /session/sign-in/bind-social-related-user', () => {
|
||||
it('throw if session is not authorized', async () => {
|
||||
await expect(
|
||||
sessionRequest
|
||||
.post('/session/bind-social-related-user')
|
||||
.post('/session/sign-in/bind-social-related-user')
|
||||
.send({ connectorId: 'connectorId' })
|
||||
).resolves.toHaveProperty('statusCode', 400);
|
||||
});
|
||||
|
@ -445,7 +461,7 @@ describe('sessionRoutes', () => {
|
|||
});
|
||||
await expect(
|
||||
sessionRequest
|
||||
.post('/session/bind-social-related-user')
|
||||
.post('/session/sign-in/bind-social-related-user')
|
||||
.send({ connectorId: 'connectorId' })
|
||||
).resolves.toHaveProperty('statusCode', 400);
|
||||
});
|
||||
|
@ -459,7 +475,7 @@ describe('sessionRoutes', () => {
|
|||
},
|
||||
},
|
||||
});
|
||||
const response = await sessionRequest.post('/session/bind-social-related-user').send({
|
||||
const response = await sessionRequest.post('/session/sign-in/bind-social-related-user').send({
|
||||
connectorId: 'connectorId',
|
||||
});
|
||||
expect(response.statusCode).toEqual(200);
|
||||
|
@ -890,15 +906,29 @@ describe('sessionRoutes', () => {
|
|||
});
|
||||
|
||||
describe('GET /sign-in-settings', () => {
|
||||
const signInExperienceQuerySpon = jest
|
||||
const signInExperienceQuerySpyOn = jest
|
||||
.spyOn(signInExperienceQueries, 'findDefaultSignInExperience')
|
||||
.mockResolvedValue(mockSignInExperience);
|
||||
|
||||
it('should call findDefaultSignInExperience', async () => {
|
||||
it('should return github and facebook connector instances', async () => {
|
||||
const response = await sessionRequest.get('/sign-in-settings');
|
||||
expect(signInExperienceQuerySpon).toHaveBeenCalledTimes(1);
|
||||
expect(signInExperienceQuerySpyOn).toHaveBeenCalledTimes(1);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(mockSignInExperience);
|
||||
expect(response.body).toMatchObject(
|
||||
expect.objectContaining({
|
||||
...mockSignInExperience,
|
||||
socialConnectors: [
|
||||
{
|
||||
...mockGithubConnectorInstance.metadata,
|
||||
id: mockGithubConnectorInstance.connector.id,
|
||||
},
|
||||
{
|
||||
...mockFacebookConnectorInstance.metadata,
|
||||
id: mockFacebookConnectorInstance.connector.id,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import pick from 'lodash.pick';
|
|||
import { Provider } from 'oidc-provider';
|
||||
import { object, string } from 'zod';
|
||||
|
||||
import { getSocialConnectorInstanceById } from '@/connectors';
|
||||
import { getConnectorInstances, getSocialConnectorInstanceById } from '@/connectors';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { createPasscode, sendPasscode, verifyPasscode } from '@/lib/passcode';
|
||||
import { assignInteractionResults, saveUserFirstConsentedAppId } from '@/lib/session';
|
||||
|
@ -259,7 +259,7 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
|||
);
|
||||
|
||||
router.post(
|
||||
'/session/bind-social-related-user',
|
||||
'/session/sign-in/bind-social-related-user',
|
||||
koaGuard({
|
||||
body: object({ connectorId: string() }),
|
||||
}),
|
||||
|
@ -577,9 +577,15 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
|||
});
|
||||
|
||||
router.get('/sign-in-settings', async (ctx, next) => {
|
||||
// TODO: Social Connector Details
|
||||
const signInExperience = await findDefaultSignInExperience();
|
||||
ctx.body = signInExperience;
|
||||
const connectorInstances = await getConnectorInstances();
|
||||
const instanceMap = new Map(
|
||||
connectorInstances.map((instance) => [instance.connector.id, instance])
|
||||
);
|
||||
const socialConnectors = signInExperience.socialSignInConnectorIds.map((id) => {
|
||||
return { ...instanceMap.get(id)?.metadata, id };
|
||||
});
|
||||
ctx.body = { ...signInExperience, socialConnectors };
|
||||
|
||||
return next();
|
||||
});
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
import { Language } from '@logto/phrases';
|
||||
import { BrandingStyle, SignInExperience, SignInMethodState } from '@logto/schemas';
|
||||
import {
|
||||
BrandingStyle,
|
||||
ConnectorPlatform,
|
||||
ConnectorType,
|
||||
SignInExperience,
|
||||
SignInMethodState,
|
||||
} from '@logto/schemas';
|
||||
|
||||
import { SignInExperienceSettings } from '@/types';
|
||||
|
||||
|
@ -7,44 +13,86 @@ export const appLogo = 'https://avatars.githubusercontent.com/u/88327661?s=200&v
|
|||
export const appHeadline = 'Build user identity in a modern way';
|
||||
export const socialConnectors = [
|
||||
{
|
||||
id: 'BE8QXN0VsrOH7xdWFDJZ9',
|
||||
target: 'github',
|
||||
platform: ConnectorPlatform.Web,
|
||||
type: ConnectorType.Social,
|
||||
logo: 'https://user-images.githubusercontent.com/5717882/156983224-7ea0296b-38fa-419d-9515-67e8a9612e09.png',
|
||||
name: {
|
||||
en: 'Sign in with GitHub',
|
||||
'zh-CN': '使用 GitHub 登录',
|
||||
},
|
||||
description: {
|
||||
en: 'Sign in with GitHub',
|
||||
'zh-CN': '使用 GitHub 登录',
|
||||
},
|
||||
readme: '',
|
||||
configTemplate: '',
|
||||
},
|
||||
{
|
||||
id: '24yt_xIUl5btN4UwvFokt',
|
||||
target: 'alipay',
|
||||
platform: ConnectorPlatform.Web,
|
||||
type: ConnectorType.Social,
|
||||
logo: 'https://user-images.githubusercontent.com/5717882/156983224-7ea0296b-38fa-419d-9515-67e8a9612e09.png',
|
||||
name: {
|
||||
en: 'Sign in with Alipay',
|
||||
'zh-CN': '使用 Alipay 登录',
|
||||
},
|
||||
description: {
|
||||
en: 'Sign in with Alipay',
|
||||
'zh-CN': '使用 Alipay 登录',
|
||||
},
|
||||
readme: '',
|
||||
configTemplate: '',
|
||||
},
|
||||
{
|
||||
id: 'E5kb2gdq769qOEYaLg1V5',
|
||||
target: 'wechat',
|
||||
platform: ConnectorPlatform.Web,
|
||||
type: ConnectorType.Social,
|
||||
logo: 'https://user-images.githubusercontent.com/5717882/156983224-7ea0296b-38fa-419d-9515-67e8a9612e09.png',
|
||||
name: {
|
||||
en: 'Sign in with WeChat',
|
||||
'zh-CN': '使用 WeChat 登录',
|
||||
},
|
||||
description: {
|
||||
en: 'Sign in with WeChat',
|
||||
'zh-CN': '使用 WeChat 登录',
|
||||
},
|
||||
readme: '',
|
||||
configTemplate: '',
|
||||
},
|
||||
{
|
||||
id: 'xY2YZEweMFPKxphngGHhy',
|
||||
target: 'google',
|
||||
platform: ConnectorPlatform.Web,
|
||||
type: ConnectorType.Social,
|
||||
logo: 'https://user-images.githubusercontent.com/5717882/156983224-7ea0296b-38fa-419d-9515-67e8a9612e09.png',
|
||||
name: {
|
||||
en: 'Sign in with Google',
|
||||
'zh-CN': '使用 Google 登录',
|
||||
},
|
||||
description: { en: 'Sign in with Google', 'zh-CN': '使用 Google 登录' },
|
||||
readme: '',
|
||||
configTemplate: '',
|
||||
},
|
||||
{
|
||||
id: 'lcXT4o2GSjbV9kg2shZC7',
|
||||
target: 'facebook',
|
||||
platform: ConnectorPlatform.Web,
|
||||
type: ConnectorType.Social,
|
||||
logo: 'https://user-images.githubusercontent.com/5717882/156983224-7ea0296b-38fa-419d-9515-67e8a9612e09.png',
|
||||
name: {
|
||||
en: 'Sign in with Meta',
|
||||
'zh-CN': '使用 Meta 登录',
|
||||
},
|
||||
description: {
|
||||
en: 'Sign in with Meta',
|
||||
'zh-CN': '使用 Meta 登录',
|
||||
},
|
||||
readme: '',
|
||||
configTemplate: '',
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -73,7 +121,7 @@ export const mockSignInExperience: SignInExperience = {
|
|||
sms: SignInMethodState.Secondary,
|
||||
social: SignInMethodState.Secondary,
|
||||
},
|
||||
socialSignInConnectorIds: ['github', 'facebook'],
|
||||
socialSignInConnectorIds: ['BE8QXN0VsrOH7xdWFDJZ9', 'lcXT4o2GSjbV9kg2shZC7'],
|
||||
};
|
||||
|
||||
export const mockSignInExperienceSettings: SignInExperienceSettings = {
|
||||
|
|
|
@ -6,6 +6,6 @@
|
|||
import { SignInExperience } from '@logto/schemas';
|
||||
import ky from 'ky';
|
||||
|
||||
export const getSignInExperience = async () => {
|
||||
return ky.get('/api/sign-in-settings').json<SignInExperience>();
|
||||
export const getSignInExperience = async <T extends SignInExperience>(): Promise<T> => {
|
||||
return ky.get('/api/sign-in-settings').json<T>();
|
||||
};
|
||||
|
|
|
@ -32,11 +32,11 @@ const SecondarySocialSignIn = ({ className }: Props) => {
|
|||
<div className={classNames(styles.socialIconList, className)}>
|
||||
{displayConnectors.map((connector) => (
|
||||
<SocialIconButton
|
||||
key={connector.target}
|
||||
key={connector.id}
|
||||
className={styles.socialButton}
|
||||
connector={connector}
|
||||
onClick={() => {
|
||||
void invokeSocialSignIn(connector.target);
|
||||
void invokeSocialSignIn(connector.id);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
|
|
@ -90,10 +90,8 @@ const useSocial = () => {
|
|||
// Filter native supported social connectors
|
||||
const socialConnectors = useMemo(
|
||||
() =>
|
||||
(experienceSettings?.socialConnectors ?? []).filter(({ target }) => {
|
||||
return (
|
||||
!isNativeWebview() || getLogtoNativeSdk()?.supportedSocialConnectorIds.includes(target)
|
||||
);
|
||||
(experienceSettings?.socialConnectors ?? []).filter(({ id }) => {
|
||||
return !isNativeWebview() || getLogtoNativeSdk()?.supportedSocialConnectorIds.includes(id);
|
||||
}),
|
||||
[experienceSettings?.socialConnectors]
|
||||
);
|
||||
|
|
|
@ -19,9 +19,7 @@ const Callback = () => {
|
|||
const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' });
|
||||
|
||||
const connectorLabel = useMemo(() => {
|
||||
const connector = experienceSettings?.socialConnectors.find(
|
||||
({ target }) => target === connectorId
|
||||
);
|
||||
const connector = experienceSettings?.socialConnectors.find(({ id }) => id === connectorId);
|
||||
|
||||
if (connector) {
|
||||
return (
|
||||
|
|
|
@ -8,7 +8,9 @@ export enum SearchParameters {
|
|||
bindWithSocial = 'bw',
|
||||
}
|
||||
|
||||
type ConnectorData = Pick<ConnectorMetadata, 'target' | 'logo' | 'name'>;
|
||||
export interface ConnectorData extends ConnectorMetadata {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type SignInExperienceSettings = {
|
||||
branding: Branding;
|
||||
|
|
|
@ -3,11 +3,10 @@
|
|||
* TODO: Remove this once we have a better way to get the sign in experience through SSR
|
||||
*/
|
||||
|
||||
import { SignInMethods } from '@logto/schemas';
|
||||
import { SignInMethods, SignInExperience } from '@logto/schemas';
|
||||
|
||||
import { socialConnectors } from '@/__mocks__/logto';
|
||||
import { getSignInExperience } from '@/apis/settings';
|
||||
import { SignInMethod, SignInExperienceSettings } from '@/types';
|
||||
import { ConnectorData, SignInMethod, SignInExperienceSettings } from '@/types';
|
||||
|
||||
const getPrimarySignInMethod = (signInMethods: SignInMethods) => {
|
||||
for (const [key, value] of Object.entries(signInMethods)) {
|
||||
|
@ -28,8 +27,11 @@ const getSecondarySignInMethods = (signInMethods: SignInMethods) =>
|
|||
return methods;
|
||||
}, []);
|
||||
|
||||
const getSignInExperienceSettings = async (): Promise<SignInExperienceSettings> => {
|
||||
const { branding, languageInfo, termsOfUse, signInMethods } = await getSignInExperience();
|
||||
const getSignInExperienceSettings = async <
|
||||
T extends SignInExperience & { socialConnectors: ConnectorData[] }
|
||||
>(): Promise<SignInExperienceSettings> => {
|
||||
const { branding, languageInfo, termsOfUse, signInMethods, socialConnectors } =
|
||||
await getSignInExperience<T>();
|
||||
|
||||
return {
|
||||
branding,
|
||||
|
|
Loading…
Add table
Reference in a new issue