2.1.0
This commit is contained in:
parent
2f14fb4a99
commit
d80617d8b4
6 changed files with 192 additions and 189 deletions
|
@ -1,3 +1,8 @@
|
||||||
|
# 2.1.0
|
||||||
|
* Angel 2 + Dart 2 update
|
||||||
|
* Support for handling errors + rejections.
|
||||||
|
* Use `ExternalAuthOptions`.
|
||||||
|
|
||||||
# 2.0.0+1
|
# 2.0.0+1
|
||||||
* Meta update to improve Pub score.
|
* Meta update to improve Pub score.
|
||||||
|
|
||||||
|
|
79
README.md
79
README.md
|
@ -10,42 +10,41 @@ First, create an options object:
|
||||||
```dart
|
```dart
|
||||||
configureServer(Angel app) async {
|
configureServer(Angel app) async {
|
||||||
// Load from a Map, i.e. app config:
|
// Load from a Map, i.e. app config:
|
||||||
var opts = new AngelOAuth2Options.fromJson(map);
|
var opts = ExternalAuthOptions.fromMap(app.configuration['auth0'] as Map);
|
||||||
|
|
||||||
// Create in-place:
|
// Create in-place:
|
||||||
var opts = const AngelAuthOAuth2Options(
|
var opts = ExternalAuthOptions(
|
||||||
callback: '<callback-url>',
|
clientId: '<client-id>',
|
||||||
key: '<client-id>',
|
clientSecret: '<client-secret>',
|
||||||
secret: '<client-secret>',
|
redirectUri: Uri.parse('<callback>'));
|
||||||
authorizationEndpoint: '<authorization-endpoint>',
|
|
||||||
tokenEndpoint: '<access-token-endpoint>');
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
After getting authenticated against the remote server, we need to be able to identify
|
After getting authenticated against the remote server, we need to be able to identify
|
||||||
users within our own application. Use an `OAuth2Verifier` to associate remote users
|
users within our own application.
|
||||||
with local users.
|
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
|
typedef FutureOr<User> OAuth2Verifier(oauth2.Client, RequestContext, ResponseContext);
|
||||||
|
|
||||||
/// You might use a pure function to create a verifier that queries a
|
/// You might use a pure function to create a verifier that queries a
|
||||||
/// given service.
|
/// given service.
|
||||||
OAuth2Verifier oauth2verifier(Service userService) {
|
OAuth2Verifier oauth2verifier(Service<User> userService) {
|
||||||
return (oauth2.Client client) async {
|
return (client) async {
|
||||||
var response = await client.get('https://api.github.com/user');
|
var response = await client.get('https://api.github.com/user');
|
||||||
var ghUser = JSON.decode(response.body);
|
var ghUser = json.decode(response.body);
|
||||||
var id = ghUser['id'];
|
var id = ghUser['id'] as int;
|
||||||
|
|
||||||
Iterable<Map> matchingUsers = await userService.index({
|
var matchingUsers = await mappedUserService.index({
|
||||||
'query': {'githubId': id}
|
'query': {'github_id': id}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (matchingUsers.isNotEmpty) {
|
if (matchingUsers.isNotEmpty) {
|
||||||
// Return the corresponding user, if it exists
|
// Return the corresponding user, if it exists.
|
||||||
return User.parse(matchingUsers.firstWhere((u) => u['githubId'] == id));
|
return matchingUsers.first;
|
||||||
} else {
|
} else {
|
||||||
// Otherwise,create a user
|
// Otherwise,create a user
|
||||||
return await userService.create({'githubId': id}).then(User.parse);
|
return await mappedUserService.create(User(githubId: id));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -56,9 +55,18 @@ Consider using the name of the remote authentication provider (ex. `facebook`).
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
configureServer(Angel app) {
|
configureServer(Angel app) {
|
||||||
// ...
|
auth.strategies['github'] = OAuth2Strategy(
|
||||||
var oauthStrategy =
|
options,
|
||||||
new OAuth2Strategy('github', OAUTH2_CONFIG, oauth2Verifier(app.service('users')));
|
authorizationEndpoint,
|
||||||
|
tokenEndpoint,
|
||||||
|
yourVerifier,
|
||||||
|
|
||||||
|
// This function is called when an error occurs, or the user REJECTS the request.
|
||||||
|
(e, req, res) async {
|
||||||
|
res.write('Ooops: $e');
|
||||||
|
await res.close();
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -74,7 +82,7 @@ a popup window. In this case, use `confirmPopupAuthentication`, which is bundled
|
||||||
```dart
|
```dart
|
||||||
configureServer(Angel app) async {
|
configureServer(Angel app) async {
|
||||||
// ...
|
// ...
|
||||||
var auth = new AngelAuth();
|
var auth = AngelAuth<User>();
|
||||||
auth.strategies['github'] = oauth2Strategy;
|
auth.strategies['github'] = oauth2Strategy;
|
||||||
|
|
||||||
// Redirect
|
// Redirect
|
||||||
|
@ -83,7 +91,7 @@ configureServer(Angel app) async {
|
||||||
// Callback
|
// Callback
|
||||||
app.get('/auth/github/callback', auth.authenticate(
|
app.get('/auth/github/callback', auth.authenticate(
|
||||||
'github',
|
'github',
|
||||||
new AngelAuthOptions(callback: confirmPopupAuthentication())
|
AngelAuthOptions(callback: confirmPopupAuthentication())
|
||||||
));
|
));
|
||||||
|
|
||||||
// Connect the plug-in!!!
|
// Connect the plug-in!!!
|
||||||
|
@ -94,14 +102,11 @@ configureServer(Angel app) async {
|
||||||
## Custom Scope Delimiter
|
## Custom Scope Delimiter
|
||||||
This package should work out-of-the-box for most OAuth2 providers, such as Github or Dropbox.
|
This package should work out-of-the-box for most OAuth2 providers, such as Github or Dropbox.
|
||||||
However, if your OAuth2 scopes are separated by a delimiter other than the default (`' '`),
|
However, if your OAuth2 scopes are separated by a delimiter other than the default (`' '`),
|
||||||
you can add it in the `AngelOAuth2Options` constructor:
|
you can add it in the `OAuth2Strategy` constructor:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
configureServer(Angel app) async {
|
configureServer(Angel app) async {
|
||||||
var opts = const AngelOAuth2Options(
|
OAuth2Strategy(..., delimiter: ' ');
|
||||||
// ...
|
|
||||||
delimiter: ','
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -113,7 +118,7 @@ You can add a `getParameters` callback to parse the contents of any arbitrary
|
||||||
response:
|
response:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
var opts = const AngelOAuth2Options(
|
OAuth2Strategy(
|
||||||
// ...
|
// ...
|
||||||
getParameters: (contentType, body) {
|
getParameters: (contentType, body) {
|
||||||
if (contentType.type == 'application') {
|
if (contentType.type == 'application') {
|
||||||
|
@ -122,7 +127,7 @@ var opts = const AngelOAuth2Options(
|
||||||
else if (contentType.subtype == 'json') return JSON.decode(body);
|
else if (contentType.subtype == 'json') return JSON.decode(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new FormatException('Invalid content-type $contentType; expected application/x-www-form-urlencoded or application/json.');
|
throw FormatException('Invalid content-type $contentType; expected application/x-www-form-urlencoded or application/json.');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
```
|
```
|
|
@ -1,3 +1,4 @@
|
||||||
|
include: package:pedantic/analysis_options.yaml
|
||||||
analyzer:
|
analyzer:
|
||||||
strong-mode:
|
strong-mode:
|
||||||
implicit-casts: false
|
implicit-casts: false
|
|
@ -1,97 +1,127 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
|
||||||
import 'package:angel_auth/angel_auth.dart';
|
import 'package:angel_auth/angel_auth.dart';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:angel_framework/http.dart';
|
||||||
import 'package:angel_auth_oauth2/angel_auth_oauth2.dart';
|
import 'package:angel_auth_oauth2/angel_auth_oauth2.dart';
|
||||||
|
import 'package:http_parser/http_parser.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:oauth2/oauth2.dart' as oauth2;
|
|
||||||
|
|
||||||
final AngelAuthOAuth2Options oAuth2Config = new AngelAuthOAuth2Options(
|
var authorizationEndpoint =
|
||||||
callback: 'http://localhost:3000/auth/github/callback',
|
Uri.parse('http://github.com/login/oauth/authorize');
|
||||||
key: '6caeaf5d4c04936ec34f',
|
|
||||||
secret: '178360518cf9de4802e2346a4b6ebec525dc4427',
|
|
||||||
authorizationEndpoint: 'http://github.com/login/oauth/authorize',
|
|
||||||
tokenEndpoint: 'https://github.com/login/oauth/access_token',
|
|
||||||
getParameters: (contentType, body) {
|
|
||||||
if (contentType.type == 'application') {
|
|
||||||
if (contentType.subtype == 'x-www-form-urlencoded')
|
|
||||||
return Uri.splitQueryString(body);
|
|
||||||
else if (contentType.subtype == 'json')
|
|
||||||
return (json.decode(body) as Map).cast<String, String>();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new FormatException(
|
var tokenEndpoint = Uri.parse('https://github.com/login/oauth/access_token');
|
||||||
'Invalid content-type $contentType; expected application/x-www-form-urlencoded or application/json.');
|
|
||||||
});
|
var options = ExternalAuthOptions(
|
||||||
|
clientId: '6caeaf5d4c04936ec34f',
|
||||||
|
clientSecret: '178360518cf9de4802e2346a4b6ebec525dc4427',
|
||||||
|
redirectUri: Uri.parse('http://localhost:3000/auth/github/callback'),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Github doesn't properly follow the OAuth2 spec, so here's logic to parse their response.
|
||||||
|
Map<String, dynamic> parseParamsFromGithub(MediaType contentType, String body) {
|
||||||
|
if (contentType.type == 'application') {
|
||||||
|
if (contentType.subtype == 'x-www-form-urlencoded')
|
||||||
|
return Uri.splitQueryString(body);
|
||||||
|
else if (contentType.subtype == 'json')
|
||||||
|
return (json.decode(body) as Map).cast<String, String>();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw FormatException(
|
||||||
|
'Invalid content-type $contentType; expected application/x-www-form-urlencoded or application/json.');
|
||||||
|
}
|
||||||
|
|
||||||
main() async {
|
main() async {
|
||||||
var app = new Angel();
|
// Create the server instance.
|
||||||
app.use('/users', new MapService());
|
var app = Angel();
|
||||||
|
var http = AngelHttp(app);
|
||||||
|
app.logger = Logger('angel')
|
||||||
|
..onRecord.listen((rec) {
|
||||||
|
print(rec);
|
||||||
|
if (rec.error != null) print(rec.error);
|
||||||
|
if (rec.stackTrace != null) print(rec.stackTrace);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a service that stores user data.
|
||||||
|
var userService = app.use('/users', MapService()).inner;
|
||||||
|
var mappedUserService = userService.map(User.parse, User.serialize);
|
||||||
|
|
||||||
|
// Set up the authenticator plugin.
|
||||||
var auth =
|
var auth =
|
||||||
new AngelAuth<User>(jwtKey: 'oauth2 example secret', allowCookie: false);
|
AngelAuth<User>(jwtKey: 'oauth2 example secret', allowCookie: false);
|
||||||
|
auth.serializer = (user) async => user.id;
|
||||||
|
auth.deserializer = (id) => mappedUserService.read(id.toString());
|
||||||
|
app.fallback(auth.decodeJwt);
|
||||||
|
|
||||||
auth.deserializer =
|
/// Create an instance of the strategy class.
|
||||||
(id) => app.service('users').read(id).then((u) => User.parse(u as Map));
|
auth.strategies['github'] = OAuth2Strategy(
|
||||||
|
options,
|
||||||
|
authorizationEndpoint,
|
||||||
|
tokenEndpoint,
|
||||||
|
|
||||||
auth.serializer = (User user) async => user.id;
|
// This function is called when the user ACCEPTS the request to sign in with Github.
|
||||||
|
(client, req, res) async {
|
||||||
auth.strategies['github'] = new OAuth2Strategy(
|
|
||||||
oAuth2Config,
|
|
||||||
(oauth2.Client client) async {
|
|
||||||
var response = await client.get('https://api.github.com/user');
|
var response = await client.get('https://api.github.com/user');
|
||||||
var ghUser = json.decode(response.body);
|
var ghUser = json.decode(response.body);
|
||||||
var id = ghUser['id'];
|
var id = ghUser['id'] as int;
|
||||||
|
|
||||||
Iterable<Map> matchingUsers = await app.service('users').index({
|
var matchingUsers = await mappedUserService.index({
|
||||||
'query': {'githubId': id}
|
'query': {'github_id': id}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (matchingUsers.isNotEmpty) {
|
if (matchingUsers.isNotEmpty) {
|
||||||
// Return the corresponding user, if it exists
|
// Return the corresponding user, if it exists.
|
||||||
return User.parse(matchingUsers.firstWhere((u) => u['githubId'] == id));
|
return matchingUsers.first;
|
||||||
} else {
|
} else {
|
||||||
// Otherwise,create a user
|
// Otherwise,create a user
|
||||||
return await app
|
return await mappedUserService.create(User(githubId: id));
|
||||||
.service('users')
|
|
||||||
.create({'githubId': id}).then((u) => User.parse(u as Map));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// This function is called when an error occurs, or the user REJECTS the request.
|
||||||
|
(e, req, res) async {
|
||||||
|
res.write('Ooops: $e');
|
||||||
|
await res.close();
|
||||||
|
},
|
||||||
|
|
||||||
|
// We have to pass this parser function when working with Github.
|
||||||
|
getParameters: parseParamsFromGithub,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Mount some routes
|
||||||
app.get('/auth/github', auth.authenticate('github'));
|
app.get('/auth/github', auth.authenticate('github'));
|
||||||
app.get(
|
app.get(
|
||||||
'/auth/github/callback',
|
'/auth/github/callback',
|
||||||
auth.authenticate('github',
|
auth.authenticate('github',
|
||||||
new AngelAuthOptions(callback: (req, res, jwt) async {
|
AngelAuthOptions(callback: (req, res, jwt) async {
|
||||||
// In real-life, you might include a pop-up callback script.
|
// In real-life, you might include a pop-up callback script.
|
||||||
//
|
//
|
||||||
// Use `confirmPopupAuthentication`, which is bundled with
|
// Use `confirmPopupAuthentication`, which is bundled with
|
||||||
// `package:angel_auth`.
|
// `package:angel_auth`.
|
||||||
|
var user = req.container.make<User>();
|
||||||
|
res.write('Your user info: ${user.toJson()}\n\n');
|
||||||
res.write('Your JWT: $jwt');
|
res.write('Your JWT: $jwt');
|
||||||
|
await res.close();
|
||||||
})));
|
})));
|
||||||
|
|
||||||
await app.configure(auth.configureServer);
|
// Start listening.
|
||||||
|
await http.startServer('127.0.0.1', 3000);
|
||||||
app.logger = new Logger('angel')..onRecord.listen(print);
|
print('Listening on ${http.uri}');
|
||||||
|
print('View user listing: ${http.uri}/users');
|
||||||
var http = new AngelHttp(app);
|
print('Sign in via Github: ${http.uri}/auth/github');
|
||||||
var server = await http.startServer(InternetAddress.loopbackIPv4, 3000);
|
|
||||||
var url = 'http://${server.address.address}:${server.port}';
|
|
||||||
print('Listening on $url');
|
|
||||||
print('View user listing: $url/users');
|
|
||||||
print('Sign in via Github: $url/auth/github');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class User extends Model {
|
class User extends Model {
|
||||||
@override
|
@override
|
||||||
String id;
|
String id;
|
||||||
|
|
||||||
int githubId;
|
int githubId;
|
||||||
|
|
||||||
User({this.id, this.githubId});
|
User({this.id, this.githubId});
|
||||||
|
|
||||||
static User parse(Map map) =>
|
static User parse(Map map) =>
|
||||||
new User(id: map['id'] as String, githubId: map['github_id'] as int);
|
User(id: map['id'] as String, githubId: map['github_id'] as int);
|
||||||
|
|
||||||
|
static Map<String, dynamic> serialize(User user) => user.toJson();
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {'id': id, 'github_id': githubId};
|
Map<String, dynamic> toJson() => {'id': id, 'github_id': githubId};
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,123 +3,85 @@ library angel_auth_oauth2;
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:angel_auth/angel_auth.dart';
|
import 'package:angel_auth/angel_auth.dart';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
import 'package:angel_validate/angel_validate.dart';
|
|
||||||
import 'package:http_parser/http_parser.dart';
|
import 'package:http_parser/http_parser.dart';
|
||||||
import 'package:oauth2/oauth2.dart' as oauth2;
|
import 'package:oauth2/oauth2.dart' as oauth2;
|
||||||
|
|
||||||
final Validator OAUTH2_OPTIONS_SCHEMA = new Validator({
|
/// An Angel [AuthStrategy] that signs users in via a third-party service that speaks OAuth 2.0.
|
||||||
'key*': isString,
|
|
||||||
'secret*': isString,
|
|
||||||
'authorizationEndpoint*': anyOf(isString, const TypeMatcher<Uri>()),
|
|
||||||
'tokenEndpoint*': anyOf(isString, const TypeMatcher<Uri>()),
|
|
||||||
'callback*': isString,
|
|
||||||
'scopes': const TypeMatcher<Iterable<String>>()
|
|
||||||
}, defaultValues: {
|
|
||||||
'scopes': <String>[]
|
|
||||||
}, customErrorMessages: {
|
|
||||||
'scopes': "'scopes' must be an Iterable of strings. You provided: {{value}}"
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Holds credentials and also specifies the means of authenticating users against a remote server.
|
|
||||||
class AngelAuthOAuth2Options {
|
|
||||||
/// Your application's client key or client ID, registered with the remote server.
|
|
||||||
final String key;
|
|
||||||
|
|
||||||
/// Your application's client secret, registered with the remote server.
|
|
||||||
final String secret;
|
|
||||||
|
|
||||||
/// The remote endpoint that prompts external users for authentication credentials.
|
|
||||||
final String authorizationEndpoint;
|
|
||||||
|
|
||||||
/// The remote endpoint that exchanges auth codes for access tokens.
|
|
||||||
final String tokenEndpoint;
|
|
||||||
|
|
||||||
/// The callback URL that the OAuth2 server should redirect authenticated users to.
|
|
||||||
final String callback;
|
|
||||||
|
|
||||||
/// Used to split application scopes. Defaults to `' '`.
|
|
||||||
final String delimiter;
|
|
||||||
final Iterable<String> scopes;
|
|
||||||
|
|
||||||
final Map<String, String> Function(MediaType, String) getParameters;
|
|
||||||
|
|
||||||
const AngelAuthOAuth2Options(
|
|
||||||
{this.key,
|
|
||||||
this.secret,
|
|
||||||
this.authorizationEndpoint,
|
|
||||||
this.tokenEndpoint,
|
|
||||||
this.callback,
|
|
||||||
this.delimiter: ' ',
|
|
||||||
this.scopes: const [],
|
|
||||||
this.getParameters});
|
|
||||||
|
|
||||||
factory AngelAuthOAuth2Options.fromJson(Map json) =>
|
|
||||||
new AngelAuthOAuth2Options(
|
|
||||||
key: json['key'] as String,
|
|
||||||
secret: json['secret'] as String,
|
|
||||||
authorizationEndpoint: json['authorizationEndpoint'] as String,
|
|
||||||
tokenEndpoint: json['tokenEndpoint'] as String,
|
|
||||||
callback: json['callback'] as String,
|
|
||||||
scopes: (json['scopes'] as Iterable)?.cast<String>()?.toList() ??
|
|
||||||
<String>[]);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
'key': key,
|
|
||||||
'secret': secret,
|
|
||||||
'authorizationEndpoint': authorizationEndpoint,
|
|
||||||
'tokenEndpoint': tokenEndpoint,
|
|
||||||
'callback': callback,
|
|
||||||
'scopes': scopes.toList()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class OAuth2Strategy<User> implements AuthStrategy<User> {
|
class OAuth2Strategy<User> implements AuthStrategy<User> {
|
||||||
final FutureOr<User> Function(oauth2.Client) verifier;
|
/// A callback that uses the third-party service to authenticate a [User].
|
||||||
|
///
|
||||||
|
/// As always, return `null` if authentication fails.
|
||||||
|
final FutureOr<User> Function(oauth2.Client, RequestContext, ResponseContext)
|
||||||
|
verifier;
|
||||||
|
|
||||||
AngelAuthOAuth2Options _options;
|
/// A callback that is triggered when an OAuth2 error occurs (i.e. the user declines to login);
|
||||||
|
final FutureOr<dynamic> Function(
|
||||||
|
oauth2.AuthorizationException, RequestContext, ResponseContext) onError;
|
||||||
|
|
||||||
/// [options] can be either a `Map` or an instance of [AngelAuthOAuth2Options].
|
/// The options defining how to connect to the third-party.
|
||||||
OAuth2Strategy(options, this.verifier) {
|
final ExternalAuthOptions options;
|
||||||
if (options is AngelAuthOAuth2Options)
|
|
||||||
_options = options;
|
|
||||||
else if (options is Map)
|
|
||||||
_options = new AngelAuthOAuth2Options.fromJson(
|
|
||||||
OAUTH2_OPTIONS_SCHEMA.enforce(options));
|
|
||||||
else
|
|
||||||
throw new ArgumentError('Invalid OAuth2 options: $options');
|
|
||||||
}
|
|
||||||
|
|
||||||
oauth2.AuthorizationCodeGrant createGrant() =>
|
/// The URL to query to receive an authentication code.
|
||||||
new oauth2.AuthorizationCodeGrant(
|
final Uri authorizationEndpoint;
|
||||||
_options.key,
|
|
||||||
Uri.parse(_options.authorizationEndpoint),
|
/// The URL to query to exchange an authentication code for a token.
|
||||||
Uri.parse(_options.tokenEndpoint),
|
final Uri tokenEndpoint;
|
||||||
secret: _options.secret,
|
|
||||||
delimiter: _options.delimiter ?? ' ',
|
/// An optional callback used to parse the response from a server who does not follow the OAuth 2.0 spec.
|
||||||
getParameters: _options.getParameters);
|
final Map<String, dynamic> Function(MediaType, String) getParameters;
|
||||||
|
|
||||||
|
/// An optional delimiter used to send requests to server who does not follow the OAuth 2.0 spec.
|
||||||
|
final String delimiter;
|
||||||
|
|
||||||
|
Uri _redirect;
|
||||||
|
|
||||||
|
OAuth2Strategy(this.options, this.authorizationEndpoint, this.tokenEndpoint,
|
||||||
|
this.verifier, this.onError,
|
||||||
|
{this.getParameters, this.delimiter = ' '});
|
||||||
|
|
||||||
|
oauth2.AuthorizationCodeGrant _createGrant() =>
|
||||||
|
new oauth2.AuthorizationCodeGrant(options.clientId, authorizationEndpoint,
|
||||||
|
tokenEndpoint,
|
||||||
|
secret: options.clientSecret,
|
||||||
|
delimiter: delimiter,
|
||||||
|
getParameters: getParameters);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<User> authenticate(RequestContext req, ResponseContext res,
|
FutureOr<User> authenticate(RequestContext req, ResponseContext res,
|
||||||
[AngelAuthOptions<User> options]) async {
|
[AngelAuthOptions<User> options]) async {
|
||||||
if (options != null) return authenticateCallback(req, res, options);
|
if (options != null) {
|
||||||
|
var result = await authenticateCallback(req, res, options);
|
||||||
|
if (result is User)
|
||||||
|
return result;
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var grant = createGrant();
|
if (_redirect == null) {
|
||||||
res.redirect(grant
|
var grant = _createGrant();
|
||||||
.getAuthorizationUrl(Uri.parse(_options.callback),
|
_redirect = grant.getAuthorizationUrl(
|
||||||
scopes: _options.scopes)
|
this.options.redirectUri,
|
||||||
.toString());
|
scopes: this.options.scopes,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.redirect(_redirect);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<User> authenticateCallback(RequestContext req, ResponseContext res,
|
/// The endpoint that is invoked by the third-party after successful authentication.
|
||||||
|
Future<dynamic> authenticateCallback(RequestContext req, ResponseContext res,
|
||||||
[AngelAuthOptions options]) async {
|
[AngelAuthOptions options]) async {
|
||||||
var grant = createGrant();
|
var grant = _createGrant();
|
||||||
await grant.getAuthorizationUrl(Uri.parse(_options.callback),
|
grant.getAuthorizationUrl(this.options.redirectUri,
|
||||||
scopes: _options.scopes);
|
scopes: this.options.scopes);
|
||||||
var client =
|
|
||||||
await grant.handleAuthorizationResponse(req.uri.queryParameters);
|
try {
|
||||||
return await verifier(client);
|
var client =
|
||||||
|
await grant.handleAuthorizationResponse(req.uri.queryParameters);
|
||||||
|
return await verifier(client, req, res);
|
||||||
|
} on oauth2.AuthorizationException catch (e) {
|
||||||
|
return await onError(e, req, res);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,8 @@ homepage: https://github.com/angel-dart/auth_oauth2.git
|
||||||
dependencies:
|
dependencies:
|
||||||
angel_auth: ^2.0.0
|
angel_auth: ^2.0.0
|
||||||
angel_framework: ^2.0.0-alpha
|
angel_framework: ^2.0.0-alpha
|
||||||
angel_validate: ^2.0.0-alpha
|
|
||||||
http_parser: ^3.0.0
|
http_parser: ^3.0.0
|
||||||
oauth2: ^1.0.0
|
oauth2: ^1.0.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
logging: ^0.11.0
|
logging: ^0.11.0
|
||||||
|
pedantic: ^1.0.0
|
Loading…
Reference in a new issue