Add 'packages/auth_twitter/' from commit '4b350894a91972378db35624d20349934fde2e72'
git-subtree-dir: packages/auth_twitter git-subtree-mainline:7b33017272
git-subtree-split:4b350894a9
This commit is contained in:
commit
58e42256e5
8 changed files with 324 additions and 0 deletions
30
packages/auth_twitter/.gitignore
vendored
Normal file
30
packages/auth_twitter/.gitignore
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
.buildlog
|
||||
.packages
|
||||
.project
|
||||
.pub/
|
||||
.scripts-bin/
|
||||
build/
|
||||
**/packages/
|
||||
|
||||
# Files created by dart2js
|
||||
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
||||
# rules if you intend to use dart2js directly
|
||||
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
||||
# differentiate from explicit Javascript files)
|
||||
*.dart.js
|
||||
*.part.js
|
||||
*.js.deps
|
||||
*.js.map
|
||||
*.info.json
|
||||
|
||||
# Directory created by dartdoc
|
||||
doc/api/
|
||||
|
||||
# Don't commit pubspec lock file
|
||||
# (Library packages only! Remove pattern if developing an application package)
|
||||
pubspec.lock
|
||||
|
||||
log.txt
|
5
packages/auth_twitter/CHANGELOG.md
Normal file
5
packages/auth_twitter/CHANGELOG.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# 2.0.0
|
||||
* Angel 2 + Dart 2 suppport.
|
||||
* Use `package:twitter` instead of `package:twit`.
|
||||
* Add `TwitterAuthorizationException`.
|
||||
* Add `onError` callback.
|
21
packages/auth_twitter/LICENSE
Normal file
21
packages/auth_twitter/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016 The Angel Framework
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
4
packages/auth_twitter/README.md
Normal file
4
packages/auth_twitter/README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
# auth_twitter
|
||||
angel_auth strategy for Twitter login.
|
||||
|
||||
See the [example](example/server.dart);
|
4
packages/auth_twitter/analysis_options.yaml
Normal file
4
packages/auth_twitter/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
94
packages/auth_twitter/example/main.dart
Normal file
94
packages/auth_twitter/example/main.dart
Normal file
|
@ -0,0 +1,94 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:angel_auth/angel_auth.dart';
|
||||
import 'package:angel_auth_twitter/angel_auth_twitter.dart';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class _User {
|
||||
final String handle;
|
||||
|
||||
_User(this.handle);
|
||||
|
||||
Map<String, dynamic> toJson() => {'handle': handle};
|
||||
}
|
||||
|
||||
main() async {
|
||||
var app = Angel();
|
||||
var http = AngelHttp(app);
|
||||
var auth = AngelAuth<_User>(
|
||||
jwtKey: 'AUTH_TWITTER_SECRET',
|
||||
allowCookie: false,
|
||||
serializer: (user) async => user.handle,
|
||||
deserializer: (screenName) async {
|
||||
// Of course, in a real app, you would fetch
|
||||
// user data, but not here.
|
||||
return _User(screenName.toString());
|
||||
},
|
||||
);
|
||||
|
||||
auth.strategies['twitter'] = TwitterStrategy(
|
||||
ExternalAuthOptions(
|
||||
clientId: Platform.environment['TWITTER_CLIENT_ID'] ??
|
||||
'qlrBWXneoSYZKS2bT4TGHaNaV',
|
||||
clientSecret: Platform.environment['TWITTER_CLIENT_SECRET'] ??
|
||||
'n2oA0ZtR7TzYincpMYElRpyYovAQlhYizTkTm2x5QxjH6mLVyE',
|
||||
redirectUri: Platform.environment['TWITTER_REDIRECT_URI'] ??
|
||||
'http://localhost:3000/auth/twitter/callback',
|
||||
),
|
||||
(twit, req, res) async {
|
||||
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
|
||||
..fallback(auth.decodeJwt)
|
||||
..get('/', auth.authenticate('twitter'));
|
||||
|
||||
app
|
||||
..get(
|
||||
'/auth/twitter/callback',
|
||||
auth.authenticate(
|
||||
'twitter',
|
||||
AngelAuthOptions(
|
||||
callback: (req, res, jwt) {
|
||||
return res.redirect('/home?token=$jwt');
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
app.get(
|
||||
'/home',
|
||||
chain([
|
||||
requireAuthentication<_User>(),
|
||||
(req, res) {
|
||||
var user = req.container.make<_User>();
|
||||
res.write('Your Twitter handle is ${user.handle}');
|
||||
return false;
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
app.logger = Logger('angel_auth_twitter')
|
||||
..onRecord.listen((rec) {
|
||||
print(rec);
|
||||
if (rec.error != null) print(rec.error);
|
||||
if (rec.stackTrace != null) print(rec.stackTrace);
|
||||
});
|
||||
|
||||
await http.startServer('127.0.0.1', 3000);
|
||||
print('Listening at ${http.uri}');
|
||||
}
|
146
packages/auth_twitter/lib/angel_auth_twitter.dart
Normal file
146
packages/auth_twitter/lib/angel_auth_twitter.dart
Normal file
|
@ -0,0 +1,146 @@
|
|||
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;
|
||||
import 'package:oauth/oauth.dart' as oauth;
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:twitter/twitter.dart';
|
||||
|
||||
/// Authenticates users by connecting to Twitter's API.
|
||||
class TwitterStrategy<User> extends AuthStrategy<User> {
|
||||
/// The options defining how to connect to the third-party.
|
||||
final ExternalAuthOptions options;
|
||||
|
||||
/// A callback that uses Twitter to authenticate a [User].
|
||||
///
|
||||
/// As always, return `null` if authentication fails.
|
||||
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;
|
||||
|
||||
oauth.Client _client;
|
||||
|
||||
/// The underlying [oauth.Client] used to query Twitter.
|
||||
oauth.Client get client => _client;
|
||||
|
||||
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(
|
||||
consumerId: options.clientId, consumerKey: options.clientSecret);
|
||||
_client = oauth.Client(tokens, client: client);
|
||||
}
|
||||
|
||||
/// Handle a response from Twitter.
|
||||
Future<Map<String, String>> handleUrlEncodedResponse(http.Response rs) async {
|
||||
var body = rs.body;
|
||||
|
||||
if (rs.statusCode != 200) {
|
||||
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);
|
||||
}
|
||||
|
||||
/// Get an access token.
|
||||
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
|
||||
}).then(handleUrlEncodedResponse);
|
||||
// var request = await createRequest("oauth/access_token",
|
||||
// method: "POST", data: {"verifier": verifier}, accessToken: token);
|
||||
}
|
||||
|
||||
/// Get a request token.
|
||||
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);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<User> authenticate(RequestContext req, ResponseContext res,
|
||||
[AngelAuthOptions options]) async {
|
||||
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 authenticateCallback(
|
||||
RequestContext req, ResponseContext res, AngelAuthOptions options) async {
|
||||
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';
|
||||
}
|
20
packages/auth_twitter/pubspec.yaml
Normal file
20
packages/auth_twitter/pubspec.yaml
Normal file
|
@ -0,0 +1,20 @@
|
|||
author: "Tobe O <thosakwe@gmail.com>"
|
||||
description: "package:angel_auth strategy for Twitter login. Auto-signs requests."
|
||||
environment:
|
||||
sdk: ">=2.0.0 <3.0.0"
|
||||
homepage: "https://github.com/angel-dart/auth_twitter.git"
|
||||
name: "angel_auth_twitter"
|
||||
version: 2.0.0
|
||||
dependencies:
|
||||
angel_auth: ^2.0.0
|
||||
angel_framework: ^2.0.0-alpha
|
||||
http: ">=0.11.0 <0.13.0"
|
||||
path: ^1.0.0
|
||||
# oauth:
|
||||
# git:
|
||||
# url: git://github.com/sh4869/oauth.dart.git
|
||||
# ref: develop
|
||||
twitter: ^1.0.0
|
||||
dev_dependencies:
|
||||
logging: ^0.11.0
|
||||
pedantic: ^1.0.0
|
Loading…
Reference in a new issue