+10
This commit is contained in:
parent
afb554fba0
commit
5b7e017f31
16 changed files with 195 additions and 161 deletions
|
@ -4,6 +4,7 @@
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/packages" />
|
<excludeFolder url="file://$MODULE_DIR$/packages" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/test/packages" />
|
<excludeFolder url="file://$MODULE_DIR$/test/packages" />
|
||||||
|
|
|
@ -25,14 +25,4 @@
|
||||||
</profile-state>
|
</profile-state>
|
||||||
</entry>
|
</entry>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
|
|
||||||
<OptionsSetting value="true" id="Add" />
|
|
||||||
<OptionsSetting value="true" id="Remove" />
|
|
||||||
<OptionsSetting value="true" id="Checkout" />
|
|
||||||
<OptionsSetting value="true" id="Update" />
|
|
||||||
<OptionsSetting value="true" id="Status" />
|
|
||||||
<OptionsSetting value="true" id="Edit" />
|
|
||||||
<ConfirmationsSetting value="0" id="Add" />
|
|
||||||
<ConfirmationsSetting value="0" id="Remove" />
|
|
||||||
</component>
|
|
||||||
</project>
|
</project>
|
|
@ -1,6 +1,7 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="All Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
|
<configuration default="false" name="All Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
|
||||||
<option name="filePath" value="$PROJECT_DIR$/test/all_tests.dart" />
|
<option name="filePath" value="$PROJECT_DIR$/test" />
|
||||||
|
<option name="scope" value="FOLDER" />
|
||||||
<method />
|
<method />
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
|
@ -1,6 +1,6 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Auth Token Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
|
<configuration default="false" name="Auth Token Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
|
||||||
<option name="filePath" value="$PROJECT_DIR$/test/auth_token.dart" />
|
<option name="filePath" value="$PROJECT_DIR$/test/auth_token_test.dart" />
|
||||||
<method />
|
<method />
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
|
@ -1,6 +1,6 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Local Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
|
<configuration default="false" name="Local Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
|
||||||
<option name="filePath" value="$PROJECT_DIR$/test/local.dart" />
|
<option name="filePath" value="$PROJECT_DIR$/test/local_test.dart" />
|
||||||
<method />
|
<method />
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
1
.travis.yml
Normal file
1
.travis.yml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
language: dart
|
|
@ -1,8 +1,12 @@
|
||||||
# angel_auth
|
# angel_auth
|
||||||
|
|
||||||
|
![version 1.1.0-dev+10](https://img.shields.io/badge/version-1.1.0--dev+10-red.svg)
|
||||||
|
![build status](https://travis-ci.org/angel-dart/auth.svg?branch=master)
|
||||||
|
|
||||||
A complete authentication plugin for Angel. Inspired by Passport.
|
A complete authentication plugin for Angel. Inspired by Passport.
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
Coming soon!
|
[Click here](https://github.com/angel-dart/auth/wiki).
|
||||||
|
|
||||||
# Supported Strategies
|
# Supported Strategies
|
||||||
* Local (with and without Basic Auth)
|
* Local (with and without Basic Auth)
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
|
||||||
|
@ -16,7 +15,7 @@ class RequireAuthorizationMiddleware extends BaseMiddleware {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.properties.containsKey('user'))
|
if (req.properties.containsKey('user') || req.method == 'OPTIONS')
|
||||||
return true;
|
return true;
|
||||||
else
|
else
|
||||||
return _reject(res);
|
return _reject(res);
|
||||||
|
|
|
@ -18,6 +18,8 @@ class AngelAuth extends AngelPlugin {
|
||||||
final RegExp _rgxBearer = new RegExp(r"^Bearer");
|
final RegExp _rgxBearer = new RegExp(r"^Bearer");
|
||||||
RequireAuthorizationMiddleware _requireAuth =
|
RequireAuthorizationMiddleware _requireAuth =
|
||||||
new RequireAuthorizationMiddleware();
|
new RequireAuthorizationMiddleware();
|
||||||
|
final bool allowCookie;
|
||||||
|
final bool allowTokenInQuery;
|
||||||
String middlewareName;
|
String middlewareName;
|
||||||
bool debug;
|
bool debug;
|
||||||
bool enforceIp;
|
bool enforceIp;
|
||||||
|
@ -40,6 +42,8 @@ class AngelAuth extends AngelPlugin {
|
||||||
AngelAuth(
|
AngelAuth(
|
||||||
{String jwtKey,
|
{String jwtKey,
|
||||||
num jwtLifeSpan,
|
num jwtLifeSpan,
|
||||||
|
this.allowCookie: true,
|
||||||
|
this.allowTokenInQuery: true,
|
||||||
this.debug: false,
|
this.debug: false,
|
||||||
this.enforceIp: true,
|
this.enforceIp: true,
|
||||||
this.middlewareName: 'auth',
|
this.middlewareName: 'auth',
|
||||||
|
@ -54,49 +58,74 @@ class AngelAuth extends AngelPlugin {
|
||||||
app.container.singleton(this);
|
app.container.singleton(this);
|
||||||
if (runtimeType != AngelAuth) app.container.singleton(this, as: AngelAuth);
|
if (runtimeType != AngelAuth) app.container.singleton(this, as: AngelAuth);
|
||||||
|
|
||||||
app.before.add(_decodeJwt);
|
app.before.add(decodeJwt);
|
||||||
app.registerMiddleware(middlewareName, _requireAuth);
|
app.registerMiddleware(middlewareName, _requireAuth);
|
||||||
|
|
||||||
if (reviveTokenEndpoint != null) {
|
if (reviveTokenEndpoint != null) {
|
||||||
app.post(reviveTokenEndpoint, _reviveJwt);
|
app.post(reviveTokenEndpoint, reviveJwt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_decodeJwt(RequestContext req, ResponseContext res) async {
|
decodeJwt(RequestContext req, ResponseContext res) async {
|
||||||
if (req.method == "POST" && req.path == reviveTokenEndpoint) {
|
if (req.method == "POST" && req.path == reviveTokenEndpoint) {
|
||||||
// Shouldn't block invalid JWT if we are reviving it
|
// Shouldn't block invalid JWT if we are reviving it
|
||||||
|
if (debug) print('Token revival endpoint accessed.');
|
||||||
if (debug)
|
return await reviveJwt(req, res);
|
||||||
print('Token revival endpoint accessed.');
|
|
||||||
|
|
||||||
return await _reviveJwt(req, res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String jwt = _getJwt(req);
|
if (debug) {
|
||||||
|
print('Enforcing JWT authentication...');
|
||||||
|
}
|
||||||
|
|
||||||
|
String jwt = getJwt(req);
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
print('Found JWT: $jwt');
|
||||||
|
}
|
||||||
|
|
||||||
if (jwt != null) {
|
if (jwt != null) {
|
||||||
var token = new AuthToken.validate(jwt, _hs256);
|
var token = new AuthToken.validate(jwt, _hs256);
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
print('Decoded auth token: ${token.toJson()}');
|
||||||
|
}
|
||||||
|
|
||||||
if (enforceIp) {
|
if (enforceIp) {
|
||||||
if (req.ip != token.ipAddress)
|
if (debug) {
|
||||||
|
print(
|
||||||
|
'Token IP: ${token.ipAddress}. Current request sent from: ${req.ip}');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.ip != null && req.ip != token.ipAddress)
|
||||||
throw new AngelHttpException.Forbidden(
|
throw new AngelHttpException.Forbidden(
|
||||||
message: "JWT cannot be accessed from this IP address.");
|
message: "JWT cannot be accessed from this IP address.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token.lifeSpan > -1) {
|
if (token.lifeSpan > -1) {
|
||||||
|
if (debug) {
|
||||||
|
print("Making sure this token hasn't already expired...");
|
||||||
|
}
|
||||||
|
|
||||||
token.issuedAt.add(new Duration(milliseconds: token.lifeSpan));
|
token.issuedAt.add(new Duration(milliseconds: token.lifeSpan));
|
||||||
|
|
||||||
if (!token.issuedAt.isAfter(new DateTime.now()))
|
if (!token.issuedAt.isAfter(new DateTime.now()))
|
||||||
throw new AngelHttpException.Forbidden(message: "Expired JWT.");
|
throw new AngelHttpException.Forbidden(message: "Expired JWT.");
|
||||||
|
} else if (debug) {
|
||||||
|
print('This token has an infinite life span.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
print('Now deserializing from this userId: ${token.userId}');
|
||||||
|
}
|
||||||
|
|
||||||
|
req.inject(AuthToken, req.properties['token'] = token);
|
||||||
req.properties["user"] = await deserializer(token.userId);
|
req.properties["user"] = await deserializer(token.userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getJwt(RequestContext req) {
|
getJwt(RequestContext req) {
|
||||||
if (debug) {
|
if (debug) {
|
||||||
print('Attempting to parse JWT');
|
print('Attempting to parse JWT');
|
||||||
}
|
}
|
||||||
|
@ -106,39 +135,40 @@ class AngelAuth extends AngelPlugin {
|
||||||
print('Found Auth header');
|
print('Found Auth header');
|
||||||
}
|
}
|
||||||
|
|
||||||
return req.headers
|
final authHeader = req.headers.value("Authorization");
|
||||||
.value("Authorization")
|
|
||||||
.replaceAll(_rgxBearer, "")
|
// Allow Basic auth to fall through
|
||||||
.trim();
|
if (_rgxBearer.hasMatch(authHeader))
|
||||||
|
return authHeader.replaceAll(_rgxBearer, "").trim();
|
||||||
} else if (req.cookies.any((cookie) => cookie.name == "token")) {
|
} else if (req.cookies.any((cookie) => cookie.name == "token")) {
|
||||||
print('Request has "token" cookie...');
|
print('Request has "token" cookie...');
|
||||||
return req.cookies.firstWhere((cookie) => cookie.name == "token").value;
|
return req.cookies.firstWhere((cookie) => cookie.name == "token").value;
|
||||||
|
} else if (allowTokenInQuery && req.query['token'] is String) {
|
||||||
|
return req.query['token'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_reviveJwt(RequestContext req, ResponseContext res) async {
|
reviveJwt(RequestContext req, ResponseContext res) async {
|
||||||
try {
|
try {
|
||||||
if (debug)
|
if (debug) print('Attempting to revive JWT...');
|
||||||
print('Attempting to revive JWT...');
|
|
||||||
|
|
||||||
var jwt = _getJwt(req);
|
var jwt = getJwt(req);
|
||||||
|
|
||||||
if (debug)
|
if (debug) print('Found JWT: $jwt');
|
||||||
print('Found JWT: $jwt');
|
|
||||||
|
|
||||||
if (jwt == null) {
|
if (jwt == null) {
|
||||||
throw new AngelHttpException.Forbidden(message: "No JWT provided");
|
throw new AngelHttpException.Forbidden(message: "No JWT provided");
|
||||||
} else {
|
} else {
|
||||||
var token = new AuthToken.validate(jwt, _hs256);
|
var token = new AuthToken.validate(jwt, _hs256);
|
||||||
|
|
||||||
if (debug)
|
if (debug) print('Validated and deserialized: $token');
|
||||||
print('Validated and deserialized: $token');
|
|
||||||
|
|
||||||
if (enforceIp) {
|
if (enforceIp) {
|
||||||
if (debug)
|
if (debug)
|
||||||
print('Token IP: ${token.ipAddress}. Current request sent from: ${req.ip}');
|
print(
|
||||||
|
'Token IP: ${token.ipAddress}. Current request sent from: ${req.ip}');
|
||||||
|
|
||||||
if (req.ip != token.ipAddress)
|
if (req.ip != token.ipAddress)
|
||||||
throw new AngelHttpException.Forbidden(
|
throw new AngelHttpException.Forbidden(
|
||||||
|
@ -147,19 +177,21 @@ class AngelAuth extends AngelPlugin {
|
||||||
|
|
||||||
if (token.lifeSpan > -1) {
|
if (token.lifeSpan > -1) {
|
||||||
if (debug) {
|
if (debug) {
|
||||||
print('Checking if token has expired... Life span is ${token.lifeSpan}');
|
print(
|
||||||
|
'Checking if token has expired... Life span is ${token.lifeSpan}');
|
||||||
}
|
}
|
||||||
|
|
||||||
token.issuedAt.add(new Duration(milliseconds: token.lifeSpan));
|
token.issuedAt.add(new Duration(milliseconds: token.lifeSpan));
|
||||||
|
|
||||||
if (!token.issuedAt.isAfter(new DateTime.now())) {
|
if (!token.issuedAt.isAfter(new DateTime.now())) {
|
||||||
print('Token has indeed expired! Resetting assignment date to current timestamp...');
|
print(
|
||||||
|
'Token has indeed expired! Resetting assignment date to current timestamp...');
|
||||||
// Extend its lifespan by changing iat
|
// Extend its lifespan by changing iat
|
||||||
token.issuedAt = new DateTime.now();
|
token.issuedAt = new DateTime.now();
|
||||||
} else if (debug) {
|
} else if (debug) {
|
||||||
print('Token has not expired yet.');
|
print('Token has not expired yet.');
|
||||||
}
|
}
|
||||||
} else if(debug) {
|
} else if (debug) {
|
||||||
print('This token never expires, so it is still valid.');
|
print('This token never expires, so it is still valid.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,9 +225,12 @@ class AngelAuth extends AngelPlugin {
|
||||||
var userId = await serializer(result);
|
var userId = await serializer(result);
|
||||||
|
|
||||||
// Create JWT
|
// Create JWT
|
||||||
var jwt = new AuthToken(userId: userId, lifeSpan: _jwtLifeSpan)
|
var jwt = new AuthToken(
|
||||||
|
userId: userId, lifeSpan: _jwtLifeSpan, ipAddress: req.ip)
|
||||||
.serialize(_hs256);
|
.serialize(_hs256);
|
||||||
req.cookies.add(new Cookie("token", jwt));
|
req.inject(AuthToken, jwt);
|
||||||
|
|
||||||
|
if (allowCookie) req.cookies.add(new Cookie("token", jwt));
|
||||||
|
|
||||||
if (req.headers.value("accept") != null &&
|
if (req.headers.value("accept") != null &&
|
||||||
(req.headers.value("accept").contains("application/json") ||
|
(req.headers.value("accept").contains("application/json") ||
|
||||||
|
|
|
@ -21,8 +21,8 @@ class LocalAuthStrategy extends AuthStrategy {
|
||||||
String usernameField;
|
String usernameField;
|
||||||
String passwordField;
|
String passwordField;
|
||||||
String invalidMessage;
|
String invalidMessage;
|
||||||
bool allowBasic;
|
final bool allowBasic;
|
||||||
bool forceBasic;
|
final bool forceBasic;
|
||||||
String realm;
|
String realm;
|
||||||
|
|
||||||
LocalAuthStrategy(LocalAuthVerifier this.verifier,
|
LocalAuthStrategy(LocalAuthVerifier this.verifier,
|
||||||
|
@ -32,7 +32,8 @@ class LocalAuthStrategy extends AuthStrategy {
|
||||||
'Please provide a valid username and password.',
|
'Please provide a valid username and password.',
|
||||||
bool this.allowBasic: true,
|
bool this.allowBasic: true,
|
||||||
bool this.forceBasic: false,
|
bool this.forceBasic: false,
|
||||||
String this.realm: 'Authentication is required.'}) {}
|
String this.realm: 'Authentication is required.'}) {
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> canLogout(RequestContext req, ResponseContext res) async {
|
Future<bool> canLogout(RequestContext req, ResponseContext res) async {
|
||||||
|
@ -47,6 +48,7 @@ class LocalAuthStrategy extends AuthStrategy {
|
||||||
|
|
||||||
if (allowBasic) {
|
if (allowBasic) {
|
||||||
String authHeader = req.headers.value(HttpHeaders.AUTHORIZATION) ?? "";
|
String authHeader = req.headers.value(HttpHeaders.AUTHORIZATION) ?? "";
|
||||||
|
|
||||||
if (_rgxBasic.hasMatch(authHeader)) {
|
if (_rgxBasic.hasMatch(authHeader)) {
|
||||||
String base64AuthString = _rgxBasic.firstMatch(authHeader).group(1);
|
String base64AuthString = _rgxBasic.firstMatch(authHeader).group(1);
|
||||||
String authString =
|
String authString =
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
name: angel_auth
|
name: angel_auth
|
||||||
description: A complete authentication plugin for Angel.
|
description: A complete authentication plugin for Angel.
|
||||||
version: 1.0.0-dev+9
|
version: 1.0.0-dev+10
|
||||||
author: Tobe O <thosakwe@gmail.com>
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
homepage: https://github.com/angel-dart/angel_auth
|
homepage: https://github.com/angel-dart/angel_auth
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
@ -3,8 +3,8 @@ import 'package:angel_framework/angel_framework.dart';
|
||||||
import 'package:angel_auth/angel_auth.dart';
|
import 'package:angel_auth/angel_auth.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'auth_token.dart' as authToken;
|
import 'auth_token_test.dart' as authToken;
|
||||||
import 'local.dart' as local;
|
import 'local_test.dart' as local;
|
||||||
|
|
||||||
wireAuth(Angel app) async {
|
wireAuth(Angel app) async {
|
||||||
|
|
109
test/local.dart
109
test/local.dart
|
@ -1,109 +0,0 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
|
||||||
import 'package:angel_auth/angel_auth.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:merge_map/merge_map.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
final AngelAuth Auth = new AngelAuth();
|
|
||||||
Map headers = {HttpHeaders.ACCEPT: ContentType.JSON.mimeType};
|
|
||||||
AngelAuthOptions localOpts = new AngelAuthOptions(
|
|
||||||
failureRedirect: '/failure', successRedirect: '/success');
|
|
||||||
Map sampleUser = {'hello': 'world'};
|
|
||||||
|
|
||||||
verifier(username, password) async {
|
|
||||||
if (username == 'username' && password == 'password') {
|
|
||||||
return sampleUser;
|
|
||||||
} else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
wireAuth(Angel app) async {
|
|
||||||
Auth.serializer = (user) async => 1337;
|
|
||||||
Auth.deserializer = (id) async => sampleUser;
|
|
||||||
|
|
||||||
Auth.strategies.add(new LocalAuthStrategy(verifier));
|
|
||||||
await app.configure(Auth);
|
|
||||||
}
|
|
||||||
|
|
||||||
main() async {
|
|
||||||
group('local', () {
|
|
||||||
Angel app;
|
|
||||||
http.Client client;
|
|
||||||
String url;
|
|
||||||
String basicAuthUrl;
|
|
||||||
|
|
||||||
setUp(() async {
|
|
||||||
client = new http.Client();
|
|
||||||
app = new Angel();
|
|
||||||
await app.configure(wireAuth);
|
|
||||||
app.get('/hello', 'Woo auth', middleware: [Auth.authenticate('local')]);
|
|
||||||
app.post('/login', 'This should not be shown',
|
|
||||||
middleware: [Auth.authenticate('local', localOpts)]);
|
|
||||||
app.get('/success', "yep", middleware: ['auth']);
|
|
||||||
app.get('/failure', "nope");
|
|
||||||
|
|
||||||
HttpServer server =
|
|
||||||
await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0);
|
|
||||||
url = "http://${server.address.host}:${server.port}";
|
|
||||||
basicAuthUrl =
|
|
||||||
"http://username:password@${server.address.host}:${server.port}";
|
|
||||||
});
|
|
||||||
|
|
||||||
tearDown(() async {
|
|
||||||
await app.httpServer.close(force: true);
|
|
||||||
client = null;
|
|
||||||
url = null;
|
|
||||||
basicAuthUrl = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
test('can use "auth" as middleware', () async {
|
|
||||||
var response = await client
|
|
||||||
.get("$url/success", headers: {'Accept': 'application/json'});
|
|
||||||
print(response.body);
|
|
||||||
expect(response.statusCode, equals(403));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('successRedirect', () async {
|
|
||||||
Map postData = {'username': 'username', 'password': 'password'};
|
|
||||||
var response = await client.post("$url/login",
|
|
||||||
body: JSON.encode(postData),
|
|
||||||
headers: {HttpHeaders.CONTENT_TYPE: ContentType.JSON.mimeType});
|
|
||||||
expect(response.statusCode, equals(200));
|
|
||||||
expect(response.headers[HttpHeaders.LOCATION], equals('/success'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('failureRedirect', () async {
|
|
||||||
Map postData = {'username': 'password', 'password': 'username'};
|
|
||||||
var response = await client.post("$url/login",
|
|
||||||
body: JSON.encode(postData),
|
|
||||||
headers: {HttpHeaders.CONTENT_TYPE: ContentType.JSON.mimeType});
|
|
||||||
print("Login response: ${response.body}");
|
|
||||||
expect(response.headers[HttpHeaders.LOCATION], equals('/failure'));
|
|
||||||
expect(response.statusCode, equals(401));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('allow basic', () async {
|
|
||||||
String authString = BASE64.encode("username:password".runes.toList());
|
|
||||||
var response = await client.get("$url/hello",
|
|
||||||
headers: {HttpHeaders.AUTHORIZATION: 'Basic $authString'});
|
|
||||||
expect(response.body, equals('"Woo auth"'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('allow basic via URL encoding', () async {
|
|
||||||
var response = await client.get("$basicAuthUrl/hello");
|
|
||||||
expect(response.body, equals('"Woo auth"'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('force basic', () async {
|
|
||||||
Auth.strategies.clear();
|
|
||||||
Auth.strategies.add(new LocalAuthStrategy(verifier,
|
|
||||||
forceBasic: true, realm: 'test'));
|
|
||||||
var response = await client.get("$url/hello", headers: headers);
|
|
||||||
print(response.headers);
|
|
||||||
expect(response.headers[HttpHeaders.WWW_AUTHENTICATE],
|
|
||||||
equals('Basic realm="test"'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
111
test/local_test.dart
Normal file
111
test/local_test.dart
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:angel_auth/angel_auth.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:merge_map/merge_map.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
final AngelAuth Auth = new AngelAuth();
|
||||||
|
Map headers = {HttpHeaders.ACCEPT: ContentType.JSON.mimeType};
|
||||||
|
AngelAuthOptions localOpts = new AngelAuthOptions(
|
||||||
|
failureRedirect: '/failure', successRedirect: '/success');
|
||||||
|
Map sampleUser = {'hello': 'world'};
|
||||||
|
|
||||||
|
verifier(username, password) async {
|
||||||
|
if (username == 'username' && password == 'password') {
|
||||||
|
return sampleUser;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
wireAuth(Angel app) async {
|
||||||
|
Auth.serializer = (user) async => 1337;
|
||||||
|
Auth.deserializer = (id) async => sampleUser;
|
||||||
|
|
||||||
|
Auth.strategies.add(new LocalAuthStrategy(verifier));
|
||||||
|
await app.configure(Auth);
|
||||||
|
}
|
||||||
|
|
||||||
|
main() async {
|
||||||
|
Angel app;
|
||||||
|
http.Client client;
|
||||||
|
String url;
|
||||||
|
String basicAuthUrl;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
client = new http.Client();
|
||||||
|
app = new Angel(debug: true);
|
||||||
|
await app.configure(wireAuth);
|
||||||
|
app.get('/hello', 'Woo auth', middleware: [Auth.authenticate('local')]);
|
||||||
|
app.post('/login', 'This should not be shown',
|
||||||
|
middleware: [Auth.authenticate('local', localOpts)]);
|
||||||
|
app.get('/success', "yep", middleware: ['auth']);
|
||||||
|
app.get('/failure', "nope");
|
||||||
|
|
||||||
|
app
|
||||||
|
..normalize()
|
||||||
|
..dumpTree();
|
||||||
|
|
||||||
|
HttpServer server =
|
||||||
|
await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0);
|
||||||
|
url = "http://${server.address.host}:${server.port}";
|
||||||
|
basicAuthUrl =
|
||||||
|
"http://username:password@${server.address.host}:${server.port}";
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
await app.httpServer.close(force: true);
|
||||||
|
client = null;
|
||||||
|
url = null;
|
||||||
|
basicAuthUrl = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can use "auth" as middleware', () async {
|
||||||
|
var response = await client
|
||||||
|
.get("$url/success", headers: {'Accept': 'application/json'});
|
||||||
|
print(response.body);
|
||||||
|
expect(response.statusCode, equals(403));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successRedirect', () async {
|
||||||
|
Map postData = {'username': 'username', 'password': 'password'};
|
||||||
|
var response = await client.post("$url/login",
|
||||||
|
body: JSON.encode(postData),
|
||||||
|
headers: {HttpHeaders.CONTENT_TYPE: ContentType.JSON.mimeType});
|
||||||
|
expect(response.statusCode, equals(200));
|
||||||
|
expect(response.headers[HttpHeaders.LOCATION], equals('/success'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failureRedirect', () async {
|
||||||
|
Map postData = {'username': 'password', 'password': 'username'};
|
||||||
|
var response = await client.post("$url/login",
|
||||||
|
body: JSON.encode(postData),
|
||||||
|
headers: {HttpHeaders.CONTENT_TYPE: ContentType.JSON.mimeType});
|
||||||
|
print("Login response: ${response.body}");
|
||||||
|
expect(response.headers[HttpHeaders.LOCATION], equals('/failure'));
|
||||||
|
expect(response.statusCode, equals(401));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('allow basic', () async {
|
||||||
|
String authString = BASE64.encode("username:password".runes.toList());
|
||||||
|
var response = await client.get("$url/hello",
|
||||||
|
headers: {HttpHeaders.AUTHORIZATION: 'Basic $authString'});
|
||||||
|
expect(response.body, equals('"Woo auth"'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('allow basic via URL encoding', () async {
|
||||||
|
var response = await client.get("$basicAuthUrl/hello");
|
||||||
|
expect(response.body, equals('"Woo auth"'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('force basic', () async {
|
||||||
|
Auth.strategies.clear();
|
||||||
|
Auth.strategies
|
||||||
|
.add(new LocalAuthStrategy(verifier, forceBasic: true, realm: 'test'));
|
||||||
|
var response = await client.get("$url/hello", headers: headers);
|
||||||
|
print(response.headers);
|
||||||
|
expect(response.headers[HttpHeaders.WWW_AUTHENTICATE],
|
||||||
|
equals('Basic realm="test"'));
|
||||||
|
});
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
../packages
|
|
Loading…
Reference in a new issue