0
Fork 0
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:
wangsijie 2022-10-20 14:41:41 +08:00 committed by GitHub
parent 55eb5f038d
commit 387bc21384
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 38 additions and 1 deletions

View file

@ -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);
});
});
});

View file

@ -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);

View file

@ -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}}',

View file

@ -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}}",

View file

@ -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}}',

View file

@ -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}}',

View file

@ -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}}',

View file

@ -60,6 +60,7 @@ const errors = {
forgot_password_verification_expired: '忘记密码验证已过期,请尝试重新验证。',
unauthorized: '请先登录',
unsupported_prompt_name: '不支持的 prompt name',
forgot_password_not_enabled: '忘记密码功能没有开启。',
},
connector: {
general: '连接器发生未知错误{{errorDescription}}',