diff --git a/README.md b/README.md index bd35602..a367b2e 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Appwrite is an open-source backend as a service server that abstract and simplif -![Appwrite](https://appwrite.io/images/github.png) +![Appwrite](https://github.com/appwrite/appwrite/raw/main/public/images/github.png) ## Installation @@ -21,7 +21,7 @@ Add this to your package's `pubspec.yaml` file: ```yml dependencies: - appwrite: ^12.0.0-rc.6 + appwrite: ^12.0.0 ``` You can install packages from the command line: diff --git a/docs/examples/account/add-authenticator.md b/docs/examples/account/create-mfa-authenticator.md similarity index 82% rename from docs/examples/account/add-authenticator.md rename to docs/examples/account/create-mfa-authenticator.md index 6f32c04..b43ec1a 100644 --- a/docs/examples/account/add-authenticator.md +++ b/docs/examples/account/create-mfa-authenticator.md @@ -6,6 +6,6 @@ Client client = Client() Account account = Account(client); -MfaType result = await account.addAuthenticator( +MfaType result = await account.createMfaAuthenticator( type: AuthenticatorType.totp, ); diff --git a/docs/examples/account/create-challenge.md b/docs/examples/account/create-mfa-challenge.md similarity index 70% rename from docs/examples/account/create-challenge.md rename to docs/examples/account/create-mfa-challenge.md index a661116..64bcb36 100644 --- a/docs/examples/account/create-challenge.md +++ b/docs/examples/account/create-mfa-challenge.md @@ -6,6 +6,6 @@ Client client = Client() Account account = Account(client); -MfaChallenge result = await account.createChallenge( - factor: AuthenticationFactor.totp, +MfaChallenge result = await account.createMfaChallenge( + factor: AuthenticationFactor.email, ); diff --git a/docs/examples/account/create-mfa-recovery-codes.md b/docs/examples/account/create-mfa-recovery-codes.md new file mode 100644 index 0000000..68fdaaa --- /dev/null +++ b/docs/examples/account/create-mfa-recovery-codes.md @@ -0,0 +1,9 @@ +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint + .setProject('5df5acd0d48c2'); // Your project ID + +Account account = Account(client); + +MfaRecoveryCodes result = await account.createMfaRecoveryCodes(); diff --git a/docs/examples/account/delete-authenticator.md b/docs/examples/account/delete-mfa-authenticator.md similarity index 88% rename from docs/examples/account/delete-authenticator.md rename to docs/examples/account/delete-mfa-authenticator.md index f4f44b1..3f62177 100644 --- a/docs/examples/account/delete-authenticator.md +++ b/docs/examples/account/delete-mfa-authenticator.md @@ -6,7 +6,7 @@ Client client = Client() Account account = Account(client); -await account.deleteAuthenticator( +await account.deleteMfaAuthenticator( type: AuthenticatorType.totp, otp: '', ); diff --git a/docs/examples/account/get-mfa-recovery-codes.md b/docs/examples/account/get-mfa-recovery-codes.md new file mode 100644 index 0000000..008f8ce --- /dev/null +++ b/docs/examples/account/get-mfa-recovery-codes.md @@ -0,0 +1,9 @@ +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint + .setProject('5df5acd0d48c2'); // Your project ID + +Account account = Account(client); + +MfaRecoveryCodes result = await account.getMfaRecoveryCodes(); diff --git a/docs/examples/account/list-factors.md b/docs/examples/account/list-mfa-factors.md similarity index 81% rename from docs/examples/account/list-factors.md rename to docs/examples/account/list-mfa-factors.md index b58f175..08186e3 100644 --- a/docs/examples/account/list-factors.md +++ b/docs/examples/account/list-mfa-factors.md @@ -6,4 +6,4 @@ Client client = Client() Account account = Account(client); -MfaFactors result = await account.listFactors(); +MfaFactors result = await account.listMfaFactors(); diff --git a/docs/examples/account/verify-authenticator.md b/docs/examples/account/update-mfa-authenticator.md similarity index 84% rename from docs/examples/account/verify-authenticator.md rename to docs/examples/account/update-mfa-authenticator.md index 74f7111..4f5074e 100644 --- a/docs/examples/account/verify-authenticator.md +++ b/docs/examples/account/update-mfa-authenticator.md @@ -6,7 +6,7 @@ Client client = Client() Account account = Account(client); -User result = await account.verifyAuthenticator( +User result = await account.updateMfaAuthenticator( type: AuthenticatorType.totp, otp: '', ); diff --git a/docs/examples/account/update-challenge.md b/docs/examples/account/update-mfa-challenge.md similarity index 86% rename from docs/examples/account/update-challenge.md rename to docs/examples/account/update-mfa-challenge.md index a27e620..34c3780 100644 --- a/docs/examples/account/update-challenge.md +++ b/docs/examples/account/update-mfa-challenge.md @@ -6,7 +6,7 @@ Client client = Client() Account account = Account(client); - result = await account.updateChallenge( + result = await account.updateMfaChallenge( challengeId: '', otp: '', ); diff --git a/docs/examples/account/update-mfa-recovery-codes.md b/docs/examples/account/update-mfa-recovery-codes.md new file mode 100644 index 0000000..914366f --- /dev/null +++ b/docs/examples/account/update-mfa-recovery-codes.md @@ -0,0 +1,9 @@ +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint + .setProject('5df5acd0d48c2'); // Your project ID + +Account account = Account(client); + +MfaRecoveryCodes result = await account.updateMfaRecoveryCodes(); diff --git a/lib/enums.dart b/lib/enums.dart index c450fce..426e3a6 100644 --- a/lib/enums.dart +++ b/lib/enums.dart @@ -1,8 +1,8 @@ /// Appwrite Enums library appwrite.enums; -part 'src/enums/authentication_factor.dart'; part 'src/enums/authenticator_type.dart'; +part 'src/enums/authentication_factor.dart'; part 'src/enums/o_auth_provider.dart'; part 'src/enums/browser.dart'; part 'src/enums/credit_card.dart'; diff --git a/lib/models.dart b/lib/models.dart index 920216e..a6de0d8 100644 --- a/lib/models.dart +++ b/lib/models.dart @@ -44,6 +44,7 @@ part 'src/models/currency.dart'; part 'src/models/phone.dart'; part 'src/models/headers.dart'; part 'src/models/mfa_challenge.dart'; +part 'src/models/mfa_recovery_codes.dart'; part 'src/models/mfa_type.dart'; part 'src/models/mfa_factors.dart'; part 'src/models/subscriber.dart'; diff --git a/lib/services/account.dart b/lib/services/account.dart index 3309947..dfb7892 100644 --- a/lib/services/account.dart +++ b/lib/services/account.dart @@ -184,9 +184,76 @@ class Account extends Service { } + /// Add Authenticator + /// + /// Add an authenticator app to be used as an MFA factor. Verify the + /// authenticator using the [verify + /// authenticator](/docs/references/cloud/client-web/account#verifyAuthenticator) + /// method. + Future createMfaAuthenticator({required enums.AuthenticatorType type}) async { + final String apiPath = '/account/mfa/authenticators/{type}'.replaceAll('{type}', type.value); + + final Map apiParams = { + }; + + final Map apiHeaders = { + 'content-type': 'application/json', + }; + + final res = await client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); + + return models.MfaType.fromMap(res.data); + + } + + /// Verify Authenticator + /// + /// Verify an authenticator app after adding it using the [add + /// authenticator](/docs/references/cloud/client-web/account#addAuthenticator) + /// method. + Future updateMfaAuthenticator({required enums.AuthenticatorType type, required String otp}) async { + final String apiPath = '/account/mfa/authenticators/{type}'.replaceAll('{type}', type.value); + + final Map apiParams = { + 'otp': otp, + }; + + final Map apiHeaders = { + 'content-type': 'application/json', + }; + + final res = await client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); + + return models.User.fromMap(res.data); + + } + + /// Delete Authenticator + /// + /// Delete an authenticator for a user by ID. + Future deleteMfaAuthenticator({required enums.AuthenticatorType type, required String otp}) async { + final String apiPath = '/account/mfa/authenticators/{type}'.replaceAll('{type}', type.value); + + final Map apiParams = { + 'otp': otp, + }; + + final Map apiHeaders = { + 'content-type': 'application/json', + }; + + final res = await client.call(HttpMethod.delete, path: apiPath, params: apiParams, headers: apiHeaders); + + return models.User.fromMap(res.data); + + } + /// Create 2FA Challenge /// - Future createChallenge({required enums.AuthenticationFactor factor}) async { + /// Begin the process of MFA verification after sign-in. Finish the flow with + /// [updateMfaChallenge](/docs/references/cloud/client-web/account#updateMfaChallenge) + /// method. + Future createMfaChallenge({required enums.AuthenticationFactor factor}) async { const String apiPath = '/account/mfa/challenge'; final Map apiParams = { @@ -205,8 +272,12 @@ class Account extends Service { /// Create MFA Challenge (confirmation) /// - /// Complete the MFA challenge by providing the one-time password. - Future updateChallenge({required String challengeId, required String otp}) async { + /// Complete the MFA challenge by providing the one-time password. Finish the + /// process of MFA verification by providing the one-time password. To begin + /// the flow, use + /// [createMfaChallenge](/docs/references/cloud/client-web/account#createMfaChallenge) + /// method. + Future updateMfaChallenge({required String challengeId, required String otp}) async { const String apiPath = '/account/mfa/challenge'; final Map apiParams = { @@ -227,7 +298,7 @@ class Account extends Service { /// List Factors /// /// List the factors available on the account to be used as a MFA challange. - Future listFactors() async { + Future listMfaFactors() async { const String apiPath = '/account/mfa/factors'; final Map apiParams = { @@ -243,14 +314,37 @@ class Account extends Service { } - /// Add Authenticator + /// Get MFA Recovery Codes /// - /// Add an authenticator app to be used as an MFA factor. Verify the - /// authenticator using the [verify - /// authenticator](/docs/references/cloud/client-web/account#verifyAuthenticator) + /// Get recovery codes that can be used as backup for MFA flow. Before getting + /// codes, they must be generated using + /// [createMfaRecoveryCodes](/docs/references/cloud/client-web/account#createMfaRecoveryCodes) + /// method. An OTP challenge is required to read recovery codes. + Future getMfaRecoveryCodes() async { + const String apiPath = '/account/mfa/recovery-codes'; + + final Map apiParams = { + }; + + final Map apiHeaders = { + 'content-type': 'application/json', + }; + + final res = await client.call(HttpMethod.get, path: apiPath, params: apiParams, headers: apiHeaders); + + return models.MfaRecoveryCodes.fromMap(res.data); + + } + + /// Create MFA Recovery Codes + /// + /// Generate recovery codes as backup for MFA flow. It's recommended to + /// generate and show then immediately after user successfully adds their + /// authehticator. Recovery codes can be used as a MFA verification type in + /// [createMfaChallenge](/docs/references/cloud/client-web/account#createMfaChallenge) /// method. - Future addAuthenticator({required enums.AuthenticatorType type}) async { - final String apiPath = '/account/mfa/{type}'.replaceAll('{type}', type.value); + Future createMfaRecoveryCodes() async { + const String apiPath = '/account/mfa/recovery-codes'; final Map apiParams = { }; @@ -261,49 +355,29 @@ class Account extends Service { final res = await client.call(HttpMethod.post, path: apiPath, params: apiParams, headers: apiHeaders); - return models.MfaType.fromMap(res.data); + return models.MfaRecoveryCodes.fromMap(res.data); } - /// Verify Authenticator + /// Regenerate MFA Recovery Codes /// - /// Verify an authenticator app after adding it using the [add - /// authenticator](/docs/references/cloud/client-web/account#addAuthenticator) - /// method. - Future verifyAuthenticator({required enums.AuthenticatorType type, required String otp}) async { - final String apiPath = '/account/mfa/{type}'.replaceAll('{type}', type.value); + /// Regenerate recovery codes that can be used as backup for MFA flow. Before + /// regenerating codes, they must be first generated using + /// [createMfaRecoveryCodes](/docs/references/cloud/client-web/account#createMfaRecoveryCodes) + /// method. An OTP challenge is required to regenreate recovery codes. + Future updateMfaRecoveryCodes() async { + const String apiPath = '/account/mfa/recovery-codes'; final Map apiParams = { - 'otp': otp, }; final Map apiHeaders = { 'content-type': 'application/json', }; - final res = await client.call(HttpMethod.put, path: apiPath, params: apiParams, headers: apiHeaders); + final res = await client.call(HttpMethod.patch, path: apiPath, params: apiParams, headers: apiHeaders); - return models.User.fromMap(res.data); - - } - - /// Delete Authenticator - /// - /// Delete an authenticator for a user by ID. - Future deleteAuthenticator({required enums.AuthenticatorType type, required String otp}) async { - final String apiPath = '/account/mfa/{type}'.replaceAll('{type}', type.value); - - final Map apiParams = { - 'otp': otp, - }; - - final Map apiHeaders = { - 'content-type': 'application/json', - }; - - final res = await client.call(HttpMethod.delete, path: apiPath, params: apiParams, headers: apiHeaders); - - return models.User.fromMap(res.data); + return models.MfaRecoveryCodes.fromMap(res.data); } @@ -708,10 +782,11 @@ class Account extends Service { } - /// Update (or renew) session + /// Update session /// - /// Extend session's expiry to increase it's lifespan. Extending a session is - /// useful when session length is short such as 5 minutes. + /// Use this endpoint to extend a session's length. Extending a session is + /// useful when session expiry is short. If the session was created using an + /// OAuth provider, this endpoint refreshes the access token from the provider. Future updateSession({required String sessionId}) async { final String apiPath = '/account/sessions/{sessionId}'.replaceAll('{sessionId}', sessionId); diff --git a/lib/src/client_browser.dart b/lib/src/client_browser.dart index 0c6e013..6f3ded4 100644 --- a/lib/src/client_browser.dart +++ b/lib/src/client_browser.dart @@ -43,7 +43,7 @@ class ClientBrowser extends ClientBase with ClientMixin { 'x-sdk-name': 'Flutter', 'x-sdk-platform': 'client', 'x-sdk-language': 'flutter', - 'x-sdk-version': '12.0.0-rc.6', + 'x-sdk-version': '12.0.0', 'X-Appwrite-Response-Format': '1.5.0', }; diff --git a/lib/src/client_io.dart b/lib/src/client_io.dart index 99fb6e0..88063f3 100644 --- a/lib/src/client_io.dart +++ b/lib/src/client_io.dart @@ -64,7 +64,7 @@ class ClientIO extends ClientBase with ClientMixin { 'x-sdk-name': 'Flutter', 'x-sdk-platform': 'client', 'x-sdk-language': 'flutter', - 'x-sdk-version': '12.0.0-rc.6', + 'x-sdk-version': '12.0.0', 'X-Appwrite-Response-Format' : '1.5.0', }; diff --git a/lib/src/enums/authentication_factor.dart b/lib/src/enums/authentication_factor.dart index 228281a..b9f7ccd 100644 --- a/lib/src/enums/authentication_factor.dart +++ b/lib/src/enums/authentication_factor.dart @@ -1,9 +1,10 @@ part of appwrite.enums; enum AuthenticationFactor { - totp(value: 'totp'), + email(value: 'email'), phone(value: 'phone'), - email(value: 'email'); + totp(value: 'totp'), + recoverycode(value: 'recoverycode'); const AuthenticationFactor({ required this.value diff --git a/lib/src/enums/flag.dart b/lib/src/enums/flag.dart index 38a890a..9eacf8d 100644 --- a/lib/src/enums/flag.dart +++ b/lib/src/enums/flag.dart @@ -34,7 +34,7 @@ enum Flag { switzerland(value: 'ch'), chile(value: 'cl'), china(value: 'cn'), - cTeDIvoire(value: 'ci'), + coteDIvoire(value: 'ci'), cameroon(value: 'cm'), democraticRepublicOfTheCongo(value: 'cd'), republicOfTheCongo(value: 'cg'), diff --git a/lib/src/models/mfa_recovery_codes.dart b/lib/src/models/mfa_recovery_codes.dart new file mode 100644 index 0000000..1740f2b --- /dev/null +++ b/lib/src/models/mfa_recovery_codes.dart @@ -0,0 +1,23 @@ +part of appwrite.models; + +/// MFA Recovery Codes +class MfaRecoveryCodes implements Model { + /// Recovery codes. + final List recoveryCodes; + + MfaRecoveryCodes({ + required this.recoveryCodes, + }); + + factory MfaRecoveryCodes.fromMap(Map map) { + return MfaRecoveryCodes( + recoveryCodes: map['recoveryCodes'] ?? [], + ); + } + + Map toMap() { + return { + "recoveryCodes": recoveryCodes, + }; + } +} diff --git a/lib/src/models/mfa_type.dart b/lib/src/models/mfa_type.dart index d30fb32..7100245 100644 --- a/lib/src/models/mfa_type.dart +++ b/lib/src/models/mfa_type.dart @@ -2,22 +2,18 @@ part of appwrite.models; /// MFAType class MfaType implements Model { - /// Backup codes. - final List backups; /// Secret token used for TOTP factor. final String secret; /// URI for authenticator apps. final String uri; MfaType({ - required this.backups, required this.secret, required this.uri, }); factory MfaType.fromMap(Map map) { return MfaType( - backups: map['backups'] ?? [], secret: map['secret'].toString(), uri: map['uri'].toString(), ); @@ -25,7 +21,6 @@ class MfaType implements Model { Map toMap() { return { - "backups": backups, "secret": secret, "uri": uri, }; diff --git a/lib/src/models/session.dart b/lib/src/models/session.dart index 76b921f..151afb7 100644 --- a/lib/src/models/session.dart +++ b/lib/src/models/session.dart @@ -56,6 +56,8 @@ class Session implements Model { final List factors; /// Secret used to authenticate the user. Only included if the request was made with an API key final String secret; + /// Most recent date in ISO 8601 format when the session successfully passed MFA challenge. + final String mfaUpdatedAt; Session({ required this.$id, @@ -85,6 +87,7 @@ class Session implements Model { required this.current, required this.factors, required this.secret, + required this.mfaUpdatedAt, }); factory Session.fromMap(Map map) { @@ -116,6 +119,7 @@ class Session implements Model { current: map['current'], factors: map['factors'] ?? [], secret: map['secret'].toString(), + mfaUpdatedAt: map['mfaUpdatedAt'].toString(), ); } @@ -148,6 +152,7 @@ class Session implements Model { "current": current, "factors": factors, "secret": secret, + "mfaUpdatedAt": mfaUpdatedAt, }; } } diff --git a/lib/src/models/user.dart b/lib/src/models/user.dart index e5108be..c201d42 100644 --- a/lib/src/models/user.dart +++ b/lib/src/models/user.dart @@ -34,8 +34,6 @@ class User implements Model { final bool phoneVerification; /// Multi factor authentication status. final bool mfa; - /// TOTP status. - final bool totp; /// User preferences as a key-value object final Preferences prefs; /// A user-owned message receiver. A single user may have multiple e.g. emails, phones, and a browser. Each target is registered with a single provider. @@ -60,7 +58,6 @@ class User implements Model { required this.emailVerification, required this.phoneVerification, required this.mfa, - required this.totp, required this.prefs, required this.targets, required this.accessedAt, @@ -84,7 +81,6 @@ class User implements Model { emailVerification: map['emailVerification'], phoneVerification: map['phoneVerification'], mfa: map['mfa'], - totp: map['totp'], prefs: Preferences.fromMap(map['prefs']), targets: List.from(map['targets'].map((p) => Target.fromMap(p))), accessedAt: map['accessedAt'].toString(), @@ -109,7 +105,6 @@ class User implements Model { "emailVerification": emailVerification, "phoneVerification": phoneVerification, "mfa": mfa, - "totp": totp, "prefs": prefs.toMap(), "targets": targets.map((p) => p.toMap()).toList(), "accessedAt": accessedAt, diff --git a/pubspec.yaml b/pubspec.yaml index d083f74..adbd5bb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: appwrite -version: 12.0.0-rc.6 +version: 12.0.0 description: Appwrite is an open-source self-hosted backend server that abstract and simplify complex and repetitive development tasks behind a very simple REST API homepage: https://appwrite.io repository: https://github.com/appwrite/sdk-for-flutter diff --git a/test/services/account_test.dart b/test/services/account_test.dart index 0035c6e..c82c152 100644 --- a/test/services/account_test.dart +++ b/test/services/account_test.dart @@ -69,7 +69,6 @@ void main() { 'emailVerification': true, 'phoneVerification': true, 'mfa': true, - 'totp': true, 'prefs': {}, 'targets': [], 'accessedAt': '2020-10-15T06:38:00.000+00:00',}; @@ -101,7 +100,6 @@ void main() { 'emailVerification': true, 'phoneVerification': true, 'mfa': true, - 'totp': true, 'prefs': {}, 'targets': [], 'accessedAt': '2020-10-15T06:38:00.000+00:00',}; @@ -136,7 +134,6 @@ void main() { 'emailVerification': true, 'phoneVerification': true, 'mfa': true, - 'totp': true, 'prefs': {}, 'targets': [], 'accessedAt': '2020-10-15T06:38:00.000+00:00',}; @@ -233,7 +230,6 @@ void main() { 'emailVerification': true, 'phoneVerification': true, 'mfa': true, - 'totp': true, 'prefs': {}, 'targets': [], 'accessedAt': '2020-10-15T06:38:00.000+00:00',}; @@ -251,7 +247,91 @@ void main() { }); - test('test method createChallenge()', () async { + test('test method createMfaAuthenticator()', () async { + final Map data = { + 'secret': '1', + 'uri': '1',}; + + + when(client.call( + HttpMethod.post, + )).thenAnswer((_) async => Response(data: data)); + + + final response = await account.createMfaAuthenticator( + type: 'totp', + ); + expect(response, isA()); + + }); + + test('test method updateMfaAuthenticator()', () async { + final Map data = { + '\$id': '5e5ea5c16897e', + '\$createdAt': '2020-10-15T06:38:00.000+00:00', + '\$updatedAt': '2020-10-15T06:38:00.000+00:00', + 'name': 'John Doe', + 'registration': '2020-10-15T06:38:00.000+00:00', + 'status': true, + 'labels': [], + 'passwordUpdate': '2020-10-15T06:38:00.000+00:00', + 'email': 'john@appwrite.io', + 'phone': '+4930901820', + 'emailVerification': true, + 'phoneVerification': true, + 'mfa': true, + 'prefs': {}, + 'targets': [], + 'accessedAt': '2020-10-15T06:38:00.000+00:00',}; + + + when(client.call( + HttpMethod.put, + )).thenAnswer((_) async => Response(data: data)); + + + final response = await account.updateMfaAuthenticator( + type: 'totp', + otp: '', + ); + expect(response, isA()); + + }); + + test('test method deleteMfaAuthenticator()', () async { + final Map data = { + '\$id': '5e5ea5c16897e', + '\$createdAt': '2020-10-15T06:38:00.000+00:00', + '\$updatedAt': '2020-10-15T06:38:00.000+00:00', + 'name': 'John Doe', + 'registration': '2020-10-15T06:38:00.000+00:00', + 'status': true, + 'labels': [], + 'passwordUpdate': '2020-10-15T06:38:00.000+00:00', + 'email': 'john@appwrite.io', + 'phone': '+4930901820', + 'emailVerification': true, + 'phoneVerification': true, + 'mfa': true, + 'prefs': {}, + 'targets': [], + 'accessedAt': '2020-10-15T06:38:00.000+00:00',}; + + + when(client.call( + HttpMethod.delete, + )).thenAnswer((_) async => Response(data: data)); + + + final response = await account.deleteMfaAuthenticator( + type: 'totp', + otp: '', + ); + expect(response, isA()); + + }); + + test('test method createMfaChallenge()', () async { final Map data = { '\$id': 'bb8ea5c16897e', '\$createdAt': '2020-10-15T06:38:00.000+00:00', @@ -264,14 +344,14 @@ void main() { )).thenAnswer((_) async => Response(data: data)); - final response = await account.createChallenge( - factor: 'totp', + final response = await account.createMfaChallenge( + factor: 'email', ); expect(response, isA()); }); - test('test method updateChallenge()', () async { + test('test method updateMfaChallenge()', () async { final data = ''; when(client.call( @@ -279,13 +359,13 @@ void main() { )).thenAnswer((_) async => Response(data: data)); - final response = await account.updateChallenge( + final response = await account.updateMfaChallenge( challengeId: '', otp: '', ); }); - test('test method listFactors()', () async { + test('test method listMfaFactors()', () async { final Map data = { 'totp': true, 'phone': true, @@ -297,17 +377,31 @@ void main() { )).thenAnswer((_) async => Response(data: data)); - final response = await account.listFactors( + final response = await account.listMfaFactors( ); expect(response, isA()); }); - test('test method addAuthenticator()', () async { + test('test method getMfaRecoveryCodes()', () async { final Map data = { - 'backups': [], - 'secret': '1', - 'uri': '1',}; + 'recoveryCodes': [],}; + + + when(client.call( + HttpMethod.get, + )).thenAnswer((_) async => Response(data: data)); + + + final response = await account.getMfaRecoveryCodes( + ); + expect(response, isA()); + + }); + + test('test method createMfaRecoveryCodes()', () async { + final Map data = { + 'recoveryCodes': [],}; when(client.call( @@ -315,78 +409,25 @@ void main() { )).thenAnswer((_) async => Response(data: data)); - final response = await account.addAuthenticator( - type: 'totp', + final response = await account.createMfaRecoveryCodes( ); - expect(response, isA()); + expect(response, isA()); }); - test('test method verifyAuthenticator()', () async { + test('test method updateMfaRecoveryCodes()', () async { final Map data = { - '\$id': '5e5ea5c16897e', - '\$createdAt': '2020-10-15T06:38:00.000+00:00', - '\$updatedAt': '2020-10-15T06:38:00.000+00:00', - 'name': 'John Doe', - 'registration': '2020-10-15T06:38:00.000+00:00', - 'status': true, - 'labels': [], - 'passwordUpdate': '2020-10-15T06:38:00.000+00:00', - 'email': 'john@appwrite.io', - 'phone': '+4930901820', - 'emailVerification': true, - 'phoneVerification': true, - 'mfa': true, - 'totp': true, - 'prefs': {}, - 'targets': [], - 'accessedAt': '2020-10-15T06:38:00.000+00:00',}; + 'recoveryCodes': [],}; when(client.call( - HttpMethod.put, + HttpMethod.patch, )).thenAnswer((_) async => Response(data: data)); - final response = await account.verifyAuthenticator( - type: 'totp', - otp: '', + final response = await account.updateMfaRecoveryCodes( ); - expect(response, isA()); - - }); - - test('test method deleteAuthenticator()', () async { - final Map data = { - '\$id': '5e5ea5c16897e', - '\$createdAt': '2020-10-15T06:38:00.000+00:00', - '\$updatedAt': '2020-10-15T06:38:00.000+00:00', - 'name': 'John Doe', - 'registration': '2020-10-15T06:38:00.000+00:00', - 'status': true, - 'labels': [], - 'passwordUpdate': '2020-10-15T06:38:00.000+00:00', - 'email': 'john@appwrite.io', - 'phone': '+4930901820', - 'emailVerification': true, - 'phoneVerification': true, - 'mfa': true, - 'totp': true, - 'prefs': {}, - 'targets': [], - 'accessedAt': '2020-10-15T06:38:00.000+00:00',}; - - - when(client.call( - HttpMethod.delete, - )).thenAnswer((_) async => Response(data: data)); - - - final response = await account.deleteAuthenticator( - type: 'totp', - otp: '', - ); - expect(response, isA()); + expect(response, isA()); }); @@ -405,7 +446,6 @@ void main() { 'emailVerification': true, 'phoneVerification': true, 'mfa': true, - 'totp': true, 'prefs': {}, 'targets': [], 'accessedAt': '2020-10-15T06:38:00.000+00:00',}; @@ -438,7 +478,6 @@ void main() { 'emailVerification': true, 'phoneVerification': true, 'mfa': true, - 'totp': true, 'prefs': {}, 'targets': [], 'accessedAt': '2020-10-15T06:38:00.000+00:00',}; @@ -471,7 +510,6 @@ void main() { 'emailVerification': true, 'phoneVerification': true, 'mfa': true, - 'totp': true, 'prefs': {}, 'targets': [], 'accessedAt': '2020-10-15T06:38:00.000+00:00',}; @@ -520,7 +558,6 @@ void main() { 'emailVerification': true, 'phoneVerification': true, 'mfa': true, - 'totp': true, 'prefs': {}, 'targets': [], 'accessedAt': '2020-10-15T06:38:00.000+00:00',}; @@ -642,7 +679,8 @@ void main() { 'countryName': 'United States', 'current': true, 'factors': [], - 'secret': '5e5bb8c16897e',}; + 'secret': '5e5bb8c16897e', + 'mfaUpdatedAt': '2020-10-15T06:38:00.000+00:00',}; when(client.call( @@ -684,7 +722,8 @@ void main() { 'countryName': 'United States', 'current': true, 'factors': [], - 'secret': '5e5bb8c16897e',}; + 'secret': '5e5bb8c16897e', + 'mfaUpdatedAt': '2020-10-15T06:38:00.000+00:00',}; when(client.call( @@ -728,7 +767,8 @@ void main() { 'countryName': 'United States', 'current': true, 'factors': [], - 'secret': '5e5bb8c16897e',}; + 'secret': '5e5bb8c16897e', + 'mfaUpdatedAt': '2020-10-15T06:38:00.000+00:00',}; when(client.call( @@ -784,7 +824,8 @@ void main() { 'countryName': 'United States', 'current': true, 'factors': [], - 'secret': '5e5bb8c16897e',}; + 'secret': '5e5bb8c16897e', + 'mfaUpdatedAt': '2020-10-15T06:38:00.000+00:00',}; when(client.call( @@ -828,7 +869,8 @@ void main() { 'countryName': 'United States', 'current': true, 'factors': [], - 'secret': '5e5bb8c16897e',}; + 'secret': '5e5bb8c16897e', + 'mfaUpdatedAt': '2020-10-15T06:38:00.000+00:00',}; when(client.call( @@ -872,7 +914,8 @@ void main() { 'countryName': 'United States', 'current': true, 'factors': [], - 'secret': '5e5bb8c16897e',}; + 'secret': '5e5bb8c16897e', + 'mfaUpdatedAt': '2020-10-15T06:38:00.000+00:00',}; when(client.call( @@ -915,7 +958,8 @@ void main() { 'countryName': 'United States', 'current': true, 'factors': [], - 'secret': '5e5bb8c16897e',}; + 'secret': '5e5bb8c16897e', + 'mfaUpdatedAt': '2020-10-15T06:38:00.000+00:00',}; when(client.call( @@ -958,7 +1002,6 @@ void main() { 'emailVerification': true, 'phoneVerification': true, 'mfa': true, - 'totp': true, 'prefs': {}, 'targets': [], 'accessedAt': '2020-10-15T06:38:00.000+00:00',}; diff --git a/test/src/models/mfa_recovery_codes_test.dart b/test/src/models/mfa_recovery_codes_test.dart new file mode 100644 index 0000000..7153abb --- /dev/null +++ b/test/src/models/mfa_recovery_codes_test.dart @@ -0,0 +1,18 @@ +import 'package:appwrite/models.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('MfaRecoveryCodes', () { + + test('model', () { + final model = MfaRecoveryCodes( + recoveryCodes: [], + ); + + final map = model.toMap(); + final result = MfaRecoveryCodes.fromMap(map); + + expect(result.recoveryCodes, []); + }); + }); +} diff --git a/test/src/models/mfa_type_test.dart b/test/src/models/mfa_type_test.dart index 2f106ca..cab65e8 100644 --- a/test/src/models/mfa_type_test.dart +++ b/test/src/models/mfa_type_test.dart @@ -6,7 +6,6 @@ void main() { test('model', () { final model = MfaType( - backups: [], secret: '1', uri: '1', ); @@ -14,7 +13,6 @@ void main() { final map = model.toMap(); final result = MfaType.fromMap(map); - expect(result.backups, []); expect(result.secret, '1'); expect(result.uri, '1'); }); diff --git a/test/src/models/session_test.dart b/test/src/models/session_test.dart index 20a1803..b2117b8 100644 --- a/test/src/models/session_test.dart +++ b/test/src/models/session_test.dart @@ -33,6 +33,7 @@ void main() { current: true, factors: [], secret: '5e5bb8c16897e', + mfaUpdatedAt: '2020-10-15T06:38:00.000+00:00', ); final map = model.toMap(); @@ -65,6 +66,7 @@ void main() { expect(result.current, true); expect(result.factors, []); expect(result.secret, '5e5bb8c16897e'); + expect(result.mfaUpdatedAt, '2020-10-15T06:38:00.000+00:00'); }); }); } diff --git a/test/src/models/user_test.dart b/test/src/models/user_test.dart index cd85545..9b78a7d 100644 --- a/test/src/models/user_test.dart +++ b/test/src/models/user_test.dart @@ -19,7 +19,6 @@ void main() { emailVerification: true, phoneVerification: true, mfa: true, - totp: true, prefs: Preferences(data: {}), targets: [], accessedAt: '2020-10-15T06:38:00.000+00:00', @@ -41,7 +40,6 @@ void main() { expect(result.emailVerification, true); expect(result.phoneVerification, true); expect(result.mfa, true); - expect(result.totp, true); expect(result.prefs.data, {"data": {}}); expect(result.targets, []); expect(result.accessedAt, '2020-10-15T06:38:00.000+00:00');