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
|
||||
* Angel 2 + Dart 2 update
|
||||
* 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
|
||||
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
|
||||
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
|
||||
configureServer(Angel app) async {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'dart:convert';
|
||||
import 'package:angel_auth/angel_auth.dart';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:angel_auth_oauth2/angel_auth_oauth2.dart';
|
||||
import 'package:angel3_auth/angel3_auth.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:angel3_framework/http.dart';
|
||||
import 'package:angel3_auth_oauth2/angel3_auth_oauth2.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
|
@ -63,7 +63,7 @@ void main() async {
|
|||
(client, req, res) async {
|
||||
var response = await client.get(Uri.parse('https://api.github.com/user'));
|
||||
var ghUser = json.decode(response.body);
|
||||
var id = ghUser['id'] as int;
|
||||
var id = ghUser['id'] as int?;
|
||||
|
||||
var matchingUsers = await mappedUserService.index({
|
||||
'query': {'github_id': id}
|
||||
|
@ -85,7 +85,7 @@ void main() async {
|
|||
},
|
||||
|
||||
// We have to pass this parser function when working with Github.
|
||||
getParameters: parseParamsFromGithub,
|
||||
//getParameters: parseParamsFromGithub,
|
||||
);
|
||||
|
||||
// Mount some routes
|
||||
|
@ -98,7 +98,7 @@ void main() async {
|
|||
//
|
||||
// Use `confirmPopupAuthentication`, which is bundled with
|
||||
// `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 JWT: $jwt');
|
||||
await res.close();
|
||||
|
@ -113,14 +113,14 @@ void main() async {
|
|||
|
||||
class User extends Model {
|
||||
@override
|
||||
String id;
|
||||
String? id;
|
||||
|
||||
int githubId;
|
||||
int? githubId;
|
||||
|
||||
User({this.id, this.githubId});
|
||||
|
||||
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();
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
library angel_auth_oauth2;
|
||||
library angel3_auth_oauth2;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:angel_auth/angel_auth.dart';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel3_auth/angel3_auth.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:oauth2/oauth2.dart' as oauth2;
|
||||
|
||||
|
@ -28,33 +28,33 @@ class OAuth2Strategy<User> implements AuthStrategy<User> {
|
|||
final Uri tokenEndpoint;
|
||||
|
||||
/// An optional callback used to parse the response from a server who does not follow the OAuth 2.0 spec.
|
||||
final Map<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.
|
||||
final String delimiter;
|
||||
|
||||
Uri _redirect;
|
||||
Uri? _redirect;
|
||||
|
||||
OAuth2Strategy(this.options, this.authorizationEndpoint, this.tokenEndpoint,
|
||||
this.verifier, this.onError,
|
||||
{this.getParameters, this.delimiter = ' '});
|
||||
|
||||
oauth2.AuthorizationCodeGrant _createGrant() =>
|
||||
new oauth2.AuthorizationCodeGrant(options.clientId, authorizationEndpoint,
|
||||
tokenEndpoint,
|
||||
secret: options.clientSecret,
|
||||
delimiter: delimiter,
|
||||
getParameters: getParameters);
|
||||
oauth2.AuthorizationCodeGrant _createGrant() => oauth2.AuthorizationCodeGrant(
|
||||
options.clientId, authorizationEndpoint, tokenEndpoint,
|
||||
secret: options.clientSecret,
|
||||
delimiter: delimiter,
|
||||
getParameters: getParameters);
|
||||
|
||||
@override
|
||||
FutureOr<User> authenticate(RequestContext req, ResponseContext res,
|
||||
[AngelAuthOptions<User> options]) async {
|
||||
FutureOr<User?> authenticate(RequestContext req, ResponseContext res,
|
||||
[AngelAuthOptions<User>? options]) async {
|
||||
if (options != null) {
|
||||
var result = await authenticateCallback(req, res, options);
|
||||
if (result is User)
|
||||
if (result is User) {
|
||||
return result;
|
||||
else
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (_redirect == null) {
|
||||
|
@ -65,20 +65,20 @@ class OAuth2Strategy<User> implements AuthStrategy<User> {
|
|||
);
|
||||
}
|
||||
|
||||
res.redirect(_redirect);
|
||||
await res.redirect(_redirect);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// The endpoint that is invoked by the third-party after successful authentication.
|
||||
Future<dynamic> authenticateCallback(RequestContext req, ResponseContext res,
|
||||
[AngelAuthOptions options]) async {
|
||||
[AngelAuthOptions? options]) async {
|
||||
var grant = _createGrant();
|
||||
grant.getAuthorizationUrl(this.options.redirectUri,
|
||||
scopes: this.options.scopes);
|
||||
|
||||
try {
|
||||
var client =
|
||||
await grant.handleAuthorizationResponse(req.uri.queryParameters);
|
||||
await grant.handleAuthorizationResponse(req.uri!.queryParameters);
|
||||
return await verifier(client, req, res);
|
||||
} on oauth2.AuthorizationException catch (e) {
|
||||
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.
|
||||
version: 3.0.0
|
||||
#author: Tobe O <thosakwe@gmail.com>
|
||||
publish_to: none
|
||||
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/auth_oauth2
|
||||
environment:
|
||||
sdk: ">=2.10.0 <3.0.0"
|
||||
homepage: https://github.com/angel-dart/auth_oauth2.git
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
dependencies:
|
||||
angel_auth:
|
||||
git:
|
||||
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
|
||||
angel3_auth: ^4.0.0
|
||||
angel3_framework: ^4.0.0
|
||||
http_parser: ^4.0.0
|
||||
oauth2: ^2.0.0
|
||||
dev_dependencies:
|
||||
logging: ^1.0.0
|
||||
pedantic: ^1.0.0
|
||||
logging: ^1.0.1
|
||||
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
|
||||
* Add `ignoreQueryAndFragment` to `ResponseCache`.
|
||||
* Rename `CacheService.ignoreQuery` to `ignoreParams`.
|
||||
|
|
27
packages/cache/README.md
vendored
27
packages/cache/README.md
vendored
|
@ -1,6 +1,9 @@
|
|||
# cache
|
||||
[![Pub](https://img.shields.io/pub/v/angel_cache.svg)](https://pub.dartlang.org/packages/angel_cache)
|
||||
[![build status](https://travis-ci.org/angel-dart/cache.svg)](https://travis-ci.org/angel-dart/cache)
|
||||
# angel3_cache
|
||||
[![version](https://img.shields.io/badge/pub-v4.0.0-brightgreen)](https://pub.dartlang.org/packages/angel3_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).
|
||||
|
||||
|
@ -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.
|
||||
|
||||
```dart
|
||||
main() async {
|
||||
var app = new Angel()..lazyParseBodies = true;
|
||||
void main() async {
|
||||
var app = Angel()..lazyParseBodies = true;
|
||||
|
||||
app.use(
|
||||
'/api/todos',
|
||||
new CacheService(
|
||||
database: new AnonymousService(
|
||||
CacheService(
|
||||
database: AnonymousService(
|
||||
index: ([params]) {
|
||||
print('Fetched directly from the underlying service at ${new DateTime.now()}!');
|
||||
return ['foo', 'bar', 'baz'];
|
||||
|
@ -51,10 +54,10 @@ To initialize a simple cache:
|
|||
```dart
|
||||
Future configureServer(Angel app) async {
|
||||
// Simple instance.
|
||||
var cache = new ResponseCache();
|
||||
var cache = ResponseCache();
|
||||
|
||||
// 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.
|
||||
app.shutdownHooks.add((_) => cache.close());
|
||||
|
@ -62,8 +65,8 @@ Future configureServer(Angel app) async {
|
|||
// Use `patterns` to specify which resources should be cached.
|
||||
cache.patterns.addAll([
|
||||
'robots.txt',
|
||||
new RegExp(r'\.(png|jpg|gif|txt)$'),
|
||||
new Glob('public/**/*'),
|
||||
RegExp(r'\.(png|jpg|gif|txt)$'),
|
||||
Glob('public/**/*'),
|
||||
]);
|
||||
|
||||
// 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 {
|
||||
app.addRoute('PURGE', '*', (req, res) {
|
||||
if (req.ip != '127.0.0.1')
|
||||
throw new AngelHttpException.forbidden();
|
||||
throw AngelHttpException.forbidden();
|
||||
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:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:angel3_cache/angel3_cache.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:angel3_framework/http.dart';
|
||||
|
||||
main() async {
|
||||
var app = Angel();
|
||||
|
@ -13,7 +13,7 @@ main() async {
|
|||
print(
|
||||
'Fetched directly from the underlying service at ${new DateTime.now()}!');
|
||||
return ['foo', 'bar', 'baz'];
|
||||
}, read: (id, [params]) {
|
||||
}, read: (dynamic id, [params]) {
|
||||
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:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:angel3_cache/angel3_cache.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:angel3_framework/http.dart';
|
||||
import 'package:glob/glob.dart';
|
||||
|
||||
main() async {
|
||||
|
@ -23,8 +23,8 @@ main() async {
|
|||
app.addRoute('PURGE', '*', (req, res) {
|
||||
if (req.ip != '127.0.0.1') throw AngelHttpException.forbidden();
|
||||
|
||||
cache.purge(req.uri.path);
|
||||
print('Purged ${req.uri.path}');
|
||||
cache.purge(req.uri!.path);
|
||||
print('Purged ${req.uri!.path}');
|
||||
});
|
||||
|
||||
// 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:io' show HttpDate;
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:pool/pool.dart';
|
||||
|
||||
/// A flexible response cache for Angel.
|
||||
|
@ -14,7 +14,7 @@ class ResponseCache {
|
|||
final List<Pattern> patterns = [];
|
||||
|
||||
/// 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, Pool> _writeLocks = {};
|
||||
|
@ -38,21 +38,21 @@ class ResponseCache {
|
|||
Future<bool> ifModifiedSince(RequestContext req, ResponseContext res) async {
|
||||
if (req.method != 'GET' && req.method != 'HEAD') return true;
|
||||
|
||||
if (req.headers.ifModifiedSince != null) {
|
||||
var modifiedSince = req.headers.ifModifiedSince;
|
||||
if (req.headers!.ifModifiedSince != null) {
|
||||
var modifiedSince = req.headers!.ifModifiedSince;
|
||||
|
||||
// Check if there is a cache entry.
|
||||
for (var pattern in patterns) {
|
||||
if (pattern.allMatches(_getEffectivePath(req)).isNotEmpty &&
|
||||
_cache.containsKey(_getEffectivePath(req))) {
|
||||
var response = _cache[_getEffectivePath(req)];
|
||||
var response = _cache[_getEffectivePath(req)]!;
|
||||
//print('timestamp ${response.timestamp} vs since ${modifiedSince}');
|
||||
|
||||
if (response.timestamp.compareTo(modifiedSince) <= 0) {
|
||||
if (response.timestamp.compareTo(modifiedSince!) <= 0) {
|
||||
if (timeout != null) {
|
||||
// If the cache timeout has been met, don't send the cached response.
|
||||
if (new DateTime.now().toUtc().difference(response.timestamp) >=
|
||||
timeout) return true;
|
||||
timeout!) return true;
|
||||
}
|
||||
|
||||
res.statusCode = 304;
|
||||
|
@ -66,7 +66,7 @@ class ResponseCache {
|
|||
}
|
||||
|
||||
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.
|
||||
Future<bool> handleRequest(RequestContext req, ResponseContext res) async {
|
||||
|
@ -77,7 +77,7 @@ class ResponseCache {
|
|||
// Check if there is a cache entry.
|
||||
//
|
||||
// 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) {
|
||||
if (pattern.allMatches(_getEffectivePath(req)).isNotEmpty) {
|
||||
var now = new DateTime.now().toUtc();
|
||||
|
@ -87,10 +87,10 @@ class ResponseCache {
|
|||
|
||||
if (timeout != null) {
|
||||
// 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
|
||||
..headers.addAll(response.headers)
|
||||
..add(response.body)
|
||||
|
@ -123,8 +123,8 @@ class ResponseCache {
|
|||
if (timeout == null) return true;
|
||||
|
||||
// Otherwise, don't invalidate unless the timeout has been exceeded.
|
||||
var response = _cache[_getEffectivePath(req)];
|
||||
if (now.difference(response.timestamp) < timeout) return true;
|
||||
var response = _cache[_getEffectivePath(req)]!;
|
||||
if (now.difference(response.timestamp) < timeout!) return true;
|
||||
|
||||
// If the cache entry should be invalidated, then invalidate it.
|
||||
purge(_getEffectivePath(req));
|
||||
|
@ -135,7 +135,7 @@ class ResponseCache {
|
|||
_writeLocks.putIfAbsent(_getEffectivePath(req), () => new Pool(1));
|
||||
await writeLock.withResource(() {
|
||||
_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);
|
||||
|
@ -154,7 +154,7 @@ class ResponseCache {
|
|||
..['last-modified'] = HttpDate.format(modified);
|
||||
|
||||
if (timeout != null) {
|
||||
var expiry = new DateTime.now().add(timeout);
|
||||
var expiry = new DateTime.now().add(timeout!);
|
||||
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 'package:collection/collection.dart';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
|
||||
/// 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.
|
||||
final bool ignoreParams;
|
||||
|
||||
final Duration timeout;
|
||||
final Duration? timeout;
|
||||
|
||||
final Map<Id, _CachedItem<Data>> _cache = {};
|
||||
_CachedItem<List<Data>> _indexed;
|
||||
_CachedItem<List<Data>>? _indexed;
|
||||
|
||||
CacheService(
|
||||
{@required this.database,
|
||||
@required this.cache,
|
||||
{required this.database,
|
||||
required this.cache,
|
||||
this.ignoreParams: false,
|
||||
this.timeout}) {
|
||||
assert(database != null);
|
||||
}
|
||||
this.timeout}) {}
|
||||
|
||||
Future<T> _getCached<T>(
|
||||
Map<String, dynamic> params,
|
||||
_CachedItem get(),
|
||||
Map<String, dynamic>? params,
|
||||
_CachedItem? get(),
|
||||
FutureOr<T> getFresh(),
|
||||
FutureOr<T> getCached(),
|
||||
FutureOr<T> save(T data, DateTime now)) async {
|
||||
|
@ -46,7 +43,7 @@ class CacheService<Id, Data> extends Service<Id, Data> {
|
|||
if (cached != null) {
|
||||
// If the entry has expired, don't send from the cache
|
||||
var expired =
|
||||
timeout != null && now.difference(cached.timestamp) >= timeout;
|
||||
timeout != null && now.difference(cached.timestamp) >= timeout!;
|
||||
|
||||
if (timeout == null || !expired) {
|
||||
// Read from the cache if necessary
|
||||
|
@ -54,7 +51,7 @@ class CacheService<Id, Data> extends Service<Id, Data> {
|
|||
(params != null &&
|
||||
cached.params != null &&
|
||||
const MapEquality().equals(
|
||||
params['query'] as Map, cached.params['query'] as Map));
|
||||
params['query'] as Map?, cached.params['query'] as Map?));
|
||||
if (queryEqual) {
|
||||
return await getCached();
|
||||
}
|
||||
|
@ -69,7 +66,7 @@ class CacheService<Id, Data> extends Service<Id, Data> {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<List<Data>> index([Map<String, dynamic> params]) {
|
||||
Future<List<Data>> index([Map<String, dynamic>? params]) {
|
||||
return _getCached(
|
||||
params,
|
||||
() => _indexed,
|
||||
|
@ -83,7 +80,7 @@ class CacheService<Id, Data> extends Service<Id, Data> {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<Data> read(Id id, [Map<String, dynamic> params]) async {
|
||||
Future<Data> read(Id id, [Map<String, dynamic>? params]) async {
|
||||
return _getCached<Data>(
|
||||
params,
|
||||
() => _cache[id],
|
||||
|
@ -97,27 +94,27 @@ class CacheService<Id, Data> extends Service<Id, Data> {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<Data> create(data, [Map<String, dynamic> params]) {
|
||||
Future<Data> create(data, [Map<String, dynamic>? params]) {
|
||||
_indexed = null;
|
||||
return database.create(data, params);
|
||||
}
|
||||
|
||||
@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;
|
||||
_cache.remove(id);
|
||||
return database.modify(id, data, params);
|
||||
}
|
||||
|
||||
@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;
|
||||
_cache.remove(id);
|
||||
return database.modify(id, data, params);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Data> remove(Id id, [Map<String, dynamic> params]) {
|
||||
Future<Data> remove(Id id, [Map<String, dynamic>? params]) {
|
||||
_indexed = null;
|
||||
_cache.remove(id);
|
||||
return database.remove(id, params);
|
||||
|
@ -127,7 +124,7 @@ class CacheService<Id, Data> extends Service<Id, Data> {
|
|||
class _CachedItem<Data> {
|
||||
final params;
|
||||
final DateTime timestamp;
|
||||
final Data data;
|
||||
final Data? 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 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
|
||||
/// 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.
|
||||
RequestHandler cacheSerializationResults(
|
||||
{Duration timeout,
|
||||
FutureOr<bool> Function(RequestContext, ResponseContext, Object)
|
||||
{Duration? timeout,
|
||||
FutureOr<bool> Function(RequestContext, ResponseContext, Object)?
|
||||
shouldCache}) {
|
||||
return (RequestContext req, ResponseContext res) async {
|
||||
var oldSerializer = res.serializer;
|
||||
|
@ -20,7 +20,7 @@ RequestHandler cacheSerializationResults(
|
|||
// return cache.putIfAbsent(value, () => oldSerializer(value));
|
||||
//}
|
||||
|
||||
return oldSerializer(value);
|
||||
return oldSerializer!(value);
|
||||
};
|
||||
|
||||
return true;
|
||||
|
|
37
packages/cache/pubspec.yaml
vendored
37
packages/cache/pubspec.yaml
vendored
|
@ -1,26 +1,17 @@
|
|||
name: angel_cache
|
||||
version: 3.0.0
|
||||
homepage: https://github.com/angel-dart/cache
|
||||
description: Support for server-side caching in Angel.
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
publish_to: none
|
||||
name: angel3_cache
|
||||
version: 4.0.0
|
||||
description: Support for server-side caching in Angel3 Framework
|
||||
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/cache
|
||||
environment:
|
||||
sdk: ">=2.10.0 <3.0.0"
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
dependencies:
|
||||
angel_framework:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x
|
||||
path: packages/framework
|
||||
collection: ^1.0.0
|
||||
meta: ^1.0.0
|
||||
pool: ^1.0.0
|
||||
angel3_framework: ^4.0.0
|
||||
collection: ^1.15.0
|
||||
meta: ^1.4.0
|
||||
pool: ^1.5.0
|
||||
dev_dependencies:
|
||||
angel_test:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x
|
||||
path: packages/test
|
||||
glob: ^2.0.0
|
||||
http: ^0.13.0
|
||||
test: ^1.16.5
|
||||
angel3_test: ^4.0.0
|
||||
glob: ^2.0.1
|
||||
http: ^0.13.3
|
||||
test: ^1.17.5
|
||||
pedantic: ^1.11.0
|
||||
|
|
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:io';
|
||||
import 'package:angel_cache/angel_cache.dart';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_test/angel_test.dart';
|
||||
import 'package:angel3_cache/angel3_cache.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:angel3_test/angel3_test.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:glob/glob.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
main() async {
|
||||
group('no timeout', () {
|
||||
TestClient client;
|
||||
DateTime lastModified;
|
||||
http.Response response1, response2;
|
||||
late TestClient client;
|
||||
late DateTime lastModified;
|
||||
late http.Response response1, response2;
|
||||
|
||||
setUp(() async {
|
||||
var app = new Angel();
|
||||
|
@ -29,8 +29,8 @@ main() async {
|
|||
});
|
||||
|
||||
app.addRoute('PURGE', '*', (req, res) {
|
||||
cache.purge(req.uri.path);
|
||||
print('Purged ${req.uri.path}');
|
||||
cache.purge(req.uri!.path);
|
||||
print('Purged ${req.uri!.path}');
|
||||
});
|
||||
|
||||
app.responseFinalizers.add(cache.responseFinalizer);
|
||||
|
@ -38,14 +38,15 @@ main() async {
|
|||
var oldHandler = app.errorHandler;
|
||||
app.errorHandler = (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);
|
||||
response1 = await client.get(Uri.parse('/date.txt'));
|
||||
response2 = await client.get(Uri.parse('/date.txt'));
|
||||
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 2 status: ${response2.statusCode}');
|
||||
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
|
||||
* Updates for Dart 2 and Angel 2.
|
|
@ -1,6 +1,10 @@
|
|||
# cors
|
||||
[![Pub](https://img.shields.io/pub/v/angel_cors.svg)](https://pub.dartlang.org/packages/angel_cors)
|
||||
[![build status](https://travis-ci.org/angel-dart/cors.svg)](https://travis-ci.org/angel-dart/cors)
|
||||
# angel3_cors
|
||||
[![version](https://img.shields.io/badge/pub-v4.0.0-brightgreen)](https://pub.dartlang.org/packages/angel3_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.
|
||||
Port of [the original Express CORS middleware](https://github.com/expressjs/cors).
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'package:angel_cors/angel_cors.dart';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel3_cors/angel3_cors.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
|
||||
Future configureServer(Angel app) async {
|
||||
// The default options will allow CORS for any request.
|
||||
|
@ -43,7 +43,7 @@ Future configureServer(Angel app) async {
|
|||
app.fallback(dynamicCors((req, res) {
|
||||
return CorsOptions(
|
||||
origin: [
|
||||
req.headers.value('origin') ?? 'https://pub.dartlang.org',
|
||||
req.headers!.value('origin') ?? 'https://pub.dartlang.org',
|
||||
RegExp(r'\.com$'),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
/// Angel CORS middleware.
|
||||
library angel_cors;
|
||||
library angel3_cors;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'src/cors_options.dart';
|
||||
export 'src/cors_options.dart';
|
||||
|
||||
/// 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 ??= [];
|
||||
if (allowedOrigin is Iterable) {
|
||||
return allowedOrigin.any((x) => _isOriginAllowed(origin, x));
|
||||
|
@ -38,12 +38,12 @@ Future<bool> Function(RequestContext, ResponseContext) dynamicCors(
|
|||
|
||||
/// Applies the given [CorsOptions].
|
||||
Future<bool> Function(RequestContext, ResponseContext) cors(
|
||||
[CorsOptions options]) {
|
||||
[CorsOptions? options]) {
|
||||
options ??= CorsOptions();
|
||||
|
||||
return (req, res) async {
|
||||
// access-control-allow-credentials
|
||||
if (options.credentials == true) {
|
||||
if (options!.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) {
|
||||
res.headers['access-control-allow-headers'] =
|
||||
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'] =
|
||||
req.headers.value('access-control-request-headers');
|
||||
req.headers!.value('access-control-request-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['vary'] = 'origin';
|
||||
} else {
|
||||
bool isAllowed =
|
||||
_isOriginAllowed(req.headers.value('origin'), options.origin);
|
||||
var isAllowed =
|
||||
_isOriginAllowed(req.headers!.value('origin'), options.origin);
|
||||
|
||||
res.headers['access-control-allow-origin'] =
|
||||
isAllowed ? req.headers.value('origin') : false.toString();
|
||||
isAllowed ? req.headers!.value('origin')! : false.toString();
|
||||
|
||||
if (isAllowed) {
|
||||
res.headers['vary'] = 'origin';
|
||||
|
@ -92,7 +92,8 @@ Future<bool> Function(RequestContext, ResponseContext) cors(
|
|||
}
|
||||
|
||||
if (req.method != 'OPTIONS') return true;
|
||||
res.statusCode = options.successStatus ?? 204;
|
||||
//res.statusCode = options.successStatus ?? 204;
|
||||
res.statusCode = options.successStatus;
|
||||
res.contentLength = 0;
|
||||
await res.close();
|
||||
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.
|
||||
///
|
||||
/// Default: `null`
|
||||
final int maxAge;
|
||||
final int? maxAge;
|
||||
|
||||
/// The status code to be sent on successful `OPTIONS` requests, if [preflightContinue] is `false`.
|
||||
final int successStatus;
|
||||
|
@ -50,7 +50,7 @@ class CorsOptions {
|
|||
|
||||
CorsOptions(
|
||||
{Iterable<String> allowedHeaders = const [],
|
||||
this.credentials,
|
||||
this.credentials = false,
|
||||
this.maxAge,
|
||||
Iterable<String> methods = const [
|
||||
'GET',
|
||||
|
@ -64,10 +64,10 @@ class CorsOptions {
|
|||
this.successStatus = 204,
|
||||
this.preflightContinue = false,
|
||||
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.
|
||||
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/cors
|
||||
environment:
|
||||
sdk: ">=2.10.0 <3.0.0"
|
||||
homepage: https://github.com/angel-dart/cors.git
|
||||
name: angel_cors
|
||||
version: 3.0.0
|
||||
publish_to: none
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
dependencies:
|
||||
angel_framework:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x
|
||||
path: packages/framework
|
||||
angel3_framework: ^4.0.0
|
||||
dev_dependencies:
|
||||
angel_test:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x
|
||||
path: packages/test
|
||||
http: ^0.13.0
|
||||
angel3_test: ^4.0.0
|
||||
http: ^0.13.3
|
||||
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:angel_framework/http.dart';
|
||||
import 'package:angel_cors/angel_cors.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:angel3_framework/http.dart';
|
||||
import 'package:angel3_cors/angel3_cors.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
main() {
|
||||
Angel app;
|
||||
AngelHttp server;
|
||||
http.Client client;
|
||||
void main() {
|
||||
Angel? app;
|
||||
late AngelHttp server;
|
||||
http.Client? client;
|
||||
|
||||
setUp(() async {
|
||||
app = Angel()
|
||||
|
@ -24,7 +24,7 @@ main() {
|
|||
cors(CorsOptions(
|
||||
origin: ['foo.bar', 'baz.quux'],
|
||||
)),
|
||||
(req, res) => req.headers['origin']
|
||||
(req, res) => req.headers!['origin']
|
||||
]))
|
||||
..get(
|
||||
'/origins',
|
||||
|
@ -32,7 +32,7 @@ main() {
|
|||
cors(CorsOptions(
|
||||
origin: 'foo.bar',
|
||||
)),
|
||||
(req, res) => req.headers['origin']
|
||||
(req, res) => req.headers!['origin']
|
||||
]))
|
||||
..get(
|
||||
'/originr',
|
||||
|
@ -40,7 +40,7 @@ main() {
|
|||
cors(CorsOptions(
|
||||
origin: RegExp(r'^foo\.[^x]+$'),
|
||||
)),
|
||||
(req, res) => req.headers['origin']
|
||||
(req, res) => req.headers!['origin']
|
||||
]))
|
||||
..get(
|
||||
'/originp',
|
||||
|
@ -48,7 +48,7 @@ main() {
|
|||
cors(CorsOptions(
|
||||
origin: (String s) => s.endsWith('.bar'),
|
||||
)),
|
||||
(req, res) => req.headers['origin']
|
||||
(req, res) => req.headers!['origin']
|
||||
]))
|
||||
..options('/status', cors(CorsOptions(successStatus: 418)))
|
||||
..fallback(cors(CorsOptions()))
|
||||
|
@ -57,7 +57,7 @@ main() {
|
|||
})
|
||||
..fallback((req, res) => throw AngelHttpException.notFound());
|
||||
|
||||
server = AngelHttp(app);
|
||||
server = AngelHttp(app!);
|
||||
await server.startServer('127.0.0.1', 0);
|
||||
client = http.Client();
|
||||
});
|
||||
|
@ -70,93 +70,93 @@ main() {
|
|||
|
||||
test('status 204 by default', () async {
|
||||
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);
|
||||
});
|
||||
|
||||
test('content length 0 by default', () async {
|
||||
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);
|
||||
});
|
||||
|
||||
test('custom successStatus', () async {
|
||||
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);
|
||||
});
|
||||
|
||||
test('max age', () async {
|
||||
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');
|
||||
});
|
||||
|
||||
test('methods', () async {
|
||||
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');
|
||||
});
|
||||
|
||||
test('dynamicCors.credentials', () async {
|
||||
var rq =
|
||||
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');
|
||||
});
|
||||
|
||||
test('credentials', () async {
|
||||
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');
|
||||
});
|
||||
|
||||
test('exposed headers', () async {
|
||||
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');
|
||||
});
|
||||
|
||||
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'});
|
||||
expect(response.headers['access-control-allow-origin'], 'false');
|
||||
});
|
||||
|
||||
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'});
|
||||
expect(response.headers['access-control-allow-origin'], 'foo.bar');
|
||||
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'});
|
||||
expect(response.headers['access-control-allow-origin'], 'baz.quux');
|
||||
expect(response.headers['vary'], 'origin');
|
||||
});
|
||||
|
||||
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'});
|
||||
expect(response.headers['access-control-allow-origin'], 'foo.bar');
|
||||
expect(response.headers['vary'], 'origin');
|
||||
});
|
||||
|
||||
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'});
|
||||
expect(response.headers['access-control-allow-origin'], 'foo.bar');
|
||||
expect(response.headers['vary'], 'origin');
|
||||
});
|
||||
|
||||
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'});
|
||||
expect(response.headers['access-control-allow-origin'], 'foo.bar');
|
||||
expect(response.headers['vary'], 'origin');
|
||||
});
|
||||
|
||||
test('POST works', () async {
|
||||
final response = await client.post(server.uri);
|
||||
final response = await client!.post(server.uri);
|
||||
expect(response.statusCode, equals(200));
|
||||
print('Response: ${response.body}');
|
||||
print('Headers: ${response.headers}');
|
||||
|
@ -164,7 +164,7 @@ main() {
|
|||
});
|
||||
|
||||
test('mirror headers', () async {
|
||||
final response = await client
|
||||
final response = await client!
|
||||
.post(server.uri, headers: {'access-control-request-headers': 'foo'});
|
||||
expect(response.statusCode, equals(200));
|
||||
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
|
||||
* Remove `implicitGrant`, and inline it into `requestAuthorizationCode`.
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
# oauth2
|
||||
[![Pub](https://img.shields.io/pub/v/angel_oauth2.svg)](https://pub.dartlang.org/packages/angel_oauth2)
|
||||
[![build status](https://travis-ci.org/angel-dart/oauth2.svg)](https://travis-ci.org/angel-dart/oauth2)
|
||||
# angel3_oauth2
|
||||
[![version](https://img.shields.io/badge/pub-v4.0.0-brightgreen)](https://pub.dartlang.org/packages/angel3_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
|
||||
[Angel](https://angel-dart.github.io/) to build a spec-compliant
|
||||
|
@ -16,8 +19,8 @@ In your `pubspec.yaml`:
|
|||
|
||||
```yaml
|
||||
dependencies:
|
||||
angel_framework: ^2.0.0-alpha
|
||||
angel_oauth2: ^2.0.0
|
||||
angel3_framework: ^4.0.0
|
||||
angel3_oauth2: ^4.0.0
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
@ -28,7 +31,7 @@ Your server needs to have definitions of at least two types:
|
|||
Define a server class as such:
|
||||
|
||||
```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> {}
|
||||
```
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'dart:async';
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_oauth2/angel_oauth2.dart';
|
||||
|
||||
main() async {
|
||||
void main() async {
|
||||
var app = Angel();
|
||||
var oauth2 = _ExampleAuthorizationServer();
|
||||
var _rgxBearer = RegExp(r'^[Bb]earer ([^\n\s]+)$');
|
||||
|
@ -17,7 +17,7 @@ main() async {
|
|||
// Assume that all other requests must be authenticated...
|
||||
app.fallback((req, res) {
|
||||
var authToken =
|
||||
req.headers.value('authorization')?.replaceAll(_rgxBearer, '')?.trim();
|
||||
req.headers!.value('authorization')?.replaceAll(_rgxBearer, '').trim();
|
||||
|
||||
if (authToken == null) {
|
||||
throw AngelHttpException.forbidden();
|
||||
|
@ -38,13 +38,13 @@ class User {}
|
|||
class _ExampleAuthorizationServer
|
||||
extends AuthorizationServer<ThirdPartyApp, User> {
|
||||
@override
|
||||
FutureOr<ThirdPartyApp> findClient(String clientId) {
|
||||
FutureOr<ThirdPartyApp> findClient(String? clientId) {
|
||||
// TODO: Add your code to find the app associated with a client ID.
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@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.
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ class _ExampleAuthorizationServer
|
|||
@override
|
||||
FutureOr requestAuthorizationCode(
|
||||
ThirdPartyApp client,
|
||||
String redirectUri,
|
||||
String? redirectUri,
|
||||
Iterable<String> scopes,
|
||||
String state,
|
||||
RequestContext req,
|
||||
|
@ -64,9 +64,9 @@ class _ExampleAuthorizationServer
|
|||
|
||||
@override
|
||||
FutureOr<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
|
||||
ThirdPartyApp client,
|
||||
String authCode,
|
||||
String redirectUri,
|
||||
ThirdPartyApp? client,
|
||||
String? authCode,
|
||||
String? redirectUri,
|
||||
RequestContext req,
|
||||
ResponseContext res) {
|
||||
// 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.
|
||||
class AuthorizationException extends AngelHttpException {
|
||||
final ErrorResponse errorResponse;
|
||||
|
||||
AuthorizationException(this.errorResponse,
|
||||
{StackTrace stackTrace, int statusCode, error})
|
||||
{StackTrace? stackTrace, int? statusCode, error})
|
||||
: super(error ?? errorResponse,
|
||||
stackTrace: stackTrace, message: '', statusCode: statusCode ?? 400);
|
||||
|
||||
|
@ -16,8 +16,9 @@ class AuthorizationException extends AngelHttpException {
|
|||
'error_description': errorResponse.description,
|
||||
};
|
||||
|
||||
if (errorResponse.uri != null)
|
||||
if (errorResponse.uri != null) {
|
||||
m['error_uri'] = errorResponse.uri.toString();
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
@ -78,10 +79,10 @@ class ErrorResponse {
|
|||
final String description;
|
||||
|
||||
/// 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.
|
||||
final String state;
|
||||
final String? state;
|
||||
|
||||
const ErrorResponse(this.code, this.description, this.state, {this.uri});
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ class Pkce {
|
|||
final String codeChallengeMethod;
|
||||
|
||||
/// The proof key that is used to secure public clients.
|
||||
final String codeChallenge;
|
||||
final String? codeChallenge;
|
||||
|
||||
Pkce(this.codeChallengeMethod, this.codeChallenge) {
|
||||
assert(codeChallengeMethod == 'plain' || codeChallengeMethod == 's256',
|
||||
|
@ -17,7 +17,7 @@ class Pkce {
|
|||
}
|
||||
|
||||
/// 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 codeChallengeMethod =
|
||||
data['code_challenge_method']?.toString() ?? 'plain';
|
||||
|
@ -44,7 +44,7 @@ class Pkce {
|
|||
bool get isS256 => codeChallengeMethod == 's256';
|
||||
|
||||
/// 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;
|
||||
|
||||
if (isS256) {
|
||||
|
@ -57,7 +57,7 @@ class Pkce {
|
|||
if (foreignChallenge != codeChallenge) {
|
||||
throw AuthorizationException(
|
||||
ErrorResponse(ErrorResponse.invalidGrant,
|
||||
"The given `code_verifier` parameter is invalid.", state,
|
||||
'The given `code_verifier` parameter is invalid.', state,
|
||||
uri: uri),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,13 +4,13 @@ class AuthorizationTokenResponse {
|
|||
final String accessToken;
|
||||
|
||||
/// 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.
|
||||
final int expiresIn;
|
||||
final int? expiresIn;
|
||||
|
||||
/// Optional, if identical to the scope requested by the client; otherwise, required.
|
||||
final Iterable<String> scope;
|
||||
final Iterable<String>? scope;
|
||||
|
||||
const AuthorizationTokenResponse(this.accessToken,
|
||||
{this.refreshToken, this.expiresIn, this.scope});
|
||||
|
@ -19,7 +19,7 @@ class AuthorizationTokenResponse {
|
|||
var map = <String, dynamic>{'access_token': accessToken};
|
||||
if (refreshToken?.isNotEmpty == true) map['refresh_token'] = refreshToken;
|
||||
if (expiresIn != null) map['expires_in'] = expiresIn;
|
||||
if (scope != null) map['scope'] = scope.toList();
|
||||
if (scope != null) map['scope'] = scope!.toList();
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
@ -40,12 +40,12 @@ class DeviceCodeResponse {
|
|||
/// OPTIONAL. A verification URI that includes the [userCode] (or
|
||||
/// other information with the same function as the [userCode]),
|
||||
/// designed for non-textual transmission.
|
||||
final Uri verificationUriComplete;
|
||||
final Uri? verificationUriComplete;
|
||||
|
||||
/// OPTIONAL. The minimum amount of time in seconds that the client
|
||||
/// SHOULD wait between polling requests to the token endpoint. If no
|
||||
/// 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].
|
||||
final int expiresIn;
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'exception.dart';
|
||||
import 'pkce.dart';
|
||||
import 'response.dart';
|
||||
import 'token_type.dart';
|
||||
|
||||
/// A request handler that performs an arbitrary authorization token grant.
|
||||
typedef FutureOr<AuthorizationTokenResponse> ExtensionGrant(
|
||||
typedef ExtensionGrant = FutureOr<AuthorizationTokenResponse> Function(
|
||||
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 {
|
||||
Map<String, dynamic> data;
|
||||
|
||||
|
@ -46,7 +46,7 @@ Future<Iterable<String>> _getScopes(RequestContext req,
|
|||
data = req.queryParameters;
|
||||
}
|
||||
|
||||
return data['scope']?.toString()?.split(' ') ?? [];
|
||||
return data['scope']?.toString().split(' ') ?? [];
|
||||
}
|
||||
|
||||
/// 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 => {};
|
||||
|
||||
/// 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].
|
||||
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.
|
||||
Future<String> getPkceCodeVerifier(RequestContext req,
|
||||
{bool body = true, String state, Uri uri}) async {
|
||||
{bool body = true, String? state, Uri? uri}) async {
|
||||
var data = body
|
||||
? await req.parseBody().then((_) => req.bodyAsMap)
|
||||
: req.queryParameters;
|
||||
|
@ -75,14 +75,14 @@ abstract class AuthorizationServer<Client, User> {
|
|||
|
||||
if (codeVerifier == null) {
|
||||
throw AuthorizationException(ErrorResponse(ErrorResponse.invalidRequest,
|
||||
"Missing `code_verifier` parameter.", state,
|
||||
'Missing `code_verifier` parameter.', state,
|
||||
uri: uri));
|
||||
} else if (codeVerifier is! String) {
|
||||
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));
|
||||
} else {
|
||||
return codeVerifier as String;
|
||||
return codeVerifier;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,7 @@ abstract class AuthorizationServer<Client, User> {
|
|||
/// the same.
|
||||
FutureOr<void> requestAuthorizationCode(
|
||||
Client client,
|
||||
String redirectUri,
|
||||
String? redirectUri,
|
||||
Iterable<String> scopes,
|
||||
String state,
|
||||
RequestContext req,
|
||||
|
@ -113,16 +113,16 @@ abstract class AuthorizationServer<Client, User> {
|
|||
|
||||
/// Exchanges an authorization code for an authorization token.
|
||||
FutureOr<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
|
||||
Client client,
|
||||
String authCode,
|
||||
String redirectUri,
|
||||
Client? client,
|
||||
String? authCode,
|
||||
String? redirectUri,
|
||||
RequestContext req,
|
||||
ResponseContext res) {
|
||||
throw AuthorizationException(
|
||||
ErrorResponse(
|
||||
ErrorResponse.unsupportedResponseType,
|
||||
'Authorization code grants are not supported.',
|
||||
req.uri.queryParameters['state'] ?? '',
|
||||
req.uri!.queryParameters['state'] ?? '',
|
||||
),
|
||||
statusCode: 400,
|
||||
);
|
||||
|
@ -130,8 +130,8 @@ abstract class AuthorizationServer<Client, User> {
|
|||
|
||||
/// Refresh an authorization token.
|
||||
FutureOr<AuthorizationTokenResponse> refreshAuthorizationToken(
|
||||
Client client,
|
||||
String refreshToken,
|
||||
Client? client,
|
||||
String? refreshToken,
|
||||
Iterable<String> scopes,
|
||||
RequestContext req,
|
||||
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].
|
||||
FutureOr<AuthorizationTokenResponse> resourceOwnerPasswordCredentialsGrant(
|
||||
Client client,
|
||||
String username,
|
||||
String password,
|
||||
Client? client,
|
||||
String? username,
|
||||
String? password,
|
||||
Iterable<String> scopes,
|
||||
RequestContext req,
|
||||
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.
|
||||
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);
|
||||
throw AuthorizationException(
|
||||
ErrorResponse(
|
||||
|
@ -196,7 +196,7 @@ abstract class AuthorizationServer<Client, User> {
|
|||
/// Produces an authorization token from a given device code.
|
||||
FutureOr<AuthorizationTokenResponse> exchangeDeviceCodeForToken(
|
||||
Client client,
|
||||
String deviceCode,
|
||||
String? deviceCode,
|
||||
String state,
|
||||
RequestContext req,
|
||||
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.
|
||||
Uri completeImplicitGrant(AuthorizationTokenResponse token, Uri redirectUri,
|
||||
{String state}) {
|
||||
{String? state}) {
|
||||
var queryParameters = <String, String>{};
|
||||
|
||||
queryParameters.addAll({
|
||||
|
@ -223,17 +223,18 @@ abstract class AuthorizationServer<Client, User> {
|
|||
|
||||
if (state != null) queryParameters['state'] = state;
|
||||
|
||||
if (token.expiresIn != null)
|
||||
if (token.expiresIn != null) {
|
||||
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 =
|
||||
queryParameters.keys.fold<StringBuffer>(StringBuffer(), (buf, k) {
|
||||
if (buf.isNotEmpty) buf.write('&');
|
||||
return buf
|
||||
..write(
|
||||
'$k=' + Uri.encodeComponent(queryParameters[k]),
|
||||
'$k=' + Uri.encodeComponent(queryParameters[k]!),
|
||||
);
|
||||
}).toString();
|
||||
|
||||
|
@ -244,14 +245,14 @@ abstract class AuthorizationServer<Client, User> {
|
|||
/// of grant the client is requesting.
|
||||
Future<void> authorizationEndpoint(
|
||||
RequestContext req, ResponseContext res) async {
|
||||
String state = '';
|
||||
var state = '';
|
||||
|
||||
try {
|
||||
var query = req.queryParameters;
|
||||
state = query['state']?.toString() ?? '';
|
||||
var responseType = await _getParam(req, 'response_type', state);
|
||||
|
||||
req.container.registerLazySingleton<Pkce>((_) {
|
||||
req.container!.registerLazySingleton<Pkce>((_) {
|
||||
return Pkce.fromJson(req.queryParameters, state: state);
|
||||
});
|
||||
|
||||
|
@ -260,7 +261,7 @@ abstract class AuthorizationServer<Client, User> {
|
|||
var clientId = await _getParam(req, 'client_id', state);
|
||||
|
||||
// Find client
|
||||
var client = await findClient(clientId);
|
||||
var client = await findClient(clientId)!;
|
||||
|
||||
if (client == null) {
|
||||
throw AuthorizationException(ErrorResponse(
|
||||
|
@ -309,16 +310,16 @@ abstract class AuthorizationServer<Client, User> {
|
|||
/// A request handler that either exchanges authorization codes for authorization tokens,
|
||||
/// or refreshes authorization tokens.
|
||||
Future tokenEndpoint(RequestContext req, ResponseContext res) async {
|
||||
String state = '';
|
||||
Client client;
|
||||
var state = '';
|
||||
Client? client;
|
||||
|
||||
try {
|
||||
AuthorizationTokenResponse response;
|
||||
AuthorizationTokenResponse? response;
|
||||
var body = await req.parseBody().then((_) => req.bodyAsMap);
|
||||
|
||||
state = body['state']?.toString() ?? '';
|
||||
|
||||
req.container.registerLazySingleton<Pkce>((_) {
|
||||
req.container!.registerLazySingleton<Pkce>((_) {
|
||||
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' &&
|
||||
grantType != null) {
|
||||
var match =
|
||||
_rgxBasic.firstMatch(req.headers.value('authorization') ?? '');
|
||||
_rgxBasic.firstMatch(req.headers!.value('authorization') ?? '');
|
||||
|
||||
if (match != null) {
|
||||
match = _rgxBasicAuth
|
||||
.firstMatch(String.fromCharCodes(base64Url.decode(match[1])));
|
||||
.firstMatch(String.fromCharCodes(base64Url.decode(match[1]!)));
|
||||
}
|
||||
|
||||
if (match == null) {
|
||||
|
@ -402,7 +403,7 @@ abstract class AuthorizationServer<Client, User> {
|
|||
);
|
||||
}
|
||||
} else if (extensionGrants.containsKey(grantType)) {
|
||||
response = await extensionGrants[grantType](req, res);
|
||||
response = await extensionGrants[grantType!]!(req, res);
|
||||
} else if (grantType == null) {
|
||||
// This is a device code grant.
|
||||
var clientId = await _getParam(req, 'client_id', state, body: true);
|
||||
|
|
|
@ -1,23 +1,19 @@
|
|||
name: angel_oauth2
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
name: angel3_oauth2
|
||||
version: 4.0.0
|
||||
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
|
||||
version: 2.3.0
|
||||
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/oauth2
|
||||
environment:
|
||||
sdk: ">=2.10.0 <2.12.0"
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
dependencies:
|
||||
angel_framework: #^2.0.0-rc.0
|
||||
path: ../framework
|
||||
angel_http_exception: #^1.0.0
|
||||
path: ../http_exception
|
||||
crypto: ^2.0.0
|
||||
angel3_framework: ^4.0.0
|
||||
angel3_http_exception: ^3.0.0
|
||||
crypto: ^3.0.1
|
||||
collection: ^1.15.0-nullsafety.4
|
||||
dev_dependencies:
|
||||
angel_validate: #^2.0.0-alpha
|
||||
path: ../validate
|
||||
angel_test: #^2.0.0-alpha
|
||||
path: ../test
|
||||
logging:
|
||||
oauth2: ^1.0.0
|
||||
pedantic: ^1.0.0
|
||||
test: ^1.15.7
|
||||
uuid: ^2.0.0
|
||||
angel3_validate: ^4.0.0
|
||||
angel3_test: ^4.0.0
|
||||
logging: ^1.0.1
|
||||
oauth2: ^2.0.0
|
||||
pedantic: ^1.11.0
|
||||
test: ^1.17.5
|
||||
uuid: ^3.0.4
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:angel_oauth2/angel_oauth2.dart';
|
||||
import 'package:angel_test/angel_test.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:angel3_framework/http.dart';
|
||||
import 'package:angel3_oauth2/angel3_oauth2.dart';
|
||||
import 'package:angel3_test/angel3_test.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:oauth2/oauth2.dart' as oauth2;
|
||||
import 'package:test/test.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'common.dart';
|
||||
|
||||
main() {
|
||||
void main() {
|
||||
Angel app;
|
||||
Uri authorizationEndpoint, tokenEndpoint, redirectUri;
|
||||
TestClient testClient;
|
||||
late Uri authorizationEndpoint, tokenEndpoint, redirectUri;
|
||||
late TestClient testClient;
|
||||
|
||||
setUp(() async {
|
||||
app = Angel();
|
||||
app.configuration['properties'] = app.configuration;
|
||||
app.container.registerSingleton(AuthCodes());
|
||||
app.container!.registerSingleton(AuthCodes());
|
||||
|
||||
var server = _Server();
|
||||
|
||||
|
@ -62,7 +62,7 @@ main() {
|
|||
test('show authorization form', () async {
|
||||
var grant = createGrant();
|
||||
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}');
|
||||
expect(
|
||||
response.body,
|
||||
|
@ -73,7 +73,7 @@ main() {
|
|||
test('preserves state', () async {
|
||||
var grant = createGrant();
|
||||
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}');
|
||||
expect(json.decode(response.body)['state'], 'goodbye');
|
||||
});
|
||||
|
@ -81,7 +81,7 @@ main() {
|
|||
test('sends auth code', () async {
|
||||
var grant = createGrant();
|
||||
var url = grant.getAuthorizationUrl(redirectUri);
|
||||
var response = await testClient.client.get(url);
|
||||
var response = await testClient.client!.get(url);
|
||||
print('Body: ${response.body}');
|
||||
expect(
|
||||
json.decode(response.body),
|
||||
|
@ -95,7 +95,7 @@ main() {
|
|||
test('exchange code for token', () async {
|
||||
var grant = createGrant();
|
||||
var url = grant.getAuthorizationUrl(redirectUri);
|
||||
var response = await testClient.client.get(url);
|
||||
var response = await testClient.client!.get(url);
|
||||
print('Body: ${response.body}');
|
||||
|
||||
var authCode = json.decode(response.body)['code'].toString();
|
||||
|
@ -106,7 +106,7 @@ main() {
|
|||
test('can send refresh token', () async {
|
||||
var grant = createGrant();
|
||||
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}');
|
||||
|
||||
var authCode = json.decode(response.body)['code'].toString();
|
||||
|
@ -122,20 +122,20 @@ class _Server extends AuthorizationServer<PseudoApplication, Map> {
|
|||
final Uuid _uuid = Uuid();
|
||||
|
||||
@override
|
||||
FutureOr<PseudoApplication> findClient(String clientId) {
|
||||
FutureOr<PseudoApplication>? findClient(String? clientId) {
|
||||
return clientId == pseudoApplication.id ? pseudoApplication : null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> verifyClient(
|
||||
PseudoApplication client, String clientSecret) async {
|
||||
PseudoApplication client, String? clientSecret) async {
|
||||
return client.secret == clientSecret;
|
||||
}
|
||||
|
||||
@override
|
||||
Future requestAuthorizationCode(
|
||||
PseudoApplication client,
|
||||
String redirectUri,
|
||||
String? redirectUri,
|
||||
Iterable<String> scopes,
|
||||
String state,
|
||||
RequestContext req,
|
||||
|
@ -147,28 +147,29 @@ class _Server extends AuthorizationServer<PseudoApplication, Map> {
|
|||
client, redirectUri, scopes, state, req, res, implicit);
|
||||
}
|
||||
|
||||
if (state == 'hello')
|
||||
if (state == 'hello') {
|
||||
return 'Hello ${pseudoApplication.id}:${pseudoApplication.secret}';
|
||||
}
|
||||
|
||||
var authCode = _uuid.v4();
|
||||
var authCodes = req.container.make<AuthCodes>();
|
||||
var authCodes = req.container!.make<AuthCodes>()!;
|
||||
authCodes[authCode] = state;
|
||||
|
||||
res.headers['content-type'] = 'application/json';
|
||||
var result = {'code': authCode};
|
||||
if (state?.isNotEmpty == true) result['state'] = state;
|
||||
if (state.isNotEmpty == true) result['state'] = state;
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
|
||||
PseudoApplication client,
|
||||
String authCode,
|
||||
String redirectUri,
|
||||
PseudoApplication? client,
|
||||
String? authCode,
|
||||
String? redirectUri,
|
||||
RequestContext req,
|
||||
ResponseContext res) async {
|
||||
var authCodes = req.container.make<AuthCodes>();
|
||||
var state = authCodes[authCode];
|
||||
var authCodes = req.container!.make<AuthCodes>()!;
|
||||
var state = authCodes[authCode!];
|
||||
var refreshToken = state == 'can_refresh' ? '${authCode}_refresh' : null;
|
||||
return AuthorizationTokenResponse('${authCode}_access',
|
||||
refreshToken: refreshToken);
|
||||
|
@ -179,7 +180,7 @@ class AuthCodes extends MapBase<String, String> with MapMixin<String, String> {
|
|||
var inner = <String, String>{};
|
||||
|
||||
@override
|
||||
String operator [](Object key) => inner[key];
|
||||
String? operator [](Object? key) => inner[key as String];
|
||||
|
||||
@override
|
||||
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;
|
||||
|
||||
@override
|
||||
String remove(Object key) => inner.remove(key);
|
||||
String? remove(Object? key) => inner.remove(key);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_test/angel_test.dart';
|
||||
import 'package:angel_oauth2/angel_oauth2.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:angel3_test/angel3_test.dart';
|
||||
import 'package:angel3_oauth2/angel3_oauth2.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
TestClient client;
|
||||
late TestClient client;
|
||||
|
||||
setUp(() async {
|
||||
var app = Angel();
|
||||
|
@ -30,7 +30,7 @@ void main() {
|
|||
|
||||
test('authenticate via client credentials', () async {
|
||||
var response = await client.post(
|
||||
'/oauth2/token',
|
||||
Uri.parse('oauth2/token'),
|
||||
headers: {
|
||||
'Authorization': 'Basic ' + base64Url.encode('foo:bar'.codeUnits),
|
||||
},
|
||||
|
@ -58,7 +58,7 @@ void main() {
|
|||
|
||||
test('force correct id', () async {
|
||||
var response = await client.post(
|
||||
'/oauth2/token',
|
||||
Uri.parse('/oauth2/token'),
|
||||
headers: {
|
||||
'Authorization': 'Basic ' + base64Url.encode('fooa:bar'.codeUnits),
|
||||
},
|
||||
|
@ -73,7 +73,7 @@ void main() {
|
|||
|
||||
test('force correct secret', () async {
|
||||
var response = await client.post(
|
||||
'/oauth2/token',
|
||||
Uri.parse('/oauth2/token'),
|
||||
headers: {
|
||||
'Authorization': 'Basic ' + base64Url.encode('foo:bara'.codeUnits),
|
||||
},
|
||||
|
@ -90,19 +90,21 @@ void main() {
|
|||
class _AuthorizationServer
|
||||
extends AuthorizationServer<PseudoApplication, PseudoUser> {
|
||||
@override
|
||||
PseudoApplication findClient(String clientId) {
|
||||
PseudoApplication? findClient(String? clientId) {
|
||||
return clientId == pseudoApplication.id ? pseudoApplication : null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> verifyClient(
|
||||
PseudoApplication client, String clientSecret) async {
|
||||
PseudoApplication client, String? clientSecret) async {
|
||||
return client.secret == clientSecret;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AuthorizationTokenResponse> clientCredentialsGrant(
|
||||
PseudoApplication client, RequestContext req, ResponseContext res) async {
|
||||
PseudoApplication? client,
|
||||
RequestContext req,
|
||||
ResponseContext res) async {
|
||||
return AuthorizationTokenResponse('foo');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ const List<PseudoUser> pseudoUsers = [
|
|||
];
|
||||
|
||||
class PseudoUser {
|
||||
final String username, password;
|
||||
final String? username, password;
|
||||
|
||||
const PseudoUser({this.username, this.password});
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import 'dart:async';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_test/angel_test.dart';
|
||||
import 'package:angel_oauth2/angel_oauth2.dart';
|
||||
import 'package:angel_validate/angel_validate.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:angel3_test/angel3_test.dart';
|
||||
import 'package:angel3_oauth2/angel3_oauth2.dart';
|
||||
import 'package:angel3_validate/angel3_validate.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
main() {
|
||||
TestClient client;
|
||||
void main() {
|
||||
late TestClient client;
|
||||
|
||||
setUp(() async {
|
||||
var app = Angel();
|
||||
|
@ -38,7 +38,7 @@ main() {
|
|||
|
||||
group('get initial code', () {
|
||||
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',
|
||||
});
|
||||
print(response.body);
|
||||
|
@ -46,7 +46,7 @@ main() {
|
|||
});
|
||||
|
||||
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',
|
||||
});
|
||||
print(response.body);
|
||||
|
@ -55,16 +55,16 @@ main() {
|
|||
allOf(
|
||||
hasStatus(200),
|
||||
isJson({
|
||||
"device_code": "foo",
|
||||
"user_code": "bar",
|
||||
"verification_uri": "https://regiostech.com?scopes",
|
||||
"expires_in": 3600
|
||||
'device_code': 'foo',
|
||||
'user_code': 'bar',
|
||||
'verification_uri': 'https://regiostech.com?scopes',
|
||||
'expires_in': 3600
|
||||
}),
|
||||
));
|
||||
});
|
||||
|
||||
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',
|
||||
'scope': 'bar baz quux',
|
||||
});
|
||||
|
@ -74,11 +74,11 @@ main() {
|
|||
allOf(
|
||||
hasStatus(200),
|
||||
isJson({
|
||||
"device_code": "foo",
|
||||
"user_code": "bar",
|
||||
"verification_uri": Uri.parse("https://regiostech.com").replace(
|
||||
'device_code': 'foo',
|
||||
'user_code': 'bar',
|
||||
'verification_uri': Uri.parse('https://regiostech.com').replace(
|
||||
queryParameters: {'scopes': 'bar,baz,quux'}).toString(),
|
||||
"expires_in": 3600
|
||||
'expires_in': 3600
|
||||
}),
|
||||
));
|
||||
});
|
||||
|
@ -86,7 +86,7 @@ main() {
|
|||
|
||||
group('get token', () {
|
||||
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',
|
||||
'client_id': 'foo',
|
||||
'device_code': 'bar',
|
||||
|
@ -97,7 +97,7 @@ main() {
|
|||
response,
|
||||
allOf(
|
||||
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
|
||||
// has already been tested.
|
||||
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',
|
||||
'client_id': 'foo',
|
||||
'device_code': 'brute',
|
||||
|
@ -120,9 +120,9 @@ main() {
|
|||
allOf(
|
||||
hasStatus(400),
|
||||
isJson({
|
||||
"error": "slow_down",
|
||||
"error_description":
|
||||
"Ho, brother! Ho, whoa, whoa, whoa now! You got too much dip on your chip!"
|
||||
'error': 'slow_down',
|
||||
'error_description':
|
||||
'Ho, brother! Ho, whoa, whoa, whoa now! You got too much dip on your chip!'
|
||||
}),
|
||||
));
|
||||
});
|
||||
|
@ -132,13 +132,13 @@ main() {
|
|||
class _AuthorizationServer
|
||||
extends AuthorizationServer<PseudoApplication, PseudoUser> {
|
||||
@override
|
||||
PseudoApplication findClient(String clientId) {
|
||||
PseudoApplication? findClient(String? clientId) {
|
||||
return clientId == pseudoApplication.id ? pseudoApplication : null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> verifyClient(
|
||||
PseudoApplication client, String clientSecret) async {
|
||||
PseudoApplication client, String? clientSecret) async {
|
||||
return client.secret == clientSecret;
|
||||
}
|
||||
|
||||
|
@ -156,14 +156,14 @@ class _AuthorizationServer
|
|||
@override
|
||||
FutureOr<AuthorizationTokenResponse> exchangeDeviceCodeForToken(
|
||||
PseudoApplication client,
|
||||
String deviceCode,
|
||||
String? deviceCode,
|
||||
String state,
|
||||
RequestContext req,
|
||||
ResponseContext res) {
|
||||
if (deviceCode == 'brute') {
|
||||
throw AuthorizationException(ErrorResponse(
|
||||
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));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import 'dart:async';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_test/angel_test.dart';
|
||||
import 'package:angel_oauth2/angel_oauth2.dart';
|
||||
import 'package:angel_validate/angel_validate.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:angel3_test/angel3_test.dart';
|
||||
import 'package:angel3_oauth2/angel3_oauth2.dart';
|
||||
import 'package:angel3_validate/angel3_validate.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
main() {
|
||||
TestClient client;
|
||||
void main() {
|
||||
late TestClient client;
|
||||
|
||||
setUp(() async {
|
||||
var app = Angel();
|
||||
|
@ -30,7 +30,8 @@ main() {
|
|||
|
||||
test('authenticate via implicit grant', () async {
|
||||
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}');
|
||||
|
@ -47,27 +48,27 @@ main() {
|
|||
class _AuthorizationServer
|
||||
extends AuthorizationServer<PseudoApplication, PseudoUser> {
|
||||
@override
|
||||
PseudoApplication findClient(String clientId) {
|
||||
PseudoApplication? findClient(String? clientId) {
|
||||
return clientId == pseudoApplication.id ? pseudoApplication : null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> verifyClient(
|
||||
PseudoApplication client, String clientSecret) async {
|
||||
PseudoApplication client, String? clientSecret) async {
|
||||
return client.secret == clientSecret;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> requestAuthorizationCode(
|
||||
PseudoApplication client,
|
||||
String redirectUri,
|
||||
String? redirectUri,
|
||||
Iterable<String> scopes,
|
||||
String state,
|
||||
RequestContext req,
|
||||
ResponseContext res,
|
||||
bool implicit) async {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import 'dart:async';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:angel_oauth2/angel_oauth2.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:angel3_framework/http.dart';
|
||||
import 'package:angel3_oauth2/angel3_oauth2.dart';
|
||||
import 'package:collection/collection.dart' show IterableExtension;
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:oauth2/oauth2.dart' as oauth2;
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
main() {
|
||||
Angel app;
|
||||
Uri tokenEndpoint;
|
||||
void main() {
|
||||
late Angel app;
|
||||
late Uri tokenEndpoint;
|
||||
|
||||
setUp(() async {
|
||||
app = Angel();
|
||||
|
@ -50,7 +51,7 @@ main() {
|
|||
});
|
||||
|
||||
test('force correct username+password', () async {
|
||||
oauth2.Client client;
|
||||
oauth2.Client? client;
|
||||
|
||||
try {
|
||||
client = await oauth2.resourceOwnerPasswordGrant(
|
||||
|
@ -88,20 +89,20 @@ main() {
|
|||
class _AuthorizationServer
|
||||
extends AuthorizationServer<PseudoApplication, PseudoUser> {
|
||||
@override
|
||||
PseudoApplication findClient(String clientId) {
|
||||
PseudoApplication? findClient(String? clientId) {
|
||||
return clientId == pseudoApplication.id ? pseudoApplication : null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> verifyClient(
|
||||
PseudoApplication client, String clientSecret) async {
|
||||
PseudoApplication client, String? clientSecret) async {
|
||||
return client.secret == clientSecret;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AuthorizationTokenResponse> refreshAuthorizationToken(
|
||||
PseudoApplication client,
|
||||
String refreshToken,
|
||||
PseudoApplication? client,
|
||||
String? refreshToken,
|
||||
Iterable<String> scopes,
|
||||
RequestContext req,
|
||||
ResponseContext res) async {
|
||||
|
@ -110,15 +111,14 @@ class _AuthorizationServer
|
|||
|
||||
@override
|
||||
Future<AuthorizationTokenResponse> resourceOwnerPasswordCredentialsGrant(
|
||||
PseudoApplication client,
|
||||
String username,
|
||||
String password,
|
||||
PseudoApplication? client,
|
||||
String? username,
|
||||
String? password,
|
||||
Iterable<String> scopes,
|
||||
RequestContext req,
|
||||
ResponseContext res) async {
|
||||
var user = pseudoUsers.firstWhere(
|
||||
(u) => u.username == username && u.password == password,
|
||||
orElse: () => null);
|
||||
var user = pseudoUsers.firstWhereOrNull(
|
||||
(u) => u.username == username && u.password == password);
|
||||
|
||||
if (user == null) {
|
||||
var body = await req.parseBody().then((_) => req.bodyAsMap);
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:angel_oauth2/angel_oauth2.dart';
|
||||
import 'package:angel_test/angel_test.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:angel3_framework/http.dart';
|
||||
import 'package:angel3_oauth2/angel3_oauth2.dart';
|
||||
import 'package:angel3_test/angel3_test.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
main() {
|
||||
void main() {
|
||||
Angel app;
|
||||
Uri authorizationEndpoint, tokenEndpoint;
|
||||
TestClient testClient;
|
||||
late Uri authorizationEndpoint, tokenEndpoint;
|
||||
late TestClient testClient;
|
||||
|
||||
setUp(() async {
|
||||
app = Angel();
|
||||
app.container.registerSingleton(AuthCodes());
|
||||
app.container!.registerSingleton(AuthCodes());
|
||||
|
||||
var server = _Server();
|
||||
|
||||
|
@ -53,14 +53,14 @@ main() {
|
|||
'redirect_uri': 'https://freddie.mercu.ry',
|
||||
'code_challenge': 'foo',
|
||||
});
|
||||
var response = await testClient
|
||||
.get(url.toString(), headers: {'accept': 'application/json'});
|
||||
var response =
|
||||
await testClient.get(url, headers: {'accept': 'application/json'});
|
||||
print(response.body);
|
||||
expect(
|
||||
response,
|
||||
allOf(
|
||||
hasStatus(200),
|
||||
isJson({"code": "ok"}),
|
||||
isJson({'code': 'ok'}),
|
||||
));
|
||||
});
|
||||
|
||||
|
@ -72,14 +72,14 @@ main() {
|
|||
'code_challenge': 'foo',
|
||||
'code_challenge_method': 'plain',
|
||||
});
|
||||
var response = await testClient
|
||||
.get(url.toString(), headers: {'accept': 'application/json'});
|
||||
var response =
|
||||
await testClient.get(url, headers: {'accept': 'application/json'});
|
||||
print(response.body);
|
||||
expect(
|
||||
response,
|
||||
allOf(
|
||||
hasStatus(200),
|
||||
isJson({"code": "ok"}),
|
||||
isJson({'code': 'ok'}),
|
||||
));
|
||||
});
|
||||
|
||||
|
@ -91,14 +91,14 @@ main() {
|
|||
'code_challenge': 'foo',
|
||||
'code_challenge_method': 's256',
|
||||
});
|
||||
var response = await testClient
|
||||
.get(url.toString(), headers: {'accept': 'application/json'});
|
||||
var response =
|
||||
await testClient.get(url, headers: {'accept': 'application/json'});
|
||||
print(response.body);
|
||||
expect(
|
||||
response,
|
||||
allOf(
|
||||
hasStatus(200),
|
||||
isJson({"code": "ok"}),
|
||||
isJson({'code': 'ok'}),
|
||||
));
|
||||
});
|
||||
|
||||
|
@ -110,16 +110,16 @@ main() {
|
|||
'code_challenge': 'foo',
|
||||
'code_challenge_method': 'bar',
|
||||
});
|
||||
var response = await testClient
|
||||
.get(url.toString(), headers: {'accept': 'application/json'});
|
||||
var response =
|
||||
await testClient.get(url, headers: {'accept': 'application/json'});
|
||||
print(response.body);
|
||||
expect(
|
||||
response,
|
||||
allOf(
|
||||
hasStatus(400),
|
||||
isJson({
|
||||
"error": "invalid_request",
|
||||
"error_description":
|
||||
'error': 'invalid_request',
|
||||
'error_description':
|
||||
"The `code_challenge_method` parameter must be either 'plain' or 's256'."
|
||||
}),
|
||||
));
|
||||
|
@ -131,16 +131,16 @@ main() {
|
|||
'client_id': 'freddie mercury',
|
||||
'redirect_uri': 'https://freddie.mercu.ry'
|
||||
});
|
||||
var response = await testClient
|
||||
.get(url.toString(), headers: {'accept': 'application/json'});
|
||||
var response =
|
||||
await testClient.get(url, headers: {'accept': 'application/json'});
|
||||
print(response.body);
|
||||
expect(
|
||||
response,
|
||||
allOf(
|
||||
hasStatus(400),
|
||||
isJson({
|
||||
"error": "invalid_request",
|
||||
"error_description": "Missing `code_challenge` parameter."
|
||||
'error': 'invalid_request',
|
||||
'error_description': 'Missing `code_challenge` parameter.'
|
||||
}),
|
||||
));
|
||||
});
|
||||
|
@ -150,7 +150,7 @@ main() {
|
|||
test('with correct verifier', () async {
|
||||
var url = tokenEndpoint.replace(
|
||||
userInfo: '${pseudoApplication.id}:${pseudoApplication.secret}');
|
||||
var response = await testClient.post(url.toString(), headers: {
|
||||
var response = await testClient.post(url, headers: {
|
||||
'accept': 'application/json',
|
||||
// 'authorization': 'Basic ' + base64Url.encode(ascii.encode(url.userInfo))
|
||||
}, body: {
|
||||
|
@ -166,13 +166,13 @@ main() {
|
|||
response,
|
||||
allOf(
|
||||
hasStatus(200),
|
||||
isJson({"token_type": "bearer", "access_token": "yes"}),
|
||||
isJson({'token_type': 'bearer', 'access_token': 'yes'}),
|
||||
));
|
||||
});
|
||||
test('with incorrect verifier', () async {
|
||||
var url = tokenEndpoint.replace(
|
||||
userInfo: '${pseudoApplication.id}:${pseudoApplication.secret}');
|
||||
var response = await testClient.post(url.toString(), headers: {
|
||||
var response = await testClient.post(url, headers: {
|
||||
'accept': 'application/json',
|
||||
// 'authorization': 'Basic ' + base64Url.encode(ascii.encode(url.userInfo))
|
||||
}, body: {
|
||||
|
@ -189,9 +189,9 @@ main() {
|
|||
allOf(
|
||||
hasStatus(400),
|
||||
isJson({
|
||||
"error": "invalid_grant",
|
||||
"error_description":
|
||||
"The given `code_verifier` parameter is invalid."
|
||||
'error': 'invalid_grant',
|
||||
'error_description':
|
||||
'The given `code_verifier` parameter is invalid.'
|
||||
}),
|
||||
));
|
||||
});
|
||||
|
@ -199,7 +199,7 @@ main() {
|
|||
test('with missing verifier', () async {
|
||||
var url = tokenEndpoint.replace(
|
||||
userInfo: '${pseudoApplication.id}:${pseudoApplication.secret}');
|
||||
var response = await testClient.post(url.toString(), headers: {
|
||||
var response = await testClient.post(url, headers: {
|
||||
'accept': 'application/json',
|
||||
// 'authorization': 'Basic ' + base64Url.encode(ascii.encode(url.userInfo))
|
||||
}, body: {
|
||||
|
@ -215,8 +215,8 @@ main() {
|
|||
allOf(
|
||||
hasStatus(400),
|
||||
isJson({
|
||||
"error": "invalid_request",
|
||||
"error_description": "Missing `code_verifier` parameter."
|
||||
'error': 'invalid_request',
|
||||
'error_description': 'Missing `code_verifier` parameter.'
|
||||
}),
|
||||
));
|
||||
});
|
||||
|
@ -225,34 +225,34 @@ main() {
|
|||
|
||||
class _Server extends AuthorizationServer<PseudoApplication, Map> {
|
||||
@override
|
||||
FutureOr<PseudoApplication> findClient(String clientId) {
|
||||
FutureOr<PseudoApplication> findClient(String? clientId) {
|
||||
return pseudoApplication;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> verifyClient(
|
||||
PseudoApplication client, String clientSecret) async {
|
||||
PseudoApplication client, String? clientSecret) async {
|
||||
return client.secret == clientSecret;
|
||||
}
|
||||
|
||||
@override
|
||||
Future requestAuthorizationCode(
|
||||
PseudoApplication client,
|
||||
String redirectUri,
|
||||
String? redirectUri,
|
||||
Iterable<String> scopes,
|
||||
String state,
|
||||
RequestContext req,
|
||||
ResponseContext res,
|
||||
bool implicit) async {
|
||||
req.container.make<Pkce>();
|
||||
req.container!.make<Pkce>();
|
||||
return {'code': 'ok'};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AuthorizationTokenResponse> exchangeAuthorizationCodeForToken(
|
||||
PseudoApplication client,
|
||||
String authCode,
|
||||
String redirectUri,
|
||||
PseudoApplication? client,
|
||||
String? authCode,
|
||||
String? redirectUri,
|
||||
RequestContext req,
|
||||
ResponseContext res) async {
|
||||
var codeVerifier = await getPkceCodeVerifier(req);
|
||||
|
@ -266,7 +266,7 @@ class AuthCodes extends MapBase<String, String> with MapMixin<String, String> {
|
|||
var inner = <String, String>{};
|
||||
|
||||
@override
|
||||
String operator [](Object key) => inner[key];
|
||||
String? operator [](Object? key) => inner[key as String];
|
||||
|
||||
@override
|
||||
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;
|
||||
|
||||
@override
|
||||
String remove(Object key) => inner.remove(key);
|
||||
String? remove(Object? key) => inner.remove(key);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue