mirror of
https://github.com/logto-io/logto.git
synced 2025-01-27 21:39:16 -05:00
chore(core): refactor sign-in routes (#273)
* chore(core): refactor sign-in routes * feat(core): fix order of userLog assignments
This commit is contained in:
parent
44e2be0972
commit
89a185c845
3 changed files with 137 additions and 271 deletions
18
packages/core/src/lib/session.ts
Normal file
18
packages/core/src/lib/session.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { Context } from 'koa';
|
||||||
|
import { InteractionResults, Provider } from 'oidc-provider';
|
||||||
|
|
||||||
|
// TODO: change this after frontend is ready.
|
||||||
|
// Should combine baseUrl(domain) from database with a 'callback' endpoint.
|
||||||
|
export const connectorRedirectUrl = 'https://logto.dev/callback';
|
||||||
|
|
||||||
|
export const assignInteractionResults = async (
|
||||||
|
ctx: Context,
|
||||||
|
provider: Provider,
|
||||||
|
result: InteractionResults,
|
||||||
|
merge = false
|
||||||
|
) => {
|
||||||
|
const redirectTo = await provider.interactionResult(ctx.req, ctx.res, result, {
|
||||||
|
mergeWithLastSubmission: merge,
|
||||||
|
});
|
||||||
|
ctx.body = { redirectTo };
|
||||||
|
};
|
|
@ -1,193 +0,0 @@
|
||||||
import { PasscodeType, UserLogType } from '@logto/schemas';
|
|
||||||
import { Context } from 'koa';
|
|
||||||
import { InteractionResults, Provider } from 'oidc-provider';
|
|
||||||
|
|
||||||
import { getSocialConnectorInstanceById } from '@/connectors';
|
|
||||||
import { SocialUserInfo } from '@/connectors/types';
|
|
||||||
import RequestError from '@/errors/RequestError';
|
|
||||||
import { WithUserLogContext } from '@/middleware/koa-user-log';
|
|
||||||
import {
|
|
||||||
findUserByEmail,
|
|
||||||
findUserByPhone,
|
|
||||||
hasUserWithEmail,
|
|
||||||
hasUserWithPhone,
|
|
||||||
hasUserWithIdentity,
|
|
||||||
findUserByIdentity,
|
|
||||||
updateUserById,
|
|
||||||
} from '@/queries/user';
|
|
||||||
import assertThat from '@/utils/assert-that';
|
|
||||||
import { emailRegEx, phoneRegEx } from '@/utils/regex';
|
|
||||||
|
|
||||||
import { createPasscode, sendPasscode, verifyPasscode } from './passcode';
|
|
||||||
import {
|
|
||||||
findSocialRelatedUser,
|
|
||||||
getUserInfoFromInteractionResult,
|
|
||||||
SocialUserInfoSession,
|
|
||||||
} from './social';
|
|
||||||
import { findUserByUsernameAndPassword } from './user';
|
|
||||||
|
|
||||||
const assignSignInResult = async (ctx: Context, provider: Provider, userId: string) => {
|
|
||||||
const redirectTo = await provider.interactionResult(
|
|
||||||
ctx.req,
|
|
||||||
ctx.res,
|
|
||||||
{
|
|
||||||
login: { accountId: userId },
|
|
||||||
},
|
|
||||||
{ mergeWithLastSubmission: false }
|
|
||||||
);
|
|
||||||
ctx.body = { redirectTo };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sendSignInWithEmailPasscode = async (ctx: Context, jti: string, email: string) => {
|
|
||||||
assertThat(emailRegEx.test(email), new RequestError('user.invalid_email'));
|
|
||||||
assertThat(
|
|
||||||
await hasUserWithEmail(email),
|
|
||||||
new RequestError({
|
|
||||||
code: 'user.email_not_exists',
|
|
||||||
status: 422,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const passcode = await createPasscode(jti, PasscodeType.SignIn, { email });
|
|
||||||
await sendPasscode(passcode);
|
|
||||||
ctx.state = 204;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sendSignInWithPhonePasscode = async (ctx: Context, jti: string, phone: string) => {
|
|
||||||
assertThat(phoneRegEx.test(phone), new RequestError('user.invalid_phone'));
|
|
||||||
assertThat(
|
|
||||||
await hasUserWithPhone(phone),
|
|
||||||
new RequestError({
|
|
||||||
code: 'user.phone_not_exists',
|
|
||||||
status: 422,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const passcode = await createPasscode(jti, PasscodeType.SignIn, { phone });
|
|
||||||
await sendPasscode(passcode);
|
|
||||||
ctx.state = 204;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const signInWithUsernameAndPassword = async (
|
|
||||||
ctx: WithUserLogContext<Context>,
|
|
||||||
provider: Provider,
|
|
||||||
username: string,
|
|
||||||
password: string
|
|
||||||
) => {
|
|
||||||
assertThat(username && password, 'session.insufficient_info');
|
|
||||||
|
|
||||||
const { id } = await findUserByUsernameAndPassword(username, password);
|
|
||||||
await assignSignInResult(ctx, provider, id);
|
|
||||||
ctx.userLog.userId = id;
|
|
||||||
ctx.userLog.username = username;
|
|
||||||
ctx.userLog.type = UserLogType.SignInUsernameAndPassword;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const signInWithEmailAndPasscode = async (
|
|
||||||
ctx: WithUserLogContext<Context>,
|
|
||||||
provider: Provider,
|
|
||||||
{ jti, email, code }: { jti: string; email: string; code: string }
|
|
||||||
) => {
|
|
||||||
await verifyPasscode(jti, PasscodeType.SignIn, code, { email });
|
|
||||||
const { id } = await findUserByEmail(email);
|
|
||||||
|
|
||||||
await assignSignInResult(ctx, provider, id);
|
|
||||||
ctx.userLog.userId = id;
|
|
||||||
ctx.userLog.email = email;
|
|
||||||
ctx.userLog.type = UserLogType.SignInEmail;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const signInWithPhoneAndPasscode = async (
|
|
||||||
ctx: WithUserLogContext<Context>,
|
|
||||||
provider: Provider,
|
|
||||||
{ jti, phone, code }: { jti: string; phone: string; code: string }
|
|
||||||
) => {
|
|
||||||
await verifyPasscode(jti, PasscodeType.SignIn, code, { phone });
|
|
||||||
const { id } = await findUserByPhone(phone);
|
|
||||||
|
|
||||||
await assignSignInResult(ctx, provider, id);
|
|
||||||
ctx.userLog.userId = id;
|
|
||||||
ctx.userLog.phone = phone;
|
|
||||||
ctx.userLog.type = UserLogType.SignInPhone;
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: change this after frontend is ready.
|
|
||||||
// Should combine baseUrl(domain) from database with a 'callback' endpoint.
|
|
||||||
const connectorRedirectUrl = 'https://logto.dev/callback';
|
|
||||||
|
|
||||||
export const assignRedirectUrlForSocial = async (
|
|
||||||
ctx: WithUserLogContext<Context>,
|
|
||||||
connectorId: string,
|
|
||||||
state: string
|
|
||||||
) => {
|
|
||||||
const connector = await getSocialConnectorInstanceById(connectorId);
|
|
||||||
assertThat(connector.connector.enabled, 'connector.not_enabled');
|
|
||||||
const redirectTo = await connector.getAuthorizationUri(connectorRedirectUrl, state);
|
|
||||||
ctx.body = { redirectTo };
|
|
||||||
};
|
|
||||||
|
|
||||||
const saveUserInfoToSession = async (
|
|
||||||
ctx: Context,
|
|
||||||
provider: Provider,
|
|
||||||
socialUserInfo: SocialUserInfoSession
|
|
||||||
) => {
|
|
||||||
const redirectTo = await provider.interactionResult(
|
|
||||||
ctx.req,
|
|
||||||
ctx.res,
|
|
||||||
{
|
|
||||||
socialUserInfo,
|
|
||||||
},
|
|
||||||
{ mergeWithLastSubmission: true }
|
|
||||||
);
|
|
||||||
ctx.body = { redirectTo };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const signInWithSocial = async (
|
|
||||||
ctx: WithUserLogContext<Context>,
|
|
||||||
provider: Provider,
|
|
||||||
connectorId: string,
|
|
||||||
userInfo: SocialUserInfo
|
|
||||||
) => {
|
|
||||||
ctx.userLog.connectorId = connectorId;
|
|
||||||
ctx.userLog.type = UserLogType.SignInSocial;
|
|
||||||
|
|
||||||
if (!(await hasUserWithIdentity(connectorId, userInfo.id))) {
|
|
||||||
await saveUserInfoToSession(ctx, provider, { connectorId, userInfo });
|
|
||||||
const relatedInfo = await findSocialRelatedUser(userInfo);
|
|
||||||
throw new RequestError(
|
|
||||||
{
|
|
||||||
code: 'user.identity_not_exists',
|
|
||||||
status: 422,
|
|
||||||
},
|
|
||||||
relatedInfo && { relatedUser: relatedInfo[0] }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { id, identities } = await findUserByIdentity(connectorId, userInfo.id);
|
|
||||||
// Update social connector's user info
|
|
||||||
await updateUserById(id, {
|
|
||||||
identities: { ...identities, [connectorId]: { userId: userInfo.id, details: userInfo } },
|
|
||||||
});
|
|
||||||
ctx.userLog.userId = id;
|
|
||||||
await assignSignInResult(ctx, provider, id);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const signInWithSocialRelatedUser = async (
|
|
||||||
ctx: WithUserLogContext<Context>,
|
|
||||||
provider: Provider,
|
|
||||||
{ connectorId, result }: { connectorId: string; result: InteractionResults }
|
|
||||||
) => {
|
|
||||||
ctx.userLog.connectorId = connectorId;
|
|
||||||
ctx.userLog.type = UserLogType.SignInSocial;
|
|
||||||
|
|
||||||
const userInfo = await getUserInfoFromInteractionResult(connectorId, result);
|
|
||||||
const relatedInfo = await findSocialRelatedUser(userInfo);
|
|
||||||
|
|
||||||
assertThat(relatedInfo, 'session.connector_session_not_found');
|
|
||||||
|
|
||||||
const { id, identities } = relatedInfo[1];
|
|
||||||
|
|
||||||
await updateUserById(id, {
|
|
||||||
identities: { ...identities, [connectorId]: { userId: userInfo.id, details: userInfo } },
|
|
||||||
});
|
|
||||||
ctx.userLog.userId = id;
|
|
||||||
await assignSignInResult(ctx, provider, id);
|
|
||||||
};
|
|
|
@ -7,20 +7,16 @@ import pick from 'lodash.pick';
|
||||||
import { Provider } from 'oidc-provider';
|
import { Provider } from 'oidc-provider';
|
||||||
import { object, string } from 'zod';
|
import { object, string } from 'zod';
|
||||||
|
|
||||||
|
import { getSocialConnectorInstanceById } from '@/connectors';
|
||||||
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 { assignInteractionResults, connectorRedirectUrl } from '@/lib/session';
|
||||||
import {
|
import {
|
||||||
assignRedirectUrlForSocial,
|
findSocialRelatedUser,
|
||||||
sendSignInWithEmailPasscode,
|
getUserInfoByAuthCode,
|
||||||
sendSignInWithPhonePasscode,
|
getUserInfoFromInteractionResult,
|
||||||
signInWithSocial,
|
} from '@/lib/social';
|
||||||
signInWithEmailAndPasscode,
|
import { encryptUserPassword, generateUserId, findUserByUsernameAndPassword } from '@/lib/user';
|
||||||
signInWithPhoneAndPasscode,
|
|
||||||
signInWithUsernameAndPassword,
|
|
||||||
signInWithSocialRelatedUser,
|
|
||||||
} from '@/lib/sign-in';
|
|
||||||
import { getUserInfoByAuthCode, getUserInfoFromInteractionResult } from '@/lib/social';
|
|
||||||
import { encryptUserPassword, generateUserId } from '@/lib/user';
|
|
||||||
import koaGuard from '@/middleware/koa-guard';
|
import koaGuard from '@/middleware/koa-guard';
|
||||||
import {
|
import {
|
||||||
hasUser,
|
hasUser,
|
||||||
|
@ -30,6 +26,9 @@ import {
|
||||||
insertUser,
|
insertUser,
|
||||||
findUserById,
|
findUserById,
|
||||||
updateUserById,
|
updateUserById,
|
||||||
|
findUserByEmail,
|
||||||
|
findUserByPhone,
|
||||||
|
findUserByIdentity,
|
||||||
} from '@/queries/user';
|
} from '@/queries/user';
|
||||||
import assertThat from '@/utils/assert-that';
|
import assertThat from '@/utils/assert-that';
|
||||||
import { emailRegEx, phoneRegEx } from '@/utils/regex';
|
import { emailRegEx, phoneRegEx } from '@/utils/regex';
|
||||||
|
@ -55,8 +54,16 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
||||||
'/session/sign-in/username-password',
|
'/session/sign-in/username-password',
|
||||||
koaGuard({ body: object({ username: string(), password: string() }) }),
|
koaGuard({ body: object({ username: string(), password: string() }) }),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
|
ctx.userLog.type = UserLogType.SignInUsernameAndPassword;
|
||||||
const { username, password } = ctx.guard.body;
|
const { username, password } = ctx.guard.body;
|
||||||
await signInWithUsernameAndPassword(ctx, provider, username, password);
|
|
||||||
|
assertThat(username && password, 'session.insufficient_info');
|
||||||
|
ctx.userLog.username = username;
|
||||||
|
|
||||||
|
const { id } = await findUserByUsernameAndPassword(username, password);
|
||||||
|
ctx.userLog.userId = id;
|
||||||
|
|
||||||
|
await assignInteractionResults(ctx, provider, { login: { accountId: id } });
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
@ -66,16 +73,33 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
||||||
'/session/sign-in/passwordless/phone',
|
'/session/sign-in/passwordless/phone',
|
||||||
koaGuard({ body: object({ phone: string(), code: string().optional() }) }),
|
koaGuard({ body: object({ phone: string(), code: string().optional() }) }),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
|
ctx.userLog.type = UserLogType.SignInPhone;
|
||||||
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||||
const { phone, code } = ctx.guard.body;
|
const { phone, code } = ctx.guard.body;
|
||||||
|
|
||||||
|
assertThat(phoneRegEx.test(phone), new RequestError('user.invalid_phone'));
|
||||||
|
assertThat(
|
||||||
|
await hasUserWithPhone(phone),
|
||||||
|
new RequestError({
|
||||||
|
code: 'user.phone_not_exists',
|
||||||
|
status: 422,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
ctx.userLog.phone = phone;
|
||||||
|
|
||||||
if (!code) {
|
if (!code) {
|
||||||
await sendSignInWithPhonePasscode(ctx, jti, phone);
|
const passcode = await createPasscode(jti, PasscodeType.SignIn, { phone });
|
||||||
|
await sendPasscode(passcode);
|
||||||
|
ctx.state = 204;
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
await signInWithPhoneAndPasscode(ctx, provider, { jti, phone, code });
|
await verifyPasscode(jti, PasscodeType.SignIn, code, { phone });
|
||||||
|
const { id } = await findUserByPhone(phone);
|
||||||
|
ctx.userLog.userId = id;
|
||||||
|
|
||||||
|
await assignInteractionResults(ctx, provider, { login: { accountId: id } });
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
@ -85,16 +109,33 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
||||||
'/session/sign-in/passwordless/email',
|
'/session/sign-in/passwordless/email',
|
||||||
koaGuard({ body: object({ email: string(), code: string().optional() }) }),
|
koaGuard({ body: object({ email: string(), code: string().optional() }) }),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
|
ctx.userLog.type = UserLogType.SignInEmail;
|
||||||
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||||
const { email, code } = ctx.guard.body;
|
const { email, code } = ctx.guard.body;
|
||||||
|
|
||||||
|
assertThat(emailRegEx.test(email), new RequestError('user.invalid_email'));
|
||||||
|
assertThat(
|
||||||
|
await hasUserWithEmail(email),
|
||||||
|
new RequestError({
|
||||||
|
code: 'user.email_not_exists',
|
||||||
|
status: 422,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
ctx.userLog.email = email;
|
||||||
|
|
||||||
if (!code) {
|
if (!code) {
|
||||||
await sendSignInWithEmailPasscode(ctx, jti, email);
|
const passcode = await createPasscode(jti, PasscodeType.SignIn, { email });
|
||||||
|
await sendPasscode(passcode);
|
||||||
|
ctx.state = 204;
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
await signInWithEmailAndPasscode(ctx, provider, { jti, email, code });
|
await verifyPasscode(jti, PasscodeType.SignIn, code, { email });
|
||||||
|
const { id } = await findUserByEmail(email);
|
||||||
|
ctx.userLog.userId = id;
|
||||||
|
|
||||||
|
await assignInteractionResults(ctx, provider, { login: { accountId: id } });
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
@ -106,17 +147,43 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
||||||
body: object({ connectorId: string(), code: string().optional(), state: string() }),
|
body: object({ connectorId: string(), code: string().optional(), state: string() }),
|
||||||
}),
|
}),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
|
ctx.userLog.type = UserLogType.SignInSocial;
|
||||||
const { connectorId, code, state } = ctx.guard.body;
|
const { connectorId, code, state } = ctx.guard.body;
|
||||||
|
|
||||||
|
ctx.userLog.connectorId = connectorId;
|
||||||
|
|
||||||
if (!code) {
|
if (!code) {
|
||||||
assertThat(state, 'session.insufficient_info');
|
assertThat(state, 'session.insufficient_info');
|
||||||
await assignRedirectUrlForSocial(ctx, connectorId, state);
|
const connector = await getSocialConnectorInstanceById(connectorId);
|
||||||
|
assertThat(connector.connector.enabled, 'connector.not_enabled');
|
||||||
|
const redirectTo = await connector.getAuthorizationUri(connectorRedirectUrl, state);
|
||||||
|
ctx.body = { redirectTo };
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
const userInfo = await getUserInfoByAuthCode(connectorId, code);
|
const userInfo = await getUserInfoByAuthCode(connectorId, code);
|
||||||
await signInWithSocial(ctx, provider, connectorId, userInfo);
|
|
||||||
|
if (!(await hasUserWithIdentity(connectorId, userInfo.id))) {
|
||||||
|
await assignInteractionResults(ctx, provider, { connectorId, userInfo }, true);
|
||||||
|
const relatedInfo = await findSocialRelatedUser(userInfo);
|
||||||
|
throw new RequestError(
|
||||||
|
{
|
||||||
|
code: 'user.identity_not_exists',
|
||||||
|
status: 422,
|
||||||
|
},
|
||||||
|
relatedInfo && { relatedUser: relatedInfo[0] }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id, identities } = await findUserByIdentity(connectorId, userInfo.id);
|
||||||
|
ctx.userLog.userId = id;
|
||||||
|
|
||||||
|
// Update social connector's user info
|
||||||
|
await updateUserById(id, {
|
||||||
|
identities: { ...identities, [connectorId]: { userId: userInfo.id, details: userInfo } },
|
||||||
|
});
|
||||||
|
await assignInteractionResults(ctx, provider, { login: { accountId: id } });
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
@ -128,12 +195,26 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
||||||
body: object({ connectorId: string() }),
|
body: object({ connectorId: string() }),
|
||||||
}),
|
}),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
|
ctx.userLog.type = UserLogType.SignInSocial;
|
||||||
const { connectorId } = ctx.guard.body;
|
const { connectorId } = ctx.guard.body;
|
||||||
|
|
||||||
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
|
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||||
|
|
||||||
assertThat(result, 'session.connector_session_not_found');
|
assertThat(result, 'session.connector_session_not_found');
|
||||||
|
|
||||||
await signInWithSocialRelatedUser(ctx, provider, { connectorId, result });
|
ctx.userLog.connectorId = connectorId;
|
||||||
|
|
||||||
|
const userInfo = await getUserInfoFromInteractionResult(connectorId, result);
|
||||||
|
const relatedInfo = await findSocialRelatedUser(userInfo);
|
||||||
|
|
||||||
|
assertThat(relatedInfo, 'session.connector_session_not_found');
|
||||||
|
|
||||||
|
const { id, identities } = relatedInfo[1];
|
||||||
|
ctx.userLog.userId = id;
|
||||||
|
|
||||||
|
await updateUserById(id, {
|
||||||
|
identities: { ...identities, [connectorId]: { userId: userInfo.id, details: userInfo } },
|
||||||
|
});
|
||||||
|
await assignInteractionResults(ctx, provider, { login: { accountId: id } });
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
@ -169,13 +250,7 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
||||||
const finalGrantId = await grant.save();
|
const finalGrantId = await grant.save();
|
||||||
|
|
||||||
// V2: configure consent
|
// V2: configure consent
|
||||||
const redirectTo = await provider.interactionResult(
|
await assignInteractionResults(ctx, provider, { consent: { grantId: finalGrantId } }, true);
|
||||||
ctx.req,
|
|
||||||
ctx.res,
|
|
||||||
{ consent: { grantId: finalGrantId } },
|
|
||||||
{ mergeWithLastSubmission: true }
|
|
||||||
);
|
|
||||||
ctx.body = { redirectTo };
|
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
@ -184,7 +259,9 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
||||||
'/session/register/username-password',
|
'/session/register/username-password',
|
||||||
koaGuard({ body: object({ username: string(), password: string() }) }),
|
koaGuard({ body: object({ username: string(), password: string() }) }),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
|
ctx.userLog.type = UserLogType.RegisterUsernameAndPassword;
|
||||||
const { username, password } = ctx.guard.body;
|
const { username, password } = ctx.guard.body;
|
||||||
|
|
||||||
assertThat(
|
assertThat(
|
||||||
username && password,
|
username && password,
|
||||||
new RequestError({
|
new RequestError({
|
||||||
|
@ -199,8 +276,10 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
||||||
status: 422,
|
status: 422,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
ctx.userLog.username = username;
|
||||||
|
|
||||||
const id = await generateUserId();
|
const id = await generateUserId();
|
||||||
|
ctx.userLog.userId = id;
|
||||||
|
|
||||||
const { passwordEncryptionSalt, passwordEncrypted, passwordEncryptionMethod } =
|
const { passwordEncryptionSalt, passwordEncrypted, passwordEncryptionMethod } =
|
||||||
encryptUserPassword(id, password);
|
encryptUserPassword(id, password);
|
||||||
|
@ -212,20 +291,7 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
||||||
passwordEncryptionMethod,
|
passwordEncryptionMethod,
|
||||||
passwordEncryptionSalt,
|
passwordEncryptionSalt,
|
||||||
});
|
});
|
||||||
|
await assignInteractionResults(ctx, provider, { login: { accountId: id } });
|
||||||
ctx.userLog.userId = id;
|
|
||||||
ctx.userLog.username = username;
|
|
||||||
ctx.userLog.type = UserLogType.RegisterUsernameAndPassword;
|
|
||||||
|
|
||||||
const redirectTo = await provider.interactionResult(
|
|
||||||
ctx.req,
|
|
||||||
ctx.res,
|
|
||||||
{ login: { accountId: id } },
|
|
||||||
{
|
|
||||||
mergeWithLastSubmission: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
ctx.body = { redirectTo };
|
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
@ -235,6 +301,7 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
||||||
'/session/register/passwordless/phone',
|
'/session/register/passwordless/phone',
|
||||||
koaGuard({ body: object({ phone: string(), code: string().optional() }) }),
|
koaGuard({ body: object({ phone: string(), code: string().optional() }) }),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
|
ctx.userLog.type = UserLogType.RegisterPhone;
|
||||||
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||||
const { phone, code } = ctx.guard.body;
|
const { phone, code } = ctx.guard.body;
|
||||||
|
|
||||||
|
@ -243,6 +310,7 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
||||||
!(await hasUserWithPhone(phone)),
|
!(await hasUserWithPhone(phone)),
|
||||||
new RequestError({ code: 'user.phone_exists_register', status: 422 })
|
new RequestError({ code: 'user.phone_exists_register', status: 422 })
|
||||||
);
|
);
|
||||||
|
ctx.userLog.phone = phone;
|
||||||
|
|
||||||
if (!code) {
|
if (!code) {
|
||||||
const passcode = await createPasscode(jti, PasscodeType.Register, { phone });
|
const passcode = await createPasscode(jti, PasscodeType.Register, { phone });
|
||||||
|
@ -254,20 +322,10 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
||||||
|
|
||||||
await verifyPasscode(jti, PasscodeType.Register, code, { phone });
|
await verifyPasscode(jti, PasscodeType.Register, code, { phone });
|
||||||
const id = await generateUserId();
|
const id = await generateUserId();
|
||||||
|
ctx.userLog.userId = id;
|
||||||
|
|
||||||
await insertUser({ id, primaryPhone: phone });
|
await insertUser({ id, primaryPhone: phone });
|
||||||
const redirectTo = await provider.interactionResult(
|
await assignInteractionResults(ctx, provider, { login: { accountId: id } });
|
||||||
ctx.req,
|
|
||||||
ctx.res,
|
|
||||||
{ login: { accountId: id } },
|
|
||||||
{ mergeWithLastSubmission: false }
|
|
||||||
);
|
|
||||||
ctx.body = { redirectTo };
|
|
||||||
ctx.userLog = {
|
|
||||||
...ctx.userLog,
|
|
||||||
type: UserLogType.RegisterPhone,
|
|
||||||
userId: id,
|
|
||||||
phone,
|
|
||||||
};
|
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
@ -277,6 +335,7 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
||||||
'/session/register/passwordless/email',
|
'/session/register/passwordless/email',
|
||||||
koaGuard({ body: object({ email: string(), code: string().optional() }) }),
|
koaGuard({ body: object({ email: string(), code: string().optional() }) }),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
|
ctx.userLog.type = UserLogType.RegisterPhone;
|
||||||
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||||
const { email, code } = ctx.guard.body;
|
const { email, code } = ctx.guard.body;
|
||||||
|
|
||||||
|
@ -285,6 +344,7 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
||||||
!(await hasUserWithEmail(email)),
|
!(await hasUserWithEmail(email)),
|
||||||
new RequestError({ code: 'user.email_exists_register', status: 422 })
|
new RequestError({ code: 'user.email_exists_register', status: 422 })
|
||||||
);
|
);
|
||||||
|
ctx.userLog.email = email;
|
||||||
|
|
||||||
if (!code) {
|
if (!code) {
|
||||||
const passcode = await createPasscode(jti, PasscodeType.Register, { email });
|
const passcode = await createPasscode(jti, PasscodeType.Register, { email });
|
||||||
|
@ -296,20 +356,10 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
||||||
|
|
||||||
await verifyPasscode(jti, PasscodeType.Register, code, { email });
|
await verifyPasscode(jti, PasscodeType.Register, code, { email });
|
||||||
const id = await generateUserId();
|
const id = await generateUserId();
|
||||||
|
ctx.userLog.userId = id;
|
||||||
|
|
||||||
await insertUser({ id, primaryEmail: email });
|
await insertUser({ id, primaryEmail: email });
|
||||||
const redirectTo = await provider.interactionResult(
|
await assignInteractionResults(ctx, provider, { login: { accountId: id } });
|
||||||
ctx.req,
|
|
||||||
ctx.res,
|
|
||||||
{ login: { accountId: id } },
|
|
||||||
{ mergeWithLastSubmission: false }
|
|
||||||
);
|
|
||||||
ctx.body = { redirectTo };
|
|
||||||
ctx.userLog = {
|
|
||||||
...ctx.userLog,
|
|
||||||
type: UserLogType.RegisterPhone,
|
|
||||||
userId: id,
|
|
||||||
email,
|
|
||||||
};
|
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
@ -347,13 +397,7 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const redirectTo = await provider.interactionResult(
|
await assignInteractionResults(ctx, provider, { login: { accountId: id } });
|
||||||
ctx.req,
|
|
||||||
ctx.res,
|
|
||||||
{ login: { accountId: id } },
|
|
||||||
{ mergeWithLastSubmission: false }
|
|
||||||
);
|
|
||||||
ctx.body = { redirectTo };
|
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
@ -391,10 +435,7 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
||||||
router.delete('/session', async (ctx, next) => {
|
router.delete('/session', async (ctx, next) => {
|
||||||
await provider.interactionDetails(ctx.req, ctx.res);
|
await provider.interactionDetails(ctx.req, ctx.res);
|
||||||
const error: LogtoErrorCode = 'oidc.aborted';
|
const error: LogtoErrorCode = 'oidc.aborted';
|
||||||
const redirectTo = await provider.interactionResult(ctx.req, ctx.res, {
|
await assignInteractionResults(ctx, provider, { error });
|
||||||
error,
|
|
||||||
});
|
|
||||||
ctx.body = { redirectTo };
|
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue