2.0.0 done

This commit is contained in:
Tobe O 2019-04-11 10:28:51 -04:00
parent e86f6d1427
commit db505a470c
3 changed files with 85 additions and 23 deletions

View file

@ -1,3 +1,5 @@
# 2.0.0
* Angel 2 + Dart 2 suppport.
* Use `package:twitter` instead of `package:twit`.
* Use `package:twitter` instead of `package:twit`.
* Add `TwitterAuthorizationException`.
* Add `onError` callback.

View file

@ -38,11 +38,19 @@ main() async {
'http://localhost:3000/auth/twitter/callback',
),
(twit, req, res) async {
var response =
await twit.twitterClient.get('/account/verify_credentials.json');
var response = await twit.twitterClient
.get('https://api.twitter.com/1.1/account/verify_credentials.json');
var userData = json.decode(response.body) as Map;
return _User(userData['screen_name'] as String);
},
(e, req, res) async {
// When an error occurs, i.e. the user declines to approve the application.
if (e.isDenial) {
res.write("Why'd you say no???");
} else {
res.write("oops: ${e.message}");
}
},
);
app
@ -69,6 +77,7 @@ main() async {
(req, res) {
var user = req.container.make<_User>();
res.write('Your Twitter handle is ${user.handle}');
return false;
},
]),
);

View file

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'package:angel_auth/angel_auth.dart';
import 'package:angel_framework/angel_framework.dart';
import 'package:http/http.dart' as http;
@ -17,6 +18,10 @@ class TwitterStrategy<User> extends AuthStrategy<User> {
final FutureOr<User> Function(Twitter, RequestContext, ResponseContext)
verifier;
/// A callback that is triggered when an OAuth2 error occurs (i.e. the user declines to login);
final FutureOr<dynamic> Function(
TwitterAuthorizationException, RequestContext, ResponseContext) onError;
/// The root of Twitter's API. Defaults to `'https://api.twitter.com'`.
final Uri baseUrl;
@ -25,7 +30,7 @@ class TwitterStrategy<User> extends AuthStrategy<User> {
/// The underlying [oauth.Client] used to query Twitter.
oauth.Client get client => _client;
TwitterStrategy(this.options, this.verifier,
TwitterStrategy(this.options, this.verifier, this.onError,
{http.BaseClient client, Uri baseUrl})
: this.baseUrl = baseUrl ?? Uri.parse('https://api.twitter.com') {
var tokens = oauth.Tokens(
@ -38,8 +43,16 @@ class TwitterStrategy<User> extends AuthStrategy<User> {
var body = rs.body;
if (rs.statusCode != 200) {
throw new AngelHttpException.notAuthenticated(
message: 'Twitter authentication error: $body');
var err = json.decode(rs.body) as Map;
var errors = err['errors'] as List;
if (errors.isNotEmpty) {
throw TwitterAuthorizationException(
errors[0]['message'] as String, false);
} else {
throw StateError(
'Twitter returned an error response without an error message: ${rs.body}');
}
}
return Uri.splitQueryString(body);
@ -49,6 +62,9 @@ class TwitterStrategy<User> extends AuthStrategy<User> {
Future<Map<String, String>> getAccessToken(String token, String verifier) {
return _client.post(
baseUrl.replace(path: p.join(baseUrl.path, 'oauth/access_token')),
headers: {
'accept': 'application/json'
},
body: {
'oauth_token': token,
'oauth_verifier': verifier
@ -61,6 +77,9 @@ class TwitterStrategy<User> extends AuthStrategy<User> {
Future<Map<String, String>> getRequestToken() {
return _client.post(
baseUrl.replace(path: p.join(baseUrl.path, 'oauth/request_token')),
headers: {
'accept': 'application/json'
},
body: {
"oauth_callback": options.redirectUri.toString()
}).then(handleUrlEncodedResponse);
@ -69,27 +88,59 @@ class TwitterStrategy<User> extends AuthStrategy<User> {
@override
Future<User> authenticate(RequestContext req, ResponseContext res,
[AngelAuthOptions options]) async {
if (options != null) {
return await authenticateCallback(req, res, options);
} else {
var result = await getRequestToken();
var token = result['oauth_token'];
var url = baseUrl.replace(
path: p.join(baseUrl.path, 'oauth/authorize'),
queryParameters: {'oauth_token': token});
res.redirect(url);
try {
if (options != null) {
var result = await authenticateCallback(req, res, options);
if (result is User)
return result;
else
return null;
} else {
var result = await getRequestToken();
var token = result['oauth_token'];
var url = baseUrl.replace(
path: p.join(baseUrl.path, 'oauth/authorize'),
queryParameters: {'oauth_token': token});
res.redirect(url);
return null;
}
} on TwitterAuthorizationException catch (e) {
var result = await onError(e, req, res);
await req.app.executeHandler(result, req, res);
await res.close();
return null;
}
}
Future<User> authenticateCallback(
Future authenticateCallback(
RequestContext req, ResponseContext res, AngelAuthOptions options) async {
// TODO: Handle errors
var token = req.queryParameters['oauth_token'] as String;
var verifier = req.queryParameters['oauth_verifier'] as String;
var loginData = await getAccessToken(token, verifier);
var twitter = Twitter(this.options.clientId, this.options.clientSecret,
loginData['oauth_token'], loginData['oauth_token_secret']);
return await this.verifier(twitter, req, res);
try {
if (req.queryParameters.containsKey('denied')) {
throw TwitterAuthorizationException(
'The user denied the Twitter authorization attempt.', true);
}
var token = req.queryParameters['oauth_token'] as String;
var verifier = req.queryParameters['oauth_verifier'] as String;
var loginData = await getAccessToken(token, verifier);
var twitter = Twitter(this.options.clientId, this.options.clientSecret,
loginData['oauth_token'], loginData['oauth_token_secret']);
return await this.verifier(twitter, req, res);
} on TwitterAuthorizationException catch (e) {
return await onError(e, req, res);
}
}
}
class TwitterAuthorizationException implements Exception {
/// The message associated with this exception.
final String message;
/// Whether the user denied the authorization attempt.
final bool isDenial;
TwitterAuthorizationException(this.message, this.isDenial);
@override
String toString() => 'TwitterAuthorizationException: $message';
}