From f04221bbecf560a8c97055015b40b59d518d3219 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 12 Jan 2017 16:43:33 -0500 Subject: [PATCH 01/13] Initial commit --- .gitignore | 27 +++++++++++++++++++++++++++ LICENSE | 21 +++++++++++++++++++++ README.md | 2 ++ 3 files changed, 50 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7c280441 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub +.buildlog +.packages +.project +.pub/ +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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..3de28325 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Tobe O + +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. diff --git a/README.md b/README.md new file mode 100644 index 00000000..c3b29d01 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# auth_oauth2 +angel_auth strategy for OAuth2 login, i.e. Facebook. From 0a43161b86235b5c246978ef7a7a6484063a3999 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Thu, 12 Jan 2017 17:48:51 -0500 Subject: [PATCH 02/13] 0.0.0 --- .gitignore | 2 + README.md | 2 + example/basic.dart | 54 ++++++++++++++++++ lib/angel_auth_oauth2.dart | 114 +++++++++++++++++++++++++++++++++++++ pubspec.yaml | 13 +++++ 5 files changed, 185 insertions(+) create mode 100644 example/basic.dart create mode 100644 lib/angel_auth_oauth2.dart create mode 100644 pubspec.yaml diff --git a/.gitignore b/.gitignore index 7c280441..2aa48381 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ doc/api/ # Don't commit pubspec lock file # (Library packages only! Remove pattern if developing an application package) pubspec.lock + +log.txt \ No newline at end of file diff --git a/README.md b/README.md index c3b29d01..8e7ae431 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # auth_oauth2 +[![version 0.0.0](https://img.shields.io/badge/pub-v0.0.0-red.svg)](https://pub.dartlang.org/packages/angel_auth_oauth2) + angel_auth strategy for OAuth2 login, i.e. Facebook. diff --git a/example/basic.dart b/example/basic.dart new file mode 100644 index 00000000..00ca715d --- /dev/null +++ b/example/basic.dart @@ -0,0 +1,54 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:angel_auth/angel_auth.dart'; +import 'package:angel_diagnostics/angel_diagnostics.dart'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_auth_oauth2/angel_auth_oauth2.dart'; +import 'package:oauth2/oauth2.dart' as oauth2; + +const Map OAUTH2_CONFIG = const { + 'callback': '', + 'key': '', + 'secret': '', + 'authorizationEndpoint': '', + 'tokenEndpoint': '' +}; + +main() async { + var app = new Angel()..use('/users', new MemoryService()); + + var auth = new AngelAuth(jwtKey: 'oauth2 example secret', allowCookie: false); + auth.deserializer = + (String idStr) => app.service('users').read(int.parse(idStr)); + auth.serializer = (User user) async => user.id; + + auth.strategies.add(new OAuth2Strategy('example_site', OAUTH2_CONFIG, + (oauth2.Client client) async { + var response = await client.get('/link/to/user/profile'); + return JSON.decode(response.body); + })); + + app.get('/auth/example_site', auth.authenticate('example_site')); + app.get( + '/auth/example_site/callback', + auth.authenticate('example_site', + new AngelAuthOptions(callback: (req, res, jwt) async { + // In real-life, you might include a pop-up callback script + res.write('Your JWT: $jwt'); + }))); + + await app.configure(auth); + await app.configure(logRequests(new File('log.txt'))); + await app.configure(profileRequests()); + + var server = await app.startServer(InternetAddress.LOOPBACK_IP_V4, 3000); + print('Listening on http://${server.address.address}:${server.port}'); +} + +class User extends MemoryModel { + String example_siteId; + + User({int id, this.example_siteId}) { + this.id = id; + } +} diff --git a/lib/angel_auth_oauth2.dart b/lib/angel_auth_oauth2.dart new file mode 100644 index 00000000..6de6f066 --- /dev/null +++ b/lib/angel_auth_oauth2.dart @@ -0,0 +1,114 @@ +library angel_auth_oauth2; + +import 'dart:async'; +import 'package:angel_auth/angel_auth.dart'; +import 'package:angel_framework/src/http/response_context.dart'; +import 'package:angel_framework/src/http/request_context.dart'; +import 'package:angel_validate/angel_validate.dart'; +import 'package:oauth2/oauth2.dart' as oauth2; + +/// Loads a user profile via OAuth2. +typedef Future ProfileLoader(oauth2.Client client); + +final Validator OAUTH2_OPTIONS_SCHEMA = new Validator({ + 'key*': isString, + 'secret*': isString, + 'authorizationEndpoint*': isString, + 'tokenEndpoint*': isString, + 'callback*': isString, + 'scopes': new isInstanceOf>() +}, defaultValues: { + 'scopes': [] +}, customErrorMessages: { + 'scopes': "'scopes' must be an Iterable of strings. You provided: {{value}}" +}); + +class AngelAuthOauth2Options { + String key, secret, authorizationEndpoint, tokenEndpoint, callback; + Iterable scopes; + + AngelAuthOauth2Options( + {this.key, + this.secret, + this.authorizationEndpoint, + this.tokenEndpoint, + this.callback, + Iterable scopes: const []}) { + this.scopes = scopes ?? []; + } + + factory AngelAuthOauth2Options.fromJson(Map json) => + new AngelAuthOauth2Options( + key: json['key'], + secret: json['secret'], + authorizationEndpoint: json['authorizationEndpoint'], + tokenEndpoint: json['tokenEndpoint'], + callback: json['callback'], + scopes: json['scopes'] ?? []); + + Map toJson() { + return { + 'key': key, + 'secret': secret, + 'authorizationEndpoint': authorizationEndpoint, + 'tokenEndpoint': tokenEndpoint, + 'callback': callback, + 'scopes': scopes.toList() + }; + } +} + +class OAuth2Strategy implements AuthStrategy { + String _name; + AngelAuthOauth2Options _options; + final ProfileLoader profileLoader; + + @override + String get name => _name; + + @override + set name(String value) => _name = name; + + /// [options] can be either a `Map` or an instance of [AngelAuthOauth2Options]. + OAuth2Strategy(this._name, options, this.profileLoader) { + if (options is AngelAuthOauth2Options) + _options = options; + else if (options is Map) + _options = new AngelAuthOauth2Options.fromJson( + 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), + secret: _options.secret); + + @override + Future authenticate(RequestContext req, ResponseContext res, + [AngelAuthOptions options]) async { + if (options != null) return authenticateCallback(req, res, options); + + var grant = createGrant(); + res.redirect(grant + .getAuthorizationUrl(Uri.parse(_options.callback), + scopes: _options.scopes) + .toString()); + return false; + } + + Future authenticateCallback(RequestContext req, ResponseContext res, + [AngelAuthOptions options]) async { + var grant = createGrant(); + await grant.getAuthorizationUrl(Uri.parse(_options.callback), + scopes: _options.scopes); + var client = await grant.handleAuthorizationResponse(req.query); + return await profileLoader(client); + } + + @override + Future canLogout(RequestContext req, ResponseContext res) async => true; +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 00000000..3fb18d80 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,13 @@ +name: angel_auth_oauth2 +description: angel_auth strategy for OAuth2 login, i.e. Facebook. +version: 0.0.0 +author: Tobe O +environment: + sdk: ">=1.19.0" +homepage: https://github.com/angel-dart/auth_oauth2.git +dependencies: + angel_auth: ^1.0.0-dev + angel_validate: ^1.0.0-beta + oauth2: ^1.0.0 +dev_dependencies: + angel_diagnostics: ^1.0.0-dev \ No newline at end of file From 056d6809ed1c1363dd4a8ca4d61b2a28431d0c2d Mon Sep 17 00:00:00 2001 From: thosakwe Date: Wed, 22 Feb 2017 20:13:23 -0500 Subject: [PATCH 03/13] 1.0.0 --- README.md | 3 ++- example/basic.dart | 7 ++++--- lib/angel_auth_oauth2.dart | 24 ++++++++++++------------ pubspec.yaml | 2 +- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 8e7ae431..f3cd0ea8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # auth_oauth2 -[![version 0.0.0](https://img.shields.io/badge/pub-v0.0.0-red.svg)](https://pub.dartlang.org/packages/angel_auth_oauth2) + +[![version 1.0.0](https://img.shields.io/badge/pub-1.0.0-brightgreen.svg)](https://pub.dartlang.org/packages/angel_auth_oauth2) angel_auth strategy for OAuth2 login, i.e. Facebook. diff --git a/example/basic.dart b/example/basic.dart index 00ca715d..cf3ed935 100644 --- a/example/basic.dart +++ b/example/basic.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:angel_auth/angel_auth.dart'; import 'package:angel_diagnostics/angel_diagnostics.dart'; import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_framework/common.dart'; import 'package:angel_auth_oauth2/angel_auth_oauth2.dart'; import 'package:oauth2/oauth2.dart' as oauth2; @@ -15,7 +16,7 @@ const Map OAUTH2_CONFIG = const { }; main() async { - var app = new Angel()..use('/users', new MemoryService()); + var app = new Angel()..use('/users', new TypedService(new MapService())); var auth = new AngelAuth(jwtKey: 'oauth2 example secret', allowCookie: false); auth.deserializer = @@ -45,10 +46,10 @@ main() async { print('Listening on http://${server.address.address}:${server.port}'); } -class User extends MemoryModel { +class User extends Model { String example_siteId; - User({int id, this.example_siteId}) { + User({String id, this.example_siteId}) { this.id = id; } } diff --git a/lib/angel_auth_oauth2.dart b/lib/angel_auth_oauth2.dart index 6de6f066..0d29dfd2 100644 --- a/lib/angel_auth_oauth2.dart +++ b/lib/angel_auth_oauth2.dart @@ -8,7 +8,7 @@ import 'package:angel_validate/angel_validate.dart'; import 'package:oauth2/oauth2.dart' as oauth2; /// Loads a user profile via OAuth2. -typedef Future ProfileLoader(oauth2.Client client); +typedef Future OAuth2Verifier(oauth2.Client client); final Validator OAUTH2_OPTIONS_SCHEMA = new Validator({ 'key*': isString, @@ -23,11 +23,11 @@ final Validator OAUTH2_OPTIONS_SCHEMA = new Validator({ 'scopes': "'scopes' must be an Iterable of strings. You provided: {{value}}" }); -class AngelAuthOauth2Options { +class AngelAuthOAuth2Options { String key, secret, authorizationEndpoint, tokenEndpoint, callback; Iterable scopes; - AngelAuthOauth2Options( + AngelAuthOAuth2Options( {this.key, this.secret, this.authorizationEndpoint, @@ -37,8 +37,8 @@ class AngelAuthOauth2Options { this.scopes = scopes ?? []; } - factory AngelAuthOauth2Options.fromJson(Map json) => - new AngelAuthOauth2Options( + factory AngelAuthOAuth2Options.fromJson(Map json) => + new AngelAuthOAuth2Options( key: json['key'], secret: json['secret'], authorizationEndpoint: json['authorizationEndpoint'], @@ -60,8 +60,8 @@ class AngelAuthOauth2Options { class OAuth2Strategy implements AuthStrategy { String _name; - AngelAuthOauth2Options _options; - final ProfileLoader profileLoader; + AngelAuthOAuth2Options _options; + final OAuth2Verifier verifier; @override String get name => _name; @@ -69,12 +69,12 @@ class OAuth2Strategy implements AuthStrategy { @override set name(String value) => _name = name; - /// [options] can be either a `Map` or an instance of [AngelAuthOauth2Options]. - OAuth2Strategy(this._name, options, this.profileLoader) { - if (options is AngelAuthOauth2Options) + /// [options] can be either a `Map` or an instance of [AngelAuthOAuth2Options]. + OAuth2Strategy(this._name, options, this.verifier) { + if (options is AngelAuthOAuth2Options) _options = options; else if (options is Map) - _options = new AngelAuthOauth2Options.fromJson( + _options = new AngelAuthOAuth2Options.fromJson( OAUTH2_OPTIONS_SCHEMA.enforce(options)); else throw new ArgumentError('Invalid OAuth2 options: $options'); @@ -106,7 +106,7 @@ class OAuth2Strategy implements AuthStrategy { await grant.getAuthorizationUrl(Uri.parse(_options.callback), scopes: _options.scopes); var client = await grant.handleAuthorizationResponse(req.query); - return await profileLoader(client); + return await verifier(client); } @override diff --git a/pubspec.yaml b/pubspec.yaml index 3fb18d80..fcaae8ba 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: angel_auth_oauth2 description: angel_auth strategy for OAuth2 login, i.e. Facebook. -version: 0.0.0 +version: 1.0.0 author: Tobe O environment: sdk: ">=1.19.0" From 56598f33defd9ade5a7c61d52bff9c8bcc22148e Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 3 Jun 2017 16:43:36 -0400 Subject: [PATCH 04/13] Github works --- .analysis-options | 2 + .gitignore | 46 ++++++++++- .../runConfigurations/Github_Auth_Server.xml | 7 ++ example/basic.dart | 55 ------------- example/github.dart | 81 +++++++++++++++++++ lib/angel_auth_oauth2.dart | 31 +++++-- pubspec.yaml | 13 +-- 7 files changed, 167 insertions(+), 68 deletions(-) create mode 100644 .analysis-options create mode 100644 .idea/runConfigurations/Github_Auth_Server.xml delete mode 100644 example/basic.dart create mode 100644 example/github.dart diff --git a/.analysis-options b/.analysis-options new file mode 100644 index 00000000..518eb901 --- /dev/null +++ b/.analysis-options @@ -0,0 +1,2 @@ +analyzer: + strong-mode: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2aa48381..a26910ac 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,48 @@ doc/api/ # (Library packages only! Remove pattern if developing an application package) pubspec.lock -log.txt \ No newline at end of file +log.txt +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties diff --git a/.idea/runConfigurations/Github_Auth_Server.xml b/.idea/runConfigurations/Github_Auth_Server.xml new file mode 100644 index 00000000..c68cda20 --- /dev/null +++ b/.idea/runConfigurations/Github_Auth_Server.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/example/basic.dart b/example/basic.dart deleted file mode 100644 index cf3ed935..00000000 --- a/example/basic.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; -import 'package:angel_auth/angel_auth.dart'; -import 'package:angel_diagnostics/angel_diagnostics.dart'; -import 'package:angel_framework/angel_framework.dart'; -import 'package:angel_framework/common.dart'; -import 'package:angel_auth_oauth2/angel_auth_oauth2.dart'; -import 'package:oauth2/oauth2.dart' as oauth2; - -const Map OAUTH2_CONFIG = const { - 'callback': '', - 'key': '', - 'secret': '', - 'authorizationEndpoint': '', - 'tokenEndpoint': '' -}; - -main() async { - var app = new Angel()..use('/users', new TypedService(new MapService())); - - var auth = new AngelAuth(jwtKey: 'oauth2 example secret', allowCookie: false); - auth.deserializer = - (String idStr) => app.service('users').read(int.parse(idStr)); - auth.serializer = (User user) async => user.id; - - auth.strategies.add(new OAuth2Strategy('example_site', OAUTH2_CONFIG, - (oauth2.Client client) async { - var response = await client.get('/link/to/user/profile'); - return JSON.decode(response.body); - })); - - app.get('/auth/example_site', auth.authenticate('example_site')); - app.get( - '/auth/example_site/callback', - auth.authenticate('example_site', - new AngelAuthOptions(callback: (req, res, jwt) async { - // In real-life, you might include a pop-up callback script - res.write('Your JWT: $jwt'); - }))); - - await app.configure(auth); - await app.configure(logRequests(new File('log.txt'))); - await app.configure(profileRequests()); - - var server = await app.startServer(InternetAddress.LOOPBACK_IP_V4, 3000); - print('Listening on http://${server.address.address}:${server.port}'); -} - -class User extends Model { - String example_siteId; - - User({String id, this.example_siteId}) { - this.id = id; - } -} diff --git a/example/github.dart b/example/github.dart new file mode 100644 index 00000000..cb92b891 --- /dev/null +++ b/example/github.dart @@ -0,0 +1,81 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:angel_auth/angel_auth.dart'; +import 'package:angel_diagnostics/angel_diagnostics.dart'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_framework/common.dart'; +import 'package:angel_auth_oauth2/angel_auth_oauth2.dart'; +import 'package:oauth2/oauth2.dart' as oauth2; + +const AngelAuthOAuth2Options OAUTH2_CONFIG = const AngelAuthOAuth2Options( + callback: 'http://localhost:3000/auth/github/callback', + key: '6caeaf5d4c04936ec34f', + secret: '178360518cf9de4802e2346a4b6ebec525dc4427', + authorizationEndpoint: 'http://github.com/login/oauth/authorize', + tokenEndpoint: 'https://github.com/login/oauth/access_token'); + +main() async { + var app = new Angel(); + app.lazyParseBodies = true; + app.use('/users', new MapService()); + + var auth = new AngelAuth(jwtKey: 'oauth2 example secret', allowCookie: false); + + auth.deserializer = app.service('users').read; + auth.serializer = (User user) async => user.id; + + auth.strategies.add( + new OAuth2Strategy('github', OAUTH2_CONFIG, (oauth2.Client client) async { + var response = await client.get('https://api.github.com/user'); + var ghUser = JSON.decode(response.body); + var id = ghUser['id']; + + Iterable matchingUsers = await app.service('users').index({ + 'query': {'githubId': id} + }); + + if (matchingUsers.isNotEmpty) { + // Return the corresponding user, if it exists + return User.parse(matchingUsers.firstWhere((u) => u['githubId'] == id)); + } else { + // Otherwise,create a user + return await app + .service('users') + .create({'githubId': id}).then(User.parse); + } + })); + + app.get('/auth/github', auth.authenticate('github')); + app.get( + '/auth/github/callback', + auth.authenticate('github', + new AngelAuthOptions(callback: (req, res, jwt) async { + // In real-life, you might include a pop-up callback script. + // + // Use `confirmPopupAuthentication`, which is bundled with + // `package:angel_auth`. + res.write('Your JWT: $jwt'); + }))); + + await app.configure(auth); + await app.configure(logRequests()); + + var server = await app.startServer(InternetAddress.LOOPBACK_IP_V4, 3000); + var url = 'http://${server.address.address}:${server.port}'; + print('Listening on $url'); + print('View user listing: $url/users'); + print('Sign in via Github: $url/auth/github'); +} + +class User extends Model { + @override + String id; + int githubId; + + User({this.id, this.githubId}); + + static User parse(Map map) => + new User(id: map['id'], githubId: map['github_id']); + + Map toJson() => {'id': id, 'github_id': githubId}; +} diff --git a/lib/angel_auth_oauth2.dart b/lib/angel_auth_oauth2.dart index 0d29dfd2..226ef36a 100644 --- a/lib/angel_auth_oauth2.dart +++ b/lib/angel_auth_oauth2.dart @@ -23,19 +23,35 @@ final Validator OAUTH2_OPTIONS_SCHEMA = new Validator({ 'scopes': "'scopes' must be an Iterable of strings. You provided: {{value}}" }); +/// Holds credentials and also specifies the means of authenticating users against a remote server. class AngelAuthOAuth2Options { - String key, secret, authorizationEndpoint, tokenEndpoint, callback; - Iterable scopes; + /// Your application's client key or client ID, registered with the remote server. + final String key; - AngelAuthOAuth2Options( + /// Your application's client secret, registered with the remote server. + final String secret; + + /// The remote endpoint that prompts external users for authentication credentials. + final String authorizationEndpoint; + + /// The remote endpoint that exchanges auth codes for access tokens. + final String tokenEndpoint; + + /// 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 scopes; + + const AngelAuthOAuth2Options( {this.key, this.secret, this.authorizationEndpoint, this.tokenEndpoint, this.callback, - Iterable scopes: const []}) { - this.scopes = scopes ?? []; - } + this.delimiter: ' ', + this.scopes: const []}); factory AngelAuthOAuth2Options.fromJson(Map json) => new AngelAuthOAuth2Options( @@ -85,7 +101,8 @@ class OAuth2Strategy implements AuthStrategy { _options.key, Uri.parse(_options.authorizationEndpoint), Uri.parse(_options.tokenEndpoint), - secret: _options.secret); + secret: _options.secret, + delimiter: _options.delimiter ?? ' '); @override Future authenticate(RequestContext req, ResponseContext res, diff --git a/pubspec.yaml b/pubspec.yaml index fcaae8ba..93b46603 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,11 +3,14 @@ description: angel_auth strategy for OAuth2 login, i.e. Facebook. version: 1.0.0 author: Tobe O environment: - sdk: ">=1.19.0" + sdk: ">=1.19.0" homepage: https://github.com/angel-dart/auth_oauth2.git dependencies: - angel_auth: ^1.0.0-dev - angel_validate: ^1.0.0-beta - oauth2: ^1.0.0 + angel_auth: ^1.0.0-dev + angel_validate: ^1.0.0-beta + oauth2: ^1.0.0 dev_dependencies: - angel_diagnostics: ^1.0.0-dev \ No newline at end of file + angel_diagnostics: ^1.0.0-dev +dependency_overrides: + oauth2: + git: https://github.com/thosakwe/oauth2.git \ No newline at end of file From 9f812f24855f4c1e486cef0dc877fad6c72e3e79 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 3 Jun 2017 17:05:13 -0400 Subject: [PATCH 05/13] Added docs --- .idea/auth_oauth2.iml | 18 ++++++++ .idea/modules.xml | 8 ++++ .idea/vcs.xml | 6 +++ README.md | 105 +++++++++++++++++++++++++++++++++++++++++- pubspec.yaml | 2 +- 5 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 .idea/auth_oauth2.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/auth_oauth2.iml b/.idea/auth_oauth2.iml new file mode 100644 index 00000000..7fe256fb --- /dev/null +++ b/.idea/auth_oauth2.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..afb47af5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index f3cd0ea8..edb4b7cd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,106 @@ # auth_oauth2 -[![version 1.0.0](https://img.shields.io/badge/pub-1.0.0-brightgreen.svg)](https://pub.dartlang.org/packages/angel_auth_oauth2) +[![version 1.0.1](https://img.shields.io/badge/pub-1.0.1-brightgreen.svg)](https://pub.dartlang.org/packages/angel_auth_oauth2) -angel_auth strategy for OAuth2 login, i.e. Facebook. +`package:angel_auth` strategy for OAuth2 login, i.e. Facebook or Github. + +# Usage +First, create an options object: + +```dart +configureServer(Angel app) async { + // Load from a Map, i.e. app config: + var opts = new AngelOAuth2Options.fromJson(map); + + // Create in-place: + const AngelAuthOAuth2Options OAUTH2_CONFIG = const AngelAuthOAuth2Options( + callback: '', + key: '', + secret: '', + authorizationEndpoint: '', + tokenEndpoint: ''); +} +``` + +After getting authenticated against the remote server, we need to be able to identify +users within our own application. Use an `OAuth2Verifier` to associate remote users +with local users. + +```dart +/// You might use a pure function to create a verifier that queries a +/// given service. +OAuth2Verifier oauth2verifier(Service userService) { + return (oauth2.Client client) async { + var response = await client.get('https://api.github.com/user'); + var ghUser = JSON.decode(response.body); + var id = ghUser['id']; + + Iterable matchingUsers = await userService.index({ + 'query': {'githubId': id} + }); + + if (matchingUsers.isNotEmpty) { + // Return the corresponding user, if it exists + return User.parse(matchingUsers.firstWhere((u) => u['githubId'] == id)); + } else { + // Otherwise,create a user + return await userService.create({'githubId': id}).then(User.parse); + } + }; +} +``` + +Now, initialize an `OAuth2Strategy`, using the options and verifier. +You'll also need to provide a name for this instance of the strategy. +Consider using the name of the remote authentication provider (ex. `facebook`). + +```dart +configureServer(Angel app) { + // ... + var oauthStrategy = + new OAuth2Strategy('github', OAUTH2_CONFIG, oauth2Verifier(app.service('users'))); +} +``` + +Lastly, connect it to an `AngelAuth` instance, and wire it up to an `Angel` server. +Set up two routes: + 1. Redirect users to the external provider + 2. Acts as a callback and handles an access code + +In the case of the callback route, you may want to display an HTML page that closes +a popup window. In this case, use `confirmPopupAuthentication`, which is bundled with +`package:angel_auth`, as a `callback` function: + +```dart +configureServer(Angel app) async { + // ... + var auth = new AngelAuth(); + auth.strategies.add(oauth2Strategy); + + // Redirect + app.get('/auth/github', auth.authenticate('github')); + + // Callback + app.get('/auth/github/callback', auth.authenticate( + 'github', + new AngelAuthOptions(callback: confirmPopupAuthentication()) + )); + + // Connect the plug-in!!! + await app.configure(auth); +} +``` + +## Custom Scope Delimiter +This package should work out-of-the-box for most OAuth2 providers, such as Github or Dropbox. +However, if your OAuth2 scopes are separated by a delimiter other than the default (`' '`), +you can add it in the `AngelOAuth2Options` constructor: + +```dart +configureServer(Angel app) async { + const AngelOAuth2Options OPTS = const AngelOAuth2Options( + // ... + delimiter: ',' + ); +} +``` \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 93b46603..f5eb1aac 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: angel_auth_oauth2 description: angel_auth strategy for OAuth2 login, i.e. Facebook. -version: 1.0.0 +version: 1.0.1 author: Tobe O environment: sdk: ">=1.19.0" From 3944a3f482ff2b3de5ed437cb35c68acf847077f Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 3 Jun 2017 17:06:12 -0400 Subject: [PATCH 06/13] Bye dependency_overrides --- pubspec.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index f5eb1aac..f34a9ce7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,4 @@ dependencies: angel_validate: ^1.0.0-beta oauth2: ^1.0.0 dev_dependencies: - angel_diagnostics: ^1.0.0-dev -dependency_overrides: - oauth2: - git: https://github.com/thosakwe/oauth2.git \ No newline at end of file + angel_diagnostics: ^1.0.0-dev \ No newline at end of file From ed3cb579aecbf4e9892ba688a7844593d89ac996 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Fri, 30 Mar 2018 12:44:15 -0400 Subject: [PATCH 07/13] Added getParameters --- .idea/auth_oauth2.iml | 2 - .../runConfigurations/Github_Auth_Server.xml | 2 +- CHANGELOG.md | 2 + README.md | 28 +++++++++++-- .analysis-options => analysis_options.yaml | 0 example/{github.dart => main.dart} | 34 ++++++++++----- lib/angel_auth_oauth2.dart | 42 +++++++++---------- pubspec.yaml | 6 +-- 8 files changed, 73 insertions(+), 43 deletions(-) create mode 100644 CHANGELOG.md rename .analysis-options => analysis_options.yaml (100%) rename example/{github.dart => main.dart} (66%) diff --git a/.idea/auth_oauth2.iml b/.idea/auth_oauth2.iml index 7fe256fb..eae13016 100644 --- a/.idea/auth_oauth2.iml +++ b/.idea/auth_oauth2.iml @@ -5,8 +5,6 @@ - - diff --git a/.idea/runConfigurations/Github_Auth_Server.xml b/.idea/runConfigurations/Github_Auth_Server.xml index c68cda20..d3ff1581 100644 --- a/.idea/runConfigurations/Github_Auth_Server.xml +++ b/.idea/runConfigurations/Github_Auth_Server.xml @@ -1,6 +1,6 @@ - diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..d08eb72d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +# 1.0.2 +Added `getParameters` to `AngelOAuth2Options`. \ No newline at end of file diff --git a/README.md b/README.md index edb4b7cd..e7b14ff7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # auth_oauth2 -[![version 1.0.1](https://img.shields.io/badge/pub-1.0.1-brightgreen.svg)](https://pub.dartlang.org/packages/angel_auth_oauth2) +[![Pub](https://img.shields.io/pub/v/angel_auth_oauth2.svg)](https://pub.dartlang.org/packages/angel_auth_oauth2) `package:angel_auth` strategy for OAuth2 login, i.e. Facebook or Github. @@ -13,7 +13,7 @@ configureServer(Angel app) async { var opts = new AngelOAuth2Options.fromJson(map); // Create in-place: - const AngelAuthOAuth2Options OAUTH2_CONFIG = const AngelAuthOAuth2Options( + var opts = const AngelAuthOAuth2Options( callback: '', key: '', secret: '', @@ -98,9 +98,31 @@ you can add it in the `AngelOAuth2Options` constructor: ```dart configureServer(Angel app) async { - const AngelOAuth2Options OPTS = const AngelOAuth2Options( + var opts = const AngelOAuth2Options( // ... delimiter: ',' ); } +``` + +## Handling non-JSON responses +Many OAuth2 providers do not follow the specification, and do not return +`application/json` responses. + +You can add a `getParameters` callback to parse the contents of any arbitrary +response: + +```dart +var opts = const AngelOAuth2Options( + // ... + getParameters: (contentType, body) { + if (contentType.type == 'application') { + if (contentType.subtype == 'x-www-form-urlencoded') + return Uri.splitQueryString(body); + else if (contentType.subtype == 'json') return JSON.decode(body); + } + + throw new FormatException('Invalid content-type $contentType; expected application/x-www-form-urlencoded or application/json.'); + } +); ``` \ No newline at end of file diff --git a/.analysis-options b/analysis_options.yaml similarity index 100% rename from .analysis-options rename to analysis_options.yaml diff --git a/example/github.dart b/example/main.dart similarity index 66% rename from example/github.dart rename to example/main.dart index cb92b891..d84141e5 100644 --- a/example/github.dart +++ b/example/main.dart @@ -1,31 +1,43 @@ import 'dart:convert'; import 'dart:io'; import 'package:angel_auth/angel_auth.dart'; -import 'package:angel_diagnostics/angel_diagnostics.dart'; import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/common.dart'; import 'package:angel_auth_oauth2/angel_auth_oauth2.dart'; +import 'package:logging/logging.dart'; import 'package:oauth2/oauth2.dart' as oauth2; -const AngelAuthOAuth2Options OAUTH2_CONFIG = const AngelAuthOAuth2Options( +final AngelAuthOAuth2Options oAuth2Config = new AngelAuthOAuth2Options( callback: 'http://localhost:3000/auth/github/callback', key: '6caeaf5d4c04936ec34f', secret: '178360518cf9de4802e2346a4b6ebec525dc4427', authorizationEndpoint: 'http://github.com/login/oauth/authorize', - tokenEndpoint: 'https://github.com/login/oauth/access_token'); + tokenEndpoint: 'https://github.com/login/oauth/access_token', + getParameters: (contentType, body) { + if (contentType.type == 'application') { + if (contentType.subtype == 'x-www-form-urlencoded') + return Uri.splitQueryString(body); + else if (contentType.subtype == 'json') return JSON.decode(body); + } + + throw new FormatException('Invalid content-type $contentType; expected application/x-www-form-urlencoded or application/json.'); + }); main() async { var app = new Angel(); app.lazyParseBodies = true; app.use('/users', new MapService()); - var auth = new AngelAuth(jwtKey: 'oauth2 example secret', allowCookie: false); + var auth = + new AngelAuth(jwtKey: 'oauth2 example secret', allowCookie: false); + + auth.deserializer = + (id) => app.service('users').read(id).then((u) => User.parse(u)); - auth.deserializer = app.service('users').read; auth.serializer = (User user) async => user.id; auth.strategies.add( - new OAuth2Strategy('github', OAUTH2_CONFIG, (oauth2.Client client) async { + new OAuth2Strategy('github', oAuth2Config, (oauth2.Client client) async { var response = await client.get('https://api.github.com/user'); var ghUser = JSON.decode(response.body); var id = ghUser['id']; @@ -41,7 +53,7 @@ main() async { // Otherwise,create a user return await app .service('users') - .create({'githubId': id}).then(User.parse); + .create({'githubId': id}).then((u) => User.parse(u)); } })); @@ -57,10 +69,12 @@ main() async { res.write('Your JWT: $jwt'); }))); - await app.configure(auth); - await app.configure(logRequests()); + await app.configure(auth.configureServer); - var server = await app.startServer(InternetAddress.LOOPBACK_IP_V4, 3000); + app.logger = new Logger('angel')..onRecord.listen(print); + + var http = new AngelHttp(app); + var server = await http.startServer(InternetAddress.LOOPBACK_IP_V4, 3000); var url = 'http://${server.address.address}:${server.port}'; print('Listening on $url'); print('View user listing: $url/users'); diff --git a/lib/angel_auth_oauth2.dart b/lib/angel_auth_oauth2.dart index 226ef36a..54d0a824 100644 --- a/lib/angel_auth_oauth2.dart +++ b/lib/angel_auth_oauth2.dart @@ -2,21 +2,18 @@ library angel_auth_oauth2; import 'dart:async'; import 'package:angel_auth/angel_auth.dart'; -import 'package:angel_framework/src/http/response_context.dart'; -import 'package:angel_framework/src/http/request_context.dart'; +import 'package:angel_framework/angel_framework.dart'; import 'package:angel_validate/angel_validate.dart'; +import 'package:http_parser/http_parser.dart'; import 'package:oauth2/oauth2.dart' as oauth2; -/// Loads a user profile via OAuth2. -typedef Future OAuth2Verifier(oauth2.Client client); - final Validator OAUTH2_OPTIONS_SCHEMA = new Validator({ 'key*': isString, 'secret*': isString, - 'authorizationEndpoint*': isString, - 'tokenEndpoint*': isString, + 'authorizationEndpoint*': anyOf(isString, const isInstanceOf()), + 'tokenEndpoint*': anyOf(isString, const isInstanceOf()), 'callback*': isString, - 'scopes': new isInstanceOf>() + 'scopes': const isInstanceOf>() }, defaultValues: { 'scopes': [] }, customErrorMessages: { @@ -32,10 +29,10 @@ class AngelAuthOAuth2Options { final String secret; /// The remote endpoint that prompts external users for authentication credentials. - final String authorizationEndpoint; + final authorizationEndpoint; /// The remote endpoint that exchanges auth codes for access tokens. - final String tokenEndpoint; + final tokenEndpoint; /// The callback URL that the OAuth2 server should redirect authenticated users to. final String callback; @@ -44,6 +41,8 @@ class AngelAuthOAuth2Options { final String delimiter; final Iterable scopes; + final Map Function(MediaType, String) getParameters; + const AngelAuthOAuth2Options( {this.key, this.secret, @@ -51,7 +50,8 @@ class AngelAuthOAuth2Options { this.tokenEndpoint, this.callback, this.delimiter: ' ', - this.scopes: const []}); + this.scopes: const [], + this.getParameters}); factory AngelAuthOAuth2Options.fromJson(Map json) => new AngelAuthOAuth2Options( @@ -62,7 +62,7 @@ class AngelAuthOAuth2Options { callback: json['callback'], scopes: json['scopes'] ?? []); - Map toJson() { + Map toJson() { return { 'key': key, 'secret': secret, @@ -74,19 +74,14 @@ class AngelAuthOAuth2Options { } } -class OAuth2Strategy implements AuthStrategy { - String _name; +class OAuth2Strategy implements AuthStrategy { + final FutureOr Function(oauth2.Client) verifier; + String name; + AngelAuthOAuth2Options _options; - final OAuth2Verifier verifier; - - @override - String get name => _name; - - @override - set name(String value) => _name = name; /// [options] can be either a `Map` or an instance of [AngelAuthOAuth2Options]. - OAuth2Strategy(this._name, options, this.verifier) { + OAuth2Strategy(this.name, options, this.verifier) { if (options is AngelAuthOAuth2Options) _options = options; else if (options is Map) @@ -102,7 +97,8 @@ class OAuth2Strategy implements AuthStrategy { Uri.parse(_options.authorizationEndpoint), Uri.parse(_options.tokenEndpoint), secret: _options.secret, - delimiter: _options.delimiter ?? ' '); + delimiter: _options.delimiter ?? ' ', + getParameters: _options.getParameters); @override Future authenticate(RequestContext req, ResponseContext res, diff --git a/pubspec.yaml b/pubspec.yaml index f34a9ce7..06c39ae7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: angel_auth_oauth2 description: angel_auth strategy for OAuth2 login, i.e. Facebook. -version: 1.0.1 +version: 1.0.2 author: Tobe O environment: sdk: ">=1.19.0" @@ -8,6 +8,4 @@ homepage: https://github.com/angel-dart/auth_oauth2.git dependencies: angel_auth: ^1.0.0-dev angel_validate: ^1.0.0-beta - oauth2: ^1.0.0 -dev_dependencies: - angel_diagnostics: ^1.0.0-dev \ No newline at end of file + oauth2: ^1.0.0 \ No newline at end of file From 5056738ef4269fd296bf528c6d5b97503497e2b6 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Fri, 30 Mar 2018 12:44:47 -0400 Subject: [PATCH 08/13] Upper SDK constraint --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 06c39ae7..5cd03915 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: angel_auth strategy for OAuth2 login, i.e. Facebook. version: 1.0.2 author: Tobe O environment: - sdk: ">=1.19.0" + sdk: ">=1.19.0 <3.0.0" homepage: https://github.com/angel-dart/auth_oauth2.git dependencies: angel_auth: ^1.0.0-dev From 3a5a31c5de963f213be39dd89582b1387bb60741 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Tue, 11 Sep 2018 23:23:42 -0400 Subject: [PATCH 09/13] 2.0.0 --- .gitignore | 2 ++ README.md | 2 +- analysis_options.yaml | 3 ++- example/main.dart | 52 ++++++++++++++++++++------------------ lib/angel_auth_oauth2.dart | 44 +++++++++++++++----------------- pubspec.yaml | 9 ++++--- 6 files changed, 58 insertions(+), 54 deletions(-) diff --git a/.gitignore b/.gitignore index a26910ac..199ba0a0 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,5 @@ com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties + +.dart_tool \ No newline at end of file diff --git a/README.md b/README.md index e7b14ff7..27579958 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ a popup window. In this case, use `confirmPopupAuthentication`, which is bundled configureServer(Angel app) async { // ... var auth = new AngelAuth(); - auth.strategies.add(oauth2Strategy); + auth.strategies['github'] = oauth2Strategy; // Redirect app.get('/auth/github', auth.authenticate('github')); diff --git a/analysis_options.yaml b/analysis_options.yaml index 518eb901..eae1e42a 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,2 +1,3 @@ analyzer: - strong-mode: true \ No newline at end of file + strong-mode: + implicit-casts: false \ No newline at end of file diff --git a/example/main.dart b/example/main.dart index d84141e5..95c98930 100644 --- a/example/main.dart +++ b/example/main.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'dart:io'; import 'package:angel_auth/angel_auth.dart'; import 'package:angel_framework/angel_framework.dart'; -import 'package:angel_framework/common.dart'; import 'package:angel_auth_oauth2/angel_auth_oauth2.dart'; import 'package:logging/logging.dart'; import 'package:oauth2/oauth2.dart' as oauth2; @@ -17,45 +16,48 @@ final AngelAuthOAuth2Options oAuth2Config = new AngelAuthOAuth2Options( if (contentType.type == 'application') { if (contentType.subtype == 'x-www-form-urlencoded') return Uri.splitQueryString(body); - else if (contentType.subtype == 'json') return JSON.decode(body); + else if (contentType.subtype == 'json') + return (json.decode(body) as Map).cast(); } - throw new FormatException('Invalid content-type $contentType; expected application/x-www-form-urlencoded or application/json.'); + throw new FormatException( + 'Invalid content-type $contentType; expected application/x-www-form-urlencoded or application/json.'); }); main() async { var app = new Angel(); - app.lazyParseBodies = true; app.use('/users', new MapService()); var auth = new AngelAuth(jwtKey: 'oauth2 example secret', allowCookie: false); auth.deserializer = - (id) => app.service('users').read(id).then((u) => User.parse(u)); + (id) => app.service('users').read(id).then((u) => User.parse(u as Map)); auth.serializer = (User user) async => user.id; - auth.strategies.add( - new OAuth2Strategy('github', oAuth2Config, (oauth2.Client client) async { - var response = await client.get('https://api.github.com/user'); - var ghUser = JSON.decode(response.body); - var id = ghUser['id']; + auth.strategies['github'] = new OAuth2Strategy( + oAuth2Config, + (oauth2.Client client) async { + var response = await client.get('https://api.github.com/user'); + var ghUser = json.decode(response.body); + var id = ghUser['id']; - Iterable matchingUsers = await app.service('users').index({ - 'query': {'githubId': id} - }); + Iterable matchingUsers = await app.service('users').index({ + 'query': {'githubId': id} + }); - if (matchingUsers.isNotEmpty) { - // Return the corresponding user, if it exists - return User.parse(matchingUsers.firstWhere((u) => u['githubId'] == id)); - } else { - // Otherwise,create a user - return await app - .service('users') - .create({'githubId': id}).then((u) => User.parse(u)); - } - })); + if (matchingUsers.isNotEmpty) { + // Return the corresponding user, if it exists + return User.parse(matchingUsers.firstWhere((u) => u['githubId'] == id)); + } else { + // Otherwise,create a user + return await app + .service('users') + .create({'githubId': id}).then((u) => User.parse(u as Map)); + } + }, + ); app.get('/auth/github', auth.authenticate('github')); app.get( @@ -74,7 +76,7 @@ main() async { app.logger = new Logger('angel')..onRecord.listen(print); var http = new AngelHttp(app); - var server = await http.startServer(InternetAddress.LOOPBACK_IP_V4, 3000); + var server = await http.startServer(InternetAddress.loopbackIPv4, 3000); var url = 'http://${server.address.address}:${server.port}'; print('Listening on $url'); print('View user listing: $url/users'); @@ -89,7 +91,7 @@ class User extends Model { User({this.id, this.githubId}); static User parse(Map map) => - new User(id: map['id'], githubId: map['github_id']); + new User(id: map['id'] as String, githubId: map['github_id'] as int); Map toJson() => {'id': id, 'github_id': githubId}; } diff --git a/lib/angel_auth_oauth2.dart b/lib/angel_auth_oauth2.dart index 54d0a824..b24d5895 100644 --- a/lib/angel_auth_oauth2.dart +++ b/lib/angel_auth_oauth2.dart @@ -10,10 +10,10 @@ import 'package:oauth2/oauth2.dart' as oauth2; final Validator OAUTH2_OPTIONS_SCHEMA = new Validator({ 'key*': isString, 'secret*': isString, - 'authorizationEndpoint*': anyOf(isString, const isInstanceOf()), - 'tokenEndpoint*': anyOf(isString, const isInstanceOf()), + 'authorizationEndpoint*': anyOf(isString, const TypeMatcher()), + 'tokenEndpoint*': anyOf(isString, const TypeMatcher()), 'callback*': isString, - 'scopes': const isInstanceOf>() + 'scopes': const TypeMatcher>() }, defaultValues: { 'scopes': [] }, customErrorMessages: { @@ -29,10 +29,10 @@ class AngelAuthOAuth2Options { final String secret; /// The remote endpoint that prompts external users for authentication credentials. - final authorizationEndpoint; + final String authorizationEndpoint; /// The remote endpoint that exchanges auth codes for access tokens. - final tokenEndpoint; + final String tokenEndpoint; /// The callback URL that the OAuth2 server should redirect authenticated users to. final String callback; @@ -55,12 +55,13 @@ class AngelAuthOAuth2Options { factory AngelAuthOAuth2Options.fromJson(Map json) => new AngelAuthOAuth2Options( - key: json['key'], - secret: json['secret'], - authorizationEndpoint: json['authorizationEndpoint'], - tokenEndpoint: json['tokenEndpoint'], - callback: json['callback'], - scopes: json['scopes'] ?? []); + 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()?.toList() ?? + []); Map toJson() { return { @@ -74,14 +75,13 @@ class AngelAuthOAuth2Options { } } -class OAuth2Strategy implements AuthStrategy { - final FutureOr Function(oauth2.Client) verifier; - String name; +class OAuth2Strategy implements AuthStrategy { + final FutureOr Function(oauth2.Client) verifier; AngelAuthOAuth2Options _options; /// [options] can be either a `Map` or an instance of [AngelAuthOAuth2Options]. - OAuth2Strategy(this.name, options, this.verifier) { + OAuth2Strategy(options, this.verifier) { if (options is AngelAuthOAuth2Options) _options = options; else if (options is Map) @@ -101,8 +101,8 @@ class OAuth2Strategy implements AuthStrategy { getParameters: _options.getParameters); @override - Future authenticate(RequestContext req, ResponseContext res, - [AngelAuthOptions options]) async { + FutureOr authenticate(RequestContext req, ResponseContext res, + [AngelAuthOptions options]) async { if (options != null) return authenticateCallback(req, res, options); var grant = createGrant(); @@ -110,18 +110,16 @@ class OAuth2Strategy implements AuthStrategy { .getAuthorizationUrl(Uri.parse(_options.callback), scopes: _options.scopes) .toString()); - return false; + return null; } - Future authenticateCallback(RequestContext req, ResponseContext res, + Future authenticateCallback(RequestContext req, ResponseContext res, [AngelAuthOptions options]) async { var grant = createGrant(); await grant.getAuthorizationUrl(Uri.parse(_options.callback), scopes: _options.scopes); - var client = await grant.handleAuthorizationResponse(req.query); + var client = + await grant.handleAuthorizationResponse(req.uri.queryParameters); return await verifier(client); } - - @override - Future canLogout(RequestContext req, ResponseContext res) async => true; } diff --git a/pubspec.yaml b/pubspec.yaml index 5cd03915..15782ad5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,12 @@ name: angel_auth_oauth2 description: angel_auth strategy for OAuth2 login, i.e. Facebook. -version: 1.0.2 +version: 2.0.0 author: Tobe O environment: - sdk: ">=1.19.0 <3.0.0" + sdk: ">=2.0.0-dev <3.0.0" homepage: https://github.com/angel-dart/auth_oauth2.git dependencies: - angel_auth: ^1.0.0-dev - angel_validate: ^1.0.0-beta + angel_auth: ^2.0.0 + angel_framework: ^2.0.0-alpha + angel_validate: ^2.0.0-alpha oauth2: ^1.0.0 \ No newline at end of file From 3fd2b5d310cf25fb50402783d07907fa02a7d936 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Tue, 11 Sep 2018 23:24:39 -0400 Subject: [PATCH 10/13] update deps --- pubspec.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 15782ad5..2e881912 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,4 +9,7 @@ dependencies: angel_auth: ^2.0.0 angel_framework: ^2.0.0-alpha angel_validate: ^2.0.0-alpha - oauth2: ^1.0.0 \ No newline at end of file + http_parser: ^3.0.0 + oauth2: ^1.0.0 +dev_dependencies: + logging: ^0.11.0 \ No newline at end of file From 2f14fb4a9906eb013245f70ca24ec074bd2caf0b Mon Sep 17 00:00:00 2001 From: Tobe O Date: Wed, 12 Sep 2018 10:17:28 -0400 Subject: [PATCH 11/13] Bump to 2.0.0+1, meta update --- CHANGELOG.md | 6 ++++++ pubspec.yaml | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d08eb72d..fe600b94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,8 @@ +# 2.0.0+1 +* Meta update to improve Pub score. + +# 2.0.0 +* Angel 2 + Dart 2 updates. + # 1.0.2 Added `getParameters` to `AngelOAuth2Options`. \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 2e881912..5f28e246 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: angel_auth_oauth2 -description: angel_auth strategy for OAuth2 login, i.e. Facebook. -version: 2.0.0 +description: angel_auth strategy for OAuth2 login, i.e. Facebook, Github, etc. +version: 2.0.0+1 author: Tobe O environment: sdk: ">=2.0.0-dev <3.0.0" From d80617d8b41f309cf2d1626960bd6753298efcb7 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 5 Jan 2019 19:43:06 -0500 Subject: [PATCH 12/13] 2.1.0 --- CHANGELOG.md | 5 ++ README.md | 79 +++++++++--------- analysis_options.yaml | 1 + example/main.dart | 126 +++++++++++++++++----------- lib/angel_auth_oauth2.dart | 166 ++++++++++++++----------------------- pubspec.yaml | 4 +- 6 files changed, 192 insertions(+), 189 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe600b94..ae11c7b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.1.0 +* Angel 2 + Dart 2 update +* Support for handling errors + rejections. +* Use `ExternalAuthOptions`. + # 2.0.0+1 * Meta update to improve Pub score. diff --git a/README.md b/README.md index 27579958..cd6cddc0 100644 --- a/README.md +++ b/README.md @@ -10,42 +10,41 @@ First, create an options object: ```dart configureServer(Angel app) async { // Load from a Map, i.e. app config: - var opts = new AngelOAuth2Options.fromJson(map); + var opts = ExternalAuthOptions.fromMap(app.configuration['auth0'] as Map); // Create in-place: - var opts = const AngelAuthOAuth2Options( - callback: '', - key: '', - secret: '', - authorizationEndpoint: '', - tokenEndpoint: ''); + var opts = ExternalAuthOptions( + clientId: '', + clientSecret: '', + redirectUri: Uri.parse('')); } ``` After getting authenticated against the remote server, we need to be able to identify -users within our own application. Use an `OAuth2Verifier` to associate remote users -with local users. +users within our own application. ```dart +typedef FutureOr OAuth2Verifier(oauth2.Client, RequestContext, ResponseContext); + /// You might use a pure function to create a verifier that queries a /// given service. -OAuth2Verifier oauth2verifier(Service userService) { - return (oauth2.Client client) async { +OAuth2Verifier oauth2verifier(Service userService) { + return (client) async { var response = await client.get('https://api.github.com/user'); - var ghUser = JSON.decode(response.body); - var id = ghUser['id']; - - Iterable matchingUsers = await userService.index({ - 'query': {'githubId': id} - }); - - if (matchingUsers.isNotEmpty) { - // Return the corresponding user, if it exists - return User.parse(matchingUsers.firstWhere((u) => u['githubId'] == id)); - } else { - // Otherwise,create a user - return await userService.create({'githubId': id}).then(User.parse); - } + var ghUser = json.decode(response.body); + var id = ghUser['id'] as int; + + var matchingUsers = await mappedUserService.index({ + 'query': {'github_id': id} + }); + + if (matchingUsers.isNotEmpty) { + // Return the corresponding user, if it exists. + return matchingUsers.first; + } else { + // Otherwise,create a user + return await mappedUserService.create(User(githubId: id)); + } }; } ``` @@ -56,9 +55,18 @@ Consider using the name of the remote authentication provider (ex. `facebook`). ```dart configureServer(Angel app) { - // ... - var oauthStrategy = - new OAuth2Strategy('github', OAUTH2_CONFIG, oauth2Verifier(app.service('users'))); + auth.strategies['github'] = OAuth2Strategy( + options, + authorizationEndpoint, + tokenEndpoint, + yourVerifier, + + // This function is called when an error occurs, or the user REJECTS the request. + (e, req, res) async { + res.write('Ooops: $e'); + await res.close(); + }, + ); } ``` @@ -74,7 +82,7 @@ a popup window. In this case, use `confirmPopupAuthentication`, which is bundled ```dart configureServer(Angel app) async { // ... - var auth = new AngelAuth(); + var auth = AngelAuth(); auth.strategies['github'] = oauth2Strategy; // Redirect @@ -83,7 +91,7 @@ configureServer(Angel app) async { // Callback app.get('/auth/github/callback', auth.authenticate( 'github', - new AngelAuthOptions(callback: confirmPopupAuthentication()) + AngelAuthOptions(callback: confirmPopupAuthentication()) )); // Connect the plug-in!!! @@ -94,14 +102,11 @@ configureServer(Angel app) async { ## Custom Scope Delimiter This package should work out-of-the-box for most OAuth2 providers, such as Github or Dropbox. However, if your OAuth2 scopes are separated by a delimiter other than the default (`' '`), -you can add it in the `AngelOAuth2Options` constructor: +you can add it in the `OAuth2Strategy` constructor: ```dart configureServer(Angel app) async { - var opts = const AngelOAuth2Options( - // ... - delimiter: ',' - ); + OAuth2Strategy(..., delimiter: ' '); } ``` @@ -113,7 +118,7 @@ You can add a `getParameters` callback to parse the contents of any arbitrary response: ```dart -var opts = const AngelOAuth2Options( +OAuth2Strategy( // ... getParameters: (contentType, body) { if (contentType.type == 'application') { @@ -122,7 +127,7 @@ var opts = const AngelOAuth2Options( else if (contentType.subtype == 'json') return JSON.decode(body); } - throw new FormatException('Invalid content-type $contentType; expected application/x-www-form-urlencoded or application/json.'); + throw FormatException('Invalid content-type $contentType; expected application/x-www-form-urlencoded or application/json.'); } ); ``` \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index eae1e42a..c230cee7 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,3 +1,4 @@ +include: package:pedantic/analysis_options.yaml analyzer: strong-mode: implicit-casts: false \ No newline at end of file diff --git a/example/main.dart b/example/main.dart index 95c98930..123cd0e4 100644 --- a/example/main.dart +++ b/example/main.dart @@ -1,97 +1,127 @@ import 'dart:convert'; -import 'dart:io'; import 'package:angel_auth/angel_auth.dart'; import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_framework/http.dart'; import 'package:angel_auth_oauth2/angel_auth_oauth2.dart'; +import 'package:http_parser/http_parser.dart'; import 'package:logging/logging.dart'; -import 'package:oauth2/oauth2.dart' as oauth2; -final AngelAuthOAuth2Options oAuth2Config = new AngelAuthOAuth2Options( - callback: 'http://localhost:3000/auth/github/callback', - key: '6caeaf5d4c04936ec34f', - secret: '178360518cf9de4802e2346a4b6ebec525dc4427', - authorizationEndpoint: 'http://github.com/login/oauth/authorize', - tokenEndpoint: 'https://github.com/login/oauth/access_token', - getParameters: (contentType, body) { - if (contentType.type == 'application') { - if (contentType.subtype == 'x-www-form-urlencoded') - return Uri.splitQueryString(body); - else if (contentType.subtype == 'json') - return (json.decode(body) as Map).cast(); - } +var authorizationEndpoint = + Uri.parse('http://github.com/login/oauth/authorize'); - throw new FormatException( - 'Invalid content-type $contentType; expected application/x-www-form-urlencoded or application/json.'); - }); +var tokenEndpoint = Uri.parse('https://github.com/login/oauth/access_token'); + +var options = ExternalAuthOptions( + clientId: '6caeaf5d4c04936ec34f', + clientSecret: '178360518cf9de4802e2346a4b6ebec525dc4427', + redirectUri: Uri.parse('http://localhost:3000/auth/github/callback'), +); + +/// Github doesn't properly follow the OAuth2 spec, so here's logic to parse their response. +Map parseParamsFromGithub(MediaType contentType, String body) { + if (contentType.type == 'application') { + if (contentType.subtype == 'x-www-form-urlencoded') + return Uri.splitQueryString(body); + else if (contentType.subtype == 'json') + return (json.decode(body) as Map).cast(); + } + + throw FormatException( + 'Invalid content-type $contentType; expected application/x-www-form-urlencoded or application/json.'); +} main() async { - var app = new Angel(); - app.use('/users', new MapService()); + // Create the server instance. + var app = Angel(); + var http = AngelHttp(app); + app.logger = Logger('angel') + ..onRecord.listen((rec) { + print(rec); + if (rec.error != null) print(rec.error); + if (rec.stackTrace != null) print(rec.stackTrace); + }); + // Create a service that stores user data. + var userService = app.use('/users', MapService()).inner; + var mappedUserService = userService.map(User.parse, User.serialize); + + // Set up the authenticator plugin. var auth = - new AngelAuth(jwtKey: 'oauth2 example secret', allowCookie: false); + AngelAuth(jwtKey: 'oauth2 example secret', allowCookie: false); + auth.serializer = (user) async => user.id; + auth.deserializer = (id) => mappedUserService.read(id.toString()); + app.fallback(auth.decodeJwt); - auth.deserializer = - (id) => app.service('users').read(id).then((u) => User.parse(u as Map)); + /// Create an instance of the strategy class. + auth.strategies['github'] = OAuth2Strategy( + options, + authorizationEndpoint, + tokenEndpoint, - auth.serializer = (User user) async => user.id; - - auth.strategies['github'] = new OAuth2Strategy( - oAuth2Config, - (oauth2.Client client) async { + // This function is called when the user ACCEPTS the request to sign in with Github. + (client, req, res) async { var response = await client.get('https://api.github.com/user'); var ghUser = json.decode(response.body); - var id = ghUser['id']; + var id = ghUser['id'] as int; - Iterable matchingUsers = await app.service('users').index({ - 'query': {'githubId': id} + var matchingUsers = await mappedUserService.index({ + 'query': {'github_id': id} }); if (matchingUsers.isNotEmpty) { - // Return the corresponding user, if it exists - return User.parse(matchingUsers.firstWhere((u) => u['githubId'] == id)); + // Return the corresponding user, if it exists. + return matchingUsers.first; } else { // Otherwise,create a user - return await app - .service('users') - .create({'githubId': id}).then((u) => User.parse(u as Map)); + return await mappedUserService.create(User(githubId: id)); } }, + + // This function is called when an error occurs, or the user REJECTS the request. + (e, req, res) async { + res.write('Ooops: $e'); + await res.close(); + }, + + // We have to pass this parser function when working with Github. + getParameters: parseParamsFromGithub, ); + // Mount some routes app.get('/auth/github', auth.authenticate('github')); app.get( '/auth/github/callback', auth.authenticate('github', - new AngelAuthOptions(callback: (req, res, jwt) async { + AngelAuthOptions(callback: (req, res, jwt) async { // In real-life, you might include a pop-up callback script. // // Use `confirmPopupAuthentication`, which is bundled with // `package:angel_auth`. + var user = req.container.make(); + res.write('Your user info: ${user.toJson()}\n\n'); res.write('Your JWT: $jwt'); + await res.close(); }))); - await app.configure(auth.configureServer); - - app.logger = new Logger('angel')..onRecord.listen(print); - - var http = new AngelHttp(app); - var server = await http.startServer(InternetAddress.loopbackIPv4, 3000); - var url = 'http://${server.address.address}:${server.port}'; - print('Listening on $url'); - print('View user listing: $url/users'); - print('Sign in via Github: $url/auth/github'); + // Start listening. + await http.startServer('127.0.0.1', 3000); + print('Listening on ${http.uri}'); + print('View user listing: ${http.uri}/users'); + print('Sign in via Github: ${http.uri}/auth/github'); } class User extends Model { @override String id; + int githubId; User({this.id, this.githubId}); static User parse(Map map) => - new User(id: map['id'] as String, githubId: map['github_id'] as int); + User(id: map['id'] as String, githubId: map['github_id'] as int); + + static Map serialize(User user) => user.toJson(); Map toJson() => {'id': id, 'github_id': githubId}; } diff --git a/lib/angel_auth_oauth2.dart b/lib/angel_auth_oauth2.dart index b24d5895..2583d54e 100644 --- a/lib/angel_auth_oauth2.dart +++ b/lib/angel_auth_oauth2.dart @@ -3,123 +3,85 @@ library angel_auth_oauth2; import 'dart:async'; import 'package:angel_auth/angel_auth.dart'; import 'package:angel_framework/angel_framework.dart'; -import 'package:angel_validate/angel_validate.dart'; import 'package:http_parser/http_parser.dart'; import 'package:oauth2/oauth2.dart' as oauth2; -final Validator OAUTH2_OPTIONS_SCHEMA = new Validator({ - 'key*': isString, - 'secret*': isString, - 'authorizationEndpoint*': anyOf(isString, const TypeMatcher()), - 'tokenEndpoint*': anyOf(isString, const TypeMatcher()), - 'callback*': isString, - 'scopes': const TypeMatcher>() -}, defaultValues: { - 'scopes': [] -}, customErrorMessages: { - 'scopes': "'scopes' must be an Iterable of strings. You provided: {{value}}" -}); - -/// Holds credentials and also specifies the means of authenticating users against a remote server. -class AngelAuthOAuth2Options { - /// Your application's client key or client ID, registered with the remote server. - final String key; - - /// Your application's client secret, registered with the remote server. - final String secret; - - /// The remote endpoint that prompts external users for authentication credentials. - final String authorizationEndpoint; - - /// The remote endpoint that exchanges auth codes for access tokens. - final String tokenEndpoint; - - /// 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 scopes; - - final Map Function(MediaType, String) getParameters; - - const AngelAuthOAuth2Options( - {this.key, - this.secret, - this.authorizationEndpoint, - this.tokenEndpoint, - this.callback, - this.delimiter: ' ', - this.scopes: const [], - this.getParameters}); - - factory AngelAuthOAuth2Options.fromJson(Map json) => - new AngelAuthOAuth2Options( - 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()?.toList() ?? - []); - - Map toJson() { - return { - 'key': key, - 'secret': secret, - 'authorizationEndpoint': authorizationEndpoint, - 'tokenEndpoint': tokenEndpoint, - 'callback': callback, - 'scopes': scopes.toList() - }; - } -} - +/// An Angel [AuthStrategy] that signs users in via a third-party service that speaks OAuth 2.0. class OAuth2Strategy implements AuthStrategy { - final FutureOr Function(oauth2.Client) verifier; + /// A callback that uses the third-party service to authenticate a [User]. + /// + /// As always, return `null` if authentication fails. + final FutureOr Function(oauth2.Client, RequestContext, ResponseContext) + verifier; - AngelAuthOAuth2Options _options; + /// A callback that is triggered when an OAuth2 error occurs (i.e. the user declines to login); + final FutureOr Function( + oauth2.AuthorizationException, RequestContext, ResponseContext) onError; - /// [options] can be either a `Map` or an instance of [AngelAuthOAuth2Options]. - OAuth2Strategy(options, this.verifier) { - if (options is AngelAuthOAuth2Options) - _options = options; - else if (options is Map) - _options = new AngelAuthOAuth2Options.fromJson( - OAUTH2_OPTIONS_SCHEMA.enforce(options)); - else - throw new ArgumentError('Invalid OAuth2 options: $options'); - } + /// The options defining how to connect to the third-party. + final ExternalAuthOptions options; - oauth2.AuthorizationCodeGrant createGrant() => - new oauth2.AuthorizationCodeGrant( - _options.key, - Uri.parse(_options.authorizationEndpoint), - Uri.parse(_options.tokenEndpoint), - secret: _options.secret, - delimiter: _options.delimiter ?? ' ', - getParameters: _options.getParameters); + /// 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 Function(MediaType, String) getParameters; + + /// An optional delimiter used to send requests to server who does not follow the OAuth 2.0 spec. + final String delimiter; + + Uri _redirect; + + OAuth2Strategy(this.options, this.authorizationEndpoint, this.tokenEndpoint, + this.verifier, this.onError, + {this.getParameters, this.delimiter = ' '}); + + oauth2.AuthorizationCodeGrant _createGrant() => + new oauth2.AuthorizationCodeGrant(options.clientId, authorizationEndpoint, + tokenEndpoint, + secret: options.clientSecret, + delimiter: delimiter, + getParameters: getParameters); @override FutureOr authenticate(RequestContext req, ResponseContext res, [AngelAuthOptions options]) async { - if (options != null) return authenticateCallback(req, res, options); + if (options != null) { + var result = await authenticateCallback(req, res, options); + if (result is User) + return result; + else + return null; + } - var grant = createGrant(); - res.redirect(grant - .getAuthorizationUrl(Uri.parse(_options.callback), - scopes: _options.scopes) - .toString()); + if (_redirect == null) { + var grant = _createGrant(); + _redirect = grant.getAuthorizationUrl( + this.options.redirectUri, + scopes: this.options.scopes, + ); + } + + res.redirect(_redirect); return null; } - Future authenticateCallback(RequestContext req, ResponseContext res, + /// The endpoint that is invoked by the third-party after successful authentication. + Future authenticateCallback(RequestContext req, ResponseContext res, [AngelAuthOptions options]) async { - var grant = createGrant(); - await grant.getAuthorizationUrl(Uri.parse(_options.callback), - scopes: _options.scopes); - var client = - await grant.handleAuthorizationResponse(req.uri.queryParameters); - return await verifier(client); + 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); + } } } diff --git a/pubspec.yaml b/pubspec.yaml index 5f28e246..2b1e6a3a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,8 +8,8 @@ homepage: https://github.com/angel-dart/auth_oauth2.git dependencies: angel_auth: ^2.0.0 angel_framework: ^2.0.0-alpha - angel_validate: ^2.0.0-alpha http_parser: ^3.0.0 oauth2: ^1.0.0 dev_dependencies: - logging: ^0.11.0 \ No newline at end of file + logging: ^0.11.0 + pedantic: ^1.0.0 \ No newline at end of file From 97bd61e7ee6736c8a871d6cc45a5203e07bb03e6 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 5 Jan 2019 19:43:31 -0500 Subject: [PATCH 13/13] Bump spec --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 2b1e6a3a..de9434de 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: angel_auth_oauth2 description: angel_auth strategy for OAuth2 login, i.e. Facebook, Github, etc. -version: 2.0.0+1 +version: 2.1.0 author: Tobe O environment: sdk: ">=2.0.0-dev <3.0.0"