mirror of
https://github.com/logto-io/logto.git
synced 2025-03-24 22:41:28 -05:00
refactor(ui): replace social flow api (#2751)
This commit is contained in:
parent
e02f7e5ac2
commit
be2c65dc0c
13 changed files with 140 additions and 174 deletions
|
@ -2,12 +2,21 @@ import { maskUserInfo } from './format.js';
|
||||||
|
|
||||||
describe('maskUserInfo', () => {
|
describe('maskUserInfo', () => {
|
||||||
it('phone', () => {
|
it('phone', () => {
|
||||||
expect(maskUserInfo({ type: 'phone', value: '1234567890' })).toBe('****7890');
|
expect(maskUserInfo({ type: 'phone', value: '1234567890' })).toEqual({
|
||||||
|
type: 'phone',
|
||||||
|
value: '****7890',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
it('email with name less than 5', () => {
|
it('email with name less than 5', () => {
|
||||||
expect(maskUserInfo({ type: 'email', value: 'test@logto.io' })).toBe('****@logto.io');
|
expect(maskUserInfo({ type: 'email', value: 'test@logto.io' })).toEqual({
|
||||||
|
type: 'email',
|
||||||
|
value: '****@logto.io',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
it('email with name more than 4', () => {
|
it('email with name more than 4', () => {
|
||||||
expect(maskUserInfo({ type: 'email', value: 'foo_test@logto.io' })).toBe('foo_****@logto.io');
|
expect(maskUserInfo({ type: 'email', value: 'foo_test@logto.io' })).toEqual({
|
||||||
|
type: 'email',
|
||||||
|
value: 'foo_****@logto.io',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,17 +1,26 @@
|
||||||
export const maskUserInfo = ({ type, value }: { type: 'email' | 'phone'; value: string }) => {
|
export const maskUserInfo = (info: { type: 'email' | 'phone'; value: string }) => {
|
||||||
|
const { type, value } = info;
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return value;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'phone') {
|
if (type === 'phone') {
|
||||||
return `****${value.slice(-4)}`;
|
return {
|
||||||
|
type,
|
||||||
|
value: `****${value.slice(-4)}`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Email
|
||||||
const [name = '', domain = ''] = value.split('@');
|
const [name = '', domain = ''] = value.split('@');
|
||||||
|
|
||||||
const preview = name.length > 4 ? `${name.slice(0, 4)}` : '';
|
const preview = name.length > 4 ? `${name.slice(0, 4)}` : '';
|
||||||
|
|
||||||
return `${preview}****@${domain}`;
|
return {
|
||||||
|
type,
|
||||||
|
value: `${preview}****@${domain}`,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const stringifyError = (error: Error) =>
|
export const stringifyError = (error: Error) =>
|
||||||
|
|
|
@ -1,13 +1,6 @@
|
||||||
import ky from 'ky';
|
import ky from 'ky';
|
||||||
|
|
||||||
import { consent } from './consent';
|
import { consent } from './consent';
|
||||||
import {
|
|
||||||
invokeSocialSignIn,
|
|
||||||
signInWithSocial,
|
|
||||||
bindSocialAccount,
|
|
||||||
bindSocialRelatedUser,
|
|
||||||
registerWithSocial,
|
|
||||||
} from './social';
|
|
||||||
|
|
||||||
jest.mock('ky', () => ({
|
jest.mock('ky', () => ({
|
||||||
extend: () => ky,
|
extend: () => ky,
|
||||||
|
@ -17,10 +10,6 @@ jest.mock('ky', () => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('api', () => {
|
describe('api', () => {
|
||||||
const phone = '18888888';
|
|
||||||
const code = '111111';
|
|
||||||
const email = 'foo@logto.io';
|
|
||||||
|
|
||||||
const mockKyPost = ky.post as jest.Mock;
|
const mockKyPost = ky.post as jest.Mock;
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -31,56 +20,4 @@ describe('api', () => {
|
||||||
await consent();
|
await consent();
|
||||||
expect(ky.post).toBeCalledWith('/api/session/consent');
|
expect(ky.post).toBeCalledWith('/api/session/consent');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('invokeSocialSignIn', async () => {
|
|
||||||
await invokeSocialSignIn('connectorId', 'state', 'redirectUri');
|
|
||||||
expect(ky.post).toBeCalledWith('/api/session/sign-in/social', {
|
|
||||||
json: {
|
|
||||||
connectorId: 'connectorId',
|
|
||||||
state: 'state',
|
|
||||||
redirectUri: 'redirectUri',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('signInWithSocial', async () => {
|
|
||||||
const parameters = {
|
|
||||||
connectorId: 'connectorId',
|
|
||||||
data: {
|
|
||||||
redirectUri: 'redirectUri',
|
|
||||||
code: 'code',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
await signInWithSocial(parameters);
|
|
||||||
expect(ky.post).toBeCalledWith('/api/session/sign-in/social/auth', {
|
|
||||||
json: parameters,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('bindSocialAccount', async () => {
|
|
||||||
await bindSocialAccount('connectorId');
|
|
||||||
expect(ky.post).toBeCalledWith('/api/session/bind-social', {
|
|
||||||
json: {
|
|
||||||
connectorId: 'connectorId',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('bindSocialRelatedUser', async () => {
|
|
||||||
await bindSocialRelatedUser('connectorId');
|
|
||||||
expect(ky.post).toBeCalledWith('/api/session/sign-in/bind-social-related-user', {
|
|
||||||
json: {
|
|
||||||
connectorId: 'connectorId',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('registerWithSocial', async () => {
|
|
||||||
await registerWithSocial('connectorId');
|
|
||||||
expect(ky.post).toBeCalledWith('/api/session/register/social', {
|
|
||||||
json: {
|
|
||||||
connectorId: 'connectorId',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,6 +7,8 @@ import type {
|
||||||
PhonePasswordPayload,
|
PhonePasswordPayload,
|
||||||
EmailPasscodePayload,
|
EmailPasscodePayload,
|
||||||
PhonePasscodePayload,
|
PhonePasscodePayload,
|
||||||
|
SocialConnectorPayload,
|
||||||
|
SocialIdentityPayload,
|
||||||
} from '@logto/schemas';
|
} from '@logto/schemas';
|
||||||
import { conditional } from '@silverhand/essentials';
|
import { conditional } from '@silverhand/essentials';
|
||||||
|
|
||||||
|
@ -28,15 +30,20 @@ export const signInWithPasswordIdentifier = async (
|
||||||
payload: PasswordSignInPayload,
|
payload: PasswordSignInPayload,
|
||||||
socialToBind?: string
|
socialToBind?: string
|
||||||
) => {
|
) => {
|
||||||
await api.put(`${interactionPrefix}`, {
|
|
||||||
json: {
|
|
||||||
event: InteractionEvent.SignIn,
|
|
||||||
identifier: payload,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (socialToBind) {
|
if (socialToBind) {
|
||||||
// TODO: bind social account
|
await api.patch(`${interactionPrefix}/identifiers`, {
|
||||||
|
json: payload,
|
||||||
|
});
|
||||||
|
await api.patch(`${interactionPrefix}/profile`, {
|
||||||
|
json: { connectorId: socialToBind },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await api.put(`${interactionPrefix}`, {
|
||||||
|
json: {
|
||||||
|
event: InteractionEvent.SignIn,
|
||||||
|
identifier: payload,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return api.post(`${interactionPrefix}/submit`).json<Response>();
|
return api.post(`${interactionPrefix}/submit`).json<Response>();
|
||||||
|
@ -89,9 +96,9 @@ export const signInWithPasscodeIdentifier = async (
|
||||||
json: payload,
|
json: payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (socialToBind) {
|
await api.patch(`${interactionPrefix}/profile`, {
|
||||||
// TODO: bind social account
|
json: { connectorId: socialToBind },
|
||||||
}
|
});
|
||||||
|
|
||||||
return api.post(`${interactionPrefix}/submit`).json<Response>();
|
return api.post(`${interactionPrefix}/submit`).json<Response>();
|
||||||
};
|
};
|
||||||
|
@ -110,9 +117,9 @@ export const addProfileWithPasscodeIdentifier = async (
|
||||||
json: identifier,
|
json: identifier,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (socialToBind) {
|
await api.patch(`${interactionPrefix}/profile`, {
|
||||||
// TODO: bind social account
|
json: { connectorId: socialToBind },
|
||||||
}
|
});
|
||||||
|
|
||||||
return api.post(`${interactionPrefix}/submit`).json<Response>();
|
return api.post(`${interactionPrefix}/submit`).json<Response>();
|
||||||
};
|
};
|
||||||
|
@ -159,9 +166,65 @@ export const addProfile = async (
|
||||||
) => {
|
) => {
|
||||||
await api.patch(`${interactionPrefix}/profile`, { json: payload });
|
await api.patch(`${interactionPrefix}/profile`, { json: payload });
|
||||||
|
|
||||||
if (socialToBind) {
|
await api.patch(`${interactionPrefix}/profile`, {
|
||||||
// TODO: bind social account
|
json: { connectorId: socialToBind },
|
||||||
}
|
});
|
||||||
|
|
||||||
|
return api.post(`${interactionPrefix}/submit`).json<Response>();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSocialAuthorizationUrl = async (
|
||||||
|
connectorId: string,
|
||||||
|
state: string,
|
||||||
|
redirectUri: string
|
||||||
|
) => {
|
||||||
|
await api.put(`${interactionPrefix}`, { json: { event: InteractionEvent.SignIn } });
|
||||||
|
|
||||||
|
return api
|
||||||
|
.post(`${interactionPrefix}/${verificationPath}/social-authorization-uri`, {
|
||||||
|
json: {
|
||||||
|
connectorId,
|
||||||
|
state,
|
||||||
|
redirectUri,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.json<Response>();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const signInWithSocial = async (payload: SocialConnectorPayload) => {
|
||||||
|
await api.patch(`${interactionPrefix}/identifiers`, {
|
||||||
|
json: payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
return api.post(`${interactionPrefix}/submit`).json<Response>();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const registerWithVerifiedSocial = async (connectorId: string) => {
|
||||||
|
await api.put(`${interactionPrefix}/event`, {
|
||||||
|
json: {
|
||||||
|
event: InteractionEvent.Register,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await api.patch(`${interactionPrefix}/profile`, {
|
||||||
|
json: {
|
||||||
|
connectorId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return api.post(`${interactionPrefix}/submit`).json<Response>();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const bindSocialRelatedUser = async (payload: SocialIdentityPayload) => {
|
||||||
|
await api.patch(`${interactionPrefix}/identifiers`, {
|
||||||
|
json: payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
await api.patch(`${interactionPrefix}/profile`, {
|
||||||
|
json: {
|
||||||
|
connectorId: payload.connectorId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return api.post(`${interactionPrefix}/submit`).json<Response>();
|
return api.post(`${interactionPrefix}/submit`).json<Response>();
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,37 +1,5 @@
|
||||||
import api from './api';
|
import api from './api';
|
||||||
|
|
||||||
export const invokeSocialSignIn = async (
|
|
||||||
connectorId: string,
|
|
||||||
state: string,
|
|
||||||
redirectUri: string
|
|
||||||
) => {
|
|
||||||
type Response = {
|
|
||||||
redirectTo: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
return api
|
|
||||||
.post('/api/session/sign-in/social', {
|
|
||||||
json: {
|
|
||||||
connectorId,
|
|
||||||
state,
|
|
||||||
redirectUri,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.json<Response>();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const signInWithSocial = async (json: { connectorId: string; data: unknown }) => {
|
|
||||||
type Response = {
|
|
||||||
redirectTo: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
return api
|
|
||||||
.post('/api/session/sign-in/social/auth', {
|
|
||||||
json,
|
|
||||||
})
|
|
||||||
.json<Response>();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const bindSocialAccount = async (connectorId: string) => {
|
export const bindSocialAccount = async (connectorId: string) => {
|
||||||
return api
|
return api
|
||||||
.post('/api/session/bind-social', {
|
.post('/api/session/bind-social', {
|
||||||
|
@ -41,31 +9,3 @@ export const bindSocialAccount = async (connectorId: string) => {
|
||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const bindSocialRelatedUser = async (connectorId: string) => {
|
|
||||||
type Response = {
|
|
||||||
redirectTo: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
return api
|
|
||||||
.post('/api/session/sign-in/bind-social-related-user', {
|
|
||||||
json: {
|
|
||||||
connectorId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.json<Response>();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const registerWithSocial = async (connectorId: string) => {
|
|
||||||
type Response = {
|
|
||||||
redirectTo: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
return api
|
|
||||||
.post('/api/session/register/social', {
|
|
||||||
json: {
|
|
||||||
connectorId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.json<Response>();
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { InteractionEvent } from '@logto/schemas';
|
import { InteractionEvent } from '@logto/schemas';
|
||||||
|
|
||||||
import { UserFlow } from '@/types';
|
import { UserFlow, SearchParameters } from '@/types';
|
||||||
|
import { getSearchParameters } from '@/utils';
|
||||||
|
|
||||||
import type { SendPasscodePayload } from './interaction';
|
import type { SendPasscodePayload } from './interaction';
|
||||||
import { putInteraction, sendPasscode } from './interaction';
|
import { putInteraction, sendPasscode } from './interaction';
|
||||||
|
@ -10,7 +11,10 @@ export const getSendPasscodeApi = (type: UserFlow) => async (payload: SendPassco
|
||||||
await putInteraction(InteractionEvent.ForgotPassword);
|
await putInteraction(InteractionEvent.ForgotPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === UserFlow.signIn) {
|
const socialToBind = getSearchParameters(location.search, SearchParameters.bindWithSocial);
|
||||||
|
|
||||||
|
// Init a new interaction only if the user is not binding with a social
|
||||||
|
if (type === UserFlow.signIn && !socialToBind) {
|
||||||
await putInteraction(InteractionEvent.SignIn);
|
await putInteraction(InteractionEvent.SignIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { fireEvent, waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||||
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
||||||
import { registerWithSocial, bindSocialRelatedUser } from '@/apis/social';
|
import { registerWithVerifiedSocial, bindSocialRelatedUser } from '@/apis/interaction';
|
||||||
|
|
||||||
import SocialCreateAccount from '.';
|
import SocialCreateAccount from '.';
|
||||||
|
|
||||||
|
@ -11,12 +11,12 @@ const mockNavigate = jest.fn();
|
||||||
jest.mock('react-router-dom', () => ({
|
jest.mock('react-router-dom', () => ({
|
||||||
...jest.requireActual('react-router-dom'),
|
...jest.requireActual('react-router-dom'),
|
||||||
useNavigate: () => mockNavigate,
|
useNavigate: () => mockNavigate,
|
||||||
useLocation: () => ({ state: { relatedUser: 'foo@logto.io' } }),
|
useLocation: () => ({ state: { relatedUser: { type: 'email', value: 'foo@logto.io' } } }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('@/apis/social', () => ({
|
jest.mock('@/apis/interaction', () => ({
|
||||||
registerWithSocial: jest.fn(async () => 0),
|
registerWithVerifiedSocial: jest.fn(async () => ({ redirectTo: '/' })),
|
||||||
bindSocialRelatedUser: jest.fn(async () => 0),
|
bindSocialRelatedUser: jest.fn(async () => ({ redirectTo: '/' })),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('SocialCreateAccount', () => {
|
describe('SocialCreateAccount', () => {
|
||||||
|
@ -31,7 +31,7 @@ describe('SocialCreateAccount', () => {
|
||||||
expect(queryByText('secondary.social_bind_with')).not.toBeNull();
|
expect(queryByText('secondary.social_bind_with')).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call registerWithSocial when click create button', async () => {
|
it('should call registerWithVerifiedSocial when click create button', async () => {
|
||||||
const { getByText } = renderWithPageContext(<SocialCreateAccount connectorId="github" />);
|
const { getByText } = renderWithPageContext(<SocialCreateAccount connectorId="github" />);
|
||||||
const createButton = getByText('action.create');
|
const createButton = getByText('action.create');
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ describe('SocialCreateAccount', () => {
|
||||||
fireEvent.click(createButton);
|
fireEvent.click(createButton);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(registerWithSocial).toBeCalledWith('github');
|
expect(registerWithVerifiedSocial).toBeCalledWith('github');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render bindUser Button when relatedUserInfo found', async () => {
|
it('should render bindUser Button when relatedUserInfo found', async () => {
|
||||||
|
@ -48,6 +48,6 @@ describe('SocialCreateAccount', () => {
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
fireEvent.click(bindButton);
|
fireEvent.click(bindButton);
|
||||||
});
|
});
|
||||||
expect(bindSocialRelatedUser).toBeCalledWith('github');
|
expect(bindSocialRelatedUser).toBeCalledWith({ connectorId: 'github', identityType: 'email' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,9 +27,9 @@ const SocialCreateAccount = ({ connectorId, className }: Props) => {
|
||||||
<div className={styles.desc}>{t('description.social_bind_with_existing')}</div>
|
<div className={styles.desc}>{t('description.social_bind_with_existing')}</div>
|
||||||
<Button
|
<Button
|
||||||
title="action.bind"
|
title="action.bind"
|
||||||
i18nProps={{ address: relatedUser }}
|
i18nProps={{ address: relatedUser.value }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
bindSocialRelatedUser(connectorId);
|
bindSocialRelatedUser({ connectorId, identityType: relatedUser.type });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
import type { SocialIdentityPayload } from '@logto/schemas';
|
||||||
import { conditional } from '@silverhand/essentials';
|
import { conditional } from '@silverhand/essentials';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { is } from 'superstruct';
|
import { is } from 'superstruct';
|
||||||
|
|
||||||
import { registerWithSocial, bindSocialRelatedUser } from '@/apis/social';
|
import { registerWithVerifiedSocial, bindSocialRelatedUser } from '@/apis/interaction';
|
||||||
import useApi from '@/hooks/use-api';
|
import useApi from '@/hooks/use-api';
|
||||||
import { bindSocialStateGuard } from '@/types/guard';
|
import { bindSocialStateGuard } from '@/types/guard';
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ const useBindSocial = () => {
|
||||||
const requiredProfileErrorHandlers = useRequiredProfileErrorHandler();
|
const requiredProfileErrorHandlers = useRequiredProfileErrorHandler();
|
||||||
|
|
||||||
const { result: registerResult, run: asyncRegisterWithSocial } = useApi(
|
const { result: registerResult, run: asyncRegisterWithSocial } = useApi(
|
||||||
registerWithSocial,
|
registerWithVerifiedSocial,
|
||||||
requiredProfileErrorHandlers
|
requiredProfileErrorHandlers
|
||||||
);
|
);
|
||||||
const { result: bindUserResult, run: asyncBindSocialRelatedUser } = useApi(
|
const { result: bindUserResult, run: asyncBindSocialRelatedUser } = useApi(
|
||||||
|
@ -30,8 +31,8 @@ const useBindSocial = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const bindRelatedUserHandler = useCallback(
|
const bindRelatedUserHandler = useCallback(
|
||||||
(connectorId: string) => {
|
(payload: SocialIdentityPayload) => {
|
||||||
void asyncBindSocialRelatedUser(connectorId);
|
void asyncBindSocialRelatedUser(payload);
|
||||||
},
|
},
|
||||||
[asyncBindSocialRelatedUser]
|
[asyncBindSocialRelatedUser]
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { useEffect, useCallback, useContext, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { signInWithSocial } from '@/apis/social';
|
import { signInWithSocial } from '@/apis/interaction';
|
||||||
import { parseQueryParameters } from '@/utils';
|
import { parseQueryParameters } from '@/utils';
|
||||||
import { stateValidation } from '@/utils/social-connectors';
|
import { stateValidation } from '@/utils/social-connectors';
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ const useSocialSignInListener = () => {
|
||||||
async (connectorId: string, data: Record<string, unknown>) => {
|
async (connectorId: string, data: Record<string, unknown>) => {
|
||||||
void asyncSignInWithSocial({
|
void asyncSignInWithSocial({
|
||||||
connectorId,
|
connectorId,
|
||||||
data: {
|
connectorData: {
|
||||||
redirectUri: `${window.location.origin}/callback/${connectorId}`, // For validation use only
|
redirectUri: `${window.location.origin}/callback/${connectorId}`, // For validation use only
|
||||||
...data,
|
...data,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { ConnectorMetadata } from '@logto/schemas';
|
import type { ConnectorMetadata } from '@logto/schemas';
|
||||||
import { useCallback, useContext } from 'react';
|
import { useCallback, useContext } from 'react';
|
||||||
|
|
||||||
import { invokeSocialSignIn } from '@/apis/social';
|
import { getSocialAuthorizationUrl } from '@/apis/interaction';
|
||||||
import { getLogtoNativeSdk, isNativeWebview } from '@/utils/native-sdk';
|
import { getLogtoNativeSdk, isNativeWebview } from '@/utils/native-sdk';
|
||||||
import { generateState, storeState, buildSocialLandingUri } from '@/utils/social-connectors';
|
import { generateState, storeState, buildSocialLandingUri } from '@/utils/social-connectors';
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ const useSocial = () => {
|
||||||
const { experienceSettings, theme } = useContext(PageContext);
|
const { experienceSettings, theme } = useContext(PageContext);
|
||||||
const { termsValidation } = useTerms();
|
const { termsValidation } = useTerms();
|
||||||
|
|
||||||
const { run: asyncInvokeSocialSignIn } = useApi(invokeSocialSignIn);
|
const { run: asyncInvokeSocialSignIn } = useApi(getSocialAuthorizationUrl);
|
||||||
|
|
||||||
const nativeSignInHandler = useCallback((redirectTo: string, connector: ConnectorMetadata) => {
|
const nativeSignInHandler = useCallback((redirectTo: string, connector: ConnectorMetadata) => {
|
||||||
const { id: connectorId, platform } = connector;
|
const { id: connectorId, platform } = connector;
|
||||||
|
|
|
@ -3,18 +3,18 @@ import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
||||||
|
|
||||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||||
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
||||||
import * as socialSignInApi from '@/apis/social';
|
import { signInWithSocial } from '@/apis/interaction';
|
||||||
import { generateState, storeState } from '@/utils/social-connectors';
|
import { generateState, storeState } from '@/utils/social-connectors';
|
||||||
|
|
||||||
import SocialCallback from '.';
|
import SocialCallback from '.';
|
||||||
|
|
||||||
const origin = 'http://localhost:3000';
|
const origin = 'http://localhost:3000';
|
||||||
|
|
||||||
describe('SocialCallbackPage with code', () => {
|
jest.mock('@/apis/interaction', () => ({
|
||||||
const signInWithSocialSpy = jest
|
signInWithSocial: jest.fn().mockResolvedValue({ redirectTo: `/sign-in` }),
|
||||||
.spyOn(socialSignInApi, 'signInWithSocial')
|
}));
|
||||||
.mockResolvedValue({ redirectTo: `/sign-in` });
|
|
||||||
|
|
||||||
|
describe('SocialCallbackPage with code', () => {
|
||||||
it('callback validation and signIn with social', async () => {
|
it('callback validation and signIn with social', async () => {
|
||||||
const state = generateState();
|
const state = generateState();
|
||||||
storeState(state, 'github');
|
storeState(state, 'github');
|
||||||
|
@ -43,7 +43,7 @@ describe('SocialCallbackPage with code', () => {
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(signInWithSocialSpy).toBeCalled();
|
expect(signInWithSocial).toBeCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,10 @@ import { SignInIdentifier, MissingProfile } from '@logto/schemas';
|
||||||
import * as s from 'superstruct';
|
import * as s from 'superstruct';
|
||||||
|
|
||||||
export const bindSocialStateGuard = s.object({
|
export const bindSocialStateGuard = s.object({
|
||||||
relatedUser: s.optional(s.string()),
|
relatedUser: s.object({
|
||||||
|
type: s.union([s.literal('email'), s.literal('phone')]),
|
||||||
|
value: s.string(),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const passcodeStateGuard = s.object({
|
export const passcodeStateGuard = s.object({
|
||||||
|
|
Loading…
Add table
Reference in a new issue