Skip to content

Commit b1eb1b2

Browse files
authored
feat(client): add mfa operations (#1049)
1 parent a9d890c commit b1eb1b2

File tree

17 files changed

+530
-47
lines changed

17 files changed

+530
-47
lines changed

packages/client-password/src/client-password.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
CreateUserServicePassword,
55
CreateUserResult,
66
LoginUserPasswordService,
7+
AuthenticationResult,
78
} from '@accounts/types';
89
import { AccountsClientPasswordOptions } from './types';
910

@@ -39,7 +40,7 @@ export class AccountsClientPassword {
3940
/**
4041
* Log the user in with a password.
4142
*/
42-
public async login(user: LoginUserPasswordService): Promise<LoginResult> {
43+
public async login(user: LoginUserPasswordService): Promise<AuthenticationResult> {
4344
const hashedPassword = this.hashPassword(user.password as string);
4445
return this.client.loginWithService('password', {
4546
...user,

packages/client/__tests__/accounts-client.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ const mockTransport = {
3030
verifyEmail: jest.fn(() => Promise.resolve()),
3131
sendVerificationEmail: jest.fn(() => Promise.resolve()),
3232
impersonate: jest.fn(() => Promise.resolve(impersonateResult)),
33+
getUser: jest.fn(() => Promise.resolve()),
34+
mfaChallenge: jest.fn(() => Promise.resolve()),
35+
mfaAssociate: jest.fn(() => Promise.resolve()),
36+
mfaAssociateByMfaToken: jest.fn(() => Promise.resolve()),
37+
authenticators: jest.fn(() => Promise.resolve()),
38+
authenticatorsByMfaToken: jest.fn(() => Promise.resolve()),
3339
};
3440

3541
describe('Accounts', () => {
@@ -270,4 +276,53 @@ describe('Accounts', () => {
270276
expect(userTokens).toEqual(impersonateResult.tokens);
271277
});
272278
});
279+
280+
describe('getUser', () => {
281+
it('calls transport', async () => {
282+
await accountsClient.getUser();
283+
expect(mockTransport.getUser).toHaveBeenCalledWith();
284+
});
285+
});
286+
287+
describe('mfaChallenge', () => {
288+
it('calls transport', async () => {
289+
await accountsClient.mfaChallenge('mfaTokenTest', 'authenticatorIdTest');
290+
expect(mockTransport.mfaChallenge).toHaveBeenCalledWith(
291+
'mfaTokenTest',
292+
'authenticatorIdTest'
293+
);
294+
});
295+
});
296+
297+
describe('mfaAssociate', () => {
298+
it('calls transport', async () => {
299+
await accountsClient.mfaAssociate('typeTest');
300+
expect(mockTransport.mfaAssociate).toHaveBeenCalledWith('typeTest', undefined);
301+
});
302+
});
303+
304+
describe('mfaAssociateByMfaToken', () => {
305+
it('calls transport', async () => {
306+
await accountsClient.mfaAssociateByMfaToken('mfaTokenTest', 'typeTest');
307+
expect(mockTransport.mfaAssociateByMfaToken).toHaveBeenCalledWith(
308+
'mfaTokenTest',
309+
'typeTest',
310+
undefined
311+
);
312+
});
313+
});
314+
315+
describe('authenticators', () => {
316+
it('calls transport', async () => {
317+
await accountsClient.authenticators();
318+
expect(mockTransport.authenticators).toHaveBeenCalledWith();
319+
});
320+
});
321+
322+
describe('authenticatorsByMfaToken', () => {
323+
it('calls transport', async () => {
324+
await accountsClient.authenticatorsByMfaToken('mfaTokenTest');
325+
expect(mockTransport.authenticatorsByMfaToken).toHaveBeenCalledWith('mfaTokenTest');
326+
});
327+
});
273328
});

packages/client/src/accounts-client.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { LoginResult, Tokens, ImpersonationResult, User } from '@accounts/types';
1+
import { Tokens, ImpersonationResult, User, AuthenticationResult } from '@accounts/types';
22
import { TransportInterface } from './transport-interface';
33
import { TokenStorage, AccountsClientOptions } from './types';
44
import { tokenStorageLocal } from './token-storage-local';
@@ -102,9 +102,11 @@ export class AccountsClient {
102102
public async loginWithService(
103103
service: string,
104104
credentials: { [key: string]: any }
105-
): Promise<LoginResult> {
105+
): Promise<AuthenticationResult> {
106106
const response = await this.transport.loginWithService(service, credentials);
107-
await this.setTokens(response.tokens);
107+
if ('tokens' in response) {
108+
await this.setTokens(response.tokens);
109+
}
108110
return response;
109111
}
110112

@@ -198,6 +200,26 @@ export class AccountsClient {
198200
await this.clearTokens();
199201
}
200202

203+
public mfaChallenge(mfaToken: string, authenticatorId: string) {
204+
return this.transport.mfaChallenge(mfaToken, authenticatorId);
205+
}
206+
207+
public mfaAssociate(type: string, params?: any) {
208+
return this.transport.mfaAssociate(type, params);
209+
}
210+
211+
public mfaAssociateByMfaToken(mfaToken: string, type: string, params?: any) {
212+
return this.transport.mfaAssociateByMfaToken(mfaToken, type, params);
213+
}
214+
215+
public authenticators() {
216+
return this.transport.authenticators();
217+
}
218+
219+
public authenticatorsByMfaToken(mfaToken: string) {
220+
return this.transport.authenticatorsByMfaToken(mfaToken);
221+
}
222+
201223
private getTokenKey(tokenName: TokenKey): string {
202224
return `${this.options.tokenStoragePrefix}:${tokenName}`;
203225
}

packages/client/src/transport-interface.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import {
55
CreateUser,
66
User,
77
CreateUserResult,
8+
Authenticator,
9+
AuthenticationResult,
810
} from '@accounts/types';
911
import { AccountsClient } from './accounts-client';
1012

11-
export interface TransportInterface {
13+
export interface TransportInterface extends TransportMfaInterface {
1214
client: AccountsClient;
1315
createUser(user: CreateUser): Promise<CreateUserResult>;
1416
authenticateWithService(
@@ -22,7 +24,7 @@ export interface TransportInterface {
2224
authenticateParams: {
2325
[key: string]: string | object;
2426
}
25-
): Promise<LoginResult>;
27+
): Promise<AuthenticationResult>;
2628
logout(): Promise<void>;
2729
getUser(): Promise<User>;
2830
refreshTokens(accessToken: string, refreshToken: string): Promise<LoginResult>;
@@ -34,3 +36,11 @@ export interface TransportInterface {
3436
changePassword(oldPassword: string, newPassword: string): Promise<void>;
3537
impersonate(token: string, impersonated: ImpersonationUserIdentity): Promise<ImpersonationResult>;
3638
}
39+
40+
interface TransportMfaInterface {
41+
authenticators(): Promise<Authenticator[]>;
42+
authenticatorsByMfaToken(mfaToken?: string): Promise<Authenticator[]>;
43+
mfaAssociate(type: string, params?: any): Promise<any>;
44+
mfaAssociateByMfaToken(mfaToken: string, type: string, params?: any): Promise<any>;
45+
mfaChallenge(mfaToken: string, authenticatorId: string): Promise<any>;
46+
}

packages/e2e/__tests__/password.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { servers } from './servers';
2+
import { LoginResult } from '@accounts/types';
23

34
const user = {
45
@@ -36,12 +37,12 @@ Object.keys(servers).forEach((key) => {
3637
});
3738

3839
it('should login the user and get the session', async () => {
39-
const loginResult = await server.accountsClientPassword.login({
40+
const loginResult = (await server.accountsClientPassword.login({
4041
user: {
4142
email: user.email,
4243
},
4344
password: user.password,
44-
});
45+
})) as LoginResult;
4546
expect(loginResult.sessionId).toBeTruthy();
4647
expect(loginResult.tokens.accessToken).toBeTruthy();
4748
expect(loginResult.tokens.refreshToken).toBeTruthy();
@@ -91,12 +92,12 @@ Object.keys(servers).forEach((key) => {
9192
user.password = newPassword;
9293
expect(data).toBeNull();
9394

94-
const loginResult = await server.accountsClientPassword.login({
95+
const loginResult = (await server.accountsClientPassword.login({
9596
user: {
9697
email: user.email,
9798
},
9899
password: user.password,
99-
});
100+
})) as LoginResult;
100101
expect(loginResult.sessionId).toBeTruthy();
101102
expect(loginResult.tokens.accessToken).toBeTruthy();
102103
expect(loginResult.tokens.refreshToken).toBeTruthy();
@@ -116,12 +117,12 @@ Object.keys(servers).forEach((key) => {
116117
user.password = newPassword;
117118
expect(data).toBeNull();
118119

119-
const loginResult = await server.accountsClientPassword.login({
120+
const loginResult = (await server.accountsClientPassword.login({
120121
user: {
121122
email: user.email,
122123
},
123124
password: user.password,
124-
});
125+
})) as LoginResult;
125126
expect(loginResult.sessionId).toBeTruthy();
126127
expect(loginResult.tokens.accessToken).toBeTruthy();
127128
expect(loginResult.tokens.refreshToken).toBeTruthy();

0 commit comments

Comments
 (0)