11import * as messages from './message' ;
22import {
3- FileFactory ,
4- UserFactory ,
3+ DeviceFactory ,
54 Entity ,
6- ManagedFactory ,
75 EntityFactory ,
8- DeviceFactory ,
9- LoginOption , OAuthOptions ,
6+ FileFactory ,
7+ LoginOption ,
8+ ManagedFactory ,
9+ OAuthOptions ,
10+ UserFactory ,
1011} from './binding' ;
11- import {
12- atob ,
13- Class , deprecated ,
14- isNode ,
15- JsonMap ,
16- Lockable ,
17- uuid ,
18- openWindow ,
19- } from './util' ;
20- import {
21- Message , StatusCode , Connector , OAuthMessage ,
22- } from './connector' ;
12+ import { atob , Class , deprecated , isNode , JsonMap , Lockable , openWindow , uuid , } from './util' ;
13+ import { Connector , Message , OAuthMessage , RestSpecification , StatusCode } from './connector' ;
2314import { BloomFilter } from './caching' ;
2415import { GeoPoint } from './GeoPoint' ;
2516import type { ConnectData , EntityManagerFactory } from './EntityManagerFactory' ;
2617import * as model from './model' ;
2718import type { Metamodel } from './metamodel' ;
19+ import { EntityType , ManagedType , MapAttribute , PluralAttribute , } from './metamodel' ;
2820
2921import { Builder } from './query' ;
3022import { EntityExistsError , IllegalEntityError , PersistentError } from './error' ;
31- import {
32- MapAttribute , EntityType , ManagedType , PluralAttribute ,
33- } from './metamodel' ;
3423
3524import {
3625 Code ,
@@ -43,6 +32,9 @@ import {
4332 Validator ,
4433} from './intersection' ;
4534import { appendQueryParams , CACHE_REPLACEMENT_SUPPORTED } from './connector/Message' ;
35+ import { MFAError } from './error/MFAError' ;
36+ import { Base64 } from './util/Base64' ;
37+ import { MFAResponse } from './util/Mfa' ;
4638
4739const DB_PREFIX = '/db/' ;
4840
@@ -958,6 +950,79 @@ export class EntityManager extends Lockable {
958950 return this . withLock ( ( ) => this . send ( new messages . Logout ( ) ) . then ( this . _logout . bind ( this ) ) ) ;
959951 }
960952
953+ /**
954+ * Starts the MFA initiate process - note you must be logged in, to start the mfa setup process
955+ *
956+ * @returns A promise that resolves to an object with the following properties:
957+ * - qrCode: A Base64 representation of the QR code for MFA setup.
958+ * - keyUri: The URI for the MFA secret key.
959+ * @example
960+ * const { qrCode, keyUri } = await db.initMFA();
961+ * const code = await setupMFADevice(qrCode, keyUri);
962+ * const user = await db.finishMFA(code);
963+ */
964+ async initMFA ( ) : Promise < MFAResponse > {
965+ return this . send ( new messages . MFAInitChallenge ( ) ) . then ( ( resp ) => {
966+ return {
967+ qrCode : resp . entity . qrCode as Base64 < 'png' > ,
968+ keyUri : resp . entity . keyUri as string
969+ } ;
970+ } ) ;
971+ }
972+
973+ /**
974+ * Finishes the MFA (Multi-Factor Authentication) initiation process.
975+ *
976+ * @param code - The verification code for MFA.
977+ * @returns A promise that resolves with the user object of the logged-in user.
978+ */
979+ public finishMFA ( code : number ) : Promise < model . User > {
980+ return this . send ( new messages . MFAInitFinish ( { code } ) ) . then ( ( resp ) => {
981+ return this . User . me ! ; // to be here user is already logged in;
982+ } ) ;
983+ }
984+
985+ /**
986+ * Submit a verification code after a login
987+ *
988+ * @param code - A 6 digit verification code
989+ * @param token - An MFA token obtained during the login process
990+ * @return The logged-in user object
991+ */
992+ async submitMFACode ( code : number , token : string ) : Promise < model . User > {
993+ const loginType = this . tokenStorage . temporary ? LoginOption . SESSION_LOGIN : LoginOption . PERSIST_LOGIN ;
994+ const msg = new messages . MFAToken ( {
995+ authToken : token ,
996+ code,
997+ global : loginType === LoginOption . PERSIST_LOGIN ,
998+ } ) ;
999+ return this . withLock ( ( ) => this . _userRequest ( msg , loginType ) ) as Promise < model . User > ;
1000+ }
1001+
1002+ /**
1003+ * Disables Multi-Factor Authentication for the currently logged in user.
1004+ *
1005+ * @throws {PersistentError } - Thrown when the user is not logged in.
1006+ * @return A promise that resolves when Multi-Factor Authentication is successfully disabled.
1007+ */
1008+ disableMFA ( ) : Promise < any > {
1009+ if ( ! this . User . me )
1010+ throw new PersistentError ( 'User not Logged in' ) ;
1011+
1012+ return this . send ( new messages . MFADelete ( ) ) ;
1013+ }
1014+
1015+ /**
1016+ * Returns the current MFA status of the user
1017+ *
1018+ * @returns A promise that resolves to the MFA status of the user.
1019+ * Possible values are 'ENABLED' if MFA is enabled, 'DISABLED' if MFA is
1020+ * disabled, or 'PENDING' if MFA status is pending.
1021+ */
1022+ getMFAStatus ( ) : Promise < 'ENABLED' | 'DISABLED' | 'PENDING' > {
1023+ return this . send ( new messages . MFAStatus ( ) ) . then ( ( resp ) => resp . entity ) ;
1024+ }
1025+
9611026 loginWithOAuth ( provider : string , options : OAuthOptions ) : any | string | Promise < model . User | null > {
9621027 if ( ! this . connection ) {
9631028 throw new Error ( 'This EntityManager is not connected.' ) ;
@@ -1028,7 +1093,7 @@ export class EntityManager extends Lockable {
10281093 private _loginOAuthDevice ( provider : string , opt : OAuthOptions ) : Promise < model . User | null > {
10291094 return this . _userRequest ( new messages . OAuth2 ( provider , opt . deviceCode ) , opt . loginOption )
10301095 . catch ( ( ) => new Promise ( ( resolve ) => setTimeout ( resolve , 5000 ) )
1031- . then ( ( ) => this . _loginOAuthDevice ( provider , opt ) ) ) ;
1096+ . then ( ( ) => this . _loginOAuthDevice ( provider , opt ) ) ) as Promise < model . User | null > ;
10321097 }
10331098
10341099 renew ( loginOption ?: LoginOption | boolean ) {
@@ -1092,7 +1157,9 @@ export class EntityManager extends Lockable {
10921157
10931158 return this . send ( msg , ! login )
10941159 . then (
1095- ( response ) => ( response . entity ? this . _updateUser ( response . entity , login ) : null ) ,
1160+ ( response ) => {
1161+ return response . entity ? this . _updateUser ( response . entity , login ) : null ;
1162+ } ,
10961163 ( e ) => {
10971164 if ( e . status === StatusCode . OBJECT_NOT_FOUND ) {
10981165 if ( login ) {
@@ -1101,6 +1168,11 @@ export class EntityManager extends Lockable {
11011168 return null ;
11021169 }
11031170
1171+ if ( e . status === StatusCode . FORBIDDEN ) {
1172+ const { data } = e ;
1173+ throw new MFAError ( data [ 'baqend-mfa-auth-token' ] ) ; // If MFA is required: throw an error containing the auth token
1174+ }
1175+
11041176 throw e ;
11051177 } ,
11061178 ) ;
0 commit comments