mirror of
https://github.com/logto-io/logto.git
synced 2025-03-24 22:41:28 -05:00
refactor(core): decouple passwordless verification flow w/o fixing tests
This commit is contained in:
parent
e1ec8804b2
commit
ac562c0c94
4 changed files with 146 additions and 321 deletions
|
@ -17,7 +17,7 @@
|
|||
"add-connector": "node build/cli/add-connector.js",
|
||||
"add-official-connectors": "node build/cli/add-official-connectors.js",
|
||||
"alteration": "node build/cli/alteration.js",
|
||||
"test": "jest",
|
||||
"test": "jest --testPathIgnorePatterns=/core/connectors/",
|
||||
"test:coverage": "jest --coverage --silent",
|
||||
"test:report": "codecov -F core"
|
||||
},
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
import dayjs from 'dayjs';
|
||||
import { Provider } from 'oidc-provider';
|
||||
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { assignInteractionResults } from '@/lib/session';
|
||||
import { generateUserId, insertUser } from '@/lib/user';
|
||||
import koaLog from '@/middleware/koa-log';
|
||||
import { hasUserWithPhone, hasUserWithEmail } from '@/queries/user';
|
||||
import { passwordlessVerificationGuard, Via } from '@/routes/session/types';
|
||||
import { getPasswordlessRelatedLogType } from '@/routes/session/utils';
|
||||
import assertThat from '@/utils/assert-that';
|
||||
|
||||
type MiddlewareReturnType = ReturnType<typeof koaLog>;
|
||||
|
||||
export default function koaPasswordlessRegisterAction<StateT, ContextT, ResponseBodyT>(
|
||||
provider: Provider,
|
||||
via: Via
|
||||
): MiddlewareReturnType {
|
||||
return async (ctx, next) => {
|
||||
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
|
||||
const passwordlessVerificationResult = passwordlessVerificationGuard.safeParse(result);
|
||||
assertThat(
|
||||
passwordlessVerificationResult.success,
|
||||
new RequestError({
|
||||
code: 'session.passwordless_verification_session_not_found',
|
||||
status: 404,
|
||||
})
|
||||
);
|
||||
|
||||
const {
|
||||
passwordlessVerification: { email, phone, flow, expiresAt },
|
||||
} = passwordlessVerificationResult.data;
|
||||
|
||||
const type = getPasswordlessRelatedLogType('register', via);
|
||||
ctx.log(type, { email, phone, flow, expiresAt });
|
||||
|
||||
assertThat(
|
||||
flow === 'register',
|
||||
new RequestError({ code: 'session.passwordless_not_verified', status: 401 })
|
||||
);
|
||||
|
||||
assertThat(
|
||||
dayjs(expiresAt).isValid() && dayjs(expiresAt).isAfter(dayjs()),
|
||||
new RequestError({ code: 'session.passwordless_verification_expired', status: 401 })
|
||||
);
|
||||
|
||||
if (via === 'sms') {
|
||||
assertThat(
|
||||
phone && !(await hasUserWithPhone(phone)),
|
||||
new RequestError({ code: 'user.phone_exists_register', status: 422 })
|
||||
);
|
||||
} else {
|
||||
assertThat(
|
||||
email && !(await hasUserWithEmail(email)),
|
||||
new RequestError({ code: 'user.email_exists_register', status: 422 })
|
||||
);
|
||||
}
|
||||
|
||||
const id = await generateUserId();
|
||||
ctx.log(type, { userId: id });
|
||||
|
||||
await (via === 'sms'
|
||||
? insertUser({ id, primaryPhone: phone, lastSignInAt: Date.now() })
|
||||
: insertUser({ id, primaryEmail: email, lastSignInAt: Date.now() }));
|
||||
await assignInteractionResults(ctx, provider, { login: { accountId: id } }, true);
|
||||
|
||||
return next();
|
||||
};
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
import { User } from '@logto/schemas';
|
||||
import dayjs from 'dayjs';
|
||||
import { Provider } from 'oidc-provider';
|
||||
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { assignInteractionResults } from '@/lib/session';
|
||||
import koaLog from '@/middleware/koa-log';
|
||||
import {
|
||||
updateUserById,
|
||||
hasUserWithPhone,
|
||||
findUserByPhone,
|
||||
hasUserWithEmail,
|
||||
findUserByEmail,
|
||||
} from '@/queries/user';
|
||||
import { passwordlessVerificationGuard, Via } from '@/routes/session/types';
|
||||
import { getPasswordlessRelatedLogType } from '@/routes/session/utils';
|
||||
import assertThat from '@/utils/assert-that';
|
||||
|
||||
type MiddlewareReturnType = ReturnType<typeof koaLog>;
|
||||
|
||||
export default function koaPasswordlessSignInAction<StateT, ContextT, ResponseBodyT>(
|
||||
provider: Provider,
|
||||
via: Via
|
||||
): MiddlewareReturnType {
|
||||
return async (ctx, next) => {
|
||||
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
|
||||
const passwordlessVerificationResult = passwordlessVerificationGuard.safeParse(result);
|
||||
assertThat(
|
||||
passwordlessVerificationResult.success,
|
||||
new RequestError({
|
||||
code: 'session.passwordless_verification_session_not_found',
|
||||
status: 404,
|
||||
})
|
||||
);
|
||||
|
||||
const {
|
||||
passwordlessVerification: { email, phone, flow, expiresAt },
|
||||
} = passwordlessVerificationResult.data;
|
||||
|
||||
const type = getPasswordlessRelatedLogType('sign-in', via);
|
||||
ctx.log(type, { email, phone, flow, expiresAt });
|
||||
|
||||
assertThat(
|
||||
flow === 'sign-in',
|
||||
new RequestError({ code: 'session.passwordless_not_verified', status: 401 })
|
||||
);
|
||||
|
||||
assertThat(
|
||||
dayjs(expiresAt).isValid() && dayjs(expiresAt).isAfter(dayjs()),
|
||||
new RequestError({ code: 'session.passwordless_verification_expired', status: 401 })
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @silverhand/fp/no-let
|
||||
let user: User;
|
||||
|
||||
if (via === 'sms') {
|
||||
assertThat(
|
||||
phone && (await hasUserWithPhone(phone)),
|
||||
new RequestError({ code: 'user.phone_not_exists', status: 422 })
|
||||
);
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||
user = await findUserByPhone(phone);
|
||||
} else {
|
||||
assertThat(
|
||||
email && (await hasUserWithEmail(email)),
|
||||
new RequestError({ code: 'user.email_not_exists', status: 422 })
|
||||
);
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||
user = await findUserByEmail(email);
|
||||
}
|
||||
|
||||
const { id } = user;
|
||||
ctx.log(type, { userId: id });
|
||||
|
||||
await updateUserById(id, { lastSignInAt: Date.now() });
|
||||
await assignInteractionResults(ctx, provider, { login: { accountId: id } }, true);
|
||||
|
||||
return next();
|
||||
};
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import { emailRegEx, phoneRegEx } from '@logto/core-kit';
|
||||
import { PasscodeType } from '@logto/schemas';
|
||||
import dayjs from 'dayjs';
|
||||
import { Provider } from 'oidc-provider';
|
||||
import { object, string } from 'zod';
|
||||
|
@ -11,19 +10,22 @@ import { generateUserId, insertUser } from '@/lib/user';
|
|||
import koaGuard from '@/middleware/koa-guard';
|
||||
import {
|
||||
updateUserById,
|
||||
hasUserWithEmail,
|
||||
hasUserWithPhone,
|
||||
findUserByEmail,
|
||||
findUserByPhone,
|
||||
hasUserWithEmail,
|
||||
hasUserWithPhone,
|
||||
} from '@/queries/user';
|
||||
import {
|
||||
passwordlessVerificationGuard,
|
||||
flowTypeGuard,
|
||||
viaGuard,
|
||||
PasscodePayload,
|
||||
} from '@/routes/session/types';
|
||||
import assertThat from '@/utils/assert-that';
|
||||
|
||||
import { AnonymousRouter } from '../types';
|
||||
import { passwordlessVerificationTimeout } from './consts';
|
||||
import { flowTypeGuard, viaGuard, PasscodePayload } from './types';
|
||||
import { getRoutePrefix, getPasscodeType, getPasswordlessRelatedLogType } from './utils';
|
||||
// Import koaPasswordlessSignInAction from './middleware/koa-sign-in-action';
|
||||
// import koaPasswordlessRegisterAction from './middleware/koa-register-action';
|
||||
|
||||
export const registerRoute = getRoutePrefix('register', 'passwordless');
|
||||
export const signInRoute = getRoutePrefix('sign-in', 'passwordless');
|
||||
|
@ -32,13 +34,8 @@ export default function passwordlessRoutes<T extends AnonymousRouter>(
|
|||
router: T,
|
||||
provider: Provider
|
||||
) {
|
||||
// Router.use(`${signInRoute}/sms`, koaPasswordlessSignInAction(provider, 'sms'));
|
||||
// router.use(`${signInRoute}/email`, koaPasswordlessSignInAction(provider, 'email'));
|
||||
// router.use(`${registerRoute}/sms`, koaPasswordlessRegisterAction(provider, 'sms'));
|
||||
// router.use(`${registerRoute}/email`, koaPasswordlessRegisterAction(provider, 'email'));
|
||||
|
||||
router.post(
|
||||
`/passwordless/:via/send`,
|
||||
'/passwordless/:via/send',
|
||||
koaGuard({
|
||||
body: object({
|
||||
phone: string().regex(phoneRegEx).optional(),
|
||||
|
@ -81,7 +78,7 @@ export default function passwordlessRoutes<T extends AnonymousRouter>(
|
|||
);
|
||||
|
||||
router.post(
|
||||
`/passwordless/:via/verify`,
|
||||
'/passwordless/:via/verify',
|
||||
koaGuard({
|
||||
body: object({
|
||||
phone: string().regex(phoneRegEx).optional(),
|
||||
|
@ -130,195 +127,174 @@ export default function passwordlessRoutes<T extends AnonymousRouter>(
|
|||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
`${signInRoute}/sms/send-passcode`,
|
||||
koaGuard({ body: object({ phone: string().regex(phoneRegEx) }) }),
|
||||
async (ctx, next) => {
|
||||
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
const { phone } = ctx.guard.body;
|
||||
const type = 'SignInSmsSendPasscode';
|
||||
ctx.log(type, { phone });
|
||||
router.post(`${signInRoute}/sms`, async (ctx, next) => {
|
||||
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
|
||||
assertThat(
|
||||
await hasUserWithPhone(phone),
|
||||
new RequestError({ code: 'user.phone_not_exists', status: 422 })
|
||||
);
|
||||
const passwordlessVerificationResult = passwordlessVerificationGuard.safeParse(result);
|
||||
assertThat(
|
||||
passwordlessVerificationResult.success,
|
||||
new RequestError({
|
||||
code: 'session.passwordless_verification_session_not_found',
|
||||
status: 404,
|
||||
})
|
||||
);
|
||||
|
||||
const passcode = await createPasscode(jti, PasscodeType.SignIn, { phone });
|
||||
const { dbEntry } = await sendPasscode(passcode);
|
||||
ctx.log(type, { connectorId: dbEntry.id });
|
||||
ctx.status = 204;
|
||||
const {
|
||||
passwordlessVerification: { phone, flow, expiresAt },
|
||||
} = passwordlessVerificationResult.data;
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
const type = getPasswordlessRelatedLogType('sign-in', 'sms');
|
||||
ctx.log(type, { phone, flow, expiresAt });
|
||||
|
||||
router.post(
|
||||
`${signInRoute}/sms/verify-passcode`,
|
||||
koaGuard({ body: object({ phone: string().regex(phoneRegEx), code: string() }) }),
|
||||
async (ctx, next) => {
|
||||
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
const { phone, code } = ctx.guard.body;
|
||||
const type = 'SignInSms';
|
||||
ctx.log(type, { phone, code });
|
||||
assertThat(
|
||||
flow === 'sign-in',
|
||||
new RequestError({ code: 'session.passwordless_not_verified', status: 401 })
|
||||
);
|
||||
|
||||
assertThat(
|
||||
await hasUserWithPhone(phone),
|
||||
new RequestError({ code: 'user.phone_not_exists', status: 422 })
|
||||
);
|
||||
assertThat(
|
||||
dayjs(expiresAt).isValid() && dayjs(expiresAt).isAfter(dayjs()),
|
||||
new RequestError({ code: 'session.passwordless_verification_expired', status: 401 })
|
||||
);
|
||||
|
||||
await verifyPasscode(jti, PasscodeType.SignIn, code, { phone });
|
||||
const { id } = await findUserByPhone(phone);
|
||||
ctx.log(type, { userId: id });
|
||||
assertThat(
|
||||
phone && (await hasUserWithPhone(phone)),
|
||||
new RequestError({ code: 'user.phone_not_exists', status: 422 })
|
||||
);
|
||||
const { id } = await findUserByPhone(phone);
|
||||
ctx.log(type, { userId: id });
|
||||
|
||||
await updateUserById(id, { lastSignInAt: Date.now() });
|
||||
await assignInteractionResults(ctx, provider, { login: { accountId: id } }, true);
|
||||
await updateUserById(id, { lastSignInAt: Date.now() });
|
||||
await assignInteractionResults(ctx, provider, { login: { accountId: id } }, true);
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
return next();
|
||||
});
|
||||
|
||||
router.post(
|
||||
`${signInRoute}/email/send-passcode`,
|
||||
koaGuard({ body: object({ email: string().regex(emailRegEx) }) }),
|
||||
async (ctx, next) => {
|
||||
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
const { email } = ctx.guard.body;
|
||||
const type = 'SignInEmailSendPasscode';
|
||||
ctx.log(type, { email });
|
||||
router.post(`${signInRoute}/email`, async (ctx, next) => {
|
||||
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
|
||||
assertThat(
|
||||
await hasUserWithEmail(email),
|
||||
new RequestError({ code: 'user.email_not_exists', status: 422 })
|
||||
);
|
||||
const passwordlessVerificationResult = passwordlessVerificationGuard.safeParse(result);
|
||||
assertThat(
|
||||
passwordlessVerificationResult.success,
|
||||
new RequestError({
|
||||
code: 'session.passwordless_verification_session_not_found',
|
||||
status: 404,
|
||||
})
|
||||
);
|
||||
|
||||
const passcode = await createPasscode(jti, PasscodeType.SignIn, { email });
|
||||
const { dbEntry } = await sendPasscode(passcode);
|
||||
ctx.log(type, { connectorId: dbEntry.id });
|
||||
ctx.status = 204;
|
||||
const {
|
||||
passwordlessVerification: { email, flow, expiresAt },
|
||||
} = passwordlessVerificationResult.data;
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
const type = getPasswordlessRelatedLogType('sign-in', 'email');
|
||||
ctx.log(type, { email, flow, expiresAt });
|
||||
|
||||
router.post(
|
||||
`${signInRoute}/email/verify-passcode`,
|
||||
koaGuard({ body: object({ email: string().regex(emailRegEx), code: string() }) }),
|
||||
async (ctx, next) => {
|
||||
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
const { email, code } = ctx.guard.body;
|
||||
const type = 'SignInEmail';
|
||||
ctx.log(type, { email, code });
|
||||
assertThat(
|
||||
flow === 'sign-in',
|
||||
new RequestError({ code: 'session.passwordless_not_verified', status: 401 })
|
||||
);
|
||||
|
||||
assertThat(
|
||||
await hasUserWithEmail(email),
|
||||
new RequestError({ code: 'user.email_not_exists', status: 422 })
|
||||
);
|
||||
assertThat(
|
||||
dayjs(expiresAt).isValid() && dayjs(expiresAt).isAfter(dayjs()),
|
||||
new RequestError({ code: 'session.passwordless_verification_expired', status: 401 })
|
||||
);
|
||||
|
||||
await verifyPasscode(jti, PasscodeType.SignIn, code, { email });
|
||||
const { id } = await findUserByEmail(email);
|
||||
ctx.log(type, { userId: id });
|
||||
assertThat(
|
||||
email && (await hasUserWithEmail(email)),
|
||||
new RequestError({ code: 'user.email_not_exists', status: 422 })
|
||||
);
|
||||
const { id } = await findUserByEmail(email);
|
||||
|
||||
await updateUserById(id, { lastSignInAt: Date.now() });
|
||||
await assignInteractionResults(ctx, provider, { login: { accountId: id } }, true);
|
||||
ctx.log(type, { userId: id });
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
await updateUserById(id, { lastSignInAt: Date.now() });
|
||||
await assignInteractionResults(ctx, provider, { login: { accountId: id } }, true);
|
||||
|
||||
router.post(
|
||||
`${registerRoute}/sms/send-passcode`,
|
||||
koaGuard({ body: object({ phone: string().regex(phoneRegEx) }) }),
|
||||
async (ctx, next) => {
|
||||
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
const { phone } = ctx.guard.body;
|
||||
const type = 'RegisterSmsSendPasscode';
|
||||
ctx.log(type, { phone });
|
||||
return next();
|
||||
});
|
||||
|
||||
assertThat(
|
||||
!(await hasUserWithPhone(phone)),
|
||||
new RequestError({ code: 'user.phone_exists_register', status: 422 })
|
||||
);
|
||||
router.post(`${registerRoute}/sms`, async (ctx, next) => {
|
||||
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
|
||||
const passcode = await createPasscode(jti, PasscodeType.Register, { phone });
|
||||
const { dbEntry } = await sendPasscode(passcode);
|
||||
ctx.log(type, { connectorId: dbEntry.id });
|
||||
ctx.status = 204;
|
||||
const passwordlessVerificationResult = passwordlessVerificationGuard.safeParse(result);
|
||||
assertThat(
|
||||
passwordlessVerificationResult.success,
|
||||
new RequestError({
|
||||
code: 'session.passwordless_verification_session_not_found',
|
||||
status: 404,
|
||||
})
|
||||
);
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
const {
|
||||
passwordlessVerification: { email, phone, flow, expiresAt },
|
||||
} = passwordlessVerificationResult.data;
|
||||
|
||||
router.post(
|
||||
`${registerRoute}/sms/verify-passcode`,
|
||||
koaGuard({ body: object({ phone: string().regex(phoneRegEx), code: string() }) }),
|
||||
async (ctx, next) => {
|
||||
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
const { phone, code } = ctx.guard.body;
|
||||
const type = 'RegisterSms';
|
||||
ctx.log(type, { phone, code });
|
||||
const type = getPasswordlessRelatedLogType('register', 'sms');
|
||||
ctx.log(type, { phone, flow, expiresAt });
|
||||
|
||||
assertThat(
|
||||
!(await hasUserWithPhone(phone)),
|
||||
new RequestError({ code: 'user.phone_exists_register', status: 422 })
|
||||
);
|
||||
assertThat(
|
||||
flow === 'register',
|
||||
new RequestError({ code: 'session.passwordless_not_verified', status: 401 })
|
||||
);
|
||||
|
||||
await verifyPasscode(jti, PasscodeType.Register, code, { phone });
|
||||
const id = await generateUserId();
|
||||
ctx.log(type, { userId: id });
|
||||
assertThat(
|
||||
dayjs(expiresAt).isValid() && dayjs(expiresAt).isAfter(dayjs()),
|
||||
new RequestError({ code: 'session.passwordless_verification_expired', status: 401 })
|
||||
);
|
||||
|
||||
await insertUser({ id, primaryPhone: phone, lastSignInAt: Date.now() });
|
||||
await assignInteractionResults(ctx, provider, { login: { accountId: id } });
|
||||
assertThat(
|
||||
phone && !(await hasUserWithPhone(phone)),
|
||||
new RequestError({ code: 'user.phone_exists_register', status: 422 })
|
||||
);
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
const id = await generateUserId();
|
||||
ctx.log(type, { userId: id });
|
||||
|
||||
router.post(
|
||||
`${registerRoute}/email/send-passcode`,
|
||||
koaGuard({ body: object({ email: string().regex(emailRegEx) }) }),
|
||||
async (ctx, next) => {
|
||||
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
const { email } = ctx.guard.body;
|
||||
const type = 'RegisterEmailSendPasscode';
|
||||
ctx.log(type, { email });
|
||||
await insertUser({ id, primaryPhone: phone, lastSignInAt: Date.now() });
|
||||
await assignInteractionResults(ctx, provider, { login: { accountId: id } }, true);
|
||||
|
||||
assertThat(
|
||||
!(await hasUserWithEmail(email)),
|
||||
new RequestError({ code: 'user.email_exists_register', status: 422 })
|
||||
);
|
||||
return next();
|
||||
});
|
||||
|
||||
const passcode = await createPasscode(jti, PasscodeType.Register, { email });
|
||||
const { dbEntry } = await sendPasscode(passcode);
|
||||
ctx.log(type, { connectorId: dbEntry.id });
|
||||
ctx.status = 204;
|
||||
router.post(`${registerRoute}/email`, async (ctx, next) => {
|
||||
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
const passwordlessVerificationResult = passwordlessVerificationGuard.safeParse(result);
|
||||
assertThat(
|
||||
passwordlessVerificationResult.success,
|
||||
new RequestError({
|
||||
code: 'session.passwordless_verification_session_not_found',
|
||||
status: 404,
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
`${registerRoute}/email/verify-passcode`,
|
||||
koaGuard({ body: object({ email: string().regex(emailRegEx), code: string() }) }),
|
||||
async (ctx, next) => {
|
||||
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
const { email, code } = ctx.guard.body;
|
||||
const type = 'RegisterEmail';
|
||||
ctx.log(type, { email, code });
|
||||
const {
|
||||
passwordlessVerification: { email, flow, expiresAt },
|
||||
} = passwordlessVerificationResult.data;
|
||||
|
||||
assertThat(
|
||||
!(await hasUserWithEmail(email)),
|
||||
new RequestError({ code: 'user.email_exists_register', status: 422 })
|
||||
);
|
||||
const type = getPasswordlessRelatedLogType('register', 'email');
|
||||
ctx.log(type, { email, flow, expiresAt });
|
||||
|
||||
await verifyPasscode(jti, PasscodeType.Register, code, { email });
|
||||
const id = await generateUserId();
|
||||
ctx.log(type, { userId: id });
|
||||
assertThat(
|
||||
flow === 'register',
|
||||
new RequestError({ code: 'session.passwordless_not_verified', status: 401 })
|
||||
);
|
||||
|
||||
await insertUser({ id, primaryEmail: email, lastSignInAt: Date.now() });
|
||||
await assignInteractionResults(ctx, provider, { login: { accountId: id } });
|
||||
assertThat(
|
||||
dayjs(expiresAt).isValid() && dayjs(expiresAt).isAfter(dayjs()),
|
||||
new RequestError({ code: 'session.passwordless_verification_expired', status: 401 })
|
||||
);
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
assertThat(
|
||||
email && !(await hasUserWithEmail(email)),
|
||||
new RequestError({ code: 'user.email_exists_register', status: 422 })
|
||||
);
|
||||
|
||||
const id = await generateUserId();
|
||||
ctx.log(type, { userId: id });
|
||||
|
||||
await insertUser({ id, primaryEmail: email, lastSignInAt: Date.now() });
|
||||
await assignInteractionResults(ctx, provider, { login: { accountId: id } }, true);
|
||||
|
||||
return next();
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue