2.0.0 done
This commit is contained in:
parent
e86f6d1427
commit
db505a470c
3 changed files with 85 additions and 23 deletions
|
@ -1,3 +1,5 @@
|
||||||
# 2.0.0
|
# 2.0.0
|
||||||
* Angel 2 + Dart 2 suppport.
|
* Angel 2 + Dart 2 suppport.
|
||||||
* Use `package:twitter` instead of `package:twit`.
|
* Use `package:twitter` instead of `package:twit`.
|
||||||
|
* Add `TwitterAuthorizationException`.
|
||||||
|
* Add `onError` callback.
|
|
@ -38,11 +38,19 @@ main() async {
|
||||||
'http://localhost:3000/auth/twitter/callback',
|
'http://localhost:3000/auth/twitter/callback',
|
||||||
),
|
),
|
||||||
(twit, req, res) async {
|
(twit, req, res) async {
|
||||||
var response =
|
var response = await twit.twitterClient
|
||||||
await twit.twitterClient.get('/account/verify_credentials.json');
|
.get('https://api.twitter.com/1.1/account/verify_credentials.json');
|
||||||
var userData = json.decode(response.body) as Map;
|
var userData = json.decode(response.body) as Map;
|
||||||
return _User(userData['screen_name'] as String);
|
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
|
app
|
||||||
|
@ -69,6 +77,7 @@ main() async {
|
||||||
(req, res) {
|
(req, res) {
|
||||||
var user = req.container.make<_User>();
|
var user = req.container.make<_User>();
|
||||||
res.write('Your Twitter handle is ${user.handle}');
|
res.write('Your Twitter handle is ${user.handle}');
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
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:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
@ -17,6 +18,10 @@ class TwitterStrategy<User> extends AuthStrategy<User> {
|
||||||
final FutureOr<User> Function(Twitter, RequestContext, ResponseContext)
|
final FutureOr<User> Function(Twitter, RequestContext, ResponseContext)
|
||||||
verifier;
|
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'`.
|
/// The root of Twitter's API. Defaults to `'https://api.twitter.com'`.
|
||||||
final Uri baseUrl;
|
final Uri baseUrl;
|
||||||
|
|
||||||
|
@ -25,7 +30,7 @@ class TwitterStrategy<User> extends AuthStrategy<User> {
|
||||||
/// The underlying [oauth.Client] used to query Twitter.
|
/// The underlying [oauth.Client] used to query Twitter.
|
||||||
oauth.Client get client => _client;
|
oauth.Client get client => _client;
|
||||||
|
|
||||||
TwitterStrategy(this.options, this.verifier,
|
TwitterStrategy(this.options, this.verifier, this.onError,
|
||||||
{http.BaseClient client, Uri baseUrl})
|
{http.BaseClient client, Uri baseUrl})
|
||||||
: this.baseUrl = baseUrl ?? Uri.parse('https://api.twitter.com') {
|
: this.baseUrl = baseUrl ?? Uri.parse('https://api.twitter.com') {
|
||||||
var tokens = oauth.Tokens(
|
var tokens = oauth.Tokens(
|
||||||
|
@ -38,8 +43,16 @@ class TwitterStrategy<User> extends AuthStrategy<User> {
|
||||||
var body = rs.body;
|
var body = rs.body;
|
||||||
|
|
||||||
if (rs.statusCode != 200) {
|
if (rs.statusCode != 200) {
|
||||||
throw new AngelHttpException.notAuthenticated(
|
var err = json.decode(rs.body) as Map;
|
||||||
message: 'Twitter authentication error: $body');
|
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);
|
return Uri.splitQueryString(body);
|
||||||
|
@ -49,6 +62,9 @@ class TwitterStrategy<User> extends AuthStrategy<User> {
|
||||||
Future<Map<String, String>> getAccessToken(String token, String verifier) {
|
Future<Map<String, String>> getAccessToken(String token, String verifier) {
|
||||||
return _client.post(
|
return _client.post(
|
||||||
baseUrl.replace(path: p.join(baseUrl.path, 'oauth/access_token')),
|
baseUrl.replace(path: p.join(baseUrl.path, 'oauth/access_token')),
|
||||||
|
headers: {
|
||||||
|
'accept': 'application/json'
|
||||||
|
},
|
||||||
body: {
|
body: {
|
||||||
'oauth_token': token,
|
'oauth_token': token,
|
||||||
'oauth_verifier': verifier
|
'oauth_verifier': verifier
|
||||||
|
@ -61,6 +77,9 @@ class TwitterStrategy<User> extends AuthStrategy<User> {
|
||||||
Future<Map<String, String>> getRequestToken() {
|
Future<Map<String, String>> getRequestToken() {
|
||||||
return _client.post(
|
return _client.post(
|
||||||
baseUrl.replace(path: p.join(baseUrl.path, 'oauth/request_token')),
|
baseUrl.replace(path: p.join(baseUrl.path, 'oauth/request_token')),
|
||||||
|
headers: {
|
||||||
|
'accept': 'application/json'
|
||||||
|
},
|
||||||
body: {
|
body: {
|
||||||
"oauth_callback": options.redirectUri.toString()
|
"oauth_callback": options.redirectUri.toString()
|
||||||
}).then(handleUrlEncodedResponse);
|
}).then(handleUrlEncodedResponse);
|
||||||
|
@ -69,27 +88,59 @@ class TwitterStrategy<User> extends AuthStrategy<User> {
|
||||||
@override
|
@override
|
||||||
Future<User> authenticate(RequestContext req, ResponseContext res,
|
Future<User> authenticate(RequestContext req, ResponseContext res,
|
||||||
[AngelAuthOptions options]) async {
|
[AngelAuthOptions options]) async {
|
||||||
if (options != null) {
|
try {
|
||||||
return await authenticateCallback(req, res, options);
|
if (options != null) {
|
||||||
} else {
|
var result = await authenticateCallback(req, res, options);
|
||||||
var result = await getRequestToken();
|
if (result is User)
|
||||||
var token = result['oauth_token'];
|
return result;
|
||||||
var url = baseUrl.replace(
|
else
|
||||||
path: p.join(baseUrl.path, 'oauth/authorize'),
|
return null;
|
||||||
queryParameters: {'oauth_token': token});
|
} else {
|
||||||
res.redirect(url);
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<User> authenticateCallback(
|
Future authenticateCallback(
|
||||||
RequestContext req, ResponseContext res, AngelAuthOptions options) async {
|
RequestContext req, ResponseContext res, AngelAuthOptions options) async {
|
||||||
// TODO: Handle errors
|
try {
|
||||||
var token = req.queryParameters['oauth_token'] as String;
|
if (req.queryParameters.containsKey('denied')) {
|
||||||
var verifier = req.queryParameters['oauth_verifier'] as String;
|
throw TwitterAuthorizationException(
|
||||||
var loginData = await getAccessToken(token, verifier);
|
'The user denied the Twitter authorization attempt.', true);
|
||||||
var twitter = Twitter(this.options.clientId, this.options.clientSecret,
|
}
|
||||||
loginData['oauth_token'], loginData['oauth_token_secret']);
|
|
||||||
return await this.verifier(twitter, req, res);
|
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';
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue