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';
|
|
|
|
import 'package:http_parser/http_parser.dart';
|
2017-01-12 22:48:51 +00:00
|
|
|
import 'package:oauth2/oauth2.dart' as oauth2;
|
|
|
|
|
2019-01-06 00:43:06 +00:00
|
|
|
/// An Angel [AuthStrategy] that signs users in via a third-party service that speaks OAuth 2.0.
|
|
|
|
class OAuth2Strategy<User> implements AuthStrategy<User> {
|
|
|
|
/// 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;
|
|
|
|
|
|
|
|
/// 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;
|
|
|
|
|
|
|
|
/// The options defining how to connect to the third-party.
|
|
|
|
final ExternalAuthOptions options;
|
|
|
|
|
|
|
|
/// The URL to query to receive an authentication code.
|
|
|
|
final Uri authorizationEndpoint;
|
|
|
|
|
|
|
|
/// The URL to query to exchange an authentication code for a token.
|
|
|
|
final Uri tokenEndpoint;
|
|
|
|
|
|
|
|
/// An optional callback used to parse the response from a server who does not follow the OAuth 2.0 spec.
|
|
|
|
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.
|
2017-06-03 20:43:36 +00:00
|
|
|
final String delimiter;
|
2017-01-12 22:48:51 +00:00
|
|
|
|
2019-01-06 00:43:06 +00:00
|
|
|
Uri _redirect;
|
|
|
|
|
|
|
|
OAuth2Strategy(this.options, this.authorizationEndpoint, this.tokenEndpoint,
|
|
|
|
this.verifier, this.onError,
|
|
|
|
{this.getParameters, this.delimiter = ' '});
|
2017-01-12 22:48:51 +00:00
|
|
|
|
2019-01-06 00:43:06 +00:00
|
|
|
oauth2.AuthorizationCodeGrant _createGrant() =>
|
|
|
|
new oauth2.AuthorizationCodeGrant(options.clientId, authorizationEndpoint,
|
|
|
|
tokenEndpoint,
|
|
|
|
secret: options.clientSecret,
|
|
|
|
delimiter: delimiter,
|
|
|
|
getParameters: 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 {
|
2019-01-06 00:43:06 +00:00
|
|
|
if (options != null) {
|
|
|
|
var result = await authenticateCallback(req, res, options);
|
|
|
|
if (result is User)
|
|
|
|
return result;
|
|
|
|
else
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_redirect == null) {
|
|
|
|
var grant = _createGrant();
|
|
|
|
_redirect = grant.getAuthorizationUrl(
|
|
|
|
this.options.redirectUri,
|
|
|
|
scopes: this.options.scopes,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
res.redirect(_redirect);
|
2018-09-12 03:23:42 +00:00
|
|
|
return null;
|
2017-01-12 22:48:51 +00:00
|
|
|
}
|
|
|
|
|
2019-01-06 00:43:06 +00:00
|
|
|
/// The endpoint that is invoked by the third-party after successful authentication.
|
|
|
|
Future<dynamic> authenticateCallback(RequestContext req, ResponseContext res,
|
2017-01-12 22:48:51 +00:00
|
|
|
[AngelAuthOptions options]) async {
|
2019-01-06 00:43:06 +00:00
|
|
|
var grant = _createGrant();
|
|
|
|
grant.getAuthorizationUrl(this.options.redirectUri,
|
|
|
|
scopes: this.options.scopes);
|
|
|
|
|
|
|
|
try {
|
|
|
|
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);
|
|
|
|
}
|
2017-01-12 22:48:51 +00:00
|
|
|
}
|
|
|
|
}
|