2017-01-12 22:48:51 +00:00
|
|
|
library angel_auth_oauth2;
|
|
|
|
|
|
|
|
import 'dart:async';
|
|
|
|
import 'package:angel_auth/angel_auth.dart';
|
2018-03-30 16:44:15 +00:00
|
|
|
import 'package:angel_framework/angel_framework.dart';
|
2017-01-12 22:48:51 +00:00
|
|
|
import 'package:angel_validate/angel_validate.dart';
|
2018-03-30 16:44:15 +00:00
|
|
|
import 'package:http_parser/http_parser.dart';
|
2017-01-12 22:48:51 +00:00
|
|
|
import 'package:oauth2/oauth2.dart' as oauth2;
|
|
|
|
|
|
|
|
final Validator OAUTH2_OPTIONS_SCHEMA = new Validator({
|
|
|
|
'key*': isString,
|
|
|
|
'secret*': isString,
|
2018-09-12 03:23:42 +00:00
|
|
|
'authorizationEndpoint*': anyOf(isString, const TypeMatcher<Uri>()),
|
|
|
|
'tokenEndpoint*': anyOf(isString, const TypeMatcher<Uri>()),
|
2017-01-12 22:48:51 +00:00
|
|
|
'callback*': isString,
|
2018-09-12 03:23:42 +00:00
|
|
|
'scopes': const TypeMatcher<Iterable<String>>()
|
2017-01-12 22:48:51 +00:00
|
|
|
}, defaultValues: {
|
|
|
|
'scopes': <String>[]
|
|
|
|
}, customErrorMessages: {
|
|
|
|
'scopes': "'scopes' must be an Iterable of strings. You provided: {{value}}"
|
|
|
|
});
|
|
|
|
|
2017-06-03 20:43:36 +00:00
|
|
|
/// Holds credentials and also specifies the means of authenticating users against a remote server.
|
2017-02-23 01:13:23 +00:00
|
|
|
class AngelAuthOAuth2Options {
|
2017-06-03 20:43:36 +00:00
|
|
|
/// Your application's client key or client ID, registered with the remote server.
|
|
|
|
final String key;
|
2017-01-12 22:48:51 +00:00
|
|
|
|
2017-06-03 20:43:36 +00:00
|
|
|
/// Your application's client secret, registered with the remote server.
|
|
|
|
final String secret;
|
|
|
|
|
|
|
|
/// The remote endpoint that prompts external users for authentication credentials.
|
2018-09-12 03:23:42 +00:00
|
|
|
final String authorizationEndpoint;
|
2017-06-03 20:43:36 +00:00
|
|
|
|
|
|
|
/// The remote endpoint that exchanges auth codes for access tokens.
|
2018-09-12 03:23:42 +00:00
|
|
|
final String tokenEndpoint;
|
2017-06-03 20:43:36 +00:00
|
|
|
|
|
|
|
/// 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;
|
|
|
|
|
2018-03-30 16:44:15 +00:00
|
|
|
final Map<String, String> Function(MediaType, String) getParameters;
|
|
|
|
|
2017-06-03 20:43:36 +00:00
|
|
|
const AngelAuthOAuth2Options(
|
2017-01-12 22:48:51 +00:00
|
|
|
{this.key,
|
|
|
|
this.secret,
|
|
|
|
this.authorizationEndpoint,
|
|
|
|
this.tokenEndpoint,
|
|
|
|
this.callback,
|
2017-06-03 20:43:36 +00:00
|
|
|
this.delimiter: ' ',
|
2018-03-30 16:44:15 +00:00
|
|
|
this.scopes: const [],
|
|
|
|
this.getParameters});
|
2017-01-12 22:48:51 +00:00
|
|
|
|
2017-02-23 01:13:23 +00:00
|
|
|
factory AngelAuthOAuth2Options.fromJson(Map json) =>
|
|
|
|
new AngelAuthOAuth2Options(
|
2018-09-12 03:23:42 +00:00
|
|
|
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>[]);
|
2017-01-12 22:48:51 +00:00
|
|
|
|
2018-03-30 16:44:15 +00:00
|
|
|
Map<String, dynamic> toJson() {
|
2017-01-12 22:48:51 +00:00
|
|
|
return {
|
|
|
|
'key': key,
|
|
|
|
'secret': secret,
|
|
|
|
'authorizationEndpoint': authorizationEndpoint,
|
|
|
|
'tokenEndpoint': tokenEndpoint,
|
|
|
|
'callback': callback,
|
|
|
|
'scopes': scopes.toList()
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-12 03:23:42 +00:00
|
|
|
class OAuth2Strategy<User> implements AuthStrategy<User> {
|
|
|
|
final FutureOr<User> Function(oauth2.Client) verifier;
|
2017-01-12 22:48:51 +00:00
|
|
|
|
2018-03-30 16:44:15 +00:00
|
|
|
AngelAuthOAuth2Options _options;
|
2017-01-12 22:48:51 +00:00
|
|
|
|
2017-02-23 01:13:23 +00:00
|
|
|
/// [options] can be either a `Map` or an instance of [AngelAuthOAuth2Options].
|
2018-09-12 03:23:42 +00:00
|
|
|
OAuth2Strategy(options, this.verifier) {
|
2017-02-23 01:13:23 +00:00
|
|
|
if (options is AngelAuthOAuth2Options)
|
2017-01-12 22:48:51 +00:00
|
|
|
_options = options;
|
|
|
|
else if (options is Map)
|
2017-02-23 01:13:23 +00:00
|
|
|
_options = new AngelAuthOAuth2Options.fromJson(
|
2017-01-12 22:48:51 +00:00
|
|
|
OAUTH2_OPTIONS_SCHEMA.enforce(options));
|
|
|
|
else
|
|
|
|
throw new ArgumentError('Invalid OAuth2 options: $options');
|
|
|
|
}
|
|
|
|
|
|
|
|
oauth2.AuthorizationCodeGrant createGrant() =>
|
|
|
|
new oauth2.AuthorizationCodeGrant(
|
|
|
|
_options.key,
|
|
|
|
Uri.parse(_options.authorizationEndpoint),
|
|
|
|
Uri.parse(_options.tokenEndpoint),
|
2017-06-03 20:43:36 +00:00
|
|
|
secret: _options.secret,
|
2018-03-30 16:44:15 +00:00
|
|
|
delimiter: _options.delimiter ?? ' ',
|
|
|
|
getParameters: _options.getParameters);
|
2017-01-12 22:48:51 +00:00
|
|
|
|
|
|
|
@override
|
2018-09-12 03:23:42 +00:00
|
|
|
FutureOr<User> authenticate(RequestContext req, ResponseContext res,
|
|
|
|
[AngelAuthOptions<User> options]) async {
|
2017-01-12 22:48:51 +00:00
|
|
|
if (options != null) return authenticateCallback(req, res, options);
|
|
|
|
|
|
|
|
var grant = createGrant();
|
|
|
|
res.redirect(grant
|
|
|
|
.getAuthorizationUrl(Uri.parse(_options.callback),
|
|
|
|
scopes: _options.scopes)
|
|
|
|
.toString());
|
2018-09-12 03:23:42 +00:00
|
|
|
return null;
|
2017-01-12 22:48:51 +00:00
|
|
|
}
|
|
|
|
|
2018-09-12 03:23:42 +00:00
|
|
|
Future<User> authenticateCallback(RequestContext req, ResponseContext res,
|
2017-01-12 22:48:51 +00:00
|
|
|
[AngelAuthOptions options]) async {
|
|
|
|
var grant = createGrant();
|
|
|
|
await grant.getAuthorizationUrl(Uri.parse(_options.callback),
|
|
|
|
scopes: _options.scopes);
|
2018-09-12 03:23:42 +00:00
|
|
|
var client =
|
|
|
|
await grant.handleAuthorizationResponse(req.uri.queryParameters);
|
2017-02-23 01:13:23 +00:00
|
|
|
return await verifier(client);
|
2017-01-12 22:48:51 +00:00
|
|
|
}
|
|
|
|
}
|