mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
feat(core): control forgot password (#2212)
This commit is contained in:
parent
55eb5f038d
commit
387bc21384
8 changed files with 38 additions and 1 deletions
|
@ -2,7 +2,7 @@ import { User } from '@logto/schemas';
|
|||
import dayjs from 'dayjs';
|
||||
import { Provider } from 'oidc-provider';
|
||||
|
||||
import { mockPasswordEncrypted, mockUserWithPassword } from '@/__mocks__';
|
||||
import { mockPasswordEncrypted, mockSignInExperience, mockUserWithPassword } from '@/__mocks__';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { createRequester } from '@/utils/test-utils';
|
||||
|
||||
|
@ -15,6 +15,10 @@ const encryptUserPassword = jest.fn(async (password: string) => ({
|
|||
}));
|
||||
const findUserById = jest.fn(async (): Promise<User> => mockUserWithPassword);
|
||||
const updateUserById = jest.fn(async (..._args: unknown[]) => ({ id: 'id' }));
|
||||
const findDefaultSignInExperience = jest.fn(async () => ({
|
||||
...mockSignInExperience,
|
||||
forgotPassword: true,
|
||||
}));
|
||||
|
||||
jest.mock('@/lib/user', () => ({
|
||||
...jest.requireActual('@/lib/user'),
|
||||
|
@ -31,6 +35,10 @@ jest.mock('@/queries/user', () => ({
|
|||
updateUserById: async (...args: unknown[]) => updateUserById(...args),
|
||||
}));
|
||||
|
||||
jest.mock('@/queries/sign-in-experience', () => ({
|
||||
findDefaultSignInExperience: async () => findDefaultSignInExperience(),
|
||||
}));
|
||||
|
||||
const sendPasscode = jest.fn(async () => ({ dbEntry: { id: 'connectorIdValue' } }));
|
||||
jest.mock('@/lib/passcode', () => ({
|
||||
createPasscode: async () => ({ id: 'id' }),
|
||||
|
@ -283,5 +291,21 @@ describe('session -> forgotPasswordRoutes', () => {
|
|||
);
|
||||
expect(response.statusCode).toEqual(204);
|
||||
});
|
||||
it('should throw when forgot password is not enabeld in SIE', async () => {
|
||||
findDefaultSignInExperience.mockResolvedValueOnce({
|
||||
...mockSignInExperience,
|
||||
forgotPassword: false,
|
||||
});
|
||||
interactionDetails.mockResolvedValueOnce({
|
||||
result: {
|
||||
forgotPassword: { userId: 'id', expiresAt: dayjs().add(1, 'day').toISOString() },
|
||||
},
|
||||
});
|
||||
const response = await sessionRequest
|
||||
.post(`${forgotPasswordRoute}/reset`)
|
||||
.send({ password: mockPasswordEncrypted });
|
||||
expect(response).toHaveProperty('status', 422);
|
||||
expect(updateUserById).toBeCalledTimes(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ import RequestError from '@/errors/RequestError';
|
|||
import { createPasscode, sendPasscode, verifyPasscode } from '@/lib/passcode';
|
||||
import { encryptUserPassword } from '@/lib/user';
|
||||
import koaGuard from '@/middleware/koa-guard';
|
||||
import { findDefaultSignInExperience } from '@/queries/sign-in-experience';
|
||||
import {
|
||||
findUserByEmail,
|
||||
findUserById,
|
||||
|
@ -131,6 +132,12 @@ export default function forgotPasswordRoutes<T extends AnonymousRouter>(
|
|||
`${forgotPasswordRoute}/reset`,
|
||||
koaGuard({ body: z.object({ password: z.string().regex(passwordRegEx) }) }),
|
||||
async (ctx, next) => {
|
||||
const signInExperience = await findDefaultSignInExperience();
|
||||
assertThat(
|
||||
signInExperience.forgotPassword,
|
||||
new RequestError({ code: 'session.forgot_password_not_enabled', status: 422 })
|
||||
);
|
||||
|
||||
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
const { password } = ctx.guard.body;
|
||||
const forgotPasswordVerificationResult = forgotPasswordVerificationGuard.safeParse(result);
|
||||
|
|
|
@ -63,6 +63,7 @@ const errors = {
|
|||
'Forgot password verification has expired. Please go back and verify again.',
|
||||
unauthorized: 'Please sign in first.',
|
||||
unsupported_prompt_name: 'Unsupported prompt name.',
|
||||
forgot_password_not_enabled: 'Forgot password is not enabled.',
|
||||
},
|
||||
connector: {
|
||||
general: 'An unexpected error occurred in connector.{{errorDescription}}',
|
||||
|
|
|
@ -68,6 +68,7 @@ const errors = {
|
|||
'Forgot password verification has expired. Please go back and verify again.', // UNTRANSLATED
|
||||
unauthorized: "Veuillez vous enregistrer d'abord.",
|
||||
unsupported_prompt_name: "Nom d'invite non supporté.",
|
||||
forgot_password_not_enabled: 'Forgot password is not enabled.', // UNTRANSLATED
|
||||
},
|
||||
connector: {
|
||||
general: "Une erreur inattendue s'est produite dans le connecteur. {{errorDescription}}",
|
||||
|
|
|
@ -62,6 +62,7 @@ const errors = {
|
|||
'Forgot password verification has expired. Please go back and verify again.', // UNTRANSLATED
|
||||
unauthorized: '로그인을 먼저 해주세요.',
|
||||
unsupported_prompt_name: '지원하지 않는 Prompt 이름이예요.',
|
||||
forgot_password_not_enabled: 'Forgot password is not enabled.', // UNTRANSLATED
|
||||
},
|
||||
connector: {
|
||||
general: '연동 중에 알 수 없는 오류가 발생했어요. {{errorDescription}}',
|
||||
|
|
|
@ -64,6 +64,7 @@ const errors = {
|
|||
'Forgot password verification has expired. Please go back and verify again.', // UNTRANSLATED
|
||||
unauthorized: 'Faça login primeiro.',
|
||||
unsupported_prompt_name: 'Nome de prompt não suportado.',
|
||||
forgot_password_not_enabled: 'Forgot password is not enabled.', // UNTRANSLATED
|
||||
},
|
||||
connector: {
|
||||
general: 'Ocorreu um erro inesperado no conector.{{errorDescription}}',
|
||||
|
|
|
@ -64,6 +64,7 @@ const errors = {
|
|||
'Forgot password verification has expired. Please go back and verify again.', // UNTRANSLATED
|
||||
unauthorized: 'Lütfen önce oturum açın.',
|
||||
unsupported_prompt_name: 'Desteklenmeyen prompt adı.',
|
||||
forgot_password_not_enabled: 'Forgot password is not enabled.', // UNTRANSLATED
|
||||
},
|
||||
connector: {
|
||||
general: 'Bağlayıcıda beklenmeyen bir hata oldu.{{errorDescription}}',
|
||||
|
|
|
@ -60,6 +60,7 @@ const errors = {
|
|||
forgot_password_verification_expired: '忘记密码验证已过期,请尝试重新验证。',
|
||||
unauthorized: '请先登录',
|
||||
unsupported_prompt_name: '不支持的 prompt name',
|
||||
forgot_password_not_enabled: '忘记密码功能没有开启。',
|
||||
},
|
||||
connector: {
|
||||
general: '连接器发生未知错误{{errorDescription}}',
|
||||
|
|
Loading…
Reference in a new issue