0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-10 21:58:23 -05:00

feat(core): continue sign in with social (#2281)

This commit is contained in:
wangsijie 2022-11-01 10:08:47 +08:00 committed by GitHub
parent cb46ad9fd6
commit f419fc8279
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 387 additions and 62 deletions

View file

@ -6,10 +6,13 @@ import { createRequester } from '@/utils/test-utils';
import continueRoutes, { continueRoute } from './continue';
const getVerificationStorageFromInteraction = jest.fn();
const checkRequiredProfile = jest.fn();
jest.mock('./utils', () => ({
...jest.requireActual('./utils'),
checkRequiredProfile: () => checkRequiredProfile(),
getVerificationStorageFromInteraction: () => getVerificationStorageFromInteraction(),
}));
jest.mock('@/queries/sign-in-experience', () => ({
@ -18,10 +21,16 @@ jest.mock('@/queries/sign-in-experience', () => ({
const updateUserById = jest.fn(async (..._args: unknown[]) => mockUser);
const findUserById = jest.fn(async (..._args: unknown[]) => mockUser);
const hasUser = jest.fn();
const hasUserWithPhone = jest.fn();
const hasUserWithEmail = jest.fn();
jest.mock('@/queries/user', () => ({
updateUserById: async (...args: unknown[]) => updateUserById(...args),
findUserById: async () => findUserById(),
hasUser: async () => hasUser(),
hasUserWithPhone: async () => hasUserWithPhone(),
hasUserWithEmail: async () => hasUserWithEmail(),
}));
const interactionResult = jest.fn(async () => 'redirectTo');
@ -82,32 +91,138 @@ describe('session -> continueRoutes', () => {
expect.anything()
);
});
});
it('throws on empty continue sign in storage', async () => {
interactionDetails.mockResolvedValueOnce({
jti: 'jti',
result: {},
});
const response = await sessionRequest.post(`${continueRoute}/password`).send({
password: 'password',
});
expect(response.statusCode).toEqual(401);
});
it('throws on expired continue sign in storage', async () => {
describe('POST /session/sign-in/continue/username', () => {
it('updates user username, checks required profile, and sign in', async () => {
interactionDetails.mockResolvedValueOnce({
jti: 'jti',
result: {
continueSignIn: {
userId: mockUser.id,
expiresAt: dayjs().subtract(1, 'second').toISOString(),
expiresAt: dayjs().add(1, 'day').toISOString(),
},
},
});
const response = await sessionRequest.post(`${continueRoute}/password`).send({
password: 'password',
findUserById.mockResolvedValueOnce({
...mockUser,
username: null,
});
expect(response.statusCode).toEqual(401);
const response = await sessionRequest.post(`${continueRoute}/username`).send({
username: 'username',
});
expect(response.statusCode).toEqual(200);
expect(checkRequiredProfile).toHaveBeenCalled();
expect(hasUser).toHaveBeenCalled();
expect(updateUserById).toHaveBeenCalledWith(mockUser.id, expect.anything());
expect(response.body).toHaveProperty('redirectTo');
expect(interactionResult).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.objectContaining({ login: { accountId: mockUser.id } }),
expect.anything()
);
});
});
describe('POST /session/sign-in/continue/email', () => {
beforeEach(() => {
getVerificationStorageFromInteraction.mockResolvedValueOnce({ email: 'email' });
});
it('updates user email, checks required profile, and sign in', async () => {
interactionDetails.mockResolvedValueOnce({
jti: 'jti',
result: {
continueSignIn: {
userId: mockUser.id,
expiresAt: dayjs().add(1, 'day').toISOString(),
},
},
});
findUserById.mockResolvedValueOnce({
...mockUser,
primaryEmail: null,
});
const response = await sessionRequest.post(`${continueRoute}/email`).send();
expect(response.statusCode).toEqual(200);
expect(checkRequiredProfile).toHaveBeenCalled();
expect(hasUser).toHaveBeenCalled();
expect(updateUserById).toHaveBeenCalledWith(mockUser.id, expect.anything());
expect(response.body).toHaveProperty('redirectTo');
expect(interactionResult).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.objectContaining({ login: { accountId: mockUser.id } }),
expect.anything()
);
});
});
describe('POST /session/sign-in/continue/sms', () => {
it('updates user phone, checks required profile, and sign in', async () => {
getVerificationStorageFromInteraction.mockResolvedValueOnce({ phone: 'phone' });
interactionDetails.mockResolvedValueOnce({
jti: 'jti',
result: {
continueSignIn: {
userId: mockUser.id,
expiresAt: dayjs().add(1, 'day').toISOString(),
},
},
});
findUserById.mockResolvedValueOnce({
...mockUser,
primaryPhone: null,
});
const response = await sessionRequest.post(`${continueRoute}/sms`).send();
expect(response.statusCode).toEqual(200);
expect(checkRequiredProfile).toHaveBeenCalled();
expect(hasUserWithPhone).toHaveBeenCalled();
expect(updateUserById).toHaveBeenCalledWith(mockUser.id, expect.anything());
expect(response.body).toHaveProperty('redirectTo');
expect(interactionResult).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.objectContaining({ login: { accountId: mockUser.id } }),
expect.anything()
);
});
});
describe('general invalid cases', () => {
test.each(['password', 'username', 'email', 'sms'])(
'throws on empty continue sign in storage',
async (route) => {
interactionDetails.mockResolvedValueOnce({
jti: 'jti',
result: {},
});
const response = await sessionRequest.post(`${continueRoute}/${route}`).send({
password: 'password',
username: 'username',
});
expect(response.statusCode).toEqual(401);
}
);
test.each(['password', 'username', 'email', 'sms'])(
'throws on expired continue sign in storage',
async () => {
interactionDetails.mockResolvedValueOnce({
jti: 'jti',
result: {
continueSignIn: {
userId: mockUser.id,
expiresAt: dayjs().subtract(1, 'second').toISOString(),
},
},
});
const response = await sessionRequest.post(`${continueRoute}/password`).send({
password: 'password',
});
expect(response.statusCode).toEqual(401);
}
);
});
});

View file

@ -1,4 +1,4 @@
import { passwordRegEx } from '@logto/core-kit';
import { passwordRegEx, usernameRegEx } from '@logto/core-kit';
import type { Provider } from 'oidc-provider';
import { object, string } from 'zod';
@ -7,11 +7,23 @@ import { assignInteractionResults } from '@/lib/session';
import { encryptUserPassword } from '@/lib/user';
import koaGuard from '@/middleware/koa-guard';
import { findDefaultSignInExperience } from '@/queries/sign-in-experience';
import { findUserById, updateUserById } from '@/queries/user';
import {
findUserById,
hasUser,
hasUserWithEmail,
hasUserWithPhone,
updateUserById,
} from '@/queries/user';
import assertThat from '@/utils/assert-that';
import type { AnonymousRouter } from '../types';
import { checkRequiredProfile, getContinueSignInResult, getRoutePrefix } from './utils';
import { emailSessionResultGuard, smsSessionResultGuard } from './types';
import {
checkRequiredProfile,
getContinueSignInResult,
getRoutePrefix,
getVerificationStorageFromInteraction,
} from './utils';
export const continueRoute = getRoutePrefix('sign-in', 'continue');
@ -48,4 +60,110 @@ export default function continueRoutes<T extends AnonymousRouter>(router: T, pro
return next();
}
);
router.post(
`${continueRoute}/username`,
koaGuard({
body: object({
username: string().regex(usernameRegEx),
}),
}),
async (ctx, next) => {
const { username } = ctx.guard.body;
const { userId } = await getContinueSignInResult(ctx, provider);
const user = await findUserById(userId);
assertThat(
!user.username,
new RequestError({
code: 'user.username_exists',
})
);
assertThat(
!(await hasUser(username)),
new RequestError({
code: 'user.username_exists_register',
status: 422,
})
);
const updatedUser = await updateUserById(userId, {
username,
});
const signInExperience = await findDefaultSignInExperience();
await checkRequiredProfile(ctx, provider, updatedUser, signInExperience);
await assignInteractionResults(ctx, provider, { login: { accountId: updatedUser.id } });
return next();
}
);
router.post(`${continueRoute}/email`, async (ctx, next) => {
const { userId } = await getContinueSignInResult(ctx, provider);
const { email } = await getVerificationStorageFromInteraction(
ctx,
provider,
emailSessionResultGuard
);
const user = await findUserById(userId);
assertThat(
!user.primaryEmail,
new RequestError({
code: 'user.email_exists',
})
);
assertThat(
!(await hasUserWithEmail(email)),
new RequestError({
code: 'user.email_exists_register',
status: 422,
})
);
const updatedUser = await updateUserById(userId, {
primaryEmail: email,
});
const signInExperience = await findDefaultSignInExperience();
await checkRequiredProfile(ctx, provider, updatedUser, signInExperience);
await assignInteractionResults(ctx, provider, { login: { accountId: updatedUser.id } });
return next();
});
router.post(`${continueRoute}/sms`, async (ctx, next) => {
const { userId } = await getContinueSignInResult(ctx, provider);
const { phone } = await getVerificationStorageFromInteraction(
ctx,
provider,
smsSessionResultGuard
);
const user = await findUserById(userId);
assertThat(
!user.primaryPhone,
new RequestError({
code: 'user.sms_exists',
})
);
assertThat(
!(await hasUserWithPhone(phone)),
new RequestError({
code: 'user.phone_exists_register',
status: 422,
})
);
const updatedUser = await updateUserById(userId, {
primaryPhone: phone,
});
const signInExperience = await findDefaultSignInExperience();
await checkRequiredProfile(ctx, provider, updatedUser, signInExperience);
await assignInteractionResults(ctx, provider, { login: { accountId: updatedUser.id } });
return next();
});
}

View file

@ -12,10 +12,10 @@ import { verificationTimeout } from './consts';
import * as passwordlessActions from './middleware/passwordless-action';
import passwordlessRoutes, { registerRoute, signInRoute } from './passwordless';
const insertUser = jest.fn(async (..._args: unknown[]) => ({ id: 'foo' }));
const insertUser = jest.fn(async (..._args: unknown[]) => mockUser);
const findUserById = jest.fn(async (): Promise<User> => mockUser);
const findUserByEmail = jest.fn(async (): Promise<User> => mockUser);
const updateUserById = jest.fn(async (..._args: unknown[]) => ({ id: 'foo' }));
const updateUserById = jest.fn(async (..._args: unknown[]) => mockUser);
const findDefaultSignInExperience = jest.fn(async () => ({
...mockSignInExperience,
signUp: {
@ -33,7 +33,7 @@ jest.mock('@/lib/user', () => ({
jest.mock('@/queries/user', () => ({
findUserById: async () => findUserById(),
findUserByPhone: async () => ({ id: 'foo' }),
findUserByPhone: async () => mockUser,
findUserByEmail: async () => findUserByEmail(),
updateUserById: async (...args: unknown[]) => updateUserById(...args),
hasUser: async (username: string) => username === 'username1',
@ -250,7 +250,7 @@ describe('session -> passwordlessRoutes', () => {
expect.anything(),
expect.objectContaining({
verification: {
userId: 'foo',
userId: mockUser.id,
expiresAt: dayjs(fakeTime).add(verificationTimeout, 'second').toISOString(),
flow: PasscodeType.ForgotPassword,
},
@ -346,7 +346,7 @@ describe('session -> passwordlessRoutes', () => {
expect.anything(),
expect.objectContaining({
verification: {
userId: 'foo',
userId: mockUser.id,
expiresAt: dayjs(fakeTime).add(verificationTimeout, 'second').toISOString(),
flow: PasscodeType.ForgotPassword,
},
@ -391,7 +391,7 @@ describe('session -> passwordlessRoutes', () => {
expect.anything(),
expect.anything(),
expect.objectContaining({
login: { accountId: 'foo' },
login: { accountId: mockUser.id },
}),
expect.anything()
);
@ -413,7 +413,7 @@ describe('session -> passwordlessRoutes', () => {
expect.anything(),
expect.anything(),
expect.objectContaining({
login: { accountId: 'foo' },
login: { accountId: mockUser.id },
}),
expect.anything()
);
@ -554,7 +554,7 @@ describe('session -> passwordlessRoutes', () => {
expect.anything(),
expect.anything(),
expect.objectContaining({
login: { accountId: 'foo' },
login: { accountId: mockUser.id },
}),
expect.anything()
);
@ -578,7 +578,7 @@ describe('session -> passwordlessRoutes', () => {
expect.anything(),
expect.anything(),
expect.objectContaining({
login: { accountId: 'foo' },
login: { accountId: mockUser.id },
}),
expect.anything()
);

View file

@ -1,8 +1,9 @@
import { ConnectorType } from '@logto/connector-kit';
import type { User } from '@logto/schemas';
import { SignUpIdentifier } from '@logto/schemas';
import { Provider } from 'oidc-provider';
import { mockLogtoConnectorList, mockUser } from '@/__mocks__';
import { mockLogtoConnectorList, mockSignInExperience, mockUser } from '@/__mocks__';
import { getLogtoConnectorById } from '@/connectors';
import RequestError from '@/errors/RequestError';
import { createRequester } from '@/utils/test-utils';
@ -24,7 +25,7 @@ jest.mock('@/lib/social', () => ({
}
if (data.code === '123456') {
return { id: 'id' };
return { id: mockUser.id };
}
// This mocks the case that can not get userInfo with access token and auth code
@ -32,16 +33,16 @@ jest.mock('@/lib/social', () => ({
throw new Error(' ');
},
}));
const insertUser = jest.fn(async (..._args: unknown[]) => ({ id: 'id' }));
const insertUser = jest.fn(async (..._args: unknown[]) => mockUser);
const findUserById = jest.fn(async (): Promise<User> => mockUser);
const updateUserById = jest.fn(async (..._args: unknown[]) => ({ id: 'id' }));
const updateUserById = jest.fn(async (..._args: unknown[]) => mockUser);
jest.mock('@/queries/user', () => ({
findUserById: async () => findUserById(),
findUserByIdentity: async () => ({ id: 'id', identities: {} }),
findUserByIdentity: async () => mockUser,
updateUserById: async (...args: unknown[]) => updateUserById(...args),
hasUserWithIdentity: async (target: string, userId: string) =>
target === 'connectorTarget' && userId === 'id',
target === 'connectorTarget' && userId === mockUser.id,
}));
jest.mock('@/lib/user', () => ({
@ -49,6 +50,16 @@ jest.mock('@/lib/user', () => ({
insertUser: async (...args: unknown[]) => insertUser(...args),
}));
jest.mock('@/queries/sign-in-experience', () => ({
findDefaultSignInExperience: async () => ({
...mockSignInExperience,
signUp: {
...mockSignInExperience.signUp,
identifier: SignUpIdentifier.None,
},
}),
}));
const getLogtoConnectorByIdHelper = jest.fn(async (connectorId: string) => {
const database = {
enabled: connectorId === 'social_enabled',
@ -170,9 +181,6 @@ describe('session -> socialRoutes', () => {
describe('POST /session/sign-in/social/auth', () => {
const connectorTarget = 'connectorTarget';
afterEach(() => {
jest.clearAllMocks();
});
it('throw error when auth code is wrong', async () => {
(getLogtoConnectorById as jest.Mock).mockResolvedValueOnce({
@ -213,16 +221,19 @@ describe('session -> socialRoutes', () => {
},
});
expect(updateUserById).toHaveBeenCalledWith(
'id',
mockUser.id,
expect.objectContaining({
identities: { connectorTarget: { userId: 'id', details: { id: 'id' } } },
identities: {
...mockUser.identities,
connectorTarget: { userId: mockUser.id, details: { id: mockUser.id } },
},
})
);
expect(response.body).toHaveProperty('redirectTo');
expect(interactionResult).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.objectContaining({ login: { accountId: 'id' } }),
expect.objectContaining({ login: { accountId: mockUser.id } }),
expect.anything()
);
});
@ -244,7 +255,7 @@ describe('session -> socialRoutes', () => {
expect.anything(),
expect.anything(),
expect.objectContaining({
socialUserInfo: { connectorId: '_connectorId_', userInfo: { id: 'id' } },
socialUserInfo: { connectorId: '_connectorId_', userInfo: { id: mockUser.id } },
}),
expect.anything()
);
@ -259,9 +270,6 @@ describe('session -> socialRoutes', () => {
metadata: { target: 'connectorTarget' },
});
});
afterEach(() => {
jest.clearAllMocks();
});
it('throw if session is not authorized', async () => {
await expect(
sessionRequest
@ -321,9 +329,6 @@ describe('session -> socialRoutes', () => {
metadata: { target: 'connectorTarget' },
});
});
afterEach(() => {
jest.clearAllMocks();
});
it('register with social, assign result and redirect', async () => {
interactionDetails.mockResolvedValueOnce({
@ -359,7 +364,7 @@ describe('session -> socialRoutes', () => {
});
it('throw error if result parsing fails', async () => {
interactionDetails.mockResolvedValueOnce({ result: { login: { accountId: 'id' } } });
interactionDetails.mockResolvedValueOnce({ result: { login: { accountId: mockUser.id } } });
const response = await sessionRequest
.post(`${registerRoute}`)
.send({ connectorId: 'connectorId' });
@ -370,7 +375,7 @@ describe('session -> socialRoutes', () => {
interactionDetails.mockResolvedValueOnce({
result: {
login: { accountId: 'user1' },
socialUserInfo: { connectorId: 'connectorId', userInfo: { id: 'id' } },
socialUserInfo: { connectorId: 'connectorId', userInfo: { id: mockUser.id } },
},
});
const response = await sessionRequest
@ -387,9 +392,6 @@ describe('session -> socialRoutes', () => {
metadata: { target: 'connectorTarget' },
});
});
afterEach(() => {
jest.clearAllMocks();
});
it('throw if session is not authorized', async () => {
interactionDetails.mockResolvedValueOnce({});
await expect(

View file

@ -14,6 +14,7 @@ import {
} from '@/lib/social';
import { generateUserId, insertUser } from '@/lib/user';
import koaGuard from '@/middleware/koa-guard';
import { findDefaultSignInExperience } from '@/queries/sign-in-experience';
import {
hasUserWithIdentity,
findUserById,
@ -24,7 +25,7 @@ import assertThat from '@/utils/assert-that';
import { maskUserInfo } from '@/utils/format';
import type { AnonymousRouter } from '../types';
import { getRoutePrefix } from './utils';
import { checkRequiredProfile, getRoutePrefix } from './utils';
export const registerRoute = getRoutePrefix('register', 'social');
export const signInRoute = getRoutePrefix('sign-in', 'social');
@ -92,7 +93,8 @@ export default function socialRoutes<T extends AnonymousRouter>(router: T, provi
);
}
const { id, identities } = await findUserByIdentity(target, userInfo.id);
const user = await findUserByIdentity(target, userInfo.id);
const { id, identities } = user;
ctx.log(type, { userId: id });
// Update social connector's user info
@ -100,6 +102,9 @@ export default function socialRoutes<T extends AnonymousRouter>(router: T, provi
identities: { ...identities, [target]: { userId: userInfo.id, details: userInfo } },
lastSignInAt: Date.now(),
});
const signInExperience = await findDefaultSignInExperience();
await checkRequiredProfile(ctx, provider, user, signInExperience);
await assignInteractionResults(ctx, provider, { login: { accountId: id } });
return next();
@ -131,10 +136,13 @@ export default function socialRoutes<T extends AnonymousRouter>(router: T, provi
const { id, identities } = relatedInfo[1];
ctx.log(type, { userId: id });
await updateUserById(id, {
const user = await updateUserById(id, {
identities: { ...identities, [target]: { userId: userInfo.id, details: userInfo } },
lastSignInAt: Date.now(),
});
const signInExperience = await findDefaultSignInExperience();
await checkRequiredProfile(ctx, provider, user, signInExperience);
await assignInteractionResults(ctx, provider, { login: { accountId: id } });
return next();
@ -167,7 +175,7 @@ export default function socialRoutes<T extends AnonymousRouter>(router: T, provi
assertThat(!(await hasUserWithIdentity(target, userInfo.id)), 'user.identity_exists');
const id = await generateUserId();
await insertUser({
const user = await insertUser({
id,
name: userInfo.name ?? null,
avatar: userInfo.avatar ?? null,
@ -181,6 +189,8 @@ export default function socialRoutes<T extends AnonymousRouter>(router: T, provi
});
ctx.log(type, { userId: id });
const signInExperience = await findDefaultSignInExperience();
await checkRequiredProfile(ctx, provider, user, signInExperience);
await assignInteractionResults(ctx, provider, { login: { accountId: id } });
return next();

View file

@ -6,7 +6,7 @@ import type {
SignInIdentifier,
User,
} from '@logto/schemas';
import { logTypeGuard } from '@logto/schemas';
import { SignUpIdentifier, logTypeGuard } from '@logto/schemas';
import type { Nullable, Truthy } from '@silverhand/essentials';
import dayjs from 'dayjs';
import type { Context } from 'koa';
@ -121,7 +121,10 @@ export const assignContinueSignInResult = async (
});
};
export const getContinueSignInResult = async (ctx: Context, provider: Provider) => {
export const getContinueSignInResult = async (
ctx: Context,
provider: Provider
): Promise<{ userId: string }> => {
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
const signInResult = z
@ -147,6 +150,7 @@ export const getContinueSignInResult = async (ctx: Context, provider: Provider)
return rest;
};
/* eslint-disable complexity */
export const checkRequiredProfile = async (
ctx: Context,
provider: Provider,
@ -154,7 +158,7 @@ export const checkRequiredProfile = async (
signInExperience: SignInExperience
) => {
const { signUp } = signInExperience;
const { passwordEncrypted, id } = user;
const { passwordEncrypted, id, username, primaryEmail, primaryPhone } = user;
// If check failed, save the sign in result, the user can continue after requirements are meet
@ -162,7 +166,28 @@ export const checkRequiredProfile = async (
await assignContinueSignInResult(ctx, provider, { userId: id });
throw new RequestError({ code: 'user.require_password', status: 422 });
}
if (signUp.identifier === SignUpIdentifier.Username && !username) {
await assignContinueSignInResult(ctx, provider, { userId: id });
throw new RequestError({ code: 'user.require_username', status: 422 });
}
if (signUp.identifier === SignUpIdentifier.Email && !primaryEmail) {
await assignContinueSignInResult(ctx, provider, { userId: id });
throw new RequestError({ code: 'user.require_email', status: 422 });
}
if (signUp.identifier === SignUpIdentifier.Sms && !primaryPhone) {
await assignContinueSignInResult(ctx, provider, { userId: id });
throw new RequestError({ code: 'user.require_sms', status: 422 });
}
if (signUp.identifier === SignUpIdentifier.EmailOrSms && !primaryEmail && !primaryPhone) {
await assignContinueSignInResult(ctx, provider, { userId: id });
throw new RequestError({ code: 'user.require_email_or_sms', status: 422 });
}
};
/* eslint-enable complexity */
type SignInWithPasswordParameter = {
identifier: SignInIdentifier;

View file

@ -1,9 +1,15 @@
import { SignUpIdentifier } from '@logto/schemas';
import type { StatisticsData } from '@/api';
import { getTotalUsersCount, getNewUsersData, getActiveUsersData } from '@/api';
import { createUserByAdmin, registerNewUser, signIn } from '@/helpers';
import { createUserByAdmin, registerNewUser, setSignUpIdentifier, signIn } from '@/helpers';
import { generateUsername, generatePassword } from '@/utils';
describe('admin console dashboard', () => {
beforeAll(async () => {
await setSignUpIdentifier(SignUpIdentifier.Username);
});
it('should get total user count successfully', async () => {
const { totalUserCount: originTotalUserCount } = await getTotalUsersCount();

View file

@ -1,13 +1,18 @@
import { SignUpIdentifier } from '@logto/schemas';
import { assert } from '@silverhand/essentials';
import { getLogs, getLog } from '@/api';
import { registerNewUser } from '@/helpers';
import { registerNewUser, setSignUpIdentifier } from '@/helpers';
import { generateUsername, generatePassword } from '@/utils';
describe('admin console logs', () => {
const username = generateUsername();
const password = generatePassword();
beforeAll(async () => {
await setSignUpIdentifier(SignUpIdentifier.Username);
});
it('should get logs and visit log details successfully', async () => {
await registerNewUser(username, password);

View file

@ -1,3 +1,4 @@
import { SignUpIdentifier } from '@logto/schemas';
import { assert } from '@silverhand/essentials';
import { HTTPError } from 'got';
@ -15,7 +16,7 @@ import {
getUser,
} from '@/api';
import MockClient from '@/client';
import { setUpConnector, createUserByAdmin } from '@/helpers';
import { setUpConnector, createUserByAdmin, setSignUpIdentifier } from '@/helpers';
import { generateUsername, generatePassword } from '@/utils';
const state = 'foo_state';
@ -27,6 +28,7 @@ describe('social sign-in and register', () => {
beforeAll(async () => {
await setUpConnector(mockSocialConnectorId, mockSocialConnectorConfig);
await setSignUpIdentifier(SignUpIdentifier.None, false);
});
it('register with social', async () => {

View file

@ -47,6 +47,13 @@ const errors = {
same_password: 'Your new password cant be the same as your current password.',
require_password: 'You need to set a password before sign in.',
password_exists: 'Your password has been set.',
require_username: 'You need to set a username before sign in.',
username_exists: 'Your username has been set.',
require_email: 'You need to set an email before sign in.',
email_exists: 'Your email has been set.',
require_sms: 'You need to set a phone before sign in.',
sms_exists: 'Your phone has been set.',
require_email_or_sms: 'You need to set a phone or email before sign in.',
},
password: {
unsupported_encryption_method: 'The encryption method {{name}} is not supported.',

View file

@ -48,6 +48,13 @@ const errors = {
same_password: 'Your new password cant be the same as your current password.', // UNTRANSLATED
require_password: 'You need to set a password before sign in.', // UNTRANSLATED
password_exists: 'Your password has been set.', // UNTRANSLATED
require_username: 'You need to set a username before sign in.', // UNTRANSLATED
username_exists: 'Your username has been set.', // UNTRANSLATED
require_email: 'You need to set an email before sign in.', // UNTRANSLATED
email_exists: 'Your email has been set.', // UNTRANSLATED
require_sms: 'You need to set a phone before sign in.', // UNTRANSLATED
sms_exists: 'Your phone has been set.', // UNTRANSLATED
require_email_or_sms: 'You need to set a phone or email before sign in.', // UNTRANSLATED
},
password: {
unsupported_encryption_method: "La méthode de cryptage {{name}} n'est pas prise en charge.",

View file

@ -46,6 +46,13 @@ const errors = {
same_password: 'Your new password cant be the same as your current password.', // UNTRANSLATED
require_password: 'You need to set a password before sign in.', // UNTRANSLATED
password_exists: 'Your password has been set.', // UNTRANSLATED
require_username: 'You need to set a username before sign in.', // UNTRANSLATED
username_exists: 'Your username has been set.', // UNTRANSLATED
require_email: 'You need to set an email before sign in.', // UNTRANSLATED
email_exists: 'Your email has been set.', // UNTRANSLATED
require_sms: 'You need to set a phone before sign in.', // UNTRANSLATED
sms_exists: 'Your phone has been set.', // UNTRANSLATED
require_email_or_sms: 'You need to set a phone or email before sign in.', // UNTRANSLATED
},
password: {
unsupported_encryption_method: '{{name}} 암호화 방법을 지원하지 않아요.',

View file

@ -46,6 +46,13 @@ const errors = {
same_password: 'Your new password cant be the same as your current password.', // UNTRANSLATED
require_password: 'You need to set a password before sign in.', // UNTRANSLATED
password_exists: 'Your password has been set.', // UNTRANSLATED
require_username: 'You need to set a username before sign in.', // UNTRANSLATED
username_exists: 'Your username has been set.', // UNTRANSLATED
require_email: 'You need to set an email before sign in.', // UNTRANSLATED
email_exists: 'Your email has been set.', // UNTRANSLATED
require_sms: 'You need to set a phone before sign in.', // UNTRANSLATED
sms_exists: 'Your phone has been set.', // UNTRANSLATED
require_email_or_sms: 'You need to set a phone or email before sign in.', // UNTRANSLATED
},
password: {
unsupported_encryption_method: 'O método de enncriptação {{name}} não é suportado.',

View file

@ -47,6 +47,13 @@ const errors = {
same_password: 'Your new password cant be the same as your current password.', // UNTRANSLATED
require_password: 'You need to set a password before sign in.', // UNTRANSLATED
password_exists: 'Your password has been set.', // UNTRANSLATED
require_username: 'You need to set a username before sign in.', // UNTRANSLATED
username_exists: 'Your username has been set.', // UNTRANSLATED
require_email: 'You need to set an email before sign in.', // UNTRANSLATED
email_exists: 'Your email has been set.', // UNTRANSLATED
require_sms: 'You need to set a phone before sign in.', // UNTRANSLATED
sms_exists: 'Your phone has been set.', // UNTRANSLATED
require_email_or_sms: 'You need to set a phone or email before sign in.', // UNTRANSLATED
},
password: {
unsupported_encryption_method: '{{name}} şifreleme metodu desteklenmiyor.',

View file

@ -46,6 +46,13 @@ const errors = {
same_password: '为确保你的账户安全,新密码不能与旧密码一致',
require_password: '请设置密码',
password_exists: '密码已设置过',
require_username: '请设置用户名',
username_exists: '用户名已设置过',
require_email: '请绑定邮箱地址',
email_exists: '已绑定邮箱地址',
require_sms: '请绑定手机号码',
sms_exists: '已绑定手机号码',
require_email_or_sms: '请绑定邮箱地址或手机号码',
},
password: {
unsupported_encryption_method: '不支持的加密方法 {{name}}',