mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(core): fix
This commit is contained in:
parent
b35fe0811d
commit
07485a6e6c
4 changed files with 49 additions and 55 deletions
|
@ -436,8 +436,8 @@ describe('session -> passwordlessRoutes', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /session/register/passwordless/sms', () => {
|
describe('POST /session/register/passwordless/sms', () => {
|
||||||
afterEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.resetAllMocks();
|
||||||
});
|
});
|
||||||
it('should call interactionResult', async () => {
|
it('should call interactionResult', async () => {
|
||||||
interactionDetails.mockResolvedValueOnce({
|
interactionDetails.mockResolvedValueOnce({
|
||||||
|
@ -513,8 +513,8 @@ describe('session -> passwordlessRoutes', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /session/register/passwordless/email', () => {
|
describe('POST /session/register/passwordless/email', () => {
|
||||||
afterEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.resetAllMocks();
|
||||||
});
|
});
|
||||||
it('should call interactionResult', async () => {
|
it('should call interactionResult', async () => {
|
||||||
interactionDetails.mockResolvedValueOnce({
|
interactionDetails.mockResolvedValueOnce({
|
||||||
|
|
|
@ -15,16 +15,16 @@ import {
|
||||||
hasUserWithEmail,
|
hasUserWithEmail,
|
||||||
hasUserWithPhone,
|
hasUserWithPhone,
|
||||||
} from '@/queries/user';
|
} from '@/queries/user';
|
||||||
import { verificationStorageParser, flowTypeGuard } from '@/routes/session/types';
|
import { flowTypeGuard } from '@/routes/session/types';
|
||||||
import assertThat from '@/utils/assert-that';
|
import assertThat from '@/utils/assert-that';
|
||||||
|
|
||||||
import { AnonymousRouter } from '../types';
|
import { AnonymousRouter } from '../types';
|
||||||
import { verificationTimeout } from './consts';
|
import { verificationTimeout } from './consts';
|
||||||
import {
|
import {
|
||||||
|
getAndCheckVerificationStorage,
|
||||||
getRoutePrefix,
|
getRoutePrefix,
|
||||||
getPasscodeType,
|
getPasscodeType,
|
||||||
getPasswordlessRelatedLogType,
|
getPasswordlessRelatedLogType,
|
||||||
verificationSessionCheckByFlow,
|
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
export const registerRoute = getRoutePrefix('register', 'passwordless');
|
export const registerRoute = getRoutePrefix('register', 'passwordless');
|
||||||
|
@ -157,14 +157,8 @@ export default function passwordlessRoutes<T extends AnonymousRouter>(
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(`${signInRoute}/sms`, async (ctx, next) => {
|
router.post(`${signInRoute}/sms`, async (ctx, next) => {
|
||||||
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
|
|
||||||
|
|
||||||
const { phone, flow, expiresAt } = verificationStorageParser(result);
|
|
||||||
|
|
||||||
const type = getPasswordlessRelatedLogType('sign-in', 'sms');
|
const type = getPasswordlessRelatedLogType('sign-in', 'sms');
|
||||||
ctx.log(type, { phone, flow, expiresAt });
|
const { phone } = await getAndCheckVerificationStorage(ctx, provider, type, 'sign-in');
|
||||||
|
|
||||||
verificationSessionCheckByFlow('sign-in', { flow, expiresAt });
|
|
||||||
|
|
||||||
assertThat(
|
assertThat(
|
||||||
phone && (await hasUserWithPhone(phone)),
|
phone && (await hasUserWithPhone(phone)),
|
||||||
|
@ -181,14 +175,8 @@ export default function passwordlessRoutes<T extends AnonymousRouter>(
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post(`${signInRoute}/email`, async (ctx, next) => {
|
router.post(`${signInRoute}/email`, async (ctx, next) => {
|
||||||
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
|
|
||||||
|
|
||||||
const { email, flow, expiresAt } = verificationStorageParser(result);
|
|
||||||
|
|
||||||
const type = getPasswordlessRelatedLogType('sign-in', 'email');
|
const type = getPasswordlessRelatedLogType('sign-in', 'email');
|
||||||
ctx.log(type, { email, flow, expiresAt });
|
const { email } = await getAndCheckVerificationStorage(ctx, provider, type, 'sign-in');
|
||||||
|
|
||||||
verificationSessionCheckByFlow('sign-in', { flow, expiresAt });
|
|
||||||
|
|
||||||
assertThat(
|
assertThat(
|
||||||
email && (await hasUserWithEmail(email)),
|
email && (await hasUserWithEmail(email)),
|
||||||
|
@ -205,14 +193,8 @@ export default function passwordlessRoutes<T extends AnonymousRouter>(
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post(`${registerRoute}/sms`, async (ctx, next) => {
|
router.post(`${registerRoute}/sms`, async (ctx, next) => {
|
||||||
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
|
|
||||||
|
|
||||||
const { phone, flow, expiresAt } = verificationStorageParser(result);
|
|
||||||
|
|
||||||
const type = getPasswordlessRelatedLogType('register', 'sms');
|
const type = getPasswordlessRelatedLogType('register', 'sms');
|
||||||
ctx.log(type, { phone, flow, expiresAt });
|
const { phone } = await getAndCheckVerificationStorage(ctx, provider, type, 'register');
|
||||||
|
|
||||||
verificationSessionCheckByFlow('register', { flow, expiresAt });
|
|
||||||
|
|
||||||
assertThat(
|
assertThat(
|
||||||
phone && !(await hasUserWithPhone(phone)),
|
phone && !(await hasUserWithPhone(phone)),
|
||||||
|
@ -229,14 +211,8 @@ export default function passwordlessRoutes<T extends AnonymousRouter>(
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post(`${registerRoute}/email`, async (ctx, next) => {
|
router.post(`${registerRoute}/email`, async (ctx, next) => {
|
||||||
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
|
|
||||||
|
|
||||||
const { email, flow, expiresAt } = verificationStorageParser(result);
|
|
||||||
|
|
||||||
const type = getPasswordlessRelatedLogType('register', 'email');
|
const type = getPasswordlessRelatedLogType('register', 'email');
|
||||||
ctx.log(type, { email, flow, expiresAt });
|
const { email } = await getAndCheckVerificationStorage(ctx, provider, type, 'register');
|
||||||
|
|
||||||
verificationSessionCheckByFlow('register', { flow, expiresAt });
|
|
||||||
|
|
||||||
assertThat(
|
assertThat(
|
||||||
email && !(await hasUserWithEmail(email)),
|
email && !(await hasUserWithEmail(email)),
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import RequestError from '@/errors/RequestError';
|
|
||||||
import assertThat from '@/utils/assert-that';
|
|
||||||
|
|
||||||
export const flowTypeGuard = z.enum(['sign-in', 'register', 'forgot-password']);
|
export const flowTypeGuard = z.enum(['sign-in', 'register', 'forgot-password']);
|
||||||
|
|
||||||
export type FlowType = z.infer<typeof flowTypeGuard>;
|
export type FlowType = z.infer<typeof flowTypeGuard>;
|
||||||
|
@ -25,20 +22,3 @@ export const verificationStorageGuard = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
export type VerificationStorage = z.infer<typeof verificationStorageGuard>;
|
export type VerificationStorage = z.infer<typeof verificationStorageGuard>;
|
||||||
|
|
||||||
export const verificationStorageParser = (data: unknown): VerificationStorage => {
|
|
||||||
const verificationResult = z
|
|
||||||
.object({
|
|
||||||
verification: verificationStorageGuard,
|
|
||||||
})
|
|
||||||
.safeParse(data);
|
|
||||||
assertThat(
|
|
||||||
verificationResult.success,
|
|
||||||
new RequestError({
|
|
||||||
code: 'session.verification_session_not_found',
|
|
||||||
status: 404,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return verificationResult.data.verification;
|
|
||||||
};
|
|
||||||
|
|
|
@ -2,11 +2,15 @@ import { logTypeGuard, LogType, PasscodeType } from '@logto/schemas';
|
||||||
import { Truthy } from '@silverhand/essentials';
|
import { Truthy } from '@silverhand/essentials';
|
||||||
import camelcase from 'camelcase';
|
import camelcase from 'camelcase';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { Context } from 'koa';
|
||||||
|
import { Provider } from 'oidc-provider';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
import RequestError from '@/errors/RequestError';
|
import RequestError from '@/errors/RequestError';
|
||||||
|
import { LogContext } from '@/middleware/koa-log';
|
||||||
import assertThat from '@/utils/assert-that';
|
import assertThat from '@/utils/assert-that';
|
||||||
|
|
||||||
import { FlowType, Operation, VerificationStorage, Via } from './types';
|
import { verificationStorageGuard, FlowType, Operation, VerificationStorage, Via } from './types';
|
||||||
|
|
||||||
export const getRoutePrefix = (
|
export const getRoutePrefix = (
|
||||||
type: FlowType,
|
type: FlowType,
|
||||||
|
@ -46,6 +50,24 @@ export const getPasswordlessRelatedLogType = (
|
||||||
return result.data;
|
return result.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const verificationStorageParser = (data: unknown): VerificationStorage => {
|
||||||
|
const verificationResult = z
|
||||||
|
.object({
|
||||||
|
verification: verificationStorageGuard,
|
||||||
|
})
|
||||||
|
.safeParse(data);
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
verificationResult.success,
|
||||||
|
new RequestError({
|
||||||
|
code: 'session.verification_session_not_found',
|
||||||
|
status: 404,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return verificationResult.data.verification;
|
||||||
|
};
|
||||||
|
|
||||||
export const verificationSessionCheckByFlow = (
|
export const verificationSessionCheckByFlow = (
|
||||||
currentFlow: FlowType,
|
currentFlow: FlowType,
|
||||||
payload: Pick<VerificationStorage, 'flow' | 'expiresAt'>
|
payload: Pick<VerificationStorage, 'flow' | 'expiresAt'>
|
||||||
|
@ -62,3 +84,19 @@ export const verificationSessionCheckByFlow = (
|
||||||
new RequestError({ code: 'session.verification_expired', status: 401 })
|
new RequestError({ code: 'session.verification_expired', status: 401 })
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getAndCheckVerificationStorage = async (
|
||||||
|
ctx: Context & LogContext,
|
||||||
|
provider: Provider,
|
||||||
|
logType: LogType,
|
||||||
|
flowType: FlowType
|
||||||
|
): Promise<Pick<VerificationStorage, 'email' | 'phone'>> => {
|
||||||
|
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||||
|
const { email, phone, flow, expiresAt } = verificationStorageParser(result);
|
||||||
|
|
||||||
|
ctx.log(logType, { email, phone, flow, expiresAt });
|
||||||
|
|
||||||
|
verificationSessionCheckByFlow(flowType, { flow, expiresAt });
|
||||||
|
|
||||||
|
return { email, phone };
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue