Published oauth2, cache, cors, auth_oauth2
This commit is contained in:
parent
e79826bad0
commit
db49c82fc0
42 changed files with 488 additions and 431 deletions
12
packages/auth_oauth2/AUTHORS.md
Normal file
12
packages/auth_oauth2/AUTHORS.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
Primary Authors
|
||||||
|
===============
|
||||||
|
|
||||||
|
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
|
||||||
|
|
||||||
|
Thomas is the current maintainer of the code base. He has refactored and migrated the
|
||||||
|
code base to support NNBD.
|
||||||
|
|
||||||
|
* __[Tobe O](thosakwe@gmail.com)__
|
||||||
|
|
||||||
|
Tobe has written much of the original code prior to NNBD migration. He has moved on and
|
||||||
|
is no longer involved with the project.
|
|
@ -1,3 +1,9 @@
|
||||||
|
# 4.0.0
|
||||||
|
* Migrated to support Dart SDK 2.12.x NNBD
|
||||||
|
|
||||||
|
# 3.0.0
|
||||||
|
* Migrated to work with Dart SDK 2.12.x Non NNBD
|
||||||
|
|
||||||
# 2.1.0
|
# 2.1.0
|
||||||
* Angel 2 + Dart 2 update
|
* Angel 2 + Dart 2 update
|
||||||
* Support for handling errors + rejections.
|
* Support for handling errors + rejections.
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
# auth_oauth2
|
# angel3_auth_oauth2
|
||||||
|
[![version](https://img.shields.io/badge/pub-v4.0.0-brightgreen)](https://pub.dartlang.org/packages/angel3_auth_oauth2)
|
||||||
|
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
||||||
|
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
||||||
|
|
||||||
[![Pub](https://img.shields.io/pub/v/angel_auth_oauth2.svg)](https://pub.dartlang.org/packages/angel_auth_oauth2)
|
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/auth_oauth2/LICENSE)
|
||||||
|
|
||||||
`package:angel_auth` strategy for OAuth2 login, i.e. Facebook or Github.
|
`package:angel3_auth` strategy for OAuth2 login, i.e. Facebook or Github.
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
First, create an options object:
|
First, create an options object:
|
||||||
|
@ -77,7 +80,7 @@ Set up two routes:
|
||||||
|
|
||||||
In the case of the callback route, you may want to display an HTML page that closes
|
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
|
a popup window. In this case, use `confirmPopupAuthentication`, which is bundled with
|
||||||
`package:angel_auth`, as a `callback` function:
|
`package:angel3_auth`, as a `callback` function:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
configureServer(Angel app) async {
|
configureServer(Angel app) async {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:angel_auth/angel_auth.dart';
|
import 'package:angel3_auth/angel3_auth.dart';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:angel_framework/http.dart';
|
import 'package:angel3_framework/http.dart';
|
||||||
import 'package:angel_auth_oauth2/angel_auth_oauth2.dart';
|
import 'package:angel3_auth_oauth2/angel3_auth_oauth2.dart';
|
||||||
import 'package:http_parser/http_parser.dart';
|
import 'package:http_parser/http_parser.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ void main() async {
|
||||||
(client, req, res) async {
|
(client, req, res) async {
|
||||||
var response = await client.get(Uri.parse('https://api.github.com/user'));
|
var response = await client.get(Uri.parse('https://api.github.com/user'));
|
||||||
var ghUser = json.decode(response.body);
|
var ghUser = json.decode(response.body);
|
||||||
var id = ghUser['id'] as int;
|
var id = ghUser['id'] as int?;
|
||||||
|
|
||||||
var matchingUsers = await mappedUserService.index({
|
var matchingUsers = await mappedUserService.index({
|
||||||
'query': {'github_id': id}
|
'query': {'github_id': id}
|
||||||
|
@ -85,7 +85,7 @@ void main() async {
|
||||||
},
|
},
|
||||||
|
|
||||||
// We have to pass this parser function when working with Github.
|
// We have to pass this parser function when working with Github.
|
||||||
getParameters: parseParamsFromGithub,
|
//getParameters: parseParamsFromGithub,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mount some routes
|
// Mount some routes
|
||||||
|
@ -98,7 +98,7 @@ void main() async {
|
||||||
//
|
//
|
||||||
// Use `confirmPopupAuthentication`, which is bundled with
|
// Use `confirmPopupAuthentication`, which is bundled with
|
||||||
// `package:angel_auth`.
|
// `package:angel_auth`.
|
||||||
var user = req.container.make<User>();
|
var user = req.container!.make<User>()!;
|
||||||
res.write('Your user info: ${user.toJson()}\n\n');
|
res.write('Your user info: ${user.toJson()}\n\n');
|
||||||
res.write('Your JWT: $jwt');
|
res.write('Your JWT: $jwt');
|
||||||
await res.close();
|
await res.close();
|
||||||
|
@ -113,14 +113,14 @@ void main() async {
|
||||||
|
|
||||||
class User extends Model {
|
class User extends Model {
|
||||||
@override
|
@override
|
||||||
String id;
|
String? id;
|
||||||
|
|
||||||
int githubId;
|
int? githubId;
|
||||||
|
|
||||||
User({this.id, this.githubId});
|
User({this.id, this.githubId});
|
||||||
|
|
||||||
static User parse(Map map) =>
|
static User parse(Map map) =>
|
||||||
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<String, dynamic> serialize(User user) => user.toJson();
|
static Map<String, dynamic> serialize(User user) => user.toJson();
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
library angel_auth_oauth2;
|
library angel3_auth_oauth2;
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:angel_auth/angel_auth.dart';
|
import 'package:angel3_auth/angel3_auth.dart';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:http_parser/http_parser.dart';
|
import 'package:http_parser/http_parser.dart';
|
||||||
import 'package:oauth2/oauth2.dart' as oauth2;
|
import 'package:oauth2/oauth2.dart' as oauth2;
|
||||||
|
|
||||||
|
@ -28,34 +28,34 @@ class OAuth2Strategy<User> implements AuthStrategy<User> {
|
||||||
final Uri tokenEndpoint;
|
final Uri tokenEndpoint;
|
||||||
|
|
||||||
/// An optional callback used to parse the response from a server who does not follow the OAuth 2.0 spec.
|
/// An optional callback used to parse the response from a server who does not follow the OAuth 2.0 spec.
|
||||||
final Map<String, dynamic> Function(MediaType, String) getParameters;
|
final Map<String, dynamic> Function(MediaType?, String)? getParameters;
|
||||||
|
|
||||||
/// An optional delimiter used to send requests to server who does not follow the OAuth 2.0 spec.
|
/// An optional delimiter used to send requests to server who does not follow the OAuth 2.0 spec.
|
||||||
final String delimiter;
|
final String delimiter;
|
||||||
|
|
||||||
Uri _redirect;
|
Uri? _redirect;
|
||||||
|
|
||||||
OAuth2Strategy(this.options, this.authorizationEndpoint, this.tokenEndpoint,
|
OAuth2Strategy(this.options, this.authorizationEndpoint, this.tokenEndpoint,
|
||||||
this.verifier, this.onError,
|
this.verifier, this.onError,
|
||||||
{this.getParameters, this.delimiter = ' '});
|
{this.getParameters, this.delimiter = ' '});
|
||||||
|
|
||||||
oauth2.AuthorizationCodeGrant _createGrant() =>
|
oauth2.AuthorizationCodeGrant _createGrant() => oauth2.AuthorizationCodeGrant(
|
||||||
new oauth2.AuthorizationCodeGrant(options.clientId, authorizationEndpoint,
|
options.clientId, authorizationEndpoint, tokenEndpoint,
|
||||||
tokenEndpoint,
|
|
||||||
secret: options.clientSecret,
|
secret: options.clientSecret,
|
||||||
delimiter: delimiter,
|
delimiter: delimiter,
|
||||||
getParameters: getParameters);
|
getParameters: getParameters);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<User> authenticate(RequestContext req, ResponseContext res,
|
FutureOr<User?> authenticate(RequestContext req, ResponseContext res,
|
||||||
[AngelAuthOptions<User> options]) async {
|
[AngelAuthOptions<User>? options]) async {
|
||||||
if (options != null) {
|
if (options != null) {
|
||||||
var result = await authenticateCallback(req, res, options);
|
var result = await authenticateCallback(req, res, options);
|
||||||
if (result is User)
|
if (result is User) {
|
||||||
return result;
|
return result;
|
||||||
else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_redirect == null) {
|
if (_redirect == null) {
|
||||||
var grant = _createGrant();
|
var grant = _createGrant();
|
||||||
|
@ -65,20 +65,20 @@ class OAuth2Strategy<User> implements AuthStrategy<User> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.redirect(_redirect);
|
await res.redirect(_redirect);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The endpoint that is invoked by the third-party after successful authentication.
|
/// The endpoint that is invoked by the third-party after successful authentication.
|
||||||
Future<dynamic> authenticateCallback(RequestContext req, ResponseContext res,
|
Future<dynamic> authenticateCallback(RequestContext req, ResponseContext res,
|
||||||
[AngelAuthOptions options]) async {
|
[AngelAuthOptions? options]) async {
|
||||||
var grant = _createGrant();
|
var grant = _createGrant();
|
||||||
grant.getAuthorizationUrl(this.options.redirectUri,
|
grant.getAuthorizationUrl(this.options.redirectUri,
|
||||||
scopes: this.options.scopes);
|
scopes: this.options.scopes);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var client =
|
var client =
|
||||||
await grant.handleAuthorizationResponse(req.uri.queryParameters);
|
await grant.handleAuthorizationResponse(req.uri!.queryParameters);
|
||||||
return await verifier(client, req, res);
|
return await verifier(client, req, res);
|
||||||
} on oauth2.AuthorizationException catch (e) {
|
} on oauth2.AuthorizationException catch (e) {
|
||||||
return await onError(e, req, res);
|
return await onError(e, req, res);
|
|
@ -1,24 +1,14 @@
|
||||||
name: angel_auth_oauth2
|
name: angel3_auth_oauth2
|
||||||
|
version: 4.0.0
|
||||||
description: angel_auth strategy for OAuth2 login, i.e. Facebook, Github, etc.
|
description: angel_auth strategy for OAuth2 login, i.e. Facebook, Github, etc.
|
||||||
version: 3.0.0
|
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/auth_oauth2
|
||||||
#author: Tobe O <thosakwe@gmail.com>
|
|
||||||
publish_to: none
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.10.0 <3.0.0"
|
sdk: '>=2.12.0 <3.0.0'
|
||||||
homepage: https://github.com/angel-dart/auth_oauth2.git
|
|
||||||
dependencies:
|
dependencies:
|
||||||
angel_auth:
|
angel3_auth: ^4.0.0
|
||||||
git:
|
angel3_framework: ^4.0.0
|
||||||
url: https://github.com/dukefirehawk/angel.git
|
|
||||||
ref: sdk-2.12.x
|
|
||||||
path: packages/auth
|
|
||||||
angel_framework:
|
|
||||||
git:
|
|
||||||
url: https://github.com/dukefirehawk/angel.git
|
|
||||||
ref: sdk-2.12.x
|
|
||||||
path: packages/framework
|
|
||||||
http_parser: ^4.0.0
|
http_parser: ^4.0.0
|
||||||
oauth2: ^2.0.0
|
oauth2: ^2.0.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
logging: ^1.0.0
|
logging: ^1.0.1
|
||||||
pedantic: ^1.0.0
|
pedantic: ^1.11.0
|
12
packages/cache/AUTHORS.md
vendored
Normal file
12
packages/cache/AUTHORS.md
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
Primary Authors
|
||||||
|
===============
|
||||||
|
|
||||||
|
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
|
||||||
|
|
||||||
|
Thomas is the current maintainer of the code base. He has refactored and migrated the
|
||||||
|
code base to support NNBD.
|
||||||
|
|
||||||
|
* __[Tobe O](thosakwe@gmail.com)__
|
||||||
|
|
||||||
|
Tobe has written much of the original code prior to NNBD migration. He has moved on and
|
||||||
|
is no longer involved with the project.
|
6
packages/cache/CHANGELOG.md
vendored
6
packages/cache/CHANGELOG.md
vendored
|
@ -1,3 +1,9 @@
|
||||||
|
# 4.0.0
|
||||||
|
* Migrated to support Dart SDK 2.12.x NNBD
|
||||||
|
|
||||||
|
# 3.0.0
|
||||||
|
* Migrated to work with Dart SDK 2.12.x Non NNBD
|
||||||
|
|
||||||
# 2.0.1
|
# 2.0.1
|
||||||
* Add `ignoreQueryAndFragment` to `ResponseCache`.
|
* Add `ignoreQueryAndFragment` to `ResponseCache`.
|
||||||
* Rename `CacheService.ignoreQuery` to `ignoreParams`.
|
* Rename `CacheService.ignoreQuery` to `ignoreParams`.
|
||||||
|
|
27
packages/cache/README.md
vendored
27
packages/cache/README.md
vendored
|
@ -1,6 +1,9 @@
|
||||||
# cache
|
# angel3_cache
|
||||||
[![Pub](https://img.shields.io/pub/v/angel_cache.svg)](https://pub.dartlang.org/packages/angel_cache)
|
[![version](https://img.shields.io/badge/pub-v4.0.0-brightgreen)](https://pub.dartlang.org/packages/angel3_cache)
|
||||||
[![build status](https://travis-ci.org/angel-dart/cache.svg)](https://travis-ci.org/angel-dart/cache)
|
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
||||||
|
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
||||||
|
|
||||||
|
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/cache/LICENSE)
|
||||||
|
|
||||||
Support for server-side caching in [Angel](https://angel-dart.github.io).
|
Support for server-side caching in [Angel](https://angel-dart.github.io).
|
||||||
|
|
||||||
|
@ -17,13 +20,13 @@ This can improve the performance of sending objects that are complex to serializ
|
||||||
You can pass a [shouldCache] callback to determine which values should be cached.
|
You can pass a [shouldCache] callback to determine which values should be cached.
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
main() async {
|
void main() async {
|
||||||
var app = new Angel()..lazyParseBodies = true;
|
var app = Angel()..lazyParseBodies = true;
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
'/api/todos',
|
'/api/todos',
|
||||||
new CacheService(
|
CacheService(
|
||||||
database: new AnonymousService(
|
database: AnonymousService(
|
||||||
index: ([params]) {
|
index: ([params]) {
|
||||||
print('Fetched directly from the underlying service at ${new DateTime.now()}!');
|
print('Fetched directly from the underlying service at ${new DateTime.now()}!');
|
||||||
return ['foo', 'bar', 'baz'];
|
return ['foo', 'bar', 'baz'];
|
||||||
|
@ -51,10 +54,10 @@ To initialize a simple cache:
|
||||||
```dart
|
```dart
|
||||||
Future configureServer(Angel app) async {
|
Future configureServer(Angel app) async {
|
||||||
// Simple instance.
|
// Simple instance.
|
||||||
var cache = new ResponseCache();
|
var cache = ResponseCache();
|
||||||
|
|
||||||
// You can also pass an invalidation timeout.
|
// You can also pass an invalidation timeout.
|
||||||
var cache = new ResponseCache(timeout: const Duration(days: 2));
|
var cache = ResponseCache(timeout: const Duration(days: 2));
|
||||||
|
|
||||||
// Close the cache when the application closes.
|
// Close the cache when the application closes.
|
||||||
app.shutdownHooks.add((_) => cache.close());
|
app.shutdownHooks.add((_) => cache.close());
|
||||||
|
@ -62,8 +65,8 @@ Future configureServer(Angel app) async {
|
||||||
// Use `patterns` to specify which resources should be cached.
|
// Use `patterns` to specify which resources should be cached.
|
||||||
cache.patterns.addAll([
|
cache.patterns.addAll([
|
||||||
'robots.txt',
|
'robots.txt',
|
||||||
new RegExp(r'\.(png|jpg|gif|txt)$'),
|
RegExp(r'\.(png|jpg|gif|txt)$'),
|
||||||
new Glob('public/**/*'),
|
Glob('public/**/*'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// REQUIRED: The middleware that serves cached responses
|
// REQUIRED: The middleware that serves cached responses
|
||||||
|
@ -85,7 +88,7 @@ to ensure no arbitrary attacker can hack your cache:
|
||||||
Future configureServer(Angel app) async {
|
Future configureServer(Angel app) async {
|
||||||
app.addRoute('PURGE', '*', (req, res) {
|
app.addRoute('PURGE', '*', (req, res) {
|
||||||
if (req.ip != '127.0.0.1')
|
if (req.ip != '127.0.0.1')
|
||||||
throw new AngelHttpException.forbidden();
|
throw AngelHttpException.forbidden();
|
||||||
return cache.purge(req.uri.path);
|
return cache.purge(req.uri.path);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
8
packages/cache/example/cache_service.dart
vendored
8
packages/cache/example/cache_service.dart
vendored
|
@ -1,6 +1,6 @@
|
||||||
import 'package:angel_cache/angel_cache.dart';
|
import 'package:angel3_cache/angel3_cache.dart';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:angel_framework/http.dart';
|
import 'package:angel3_framework/http.dart';
|
||||||
|
|
||||||
main() async {
|
main() async {
|
||||||
var app = Angel();
|
var app = Angel();
|
||||||
|
@ -13,7 +13,7 @@ main() async {
|
||||||
print(
|
print(
|
||||||
'Fetched directly from the underlying service at ${new DateTime.now()}!');
|
'Fetched directly from the underlying service at ${new DateTime.now()}!');
|
||||||
return ['foo', 'bar', 'baz'];
|
return ['foo', 'bar', 'baz'];
|
||||||
}, read: (id, [params]) {
|
}, read: (dynamic id, [params]) {
|
||||||
return {id: '$id at ${new DateTime.now()}'};
|
return {id: '$id at ${new DateTime.now()}'};
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
10
packages/cache/example/main.dart
vendored
10
packages/cache/example/main.dart
vendored
|
@ -1,6 +1,6 @@
|
||||||
import 'package:angel_cache/angel_cache.dart';
|
import 'package:angel3_cache/angel3_cache.dart';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:angel_framework/http.dart';
|
import 'package:angel3_framework/http.dart';
|
||||||
import 'package:glob/glob.dart';
|
import 'package:glob/glob.dart';
|
||||||
|
|
||||||
main() async {
|
main() async {
|
||||||
|
@ -23,8 +23,8 @@ main() async {
|
||||||
app.addRoute('PURGE', '*', (req, res) {
|
app.addRoute('PURGE', '*', (req, res) {
|
||||||
if (req.ip != '127.0.0.1') throw AngelHttpException.forbidden();
|
if (req.ip != '127.0.0.1') throw AngelHttpException.forbidden();
|
||||||
|
|
||||||
cache.purge(req.uri.path);
|
cache.purge(req.uri!.path);
|
||||||
print('Purged ${req.uri.path}');
|
print('Purged ${req.uri!.path}');
|
||||||
});
|
});
|
||||||
|
|
||||||
// The response finalizer that actually saves the content
|
// The response finalizer that actually saves the content
|
||||||
|
|
30
packages/cache/lib/src/cache.dart
vendored
30
packages/cache/lib/src/cache.dart
vendored
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io' show HttpDate;
|
import 'dart:io' show HttpDate;
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:pool/pool.dart';
|
import 'package:pool/pool.dart';
|
||||||
|
|
||||||
/// A flexible response cache for Angel.
|
/// A flexible response cache for Angel.
|
||||||
|
@ -14,7 +14,7 @@ class ResponseCache {
|
||||||
final List<Pattern> patterns = [];
|
final List<Pattern> patterns = [];
|
||||||
|
|
||||||
/// An optional timeout, after which a given response will be removed from the cache, and the contents refreshed.
|
/// An optional timeout, after which a given response will be removed from the cache, and the contents refreshed.
|
||||||
final Duration timeout;
|
final Duration? timeout;
|
||||||
|
|
||||||
final Map<String, _CachedResponse> _cache = {};
|
final Map<String, _CachedResponse> _cache = {};
|
||||||
final Map<String, Pool> _writeLocks = {};
|
final Map<String, Pool> _writeLocks = {};
|
||||||
|
@ -38,21 +38,21 @@ class ResponseCache {
|
||||||
Future<bool> ifModifiedSince(RequestContext req, ResponseContext res) async {
|
Future<bool> ifModifiedSince(RequestContext req, ResponseContext res) async {
|
||||||
if (req.method != 'GET' && req.method != 'HEAD') return true;
|
if (req.method != 'GET' && req.method != 'HEAD') return true;
|
||||||
|
|
||||||
if (req.headers.ifModifiedSince != null) {
|
if (req.headers!.ifModifiedSince != null) {
|
||||||
var modifiedSince = req.headers.ifModifiedSince;
|
var modifiedSince = req.headers!.ifModifiedSince;
|
||||||
|
|
||||||
// Check if there is a cache entry.
|
// Check if there is a cache entry.
|
||||||
for (var pattern in patterns) {
|
for (var pattern in patterns) {
|
||||||
if (pattern.allMatches(_getEffectivePath(req)).isNotEmpty &&
|
if (pattern.allMatches(_getEffectivePath(req)).isNotEmpty &&
|
||||||
_cache.containsKey(_getEffectivePath(req))) {
|
_cache.containsKey(_getEffectivePath(req))) {
|
||||||
var response = _cache[_getEffectivePath(req)];
|
var response = _cache[_getEffectivePath(req)]!;
|
||||||
//print('timestamp ${response.timestamp} vs since ${modifiedSince}');
|
//print('timestamp ${response.timestamp} vs since ${modifiedSince}');
|
||||||
|
|
||||||
if (response.timestamp.compareTo(modifiedSince) <= 0) {
|
if (response.timestamp.compareTo(modifiedSince!) <= 0) {
|
||||||
if (timeout != null) {
|
if (timeout != null) {
|
||||||
// If the cache timeout has been met, don't send the cached response.
|
// If the cache timeout has been met, don't send the cached response.
|
||||||
if (new DateTime.now().toUtc().difference(response.timestamp) >=
|
if (new DateTime.now().toUtc().difference(response.timestamp) >=
|
||||||
timeout) return true;
|
timeout!) return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.statusCode = 304;
|
res.statusCode = 304;
|
||||||
|
@ -66,7 +66,7 @@ class ResponseCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getEffectivePath(RequestContext req) =>
|
String _getEffectivePath(RequestContext req) =>
|
||||||
ignoreQueryAndFragment == true ? req.uri.path : req.uri.toString();
|
ignoreQueryAndFragment == true ? req.uri!.path : req.uri.toString();
|
||||||
|
|
||||||
/// Serves content from the cache, if applicable.
|
/// Serves content from the cache, if applicable.
|
||||||
Future<bool> handleRequest(RequestContext req, ResponseContext res) async {
|
Future<bool> handleRequest(RequestContext req, ResponseContext res) async {
|
||||||
|
@ -77,7 +77,7 @@ class ResponseCache {
|
||||||
// Check if there is a cache entry.
|
// Check if there is a cache entry.
|
||||||
//
|
//
|
||||||
// If `if-modified-since` is present, this check has already been performed.
|
// If `if-modified-since` is present, this check has already been performed.
|
||||||
if (req.headers.ifModifiedSince == null) {
|
if (req.headers!.ifModifiedSince == null) {
|
||||||
for (var pattern in patterns) {
|
for (var pattern in patterns) {
|
||||||
if (pattern.allMatches(_getEffectivePath(req)).isNotEmpty) {
|
if (pattern.allMatches(_getEffectivePath(req)).isNotEmpty) {
|
||||||
var now = new DateTime.now().toUtc();
|
var now = new DateTime.now().toUtc();
|
||||||
|
@ -87,10 +87,10 @@ class ResponseCache {
|
||||||
|
|
||||||
if (timeout != null) {
|
if (timeout != null) {
|
||||||
// If the cache timeout has been met, don't send the cached response.
|
// If the cache timeout has been met, don't send the cached response.
|
||||||
if (now.difference(response.timestamp) >= timeout) return true;
|
if (now.difference(response!.timestamp) >= timeout!) return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_setCachedHeaders(response.timestamp, req, res);
|
_setCachedHeaders(response!.timestamp, req, res);
|
||||||
res
|
res
|
||||||
..headers.addAll(response.headers)
|
..headers.addAll(response.headers)
|
||||||
..add(response.body)
|
..add(response.body)
|
||||||
|
@ -123,8 +123,8 @@ class ResponseCache {
|
||||||
if (timeout == null) return true;
|
if (timeout == null) return true;
|
||||||
|
|
||||||
// Otherwise, don't invalidate unless the timeout has been exceeded.
|
// Otherwise, don't invalidate unless the timeout has been exceeded.
|
||||||
var response = _cache[_getEffectivePath(req)];
|
var response = _cache[_getEffectivePath(req)]!;
|
||||||
if (now.difference(response.timestamp) < timeout) return true;
|
if (now.difference(response.timestamp) < timeout!) return true;
|
||||||
|
|
||||||
// If the cache entry should be invalidated, then invalidate it.
|
// If the cache entry should be invalidated, then invalidate it.
|
||||||
purge(_getEffectivePath(req));
|
purge(_getEffectivePath(req));
|
||||||
|
@ -135,7 +135,7 @@ class ResponseCache {
|
||||||
_writeLocks.putIfAbsent(_getEffectivePath(req), () => new Pool(1));
|
_writeLocks.putIfAbsent(_getEffectivePath(req), () => new Pool(1));
|
||||||
await writeLock.withResource(() {
|
await writeLock.withResource(() {
|
||||||
_cache[_getEffectivePath(req)] = new _CachedResponse(
|
_cache[_getEffectivePath(req)] = new _CachedResponse(
|
||||||
new Map.from(res.headers), res.buffer.toBytes(), now);
|
new Map.from(res.headers), res.buffer!.toBytes(), now);
|
||||||
});
|
});
|
||||||
|
|
||||||
_setCachedHeaders(now, req, res);
|
_setCachedHeaders(now, req, res);
|
||||||
|
@ -154,7 +154,7 @@ class ResponseCache {
|
||||||
..['last-modified'] = HttpDate.format(modified);
|
..['last-modified'] = HttpDate.format(modified);
|
||||||
|
|
||||||
if (timeout != null) {
|
if (timeout != null) {
|
||||||
var expiry = new DateTime.now().add(timeout);
|
var expiry = new DateTime.now().add(timeout!);
|
||||||
res.headers['expires'] = HttpDate.format(expiry);
|
res.headers['expires'] = HttpDate.format(expiry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
37
packages/cache/lib/src/cache_service.dart
vendored
37
packages/cache/lib/src/cache_service.dart
vendored
|
@ -1,7 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
|
|
||||||
/// An Angel [Service] that caches data from another service.
|
/// An Angel [Service] that caches data from another service.
|
||||||
///
|
///
|
||||||
|
@ -21,22 +20,20 @@ class CacheService<Id, Data> extends Service<Id, Data> {
|
||||||
/// If you want to return a cached result more-often-than-not, you may want to enable this.
|
/// If you want to return a cached result more-often-than-not, you may want to enable this.
|
||||||
final bool ignoreParams;
|
final bool ignoreParams;
|
||||||
|
|
||||||
final Duration timeout;
|
final Duration? timeout;
|
||||||
|
|
||||||
final Map<Id, _CachedItem<Data>> _cache = {};
|
final Map<Id, _CachedItem<Data>> _cache = {};
|
||||||
_CachedItem<List<Data>> _indexed;
|
_CachedItem<List<Data>>? _indexed;
|
||||||
|
|
||||||
CacheService(
|
CacheService(
|
||||||
{@required this.database,
|
{required this.database,
|
||||||
@required this.cache,
|
required this.cache,
|
||||||
this.ignoreParams: false,
|
this.ignoreParams: false,
|
||||||
this.timeout}) {
|
this.timeout}) {}
|
||||||
assert(database != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<T> _getCached<T>(
|
Future<T> _getCached<T>(
|
||||||
Map<String, dynamic> params,
|
Map<String, dynamic>? params,
|
||||||
_CachedItem get(),
|
_CachedItem? get(),
|
||||||
FutureOr<T> getFresh(),
|
FutureOr<T> getFresh(),
|
||||||
FutureOr<T> getCached(),
|
FutureOr<T> getCached(),
|
||||||
FutureOr<T> save(T data, DateTime now)) async {
|
FutureOr<T> save(T data, DateTime now)) async {
|
||||||
|
@ -46,7 +43,7 @@ class CacheService<Id, Data> extends Service<Id, Data> {
|
||||||
if (cached != null) {
|
if (cached != null) {
|
||||||
// If the entry has expired, don't send from the cache
|
// If the entry has expired, don't send from the cache
|
||||||
var expired =
|
var expired =
|
||||||
timeout != null && now.difference(cached.timestamp) >= timeout;
|
timeout != null && now.difference(cached.timestamp) >= timeout!;
|
||||||
|
|
||||||
if (timeout == null || !expired) {
|
if (timeout == null || !expired) {
|
||||||
// Read from the cache if necessary
|
// Read from the cache if necessary
|
||||||
|
@ -54,7 +51,7 @@ class CacheService<Id, Data> extends Service<Id, Data> {
|
||||||
(params != null &&
|
(params != null &&
|
||||||
cached.params != null &&
|
cached.params != null &&
|
||||||
const MapEquality().equals(
|
const MapEquality().equals(
|
||||||
params['query'] as Map, cached.params['query'] as Map));
|
params['query'] as Map?, cached.params['query'] as Map?));
|
||||||
if (queryEqual) {
|
if (queryEqual) {
|
||||||
return await getCached();
|
return await getCached();
|
||||||
}
|
}
|
||||||
|
@ -69,7 +66,7 @@ class CacheService<Id, Data> extends Service<Id, Data> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Data>> index([Map<String, dynamic> params]) {
|
Future<List<Data>> index([Map<String, dynamic>? params]) {
|
||||||
return _getCached(
|
return _getCached(
|
||||||
params,
|
params,
|
||||||
() => _indexed,
|
() => _indexed,
|
||||||
|
@ -83,7 +80,7 @@ class CacheService<Id, Data> extends Service<Id, Data> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Data> read(Id id, [Map<String, dynamic> params]) async {
|
Future<Data> read(Id id, [Map<String, dynamic>? params]) async {
|
||||||
return _getCached<Data>(
|
return _getCached<Data>(
|
||||||
params,
|
params,
|
||||||
() => _cache[id],
|
() => _cache[id],
|
||||||
|
@ -97,27 +94,27 @@ class CacheService<Id, Data> extends Service<Id, Data> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Data> create(data, [Map<String, dynamic> params]) {
|
Future<Data> create(data, [Map<String, dynamic>? params]) {
|
||||||
_indexed = null;
|
_indexed = null;
|
||||||
return database.create(data, params);
|
return database.create(data, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Data> modify(Id id, Data data, [Map<String, dynamic> params]) {
|
Future<Data> modify(Id id, Data data, [Map<String, dynamic>? params]) {
|
||||||
_indexed = null;
|
_indexed = null;
|
||||||
_cache.remove(id);
|
_cache.remove(id);
|
||||||
return database.modify(id, data, params);
|
return database.modify(id, data, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Data> update(Id id, Data data, [Map<String, dynamic> params]) {
|
Future<Data> update(Id id, Data data, [Map<String, dynamic>? params]) {
|
||||||
_indexed = null;
|
_indexed = null;
|
||||||
_cache.remove(id);
|
_cache.remove(id);
|
||||||
return database.modify(id, data, params);
|
return database.modify(id, data, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Data> remove(Id id, [Map<String, dynamic> params]) {
|
Future<Data> remove(Id id, [Map<String, dynamic>? params]) {
|
||||||
_indexed = null;
|
_indexed = null;
|
||||||
_cache.remove(id);
|
_cache.remove(id);
|
||||||
return database.remove(id, params);
|
return database.remove(id, params);
|
||||||
|
@ -127,7 +124,7 @@ class CacheService<Id, Data> extends Service<Id, Data> {
|
||||||
class _CachedItem<Data> {
|
class _CachedItem<Data> {
|
||||||
final params;
|
final params;
|
||||||
final DateTime timestamp;
|
final DateTime timestamp;
|
||||||
final Data data;
|
final Data? data;
|
||||||
|
|
||||||
_CachedItem(this.params, this.timestamp, [this.data]);
|
_CachedItem(this.params, this.timestamp, [this.data]);
|
||||||
|
|
||||||
|
|
8
packages/cache/lib/src/serializer.dart
vendored
8
packages/cache/lib/src/serializer.dart
vendored
|
@ -1,5 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
|
|
||||||
/// A middleware that enables the caching of response serialization.
|
/// A middleware that enables the caching of response serialization.
|
||||||
///
|
///
|
||||||
|
@ -7,8 +7,8 @@ import 'package:angel_framework/angel_framework.dart';
|
||||||
///
|
///
|
||||||
/// You can pass a [shouldCache] callback to determine which values should be cached.
|
/// You can pass a [shouldCache] callback to determine which values should be cached.
|
||||||
RequestHandler cacheSerializationResults(
|
RequestHandler cacheSerializationResults(
|
||||||
{Duration timeout,
|
{Duration? timeout,
|
||||||
FutureOr<bool> Function(RequestContext, ResponseContext, Object)
|
FutureOr<bool> Function(RequestContext, ResponseContext, Object)?
|
||||||
shouldCache}) {
|
shouldCache}) {
|
||||||
return (RequestContext req, ResponseContext res) async {
|
return (RequestContext req, ResponseContext res) async {
|
||||||
var oldSerializer = res.serializer;
|
var oldSerializer = res.serializer;
|
||||||
|
@ -20,7 +20,7 @@ RequestHandler cacheSerializationResults(
|
||||||
// return cache.putIfAbsent(value, () => oldSerializer(value));
|
// return cache.putIfAbsent(value, () => oldSerializer(value));
|
||||||
//}
|
//}
|
||||||
|
|
||||||
return oldSerializer(value);
|
return oldSerializer!(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
37
packages/cache/pubspec.yaml
vendored
37
packages/cache/pubspec.yaml
vendored
|
@ -1,26 +1,17 @@
|
||||||
name: angel_cache
|
name: angel3_cache
|
||||||
version: 3.0.0
|
version: 4.0.0
|
||||||
homepage: https://github.com/angel-dart/cache
|
description: Support for server-side caching in Angel3 Framework
|
||||||
description: Support for server-side caching in Angel.
|
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/cache
|
||||||
author: Tobe O <thosakwe@gmail.com>
|
|
||||||
publish_to: none
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.10.0 <3.0.0"
|
sdk: '>=2.12.0 <3.0.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
angel_framework:
|
angel3_framework: ^4.0.0
|
||||||
git:
|
collection: ^1.15.0
|
||||||
url: https://github.com/dukefirehawk/angel.git
|
meta: ^1.4.0
|
||||||
ref: sdk-2.12.x
|
pool: ^1.5.0
|
||||||
path: packages/framework
|
|
||||||
collection: ^1.0.0
|
|
||||||
meta: ^1.0.0
|
|
||||||
pool: ^1.0.0
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
angel_test:
|
angel3_test: ^4.0.0
|
||||||
git:
|
glob: ^2.0.1
|
||||||
url: https://github.com/dukefirehawk/angel.git
|
http: ^0.13.3
|
||||||
ref: sdk-2.12.x
|
test: ^1.17.5
|
||||||
path: packages/test
|
pedantic: ^1.11.0
|
||||||
glob: ^2.0.0
|
|
||||||
http: ^0.13.0
|
|
||||||
test: ^1.16.5
|
|
||||||
|
|
21
packages/cache/test/cache_test.dart
vendored
21
packages/cache/test/cache_test.dart
vendored
|
@ -1,17 +1,17 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:angel_cache/angel_cache.dart';
|
import 'package:angel3_cache/angel3_cache.dart';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:angel_test/angel_test.dart';
|
import 'package:angel3_test/angel3_test.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:glob/glob.dart';
|
import 'package:glob/glob.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
main() async {
|
main() async {
|
||||||
group('no timeout', () {
|
group('no timeout', () {
|
||||||
TestClient client;
|
late TestClient client;
|
||||||
DateTime lastModified;
|
late DateTime lastModified;
|
||||||
http.Response response1, response2;
|
late http.Response response1, response2;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
var app = new Angel();
|
var app = new Angel();
|
||||||
|
@ -29,8 +29,8 @@ main() async {
|
||||||
});
|
});
|
||||||
|
|
||||||
app.addRoute('PURGE', '*', (req, res) {
|
app.addRoute('PURGE', '*', (req, res) {
|
||||||
cache.purge(req.uri.path);
|
cache.purge(req.uri!.path);
|
||||||
print('Purged ${req.uri.path}');
|
print('Purged ${req.uri!.path}');
|
||||||
});
|
});
|
||||||
|
|
||||||
app.responseFinalizers.add(cache.responseFinalizer);
|
app.responseFinalizers.add(cache.responseFinalizer);
|
||||||
|
@ -38,14 +38,15 @@ main() async {
|
||||||
var oldHandler = app.errorHandler;
|
var oldHandler = app.errorHandler;
|
||||||
app.errorHandler = (e, req, res) {
|
app.errorHandler = (e, req, res) {
|
||||||
if (e.error == null) return oldHandler(e, req, res);
|
if (e.error == null) return oldHandler(e, req, res);
|
||||||
return Zone.current.handleUncaughtError(e.error, e.stackTrace);
|
return Zone.current
|
||||||
|
.handleUncaughtError(e.error as Object, e.stackTrace!);
|
||||||
};
|
};
|
||||||
|
|
||||||
client = await connectTo(app);
|
client = await connectTo(app);
|
||||||
response1 = await client.get(Uri.parse('/date.txt'));
|
response1 = await client.get(Uri.parse('/date.txt'));
|
||||||
response2 = await client.get(Uri.parse('/date.txt'));
|
response2 = await client.get(Uri.parse('/date.txt'));
|
||||||
print(response2.headers);
|
print(response2.headers);
|
||||||
lastModified = HttpDate.parse(response2.headers['last-modified']);
|
lastModified = HttpDate.parse(response2.headers['last-modified']!);
|
||||||
print('Response 1 status: ${response1.statusCode}');
|
print('Response 1 status: ${response1.statusCode}');
|
||||||
print('Response 2 status: ${response2.statusCode}');
|
print('Response 2 status: ${response2.statusCode}');
|
||||||
print('Response 1 body: ${response1.body}');
|
print('Response 1 body: ${response1.body}');
|
||||||
|
|
12
packages/cors/AUTHORS.md
Normal file
12
packages/cors/AUTHORS.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
Primary Authors
|
||||||
|
===============
|
||||||
|
|
||||||
|
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
|
||||||
|
|
||||||
|
Thomas is the current maintainer of the code base. He has refactored and migrated the
|
||||||
|
code base to support NNBD.
|
||||||
|
|
||||||
|
* __[Tobe O](thosakwe@gmail.com)__
|
||||||
|
|
||||||
|
Tobe has written much of the original code prior to NNBD migration. He has moved on and
|
||||||
|
is no longer involved with the project.
|
|
@ -1,2 +1,8 @@
|
||||||
|
# 4.0.0
|
||||||
|
* Migrated to support Dart SDK 2.12.x NNBD
|
||||||
|
|
||||||
|
# 3.0.0
|
||||||
|
* Migrated to work with Dart SDK 2.12.x Non NNBD
|
||||||
|
|
||||||
# 2.0.0
|
# 2.0.0
|
||||||
* Updates for Dart 2 and Angel 2.
|
* Updates for Dart 2 and Angel 2.
|
|
@ -1,6 +1,10 @@
|
||||||
# cors
|
# angel3_cors
|
||||||
[![Pub](https://img.shields.io/pub/v/angel_cors.svg)](https://pub.dartlang.org/packages/angel_cors)
|
[![version](https://img.shields.io/badge/pub-v4.0.0-brightgreen)](https://pub.dartlang.org/packages/angel3_cors)
|
||||||
[![build status](https://travis-ci.org/angel-dart/cors.svg)](https://travis-ci.org/angel-dart/cors)
|
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
||||||
|
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
||||||
|
|
||||||
|
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/cors/LICENSE)
|
||||||
|
|
||||||
|
|
||||||
Angel CORS middleware.
|
Angel CORS middleware.
|
||||||
Port of [the original Express CORS middleware](https://github.com/expressjs/cors).
|
Port of [the original Express CORS middleware](https://github.com/expressjs/cors).
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:angel_cors/angel_cors.dart';
|
import 'package:angel3_cors/angel3_cors.dart';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
|
|
||||||
Future configureServer(Angel app) async {
|
Future configureServer(Angel app) async {
|
||||||
// The default options will allow CORS for any request.
|
// The default options will allow CORS for any request.
|
||||||
|
@ -43,7 +43,7 @@ Future configureServer(Angel app) async {
|
||||||
app.fallback(dynamicCors((req, res) {
|
app.fallback(dynamicCors((req, res) {
|
||||||
return CorsOptions(
|
return CorsOptions(
|
||||||
origin: [
|
origin: [
|
||||||
req.headers.value('origin') ?? 'https://pub.dartlang.org',
|
req.headers!.value('origin') ?? 'https://pub.dartlang.org',
|
||||||
RegExp(r'\.com$'),
|
RegExp(r'\.com$'),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
/// Angel CORS middleware.
|
/// Angel CORS middleware.
|
||||||
library angel_cors;
|
library angel3_cors;
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'src/cors_options.dart';
|
import 'src/cors_options.dart';
|
||||||
export 'src/cors_options.dart';
|
export 'src/cors_options.dart';
|
||||||
|
|
||||||
/// Determines if a request origin is CORS-able.
|
/// Determines if a request origin is CORS-able.
|
||||||
typedef bool _CorsFilter(String origin);
|
typedef _CorsFilter = bool Function(String origin);
|
||||||
|
|
||||||
bool _isOriginAllowed(String origin, [allowedOrigin]) {
|
bool _isOriginAllowed(String? origin, [allowedOrigin]) {
|
||||||
allowedOrigin ??= [];
|
allowedOrigin ??= [];
|
||||||
if (allowedOrigin is Iterable) {
|
if (allowedOrigin is Iterable) {
|
||||||
return allowedOrigin.any((x) => _isOriginAllowed(origin, x));
|
return allowedOrigin.any((x) => _isOriginAllowed(origin, x));
|
||||||
|
@ -38,12 +38,12 @@ Future<bool> Function(RequestContext, ResponseContext) dynamicCors(
|
||||||
|
|
||||||
/// Applies the given [CorsOptions].
|
/// Applies the given [CorsOptions].
|
||||||
Future<bool> Function(RequestContext, ResponseContext) cors(
|
Future<bool> Function(RequestContext, ResponseContext) cors(
|
||||||
[CorsOptions options]) {
|
[CorsOptions? options]) {
|
||||||
options ??= CorsOptions();
|
options ??= CorsOptions();
|
||||||
|
|
||||||
return (req, res) async {
|
return (req, res) async {
|
||||||
// access-control-allow-credentials
|
// access-control-allow-credentials
|
||||||
if (options.credentials == true) {
|
if (options!.credentials == true) {
|
||||||
res.headers['access-control-allow-credentials'] = 'true';
|
res.headers['access-control-allow-credentials'] = 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,9 +51,9 @@ Future<bool> Function(RequestContext, ResponseContext) cors(
|
||||||
if (req.method == 'OPTIONS' && options.allowedHeaders.isNotEmpty) {
|
if (req.method == 'OPTIONS' && options.allowedHeaders.isNotEmpty) {
|
||||||
res.headers['access-control-allow-headers'] =
|
res.headers['access-control-allow-headers'] =
|
||||||
options.allowedHeaders.join(',');
|
options.allowedHeaders.join(',');
|
||||||
} else if (req.headers['access-control-request-headers'] != null) {
|
} else if (req.headers!['access-control-request-headers'] != null) {
|
||||||
res.headers['access-control-allow-headers'] =
|
res.headers['access-control-allow-headers'] =
|
||||||
req.headers.value('access-control-request-headers');
|
req.headers!.value('access-control-request-headers')!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// access-control-expose-headers
|
// access-control-expose-headers
|
||||||
|
@ -80,11 +80,11 @@ Future<bool> Function(RequestContext, ResponseContext) cors(
|
||||||
..headers['access-control-allow-origin'] = options.origin as String
|
..headers['access-control-allow-origin'] = options.origin as String
|
||||||
..headers['vary'] = 'origin';
|
..headers['vary'] = 'origin';
|
||||||
} else {
|
} else {
|
||||||
bool isAllowed =
|
var isAllowed =
|
||||||
_isOriginAllowed(req.headers.value('origin'), options.origin);
|
_isOriginAllowed(req.headers!.value('origin'), options.origin);
|
||||||
|
|
||||||
res.headers['access-control-allow-origin'] =
|
res.headers['access-control-allow-origin'] =
|
||||||
isAllowed ? req.headers.value('origin') : false.toString();
|
isAllowed ? req.headers!.value('origin')! : false.toString();
|
||||||
|
|
||||||
if (isAllowed) {
|
if (isAllowed) {
|
||||||
res.headers['vary'] = 'origin';
|
res.headers['vary'] = 'origin';
|
||||||
|
@ -92,7 +92,8 @@ Future<bool> Function(RequestContext, ResponseContext) cors(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.method != 'OPTIONS') return true;
|
if (req.method != 'OPTIONS') return true;
|
||||||
res.statusCode = options.successStatus ?? 204;
|
//res.statusCode = options.successStatus ?? 204;
|
||||||
|
res.statusCode = options.successStatus;
|
||||||
res.contentLength = 0;
|
res.contentLength = 0;
|
||||||
await res.close();
|
await res.close();
|
||||||
return options.preflightContinue;
|
return options.preflightContinue;
|
|
@ -22,7 +22,7 @@ class CorsOptions {
|
||||||
/// Configures the **Access-Control-Max-Age** CORS header. Set to an integer to pass the header, otherwise it is omitted.
|
/// Configures the **Access-Control-Max-Age** CORS header. Set to an integer to pass the header, otherwise it is omitted.
|
||||||
///
|
///
|
||||||
/// Default: `null`
|
/// Default: `null`
|
||||||
final int maxAge;
|
final int? maxAge;
|
||||||
|
|
||||||
/// The status code to be sent on successful `OPTIONS` requests, if [preflightContinue] is `false`.
|
/// The status code to be sent on successful `OPTIONS` requests, if [preflightContinue] is `false`.
|
||||||
final int successStatus;
|
final int successStatus;
|
||||||
|
@ -50,7 +50,7 @@ class CorsOptions {
|
||||||
|
|
||||||
CorsOptions(
|
CorsOptions(
|
||||||
{Iterable<String> allowedHeaders = const [],
|
{Iterable<String> allowedHeaders = const [],
|
||||||
this.credentials,
|
this.credentials = false,
|
||||||
this.maxAge,
|
this.maxAge,
|
||||||
Iterable<String> methods = const [
|
Iterable<String> methods = const [
|
||||||
'GET',
|
'GET',
|
||||||
|
@ -64,10 +64,10 @@ class CorsOptions {
|
||||||
this.successStatus = 204,
|
this.successStatus = 204,
|
||||||
this.preflightContinue = false,
|
this.preflightContinue = false,
|
||||||
Iterable<String> exposedHeaders = const []}) {
|
Iterable<String> exposedHeaders = const []}) {
|
||||||
if (allowedHeaders != null) this.allowedHeaders.addAll(allowedHeaders);
|
this.allowedHeaders.addAll(allowedHeaders);
|
||||||
|
|
||||||
if (methods != null) this.methods.addAll(methods);
|
this.methods.addAll(methods);
|
||||||
|
|
||||||
if (exposedHeaders != null) this.exposedHeaders.addAll(exposedHeaders);
|
this.exposedHeaders.addAll(exposedHeaders);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,13 @@
|
||||||
author: Tobe O <thosakwe@gmail.com>
|
name: angel3_cors
|
||||||
|
version: 4.0.0
|
||||||
description: Angel CORS middleware. Port of expressjs/cors to the Angel framework.
|
description: Angel CORS middleware. Port of expressjs/cors to the Angel framework.
|
||||||
|
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/cors
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.10.0 <3.0.0"
|
sdk: '>=2.12.0 <3.0.0'
|
||||||
homepage: https://github.com/angel-dart/cors.git
|
|
||||||
name: angel_cors
|
|
||||||
version: 3.0.0
|
|
||||||
publish_to: none
|
|
||||||
dependencies:
|
dependencies:
|
||||||
angel_framework:
|
angel3_framework: ^4.0.0
|
||||||
git:
|
|
||||||
url: https://github.com/dukefirehawk/angel.git
|
|
||||||
ref: sdk-2.12.x
|
|
||||||
path: packages/framework
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
angel_test:
|
angel3_test: ^4.0.0
|
||||||
git:
|
http: ^0.13.3
|
||||||
url: https://github.com/dukefirehawk/angel.git
|
|
||||||
ref: sdk-2.12.x
|
|
||||||
path: packages/test
|
|
||||||
http: ^0.13.0
|
|
||||||
pedantic: ^1.11.0
|
pedantic: ^1.11.0
|
||||||
test: ^1.16.5
|
test: ^1.17.5
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:angel_framework/http.dart';
|
import 'package:angel3_framework/http.dart';
|
||||||
import 'package:angel_cors/angel_cors.dart';
|
import 'package:angel3_cors/angel3_cors.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
main() {
|
void main() {
|
||||||
Angel app;
|
Angel? app;
|
||||||
AngelHttp server;
|
late AngelHttp server;
|
||||||
http.Client client;
|
http.Client? client;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
app = Angel()
|
app = Angel()
|
||||||
|
@ -24,7 +24,7 @@ main() {
|
||||||
cors(CorsOptions(
|
cors(CorsOptions(
|
||||||
origin: ['foo.bar', 'baz.quux'],
|
origin: ['foo.bar', 'baz.quux'],
|
||||||
)),
|
)),
|
||||||
(req, res) => req.headers['origin']
|
(req, res) => req.headers!['origin']
|
||||||
]))
|
]))
|
||||||
..get(
|
..get(
|
||||||
'/origins',
|
'/origins',
|
||||||
|
@ -32,7 +32,7 @@ main() {
|
||||||
cors(CorsOptions(
|
cors(CorsOptions(
|
||||||
origin: 'foo.bar',
|
origin: 'foo.bar',
|
||||||
)),
|
)),
|
||||||
(req, res) => req.headers['origin']
|
(req, res) => req.headers!['origin']
|
||||||
]))
|
]))
|
||||||
..get(
|
..get(
|
||||||
'/originr',
|
'/originr',
|
||||||
|
@ -40,7 +40,7 @@ main() {
|
||||||
cors(CorsOptions(
|
cors(CorsOptions(
|
||||||
origin: RegExp(r'^foo\.[^x]+$'),
|
origin: RegExp(r'^foo\.[^x]+$'),
|
||||||
)),
|
)),
|
||||||
(req, res) => req.headers['origin']
|
(req, res) => req.headers!['origin']
|
||||||
]))
|
]))
|
||||||
..get(
|
..get(
|
||||||
'/originp',
|
'/originp',
|
||||||
|
@ -48,7 +48,7 @@ main() {
|
||||||
cors(CorsOptions(
|
cors(CorsOptions(
|
||||||
origin: (String s) => s.endsWith('.bar'),
|
origin: (String s) => s.endsWith('.bar'),
|
||||||
)),
|
)),
|
||||||
(req, res) => req.headers['origin']
|
(req, res) => req.headers!['origin']
|
||||||
]))
|
]))
|
||||||
..options('/status', cors(CorsOptions(successStatus: 418)))
|
..options('/status', cors(CorsOptions(successStatus: 418)))
|
||||||
..fallback(cors(CorsOptions()))
|
..fallback(cors(CorsOptions()))
|
||||||
|
@ -57,7 +57,7 @@ main() {
|
||||||
})
|
})
|
||||||
..fallback((req, res) => throw AngelHttpException.notFound());
|
..fallback((req, res) => throw AngelHttpException.notFound());
|
||||||
|
|
||||||
server = AngelHttp(app);
|
server = AngelHttp(app!);
|
||||||
await server.startServer('127.0.0.1', 0);
|
await server.startServer('127.0.0.1', 0);
|
||||||
client = http.Client();
|
client = http.Client();
|
||||||
});
|
});
|
||||||
|
@ -70,93 +70,93 @@ main() {
|
||||||
|
|
||||||
test('status 204 by default', () async {
|
test('status 204 by default', () async {
|
||||||
var rq = http.Request('OPTIONS', server.uri.replace(path: '/max_age'));
|
var rq = http.Request('OPTIONS', server.uri.replace(path: '/max_age'));
|
||||||
var response = await client.send(rq).then(http.Response.fromStream);
|
var response = await client!.send(rq).then(http.Response.fromStream);
|
||||||
expect(response.statusCode, 204);
|
expect(response.statusCode, 204);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('content length 0 by default', () async {
|
test('content length 0 by default', () async {
|
||||||
var rq = http.Request('OPTIONS', server.uri.replace(path: '/max_age'));
|
var rq = http.Request('OPTIONS', server.uri.replace(path: '/max_age'));
|
||||||
var response = await client.send(rq).then(http.Response.fromStream);
|
var response = await client!.send(rq).then(http.Response.fromStream);
|
||||||
expect(response.contentLength, 0);
|
expect(response.contentLength, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('custom successStatus', () async {
|
test('custom successStatus', () async {
|
||||||
var rq = http.Request('OPTIONS', server.uri.replace(path: '/status'));
|
var rq = http.Request('OPTIONS', server.uri.replace(path: '/status'));
|
||||||
var response = await client.send(rq).then(http.Response.fromStream);
|
var response = await client!.send(rq).then(http.Response.fromStream);
|
||||||
expect(response.statusCode, 418);
|
expect(response.statusCode, 418);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('max age', () async {
|
test('max age', () async {
|
||||||
var rq = http.Request('OPTIONS', server.uri.replace(path: '/max_age'));
|
var rq = http.Request('OPTIONS', server.uri.replace(path: '/max_age'));
|
||||||
var response = await client.send(rq).then(http.Response.fromStream);
|
var response = await client!.send(rq).then(http.Response.fromStream);
|
||||||
expect(response.headers['access-control-max-age'], '250');
|
expect(response.headers['access-control-max-age'], '250');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('methods', () async {
|
test('methods', () async {
|
||||||
var rq = http.Request('OPTIONS', server.uri.replace(path: '/methods'));
|
var rq = http.Request('OPTIONS', server.uri.replace(path: '/methods'));
|
||||||
var response = await client.send(rq).then(http.Response.fromStream);
|
var response = await client!.send(rq).then(http.Response.fromStream);
|
||||||
expect(response.headers['access-control-allow-methods'], 'GET,POST');
|
expect(response.headers['access-control-allow-methods'], 'GET,POST');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('dynamicCors.credentials', () async {
|
test('dynamicCors.credentials', () async {
|
||||||
var rq =
|
var rq =
|
||||||
http.Request('OPTIONS', server.uri.replace(path: '/credentials_d'));
|
http.Request('OPTIONS', server.uri.replace(path: '/credentials_d'));
|
||||||
var response = await client.send(rq).then(http.Response.fromStream);
|
var response = await client!.send(rq).then(http.Response.fromStream);
|
||||||
expect(response.headers['access-control-allow-credentials'], 'true');
|
expect(response.headers['access-control-allow-credentials'], 'true');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('credentials', () async {
|
test('credentials', () async {
|
||||||
var rq = http.Request('OPTIONS', server.uri.replace(path: '/credentials'));
|
var rq = http.Request('OPTIONS', server.uri.replace(path: '/credentials'));
|
||||||
var response = await client.send(rq).then(http.Response.fromStream);
|
var response = await client!.send(rq).then(http.Response.fromStream);
|
||||||
expect(response.headers['access-control-allow-credentials'], 'true');
|
expect(response.headers['access-control-allow-credentials'], 'true');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('exposed headers', () async {
|
test('exposed headers', () async {
|
||||||
var rq = http.Request('OPTIONS', server.uri.replace(path: '/headers'));
|
var rq = http.Request('OPTIONS', server.uri.replace(path: '/headers'));
|
||||||
var response = await client.send(rq).then(http.Response.fromStream);
|
var response = await client!.send(rq).then(http.Response.fromStream);
|
||||||
expect(response.headers['access-control-expose-headers'], 'x-foo,x-bar');
|
expect(response.headers['access-control-expose-headers'], 'x-foo,x-bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('invalid origin', () async {
|
test('invalid origin', () async {
|
||||||
var response = await client.get(server.uri.replace(path: '/originl'),
|
var response = await client!.get(server.uri.replace(path: '/originl'),
|
||||||
headers: {'origin': 'foreign'});
|
headers: {'origin': 'foreign'});
|
||||||
expect(response.headers['access-control-allow-origin'], 'false');
|
expect(response.headers['access-control-allow-origin'], 'false');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('list origin', () async {
|
test('list origin', () async {
|
||||||
var response = await client.get(server.uri.replace(path: '/originl'),
|
var response = await client!.get(server.uri.replace(path: '/originl'),
|
||||||
headers: {'origin': 'foo.bar'});
|
headers: {'origin': 'foo.bar'});
|
||||||
expect(response.headers['access-control-allow-origin'], 'foo.bar');
|
expect(response.headers['access-control-allow-origin'], 'foo.bar');
|
||||||
expect(response.headers['vary'], 'origin');
|
expect(response.headers['vary'], 'origin');
|
||||||
response = await client.get(server.uri.replace(path: '/originl'),
|
response = await client!.get(server.uri.replace(path: '/originl'),
|
||||||
headers: {'origin': 'baz.quux'});
|
headers: {'origin': 'baz.quux'});
|
||||||
expect(response.headers['access-control-allow-origin'], 'baz.quux');
|
expect(response.headers['access-control-allow-origin'], 'baz.quux');
|
||||||
expect(response.headers['vary'], 'origin');
|
expect(response.headers['vary'], 'origin');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('string origin', () async {
|
test('string origin', () async {
|
||||||
var response = await client.get(server.uri.replace(path: '/origins'),
|
var response = await client!.get(server.uri.replace(path: '/origins'),
|
||||||
headers: {'origin': 'foo.bar'});
|
headers: {'origin': 'foo.bar'});
|
||||||
expect(response.headers['access-control-allow-origin'], 'foo.bar');
|
expect(response.headers['access-control-allow-origin'], 'foo.bar');
|
||||||
expect(response.headers['vary'], 'origin');
|
expect(response.headers['vary'], 'origin');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('regex origin', () async {
|
test('regex origin', () async {
|
||||||
var response = await client.get(server.uri.replace(path: '/originr'),
|
var response = await client!.get(server.uri.replace(path: '/originr'),
|
||||||
headers: {'origin': 'foo.bar'});
|
headers: {'origin': 'foo.bar'});
|
||||||
expect(response.headers['access-control-allow-origin'], 'foo.bar');
|
expect(response.headers['access-control-allow-origin'], 'foo.bar');
|
||||||
expect(response.headers['vary'], 'origin');
|
expect(response.headers['vary'], 'origin');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('predicate origin', () async {
|
test('predicate origin', () async {
|
||||||
var response = await client.get(server.uri.replace(path: '/originp'),
|
var response = await client!.get(server.uri.replace(path: '/originp'),
|
||||||
headers: {'origin': 'foo.bar'});
|
headers: {'origin': 'foo.bar'});
|
||||||
expect(response.headers['access-control-allow-origin'], 'foo.bar');
|
expect(response.headers['access-control-allow-origin'], 'foo.bar');
|
||||||
expect(response.headers['vary'], 'origin');
|
expect(response.headers['vary'], 'origin');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST works', () async {
|
test('POST works', () async {
|
||||||
final response = await client.post(server.uri);
|
final response = await client!.post(server.uri);
|
||||||
expect(response.statusCode, equals(200));
|
expect(response.statusCode, equals(200));
|
||||||
print('Response: ${response.body}');
|
print('Response: ${response.body}');
|
||||||
print('Headers: ${response.headers}');
|
print('Headers: ${response.headers}');
|
||||||
|
@ -164,7 +164,7 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('mirror headers', () async {
|
test('mirror headers', () async {
|
||||||
final response = await client
|
final response = await client!
|
||||||
.post(server.uri, headers: {'access-control-request-headers': 'foo'});
|
.post(server.uri, headers: {'access-control-request-headers': 'foo'});
|
||||||
expect(response.statusCode, equals(200));
|
expect(response.statusCode, equals(200));
|
||||||
print('Response: ${response.body}');
|
print('Response: ${response.body}');
|
||||||
|
|
12
packages/oauth2/AUTHORS.md
Normal file
12
packages/oauth2/AUTHORS.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
Primary Authors
|
||||||
|
===============
|
||||||
|
|
||||||
|
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
|
||||||
|
|
||||||
|
Thomas is the current maintainer of the code base. He has refactored and migrated the
|
||||||
|
code base to support NNBD.
|
||||||
|
|
||||||
|
* __[Tobe O](thosakwe@gmail.com)__
|
||||||
|
|
||||||
|
Tobe has written much of the original code prior to NNBD migration. He has moved on and
|
||||||
|
is no longer involved with the project.
|
|
@ -1,3 +1,9 @@
|
||||||
|
# 4.0.0
|
||||||
|
* Migrated to support Dart SDK 2.12.x NNBD
|
||||||
|
|
||||||
|
# 3.0.0
|
||||||
|
* Migrated to work with Dart SDK 2.12.x Non NNBD
|
||||||
|
|
||||||
# 2.3.0
|
# 2.3.0
|
||||||
* Remove `implicitGrant`, and inline it into `requestAuthorizationCode`.
|
* Remove `implicitGrant`, and inline it into `requestAuthorizationCode`.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# oauth2
|
# angel3_oauth2
|
||||||
[![Pub](https://img.shields.io/pub/v/angel_oauth2.svg)](https://pub.dartlang.org/packages/angel_oauth2)
|
[![version](https://img.shields.io/badge/pub-v4.0.0-brightgreen)](https://pub.dartlang.org/packages/angel3_oauth2)
|
||||||
[![build status](https://travis-ci.org/angel-dart/oauth2.svg)](https://travis-ci.org/angel-dart/oauth2)
|
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
||||||
|
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
||||||
|
|
||||||
|
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/oauth2/LICENSE)
|
||||||
|
|
||||||
A class containing handlers that can be used within
|
A class containing handlers that can be used within
|
||||||
[Angel](https://angel-dart.github.io/) to build a spec-compliant
|
[Angel](https://angel-dart.github.io/) to build a spec-compliant
|
||||||
|
@ -16,8 +19,8 @@ In your `pubspec.yaml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
dependencies:
|
dependencies:
|
||||||
angel_framework: ^2.0.0-alpha
|
angel3_framework: ^4.0.0
|
||||||
angel_oauth2: ^2.0.0
|
angel3_oauth2: ^4.0.0
|
||||||
```
|
```
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
@ -28,7 +31,7 @@ Your server needs to have definitions of at least two types:
|
||||||
Define a server class as such:
|
Define a server class as such:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
import 'package:angel_oauth2/angel_oauth2.dart' as oauth2;
|
import 'package:angel3_oauth2/angel3_oauth2.dart' as oauth2;
|
||||||
|
|
||||||
class MyServer extends oauth2.AuthorizationServer<Client, User> {}
|
class MyServer extends oauth2.AuthorizationServer<Client, User> {}
|
||||||
```
|
```
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'dart:async';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
import 'package:angel_oauth2/angel_oauth2.dart';
|
import 'package:angel_oauth2/angel_oauth2.dart';
|
||||||
|
|
||||||
main() async {
|
void main() async {
|
||||||
var app = Angel();
|
var app = Angel();
|
||||||
var oauth2 = _ExampleAuthorizationServer();
|
var oauth2 = _ExampleAuthorizationServer();
|
||||||
var _rgxBearer = RegExp(r'^[Bb]earer ([^\n\s]+)$');
|
var _rgxBearer = RegExp(r'^[Bb]earer ([^\n\s]+)$');
|
||||||
|
@ -17,7 +17,7 @@ main() async {
|
||||||
// Assume that all other requests must be authenticated...
|
// Assume that all other requests must be authenticated...
|
||||||
app.fallback((req, res) {
|
app.fallback((req, res) {
|
||||||
var authToken =
|
var authToken =
|
||||||
req.headers.value('authorization')?.replaceAll(_rgxBearer, '')?.trim();
|
req.headers!.value('authorization')?.replaceAll(_rgxBearer, '').trim();
|
||||||
|
|
||||||
if (authToken == null) {
|
if (authToken == null) {
|
||||||
throw AngelHttpException.forbidden();
|
throw AngelHttpException.forbidden();
|
||||||
|
@ -38,13 +38,13 @@ class User {}
|
||||||
class _ExampleAuthorizationServer
|
class _ExampleAuthorizationServer
|
||||||
extends AuthorizationServer<ThirdPartyApp, User> {
|
extends AuthorizationServer<ThirdPartyApp, User> {
|
||||||
@override
|
@override
|
||||||
FutureOr<ThirdPartyApp> findClient(String clientId) {
|
FutureOr<ThirdPartyApp> findClient(String? clientId) {
|
||||||
// TODO: Add your code to find the app associated with a client ID.
|
// TODO: Add your code to find the app associated with a client ID.
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<bool> verifyClient(ThirdPartyApp client, String clientSecret) {
|
FutureOr<bool> verifyClient(ThirdPartyApp client, String? clientSecret) {
|
||||||
// TODO: Add your code to verify a client secret, if given one.
|
// TODO: Add your code to verify a client secret, if given one.
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ class _ExampleAuthorizationServer
|
||||||
@override
|
@override
|
||||||
FutureOr requestAuthorizationCode(
|
FutureOr requestAuthorizationCode(
|
||||||
ThirdPartyApp client,
|
ThirdPartyApp client,
|
||||||
String redirectUri,
|
String? redirectUri,
|
||||||
Iterable<String> scopes,
|
Iterable<String> scopes,
|
||||||
String state,
|
String state,
|
||||||
RequestContext req,
|
RequestContext req,
|
||||||
|
@ -64,9 +64,9 @@ class _ExampleAuthorizationServer
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
|
FutureOr<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
|
||||||
ThirdPartyApp client,
|
ThirdPartyApp? client,
|
||||||
String authCode,
|
String? authCode,
|
||||||
String redirectUri,
|
String? redirectUri,
|
||||||
RequestContext req,
|
RequestContext req,
|
||||||
ResponseContext res) {
|
ResponseContext res) {
|
||||||
// TODO: Here, you'll convert the auth code into a full-fledged token.
|
// TODO: Here, you'll convert the auth code into a full-fledged token.
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import 'package:angel_http_exception/angel_http_exception.dart';
|
import 'package:angel3_http_exception/angel3_http_exception.dart';
|
||||||
|
|
||||||
/// An Angel-friendly wrapper around OAuth2 [ErrorResponse] instances.
|
/// An Angel-friendly wrapper around OAuth2 [ErrorResponse] instances.
|
||||||
class AuthorizationException extends AngelHttpException {
|
class AuthorizationException extends AngelHttpException {
|
||||||
final ErrorResponse errorResponse;
|
final ErrorResponse errorResponse;
|
||||||
|
|
||||||
AuthorizationException(this.errorResponse,
|
AuthorizationException(this.errorResponse,
|
||||||
{StackTrace stackTrace, int statusCode, error})
|
{StackTrace? stackTrace, int? statusCode, error})
|
||||||
: super(error ?? errorResponse,
|
: super(error ?? errorResponse,
|
||||||
stackTrace: stackTrace, message: '', statusCode: statusCode ?? 400);
|
stackTrace: stackTrace, message: '', statusCode: statusCode ?? 400);
|
||||||
|
|
||||||
|
@ -16,8 +16,9 @@ class AuthorizationException extends AngelHttpException {
|
||||||
'error_description': errorResponse.description,
|
'error_description': errorResponse.description,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (errorResponse.uri != null)
|
if (errorResponse.uri != null) {
|
||||||
m['error_uri'] = errorResponse.uri.toString();
|
m['error_uri'] = errorResponse.uri.toString();
|
||||||
|
}
|
||||||
|
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
@ -78,10 +79,10 @@ class ErrorResponse {
|
||||||
final String description;
|
final String description;
|
||||||
|
|
||||||
/// An optional [Uri] directing users to more information about the error.
|
/// An optional [Uri] directing users to more information about the error.
|
||||||
final Uri uri;
|
final Uri? uri;
|
||||||
|
|
||||||
/// The exact value received from the client, if a "state" parameter was present in the client authorization request.
|
/// The exact value received from the client, if a "state" parameter was present in the client authorization request.
|
||||||
final String state;
|
final String? state;
|
||||||
|
|
||||||
const ErrorResponse(this.code, this.description, this.state, {this.uri});
|
const ErrorResponse(this.code, this.description, this.state, {this.uri});
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ class Pkce {
|
||||||
final String codeChallengeMethod;
|
final String codeChallengeMethod;
|
||||||
|
|
||||||
/// The proof key that is used to secure public clients.
|
/// The proof key that is used to secure public clients.
|
||||||
final String codeChallenge;
|
final String? codeChallenge;
|
||||||
|
|
||||||
Pkce(this.codeChallengeMethod, this.codeChallenge) {
|
Pkce(this.codeChallengeMethod, this.codeChallenge) {
|
||||||
assert(codeChallengeMethod == 'plain' || codeChallengeMethod == 's256',
|
assert(codeChallengeMethod == 'plain' || codeChallengeMethod == 's256',
|
||||||
|
@ -17,7 +17,7 @@ class Pkce {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to parse a [codeChallenge] and [codeChallengeMethod] from a [Map].
|
/// Attempts to parse a [codeChallenge] and [codeChallengeMethod] from a [Map].
|
||||||
factory Pkce.fromJson(Map data, {String state, Uri uri}) {
|
factory Pkce.fromJson(Map data, {String? state, Uri? uri}) {
|
||||||
var codeChallenge = data['code_challenge']?.toString();
|
var codeChallenge = data['code_challenge']?.toString();
|
||||||
var codeChallengeMethod =
|
var codeChallengeMethod =
|
||||||
data['code_challenge_method']?.toString() ?? 'plain';
|
data['code_challenge_method']?.toString() ?? 'plain';
|
||||||
|
@ -44,7 +44,7 @@ class Pkce {
|
||||||
bool get isS256 => codeChallengeMethod == 's256';
|
bool get isS256 => codeChallengeMethod == 's256';
|
||||||
|
|
||||||
/// Determines if a given [codeVerifier] is valid.
|
/// Determines if a given [codeVerifier] is valid.
|
||||||
void validate(String codeVerifier, {String state, Uri uri}) {
|
void validate(String codeVerifier, {String? state, Uri? uri}) {
|
||||||
String foreignChallenge;
|
String foreignChallenge;
|
||||||
|
|
||||||
if (isS256) {
|
if (isS256) {
|
||||||
|
@ -57,7 +57,7 @@ class Pkce {
|
||||||
if (foreignChallenge != codeChallenge) {
|
if (foreignChallenge != codeChallenge) {
|
||||||
throw AuthorizationException(
|
throw AuthorizationException(
|
||||||
ErrorResponse(ErrorResponse.invalidGrant,
|
ErrorResponse(ErrorResponse.invalidGrant,
|
||||||
"The given `code_verifier` parameter is invalid.", state,
|
'The given `code_verifier` parameter is invalid.', state,
|
||||||
uri: uri),
|
uri: uri),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,13 @@ class AuthorizationTokenResponse {
|
||||||
final String accessToken;
|
final String accessToken;
|
||||||
|
|
||||||
/// An optional key that can be used to refresh the [accessToken] past its expiration.
|
/// An optional key that can be used to refresh the [accessToken] past its expiration.
|
||||||
final String refreshToken;
|
final String? refreshToken;
|
||||||
|
|
||||||
/// An optional, but recommended integer that signifies the time left until the [accessToken] expires.
|
/// An optional, but recommended integer that signifies the time left until the [accessToken] expires.
|
||||||
final int expiresIn;
|
final int? expiresIn;
|
||||||
|
|
||||||
/// Optional, if identical to the scope requested by the client; otherwise, required.
|
/// Optional, if identical to the scope requested by the client; otherwise, required.
|
||||||
final Iterable<String> scope;
|
final Iterable<String>? scope;
|
||||||
|
|
||||||
const AuthorizationTokenResponse(this.accessToken,
|
const AuthorizationTokenResponse(this.accessToken,
|
||||||
{this.refreshToken, this.expiresIn, this.scope});
|
{this.refreshToken, this.expiresIn, this.scope});
|
||||||
|
@ -19,7 +19,7 @@ class AuthorizationTokenResponse {
|
||||||
var map = <String, dynamic>{'access_token': accessToken};
|
var map = <String, dynamic>{'access_token': accessToken};
|
||||||
if (refreshToken?.isNotEmpty == true) map['refresh_token'] = refreshToken;
|
if (refreshToken?.isNotEmpty == true) map['refresh_token'] = refreshToken;
|
||||||
if (expiresIn != null) map['expires_in'] = expiresIn;
|
if (expiresIn != null) map['expires_in'] = expiresIn;
|
||||||
if (scope != null) map['scope'] = scope.toList();
|
if (scope != null) map['scope'] = scope!.toList();
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,12 +40,12 @@ class DeviceCodeResponse {
|
||||||
/// OPTIONAL. A verification URI that includes the [userCode] (or
|
/// OPTIONAL. A verification URI that includes the [userCode] (or
|
||||||
/// other information with the same function as the [userCode]),
|
/// other information with the same function as the [userCode]),
|
||||||
/// designed for non-textual transmission.
|
/// designed for non-textual transmission.
|
||||||
final Uri verificationUriComplete;
|
final Uri? verificationUriComplete;
|
||||||
|
|
||||||
/// OPTIONAL. The minimum amount of time in seconds that the client
|
/// OPTIONAL. The minimum amount of time in seconds that the client
|
||||||
/// SHOULD wait between polling requests to the token endpoint. If no
|
/// SHOULD wait between polling requests to the token endpoint. If no
|
||||||
/// value is provided, clients MUST use 5 as the default.
|
/// value is provided, clients MUST use 5 as the default.
|
||||||
final int interval;
|
final int? interval;
|
||||||
|
|
||||||
/// The lifetime, in *seconds* of the [deviceCode] and [userCode].
|
/// The lifetime, in *seconds* of the [deviceCode] and [userCode].
|
||||||
final int expiresIn;
|
final int expiresIn;
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'exception.dart';
|
import 'exception.dart';
|
||||||
import 'pkce.dart';
|
import 'pkce.dart';
|
||||||
import 'response.dart';
|
import 'response.dart';
|
||||||
import 'token_type.dart';
|
import 'token_type.dart';
|
||||||
|
|
||||||
/// A request handler that performs an arbitrary authorization token grant.
|
/// A request handler that performs an arbitrary authorization token grant.
|
||||||
typedef FutureOr<AuthorizationTokenResponse> ExtensionGrant(
|
typedef ExtensionGrant = FutureOr<AuthorizationTokenResponse> Function(
|
||||||
RequestContext req, ResponseContext res);
|
RequestContext req, ResponseContext res);
|
||||||
|
|
||||||
Future<String> _getParam(RequestContext req, String name, String state,
|
Future<String?> _getParam(RequestContext req, String name, String state,
|
||||||
{bool body = false, bool throwIfEmpty = true}) async {
|
{bool body = false, bool throwIfEmpty = true}) async {
|
||||||
Map<String, dynamic> data;
|
Map<String, dynamic> data;
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ Future<Iterable<String>> _getScopes(RequestContext req,
|
||||||
data = req.queryParameters;
|
data = req.queryParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
return data['scope']?.toString()?.split(' ') ?? [];
|
return data['scope']?.toString().split(' ') ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An OAuth2 authorization server, which issues access tokens to third parties.
|
/// An OAuth2 authorization server, which issues access tokens to third parties.
|
||||||
|
@ -60,14 +60,14 @@ abstract class AuthorizationServer<Client, User> {
|
||||||
Map<String, ExtensionGrant> get extensionGrants => {};
|
Map<String, ExtensionGrant> get extensionGrants => {};
|
||||||
|
|
||||||
/// Finds the [Client] application associated with the given [clientId].
|
/// Finds the [Client] application associated with the given [clientId].
|
||||||
FutureOr<Client> findClient(String clientId);
|
FutureOr<Client>? findClient(String? clientId);
|
||||||
|
|
||||||
/// Verify that a [client] is the one identified by the [clientSecret].
|
/// Verify that a [client] is the one identified by the [clientSecret].
|
||||||
FutureOr<bool> verifyClient(Client client, String clientSecret);
|
FutureOr<bool> verifyClient(Client client, String? clientSecret);
|
||||||
|
|
||||||
/// Retrieves the PKCE `code_verifier` parameter from a [RequestContext], or throws.
|
/// Retrieves the PKCE `code_verifier` parameter from a [RequestContext], or throws.
|
||||||
Future<String> getPkceCodeVerifier(RequestContext req,
|
Future<String> getPkceCodeVerifier(RequestContext req,
|
||||||
{bool body = true, String state, Uri uri}) async {
|
{bool body = true, String? state, Uri? uri}) async {
|
||||||
var data = body
|
var data = body
|
||||||
? await req.parseBody().then((_) => req.bodyAsMap)
|
? await req.parseBody().then((_) => req.bodyAsMap)
|
||||||
: req.queryParameters;
|
: req.queryParameters;
|
||||||
|
@ -75,14 +75,14 @@ abstract class AuthorizationServer<Client, User> {
|
||||||
|
|
||||||
if (codeVerifier == null) {
|
if (codeVerifier == null) {
|
||||||
throw AuthorizationException(ErrorResponse(ErrorResponse.invalidRequest,
|
throw AuthorizationException(ErrorResponse(ErrorResponse.invalidRequest,
|
||||||
"Missing `code_verifier` parameter.", state,
|
'Missing `code_verifier` parameter.', state,
|
||||||
uri: uri));
|
uri: uri));
|
||||||
} else if (codeVerifier is! String) {
|
} else if (codeVerifier is! String) {
|
||||||
throw AuthorizationException(ErrorResponse(ErrorResponse.invalidRequest,
|
throw AuthorizationException(ErrorResponse(ErrorResponse.invalidRequest,
|
||||||
"The `code_verifier` parameter must be a string.", state,
|
'The `code_verifier` parameter must be a string.', state,
|
||||||
uri: uri));
|
uri: uri));
|
||||||
} else {
|
} else {
|
||||||
return codeVerifier as String;
|
return codeVerifier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ abstract class AuthorizationServer<Client, User> {
|
||||||
/// the same.
|
/// the same.
|
||||||
FutureOr<void> requestAuthorizationCode(
|
FutureOr<void> requestAuthorizationCode(
|
||||||
Client client,
|
Client client,
|
||||||
String redirectUri,
|
String? redirectUri,
|
||||||
Iterable<String> scopes,
|
Iterable<String> scopes,
|
||||||
String state,
|
String state,
|
||||||
RequestContext req,
|
RequestContext req,
|
||||||
|
@ -113,16 +113,16 @@ abstract class AuthorizationServer<Client, User> {
|
||||||
|
|
||||||
/// Exchanges an authorization code for an authorization token.
|
/// Exchanges an authorization code for an authorization token.
|
||||||
FutureOr<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
|
FutureOr<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
|
||||||
Client client,
|
Client? client,
|
||||||
String authCode,
|
String? authCode,
|
||||||
String redirectUri,
|
String? redirectUri,
|
||||||
RequestContext req,
|
RequestContext req,
|
||||||
ResponseContext res) {
|
ResponseContext res) {
|
||||||
throw AuthorizationException(
|
throw AuthorizationException(
|
||||||
ErrorResponse(
|
ErrorResponse(
|
||||||
ErrorResponse.unsupportedResponseType,
|
ErrorResponse.unsupportedResponseType,
|
||||||
'Authorization code grants are not supported.',
|
'Authorization code grants are not supported.',
|
||||||
req.uri.queryParameters['state'] ?? '',
|
req.uri!.queryParameters['state'] ?? '',
|
||||||
),
|
),
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
);
|
);
|
||||||
|
@ -130,8 +130,8 @@ abstract class AuthorizationServer<Client, User> {
|
||||||
|
|
||||||
/// Refresh an authorization token.
|
/// Refresh an authorization token.
|
||||||
FutureOr<AuthorizationTokenResponse> refreshAuthorizationToken(
|
FutureOr<AuthorizationTokenResponse> refreshAuthorizationToken(
|
||||||
Client client,
|
Client? client,
|
||||||
String refreshToken,
|
String? refreshToken,
|
||||||
Iterable<String> scopes,
|
Iterable<String> scopes,
|
||||||
RequestContext req,
|
RequestContext req,
|
||||||
ResponseContext res) async {
|
ResponseContext res) async {
|
||||||
|
@ -148,9 +148,9 @@ abstract class AuthorizationServer<Client, User> {
|
||||||
|
|
||||||
/// Issue an authorization token to a user after authenticating them via [username] and [password].
|
/// Issue an authorization token to a user after authenticating them via [username] and [password].
|
||||||
FutureOr<AuthorizationTokenResponse> resourceOwnerPasswordCredentialsGrant(
|
FutureOr<AuthorizationTokenResponse> resourceOwnerPasswordCredentialsGrant(
|
||||||
Client client,
|
Client? client,
|
||||||
String username,
|
String? username,
|
||||||
String password,
|
String? password,
|
||||||
Iterable<String> scopes,
|
Iterable<String> scopes,
|
||||||
RequestContext req,
|
RequestContext req,
|
||||||
ResponseContext res) async {
|
ResponseContext res) async {
|
||||||
|
@ -167,7 +167,7 @@ abstract class AuthorizationServer<Client, User> {
|
||||||
|
|
||||||
/// Performs a client credentials grant. Only use this in situations where the client is 100% trusted.
|
/// Performs a client credentials grant. Only use this in situations where the client is 100% trusted.
|
||||||
FutureOr<AuthorizationTokenResponse> clientCredentialsGrant(
|
FutureOr<AuthorizationTokenResponse> clientCredentialsGrant(
|
||||||
Client client, RequestContext req, ResponseContext res) async {
|
Client? client, RequestContext req, ResponseContext res) async {
|
||||||
var body = await req.parseBody().then((_) => req.bodyAsMap);
|
var body = await req.parseBody().then((_) => req.bodyAsMap);
|
||||||
throw AuthorizationException(
|
throw AuthorizationException(
|
||||||
ErrorResponse(
|
ErrorResponse(
|
||||||
|
@ -196,7 +196,7 @@ abstract class AuthorizationServer<Client, User> {
|
||||||
/// Produces an authorization token from a given device code.
|
/// Produces an authorization token from a given device code.
|
||||||
FutureOr<AuthorizationTokenResponse> exchangeDeviceCodeForToken(
|
FutureOr<AuthorizationTokenResponse> exchangeDeviceCodeForToken(
|
||||||
Client client,
|
Client client,
|
||||||
String deviceCode,
|
String? deviceCode,
|
||||||
String state,
|
String state,
|
||||||
RequestContext req,
|
RequestContext req,
|
||||||
ResponseContext res) async {
|
ResponseContext res) async {
|
||||||
|
@ -213,7 +213,7 @@ abstract class AuthorizationServer<Client, User> {
|
||||||
|
|
||||||
/// Returns the [Uri] that a client can be redirected to in the case of an implicit grant.
|
/// Returns the [Uri] that a client can be redirected to in the case of an implicit grant.
|
||||||
Uri completeImplicitGrant(AuthorizationTokenResponse token, Uri redirectUri,
|
Uri completeImplicitGrant(AuthorizationTokenResponse token, Uri redirectUri,
|
||||||
{String state}) {
|
{String? state}) {
|
||||||
var queryParameters = <String, String>{};
|
var queryParameters = <String, String>{};
|
||||||
|
|
||||||
queryParameters.addAll({
|
queryParameters.addAll({
|
||||||
|
@ -223,17 +223,18 @@ abstract class AuthorizationServer<Client, User> {
|
||||||
|
|
||||||
if (state != null) queryParameters['state'] = state;
|
if (state != null) queryParameters['state'] = state;
|
||||||
|
|
||||||
if (token.expiresIn != null)
|
if (token.expiresIn != null) {
|
||||||
queryParameters['expires_in'] = token.expiresIn.toString();
|
queryParameters['expires_in'] = token.expiresIn.toString();
|
||||||
|
}
|
||||||
|
|
||||||
if (token.scope != null) queryParameters['scope'] = token.scope.join(' ');
|
if (token.scope != null) queryParameters['scope'] = token.scope!.join(' ');
|
||||||
|
|
||||||
var fragment =
|
var fragment =
|
||||||
queryParameters.keys.fold<StringBuffer>(StringBuffer(), (buf, k) {
|
queryParameters.keys.fold<StringBuffer>(StringBuffer(), (buf, k) {
|
||||||
if (buf.isNotEmpty) buf.write('&');
|
if (buf.isNotEmpty) buf.write('&');
|
||||||
return buf
|
return buf
|
||||||
..write(
|
..write(
|
||||||
'$k=' + Uri.encodeComponent(queryParameters[k]),
|
'$k=' + Uri.encodeComponent(queryParameters[k]!),
|
||||||
);
|
);
|
||||||
}).toString();
|
}).toString();
|
||||||
|
|
||||||
|
@ -244,14 +245,14 @@ abstract class AuthorizationServer<Client, User> {
|
||||||
/// of grant the client is requesting.
|
/// of grant the client is requesting.
|
||||||
Future<void> authorizationEndpoint(
|
Future<void> authorizationEndpoint(
|
||||||
RequestContext req, ResponseContext res) async {
|
RequestContext req, ResponseContext res) async {
|
||||||
String state = '';
|
var state = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var query = req.queryParameters;
|
var query = req.queryParameters;
|
||||||
state = query['state']?.toString() ?? '';
|
state = query['state']?.toString() ?? '';
|
||||||
var responseType = await _getParam(req, 'response_type', state);
|
var responseType = await _getParam(req, 'response_type', state);
|
||||||
|
|
||||||
req.container.registerLazySingleton<Pkce>((_) {
|
req.container!.registerLazySingleton<Pkce>((_) {
|
||||||
return Pkce.fromJson(req.queryParameters, state: state);
|
return Pkce.fromJson(req.queryParameters, state: state);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -260,7 +261,7 @@ abstract class AuthorizationServer<Client, User> {
|
||||||
var clientId = await _getParam(req, 'client_id', state);
|
var clientId = await _getParam(req, 'client_id', state);
|
||||||
|
|
||||||
// Find client
|
// Find client
|
||||||
var client = await findClient(clientId);
|
var client = await findClient(clientId)!;
|
||||||
|
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
throw AuthorizationException(ErrorResponse(
|
throw AuthorizationException(ErrorResponse(
|
||||||
|
@ -309,16 +310,16 @@ abstract class AuthorizationServer<Client, User> {
|
||||||
/// A request handler that either exchanges authorization codes for authorization tokens,
|
/// A request handler that either exchanges authorization codes for authorization tokens,
|
||||||
/// or refreshes authorization tokens.
|
/// or refreshes authorization tokens.
|
||||||
Future tokenEndpoint(RequestContext req, ResponseContext res) async {
|
Future tokenEndpoint(RequestContext req, ResponseContext res) async {
|
||||||
String state = '';
|
var state = '';
|
||||||
Client client;
|
Client? client;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
AuthorizationTokenResponse response;
|
AuthorizationTokenResponse? response;
|
||||||
var body = await req.parseBody().then((_) => req.bodyAsMap);
|
var body = await req.parseBody().then((_) => req.bodyAsMap);
|
||||||
|
|
||||||
state = body['state']?.toString() ?? '';
|
state = body['state']?.toString() ?? '';
|
||||||
|
|
||||||
req.container.registerLazySingleton<Pkce>((_) {
|
req.container!.registerLazySingleton<Pkce>((_) {
|
||||||
return Pkce.fromJson(req.bodyAsMap, state: state);
|
return Pkce.fromJson(req.bodyAsMap, state: state);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -328,11 +329,11 @@ abstract class AuthorizationServer<Client, User> {
|
||||||
if (grantType != 'urn:ietf:params:oauth:grant-type:device_code' &&
|
if (grantType != 'urn:ietf:params:oauth:grant-type:device_code' &&
|
||||||
grantType != null) {
|
grantType != null) {
|
||||||
var match =
|
var match =
|
||||||
_rgxBasic.firstMatch(req.headers.value('authorization') ?? '');
|
_rgxBasic.firstMatch(req.headers!.value('authorization') ?? '');
|
||||||
|
|
||||||
if (match != null) {
|
if (match != null) {
|
||||||
match = _rgxBasicAuth
|
match = _rgxBasicAuth
|
||||||
.firstMatch(String.fromCharCodes(base64Url.decode(match[1])));
|
.firstMatch(String.fromCharCodes(base64Url.decode(match[1]!)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
|
@ -402,7 +403,7 @@ abstract class AuthorizationServer<Client, User> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (extensionGrants.containsKey(grantType)) {
|
} else if (extensionGrants.containsKey(grantType)) {
|
||||||
response = await extensionGrants[grantType](req, res);
|
response = await extensionGrants[grantType!]!(req, res);
|
||||||
} else if (grantType == null) {
|
} else if (grantType == null) {
|
||||||
// This is a device code grant.
|
// This is a device code grant.
|
||||||
var clientId = await _getParam(req, 'client_id', state, body: true);
|
var clientId = await _getParam(req, 'client_id', state, body: true);
|
||||||
|
|
|
@ -1,23 +1,19 @@
|
||||||
name: angel_oauth2
|
name: angel3_oauth2
|
||||||
author: Tobe O <thosakwe@gmail.com>
|
version: 4.0.0
|
||||||
description: A class containing handlers that can be used within Angel to build a spec-compliant OAuth 2.0 server.
|
description: A class containing handlers that can be used within Angel to build a spec-compliant OAuth 2.0 server.
|
||||||
homepage: https://github.com/angel-dart/oauth2.git
|
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/oauth2
|
||||||
version: 2.3.0
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.10.0 <2.12.0"
|
sdk: '>=2.12.0 <3.0.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
angel_framework: #^2.0.0-rc.0
|
angel3_framework: ^4.0.0
|
||||||
path: ../framework
|
angel3_http_exception: ^3.0.0
|
||||||
angel_http_exception: #^1.0.0
|
crypto: ^3.0.1
|
||||||
path: ../http_exception
|
collection: ^1.15.0-nullsafety.4
|
||||||
crypto: ^2.0.0
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
angel_validate: #^2.0.0-alpha
|
angel3_validate: ^4.0.0
|
||||||
path: ../validate
|
angel3_test: ^4.0.0
|
||||||
angel_test: #^2.0.0-alpha
|
logging: ^1.0.1
|
||||||
path: ../test
|
oauth2: ^2.0.0
|
||||||
logging:
|
pedantic: ^1.11.0
|
||||||
oauth2: ^1.0.0
|
test: ^1.17.5
|
||||||
pedantic: ^1.0.0
|
uuid: ^3.0.4
|
||||||
test: ^1.15.7
|
|
||||||
uuid: ^2.0.0
|
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:angel_framework/http.dart';
|
import 'package:angel3_framework/http.dart';
|
||||||
import 'package:angel_oauth2/angel_oauth2.dart';
|
import 'package:angel3_oauth2/angel3_oauth2.dart';
|
||||||
import 'package:angel_test/angel_test.dart';
|
import 'package:angel3_test/angel3_test.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:oauth2/oauth2.dart' as oauth2;
|
import 'package:oauth2/oauth2.dart' as oauth2;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
import 'common.dart';
|
import 'common.dart';
|
||||||
|
|
||||||
main() {
|
void main() {
|
||||||
Angel app;
|
Angel app;
|
||||||
Uri authorizationEndpoint, tokenEndpoint, redirectUri;
|
late Uri authorizationEndpoint, tokenEndpoint, redirectUri;
|
||||||
TestClient testClient;
|
late TestClient testClient;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
app = Angel();
|
app = Angel();
|
||||||
app.configuration['properties'] = app.configuration;
|
app.configuration['properties'] = app.configuration;
|
||||||
app.container.registerSingleton(AuthCodes());
|
app.container!.registerSingleton(AuthCodes());
|
||||||
|
|
||||||
var server = _Server();
|
var server = _Server();
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ main() {
|
||||||
test('show authorization form', () async {
|
test('show authorization form', () async {
|
||||||
var grant = createGrant();
|
var grant = createGrant();
|
||||||
var url = grant.getAuthorizationUrl(redirectUri, state: 'hello');
|
var url = grant.getAuthorizationUrl(redirectUri, state: 'hello');
|
||||||
var response = await testClient.client.get(url);
|
var response = await testClient.client!.get(url);
|
||||||
print('Body: ${response.body}');
|
print('Body: ${response.body}');
|
||||||
expect(
|
expect(
|
||||||
response.body,
|
response.body,
|
||||||
|
@ -73,7 +73,7 @@ main() {
|
||||||
test('preserves state', () async {
|
test('preserves state', () async {
|
||||||
var grant = createGrant();
|
var grant = createGrant();
|
||||||
var url = grant.getAuthorizationUrl(redirectUri, state: 'goodbye');
|
var url = grant.getAuthorizationUrl(redirectUri, state: 'goodbye');
|
||||||
var response = await testClient.client.get(url);
|
var response = await testClient.client!.get(url);
|
||||||
print('Body: ${response.body}');
|
print('Body: ${response.body}');
|
||||||
expect(json.decode(response.body)['state'], 'goodbye');
|
expect(json.decode(response.body)['state'], 'goodbye');
|
||||||
});
|
});
|
||||||
|
@ -81,7 +81,7 @@ main() {
|
||||||
test('sends auth code', () async {
|
test('sends auth code', () async {
|
||||||
var grant = createGrant();
|
var grant = createGrant();
|
||||||
var url = grant.getAuthorizationUrl(redirectUri);
|
var url = grant.getAuthorizationUrl(redirectUri);
|
||||||
var response = await testClient.client.get(url);
|
var response = await testClient.client!.get(url);
|
||||||
print('Body: ${response.body}');
|
print('Body: ${response.body}');
|
||||||
expect(
|
expect(
|
||||||
json.decode(response.body),
|
json.decode(response.body),
|
||||||
|
@ -95,7 +95,7 @@ main() {
|
||||||
test('exchange code for token', () async {
|
test('exchange code for token', () async {
|
||||||
var grant = createGrant();
|
var grant = createGrant();
|
||||||
var url = grant.getAuthorizationUrl(redirectUri);
|
var url = grant.getAuthorizationUrl(redirectUri);
|
||||||
var response = await testClient.client.get(url);
|
var response = await testClient.client!.get(url);
|
||||||
print('Body: ${response.body}');
|
print('Body: ${response.body}');
|
||||||
|
|
||||||
var authCode = json.decode(response.body)['code'].toString();
|
var authCode = json.decode(response.body)['code'].toString();
|
||||||
|
@ -106,7 +106,7 @@ main() {
|
||||||
test('can send refresh token', () async {
|
test('can send refresh token', () async {
|
||||||
var grant = createGrant();
|
var grant = createGrant();
|
||||||
var url = grant.getAuthorizationUrl(redirectUri, state: 'can_refresh');
|
var url = grant.getAuthorizationUrl(redirectUri, state: 'can_refresh');
|
||||||
var response = await testClient.client.get(url);
|
var response = await testClient.client!.get(url);
|
||||||
print('Body: ${response.body}');
|
print('Body: ${response.body}');
|
||||||
|
|
||||||
var authCode = json.decode(response.body)['code'].toString();
|
var authCode = json.decode(response.body)['code'].toString();
|
||||||
|
@ -122,20 +122,20 @@ class _Server extends AuthorizationServer<PseudoApplication, Map> {
|
||||||
final Uuid _uuid = Uuid();
|
final Uuid _uuid = Uuid();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<PseudoApplication> findClient(String clientId) {
|
FutureOr<PseudoApplication>? findClient(String? clientId) {
|
||||||
return clientId == pseudoApplication.id ? pseudoApplication : null;
|
return clientId == pseudoApplication.id ? pseudoApplication : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> verifyClient(
|
Future<bool> verifyClient(
|
||||||
PseudoApplication client, String clientSecret) async {
|
PseudoApplication client, String? clientSecret) async {
|
||||||
return client.secret == clientSecret;
|
return client.secret == clientSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future requestAuthorizationCode(
|
Future requestAuthorizationCode(
|
||||||
PseudoApplication client,
|
PseudoApplication client,
|
||||||
String redirectUri,
|
String? redirectUri,
|
||||||
Iterable<String> scopes,
|
Iterable<String> scopes,
|
||||||
String state,
|
String state,
|
||||||
RequestContext req,
|
RequestContext req,
|
||||||
|
@ -147,28 +147,29 @@ class _Server extends AuthorizationServer<PseudoApplication, Map> {
|
||||||
client, redirectUri, scopes, state, req, res, implicit);
|
client, redirectUri, scopes, state, req, res, implicit);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == 'hello')
|
if (state == 'hello') {
|
||||||
return 'Hello ${pseudoApplication.id}:${pseudoApplication.secret}';
|
return 'Hello ${pseudoApplication.id}:${pseudoApplication.secret}';
|
||||||
|
}
|
||||||
|
|
||||||
var authCode = _uuid.v4();
|
var authCode = _uuid.v4();
|
||||||
var authCodes = req.container.make<AuthCodes>();
|
var authCodes = req.container!.make<AuthCodes>()!;
|
||||||
authCodes[authCode] = state;
|
authCodes[authCode] = state;
|
||||||
|
|
||||||
res.headers['content-type'] = 'application/json';
|
res.headers['content-type'] = 'application/json';
|
||||||
var result = {'code': authCode};
|
var result = {'code': authCode};
|
||||||
if (state?.isNotEmpty == true) result['state'] = state;
|
if (state.isNotEmpty == true) result['state'] = state;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
|
Future<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
|
||||||
PseudoApplication client,
|
PseudoApplication? client,
|
||||||
String authCode,
|
String? authCode,
|
||||||
String redirectUri,
|
String? redirectUri,
|
||||||
RequestContext req,
|
RequestContext req,
|
||||||
ResponseContext res) async {
|
ResponseContext res) async {
|
||||||
var authCodes = req.container.make<AuthCodes>();
|
var authCodes = req.container!.make<AuthCodes>()!;
|
||||||
var state = authCodes[authCode];
|
var state = authCodes[authCode!];
|
||||||
var refreshToken = state == 'can_refresh' ? '${authCode}_refresh' : null;
|
var refreshToken = state == 'can_refresh' ? '${authCode}_refresh' : null;
|
||||||
return AuthorizationTokenResponse('${authCode}_access',
|
return AuthorizationTokenResponse('${authCode}_access',
|
||||||
refreshToken: refreshToken);
|
refreshToken: refreshToken);
|
||||||
|
@ -179,7 +180,7 @@ class AuthCodes extends MapBase<String, String> with MapMixin<String, String> {
|
||||||
var inner = <String, String>{};
|
var inner = <String, String>{};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String operator [](Object key) => inner[key];
|
String? operator [](Object? key) => inner[key as String];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void operator []=(String key, String value) => inner[key] = value;
|
void operator []=(String key, String value) => inner[key] = value;
|
||||||
|
@ -191,5 +192,5 @@ class AuthCodes extends MapBase<String, String> with MapMixin<String, String> {
|
||||||
Iterable<String> get keys => inner.keys;
|
Iterable<String> get keys => inner.keys;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String remove(Object key) => inner.remove(key);
|
String? remove(Object? key) => inner.remove(key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:angel_test/angel_test.dart';
|
import 'package:angel3_test/angel3_test.dart';
|
||||||
import 'package:angel_oauth2/angel_oauth2.dart';
|
import 'package:angel3_oauth2/angel3_oauth2.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'common.dart';
|
import 'common.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
TestClient client;
|
late TestClient client;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
var app = Angel();
|
var app = Angel();
|
||||||
|
@ -30,7 +30,7 @@ void main() {
|
||||||
|
|
||||||
test('authenticate via client credentials', () async {
|
test('authenticate via client credentials', () async {
|
||||||
var response = await client.post(
|
var response = await client.post(
|
||||||
'/oauth2/token',
|
Uri.parse('oauth2/token'),
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': 'Basic ' + base64Url.encode('foo:bar'.codeUnits),
|
'Authorization': 'Basic ' + base64Url.encode('foo:bar'.codeUnits),
|
||||||
},
|
},
|
||||||
|
@ -58,7 +58,7 @@ void main() {
|
||||||
|
|
||||||
test('force correct id', () async {
|
test('force correct id', () async {
|
||||||
var response = await client.post(
|
var response = await client.post(
|
||||||
'/oauth2/token',
|
Uri.parse('/oauth2/token'),
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': 'Basic ' + base64Url.encode('fooa:bar'.codeUnits),
|
'Authorization': 'Basic ' + base64Url.encode('fooa:bar'.codeUnits),
|
||||||
},
|
},
|
||||||
|
@ -73,7 +73,7 @@ void main() {
|
||||||
|
|
||||||
test('force correct secret', () async {
|
test('force correct secret', () async {
|
||||||
var response = await client.post(
|
var response = await client.post(
|
||||||
'/oauth2/token',
|
Uri.parse('/oauth2/token'),
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': 'Basic ' + base64Url.encode('foo:bara'.codeUnits),
|
'Authorization': 'Basic ' + base64Url.encode('foo:bara'.codeUnits),
|
||||||
},
|
},
|
||||||
|
@ -90,19 +90,21 @@ void main() {
|
||||||
class _AuthorizationServer
|
class _AuthorizationServer
|
||||||
extends AuthorizationServer<PseudoApplication, PseudoUser> {
|
extends AuthorizationServer<PseudoApplication, PseudoUser> {
|
||||||
@override
|
@override
|
||||||
PseudoApplication findClient(String clientId) {
|
PseudoApplication? findClient(String? clientId) {
|
||||||
return clientId == pseudoApplication.id ? pseudoApplication : null;
|
return clientId == pseudoApplication.id ? pseudoApplication : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> verifyClient(
|
Future<bool> verifyClient(
|
||||||
PseudoApplication client, String clientSecret) async {
|
PseudoApplication client, String? clientSecret) async {
|
||||||
return client.secret == clientSecret;
|
return client.secret == clientSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<AuthorizationTokenResponse> clientCredentialsGrant(
|
Future<AuthorizationTokenResponse> clientCredentialsGrant(
|
||||||
PseudoApplication client, RequestContext req, ResponseContext res) async {
|
PseudoApplication? client,
|
||||||
|
RequestContext req,
|
||||||
|
ResponseContext res) async {
|
||||||
return AuthorizationTokenResponse('foo');
|
return AuthorizationTokenResponse('foo');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ const List<PseudoUser> pseudoUsers = [
|
||||||
];
|
];
|
||||||
|
|
||||||
class PseudoUser {
|
class PseudoUser {
|
||||||
final String username, password;
|
final String? username, password;
|
||||||
|
|
||||||
const PseudoUser({this.username, this.password});
|
const PseudoUser({this.username, this.password});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:angel_test/angel_test.dart';
|
import 'package:angel3_test/angel3_test.dart';
|
||||||
import 'package:angel_oauth2/angel_oauth2.dart';
|
import 'package:angel3_oauth2/angel3_oauth2.dart';
|
||||||
import 'package:angel_validate/angel_validate.dart';
|
import 'package:angel3_validate/angel3_validate.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'common.dart';
|
import 'common.dart';
|
||||||
|
|
||||||
main() {
|
void main() {
|
||||||
TestClient client;
|
late TestClient client;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
var app = Angel();
|
var app = Angel();
|
||||||
|
@ -38,7 +38,7 @@ main() {
|
||||||
|
|
||||||
group('get initial code', () {
|
group('get initial code', () {
|
||||||
test('invalid client id', () async {
|
test('invalid client id', () async {
|
||||||
var response = await client.post('/oauth2/token', body: {
|
var response = await client.post(Uri.parse('/oauth2/token'), body: {
|
||||||
'client_id': 'barr',
|
'client_id': 'barr',
|
||||||
});
|
});
|
||||||
print(response.body);
|
print(response.body);
|
||||||
|
@ -46,7 +46,7 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('valid client id, no scopes', () async {
|
test('valid client id, no scopes', () async {
|
||||||
var response = await client.post('/oauth2/token', body: {
|
var response = await client.post(Uri.parse('/oauth2/token'), body: {
|
||||||
'client_id': 'foo',
|
'client_id': 'foo',
|
||||||
});
|
});
|
||||||
print(response.body);
|
print(response.body);
|
||||||
|
@ -55,16 +55,16 @@ main() {
|
||||||
allOf(
|
allOf(
|
||||||
hasStatus(200),
|
hasStatus(200),
|
||||||
isJson({
|
isJson({
|
||||||
"device_code": "foo",
|
'device_code': 'foo',
|
||||||
"user_code": "bar",
|
'user_code': 'bar',
|
||||||
"verification_uri": "https://regiostech.com?scopes",
|
'verification_uri': 'https://regiostech.com?scopes',
|
||||||
"expires_in": 3600
|
'expires_in': 3600
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('valid client id, with scopes', () async {
|
test('valid client id, with scopes', () async {
|
||||||
var response = await client.post('/oauth2/token', body: {
|
var response = await client.post(Uri.parse('/oauth2/token'), body: {
|
||||||
'client_id': 'foo',
|
'client_id': 'foo',
|
||||||
'scope': 'bar baz quux',
|
'scope': 'bar baz quux',
|
||||||
});
|
});
|
||||||
|
@ -74,11 +74,11 @@ main() {
|
||||||
allOf(
|
allOf(
|
||||||
hasStatus(200),
|
hasStatus(200),
|
||||||
isJson({
|
isJson({
|
||||||
"device_code": "foo",
|
'device_code': 'foo',
|
||||||
"user_code": "bar",
|
'user_code': 'bar',
|
||||||
"verification_uri": Uri.parse("https://regiostech.com").replace(
|
'verification_uri': Uri.parse('https://regiostech.com').replace(
|
||||||
queryParameters: {'scopes': 'bar,baz,quux'}).toString(),
|
queryParameters: {'scopes': 'bar,baz,quux'}).toString(),
|
||||||
"expires_in": 3600
|
'expires_in': 3600
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
@ -86,7 +86,7 @@ main() {
|
||||||
|
|
||||||
group('get token', () {
|
group('get token', () {
|
||||||
test('valid device code + timing', () async {
|
test('valid device code + timing', () async {
|
||||||
var response = await client.post('/oauth2/token', body: {
|
var response = await client.post(Uri.parse('/oauth2/token'), body: {
|
||||||
'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
|
'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
|
||||||
'client_id': 'foo',
|
'client_id': 'foo',
|
||||||
'device_code': 'bar',
|
'device_code': 'bar',
|
||||||
|
@ -97,7 +97,7 @@ main() {
|
||||||
response,
|
response,
|
||||||
allOf(
|
allOf(
|
||||||
hasStatus(200),
|
hasStatus(200),
|
||||||
isJson({"token_type": "bearer", "access_token": "foo"}),
|
isJson({'token_type': 'bearer', 'access_token': 'foo'}),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ main() {
|
||||||
// The logic for throwing errors and turning them into responses
|
// The logic for throwing errors and turning them into responses
|
||||||
// has already been tested.
|
// has already been tested.
|
||||||
test('failure', () async {
|
test('failure', () async {
|
||||||
var response = await client.post('/oauth2/token', body: {
|
var response = await client.post(Uri.parse('/oauth2/token'), body: {
|
||||||
'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
|
'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
|
||||||
'client_id': 'foo',
|
'client_id': 'foo',
|
||||||
'device_code': 'brute',
|
'device_code': 'brute',
|
||||||
|
@ -120,9 +120,9 @@ main() {
|
||||||
allOf(
|
allOf(
|
||||||
hasStatus(400),
|
hasStatus(400),
|
||||||
isJson({
|
isJson({
|
||||||
"error": "slow_down",
|
'error': 'slow_down',
|
||||||
"error_description":
|
'error_description':
|
||||||
"Ho, brother! Ho, whoa, whoa, whoa now! You got too much dip on your chip!"
|
'Ho, brother! Ho, whoa, whoa, whoa now! You got too much dip on your chip!'
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
@ -132,13 +132,13 @@ main() {
|
||||||
class _AuthorizationServer
|
class _AuthorizationServer
|
||||||
extends AuthorizationServer<PseudoApplication, PseudoUser> {
|
extends AuthorizationServer<PseudoApplication, PseudoUser> {
|
||||||
@override
|
@override
|
||||||
PseudoApplication findClient(String clientId) {
|
PseudoApplication? findClient(String? clientId) {
|
||||||
return clientId == pseudoApplication.id ? pseudoApplication : null;
|
return clientId == pseudoApplication.id ? pseudoApplication : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> verifyClient(
|
Future<bool> verifyClient(
|
||||||
PseudoApplication client, String clientSecret) async {
|
PseudoApplication client, String? clientSecret) async {
|
||||||
return client.secret == clientSecret;
|
return client.secret == clientSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,14 +156,14 @@ class _AuthorizationServer
|
||||||
@override
|
@override
|
||||||
FutureOr<AuthorizationTokenResponse> exchangeDeviceCodeForToken(
|
FutureOr<AuthorizationTokenResponse> exchangeDeviceCodeForToken(
|
||||||
PseudoApplication client,
|
PseudoApplication client,
|
||||||
String deviceCode,
|
String? deviceCode,
|
||||||
String state,
|
String state,
|
||||||
RequestContext req,
|
RequestContext req,
|
||||||
ResponseContext res) {
|
ResponseContext res) {
|
||||||
if (deviceCode == 'brute') {
|
if (deviceCode == 'brute') {
|
||||||
throw AuthorizationException(ErrorResponse(
|
throw AuthorizationException(ErrorResponse(
|
||||||
ErrorResponse.slowDown,
|
ErrorResponse.slowDown,
|
||||||
"Ho, brother! Ho, whoa, whoa, whoa now! You got too much dip on your chip!",
|
'Ho, brother! Ho, whoa, whoa, whoa now! You got too much dip on your chip!',
|
||||||
state));
|
state));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:angel_test/angel_test.dart';
|
import 'package:angel3_test/angel3_test.dart';
|
||||||
import 'package:angel_oauth2/angel_oauth2.dart';
|
import 'package:angel3_oauth2/angel3_oauth2.dart';
|
||||||
import 'package:angel_validate/angel_validate.dart';
|
import 'package:angel3_validate/angel3_validate.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'common.dart';
|
import 'common.dart';
|
||||||
|
|
||||||
main() {
|
void main() {
|
||||||
TestClient client;
|
late TestClient client;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
var app = Angel();
|
var app = Angel();
|
||||||
|
@ -30,7 +30,8 @@ main() {
|
||||||
|
|
||||||
test('authenticate via implicit grant', () async {
|
test('authenticate via implicit grant', () async {
|
||||||
var response = await client.get(
|
var response = await client.get(
|
||||||
'/oauth2/authorize?response_type=token&client_id=foo&redirect_uri=http://foo.com&state=bar',
|
Uri.parse(
|
||||||
|
'/oauth2/authorize?response_type=token&client_id=foo&redirect_uri=http://foo.com&state=bar'),
|
||||||
);
|
);
|
||||||
|
|
||||||
print('Headers: ${response.headers}');
|
print('Headers: ${response.headers}');
|
||||||
|
@ -47,27 +48,27 @@ main() {
|
||||||
class _AuthorizationServer
|
class _AuthorizationServer
|
||||||
extends AuthorizationServer<PseudoApplication, PseudoUser> {
|
extends AuthorizationServer<PseudoApplication, PseudoUser> {
|
||||||
@override
|
@override
|
||||||
PseudoApplication findClient(String clientId) {
|
PseudoApplication? findClient(String? clientId) {
|
||||||
return clientId == pseudoApplication.id ? pseudoApplication : null;
|
return clientId == pseudoApplication.id ? pseudoApplication : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> verifyClient(
|
Future<bool> verifyClient(
|
||||||
PseudoApplication client, String clientSecret) async {
|
PseudoApplication client, String? clientSecret) async {
|
||||||
return client.secret == clientSecret;
|
return client.secret == clientSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> requestAuthorizationCode(
|
Future<void> requestAuthorizationCode(
|
||||||
PseudoApplication client,
|
PseudoApplication client,
|
||||||
String redirectUri,
|
String? redirectUri,
|
||||||
Iterable<String> scopes,
|
Iterable<String> scopes,
|
||||||
String state,
|
String state,
|
||||||
RequestContext req,
|
RequestContext req,
|
||||||
ResponseContext res,
|
ResponseContext res,
|
||||||
bool implicit) async {
|
bool implicit) async {
|
||||||
var tok = AuthorizationTokenResponse('foo');
|
var tok = AuthorizationTokenResponse('foo');
|
||||||
var uri = completeImplicitGrant(tok, Uri.parse(redirectUri), state: state);
|
var uri = completeImplicitGrant(tok, Uri.parse(redirectUri!), state: state);
|
||||||
return res.redirect(uri);
|
return res.redirect(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:angel_framework/http.dart';
|
import 'package:angel3_framework/http.dart';
|
||||||
import 'package:angel_oauth2/angel_oauth2.dart';
|
import 'package:angel3_oauth2/angel3_oauth2.dart';
|
||||||
|
import 'package:collection/collection.dart' show IterableExtension;
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:oauth2/oauth2.dart' as oauth2;
|
import 'package:oauth2/oauth2.dart' as oauth2;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'common.dart';
|
import 'common.dart';
|
||||||
|
|
||||||
main() {
|
void main() {
|
||||||
Angel app;
|
late Angel app;
|
||||||
Uri tokenEndpoint;
|
late Uri tokenEndpoint;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
app = Angel();
|
app = Angel();
|
||||||
|
@ -50,7 +51,7 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('force correct username+password', () async {
|
test('force correct username+password', () async {
|
||||||
oauth2.Client client;
|
oauth2.Client? client;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
client = await oauth2.resourceOwnerPasswordGrant(
|
client = await oauth2.resourceOwnerPasswordGrant(
|
||||||
|
@ -88,20 +89,20 @@ main() {
|
||||||
class _AuthorizationServer
|
class _AuthorizationServer
|
||||||
extends AuthorizationServer<PseudoApplication, PseudoUser> {
|
extends AuthorizationServer<PseudoApplication, PseudoUser> {
|
||||||
@override
|
@override
|
||||||
PseudoApplication findClient(String clientId) {
|
PseudoApplication? findClient(String? clientId) {
|
||||||
return clientId == pseudoApplication.id ? pseudoApplication : null;
|
return clientId == pseudoApplication.id ? pseudoApplication : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> verifyClient(
|
Future<bool> verifyClient(
|
||||||
PseudoApplication client, String clientSecret) async {
|
PseudoApplication client, String? clientSecret) async {
|
||||||
return client.secret == clientSecret;
|
return client.secret == clientSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<AuthorizationTokenResponse> refreshAuthorizationToken(
|
Future<AuthorizationTokenResponse> refreshAuthorizationToken(
|
||||||
PseudoApplication client,
|
PseudoApplication? client,
|
||||||
String refreshToken,
|
String? refreshToken,
|
||||||
Iterable<String> scopes,
|
Iterable<String> scopes,
|
||||||
RequestContext req,
|
RequestContext req,
|
||||||
ResponseContext res) async {
|
ResponseContext res) async {
|
||||||
|
@ -110,15 +111,14 @@ class _AuthorizationServer
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<AuthorizationTokenResponse> resourceOwnerPasswordCredentialsGrant(
|
Future<AuthorizationTokenResponse> resourceOwnerPasswordCredentialsGrant(
|
||||||
PseudoApplication client,
|
PseudoApplication? client,
|
||||||
String username,
|
String? username,
|
||||||
String password,
|
String? password,
|
||||||
Iterable<String> scopes,
|
Iterable<String> scopes,
|
||||||
RequestContext req,
|
RequestContext req,
|
||||||
ResponseContext res) async {
|
ResponseContext res) async {
|
||||||
var user = pseudoUsers.firstWhere(
|
var user = pseudoUsers.firstWhereOrNull(
|
||||||
(u) => u.username == username && u.password == password,
|
(u) => u.username == username && u.password == password);
|
||||||
orElse: () => null);
|
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
var body = await req.parseBody().then((_) => req.bodyAsMap);
|
var body = await req.parseBody().then((_) => req.bodyAsMap);
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel3_framework/angel3_framework.dart';
|
||||||
import 'package:angel_framework/http.dart';
|
import 'package:angel3_framework/http.dart';
|
||||||
import 'package:angel_oauth2/angel_oauth2.dart';
|
import 'package:angel3_oauth2/angel3_oauth2.dart';
|
||||||
import 'package:angel_test/angel_test.dart';
|
import 'package:angel3_test/angel3_test.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'common.dart';
|
import 'common.dart';
|
||||||
|
|
||||||
main() {
|
void main() {
|
||||||
Angel app;
|
Angel app;
|
||||||
Uri authorizationEndpoint, tokenEndpoint;
|
late Uri authorizationEndpoint, tokenEndpoint;
|
||||||
TestClient testClient;
|
late TestClient testClient;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
app = Angel();
|
app = Angel();
|
||||||
app.container.registerSingleton(AuthCodes());
|
app.container!.registerSingleton(AuthCodes());
|
||||||
|
|
||||||
var server = _Server();
|
var server = _Server();
|
||||||
|
|
||||||
|
@ -53,14 +53,14 @@ main() {
|
||||||
'redirect_uri': 'https://freddie.mercu.ry',
|
'redirect_uri': 'https://freddie.mercu.ry',
|
||||||
'code_challenge': 'foo',
|
'code_challenge': 'foo',
|
||||||
});
|
});
|
||||||
var response = await testClient
|
var response =
|
||||||
.get(url.toString(), headers: {'accept': 'application/json'});
|
await testClient.get(url, headers: {'accept': 'application/json'});
|
||||||
print(response.body);
|
print(response.body);
|
||||||
expect(
|
expect(
|
||||||
response,
|
response,
|
||||||
allOf(
|
allOf(
|
||||||
hasStatus(200),
|
hasStatus(200),
|
||||||
isJson({"code": "ok"}),
|
isJson({'code': 'ok'}),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -72,14 +72,14 @@ main() {
|
||||||
'code_challenge': 'foo',
|
'code_challenge': 'foo',
|
||||||
'code_challenge_method': 'plain',
|
'code_challenge_method': 'plain',
|
||||||
});
|
});
|
||||||
var response = await testClient
|
var response =
|
||||||
.get(url.toString(), headers: {'accept': 'application/json'});
|
await testClient.get(url, headers: {'accept': 'application/json'});
|
||||||
print(response.body);
|
print(response.body);
|
||||||
expect(
|
expect(
|
||||||
response,
|
response,
|
||||||
allOf(
|
allOf(
|
||||||
hasStatus(200),
|
hasStatus(200),
|
||||||
isJson({"code": "ok"}),
|
isJson({'code': 'ok'}),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -91,14 +91,14 @@ main() {
|
||||||
'code_challenge': 'foo',
|
'code_challenge': 'foo',
|
||||||
'code_challenge_method': 's256',
|
'code_challenge_method': 's256',
|
||||||
});
|
});
|
||||||
var response = await testClient
|
var response =
|
||||||
.get(url.toString(), headers: {'accept': 'application/json'});
|
await testClient.get(url, headers: {'accept': 'application/json'});
|
||||||
print(response.body);
|
print(response.body);
|
||||||
expect(
|
expect(
|
||||||
response,
|
response,
|
||||||
allOf(
|
allOf(
|
||||||
hasStatus(200),
|
hasStatus(200),
|
||||||
isJson({"code": "ok"}),
|
isJson({'code': 'ok'}),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -110,16 +110,16 @@ main() {
|
||||||
'code_challenge': 'foo',
|
'code_challenge': 'foo',
|
||||||
'code_challenge_method': 'bar',
|
'code_challenge_method': 'bar',
|
||||||
});
|
});
|
||||||
var response = await testClient
|
var response =
|
||||||
.get(url.toString(), headers: {'accept': 'application/json'});
|
await testClient.get(url, headers: {'accept': 'application/json'});
|
||||||
print(response.body);
|
print(response.body);
|
||||||
expect(
|
expect(
|
||||||
response,
|
response,
|
||||||
allOf(
|
allOf(
|
||||||
hasStatus(400),
|
hasStatus(400),
|
||||||
isJson({
|
isJson({
|
||||||
"error": "invalid_request",
|
'error': 'invalid_request',
|
||||||
"error_description":
|
'error_description':
|
||||||
"The `code_challenge_method` parameter must be either 'plain' or 's256'."
|
"The `code_challenge_method` parameter must be either 'plain' or 's256'."
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
|
@ -131,16 +131,16 @@ main() {
|
||||||
'client_id': 'freddie mercury',
|
'client_id': 'freddie mercury',
|
||||||
'redirect_uri': 'https://freddie.mercu.ry'
|
'redirect_uri': 'https://freddie.mercu.ry'
|
||||||
});
|
});
|
||||||
var response = await testClient
|
var response =
|
||||||
.get(url.toString(), headers: {'accept': 'application/json'});
|
await testClient.get(url, headers: {'accept': 'application/json'});
|
||||||
print(response.body);
|
print(response.body);
|
||||||
expect(
|
expect(
|
||||||
response,
|
response,
|
||||||
allOf(
|
allOf(
|
||||||
hasStatus(400),
|
hasStatus(400),
|
||||||
isJson({
|
isJson({
|
||||||
"error": "invalid_request",
|
'error': 'invalid_request',
|
||||||
"error_description": "Missing `code_challenge` parameter."
|
'error_description': 'Missing `code_challenge` parameter.'
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
@ -150,7 +150,7 @@ main() {
|
||||||
test('with correct verifier', () async {
|
test('with correct verifier', () async {
|
||||||
var url = tokenEndpoint.replace(
|
var url = tokenEndpoint.replace(
|
||||||
userInfo: '${pseudoApplication.id}:${pseudoApplication.secret}');
|
userInfo: '${pseudoApplication.id}:${pseudoApplication.secret}');
|
||||||
var response = await testClient.post(url.toString(), headers: {
|
var response = await testClient.post(url, headers: {
|
||||||
'accept': 'application/json',
|
'accept': 'application/json',
|
||||||
// 'authorization': 'Basic ' + base64Url.encode(ascii.encode(url.userInfo))
|
// 'authorization': 'Basic ' + base64Url.encode(ascii.encode(url.userInfo))
|
||||||
}, body: {
|
}, body: {
|
||||||
|
@ -166,13 +166,13 @@ main() {
|
||||||
response,
|
response,
|
||||||
allOf(
|
allOf(
|
||||||
hasStatus(200),
|
hasStatus(200),
|
||||||
isJson({"token_type": "bearer", "access_token": "yes"}),
|
isJson({'token_type': 'bearer', 'access_token': 'yes'}),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
test('with incorrect verifier', () async {
|
test('with incorrect verifier', () async {
|
||||||
var url = tokenEndpoint.replace(
|
var url = tokenEndpoint.replace(
|
||||||
userInfo: '${pseudoApplication.id}:${pseudoApplication.secret}');
|
userInfo: '${pseudoApplication.id}:${pseudoApplication.secret}');
|
||||||
var response = await testClient.post(url.toString(), headers: {
|
var response = await testClient.post(url, headers: {
|
||||||
'accept': 'application/json',
|
'accept': 'application/json',
|
||||||
// 'authorization': 'Basic ' + base64Url.encode(ascii.encode(url.userInfo))
|
// 'authorization': 'Basic ' + base64Url.encode(ascii.encode(url.userInfo))
|
||||||
}, body: {
|
}, body: {
|
||||||
|
@ -189,9 +189,9 @@ main() {
|
||||||
allOf(
|
allOf(
|
||||||
hasStatus(400),
|
hasStatus(400),
|
||||||
isJson({
|
isJson({
|
||||||
"error": "invalid_grant",
|
'error': 'invalid_grant',
|
||||||
"error_description":
|
'error_description':
|
||||||
"The given `code_verifier` parameter is invalid."
|
'The given `code_verifier` parameter is invalid.'
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
@ -199,7 +199,7 @@ main() {
|
||||||
test('with missing verifier', () async {
|
test('with missing verifier', () async {
|
||||||
var url = tokenEndpoint.replace(
|
var url = tokenEndpoint.replace(
|
||||||
userInfo: '${pseudoApplication.id}:${pseudoApplication.secret}');
|
userInfo: '${pseudoApplication.id}:${pseudoApplication.secret}');
|
||||||
var response = await testClient.post(url.toString(), headers: {
|
var response = await testClient.post(url, headers: {
|
||||||
'accept': 'application/json',
|
'accept': 'application/json',
|
||||||
// 'authorization': 'Basic ' + base64Url.encode(ascii.encode(url.userInfo))
|
// 'authorization': 'Basic ' + base64Url.encode(ascii.encode(url.userInfo))
|
||||||
}, body: {
|
}, body: {
|
||||||
|
@ -215,8 +215,8 @@ main() {
|
||||||
allOf(
|
allOf(
|
||||||
hasStatus(400),
|
hasStatus(400),
|
||||||
isJson({
|
isJson({
|
||||||
"error": "invalid_request",
|
'error': 'invalid_request',
|
||||||
"error_description": "Missing `code_verifier` parameter."
|
'error_description': 'Missing `code_verifier` parameter.'
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
@ -225,34 +225,34 @@ main() {
|
||||||
|
|
||||||
class _Server extends AuthorizationServer<PseudoApplication, Map> {
|
class _Server extends AuthorizationServer<PseudoApplication, Map> {
|
||||||
@override
|
@override
|
||||||
FutureOr<PseudoApplication> findClient(String clientId) {
|
FutureOr<PseudoApplication> findClient(String? clientId) {
|
||||||
return pseudoApplication;
|
return pseudoApplication;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> verifyClient(
|
Future<bool> verifyClient(
|
||||||
PseudoApplication client, String clientSecret) async {
|
PseudoApplication client, String? clientSecret) async {
|
||||||
return client.secret == clientSecret;
|
return client.secret == clientSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future requestAuthorizationCode(
|
Future requestAuthorizationCode(
|
||||||
PseudoApplication client,
|
PseudoApplication client,
|
||||||
String redirectUri,
|
String? redirectUri,
|
||||||
Iterable<String> scopes,
|
Iterable<String> scopes,
|
||||||
String state,
|
String state,
|
||||||
RequestContext req,
|
RequestContext req,
|
||||||
ResponseContext res,
|
ResponseContext res,
|
||||||
bool implicit) async {
|
bool implicit) async {
|
||||||
req.container.make<Pkce>();
|
req.container!.make<Pkce>();
|
||||||
return {'code': 'ok'};
|
return {'code': 'ok'};
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
|
Future<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
|
||||||
PseudoApplication client,
|
PseudoApplication? client,
|
||||||
String authCode,
|
String? authCode,
|
||||||
String redirectUri,
|
String? redirectUri,
|
||||||
RequestContext req,
|
RequestContext req,
|
||||||
ResponseContext res) async {
|
ResponseContext res) async {
|
||||||
var codeVerifier = await getPkceCodeVerifier(req);
|
var codeVerifier = await getPkceCodeVerifier(req);
|
||||||
|
@ -266,7 +266,7 @@ class AuthCodes extends MapBase<String, String> with MapMixin<String, String> {
|
||||||
var inner = <String, String>{};
|
var inner = <String, String>{};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String operator [](Object key) => inner[key];
|
String? operator [](Object? key) => inner[key as String];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void operator []=(String key, String value) => inner[key] = value;
|
void operator []=(String key, String value) => inner[key] = value;
|
||||||
|
@ -278,5 +278,5 @@ class AuthCodes extends MapBase<String, String> with MapMixin<String, String> {
|
||||||
Iterable<String> get keys => inner.keys;
|
Iterable<String> get keys => inner.keys;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String remove(Object key) => inner.remove(key);
|
String? remove(Object? key) => inner.remove(key);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue