Published oauth2, cache, cors, auth_oauth2

This commit is contained in:
thomashii 2021-05-30 08:46:13 +08:00
parent e79826bad0
commit db49c82fc0
42 changed files with 488 additions and 431 deletions

View 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.

View file

@ -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.

View file

@ -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 {

View file

@ -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();

View file

@ -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);

View file

@ -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
View 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.

View file

@ -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`.

View file

@ -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);
});
}

View file

@ -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()}'};
}),
),

View file

@ -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

View file

@ -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);
}
}

View file

@ -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]);

View file

@ -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;

View file

@ -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

View file

@ -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
View 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.

View file

@ -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.

View file

@ -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).

View file

@ -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$'),
],
);

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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

View file

@ -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}');

View 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.

View file

@ -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`.

View file

@ -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> {}
```

View file

@ -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.

View file

@ -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});

View file

@ -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),
);
}

View file

@ -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;

View file

@ -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);

View file

@ -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

View file

@ -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);
}

View file

@ -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');
}
}

View file

@ -14,7 +14,7 @@ const List<PseudoUser> pseudoUsers = [
];
class PseudoUser {
final String username, password;
final String? username, password;
const PseudoUser({this.username, this.password});
}

View file

@ -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));
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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);
}