0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

feat: social register (#222)

This commit is contained in:
Wang Sijie 2022-02-14 11:11:34 +08:00 committed by GitHub
parent 8249493c40
commit 78cc86ec77
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 79 additions and 23 deletions

View file

@ -4,11 +4,18 @@ import { Provider } from 'oidc-provider';
import RequestError from '@/errors/RequestError';
import { WithUserLogContext } from '@/middleware/koa-user-log';
import { hasUser, hasUserWithEmail, hasUserWithPhone, insertUser } from '@/queries/user';
import {
hasUser,
hasUserWithEmail,
hasUserWithPhone,
hasUserWithIdentity,
insertUser,
} from '@/queries/user';
import assertThat from '@/utils/assert-that';
import { emailRegEx, phoneRegEx } from '@/utils/regex';
import { createPasscode, sendPasscode, verifyPasscode } from './passcode';
import { getUserInfoByConnectorCode } from './social';
import { encryptUserPassword, generateUserId } from './user';
const assignRegistrationResult = async (ctx: Context, provider: Provider, userId: string) => {
@ -144,3 +151,32 @@ export const registerWithPhoneAndPasscode = async (
ctx.userLog.phone = phone;
ctx.userLog.type = UserLogType.RegisterPhone;
};
export const registerWithSocial = async (
ctx: WithUserLogContext<Context>,
provider: Provider,
{ connectorId, code }: { connectorId: string; code: string }
) => {
const userInfo = await getUserInfoByConnectorCode(connectorId, code);
assertThat(
!(await hasUserWithIdentity(connectorId, userInfo.id)),
new RequestError({
code: 'user.identity_exists',
status: 422,
})
);
const id = await generateUserId();
await insertUser({
id,
identities: {
[connectorId]: {
userId: userInfo.id,
details: userInfo,
},
},
});
await assignRegistrationResult(ctx, provider, id);
};

View file

@ -17,6 +17,7 @@ import assertThat from '@/utils/assert-that';
import { emailRegEx, phoneRegEx } from '@/utils/regex';
import { createPasscode, sendPasscode, verifyPasscode } from './passcode';
import { getUserInfoByConnectorCode } from './social';
import { findUserByUsernameAndPassword } from './user';
const assignSignInResult = async (ctx: Context, provider: Provider, userId: string) => {
@ -117,22 +118,6 @@ export const assignRedirectUrlForSocial = async (
ctx.body = { redirectTo };
};
const getConnector = async (connectorId: string) => {
try {
return await getSocialConnectorInstanceById(connectorId);
} catch (error: unknown) {
// Throw a new error with status 422 when connector not found.
if (error instanceof RequestError && error.code === 'entity.not_found') {
throw new RequestError({
code: 'session.invalid_connector_id',
status: 422,
data: { connectorId },
});
}
throw error;
}
};
export const signInWithSocial = async (
ctx: WithUserLogContext<Context>,
provider: Provider,
@ -141,10 +126,7 @@ export const signInWithSocial = async (
ctx.userLog.connectorId = connectorId;
ctx.userLog.type = UserLogType.SignInSocial;
const connector = await getConnector(connectorId);
const accessToken = await connector.getAccessToken(code);
const userInfo = await connector.getUserInfo(accessToken);
const userInfo = await getUserInfoByConnectorCode(connectorId, code);
assertThat(
await hasUserWithIdentity(connectorId, userInfo.id),

View file

@ -0,0 +1,29 @@
import { getSocialConnectorInstanceById } from '@/connectors';
import { SocialUserInfo } from '@/connectors/types';
import RequestError from '@/errors/RequestError';
const getConnector = async (connectorId: string) => {
try {
return await getSocialConnectorInstanceById(connectorId);
} catch (error: unknown) {
// Throw a new error with status 422 when connector not found.
if (error instanceof RequestError && error.code === 'entity.not_found') {
throw new RequestError({
code: 'session.invalid_connector_id',
status: 422,
data: { connectorId },
});
}
throw error;
}
};
export const getUserInfoByConnectorCode = async (
connectorId: string,
code: string
): Promise<SocialUserInfo> => {
const connector = await getConnector(connectorId);
const accessToken = await connector.getAccessToken(code);
return connector.getUserInfo(accessToken);
};

View file

@ -5,6 +5,7 @@ import { object, string } from 'zod';
import RequestError from '@/errors/RequestError';
import {
registerWithSocial,
registerWithEmailAndPasscode,
registerWithPhoneAndPasscode,
registerWithUsernameAndPassword,
@ -133,14 +134,20 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
email: string().optional(),
phone: string().optional(),
code: string().optional(),
connectorId: string().optional(),
state: string().optional(),
}),
}),
async (ctx, next) => {
const interaction = await provider.interactionDetails(ctx.req, ctx.res);
const { jti } = interaction;
const { username, password, email, phone, code } = ctx.guard.body;
const { username, password, email, phone, code, connectorId, state } = ctx.guard.body;
if (email && !code) {
if (connectorId && state && !code) {
await assignRedirectUrlForSocial(ctx, connectorId, state);
} else if (connectorId && state && code) {
await registerWithSocial(ctx, provider, { connectorId, code });
} else if (email && !code) {
await sendPasscodeToEmail(ctx, jti, email);
} else if (email && code) {
await registerWithEmailAndPasscode(ctx, provider, { jti, email, code });

View file

@ -52,6 +52,7 @@ const errors = {
email_not_exists: 'The email address has not been registered yet.',
phone_not_exists: 'The phone number has not been registered yet.',
identity_not_exists: 'The social account has not been registered yet.',
identity_exists: 'The social account has been registered.',
},
password: {
unsupported_encryption_method: 'The encryption method {{name}} is not supported.',

View file

@ -53,6 +53,7 @@ const errors = {
email_not_exists: '邮箱地址尚未注册。',
phone_not_exists: '手机号码尚未注册。',
identity_not_exists: '该社交账号尚未注册。',
identity_exists: '该社交账号已被注册。',
},
password: {
unsupported_encryption_method: '不支持的加密方法 {{name}}。',