0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-17 22:04:19 -05:00

fix(core): handle not found error for password sign in (#2383)

This commit is contained in:
wangsijie 2022-11-11 14:54:46 +08:00 committed by GitHub
parent 4028669940
commit 27ce104ec5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 28 additions and 42 deletions

View file

@ -8,12 +8,7 @@ import { getLogtoConnectorById } from '@/connectors';
import type { SocialUserInfo } from '@/connectors/types'; import type { SocialUserInfo } from '@/connectors/types';
import { socialUserInfoGuard } from '@/connectors/types'; import { socialUserInfoGuard } from '@/connectors/types';
import RequestError from '@/errors/RequestError'; import RequestError from '@/errors/RequestError';
import { import { findUserByEmail, findUserByPhone } from '@/queries/user';
findUserByEmail,
findUserByPhone,
hasUserWithEmail,
hasUserWithPhone,
} from '@/queries/user';
import assertThat from '@/utils/assert-that'; import assertThat from '@/utils/assert-that';
export type SocialUserInfoSession = { export type SocialUserInfoSession = {
@ -88,16 +83,20 @@ export const getUserInfoFromInteractionResult = async (
export const findSocialRelatedUser = async ( export const findSocialRelatedUser = async (
info: SocialUserInfo info: SocialUserInfo
): Promise<Nullable<[{ type: 'email' | 'phone'; value: string }, User]>> => { ): Promise<Nullable<[{ type: 'email' | 'phone'; value: string }, User]>> => {
if (info.phone && (await hasUserWithPhone(info.phone))) { if (info.phone) {
const user = await findUserByPhone(info.phone); const user = await findUserByPhone(info.phone);
return [{ type: 'phone', value: info.phone }, user]; if (user) {
return [{ type: 'phone', value: info.phone }, user];
}
} }
if (info.email && (await hasUserWithEmail(info.email))) { if (info.email) {
const user = await findUserByEmail(info.email); const user = await findUserByEmail(info.email);
return [{ type: 'email', value: info.email }, user]; if (user) {
return [{ type: 'email', value: info.email }, user];
}
} }
return null; return null;

View file

@ -18,14 +18,14 @@ export const findUserByUsername = async (username: string) =>
`); `);
export const findUserByEmail = async (email: string) => export const findUserByEmail = async (email: string) =>
envSet.pool.one<User>(sql` envSet.pool.maybeOne<User>(sql`
select ${sql.join(Object.values(fields), sql`,`)} select ${sql.join(Object.values(fields), sql`,`)}
from ${table} from ${table}
where lower(${fields.primaryEmail})=lower(${email}) where lower(${fields.primaryEmail})=lower(${email})
`); `);
export const findUserByPhone = async (phone: string) => export const findUserByPhone = async (phone: string) =>
envSet.pool.one<User>(sql` envSet.pool.maybeOne<User>(sql`
select ${sql.join(Object.values(fields), sql`,`)} select ${sql.join(Object.values(fields), sql`,`)}
from ${table} from ${table}
where ${fields.primaryPhone}=${phone} where ${fields.primaryPhone}=${phone}

View file

@ -53,12 +53,8 @@ export const smsSignInAction = <StateT, ContextT extends WithLogContext, Respons
checkValidateExpiration(expiresAt); checkValidateExpiration(expiresAt);
assertThat(
await hasUserWithPhone(phone),
new RequestError({ code: 'user.phone_not_exists', status: 404 })
);
const user = await findUserByPhone(phone); const user = await findUserByPhone(phone);
assertThat(user, new RequestError({ code: 'user.phone_not_exists', status: 404 }));
const { id } = user; const { id } = user;
ctx.log(type, { userId: id }); ctx.log(type, { userId: id });
@ -99,12 +95,8 @@ export const emailSignInAction = <StateT, ContextT extends WithLogContext, Respo
checkValidateExpiration(expiresAt); checkValidateExpiration(expiresAt);
assertThat(
await hasUserWithEmail(email),
new RequestError({ code: 'user.email_not_exists', status: 404 })
);
const user = await findUserByEmail(email); const user = await findUserByEmail(email);
assertThat(user, new RequestError({ code: 'user.email_not_exists', status: 404 }));
const { id } = user; const { id } = user;
ctx.log(type, { userId: id }); ctx.log(type, { userId: id });

View file

@ -1,6 +1,7 @@
/* eslint-disable max-lines */ /* eslint-disable max-lines */
import type { User } from '@logto/schemas'; import type { User } from '@logto/schemas';
import { PasscodeType, SignInIdentifier, SignUpIdentifier } from '@logto/schemas'; import { PasscodeType, SignInIdentifier, SignUpIdentifier } from '@logto/schemas';
import type { Nullable } from '@silverhand/essentials';
import { addDays, addSeconds, subDays } from 'date-fns'; import { addDays, addSeconds, subDays } from 'date-fns';
import { Provider } from 'oidc-provider'; import { Provider } from 'oidc-provider';
@ -14,7 +15,8 @@ import passwordlessRoutes, { registerRoute, signInRoute } from './passwordless';
const insertUser = jest.fn(async (..._args: unknown[]) => mockUser); const insertUser = jest.fn(async (..._args: unknown[]) => mockUser);
const findUserById = jest.fn(async (): Promise<User> => mockUser); const findUserById = jest.fn(async (): Promise<User> => mockUser);
const findUserByEmail = jest.fn(async (): Promise<User> => mockUser); const findUserByEmail = jest.fn(async (): Promise<Nullable<User>> => mockUser);
const findUserByPhone = jest.fn(async (): Promise<Nullable<User>> => mockUser);
const updateUserById = jest.fn(async (..._args: unknown[]) => mockUser); const updateUserById = jest.fn(async (..._args: unknown[]) => mockUser);
const findDefaultSignInExperience = jest.fn(async () => ({ const findDefaultSignInExperience = jest.fn(async () => ({
...mockSignInExperience, ...mockSignInExperience,
@ -34,7 +36,7 @@ jest.mock('@/lib/user', () => ({
jest.mock('@/queries/user', () => ({ jest.mock('@/queries/user', () => ({
findUserById: async () => findUserById(), findUserById: async () => findUserById(),
findUserByPhone: async () => mockUser, findUserByPhone: async () => findUserByPhone(),
findUserByEmail: async () => findUserByEmail(), findUserByEmail: async () => findUserByEmail(),
updateUserById: async (...args: unknown[]) => updateUserById(...args), updateUserById: async (...args: unknown[]) => updateUserById(...args),
hasUser: async (username: string) => username === 'username1', hasUser: async (username: string) => username === 'username1',
@ -260,6 +262,7 @@ describe('session -> passwordlessRoutes', () => {
}); });
it('throw 404 (with flow `forgot-password`)', async () => { it('throw 404 (with flow `forgot-password`)', async () => {
findUserByPhone.mockResolvedValueOnce(null);
const response = await sessionRequest const response = await sessionRequest
.post('/session/passwordless/sms/verify') .post('/session/passwordless/sms/verify')
.send({ phone: '13000000001', code: '1234', flow: PasscodeType.ForgotPassword }); .send({ phone: '13000000001', code: '1234', flow: PasscodeType.ForgotPassword });
@ -358,6 +361,7 @@ describe('session -> passwordlessRoutes', () => {
it('throw 404 (with flow `forgot-password`)', async () => { it('throw 404 (with flow `forgot-password`)', async () => {
const fakeTime = new Date(); const fakeTime = new Date();
jest.useFakeTimers().setSystemTime(fakeTime); jest.useFakeTimers().setSystemTime(fakeTime);
findUserByEmail.mockResolvedValueOnce(null);
const response = await sessionRequest const response = await sessionRequest
.post('/session/passwordless/email/verify') .post('/session/passwordless/email/verify')
.send({ email: 'b@a.com', code: '1234', flow: PasscodeType.ForgotPassword }); .send({ email: 'b@a.com', code: '1234', flow: PasscodeType.ForgotPassword });
@ -501,6 +505,7 @@ describe('session -> passwordlessRoutes', () => {
}, },
}, },
}); });
findUserByPhone.mockResolvedValueOnce(null);
const response = await sessionRequest.post(`${signInRoute}/sms`); const response = await sessionRequest.post(`${signInRoute}/sms`);
expect(response.statusCode).toEqual(404); expect(response.statusCode).toEqual(404);
}); });
@ -639,6 +644,7 @@ describe('session -> passwordlessRoutes', () => {
}, },
}, },
}); });
findUserByEmail.mockResolvedValueOnce(null);
const response = await sessionRequest.post(`${signInRoute}/email`); const response = await sessionRequest.post(`${signInRoute}/email`);
expect(response.statusCode).toEqual(404); expect(response.statusCode).toEqual(404);
}); });

View file

@ -6,12 +6,7 @@ import { object, string } from 'zod';
import RequestError from '@/errors/RequestError'; import RequestError from '@/errors/RequestError';
import { createPasscode, sendPasscode, verifyPasscode } from '@/lib/passcode'; import { createPasscode, sendPasscode, verifyPasscode } from '@/lib/passcode';
import koaGuard from '@/middleware/koa-guard'; import koaGuard from '@/middleware/koa-guard';
import { import { findUserByEmail, findUserByPhone } from '@/queries/user';
findUserByEmail,
findUserByPhone,
hasUserWithEmail,
hasUserWithPhone,
} from '@/queries/user';
import { passcodeTypeGuard } from '@/routes/session/types'; import { passcodeTypeGuard } from '@/routes/session/types';
import assertThat from '@/utils/assert-that'; import assertThat from '@/utils/assert-that';
@ -105,13 +100,10 @@ export default function passwordlessRoutes<T extends AnonymousRouter>(
await verifyPasscode(jti, flow, code, { phone }); await verifyPasscode(jti, flow, code, { phone });
if (flow === PasscodeType.ForgotPassword) { if (flow === PasscodeType.ForgotPassword) {
assertThat( const user = await findUserByPhone(phone);
await hasUserWithPhone(phone), assertThat(user, new RequestError({ code: 'user.phone_not_exists', status: 404 }));
new RequestError({ code: 'user.phone_not_exists', status: 404 })
);
const { id } = await findUserByPhone(phone); await assignVerificationResult(ctx, provider, { flow, userId: user.id });
await assignVerificationResult(ctx, provider, { flow, userId: id });
ctx.status = 204; ctx.status = 204;
return next(); return next();
@ -156,14 +148,11 @@ export default function passwordlessRoutes<T extends AnonymousRouter>(
await verifyPasscode(jti, flow, code, { email }); await verifyPasscode(jti, flow, code, { email });
if (flow === PasscodeType.ForgotPassword) { if (flow === PasscodeType.ForgotPassword) {
assertThat( const user = await findUserByEmail(email);
await hasUserWithEmail(email),
new RequestError({ code: 'user.email_not_exists', status: 404 })
);
const { id } = await findUserByEmail(email); assertThat(user, new RequestError({ code: 'user.email_not_exists', status: 404 }));
await assignVerificationResult(ctx, provider, { flow, userId: id }); await assignVerificationResult(ctx, provider, { flow, userId: user.id });
ctx.status = 204; ctx.status = 204;
return next(); return next();