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:
parent
4028669940
commit
27ce104ec5
5 changed files with 28 additions and 42 deletions
|
@ -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;
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Add table
Reference in a new issue