2022-11-09 03:45:25 -05:00
|
|
|
import { SignInIdentifier, SignUpIdentifier } from '@logto/schemas';
|
2022-09-20 22:18:00 -05:00
|
|
|
import { adminConsoleApplicationId } from '@logto/schemas/lib/seeds';
|
2022-11-09 03:45:25 -05:00
|
|
|
import { assert } from '@silverhand/essentials';
|
2022-07-31 20:51:27 -05:00
|
|
|
|
|
|
|
import {
|
|
|
|
mockEmailConnectorId,
|
|
|
|
mockEmailConnectorConfig,
|
|
|
|
mockSmsConnectorId,
|
|
|
|
mockSmsConnectorConfig,
|
|
|
|
} from '@/__mocks__/connectors-mock';
|
|
|
|
import {
|
|
|
|
sendRegisterUserWithEmailPasscode,
|
2022-11-09 03:25:54 -05:00
|
|
|
verifyRegisterUserWithEmailPasscode,
|
2022-11-09 03:45:25 -05:00
|
|
|
sendSignInUserWithEmailPasscode,
|
2022-11-09 03:25:54 -05:00
|
|
|
verifySignInUserWithEmailPasscode,
|
2022-11-09 03:45:25 -05:00
|
|
|
sendRegisterUserWithSmsPasscode,
|
|
|
|
verifyRegisterUserWithSmsPasscode,
|
|
|
|
sendSignInUserWithSmsPasscode,
|
2022-07-31 20:51:27 -05:00
|
|
|
verifySignInUserWithSmsPasscode,
|
2022-11-09 03:45:25 -05:00
|
|
|
disableConnector,
|
|
|
|
signInWithPassword,
|
|
|
|
createUser,
|
2022-07-31 20:51:27 -05:00
|
|
|
} from '@/api';
|
|
|
|
import MockClient from '@/client';
|
2022-08-01 03:28:19 -05:00
|
|
|
import {
|
2022-11-09 03:25:54 -05:00
|
|
|
registerNewUser,
|
|
|
|
signIn,
|
2022-11-09 03:45:25 -05:00
|
|
|
setUpConnector,
|
|
|
|
readPasscode,
|
|
|
|
createUserByAdmin,
|
|
|
|
setSignUpIdentifier,
|
|
|
|
setSignInMethod,
|
2022-08-01 03:28:19 -05:00
|
|
|
} from '@/helpers';
|
2022-07-31 20:51:27 -05:00
|
|
|
import { generateUsername, generatePassword, generateEmail, generatePhone } from '@/utils';
|
2022-07-27 21:13:21 -05:00
|
|
|
|
|
|
|
describe('username and password flow', () => {
|
|
|
|
const username = generateUsername();
|
|
|
|
const password = generatePassword();
|
|
|
|
|
2022-11-10 03:37:12 -05:00
|
|
|
beforeAll(async () => {
|
|
|
|
await setSignUpIdentifier(SignUpIdentifier.Username, true);
|
|
|
|
await setSignInMethod([
|
|
|
|
{
|
|
|
|
identifier: SignInIdentifier.Username,
|
|
|
|
password: true,
|
|
|
|
verificationCode: false,
|
|
|
|
isPasswordPrimary: false,
|
|
|
|
},
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
2022-11-09 03:25:54 -05:00
|
|
|
it('register and sign in with username & password', async () => {
|
2022-07-27 21:13:21 -05:00
|
|
|
await expect(registerNewUser(username, password)).resolves.not.toThrow();
|
2022-11-09 03:25:54 -05:00
|
|
|
await expect(signIn({ username, password })).resolves.not.toThrow();
|
2022-07-27 21:13:21 -05:00
|
|
|
});
|
2022-11-09 03:25:54 -05:00
|
|
|
});
|
2022-07-27 21:13:21 -05:00
|
|
|
|
2022-11-09 03:25:54 -05:00
|
|
|
describe('email and password flow', () => {
|
|
|
|
const email = generateEmail();
|
|
|
|
const [localPart, domain] = email.split('@');
|
|
|
|
const password = generatePassword();
|
|
|
|
|
2022-11-09 03:45:25 -05:00
|
|
|
assert(localPart && domain, new Error('Email address local part or domain is empty'));
|
2022-11-09 03:25:54 -05:00
|
|
|
|
|
|
|
beforeAll(async () => {
|
2022-11-10 03:37:12 -05:00
|
|
|
await setUpConnector(mockEmailConnectorId, mockEmailConnectorConfig);
|
|
|
|
await setSignUpIdentifier(SignUpIdentifier.Email, true);
|
2022-11-09 03:25:54 -05:00
|
|
|
await setSignInMethod([
|
|
|
|
{
|
|
|
|
identifier: SignInIdentifier.Email,
|
|
|
|
password: true,
|
|
|
|
verificationCode: false,
|
|
|
|
isPasswordPrimary: false,
|
|
|
|
},
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('can sign in with email & password', async () => {
|
|
|
|
await createUser({ password, primaryEmail: email, username: generateUsername(), name: 'John' });
|
|
|
|
await expect(
|
|
|
|
Promise.all([
|
|
|
|
signIn({ email, password }),
|
|
|
|
signIn({ email: localPart.toUpperCase() + '@' + domain, password }),
|
|
|
|
signIn({
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
|
|
email: localPart[0]! + localPart.toUpperCase().slice(1) + '@' + domain,
|
|
|
|
password,
|
|
|
|
}),
|
|
|
|
])
|
|
|
|
).resolves.not.toThrow();
|
2022-07-27 21:13:21 -05:00
|
|
|
});
|
|
|
|
});
|
2022-07-31 20:51:27 -05:00
|
|
|
|
|
|
|
describe('email passwordless flow', () => {
|
|
|
|
beforeAll(async () => {
|
|
|
|
await setUpConnector(mockEmailConnectorId, mockEmailConnectorConfig);
|
2022-10-28 02:05:02 -05:00
|
|
|
await setSignUpIdentifier(SignUpIdentifier.Email, false);
|
2022-11-03 05:23:12 -05:00
|
|
|
await setSignInMethod([
|
|
|
|
{
|
|
|
|
identifier: SignInIdentifier.Username,
|
|
|
|
password: true,
|
|
|
|
verificationCode: false,
|
|
|
|
isPasswordPrimary: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
identifier: SignInIdentifier.Email,
|
|
|
|
password: false,
|
|
|
|
verificationCode: true,
|
|
|
|
isPasswordPrimary: false,
|
|
|
|
},
|
|
|
|
]);
|
2022-07-31 20:51:27 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
// Since we can not create a email register user throw admin. Have to run the register then sign-in concurrently.
|
|
|
|
const email = generateEmail();
|
|
|
|
|
|
|
|
it('register with email', async () => {
|
|
|
|
const client = new MockClient();
|
|
|
|
|
|
|
|
await client.initSession();
|
|
|
|
assert(client.interactionCookie, new Error('Session not found'));
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
sendRegisterUserWithEmailPasscode(email, client.interactionCookie)
|
|
|
|
).resolves.not.toThrow();
|
|
|
|
|
|
|
|
const passcodeRecord = await readPasscode();
|
|
|
|
|
|
|
|
expect(passcodeRecord).toMatchObject({
|
|
|
|
address: email,
|
|
|
|
type: 'Register',
|
|
|
|
});
|
|
|
|
|
|
|
|
const { code } = passcodeRecord;
|
|
|
|
|
|
|
|
const { redirectTo } = await verifyRegisterUserWithEmailPasscode(
|
|
|
|
email,
|
|
|
|
code,
|
|
|
|
client.interactionCookie
|
|
|
|
);
|
|
|
|
|
|
|
|
await client.processSession(redirectTo);
|
|
|
|
|
2022-09-09 05:37:04 -05:00
|
|
|
await expect(client.isAuthenticated()).resolves.toBe(true);
|
2022-07-31 20:51:27 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
it('sign-in with email', async () => {
|
|
|
|
const client = new MockClient();
|
|
|
|
|
|
|
|
await client.initSession();
|
|
|
|
assert(client.interactionCookie, new Error('Session not found'));
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
sendSignInUserWithEmailPasscode(email, client.interactionCookie)
|
|
|
|
).resolves.not.toThrow();
|
|
|
|
|
|
|
|
const passcodeRecord = await readPasscode();
|
|
|
|
|
|
|
|
expect(passcodeRecord).toMatchObject({
|
|
|
|
address: email,
|
|
|
|
type: 'SignIn',
|
|
|
|
});
|
|
|
|
|
|
|
|
const { code } = passcodeRecord;
|
|
|
|
|
|
|
|
const { redirectTo } = await verifySignInUserWithEmailPasscode(
|
|
|
|
email,
|
|
|
|
code,
|
|
|
|
client.interactionCookie
|
|
|
|
);
|
|
|
|
|
|
|
|
await client.processSession(redirectTo);
|
|
|
|
|
2022-09-09 05:37:04 -05:00
|
|
|
await expect(client.isAuthenticated()).resolves.toBe(true);
|
2022-07-31 20:51:27 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
void disableConnector(mockEmailConnectorId);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('sms passwordless flow', () => {
|
|
|
|
beforeAll(async () => {
|
|
|
|
await setUpConnector(mockSmsConnectorId, mockSmsConnectorConfig);
|
2022-10-28 02:05:02 -05:00
|
|
|
await setSignUpIdentifier(SignUpIdentifier.Sms, false);
|
2022-11-03 05:23:12 -05:00
|
|
|
await setSignInMethod([
|
|
|
|
{
|
|
|
|
identifier: SignInIdentifier.Username,
|
|
|
|
password: true,
|
|
|
|
verificationCode: false,
|
|
|
|
isPasswordPrimary: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
identifier: SignInIdentifier.Sms,
|
|
|
|
password: false,
|
|
|
|
verificationCode: true,
|
|
|
|
isPasswordPrimary: false,
|
|
|
|
},
|
|
|
|
]);
|
2022-07-31 20:51:27 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
// Since we can not create a sms register user throw admin. Have to run the register then sign-in concurrently.
|
|
|
|
const phone = generatePhone();
|
|
|
|
|
|
|
|
it('register with sms', async () => {
|
|
|
|
const client = new MockClient();
|
|
|
|
|
|
|
|
await client.initSession();
|
|
|
|
assert(client.interactionCookie, new Error('Session not found'));
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
sendRegisterUserWithSmsPasscode(phone, client.interactionCookie)
|
|
|
|
).resolves.not.toThrow();
|
|
|
|
|
|
|
|
const passcodeRecord = await readPasscode();
|
|
|
|
|
|
|
|
expect(passcodeRecord).toMatchObject({
|
|
|
|
phone,
|
|
|
|
type: 'Register',
|
|
|
|
});
|
|
|
|
|
|
|
|
const { code } = passcodeRecord;
|
|
|
|
|
|
|
|
const { redirectTo } = await verifyRegisterUserWithSmsPasscode(
|
|
|
|
phone,
|
|
|
|
code,
|
|
|
|
client.interactionCookie
|
|
|
|
);
|
|
|
|
|
|
|
|
await client.processSession(redirectTo);
|
|
|
|
|
2022-09-09 05:37:04 -05:00
|
|
|
await expect(client.isAuthenticated()).resolves.toBe(true);
|
2022-07-31 20:51:27 -05:00
|
|
|
});
|
|
|
|
|
2022-08-26 03:25:08 -05:00
|
|
|
it('sign-in with sms', async () => {
|
2022-07-31 20:51:27 -05:00
|
|
|
const client = new MockClient();
|
|
|
|
|
|
|
|
await client.initSession();
|
|
|
|
assert(client.interactionCookie, new Error('Session not found'));
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
sendSignInUserWithSmsPasscode(phone, client.interactionCookie)
|
|
|
|
).resolves.not.toThrow();
|
|
|
|
|
|
|
|
const passcodeRecord = await readPasscode();
|
|
|
|
|
|
|
|
expect(passcodeRecord).toMatchObject({
|
|
|
|
phone,
|
|
|
|
type: 'SignIn',
|
|
|
|
});
|
|
|
|
|
|
|
|
const { code } = passcodeRecord;
|
|
|
|
|
|
|
|
const { redirectTo } = await verifySignInUserWithSmsPasscode(
|
|
|
|
phone,
|
|
|
|
code,
|
|
|
|
client.interactionCookie
|
|
|
|
);
|
|
|
|
|
|
|
|
await client.processSession(redirectTo);
|
|
|
|
|
2022-09-09 05:37:04 -05:00
|
|
|
await expect(client.isAuthenticated()).resolves.toBe(true);
|
2022-07-31 20:51:27 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
void disableConnector(mockSmsConnectorId);
|
|
|
|
});
|
|
|
|
});
|
2022-08-01 03:28:19 -05:00
|
|
|
|
|
|
|
describe('sign-in and sign-out', () => {
|
|
|
|
const username = generateUsername();
|
|
|
|
const password = generatePassword();
|
|
|
|
|
|
|
|
beforeAll(async () => {
|
|
|
|
await createUserByAdmin(username, password);
|
2022-10-19 21:57:49 -05:00
|
|
|
await setSignUpIdentifier(SignUpIdentifier.Username);
|
2022-08-01 03:28:19 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
it('verify sign-in and then sign-out', async () => {
|
|
|
|
const client = new MockClient();
|
|
|
|
await client.initSession();
|
|
|
|
|
|
|
|
assert(client.interactionCookie, new Error('Session not found'));
|
|
|
|
|
2022-11-09 03:25:54 -05:00
|
|
|
const { redirectTo } = await signInWithPassword({
|
2022-08-01 03:28:19 -05:00
|
|
|
username,
|
|
|
|
password,
|
2022-11-09 03:25:54 -05:00
|
|
|
interactionCookie: client.interactionCookie,
|
|
|
|
});
|
2022-08-01 03:28:19 -05:00
|
|
|
|
|
|
|
await client.processSession(redirectTo);
|
|
|
|
|
2022-09-09 05:37:04 -05:00
|
|
|
await expect(client.isAuthenticated()).resolves.toBe(true);
|
2022-08-01 03:28:19 -05:00
|
|
|
|
|
|
|
await client.signOut();
|
|
|
|
|
2022-09-09 05:37:04 -05:00
|
|
|
await expect(client.isAuthenticated()).resolves.toBe(false);
|
2022-08-01 03:28:19 -05:00
|
|
|
});
|
|
|
|
});
|
2022-09-20 22:18:00 -05:00
|
|
|
|
|
|
|
describe('sign-in to demo app and revisit Admin Console', () => {
|
|
|
|
const username = generateUsername();
|
|
|
|
const password = generatePassword();
|
|
|
|
|
|
|
|
beforeAll(async () => {
|
|
|
|
await createUserByAdmin(username, password);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should throw in Admin Console consent step if a logged in user does not have admin role', async () => {
|
|
|
|
const client = new MockClient();
|
|
|
|
await client.initSession();
|
|
|
|
|
|
|
|
assert(client.interactionCookie, new Error('Session not found'));
|
|
|
|
|
2022-11-09 03:25:54 -05:00
|
|
|
const { redirectTo } = await signInWithPassword({
|
2022-09-20 22:18:00 -05:00
|
|
|
username,
|
|
|
|
password,
|
2022-11-09 03:25:54 -05:00
|
|
|
interactionCookie: client.interactionCookie,
|
|
|
|
});
|
2022-09-20 22:18:00 -05:00
|
|
|
|
|
|
|
await client.processSession(redirectTo);
|
|
|
|
|
|
|
|
await expect(client.isAuthenticated()).resolves.toBe(true);
|
|
|
|
|
|
|
|
const { interactionCookie } = client;
|
|
|
|
const acClient = new MockClient({ appId: adminConsoleApplicationId });
|
|
|
|
|
|
|
|
acClient.assignCookie(interactionCookie);
|
|
|
|
|
|
|
|
await expect(acClient.initSession()).rejects.toThrow();
|
|
|
|
});
|
|
|
|
});
|