Skip to content

Commit

Permalink
Custom Function Authentication Provider (#742)
Browse files Browse the repository at this point in the history
Add Custom JWT Authentication to Credentials
Fixes #399
  • Loading branch information
desistefanova authored Aug 12, 2022
1 parent f248a3c commit 4af02d4
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* Support Apple, Facebook and Google authentication. ([#740](https://github.com/realm/realm-dart/pull/740))
* Allow multiple anonymous sessions. When using anonymous authentication you can now easily log in with a different anonymous user than last time. ([#750](https://github.com/realm/realm-dart/pull/750)).
* Support `Credentials.jwt` for login user with JWT issued by custom provider . ([#715](https://github.com/realm/realm-dart/pull/715))
* Support `Credentials.function` for login user with Custom Function Authentication Provider. ([#742](https://github.com/realm/realm-dart/pull/742))

### Internal
* Added a command to `realm_dart` for deleting Atlas App Services applications. Usage: `dart run realm_dart delete-apps`. By default it will delete apps from `http://localhost:9090` which is the endpoint of the local docker image. If `--atlas-cluster` is provided, it will authenticate, delete the application from the provided cluster. (PR [#663](https://github.com/realm/realm-dart/pull/663))
Expand Down
10 changes: 9 additions & 1 deletion lib/src/cli/atlas_apps/baas_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ class BaasClient {
return { status: 'fail' };
}
};''';
static const String _authFuncSource = '''exports = (loginPayload) => {
return loginPayload["userId"];
};''';
static const String defaultAppName = "flexible";

final String _baseUrl;
Expand Down Expand Up @@ -165,6 +168,7 @@ class BaasClient {

final confirmFuncId = await _createFunction(app, 'confirmFunc', _confirmFuncSource);
final resetFuncId = await _createFunction(app, 'resetFunc', _resetFuncSource);
final authFuncId = await _createFunction(app, 'authFunc', _authFuncSource);

await enableProvider(app, 'anon-user');
await enableProvider(app, 'local-userpass', config: '''{
Expand Down Expand Up @@ -238,8 +242,12 @@ class BaasClient {
"field_name": "company"
}''');
}

if (confirmationType == null) {
await enableProvider(app, 'custom-function', config: '''{
"authFunctionName": "authFunc",
"authFunctionId": "$authFuncId"
}''');

const facebookSecret = "876750ac6d06618b323dee591602897f";
final dynamic createFacebookSecretResult = await _post('groups/$_groupId/apps/$appId/secrets', '{"name":"facebookSecret","value":"$facebookSecret"}');
String facebookClientSecretKeyName = createFacebookSecretResult['name'] as String;
Expand Down
12 changes: 10 additions & 2 deletions lib/src/credentials.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ enum AuthProviderType {
/// For authenticating with an email and a password.
emailPassword,

_function,
/// For authenticating with custom function with payload argument.
function,

_userApiKey,
_serverApiKey
}
Expand Down Expand Up @@ -96,7 +98,13 @@ class Credentials {
/// Returns a [Credentials] object that can be used to authenticate a user with a Google account using an id token.
Credentials.googleIdToken(String idToken)
: _handle = realmCore.createAppCredentialsGoogleIdToken(idToken),
provider = AuthProviderType.google;
provider = AuthProviderType.google;

/// Returns a [Credentials] object that can be used to authenticate a user with a custom Function.
/// [Custom Function Authentication Docs](https://www.mongodb.com/docs/atlas/app-services/authentication/custom-function/)
Credentials.function(String payload)
: _handle = realmCore.createAppCredentialsFunction(payload),
provider = AuthProviderType.function;
}

/// @nodoc
Expand Down
8 changes: 8 additions & 0 deletions lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,14 @@ class _RealmCore {
return RealmAppCredentialsHandle._(_realmLib.realm_app_credentials_new_google_auth_code(authCodePtr));
});
}

RealmAppCredentialsHandle createAppCredentialsFunction(String payload) {
return using((arena) {
final payloadPtr = payload.toCharPtr(arena);
final credentialsPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_app_credentials_new_function(payloadPtr));
return RealmAppCredentialsHandle._(credentialsPtr);
});
}

RealmHttpTransportHandle _createHttpTransport(HttpClient httpClient) {
final requestCallback = Pointer.fromFunction<Void Function(Handle, realm_http_request, Pointer<Void>)>(_request_callback);
Expand Down
36 changes: 36 additions & 0 deletions test/credentials_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -467,4 +467,40 @@ Future<void> main([List<String>? args]) async {
final credentials = Credentials.facebook(accessToken);
expect(() async => await app.logIn(credentials), throws<RealmException>("error fetching info from OAuth2 provider"));
});

baasTest('Function credentials - wrong payload', (configuration) {
final app = App(configuration);
final payload = 'Wrong EJSON format';
expect(() => Credentials.function(payload), throws<RealmException>("parse error"));
});

baasTest('Function credentials - login with new user', (configuration) async {
final app = App(configuration);
var userId = ObjectId().toString();
String username = "${generateRandomString(5)}@realm.io";
final payload = '{"username":"$username","userId":"$userId"}';
final credentials = Credentials.function(payload);
final user = await app.logIn(credentials);
expect(user.identities[0].id, userId);
expect(user.provider, AuthProviderType.function);
expect(user.identities[0].provider, AuthProviderType.function);
});

baasTest('Function credentials - login with existing user', (configuration) async {
final app = App(configuration);
var userId = ObjectId().toString();
final payload = '{"userId":"$userId"}';

final credentials = Credentials.function(payload);
final user = await app.logIn(credentials);
expect(user.identities[0].id, userId);
expect(user.provider, AuthProviderType.function);
user.logOut();

final sameUser = await app.logIn(credentials);
expect(sameUser.id, user.id);

expect(sameUser.identities[0].id, userId);
expect(sameUser.provider, AuthProviderType.function);
});
}

0 comments on commit 4af02d4

Please sign in to comment.