0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-06 20:40:08 -05:00

chore(core): refactor register codes (#271)

* chore(core): refactor register routes

* chore(core): remove unnecessary lib/register

* chore(core): fix
This commit is contained in:
Darcy Ye 2022-02-22 22:12:38 +08:00 committed by GitHub
parent b31e90dab2
commit ab4beadd70
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 125 additions and 183 deletions

View file

@ -1,167 +0,0 @@
import { PasscodeType, UserLogType } from '@logto/schemas';
import { Context } from 'koa';
import { Provider } from 'oidc-provider';
import { SocialUserInfo } from '@/connectors/types';
import RequestError from '@/errors/RequestError';
import { WithUserLogContext } from '@/middleware/koa-user-log';
import { hasUser, hasUserWithEmail, hasUserWithPhone, insertUser } from '@/queries/user';
import assertThat from '@/utils/assert-that';
import { emailRegEx, phoneRegEx } from '@/utils/regex';
import { createPasscode, sendPasscode, verifyPasscode } from './passcode';
import { encryptUserPassword, generateUserId } from './user';
const assignRegistrationResult = 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 registerWithUsernameAndPassword = async (
ctx: WithUserLogContext<Context>,
provider: Provider,
username: string,
password: string
) => {
assertThat(
username && password,
new RequestError({
code: 'session.insufficient_info',
status: 400,
})
);
assertThat(
!(await hasUser(username)),
new RequestError({
code: 'user.username_exists_register',
status: 422,
})
);
const id = await generateUserId();
const { passwordEncryptionSalt, passwordEncrypted, passwordEncryptionMethod } =
encryptUserPassword(id, password);
await insertUser({
id,
username,
passwordEncrypted,
passwordEncryptionMethod,
passwordEncryptionSalt,
});
await assignRegistrationResult(ctx, provider, id);
ctx.userLog.userId = id;
ctx.userLog.username = username;
ctx.userLog.type = UserLogType.RegisterUsernameAndPassword;
};
export const sendPasscodeToEmail = 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_exists_register',
status: 422,
})
);
const passcode = await createPasscode(jti, PasscodeType.Register, { email });
await sendPasscode(passcode);
ctx.state = 204;
};
export const sendPasscodeToPhone = 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_exists_register',
status: 422,
})
);
const passcode = await createPasscode(jti, PasscodeType.Register, { phone });
await sendPasscode(passcode);
ctx.state = 204;
};
export const registerWithEmailAndPasscode = async (
ctx: WithUserLogContext<Context>,
provider: Provider,
{ jti, email, code }: { jti: string; email: string; code: string }
) => {
assertThat(
!(await hasUserWithEmail(email)),
new RequestError({
code: 'user.email_exists_register',
status: 422,
})
);
await verifyPasscode(jti, PasscodeType.Register, code, { email });
const id = await generateUserId();
await insertUser({
id,
primaryEmail: email,
});
await assignRegistrationResult(ctx, provider, id);
ctx.userLog.userId = id;
ctx.userLog.email = email;
ctx.userLog.type = UserLogType.RegisterEmail;
};
export const registerWithPhoneAndPasscode = async (
ctx: WithUserLogContext<Context>,
provider: Provider,
{ jti, phone, code }: { jti: string; phone: string; code: string }
) => {
assertThat(
!(await hasUserWithPhone(phone)),
new RequestError({
code: 'user.phone_exists_register',
status: 422,
})
);
await verifyPasscode(jti, PasscodeType.Register, code, { phone });
const id = await generateUserId();
await insertUser({
id,
primaryPhone: phone,
});
await assignRegistrationResult(ctx, provider, id);
ctx.userLog.userId = id;
ctx.userLog.phone = phone;
ctx.userLog.type = UserLogType.RegisterPhone;
};
export const registerWithSocial = async (
ctx: WithUserLogContext<Context>,
provider: Provider,
connectorId: string,
userInfo: SocialUserInfo
) => {
const id = await generateUserId();
await insertUser({
id,
name: userInfo.name ?? null,
avatar: userInfo.avatar ?? null,
identities: {
[connectorId]: {
userId: userInfo.id,
details: userInfo,
},
},
});
await assignRegistrationResult(ctx, provider, id);
};

View file

@ -1,20 +1,14 @@
import path from 'path';
import { LogtoErrorCode } from '@logto/phrases';
import { userInfoSelectFields } from '@logto/schemas';
import { PasscodeType, UserLogType, userInfoSelectFields } from '@logto/schemas';
import { conditional } from '@silverhand/essentials';
import pick from 'lodash.pick';
import { Provider } from 'oidc-provider';
import { object, string } from 'zod';
import {
registerWithSocial,
registerWithEmailAndPasscode,
registerWithPhoneAndPasscode,
registerWithUsernameAndPassword,
sendPasscodeToEmail,
sendPasscodeToPhone,
} from '@/lib/register';
import RequestError from '@/errors/RequestError';
import { createPasscode, sendPasscode, verifyPasscode } from '@/lib/passcode';
import {
assignRedirectUrlForSocial,
sendSignInWithEmailPasscode,
@ -26,9 +20,19 @@ import {
signInWithSocialRelatedUser,
} from '@/lib/sign-in';
import { getUserInfoByAuthCode, getUserInfoFromInteractionResult } from '@/lib/social';
import { encryptUserPassword, generateUserId } from '@/lib/user';
import koaGuard from '@/middleware/koa-guard';
import { findUserById, hasUserWithIdentity, updateUserById } from '@/queries/user';
import {
hasUser,
hasUserWithEmail,
hasUserWithIdentity,
hasUserWithPhone,
insertUser,
findUserById,
updateUserById,
} from '@/queries/user';
import assertThat from '@/utils/assert-that';
import { emailRegEx, phoneRegEx } from '@/utils/regex';
import { AnonymousRouter } from './types';
@ -179,7 +183,47 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
koaGuard({ body: object({ username: string(), password: string() }) }),
async (ctx, next) => {
const { username, password } = ctx.guard.body;
await registerWithUsernameAndPassword(ctx, provider, username, password);
assertThat(
username && password,
new RequestError({
code: 'session.insufficient_info',
status: 400,
})
);
assertThat(
!(await hasUser(username)),
new RequestError({
code: 'user.username_exists_register',
status: 422,
})
);
const id = await generateUserId();
const { passwordEncryptionSalt, passwordEncrypted, passwordEncryptionMethod } =
encryptUserPassword(id, password);
await insertUser({
id,
username,
passwordEncrypted,
passwordEncryptionMethod,
passwordEncryptionSalt,
});
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();
}
@ -192,13 +236,36 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
const { phone, code } = ctx.guard.body;
assertThat(phoneRegEx.test(phone), 'user.invalid_phone');
assertThat(
!(await hasUserWithPhone(phone)),
new RequestError({ code: 'user.phone_exists_register', status: 422 })
);
if (!code) {
await sendPasscodeToPhone(ctx, jti, phone);
const passcode = await createPasscode(jti, PasscodeType.Register, { phone });
await sendPasscode(passcode);
ctx.state = 204;
return next();
}
await registerWithPhoneAndPasscode(ctx, provider, { jti, phone, code });
await verifyPasscode(jti, PasscodeType.Register, code, { phone });
const id = await generateUserId();
await insertUser({ id, primaryPhone: phone });
const redirectTo = await provider.interactionResult(
ctx.req,
ctx.res,
{ login: { accountId: id } },
{ mergeWithLastSubmission: false }
);
ctx.body = { redirectTo };
ctx.userLog = {
...ctx.userLog,
type: UserLogType.RegisterPhone,
userId: id,
phone,
};
return next();
}
@ -211,13 +278,36 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
const { email, code } = ctx.guard.body;
assertThat(emailRegEx.test(email), 'user.invalid_email');
assertThat(
!(await hasUserWithEmail(email)),
new RequestError({ code: 'user.email_exists_register', status: 422 })
);
if (!code) {
await sendPasscodeToEmail(ctx, jti, email);
const passcode = await createPasscode(jti, PasscodeType.Register, { email });
await sendPasscode(passcode);
ctx.state = 204;
return next();
}
await registerWithEmailAndPasscode(ctx, provider, { jti, email, code });
await verifyPasscode(jti, PasscodeType.Register, code, { email });
const id = await generateUserId();
await insertUser({ id, primaryEmail: email });
const redirectTo = await provider.interactionResult(
ctx.req,
ctx.res,
{ login: { accountId: id } },
{ mergeWithLastSubmission: false }
);
ctx.body = { redirectTo };
ctx.userLog = {
...ctx.userLog,
type: UserLogType.RegisterPhone,
userId: id,
email,
};
return next();
}
@ -242,7 +332,26 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
const userInfo = await getUserInfoFromInteractionResult(connectorId, result);
assertThat(!(await hasUserWithIdentity(connectorId, userInfo.id)), 'user.identity_exists');
await registerWithSocial(ctx, provider, connectorId, userInfo);
const id = await generateUserId();
await insertUser({
id,
name: userInfo.name ?? null,
avatar: userInfo.avatar ?? null,
identities: {
[connectorId]: {
userId: userInfo.id,
details: userInfo,
},
},
});
const redirectTo = await provider.interactionResult(
ctx.req,
ctx.res,
{ login: { accountId: id } },
{ mergeWithLastSubmission: false }
);
ctx.body = { redirectTo };
return next();
}