mirror of
https://github.com/logto-io/logto.git
synced 2025-03-17 22:31:28 -05:00
refactor(core): remove the event from sendPasscode API payload (#2742)
This commit is contained in:
parent
1f7efd3680
commit
d641e201c2
51 changed files with 418 additions and 860 deletions
|
@ -78,7 +78,7 @@ const { storeInteractionResult, mergeIdentifiers, getInteractionStorage } = awai
|
|||
() => ({
|
||||
mergeIdentifiers: jest.fn(),
|
||||
storeInteractionResult: jest.fn(),
|
||||
getInteractionStorage: jest.fn().mockResolvedValue({
|
||||
getInteractionStorage: jest.fn().mockReturnValue({
|
||||
event: InteractionEvent.SignIn,
|
||||
}),
|
||||
})
|
||||
|
@ -262,13 +262,19 @@ describe('session -> interactionRoutes', () => {
|
|||
|
||||
it('should call send passcode properly', async () => {
|
||||
const body = {
|
||||
event: InteractionEvent.SignIn,
|
||||
email: 'email@logto.io',
|
||||
};
|
||||
|
||||
const response = await sessionRequest.post(path).send(body);
|
||||
expect(getInteractionStorage).toBeCalled();
|
||||
expect(sendPasscodeToIdentifier).toBeCalledWith(body, 'jti', createLog);
|
||||
expect(sendPasscodeToIdentifier).toBeCalledWith(
|
||||
{
|
||||
event: InteractionEvent.SignIn,
|
||||
...body,
|
||||
},
|
||||
'jti',
|
||||
createLog
|
||||
);
|
||||
expect(response.status).toEqual(204);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -70,11 +70,11 @@ export default function interactionRoutes<T extends AnonymousRouter>(
|
|||
|
||||
verifySignInModeSettings(event, signInExperience);
|
||||
|
||||
if (identifier) {
|
||||
if (identifier && event !== InteractionEvent.ForgotPassword) {
|
||||
verifyIdentifierSettings(identifier, signInExperience);
|
||||
}
|
||||
|
||||
if (profile) {
|
||||
if (profile && event !== InteractionEvent.ForgotPassword) {
|
||||
verifyProfileSettings(profile, signInExperience);
|
||||
}
|
||||
|
||||
|
@ -145,10 +145,12 @@ export default function interactionRoutes<T extends AnonymousRouter>(
|
|||
async (ctx, next) => {
|
||||
const identifierPayload = ctx.guard.body;
|
||||
const { signInExperience, interactionDetails } = ctx;
|
||||
verifyIdentifierSettings(identifierPayload, signInExperience);
|
||||
|
||||
const interactionStorage = getInteractionStorage(interactionDetails.result);
|
||||
|
||||
if (interactionStorage.event === InteractionEvent.ForgotPassword) {
|
||||
verifyIdentifierSettings(identifierPayload, signInExperience);
|
||||
}
|
||||
|
||||
const verifiedIdentifier = await verifyIdentifierPayload(
|
||||
ctx,
|
||||
provider,
|
||||
|
@ -175,12 +177,14 @@ export default function interactionRoutes<T extends AnonymousRouter>(
|
|||
koaInteractionSie(),
|
||||
async (ctx, next) => {
|
||||
const profilePayload = ctx.guard.body;
|
||||
|
||||
const { signInExperience, interactionDetails } = ctx;
|
||||
verifyProfileSettings(profilePayload, signInExperience);
|
||||
|
||||
// Check interaction exists
|
||||
getInteractionStorage(interactionDetails.result);
|
||||
const { event } = getInteractionStorage(interactionDetails.result);
|
||||
|
||||
if (event !== InteractionEvent.ForgotPassword) {
|
||||
verifyProfileSettings(profilePayload, signInExperience);
|
||||
}
|
||||
|
||||
await storeInteractionResult(
|
||||
{
|
||||
|
@ -207,10 +211,13 @@ export default function interactionRoutes<T extends AnonymousRouter>(
|
|||
async (ctx, next) => {
|
||||
const profilePayload = ctx.guard.body;
|
||||
const { signInExperience, interactionDetails } = ctx;
|
||||
verifyProfileSettings(profilePayload, signInExperience);
|
||||
|
||||
const interactionStorage = getInteractionStorage(interactionDetails.result);
|
||||
|
||||
if (interactionStorage.event !== InteractionEvent.ForgotPassword) {
|
||||
verifyProfileSettings(profilePayload, signInExperience);
|
||||
}
|
||||
|
||||
await storeInteractionResult(
|
||||
{
|
||||
profile: {
|
||||
|
@ -292,9 +299,9 @@ export default function interactionRoutes<T extends AnonymousRouter>(
|
|||
async (ctx, next) => {
|
||||
const { interactionDetails, guard, createLog } = ctx;
|
||||
// Check interaction exists
|
||||
getInteractionStorage(interactionDetails.result);
|
||||
const { event } = getInteractionStorage(interactionDetails.result);
|
||||
|
||||
await sendPasscodeToIdentifier(guard.body, interactionDetails.jti, createLog);
|
||||
await sendPasscodeToIdentifier({ event, ...guard.body }, interactionDetails.jti, createLog);
|
||||
|
||||
ctx.status = 204;
|
||||
|
||||
|
|
|
@ -7,11 +7,9 @@ import { socialUserInfoGuard } from '#src/connectors/types.js';
|
|||
// Passcode Send Route Payload Guard
|
||||
export const sendPasscodePayloadGuard = z.union([
|
||||
z.object({
|
||||
event: eventGuard,
|
||||
email: z.string().regex(emailRegEx),
|
||||
}),
|
||||
z.object({
|
||||
event: eventGuard,
|
||||
phone: z.string().regex(phoneRegEx),
|
||||
}),
|
||||
]);
|
||||
|
|
|
@ -4,8 +4,6 @@ import { createMockUtils } from '@logto/shared/esm';
|
|||
|
||||
import { createMockLogContext } from '#src/test-utils/koa-audit-log.js';
|
||||
|
||||
import type { SendPasscodePayload } from '../types/index.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
const { mockEsmWithActual } = createMockUtils(jest);
|
||||
|
||||
|
@ -55,7 +53,7 @@ describe('passcode-validation utils', () => {
|
|||
it.each(sendPasscodeTestCase)(
|
||||
'send passcode successfully',
|
||||
async ({ payload, createPasscodeParams }) => {
|
||||
await sendPasscodeToIdentifier(payload as SendPasscodePayload, 'jti', log.createLog);
|
||||
await sendPasscodeToIdentifier(payload, 'jti', log.createLog);
|
||||
expect(passcode.createPasscode).toBeCalledWith('jti', ...createPasscodeParams);
|
||||
expect(passcode.sendPasscode).toBeCalled();
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ const getMessageTypesByEvent = (event: InteractionEvent): MessageTypes =>
|
|||
eventToMessageTypesMap[event];
|
||||
|
||||
export const sendPasscodeToIdentifier = async (
|
||||
payload: SendPasscodePayload,
|
||||
payload: SendPasscodePayload & { event: InteractionEvent },
|
||||
jti: string,
|
||||
createLog: LogContext['createLog']
|
||||
) => {
|
||||
|
|
|
@ -153,7 +153,7 @@ describe('verifyUserAccount', () => {
|
|||
};
|
||||
|
||||
await expect(verifyUserAccount(interaction)).rejects.toMatchError(
|
||||
new RequestError({ code: 'user.user_not_exist', status: 404 }, { identifier: 'email' })
|
||||
new RequestError({ code: 'user.user_not_exist', status: 404 }, { identity: 'email' })
|
||||
);
|
||||
|
||||
expect(findUserByIdentifierMock).toBeCalledWith({ email: 'email' });
|
||||
|
|
|
@ -26,7 +26,7 @@ const identifyUserByVerifiedEmailOrPhone = async (
|
|||
|
||||
assertThat(
|
||||
user,
|
||||
new RequestError({ code: 'user.user_not_exist', status: 404 }, { identifier: identifier.value })
|
||||
new RequestError({ code: 'user.user_not_exist', status: 404 }, { identity: identifier.value })
|
||||
);
|
||||
|
||||
const { id, isSuspended } = user;
|
||||
|
|
|
@ -68,10 +68,9 @@ export const submitInteraction = async (cookie: string) =>
|
|||
|
||||
export type VerificationPasscodePayload =
|
||||
| {
|
||||
event: InteractionEvent;
|
||||
email: string;
|
||||
}
|
||||
| { event: InteractionEvent; phone: string };
|
||||
| { phone: string };
|
||||
|
||||
export const sendVerificationPasscode = async (
|
||||
cookie: string,
|
||||
|
|
|
@ -42,7 +42,6 @@ describe('reset password', () => {
|
|||
|
||||
await client.successSend(putInteraction, { event: InteractionEvent.ForgotPassword });
|
||||
await client.successSend(sendVerificationPasscode, {
|
||||
event: InteractionEvent.ForgotPassword,
|
||||
email: userProfile.primaryEmail,
|
||||
});
|
||||
|
||||
|
@ -96,7 +95,6 @@ describe('reset password', () => {
|
|||
|
||||
await client.successSend(putInteraction, { event: InteractionEvent.ForgotPassword });
|
||||
await client.successSend(sendVerificationPasscode, {
|
||||
event: InteractionEvent.ForgotPassword,
|
||||
phone: userProfile.primaryPhone,
|
||||
});
|
||||
|
||||
|
|
|
@ -73,7 +73,6 @@ describe('Register with passwordless identifier', () => {
|
|||
});
|
||||
|
||||
await client.successSend(sendVerificationPasscode, {
|
||||
event: InteractionEvent.Register,
|
||||
email: primaryEmail,
|
||||
});
|
||||
|
||||
|
@ -120,7 +119,6 @@ describe('Register with passwordless identifier', () => {
|
|||
});
|
||||
|
||||
await client.successSend(sendVerificationPasscode, {
|
||||
event: InteractionEvent.Register,
|
||||
email: primaryEmail,
|
||||
});
|
||||
|
||||
|
@ -179,7 +177,6 @@ describe('Register with passwordless identifier', () => {
|
|||
});
|
||||
|
||||
await client.successSend(sendVerificationPasscode, {
|
||||
event: InteractionEvent.Register,
|
||||
phone: primaryPhone,
|
||||
});
|
||||
|
||||
|
@ -226,7 +223,6 @@ describe('Register with passwordless identifier', () => {
|
|||
});
|
||||
|
||||
await client.successSend(sendVerificationPasscode, {
|
||||
event: InteractionEvent.Register,
|
||||
phone: primaryPhone,
|
||||
});
|
||||
|
||||
|
@ -288,7 +284,6 @@ describe('Register with passwordless identifier', () => {
|
|||
});
|
||||
|
||||
await client.successSend(sendVerificationPasscode, {
|
||||
event: InteractionEvent.Register,
|
||||
email: primaryEmail,
|
||||
});
|
||||
|
||||
|
@ -341,7 +336,6 @@ describe('Register with passwordless identifier', () => {
|
|||
});
|
||||
|
||||
await client.successSend(sendVerificationPasscode, {
|
||||
event: InteractionEvent.Register,
|
||||
phone: primaryPhone,
|
||||
});
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@ describe('Sign-In flow using passcode identifiers', () => {
|
|||
});
|
||||
|
||||
await client.successSend(sendVerificationPasscode, {
|
||||
event: InteractionEvent.SignIn,
|
||||
email: userProfile.primaryEmail,
|
||||
});
|
||||
|
||||
|
@ -71,7 +70,6 @@ describe('Sign-In flow using passcode identifiers', () => {
|
|||
});
|
||||
|
||||
await client.successSend(sendVerificationPasscode, {
|
||||
event: InteractionEvent.SignIn,
|
||||
phone: userProfile.primaryPhone,
|
||||
});
|
||||
|
||||
|
@ -111,7 +109,6 @@ describe('Sign-In flow using passcode identifiers', () => {
|
|||
});
|
||||
|
||||
await client.successSend(sendVerificationPasscode, {
|
||||
event: InteractionEvent.SignIn,
|
||||
email: newEmail,
|
||||
});
|
||||
|
||||
|
@ -151,7 +148,6 @@ describe('Sign-In flow using passcode identifiers', () => {
|
|||
});
|
||||
|
||||
await client.successSend(sendVerificationPasscode, {
|
||||
event: InteractionEvent.SignIn,
|
||||
phone: newPhone,
|
||||
});
|
||||
|
||||
|
@ -197,7 +193,6 @@ describe('Sign-In flow using passcode identifiers', () => {
|
|||
});
|
||||
|
||||
await client.successSend(sendVerificationPasscode, {
|
||||
event: InteractionEvent.SignIn,
|
||||
email: userProfile.primaryEmail,
|
||||
});
|
||||
const { code } = await readPasscode();
|
||||
|
@ -257,7 +252,6 @@ describe('Sign-In flow using passcode identifiers', () => {
|
|||
});
|
||||
|
||||
await client.successSend(sendVerificationPasscode, {
|
||||
event: InteractionEvent.SignIn,
|
||||
email: userProfile.primaryEmail,
|
||||
});
|
||||
const { code } = await readPasscode();
|
||||
|
@ -309,7 +303,6 @@ describe('Sign-In flow using passcode identifiers', () => {
|
|||
});
|
||||
|
||||
await client.successSend(sendVerificationPasscode, {
|
||||
event: InteractionEvent.SignIn,
|
||||
email: userProfile.primaryEmail,
|
||||
});
|
||||
const { code } = await readPasscode();
|
||||
|
|
|
@ -112,7 +112,6 @@ describe('Sign-In flow using password identifiers', () => {
|
|||
await expectRejects(client.submitInteraction(), 'user.missing_profile');
|
||||
|
||||
await client.successSend(sendVerificationPasscode, {
|
||||
event: InteractionEvent.SignIn,
|
||||
email: primaryEmail,
|
||||
});
|
||||
|
||||
|
@ -172,7 +171,6 @@ describe('Sign-In flow using password identifiers', () => {
|
|||
await expectRejects(client.submitInteraction(), 'user.missing_profile');
|
||||
|
||||
await client.successSend(sendVerificationPasscode, {
|
||||
event: InteractionEvent.SignIn,
|
||||
phone: primaryPhone,
|
||||
});
|
||||
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
import { MessageTypes } from '@logto/connector-kit';
|
||||
import ky from 'ky';
|
||||
|
||||
import {
|
||||
continueApi,
|
||||
sendContinueSetEmailPasscode,
|
||||
sendContinueSetPhonePasscode,
|
||||
verifyContinueSetEmailPasscode,
|
||||
verifyContinueSetSmsPasscode,
|
||||
} from './continue';
|
||||
import { continueApi } from './continue';
|
||||
|
||||
jest.mock('ky', () => ({
|
||||
extend: () => ky,
|
||||
|
@ -68,50 +61,4 @@ describe('continue API', () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sendContinueSetEmailPasscode', async () => {
|
||||
await sendContinueSetEmailPasscode('email');
|
||||
|
||||
expect(ky.post).toBeCalledWith('/api/session/passwordless/email/send', {
|
||||
json: {
|
||||
email: 'email',
|
||||
flow: MessageTypes.Continue,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sendContinueSetSmsPasscode', async () => {
|
||||
await sendContinueSetPhonePasscode('111111');
|
||||
|
||||
expect(ky.post).toBeCalledWith('/api/session/passwordless/sms/send', {
|
||||
json: {
|
||||
phone: '111111',
|
||||
flow: MessageTypes.Continue,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('verifyContinueSetEmailPasscode', async () => {
|
||||
await verifyContinueSetEmailPasscode('email', 'passcode');
|
||||
|
||||
expect(ky.post).toBeCalledWith('/api/session/passwordless/email/verify', {
|
||||
json: {
|
||||
email: 'email',
|
||||
code: 'passcode',
|
||||
flow: MessageTypes.Continue,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('verifyContinueSetSmsPasscode', async () => {
|
||||
await verifyContinueSetSmsPasscode('phone', 'passcode');
|
||||
|
||||
expect(ky.post).toBeCalledWith('/api/session/passwordless/sms/verify', {
|
||||
json: {
|
||||
phone: 'phone',
|
||||
code: 'passcode',
|
||||
flow: MessageTypes.Continue,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { MessageTypes } from '@logto/connector-kit';
|
||||
|
||||
import api from './api';
|
||||
import { bindSocialAccount } from './social';
|
||||
|
||||
|
@ -7,7 +5,6 @@ type Response = {
|
|||
redirectTo: string;
|
||||
};
|
||||
|
||||
const passwordlessApiPrefix = '/api/session/passwordless';
|
||||
const continueApiPrefix = '/api/session/sign-in/continue';
|
||||
|
||||
type ContinueKey = 'password' | 'username' | 'email' | 'phone';
|
||||
|
@ -25,49 +22,3 @@ export const continueApi = async (key: ContinueKey, value: string, socialToBind?
|
|||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const sendContinueSetEmailPasscode = async (email: string) => {
|
||||
await api
|
||||
.post(`${passwordlessApiPrefix}/email/send`, {
|
||||
json: {
|
||||
email,
|
||||
flow: MessageTypes.Continue,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
export const sendContinueSetPhonePasscode = async (phone: string) => {
|
||||
await api
|
||||
.post(`${passwordlessApiPrefix}/sms/send`, {
|
||||
json: {
|
||||
phone,
|
||||
flow: MessageTypes.Continue,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
export const verifyContinueSetEmailPasscode = async (email: string, code: string) => {
|
||||
await api
|
||||
.post(`${passwordlessApiPrefix}/email/verify`, {
|
||||
json: { email, code, flow: MessageTypes.Continue },
|
||||
})
|
||||
.json();
|
||||
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
export const verifyContinueSetSmsPasscode = async (phone: string, code: string) => {
|
||||
await api
|
||||
.post(`${passwordlessApiPrefix}/sms/verify`, {
|
||||
json: { phone, code, flow: MessageTypes.Continue },
|
||||
})
|
||||
.json();
|
||||
|
||||
return { success: true };
|
||||
};
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
import { MessageTypes } from '@logto/connector-kit';
|
||||
|
||||
import api from './api';
|
||||
|
||||
type Response = {
|
||||
redirectTo: string;
|
||||
};
|
||||
|
||||
const forgotPasswordApiPrefix = '/api/session/forgot-password';
|
||||
|
||||
export const sendForgotPasswordSmsPasscode = async (phone: string) => {
|
||||
await api
|
||||
.post('/api/session/passwordless/sms/send', {
|
||||
json: {
|
||||
phone,
|
||||
flow: MessageTypes.ForgotPassword,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
export const verifyForgotPasswordSmsPasscode = async (phone: string, code: string) => {
|
||||
await api
|
||||
.post('/api/session/passwordless/sms/verify', {
|
||||
json: {
|
||||
phone,
|
||||
code,
|
||||
flow: MessageTypes.ForgotPassword,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
export const sendForgotPasswordEmailPasscode = async (email: string) => {
|
||||
await api
|
||||
.post('/api/session/passwordless/email/send', {
|
||||
json: {
|
||||
email,
|
||||
flow: MessageTypes.ForgotPassword,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
export const verifyForgotPasswordEmailPasscode = async (email: string, code: string) => {
|
||||
await api
|
||||
.post('/api/session/passwordless/email/verify', {
|
||||
json: {
|
||||
email,
|
||||
code,
|
||||
flow: MessageTypes.ForgotPassword,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
export const resetPassword = async (password: string) => {
|
||||
await api
|
||||
.post(`${forgotPasswordApiPrefix}/reset`, {
|
||||
json: { password },
|
||||
})
|
||||
.json<Response>();
|
||||
|
||||
return { success: true };
|
||||
};
|
|
@ -1,30 +1,6 @@
|
|||
import { MessageTypes } from '@logto/connector-kit';
|
||||
import ky from 'ky';
|
||||
|
||||
import { consent } from './consent';
|
||||
import {
|
||||
verifyForgotPasswordEmailPasscode,
|
||||
verifyForgotPasswordSmsPasscode,
|
||||
sendForgotPasswordEmailPasscode,
|
||||
sendForgotPasswordSmsPasscode,
|
||||
resetPassword,
|
||||
} from './forgot-password';
|
||||
import {
|
||||
registerWithSms,
|
||||
registerWithEmail,
|
||||
sendRegisterEmailPasscode,
|
||||
sendRegisterSmsPasscode,
|
||||
verifyRegisterEmailPasscode,
|
||||
verifyRegisterSmsPasscode,
|
||||
} from './register';
|
||||
import {
|
||||
signInWithSms,
|
||||
signInWithEmail,
|
||||
sendSignInSmsPasscode,
|
||||
sendSignInEmailPasscode,
|
||||
verifySignInEmailPasscode,
|
||||
verifySignInSmsPasscode,
|
||||
} from './sign-in';
|
||||
import {
|
||||
invokeSocialSignIn,
|
||||
signInWithSocial,
|
||||
|
@ -41,8 +17,6 @@ jest.mock('ky', () => ({
|
|||
}));
|
||||
|
||||
describe('api', () => {
|
||||
const username = 'username';
|
||||
const password = 'password';
|
||||
const phone = '18888888';
|
||||
const code = '111111';
|
||||
const email = 'foo@logto.io';
|
||||
|
@ -53,181 +27,11 @@ describe('api', () => {
|
|||
mockKyPost.mockClear();
|
||||
});
|
||||
|
||||
it('signInWithSms', async () => {
|
||||
mockKyPost.mockReturnValueOnce({
|
||||
json: () => ({
|
||||
redirectTo: '/',
|
||||
}),
|
||||
});
|
||||
await signInWithSms();
|
||||
expect(ky.post).toBeCalledWith('/api/session/sign-in/passwordless/sms');
|
||||
});
|
||||
|
||||
it('signInWithEmail', async () => {
|
||||
mockKyPost.mockReturnValueOnce({
|
||||
json: () => ({
|
||||
redirectTo: '/',
|
||||
}),
|
||||
});
|
||||
await signInWithEmail();
|
||||
expect(ky.post).toBeCalledWith('/api/session/sign-in/passwordless/email');
|
||||
});
|
||||
|
||||
it('sendSignInSmsPasscode', async () => {
|
||||
await sendSignInSmsPasscode(phone);
|
||||
expect(ky.post).toBeCalledWith('/api/session/passwordless/sms/send', {
|
||||
json: {
|
||||
phone,
|
||||
flow: MessageTypes.SignIn,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('verifySignInSmsPasscode', async () => {
|
||||
mockKyPost.mockReturnValueOnce({
|
||||
json: () => ({
|
||||
redirectTo: '/',
|
||||
}),
|
||||
});
|
||||
|
||||
await verifySignInSmsPasscode(phone, code);
|
||||
|
||||
expect(ky.post).toBeCalledWith('/api/session/passwordless/sms/verify', {
|
||||
json: {
|
||||
phone,
|
||||
code,
|
||||
flow: MessageTypes.SignIn,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sendSignInEmailPasscode', async () => {
|
||||
await sendSignInEmailPasscode(email);
|
||||
expect(ky.post).toBeCalledWith('/api/session/passwordless/email/send', {
|
||||
json: {
|
||||
email,
|
||||
flow: MessageTypes.SignIn,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('verifySignInEmailPasscode', async () => {
|
||||
mockKyPost.mockReturnValueOnce({
|
||||
json: () => ({
|
||||
redirectTo: '/',
|
||||
}),
|
||||
});
|
||||
|
||||
await verifySignInEmailPasscode(email, code);
|
||||
|
||||
expect(ky.post).toBeCalledWith('/api/session/passwordless/email/verify', {
|
||||
json: {
|
||||
email,
|
||||
code,
|
||||
flow: MessageTypes.SignIn,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('consent', async () => {
|
||||
await consent();
|
||||
expect(ky.post).toBeCalledWith('/api/session/consent');
|
||||
});
|
||||
|
||||
it('registerWithSms', async () => {
|
||||
await registerWithSms();
|
||||
expect(ky.post).toBeCalledWith('/api/session/register/passwordless/sms');
|
||||
});
|
||||
|
||||
it('registerWithEmail', async () => {
|
||||
await registerWithEmail();
|
||||
expect(ky.post).toBeCalledWith('/api/session/register/passwordless/email');
|
||||
});
|
||||
|
||||
it('sendRegisterSmsPasscode', async () => {
|
||||
await sendRegisterSmsPasscode(phone);
|
||||
expect(ky.post).toBeCalledWith('/api/session/passwordless/sms/send', {
|
||||
json: {
|
||||
phone,
|
||||
flow: MessageTypes.Register,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('verifyRegisterSmsPasscode', async () => {
|
||||
await verifyRegisterSmsPasscode(phone, code);
|
||||
expect(ky.post).toBeCalledWith('/api/session/passwordless/sms/verify', {
|
||||
json: {
|
||||
phone,
|
||||
code,
|
||||
flow: MessageTypes.Register,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sendRegisterEmailPasscode', async () => {
|
||||
await sendRegisterEmailPasscode(email);
|
||||
expect(ky.post).toBeCalledWith('/api/session/passwordless/email/send', {
|
||||
json: {
|
||||
email,
|
||||
flow: MessageTypes.Register,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('verifyRegisterEmailPasscode', async () => {
|
||||
await verifyRegisterEmailPasscode(email, code);
|
||||
expect(ky.post).toBeCalledWith('/api/session/passwordless/email/verify', {
|
||||
json: {
|
||||
email,
|
||||
code,
|
||||
flow: MessageTypes.Register,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sendForgotPasswordSmsPasscode', async () => {
|
||||
await sendForgotPasswordSmsPasscode(phone);
|
||||
expect(ky.post).toBeCalledWith('/api/session/passwordless/sms/send', {
|
||||
json: {
|
||||
phone,
|
||||
flow: MessageTypes.ForgotPassword,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('verifyForgotPasswordSmsPasscode', async () => {
|
||||
await verifyForgotPasswordSmsPasscode(phone, code);
|
||||
expect(ky.post).toBeCalledWith('/api/session/passwordless/sms/verify', {
|
||||
json: {
|
||||
phone,
|
||||
code,
|
||||
flow: MessageTypes.ForgotPassword,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sendForgotPasswordEmailPasscode', async () => {
|
||||
await sendForgotPasswordEmailPasscode(email);
|
||||
expect(ky.post).toBeCalledWith('/api/session/passwordless/email/send', {
|
||||
json: {
|
||||
email,
|
||||
flow: MessageTypes.ForgotPassword,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('verifyForgotPasswordEmailPasscode', async () => {
|
||||
await verifyForgotPasswordEmailPasscode(email, code);
|
||||
expect(ky.post).toBeCalledWith('/api/session/passwordless/email/verify', {
|
||||
json: {
|
||||
email,
|
||||
code,
|
||||
flow: MessageTypes.ForgotPassword,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('invokeSocialSignIn', async () => {
|
||||
await invokeSocialSignIn('connectorId', 'state', 'redirectUri');
|
||||
expect(ky.post).toBeCalledWith('/api/session/sign-in/social', {
|
||||
|
@ -279,13 +83,4 @@ describe('api', () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('resetPassword', async () => {
|
||||
await resetPassword('password');
|
||||
expect(ky.post).toBeCalledWith('/api/session/forgot-password/reset', {
|
||||
json: {
|
||||
password: 'password',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,12 +5,15 @@ import type {
|
|||
UsernamePasswordPayload,
|
||||
EmailPasswordPayload,
|
||||
PhonePasswordPayload,
|
||||
EmailPasscodePayload,
|
||||
PhonePasscodePayload,
|
||||
} from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
|
||||
import api from './api';
|
||||
|
||||
const interactionPrefix = '/api/interaction';
|
||||
const verificationPath = `verification`;
|
||||
|
||||
type Response = {
|
||||
redirectTo: string;
|
||||
|
@ -60,5 +63,92 @@ export const setUserPassword = async (password: string) => {
|
|||
},
|
||||
});
|
||||
|
||||
const result = await api.post(`${interactionPrefix}/submit`).json<Response | undefined>();
|
||||
|
||||
// Reset password does not have any response body
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
return result || { success: true };
|
||||
};
|
||||
|
||||
export type SendPasscodePayload = { email: string } | { phone: string };
|
||||
|
||||
export const putInteraction = async (event: InteractionEvent) =>
|
||||
api.put(`${interactionPrefix}`, { json: { event } });
|
||||
|
||||
export const sendPasscode = async (payload: SendPasscodePayload) => {
|
||||
await api.post(`${interactionPrefix}/${verificationPath}/passcode`, { json: payload });
|
||||
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
export const signInWithPasscodeIdentifier = async (
|
||||
payload: EmailPasscodePayload | PhonePasscodePayload,
|
||||
socialToBind?: string
|
||||
) => {
|
||||
await api.patch(`${interactionPrefix}/identifiers`, {
|
||||
json: payload,
|
||||
});
|
||||
|
||||
if (socialToBind) {
|
||||
// TODO: bind social account
|
||||
}
|
||||
|
||||
return api.post(`${interactionPrefix}/submit`).json<Response>();
|
||||
};
|
||||
|
||||
export const addProfileWithPasscodeIdentifier = async (
|
||||
payload: EmailPasscodePayload | PhonePasscodePayload,
|
||||
socialToBind?: string
|
||||
) => {
|
||||
await api.patch(`${interactionPrefix}/identifiers`, {
|
||||
json: payload,
|
||||
});
|
||||
|
||||
const { passcode, ...identifier } = payload;
|
||||
|
||||
await api.patch(`${interactionPrefix}/profile`, {
|
||||
json: identifier,
|
||||
});
|
||||
|
||||
if (socialToBind) {
|
||||
// TODO: bind social account
|
||||
}
|
||||
|
||||
return api.post(`${interactionPrefix}/submit`).json<Response>();
|
||||
};
|
||||
|
||||
export const verifyForgotPasswordPasscodeIdentifier = async (
|
||||
payload: EmailPasscodePayload | PhonePasscodePayload
|
||||
) => {
|
||||
await api.patch(`${interactionPrefix}/identifiers`, {
|
||||
json: payload,
|
||||
});
|
||||
|
||||
return api.post(`${interactionPrefix}/submit`).json<Response>();
|
||||
};
|
||||
|
||||
export const signInWithVerifierIdentifier = async () => {
|
||||
await api.delete(`${interactionPrefix}/profile`);
|
||||
|
||||
await api.put(`${interactionPrefix}/event`, {
|
||||
json: {
|
||||
event: InteractionEvent.SignIn,
|
||||
},
|
||||
});
|
||||
|
||||
return api.post(`${interactionPrefix}/submit`).json<Response>();
|
||||
};
|
||||
|
||||
export const registerWithVerifiedIdentifier = async (payload: SendPasscodePayload) => {
|
||||
await api.put(`${interactionPrefix}/event`, {
|
||||
json: {
|
||||
event: InteractionEvent.Register,
|
||||
},
|
||||
});
|
||||
|
||||
await api.put(`${interactionPrefix}/profile`, {
|
||||
json: payload,
|
||||
});
|
||||
|
||||
return api.post(`${interactionPrefix}/submit`).json<Response>();
|
||||
};
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
import { MessageTypes } from '@logto/connector-kit';
|
||||
|
||||
import api from './api';
|
||||
|
||||
const apiPrefix = '/api/session';
|
||||
|
||||
type Response = {
|
||||
redirectTo: string;
|
||||
};
|
||||
|
||||
export const registerWithSms = async () =>
|
||||
api.post(`${apiPrefix}/register/passwordless/sms`).json<Response>();
|
||||
|
||||
export const registerWithEmail = async () =>
|
||||
api.post(`${apiPrefix}/register/passwordless/email`).json<Response>();
|
||||
|
||||
export const sendRegisterSmsPasscode = async (phone: string) => {
|
||||
await api
|
||||
.post(`${apiPrefix}/passwordless/sms/send`, {
|
||||
json: {
|
||||
phone,
|
||||
flow: MessageTypes.Register,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
export const verifyRegisterSmsPasscode = async (phone: string, code: string) =>
|
||||
api
|
||||
.post(`${apiPrefix}/passwordless/sms/verify`, {
|
||||
json: {
|
||||
phone,
|
||||
code,
|
||||
flow: MessageTypes.Register,
|
||||
},
|
||||
})
|
||||
.json<Response>();
|
||||
|
||||
export const sendRegisterEmailPasscode = async (email: string) => {
|
||||
await api
|
||||
.post(`${apiPrefix}/passwordless/email/send`, {
|
||||
json: {
|
||||
email,
|
||||
flow: MessageTypes.Register,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
export const verifyRegisterEmailPasscode = async (email: string, code: string) =>
|
||||
api
|
||||
.post(`${apiPrefix}/passwordless/email/verify`, {
|
||||
json: {
|
||||
email,
|
||||
code,
|
||||
flow: MessageTypes.Register,
|
||||
},
|
||||
})
|
||||
.json<Response>();
|
|
@ -1,100 +0,0 @@
|
|||
import { MessageTypes } from '@logto/connector-kit';
|
||||
|
||||
import api from './api';
|
||||
import { bindSocialAccount } from './social';
|
||||
|
||||
const apiPrefix = '/api/session';
|
||||
|
||||
type Response = {
|
||||
redirectTo: string;
|
||||
};
|
||||
|
||||
export const signInWithSms = async (socialToBind?: string) => {
|
||||
const result = await api.post(`${apiPrefix}/sign-in/passwordless/sms`).json<Response>();
|
||||
|
||||
if (result.redirectTo && socialToBind) {
|
||||
await bindSocialAccount(socialToBind);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const signInWithEmail = async (socialToBind?: string) => {
|
||||
const result = await api.post(`${apiPrefix}/sign-in/passwordless/email`).json<Response>();
|
||||
|
||||
if (result.redirectTo && socialToBind) {
|
||||
await bindSocialAccount(socialToBind);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const sendSignInSmsPasscode = async (phone: string) => {
|
||||
await api
|
||||
.post(`${apiPrefix}/passwordless/sms/send`, {
|
||||
json: {
|
||||
phone,
|
||||
flow: MessageTypes.SignIn,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
export const verifySignInSmsPasscode = async (
|
||||
phone: string,
|
||||
code: string,
|
||||
socialToBind?: string
|
||||
) => {
|
||||
const result = await api
|
||||
.post(`${apiPrefix}/passwordless/sms/verify`, {
|
||||
json: {
|
||||
phone,
|
||||
code,
|
||||
flow: MessageTypes.SignIn,
|
||||
},
|
||||
})
|
||||
.json<Response>();
|
||||
|
||||
if (result.redirectTo && socialToBind) {
|
||||
await bindSocialAccount(socialToBind);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const sendSignInEmailPasscode = async (email: string) => {
|
||||
await api
|
||||
.post(`${apiPrefix}/passwordless/email/send`, {
|
||||
json: {
|
||||
email,
|
||||
flow: MessageTypes.SignIn,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
export const verifySignInEmailPasscode = async (
|
||||
email: string,
|
||||
code: string,
|
||||
socialToBind?: string
|
||||
) => {
|
||||
const result = await api
|
||||
.post(`${apiPrefix}/passwordless/email/verify`, {
|
||||
json: {
|
||||
email,
|
||||
code,
|
||||
flow: MessageTypes.SignIn,
|
||||
},
|
||||
})
|
||||
.json<Response>();
|
||||
|
||||
if (result.redirectTo && socialToBind) {
|
||||
await bindSocialAccount(socialToBind);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
|
@ -1,47 +1,22 @@
|
|||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { InteractionEvent } from '@logto/schemas';
|
||||
|
||||
import { UserFlow } from '@/types';
|
||||
|
||||
import { sendContinueSetEmailPasscode, sendContinueSetPhonePasscode } from './continue';
|
||||
import { sendForgotPasswordEmailPasscode, sendForgotPasswordSmsPasscode } from './forgot-password';
|
||||
import { sendRegisterEmailPasscode, sendRegisterSmsPasscode } from './register';
|
||||
import { sendSignInEmailPasscode, sendSignInSmsPasscode } from './sign-in';
|
||||
import type { SendPasscodePayload } from './interaction';
|
||||
import { putInteraction, sendPasscode } from './interaction';
|
||||
|
||||
export type PasscodeChannel = SignInIdentifier.Email | SignInIdentifier.Sms;
|
||||
|
||||
// TODO: @simeng-li merge in to one single api
|
||||
|
||||
export const getSendPasscodeApi = (
|
||||
type: UserFlow,
|
||||
method: PasscodeChannel
|
||||
): ((_address: string) => Promise<{ success: boolean }>) => {
|
||||
if (type === UserFlow.forgotPassword && method === SignInIdentifier.Email) {
|
||||
return sendForgotPasswordEmailPasscode;
|
||||
export const getSendPasscodeApi = (type: UserFlow) => async (payload: SendPasscodePayload) => {
|
||||
if (type === UserFlow.forgotPassword) {
|
||||
await putInteraction(InteractionEvent.ForgotPassword);
|
||||
}
|
||||
|
||||
if (type === UserFlow.forgotPassword && method === SignInIdentifier.Sms) {
|
||||
return sendForgotPasswordSmsPasscode;
|
||||
if (type === UserFlow.signIn) {
|
||||
await putInteraction(InteractionEvent.SignIn);
|
||||
}
|
||||
|
||||
if (type === UserFlow.signIn && method === SignInIdentifier.Email) {
|
||||
return sendSignInEmailPasscode;
|
||||
if (type === UserFlow.register) {
|
||||
await putInteraction(InteractionEvent.Register);
|
||||
}
|
||||
|
||||
if (type === UserFlow.signIn && method === SignInIdentifier.Sms) {
|
||||
return sendSignInSmsPasscode;
|
||||
}
|
||||
|
||||
if (type === UserFlow.register && method === SignInIdentifier.Email) {
|
||||
return sendRegisterEmailPasscode;
|
||||
}
|
||||
|
||||
if (type === UserFlow.register && method === SignInIdentifier.Sms) {
|
||||
return sendRegisterSmsPasscode;
|
||||
}
|
||||
|
||||
if (type === UserFlow.continue && method === SignInIdentifier.Email) {
|
||||
return sendContinueSetEmailPasscode;
|
||||
}
|
||||
|
||||
return sendContinueSetPhonePasscode;
|
||||
return sendPasscode(payload);
|
||||
};
|
||||
|
|
|
@ -2,14 +2,15 @@ import { fireEvent, waitFor, act } from '@testing-library/react';
|
|||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import { sendContinueSetEmailPasscode } from '@/apis/continue';
|
||||
import { putInteraction, sendPasscode } from '@/apis/interaction';
|
||||
|
||||
import EmailContinue from './EmailContinue';
|
||||
|
||||
const mockedNavigate = jest.fn();
|
||||
|
||||
jest.mock('@/apis/continue', () => ({
|
||||
sendContinueSetEmailPasscode: jest.fn(() => ({ success: true })),
|
||||
jest.mock('@/apis/interaction', () => ({
|
||||
sendPasscode: jest.fn(() => ({ success: true })),
|
||||
putInteraction: jest.fn(() => ({ success: true })),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
|
@ -39,7 +40,8 @@ describe('EmailContinue', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendContinueSetEmailPasscode).toBeCalledWith(email);
|
||||
expect(putInteraction).not.toBeCalled();
|
||||
expect(sendPasscode).toBeCalledWith({ email });
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{ pathname: '/continue/email/passcode-validation', search: '' },
|
||||
{ state: { email } }
|
||||
|
|
|
@ -138,7 +138,7 @@ describe('<EmailForm/>', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toBeCalledWith('foo@logto.io');
|
||||
expect(onSubmit).toBeCalledWith({ email: 'foo@logto.io' });
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -166,7 +166,7 @@ describe('<EmailForm/>', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toBeCalledWith('foo@logto.io');
|
||||
expect(onSubmit).toBeCalledWith({ email: 'foo@logto.io' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@ type Props = {
|
|||
errorMessage?: string;
|
||||
submitButtonText?: TFuncKey;
|
||||
clearErrorMessage?: () => void;
|
||||
onSubmit: (email: string) => Promise<void> | void;
|
||||
onSubmit: (payload: { email: string }) => Promise<void> | void;
|
||||
};
|
||||
|
||||
type FieldState = {
|
||||
|
@ -59,9 +59,9 @@ const EmailForm = ({
|
|||
return;
|
||||
}
|
||||
|
||||
await onSubmit(fieldValue.email);
|
||||
await onSubmit(fieldValue);
|
||||
},
|
||||
[validateForm, hasTerms, termsValidation, onSubmit, fieldValue.email]
|
||||
[validateForm, hasTerms, termsValidation, onSubmit, fieldValue]
|
||||
);
|
||||
|
||||
const { onChange, ...rest } = register('email', emailValidation);
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import { InteractionEvent } from '@logto/schemas';
|
||||
import { fireEvent, waitFor, act } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import { sendRegisterEmailPasscode } from '@/apis/register';
|
||||
import { putInteraction, sendPasscode } from '@/apis/interaction';
|
||||
|
||||
import EmailRegister from './EmailRegister';
|
||||
|
||||
const mockedNavigate = jest.fn();
|
||||
|
||||
jest.mock('@/apis/register', () => ({
|
||||
sendRegisterEmailPasscode: jest.fn(() => ({ success: true })),
|
||||
jest.mock('@/apis/interaction', () => ({
|
||||
sendPasscode: jest.fn(() => ({ success: true })),
|
||||
putInteraction: jest.fn(() => ({ success: true })),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
|
@ -39,7 +41,8 @@ describe('EmailRegister', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendRegisterEmailPasscode).toBeCalledWith(email);
|
||||
expect(putInteraction).toBeCalledWith(InteractionEvent.Register);
|
||||
expect(sendPasscode).toBeCalledWith({ email });
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{ pathname: '/register/email/passcode-validation', search: '' },
|
||||
{ state: { email } }
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { InteractionEvent, SignInIdentifier } from '@logto/schemas';
|
||||
import { fireEvent, waitFor, act } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import { sendForgotPasswordEmailPasscode } from '@/apis/forgot-password';
|
||||
import { putInteraction, sendPasscode } from '@/apis/interaction';
|
||||
import { UserFlow } from '@/types';
|
||||
|
||||
import EmailResetPassword from './EmailResetPassword';
|
||||
|
||||
const mockedNavigate = jest.fn();
|
||||
|
||||
jest.mock('@/apis/forgot-password', () => ({
|
||||
sendForgotPasswordEmailPasscode: jest.fn(() => ({ success: true })),
|
||||
jest.mock('@/apis/interaction', () => ({
|
||||
sendPasscode: jest.fn(() => ({ success: true })),
|
||||
putInteraction: jest.fn(() => ({ success: true })),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
|
@ -41,7 +42,8 @@ describe('EmailRegister', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendForgotPasswordEmailPasscode).toBeCalledWith(email);
|
||||
expect(putInteraction).toBeCalledWith(InteractionEvent.ForgotPassword);
|
||||
expect(sendPasscode).toBeCalledWith({ email });
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{
|
||||
pathname: `/${UserFlow.forgotPassword}/${SignInIdentifier.Email}/passcode-validation`,
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { InteractionEvent, SignInIdentifier } from '@logto/schemas';
|
||||
import { fireEvent, waitFor, act } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import { sendSignInEmailPasscode } from '@/apis/sign-in';
|
||||
import { sendPasscode, putInteraction } from '@/apis/interaction';
|
||||
|
||||
import EmailSignIn from './EmailSignIn';
|
||||
|
||||
const mockedNavigate = jest.fn();
|
||||
|
||||
jest.mock('@/apis/sign-in', () => ({
|
||||
sendSignInEmailPasscode: jest.fn(() => ({ success: true })),
|
||||
jest.mock('@/apis/interaction', () => ({
|
||||
sendPasscode: jest.fn(() => ({ success: true })),
|
||||
putInteraction: jest.fn(() => ({ success: true })),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
|
@ -51,7 +52,8 @@ describe('EmailSignIn', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendSignInEmailPasscode).not.toBeCalled();
|
||||
expect(putInteraction).not.toBeCalled();
|
||||
expect(sendPasscode).not.toBeCalled();
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{ pathname: '/sign-in/email/password', search: '' },
|
||||
{ state: { email } }
|
||||
|
@ -59,7 +61,7 @@ describe('EmailSignIn', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('EmailSignIn form with password true but not primary verification code false', async () => {
|
||||
test('EmailSignIn form with password true but verification code false', async () => {
|
||||
const { container, getByText } = renderWithPageContext(
|
||||
<MemoryRouter>
|
||||
<EmailSignIn
|
||||
|
@ -85,7 +87,8 @@ describe('EmailSignIn', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendSignInEmailPasscode).not.toBeCalled();
|
||||
expect(putInteraction).not.toBeCalled();
|
||||
expect(sendPasscode).not.toBeCalled();
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{ pathname: '/sign-in/email/password', search: '' },
|
||||
{ state: { email } }
|
||||
|
@ -93,7 +96,7 @@ describe('EmailSignIn', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('EmailSignIn form with password true but not primary verification code true', async () => {
|
||||
test('EmailSignIn form with password true but not primary, verification code true', async () => {
|
||||
const { container, getByText } = renderWithPageContext(
|
||||
<MemoryRouter>
|
||||
<EmailSignIn
|
||||
|
@ -120,7 +123,8 @@ describe('EmailSignIn', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendSignInEmailPasscode).toBeCalledWith(email);
|
||||
expect(putInteraction).toBeCalledWith(InteractionEvent.SignIn);
|
||||
expect(sendPasscode).toBeCalledWith({ email });
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{ pathname: '/sign-in/email/passcode-validation', search: '' },
|
||||
{ state: { email } }
|
||||
|
@ -155,7 +159,8 @@ describe('EmailSignIn', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendSignInEmailPasscode).toBeCalledWith(email);
|
||||
expect(putInteraction).toBeCalledWith(InteractionEvent.SignIn);
|
||||
expect(sendPasscode).toBeCalledWith({ email });
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{ pathname: '/sign-in/email/passcode-validation', search: '' },
|
||||
{ state: { email } }
|
||||
|
|
|
@ -3,16 +3,10 @@ import { act, fireEvent, waitFor } from '@testing-library/react';
|
|||
|
||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import {
|
||||
verifyContinueSetEmailPasscode,
|
||||
continueApi,
|
||||
verifyContinueSetSmsPasscode,
|
||||
} from '@/apis/continue';
|
||||
import {
|
||||
verifyForgotPasswordEmailPasscode,
|
||||
verifyForgotPasswordSmsPasscode,
|
||||
} from '@/apis/forgot-password';
|
||||
import { verifyRegisterEmailPasscode, verifyRegisterSmsPasscode } from '@/apis/register';
|
||||
import { verifySignInEmailPasscode, verifySignInSmsPasscode } from '@/apis/sign-in';
|
||||
verifyForgotPasswordPasscodeIdentifier,
|
||||
signInWithPasscodeIdentifier,
|
||||
addProfileWithPasscodeIdentifier,
|
||||
} from '@/apis/interaction';
|
||||
import { UserFlow } from '@/types';
|
||||
|
||||
import PasscodeValidation from '.';
|
||||
|
@ -32,25 +26,10 @@ jest.mock('@/apis/utils', () => ({
|
|||
getSendPasscodeApi: () => sendPasscodeApi,
|
||||
}));
|
||||
|
||||
jest.mock('@/apis/sign-in', () => ({
|
||||
verifySignInEmailPasscode: jest.fn(),
|
||||
verifySignInSmsPasscode: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@/apis/register', () => ({
|
||||
verifyRegisterEmailPasscode: jest.fn(),
|
||||
verifyRegisterSmsPasscode: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@/apis/forgot-password', () => ({
|
||||
verifyForgotPasswordEmailPasscode: jest.fn(),
|
||||
verifyForgotPasswordSmsPasscode: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@/apis/continue', () => ({
|
||||
verifyContinueSetEmailPasscode: jest.fn(),
|
||||
verifyContinueSetSmsPasscode: jest.fn(),
|
||||
continueApi: jest.fn(),
|
||||
jest.mock('@/apis/interaction', () => ({
|
||||
verifyForgotPasswordPasscodeIdentifier: jest.fn(),
|
||||
signInWithPasscodeIdentifier: jest.fn(),
|
||||
addProfileWithPasscodeIdentifier: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('<PasscodeValidation />', () => {
|
||||
|
@ -103,12 +82,12 @@ describe('<PasscodeValidation />', () => {
|
|||
fireEvent.click(resendButton);
|
||||
});
|
||||
|
||||
expect(sendPasscodeApi).toBeCalledWith(email);
|
||||
expect(sendPasscodeApi).toBeCalledWith({ email });
|
||||
});
|
||||
|
||||
describe('sign-in', () => {
|
||||
it('fire email sign-in validate passcode event', async () => {
|
||||
(verifySignInEmailPasscode as jest.Mock).mockImplementationOnce(() => ({
|
||||
(signInWithPasscodeIdentifier as jest.Mock).mockImplementationOnce(() => ({
|
||||
redirectTo: 'foo.com',
|
||||
}));
|
||||
|
||||
|
@ -124,7 +103,10 @@ describe('<PasscodeValidation />', () => {
|
|||
}
|
||||
|
||||
await waitFor(() => {
|
||||
expect(verifySignInEmailPasscode).toBeCalledWith(email, '111111', undefined);
|
||||
expect(signInWithPasscodeIdentifier).toBeCalledWith(
|
||||
{ email, passcode: '111111' },
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
|
@ -133,7 +115,7 @@ describe('<PasscodeValidation />', () => {
|
|||
});
|
||||
|
||||
it('fire sms sign-in validate passcode event', async () => {
|
||||
(verifySignInSmsPasscode as jest.Mock).mockImplementationOnce(() => ({
|
||||
(signInWithPasscodeIdentifier as jest.Mock).mockImplementationOnce(() => ({
|
||||
redirectTo: 'foo.com',
|
||||
}));
|
||||
|
||||
|
@ -149,7 +131,13 @@ describe('<PasscodeValidation />', () => {
|
|||
}
|
||||
|
||||
await waitFor(() => {
|
||||
expect(verifySignInSmsPasscode).toBeCalledWith(phone, '111111', undefined);
|
||||
expect(signInWithPasscodeIdentifier).toBeCalledWith(
|
||||
{
|
||||
phone,
|
||||
passcode: '111111',
|
||||
},
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
|
@ -160,7 +148,7 @@ describe('<PasscodeValidation />', () => {
|
|||
|
||||
describe('register', () => {
|
||||
it('fire email register validate passcode event', async () => {
|
||||
(verifyRegisterEmailPasscode as jest.Mock).mockImplementationOnce(() => ({
|
||||
(addProfileWithPasscodeIdentifier as jest.Mock).mockImplementationOnce(() => ({
|
||||
redirectTo: 'foo.com',
|
||||
}));
|
||||
|
||||
|
@ -180,7 +168,10 @@ describe('<PasscodeValidation />', () => {
|
|||
}
|
||||
|
||||
await waitFor(() => {
|
||||
expect(verifyRegisterEmailPasscode).toBeCalledWith(email, '111111');
|
||||
expect(addProfileWithPasscodeIdentifier).toBeCalledWith({
|
||||
email,
|
||||
passcode: '111111',
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
|
@ -189,7 +180,7 @@ describe('<PasscodeValidation />', () => {
|
|||
});
|
||||
|
||||
it('fire sms register validate passcode event', async () => {
|
||||
(verifyRegisterSmsPasscode as jest.Mock).mockImplementationOnce(() => ({
|
||||
(addProfileWithPasscodeIdentifier as jest.Mock).mockImplementationOnce(() => ({
|
||||
redirectTo: 'foo.com',
|
||||
}));
|
||||
|
||||
|
@ -205,7 +196,7 @@ describe('<PasscodeValidation />', () => {
|
|||
}
|
||||
|
||||
await waitFor(() => {
|
||||
expect(verifyRegisterSmsPasscode).toBeCalledWith(phone, '111111');
|
||||
expect(addProfileWithPasscodeIdentifier).toBeCalledWith({ phone, passcode: '111111' });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
|
@ -216,7 +207,7 @@ describe('<PasscodeValidation />', () => {
|
|||
|
||||
describe('forgot password', () => {
|
||||
it('fire email forgot-password validate passcode event', async () => {
|
||||
(verifyForgotPasswordEmailPasscode as jest.Mock).mockImplementationOnce(() => ({
|
||||
(verifyForgotPasswordPasscodeIdentifier as jest.Mock).mockImplementationOnce(() => ({
|
||||
success: true,
|
||||
}));
|
||||
|
||||
|
@ -237,17 +228,17 @@ describe('<PasscodeValidation />', () => {
|
|||
}
|
||||
|
||||
await waitFor(() => {
|
||||
expect(verifyForgotPasswordEmailPasscode).toBeCalledWith(email, '111111');
|
||||
expect(verifyForgotPasswordPasscodeIdentifier).toBeCalledWith({
|
||||
email,
|
||||
passcode: '111111',
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(window.location.replace).not.toBeCalled();
|
||||
expect(mockedNavigate).toBeCalledWith('/forgot-password/reset', { replace: true });
|
||||
});
|
||||
// TODO: @simeng test exception flow to fulfill the password
|
||||
});
|
||||
|
||||
it('fire sms forgot-password validate passcode event', async () => {
|
||||
(verifyForgotPasswordSmsPasscode as jest.Mock).mockImplementationOnce(() => ({
|
||||
(verifyForgotPasswordPasscodeIdentifier as jest.Mock).mockImplementationOnce(() => ({
|
||||
success: true,
|
||||
}));
|
||||
|
||||
|
@ -268,22 +259,21 @@ describe('<PasscodeValidation />', () => {
|
|||
}
|
||||
|
||||
await waitFor(() => {
|
||||
expect(verifyForgotPasswordSmsPasscode).toBeCalledWith(phone, '111111');
|
||||
expect(verifyForgotPasswordPasscodeIdentifier).toBeCalledWith({
|
||||
phone,
|
||||
passcode: '111111',
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(window.location.replace).not.toBeCalled();
|
||||
expect(mockedNavigate).toBeCalledWith('/forgot-password/reset', { replace: true });
|
||||
});
|
||||
// TODO: @simeng test exception flow to fulfill the password
|
||||
});
|
||||
});
|
||||
|
||||
describe('continue flow', () => {
|
||||
it('set email', async () => {
|
||||
(verifyContinueSetEmailPasscode as jest.Mock).mockImplementationOnce(() => ({
|
||||
success: true,
|
||||
(addProfileWithPasscodeIdentifier as jest.Mock).mockImplementationOnce(() => ({
|
||||
redirectTo: '/redirect',
|
||||
}));
|
||||
(continueApi as jest.Mock).mockImplementationOnce(() => ({ redirectTo: '/redirect' }));
|
||||
|
||||
const { container } = renderWithPageContext(
|
||||
<PasscodeValidation
|
||||
|
@ -302,20 +292,24 @@ describe('<PasscodeValidation />', () => {
|
|||
}
|
||||
|
||||
await waitFor(() => {
|
||||
expect(verifyContinueSetEmailPasscode).toBeCalledWith(email, '111111');
|
||||
expect(addProfileWithPasscodeIdentifier).toBeCalledWith(
|
||||
{
|
||||
email,
|
||||
passcode: '111111',
|
||||
},
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(continueApi).toBeCalledWith('email', email, undefined);
|
||||
expect(window.location.replace).toBeCalledWith('/redirect');
|
||||
});
|
||||
});
|
||||
|
||||
it('set Phone', async () => {
|
||||
(verifyContinueSetSmsPasscode as jest.Mock).mockImplementationOnce(() => ({
|
||||
success: true,
|
||||
(addProfileWithPasscodeIdentifier as jest.Mock).mockImplementationOnce(() => ({
|
||||
redirectTo: '/redirect',
|
||||
}));
|
||||
(continueApi as jest.Mock).mockImplementationOnce(() => ({ redirectTo: '/redirect' }));
|
||||
|
||||
const { container } = renderWithPageContext(
|
||||
<PasscodeValidation type={UserFlow.continue} method={SignInIdentifier.Sms} target={phone} />
|
||||
|
@ -330,11 +324,16 @@ describe('<PasscodeValidation />', () => {
|
|||
}
|
||||
|
||||
await waitFor(() => {
|
||||
expect(verifyContinueSetSmsPasscode).toBeCalledWith(phone, '111111');
|
||||
expect(addProfileWithPasscodeIdentifier).toBeCalledWith(
|
||||
{
|
||||
phone,
|
||||
passcode: '111111',
|
||||
},
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(continueApi).toBeCalledWith('phone', phone, undefined);
|
||||
expect(window.location.replace).toBeCalledWith('/redirect');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { useMemo, useCallback } from 'react';
|
||||
|
||||
import { verifyContinueSetEmailPasscode, continueApi } from '@/apis/continue';
|
||||
import { addProfileWithPasscodeIdentifier } from '@/apis/interaction';
|
||||
import type { ErrorHandlers } from '@/hooks/use-api';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import useRequiredProfileErrorHandler from '@/hooks/use-required-profile-error-handler';
|
||||
|
@ -24,45 +24,34 @@ const useContinueSetEmailPasscodeValidation = (email: string, errorCallback?: ()
|
|||
|
||||
const verifyPasscodeErrorHandlers: ErrorHandlers = useMemo(
|
||||
() => ({
|
||||
'user.email_not_exist': identifierNotExistErrorHandler,
|
||||
...requiredProfileErrorHandler,
|
||||
...sharedErrorHandlers,
|
||||
callback: errorCallback,
|
||||
}),
|
||||
[errorCallback, sharedErrorHandlers]
|
||||
[
|
||||
errorCallback,
|
||||
identifierNotExistErrorHandler,
|
||||
requiredProfileErrorHandler,
|
||||
sharedErrorHandlers,
|
||||
]
|
||||
);
|
||||
|
||||
const { run: verifyPasscode } = useApi(
|
||||
verifyContinueSetEmailPasscode,
|
||||
addProfileWithPasscodeIdentifier,
|
||||
verifyPasscodeErrorHandlers
|
||||
);
|
||||
|
||||
const setEmailErrorHandlers: ErrorHandlers = useMemo(
|
||||
() => ({
|
||||
'user.email_not_exist': identifierNotExistErrorHandler,
|
||||
...requiredProfileErrorHandler,
|
||||
callback: errorCallback,
|
||||
}),
|
||||
[errorCallback, identifierNotExistErrorHandler, requiredProfileErrorHandler]
|
||||
);
|
||||
|
||||
const { run: setEmail } = useApi(continueApi, setEmailErrorHandlers);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (code: string) => {
|
||||
const verified = await verifyPasscode(email, code);
|
||||
|
||||
if (!verified) {
|
||||
return;
|
||||
}
|
||||
|
||||
async (passcode: string) => {
|
||||
const socialToBind = getSearchParameters(location.search, SearchParameters.bindWithSocial);
|
||||
|
||||
const result = await setEmail('email', email, socialToBind);
|
||||
const result = await verifyPasscode({ email, passcode }, socialToBind);
|
||||
|
||||
if (result?.redirectTo) {
|
||||
window.location.replace(result.redirectTo);
|
||||
}
|
||||
},
|
||||
[email, setEmail, verifyPasscode]
|
||||
[email, verifyPasscode]
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { useMemo, useCallback } from 'react';
|
||||
|
||||
import { verifyContinueSetSmsPasscode, continueApi } from '@/apis/continue';
|
||||
import { addProfileWithPasscodeIdentifier } from '@/apis/interaction';
|
||||
import type { ErrorHandlers } from '@/hooks/use-api';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import useRequiredProfileErrorHandler from '@/hooks/use-required-profile-error-handler';
|
||||
|
@ -24,42 +24,34 @@ const useContinueSetSmsPasscodeValidation = (phone: string, errorCallback?: () =
|
|||
|
||||
const verifyPasscodeErrorHandlers: ErrorHandlers = useMemo(
|
||||
() => ({
|
||||
'user.phone_not_exist': identifierNotExistErrorHandler,
|
||||
...requiredProfileErrorHandler,
|
||||
...sharedErrorHandlers,
|
||||
callback: errorCallback,
|
||||
}),
|
||||
[errorCallback, sharedErrorHandlers]
|
||||
[
|
||||
errorCallback,
|
||||
identifierNotExistErrorHandler,
|
||||
requiredProfileErrorHandler,
|
||||
sharedErrorHandlers,
|
||||
]
|
||||
);
|
||||
|
||||
const { run: verifyPasscode } = useApi(verifyContinueSetSmsPasscode, verifyPasscodeErrorHandlers);
|
||||
|
||||
const setPhoneErrorHandlers: ErrorHandlers = useMemo(
|
||||
() => ({
|
||||
'user.phone_not_exist': identifierNotExistErrorHandler,
|
||||
...requiredProfileErrorHandler,
|
||||
callback: errorCallback,
|
||||
}),
|
||||
[errorCallback, identifierNotExistErrorHandler, requiredProfileErrorHandler]
|
||||
const { run: verifyPasscode } = useApi(
|
||||
addProfileWithPasscodeIdentifier,
|
||||
verifyPasscodeErrorHandlers
|
||||
);
|
||||
|
||||
const { run: setPhone } = useApi(continueApi, setPhoneErrorHandlers);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (code: string) => {
|
||||
const verified = await verifyPasscode(phone, code);
|
||||
|
||||
if (!verified) {
|
||||
return;
|
||||
}
|
||||
|
||||
async (passcode: string) => {
|
||||
const socialToBind = getSearchParameters(location.search, SearchParameters.bindWithSocial);
|
||||
|
||||
const result = await setPhone('phone', phone, socialToBind);
|
||||
const result = await verifyPasscode({ phone, passcode }, socialToBind);
|
||||
|
||||
if (result?.redirectTo) {
|
||||
window.location.replace(result.redirectTo);
|
||||
}
|
||||
},
|
||||
[phone, setPhone, verifyPasscode]
|
||||
[phone, verifyPasscode]
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { SignInIdentifier } from '@logto/schemas';
|
|||
import { useMemo, useEffect, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { verifyForgotPasswordEmailPasscode } from '@/apis/forgot-password';
|
||||
import { verifyForgotPasswordPasscodeIdentifier } from '@/apis/interaction';
|
||||
import type { ErrorHandlers } from '@/hooks/use-api';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import { UserFlow } from '@/types';
|
||||
|
@ -23,24 +23,30 @@ const useForgotPasswordEmailPasscodeValidation = (email: string, errorCallback?:
|
|||
const errorHandlers: ErrorHandlers = useMemo(
|
||||
() => ({
|
||||
'user.email_not_exist': identifierNotExistErrorHandler,
|
||||
'user.new_password_required_in_profile': () => {
|
||||
navigate(`/${UserFlow.forgotPassword}/reset`, { replace: true });
|
||||
},
|
||||
...sharedErrorHandlers,
|
||||
callback: errorCallback,
|
||||
}),
|
||||
[identifierNotExistErrorHandler, sharedErrorHandlers, errorCallback]
|
||||
[identifierNotExistErrorHandler, sharedErrorHandlers, errorCallback, navigate]
|
||||
);
|
||||
|
||||
const { result, run: verifyPasscode } = useApi(verifyForgotPasswordEmailPasscode, errorHandlers);
|
||||
const { result, run: verifyPasscode } = useApi(
|
||||
verifyForgotPasswordPasscodeIdentifier,
|
||||
errorHandlers
|
||||
);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (code: string) => {
|
||||
return verifyPasscode(email, code);
|
||||
async (passcode: string) => {
|
||||
return verifyPasscode({ email, passcode });
|
||||
},
|
||||
[email, verifyPasscode]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (result) {
|
||||
navigate(`/${UserFlow.forgotPassword}/reset`, { replace: true });
|
||||
navigate(`/${UserFlow.signIn}`, { replace: true });
|
||||
}
|
||||
}, [navigate, result]);
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { SignInIdentifier } from '@logto/schemas';
|
|||
import { useMemo, useEffect, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { verifyForgotPasswordSmsPasscode } from '@/apis/forgot-password';
|
||||
import { verifyForgotPasswordPasscodeIdentifier } from '@/apis/interaction';
|
||||
import type { ErrorHandlers } from '@/hooks/use-api';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import { UserFlow } from '@/types';
|
||||
|
@ -13,6 +13,7 @@ import useSharedErrorHandler from './use-shared-error-handler';
|
|||
const useForgotPasswordSmsPasscodeValidation = (phone: string, errorCallback?: () => void) => {
|
||||
const navigate = useNavigate();
|
||||
const { sharedErrorHandlers, errorMessage, clearErrorMessage } = useSharedErrorHandler();
|
||||
|
||||
const identifierNotExistErrorHandler = useIdentifierErrorAlert(
|
||||
UserFlow.forgotPassword,
|
||||
SignInIdentifier.Sms,
|
||||
|
@ -22,24 +23,30 @@ const useForgotPasswordSmsPasscodeValidation = (phone: string, errorCallback?: (
|
|||
const errorHandlers: ErrorHandlers = useMemo(
|
||||
() => ({
|
||||
'user.phone_not_exist': identifierNotExistErrorHandler,
|
||||
'user.new_password_required_in_profile': () => {
|
||||
navigate(`/${UserFlow.forgotPassword}/reset`, { replace: true });
|
||||
},
|
||||
...sharedErrorHandlers,
|
||||
callback: errorCallback,
|
||||
}),
|
||||
[sharedErrorHandlers, errorCallback, identifierNotExistErrorHandler]
|
||||
[identifierNotExistErrorHandler, sharedErrorHandlers, errorCallback, navigate]
|
||||
);
|
||||
|
||||
const { result, run: verifyPasscode } = useApi(verifyForgotPasswordSmsPasscode, errorHandlers);
|
||||
const { result, run: verifyPasscode } = useApi(
|
||||
verifyForgotPasswordPasscodeIdentifier,
|
||||
errorHandlers
|
||||
);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (code: string) => {
|
||||
return verifyPasscode(phone, code);
|
||||
async (passcode: string) => {
|
||||
return verifyPasscode({ phone, passcode });
|
||||
},
|
||||
[phone, verifyPasscode]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (result) {
|
||||
navigate(`/${UserFlow.forgotPassword}/reset`, { replace: true });
|
||||
navigate(`/${UserFlow.signIn}`, { replace: true });
|
||||
}
|
||||
}, [navigate, result]);
|
||||
|
||||
|
|
|
@ -3,8 +3,7 @@ import { useMemo, useCallback, useEffect } from 'react';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { verifyRegisterEmailPasscode } from '@/apis/register';
|
||||
import { signInWithEmail } from '@/apis/sign-in';
|
||||
import { addProfileWithPasscodeIdentifier, signInWithVerifierIdentifier } from '@/apis/interaction';
|
||||
import type { ErrorHandlers } from '@/hooks/use-api';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import { useConfirmModal } from '@/hooks/use-confirm-modal';
|
||||
|
@ -25,7 +24,10 @@ const useRegisterWithEmailPasscodeValidation = (email: string, errorCallback?: (
|
|||
|
||||
const requiredProfileErrorHandlers = useRequiredProfileErrorHandler(true);
|
||||
|
||||
const { run: signInWithEmailAsync } = useApi(signInWithEmail, requiredProfileErrorHandlers);
|
||||
const { run: signInWithEmailAsync } = useApi(
|
||||
signInWithVerifierIdentifier,
|
||||
requiredProfileErrorHandlers
|
||||
);
|
||||
|
||||
const identifierExistErrorHandler = useIdentifierErrorAlert(
|
||||
UserFlow.register,
|
||||
|
@ -75,11 +77,11 @@ const useRegisterWithEmailPasscodeValidation = (email: string, errorCallback?: (
|
|||
]
|
||||
);
|
||||
|
||||
const { result, run: verifyPasscode } = useApi(verifyRegisterEmailPasscode, errorHandlers);
|
||||
const { result, run: verifyPasscode } = useApi(addProfileWithPasscodeIdentifier, errorHandlers);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (code: string) => {
|
||||
return verifyPasscode(email, code);
|
||||
async (passcode: string) => {
|
||||
return verifyPasscode({ email, passcode });
|
||||
},
|
||||
[email, verifyPasscode]
|
||||
);
|
||||
|
|
|
@ -3,8 +3,7 @@ import { useMemo, useCallback, useEffect } from 'react';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { verifyRegisterSmsPasscode } from '@/apis/register';
|
||||
import { signInWithSms } from '@/apis/sign-in';
|
||||
import { addProfileWithPasscodeIdentifier, signInWithVerifierIdentifier } from '@/apis/interaction';
|
||||
import type { ErrorHandlers } from '@/hooks/use-api';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import { useConfirmModal } from '@/hooks/use-confirm-modal';
|
||||
|
@ -25,7 +24,10 @@ const useRegisterWithSmsPasscodeValidation = (phone: string, errorCallback?: ()
|
|||
|
||||
const requiredProfileErrorHandlers = useRequiredProfileErrorHandler(true);
|
||||
|
||||
const { run: signInWithSmsAsync } = useApi(signInWithSms, requiredProfileErrorHandlers);
|
||||
const { run: signInWithSmsAsync } = useApi(
|
||||
signInWithVerifierIdentifier,
|
||||
requiredProfileErrorHandlers
|
||||
);
|
||||
|
||||
const identifierExistErrorHandler = useIdentifierErrorAlert(
|
||||
UserFlow.register,
|
||||
|
@ -75,7 +77,7 @@ const useRegisterWithSmsPasscodeValidation = (phone: string, errorCallback?: ()
|
|||
]
|
||||
);
|
||||
|
||||
const { result, run: verifyPasscode } = useApi(verifyRegisterSmsPasscode, errorHandlers);
|
||||
const { result, run: verifyPasscode } = useApi(addProfileWithPasscodeIdentifier, errorHandlers);
|
||||
|
||||
useEffect(() => {
|
||||
if (result?.redirectTo) {
|
||||
|
@ -84,8 +86,11 @@ const useRegisterWithSmsPasscodeValidation = (phone: string, errorCallback?: ()
|
|||
}, [result]);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (code: string) => {
|
||||
return verifyPasscode(phone, code);
|
||||
async (passcode: string) => {
|
||||
return verifyPasscode({
|
||||
phone,
|
||||
passcode,
|
||||
});
|
||||
},
|
||||
[phone, verifyPasscode]
|
||||
);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { SignInIdentifier } from '@logto/schemas';
|
||||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { t } from 'i18next';
|
||||
import { useCallback, useContext } from 'react';
|
||||
import { useTimer } from 'react-timer-hook';
|
||||
|
@ -29,16 +29,17 @@ const useResendPasscode = (
|
|||
expiryTimestamp: getTimeout(),
|
||||
});
|
||||
|
||||
const { run: sendPassCode } = useApi(getSendPasscodeApi(type, method));
|
||||
const { run: sendPassCode } = useApi(getSendPasscodeApi(type));
|
||||
|
||||
const onResendPasscode = useCallback(async () => {
|
||||
const result = await sendPassCode(target);
|
||||
const payload = method === SignInIdentifier.Email ? { email: target } : { phone: target };
|
||||
const result = await sendPassCode(payload);
|
||||
|
||||
if (result) {
|
||||
setToast(t('description.passcode_sent'));
|
||||
restart(getTimeout(), true);
|
||||
}
|
||||
}, [restart, sendPassCode, setToast, target]);
|
||||
}, [method, restart, sendPassCode, setToast, target]);
|
||||
|
||||
return {
|
||||
seconds,
|
||||
|
|
|
@ -3,8 +3,7 @@ import { useMemo, useCallback, useEffect } from 'react';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { registerWithEmail } from '@/apis/register';
|
||||
import { verifySignInEmailPasscode } from '@/apis/sign-in';
|
||||
import { signInWithPasscodeIdentifier, registerWithVerifiedIdentifier } from '@/apis/interaction';
|
||||
import type { ErrorHandlers } from '@/hooks/use-api';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import { useConfirmModal } from '@/hooks/use-confirm-modal';
|
||||
|
@ -26,7 +25,10 @@ const useSignInWithEmailPasscodeValidation = (email: string, errorCallback?: ()
|
|||
|
||||
const requiredProfileErrorHandlers = useRequiredProfileErrorHandler(true);
|
||||
|
||||
const { run: registerWithEmailAsync } = useApi(registerWithEmail, requiredProfileErrorHandlers);
|
||||
const { run: registerWithEmailAsync } = useApi(
|
||||
registerWithVerifiedIdentifier,
|
||||
requiredProfileErrorHandlers
|
||||
);
|
||||
|
||||
const socialToBind = getSearchParameters(location.search, SearchParameters.bindWithSocial);
|
||||
|
||||
|
@ -51,7 +53,7 @@ const useSignInWithEmailPasscodeValidation = (email: string, errorCallback?: ()
|
|||
return;
|
||||
}
|
||||
|
||||
const result = await registerWithEmailAsync();
|
||||
const result = await registerWithEmailAsync({ email });
|
||||
|
||||
if (result?.redirectTo) {
|
||||
window.location.replace(result.redirectTo);
|
||||
|
@ -80,7 +82,10 @@ const useSignInWithEmailPasscodeValidation = (email: string, errorCallback?: ()
|
|||
]
|
||||
);
|
||||
|
||||
const { result, run: verifyPasscode } = useApi(verifySignInEmailPasscode, errorHandlers);
|
||||
const { result, run: asyncSignInWithPasscodeIdentifier } = useApi(
|
||||
signInWithPasscodeIdentifier,
|
||||
errorHandlers
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (result?.redirectTo) {
|
||||
|
@ -89,10 +94,16 @@ const useSignInWithEmailPasscodeValidation = (email: string, errorCallback?: ()
|
|||
}, [result]);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (code: string) => {
|
||||
return verifyPasscode(email, code, socialToBind);
|
||||
async (passcode: string) => {
|
||||
return asyncSignInWithPasscodeIdentifier(
|
||||
{
|
||||
email,
|
||||
passcode,
|
||||
},
|
||||
socialToBind
|
||||
);
|
||||
},
|
||||
[email, socialToBind, verifyPasscode]
|
||||
[asyncSignInWithPasscodeIdentifier, email, socialToBind]
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
@ -3,8 +3,7 @@ import { useMemo, useCallback, useEffect } from 'react';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { registerWithSms } from '@/apis/register';
|
||||
import { verifySignInSmsPasscode } from '@/apis/sign-in';
|
||||
import { signInWithPasscodeIdentifier, registerWithVerifiedIdentifier } from '@/apis/interaction';
|
||||
import type { ErrorHandlers } from '@/hooks/use-api';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import { useConfirmModal } from '@/hooks/use-confirm-modal';
|
||||
|
@ -26,7 +25,10 @@ const useSignInWithSmsPasscodeValidation = (phone: string, errorCallback?: () =>
|
|||
|
||||
const requiredProfileErrorHandlers = useRequiredProfileErrorHandler(true);
|
||||
|
||||
const { run: registerWithSmsAsync } = useApi(registerWithSms, requiredProfileErrorHandlers);
|
||||
const { run: registerWithSmsAsync } = useApi(
|
||||
registerWithVerifiedIdentifier,
|
||||
requiredProfileErrorHandlers
|
||||
);
|
||||
|
||||
const socialToBind = getSearchParameters(location.search, SearchParameters.bindWithSocial);
|
||||
|
||||
|
@ -51,7 +53,7 @@ const useSignInWithSmsPasscodeValidation = (phone: string, errorCallback?: () =>
|
|||
return;
|
||||
}
|
||||
|
||||
const result = await registerWithSmsAsync();
|
||||
const result = await registerWithSmsAsync({ phone });
|
||||
|
||||
if (result?.redirectTo) {
|
||||
window.location.replace(result.redirectTo);
|
||||
|
@ -80,7 +82,10 @@ const useSignInWithSmsPasscodeValidation = (phone: string, errorCallback?: () =>
|
|||
]
|
||||
);
|
||||
|
||||
const { result, run: verifyPasscode } = useApi(verifySignInSmsPasscode, errorHandlers);
|
||||
const { result, run: asyncSignInWithPasscodeIdentifier } = useApi(
|
||||
signInWithPasscodeIdentifier,
|
||||
errorHandlers
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (result?.redirectTo) {
|
||||
|
@ -90,9 +95,15 @@ const useSignInWithSmsPasscodeValidation = (phone: string, errorCallback?: () =>
|
|||
|
||||
const onSubmit = useCallback(
|
||||
async (code: string) => {
|
||||
return verifyPasscode(phone, code, socialToBind);
|
||||
return asyncSignInWithPasscodeIdentifier(
|
||||
{
|
||||
phone,
|
||||
passcode: code,
|
||||
},
|
||||
socialToBind
|
||||
);
|
||||
},
|
||||
[phone, socialToBind, verifyPasscode]
|
||||
[phone, socialToBind, asyncSignInWithPasscodeIdentifier]
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { SignInIdentifier } from '@logto/schemas';
|
||||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { useContext, useEffect } from 'react';
|
||||
|
||||
import TextLink from '@/components/TextLink';
|
||||
|
@ -33,7 +33,7 @@ const PasswordlessSignInLink = ({ className, method, value }: Props) => {
|
|||
text="action.sign_in_via_passcode"
|
||||
onClick={() => {
|
||||
clearErrorMessage();
|
||||
void onSubmit(value);
|
||||
void onSubmit(method === SignInIdentifier.Email ? { email: value } : { phone: value });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { InteractionEvent, SignInIdentifier } from '@logto/schemas';
|
||||
import { fireEvent, waitFor, act } from '@testing-library/react';
|
||||
|
||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import { signInWithPasswordIdentifier } from '@/apis/interaction';
|
||||
import { sendSignInEmailPasscode, sendSignInSmsPasscode } from '@/apis/sign-in';
|
||||
import { signInWithPasswordIdentifier, putInteraction, sendPasscode } from '@/apis/interaction';
|
||||
import { UserFlow } from '@/types';
|
||||
|
||||
import PasswordSignInForm from '.';
|
||||
|
||||
jest.mock('@/apis/sign-in', () => ({
|
||||
sendSignInEmailPasscode: jest.fn(() => ({ success: true })),
|
||||
sendSignInSmsPasscode: jest.fn(() => ({ success: true })),
|
||||
}));
|
||||
|
||||
jest.mock('@/apis/interaction', () => ({
|
||||
signInWithPasswordIdentifier: jest.fn(() => ({ redirectTo: '/' })),
|
||||
sendPasscode: jest.fn(() => ({ success: true })),
|
||||
putInteraction: jest.fn(() => ({ success: true })),
|
||||
}));
|
||||
|
||||
const mockedNavigate = jest.fn();
|
||||
|
@ -80,7 +76,8 @@ describe('PasswordSignInForm', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendSignInEmailPasscode).toBeCalledWith(email);
|
||||
expect(putInteraction).toBeCalledWith(InteractionEvent.SignIn);
|
||||
expect(sendPasscode).toBeCalledWith({ email });
|
||||
});
|
||||
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
|
@ -125,7 +122,8 @@ describe('PasswordSignInForm', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendSignInSmsPasscode).toBeCalledWith(phone);
|
||||
expect(putInteraction).toBeCalledWith(InteractionEvent.SignIn);
|
||||
expect(sendPasscode).toBeCalledWith({ phone });
|
||||
});
|
||||
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
|
|
|
@ -145,7 +145,7 @@ describe('<PhonePasswordless/>', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toBeCalledWith(`${defaultCountryCallingCode}${phoneNumber}`);
|
||||
expect(onSubmit).toBeCalledWith({ phone: `${defaultCountryCallingCode}${phoneNumber}` });
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -173,7 +173,7 @@ describe('<PhonePasswordless/>', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toBeCalledWith(`${defaultCountryCallingCode}${phoneNumber}`);
|
||||
expect(onSubmit).toBeCalledWith({ phone: `${defaultCountryCallingCode}${phoneNumber}` });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@ type Props = {
|
|||
errorMessage?: string;
|
||||
submitButtonText?: TFuncKey;
|
||||
clearErrorMessage?: () => void;
|
||||
onSubmit: (phone: string) => Promise<void> | void;
|
||||
onSubmit: (payload: { phone: string }) => Promise<void> | void;
|
||||
};
|
||||
|
||||
type FieldState = {
|
||||
|
@ -79,9 +79,9 @@ const PhoneForm = ({
|
|||
return;
|
||||
}
|
||||
|
||||
await onSubmit(fieldValue.phone);
|
||||
await onSubmit(fieldValue);
|
||||
},
|
||||
[validateForm, hasTerms, termsValidation, onSubmit, fieldValue.phone]
|
||||
[validateForm, hasTerms, termsValidation, onSubmit, fieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -2,7 +2,7 @@ import { fireEvent, waitFor, act } from '@testing-library/react';
|
|||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import { sendContinueSetPhonePasscode } from '@/apis/continue';
|
||||
import { putInteraction, sendPasscode } from '@/apis/interaction';
|
||||
import { getDefaultCountryCallingCode } from '@/utils/country-code';
|
||||
|
||||
import SmsContinue from './SmsContinue';
|
||||
|
@ -14,8 +14,9 @@ jest.mock('i18next', () => ({
|
|||
language: 'en',
|
||||
}));
|
||||
|
||||
jest.mock('@/apis/continue', () => ({
|
||||
sendContinueSetPhonePasscode: jest.fn(() => ({ success: true })),
|
||||
jest.mock('@/apis/interaction', () => ({
|
||||
sendPasscode: jest.fn(() => ({ success: true })),
|
||||
putInteraction: jest.fn(() => ({ success: true })),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
|
@ -47,7 +48,8 @@ describe('SmsContinue', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendContinueSetPhonePasscode).toBeCalledWith(fullPhoneNumber);
|
||||
expect(putInteraction).not.toBeCalled();
|
||||
expect(sendPasscode).toBeCalledWith({ phone: fullPhoneNumber });
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{ pathname: '/continue/sms/passcode-validation', search: '' },
|
||||
{ state: { phone: fullPhoneNumber } }
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { InteractionEvent } from '@logto/schemas';
|
||||
import { fireEvent, waitFor, act } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import { sendRegisterSmsPasscode } from '@/apis/register';
|
||||
import { putInteraction, sendPasscode } from '@/apis/interaction';
|
||||
import { getDefaultCountryCallingCode } from '@/utils/country-code';
|
||||
|
||||
import SmsRegister from './SmsRegister';
|
||||
|
@ -14,8 +15,9 @@ jest.mock('i18next', () => ({
|
|||
language: 'en',
|
||||
}));
|
||||
|
||||
jest.mock('@/apis/register', () => ({
|
||||
sendRegisterSmsPasscode: jest.fn(() => ({ success: true })),
|
||||
jest.mock('@/apis/interaction', () => ({
|
||||
sendPasscode: jest.fn(() => ({ success: true })),
|
||||
putInteraction: jest.fn(() => ({ success: true })),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
|
@ -47,7 +49,8 @@ describe('SmsRegister', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendRegisterSmsPasscode).toBeCalledWith(fullPhoneNumber);
|
||||
expect(putInteraction).toBeCalledWith(InteractionEvent.Register);
|
||||
expect(sendPasscode).toBeCalledWith({ phone: fullPhoneNumber });
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{ pathname: '/register/sms/passcode-validation', search: '' },
|
||||
{ state: { phone: fullPhoneNumber } }
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { SignInIdentifier, InteractionEvent } from '@logto/schemas';
|
||||
import { fireEvent, waitFor, act } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import { sendForgotPasswordSmsPasscode } from '@/apis/forgot-password';
|
||||
import { putInteraction, sendPasscode } from '@/apis/interaction';
|
||||
import { UserFlow } from '@/types';
|
||||
import { getDefaultCountryCallingCode } from '@/utils/country-code';
|
||||
|
||||
|
@ -16,8 +16,9 @@ jest.mock('i18next', () => ({
|
|||
language: 'en',
|
||||
}));
|
||||
|
||||
jest.mock('@/apis/forgot-password', () => ({
|
||||
sendForgotPasswordSmsPasscode: jest.fn(() => ({ success: true })),
|
||||
jest.mock('@/apis/interaction', () => ({
|
||||
sendPasscode: jest.fn(() => ({ success: true })),
|
||||
putInteraction: jest.fn(() => ({ success: true })),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
|
@ -49,7 +50,8 @@ describe('SmsRegister', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendForgotPasswordSmsPasscode).toBeCalledWith(fullPhoneNumber);
|
||||
expect(putInteraction).toBeCalledWith(InteractionEvent.ForgotPassword);
|
||||
expect(sendPasscode).toBeCalledWith({ phone: fullPhoneNumber });
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{
|
||||
pathname: `/${UserFlow.forgotPassword}/${SignInIdentifier.Sms}/passcode-validation`,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { SignInIdentifier, InteractionEvent } from '@logto/schemas';
|
||||
import { fireEvent, waitFor, act } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import { sendSignInSmsPasscode } from '@/apis/sign-in';
|
||||
import { sendPasscode, putInteraction } from '@/apis/interaction';
|
||||
import { getDefaultCountryCallingCode } from '@/utils/country-code';
|
||||
|
||||
import SmsSignIn from './SmsSignIn';
|
||||
|
@ -15,8 +15,9 @@ jest.mock('i18next', () => ({
|
|||
language: 'en',
|
||||
}));
|
||||
|
||||
jest.mock('@/apis/sign-in', () => ({
|
||||
sendSignInSmsPasscode: jest.fn(() => ({ success: true })),
|
||||
jest.mock('@/apis/interaction', () => ({
|
||||
sendPasscode: jest.fn(() => ({ success: true })),
|
||||
putInteraction: jest.fn(() => ({ success: true })),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
|
@ -59,7 +60,8 @@ describe('SmsSignIn', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendSignInSmsPasscode).not.toBeCalled();
|
||||
expect(putInteraction).not.toBeCalled();
|
||||
expect(sendPasscode).not.toBeCalled();
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{ pathname: '/sign-in/sms/password', search: '' },
|
||||
{ state: { phone: fullPhoneNumber } }
|
||||
|
@ -93,7 +95,8 @@ describe('SmsSignIn', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendSignInSmsPasscode).not.toBeCalled();
|
||||
expect(putInteraction).not.toBeCalled();
|
||||
expect(sendPasscode).not.toBeCalled();
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{ pathname: '/sign-in/sms/password', search: '' },
|
||||
{ state: { phone: fullPhoneNumber } }
|
||||
|
@ -128,7 +131,8 @@ describe('SmsSignIn', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendSignInSmsPasscode).toBeCalledWith(fullPhoneNumber);
|
||||
expect(putInteraction).toBeCalledWith(InteractionEvent.SignIn);
|
||||
expect(sendPasscode).toBeCalledWith({ phone: fullPhoneNumber });
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{ pathname: '/sign-in/sms/passcode-validation', search: '' },
|
||||
{ state: { phone: fullPhoneNumber } }
|
||||
|
@ -163,7 +167,8 @@ describe('SmsSignIn', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendSignInSmsPasscode).toBeCalledWith(fullPhoneNumber);
|
||||
expect(putInteraction).toBeCalledWith(InteractionEvent.SignIn);
|
||||
expect(sendPasscode).toBeCalledWith({ phone: fullPhoneNumber });
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{ pathname: '/sign-in/sms/passcode-validation', search: '' },
|
||||
{ state: { phone: fullPhoneNumber } }
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import type { SignInIdentifier } from '@logto/schemas';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { UserFlow } from '@/types';
|
||||
|
||||
const useContinueSignInWithPassword = (method: SignInIdentifier.Email | SignInIdentifier.Sms) => {
|
||||
const useContinueSignInWithPassword = <T extends SignInIdentifier.Email | SignInIdentifier.Sms>(
|
||||
method: T
|
||||
) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (value: string) => {
|
||||
type Payload = T extends SignInIdentifier.Email ? { email: string } : { phone: string };
|
||||
|
||||
return (payload: Payload) => {
|
||||
navigate(
|
||||
{
|
||||
pathname: `/${UserFlow.signIn}/${method}/password`,
|
||||
search: location.search,
|
||||
},
|
||||
{ state: method === SignInIdentifier.Email ? { email: value } : { phone: value } }
|
||||
{ state: payload }
|
||||
);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -7,9 +7,9 @@ import type { ErrorHandlers } from '@/hooks/use-api';
|
|||
import useApi from '@/hooks/use-api';
|
||||
import type { UserFlow } from '@/types';
|
||||
|
||||
const usePasswordlessSendCode = (
|
||||
const usePasswordlessSendCode = <T extends SignInIdentifier.Email | SignInIdentifier.Sms>(
|
||||
flow: UserFlow,
|
||||
method: SignInIdentifier.Email | SignInIdentifier.Sms,
|
||||
method: T,
|
||||
replaceCurrentPage?: boolean
|
||||
) => {
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
|
@ -28,13 +28,15 @@ const usePasswordlessSendCode = (
|
|||
setErrorMessage('');
|
||||
}, []);
|
||||
|
||||
const api = getSendPasscodeApi(flow, method);
|
||||
const api = getSendPasscodeApi(flow);
|
||||
|
||||
const { run: asyncSendPasscode } = useApi(api, errorHandlers);
|
||||
|
||||
type Payload = T extends SignInIdentifier.Email ? { email: string } : { phone: string };
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (value: string) => {
|
||||
const result = await asyncSendPasscode(value);
|
||||
async (payload: Payload) => {
|
||||
const result = await asyncSendPasscode(payload);
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
|
@ -46,7 +48,7 @@ const usePasswordlessSendCode = (
|
|||
search: location.search,
|
||||
},
|
||||
{
|
||||
state: method === SignInIdentifier.Email ? { email: value } : { phone: value },
|
||||
state: payload,
|
||||
replace: replaceCurrentPage,
|
||||
}
|
||||
);
|
||||
|
|
|
@ -27,7 +27,7 @@ const useUsernamePasswordRegister = () => {
|
|||
const { result, run: asyncSetPassword } = useApi(setUserPassword, resetPasswordErrorHandlers);
|
||||
|
||||
useEffect(() => {
|
||||
if (result?.redirectTo) {
|
||||
if (result && 'redirectTo' in result) {
|
||||
window.location.replace(result.redirectTo);
|
||||
}
|
||||
}, [result, setToast, t]);
|
||||
|
|
|
@ -2,7 +2,7 @@ import { act, waitFor, fireEvent } from '@testing-library/react';
|
|||
import { MemoryRouter, Routes, Route } from 'react-router-dom';
|
||||
|
||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import { resetPassword } from '@/apis/forgot-password';
|
||||
import { setUserPassword } from '@/apis/interaction';
|
||||
|
||||
import ResetPassword from '.';
|
||||
|
||||
|
@ -13,8 +13,8 @@ jest.mock('react-router-dom', () => ({
|
|||
useNavigate: () => mockedNavigate,
|
||||
}));
|
||||
|
||||
jest.mock('@/apis/forgot-password', () => ({
|
||||
resetPassword: jest.fn(async () => ({ redirectTo: '/' })),
|
||||
jest.mock('@/apis/interaction', () => ({
|
||||
setUserPassword: jest.fn(async () => ({ redirectTo: '/' })),
|
||||
}));
|
||||
|
||||
describe('ForgotPassword', () => {
|
||||
|
@ -51,7 +51,7 @@ describe('ForgotPassword', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(resetPassword).toBeCalledWith('123456');
|
||||
expect(setUserPassword).toBeCalledWith('123456');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useMemo, useState, useContext, useEffect, useCallback } from 'react';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { resetPassword } from '@/apis/forgot-password';
|
||||
import { setUserPassword } from '@/apis/interaction';
|
||||
import type { ErrorHandlers } from '@/hooks/use-api';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import { useConfirmModal } from '@/hooks/use-confirm-modal';
|
||||
|
@ -24,11 +24,7 @@ const useResetPassword = () => {
|
|||
() => ({
|
||||
'session.verification_session_not_found': async (error) => {
|
||||
await show({ type: 'alert', ModalContent: error.message, cancelText: 'action.got_it' });
|
||||
navigate(-1);
|
||||
},
|
||||
'session.verification_expired': async (error) => {
|
||||
await show({ type: 'alert', ModalContent: error.message, cancelText: 'action.got_it' });
|
||||
navigate(-1);
|
||||
navigate(-2);
|
||||
},
|
||||
'user.same_password': (error) => {
|
||||
setErrorMessage(error.message);
|
||||
|
@ -37,7 +33,7 @@ const useResetPassword = () => {
|
|||
[navigate, setErrorMessage, show]
|
||||
);
|
||||
|
||||
const { result, run: asyncResetPassword } = useApi(resetPassword, resetPasswordErrorHandlers);
|
||||
const { result, run: asyncResetPassword } = useApi(setUserPassword, resetPasswordErrorHandlers);
|
||||
|
||||
useEffect(() => {
|
||||
if (result) {
|
||||
|
|
|
@ -6,7 +6,6 @@ import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider
|
|||
import { mockSignInExperienceSettings } from '@/__mocks__/logto';
|
||||
import SecondaryRegister from '@/pages/SecondaryRegister';
|
||||
|
||||
jest.mock('@/apis/register', () => ({ register: jest.fn(async () => 0) }));
|
||||
jest.mock('i18next', () => ({
|
||||
language: 'en',
|
||||
}));
|
||||
|
|
|
@ -6,7 +6,6 @@ import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider
|
|||
import { mockSignInExperienceSettings } from '@/__mocks__/logto';
|
||||
import SecondarySignIn from '@/pages/SecondarySignIn';
|
||||
|
||||
jest.mock('@/apis/register', () => ({ register: jest.fn(async () => 0) }));
|
||||
jest.mock('i18next', () => ({
|
||||
language: 'en',
|
||||
}));
|
||||
|
|
Loading…
Add table
Reference in a new issue