1.1.1
This commit is contained in:
parent
c891d130b2
commit
c8444a7c7b
12 changed files with 152 additions and 51 deletions
|
@ -2,6 +2,7 @@
|
||||||
<module type="WEB_MODULE" version="4">
|
<module type="WEB_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
|
||||||
<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$/build" />
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="tests in protect_cookie_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
||||||
|
<option name="filePath" value="$PROJECT_DIR$/test/protect_cookie_test.dart" />
|
||||||
|
<option name="testRunnerOptions" value="-j4" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
|
@ -1 +1,4 @@
|
||||||
language: dart
|
language: dart
|
||||||
|
dart:
|
||||||
|
- dev
|
||||||
|
- stable
|
|
@ -1,3 +1,6 @@
|
||||||
|
# 1.1.1
|
||||||
|
* Added `protectCookie`, to better protect data sent in cookies.
|
||||||
|
|
||||||
# 1.1.0+2
|
# 1.1.0+2
|
||||||
* `LocalAuthStrategy` returns `true` on `Basic` authentication.
|
* `LocalAuthStrategy` returns `true` on `Basic` authentication.
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
analyzer:
|
analyzer:
|
||||||
strong-mode: true
|
strong-mode:
|
||||||
|
implicit-casts: false
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:dart2_constant/convert.dart';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
|
|
||||||
/// Calls [BASE64URL], but also works for strings with lengths
|
/// Calls [BASE64URL], but also works for strings with lengths
|
||||||
|
@ -21,7 +21,7 @@ String decodeBase64(String str) {
|
||||||
throw 'Illegal base64url string!"';
|
throw 'Illegal base64url string!"';
|
||||||
}
|
}
|
||||||
|
|
||||||
return UTF8.decode(BASE64URL.decode(output));
|
return utf8.decode(base64Url.decode(output));
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthToken {
|
class AuthToken {
|
||||||
|
@ -39,21 +39,23 @@ class AuthToken {
|
||||||
this.lifeSpan: -1,
|
this.lifeSpan: -1,
|
||||||
this.userId,
|
this.userId,
|
||||||
DateTime issuedAt,
|
DateTime issuedAt,
|
||||||
Map<String, dynamic> payload: const {}}) {
|
Map payload: const {}}) {
|
||||||
this.issuedAt = issuedAt ?? new DateTime.now();
|
this.issuedAt = issuedAt ?? new DateTime.now();
|
||||||
this.payload.addAll(payload ?? {});
|
this.payload.addAll(
|
||||||
|
payload?.keys?.fold({}, (out, k) => out..[k.toString()] = payload[k]) ??
|
||||||
|
{});
|
||||||
}
|
}
|
||||||
|
|
||||||
factory AuthToken.fromJson(String json) =>
|
factory AuthToken.fromJson(String jsons) =>
|
||||||
new AuthToken.fromMap(JSON.decode(json));
|
new AuthToken.fromMap(json.decode(jsons) as Map);
|
||||||
|
|
||||||
factory AuthToken.fromMap(Map data) {
|
factory AuthToken.fromMap(Map data) {
|
||||||
return new AuthToken(
|
return new AuthToken(
|
||||||
ipAddress: data["aud"],
|
ipAddress: data["aud"].toString(),
|
||||||
lifeSpan: data["exp"],
|
lifeSpan: data["exp"] as num,
|
||||||
issuedAt: DateTime.parse(data["iat"]),
|
issuedAt: DateTime.parse(data["iat"].toString()),
|
||||||
userId: data["sub"],
|
userId: data["sub"],
|
||||||
payload: data["pld"] ?? {});
|
payload: data["pld"] as Map ?? {});
|
||||||
}
|
}
|
||||||
|
|
||||||
factory AuthToken.parse(String jwt) {
|
factory AuthToken.parse(String jwt) {
|
||||||
|
@ -63,7 +65,7 @@ class AuthToken {
|
||||||
throw new AngelHttpException.notAuthenticated(message: "Invalid JWT.");
|
throw new AngelHttpException.notAuthenticated(message: "Invalid JWT.");
|
||||||
|
|
||||||
var payloadString = decodeBase64(split[1]);
|
var payloadString = decodeBase64(split[1]);
|
||||||
return new AuthToken.fromMap(JSON.decode(payloadString));
|
return new AuthToken.fromMap(json.decode(payloadString) as Map);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory AuthToken.validate(String jwt, Hmac hmac) {
|
factory AuthToken.validate(String jwt, Hmac hmac) {
|
||||||
|
@ -75,21 +77,21 @@ class AuthToken {
|
||||||
// var headerString = decodeBase64(split[0]);
|
// var headerString = decodeBase64(split[0]);
|
||||||
var payloadString = decodeBase64(split[1]);
|
var payloadString = decodeBase64(split[1]);
|
||||||
var data = split[0] + "." + split[1];
|
var data = split[0] + "." + split[1];
|
||||||
var signature = BASE64URL.encode(hmac.convert(data.codeUnits).bytes);
|
var signature = base64Url.encode(hmac.convert(data.codeUnits).bytes);
|
||||||
|
|
||||||
if (signature != split[2])
|
if (signature != split[2])
|
||||||
throw new AngelHttpException.notAuthenticated(
|
throw new AngelHttpException.notAuthenticated(
|
||||||
message: "JWT payload does not match hashed version.");
|
message: "JWT payload does not match hashed version.");
|
||||||
|
|
||||||
return new AuthToken.fromMap(JSON.decode(payloadString));
|
return new AuthToken.fromMap(json.decode(payloadString) as Map);
|
||||||
}
|
}
|
||||||
|
|
||||||
String serialize(Hmac hmac) {
|
String serialize(Hmac hmac) {
|
||||||
var headerString = BASE64URL.encode(JSON.encode(_header).codeUnits);
|
var headerString = base64Url.encode(json.encode(_header).codeUnits);
|
||||||
var payloadString = BASE64URL.encode(JSON.encode(toJson()).codeUnits);
|
var payloadString = base64Url.encode(json.encode(toJson()).codeUnits);
|
||||||
var data = headerString + "." + payloadString;
|
var data = headerString + "." + payloadString;
|
||||||
var signature = hmac.convert(data.codeUnits).bytes;
|
var signature = hmac.convert(data.codeUnits).bytes;
|
||||||
return data + "." + BASE64URL.encode(signature);
|
return data + "." + base64Url.encode(signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map toJson() {
|
Map toJson() {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import 'strategy.dart';
|
||||||
/// Handles authentication within an Angel application.
|
/// Handles authentication within an Angel application.
|
||||||
class AngelAuth<T> {
|
class AngelAuth<T> {
|
||||||
Hmac _hs256;
|
Hmac _hs256;
|
||||||
num _jwtLifeSpan;
|
int _jwtLifeSpan;
|
||||||
final StreamController<T> _onLogin = new StreamController<T>(),
|
final StreamController<T> _onLogin = new StreamController<T>(),
|
||||||
_onLogout = new StreamController<T>();
|
_onLogout = new StreamController<T>();
|
||||||
Math.Random _random = new Math.Random.secure();
|
Math.Random _random = new Math.Random.secure();
|
||||||
|
@ -24,13 +24,22 @@ class AngelAuth<T> {
|
||||||
/// If `true` (default), then users can include a JWT in the query string as `token`.
|
/// If `true` (default), then users can include a JWT in the query string as `token`.
|
||||||
final bool allowTokenInQuery;
|
final bool allowTokenInQuery;
|
||||||
|
|
||||||
|
/// Whether emitted cookies should have the `secure` and `HttpOnly` flags,
|
||||||
|
/// as well as being restricted to a specific domain.
|
||||||
|
final bool secureCookies;
|
||||||
|
|
||||||
|
/// A domain to restrict emitted cookies to.
|
||||||
|
///
|
||||||
|
/// Only applies if [secureCookies] is `true`.
|
||||||
|
final String cookieDomain;
|
||||||
|
|
||||||
/// The name to register [requireAuth] as. Default: `auth`.
|
/// The name to register [requireAuth] as. Default: `auth`.
|
||||||
String middlewareName;
|
String middlewareName;
|
||||||
|
|
||||||
/// If `true` (default), then JWT's will be considered invalid if used from a different IP than the first user's it was issued to.
|
/// If `true` (default), then JWT's will be considered invalid if used from a different IP than the first user's it was issued to.
|
||||||
///
|
///
|
||||||
/// This is a security provision. Even if a user's JWT is stolen, a remote attacker will not be able to impersonate anyone.
|
/// This is a security provision. Even if a user's JWT is stolen, a remote attacker will not be able to impersonate anyone.
|
||||||
bool enforceIp;
|
final bool enforceIp;
|
||||||
|
|
||||||
/// The endpoint to mount [reviveJwt] at. If `null`, then no revival route is mounted. Default: `/auth/token`.
|
/// The endpoint to mount [reviveJwt] at. If `null`, then no revival route is mounted. Default: `/auth/token`.
|
||||||
String reviveTokenEndpoint;
|
String reviveTokenEndpoint;
|
||||||
|
@ -62,17 +71,20 @@ class AngelAuth<T> {
|
||||||
return new String.fromCharCodes(chars);
|
return new String.fromCharCodes(chars);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `jwtLifeSpan` - should be in *milliseconds*.
|
||||||
AngelAuth(
|
AngelAuth(
|
||||||
{String jwtKey,
|
{String jwtKey,
|
||||||
num jwtLifeSpan,
|
num jwtLifeSpan,
|
||||||
this.allowCookie: true,
|
this.allowCookie: true,
|
||||||
this.allowTokenInQuery: true,
|
this.allowTokenInQuery: true,
|
||||||
this.enforceIp: true,
|
this.enforceIp: true,
|
||||||
|
this.cookieDomain,
|
||||||
|
this.secureCookies: true,
|
||||||
this.middlewareName: 'auth',
|
this.middlewareName: 'auth',
|
||||||
this.reviveTokenEndpoint: "/auth/token"})
|
this.reviveTokenEndpoint: "/auth/token"})
|
||||||
: super() {
|
: super() {
|
||||||
_hs256 = new Hmac(sha256, (jwtKey ?? _randomString()).codeUnits);
|
_hs256 = new Hmac(sha256, (jwtKey ?? _randomString()).codeUnits);
|
||||||
_jwtLifeSpan = jwtLifeSpan ?? -1;
|
_jwtLifeSpan = jwtLifeSpan?.toInt() ?? -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future configureServer(Angel app) async {
|
Future configureServer(Angel app) async {
|
||||||
|
@ -121,7 +133,7 @@ class AngelAuth<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token.lifeSpan > -1) {
|
if (token.lifeSpan > -1) {
|
||||||
token.issuedAt.add(new Duration(milliseconds: token.lifeSpan));
|
token.issuedAt.add(new Duration(milliseconds: token.lifeSpan.toInt()));
|
||||||
|
|
||||||
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.");
|
||||||
|
@ -146,20 +158,40 @@ class AngelAuth<T> {
|
||||||
req.cookies.any((cookie) => cookie.name == "token")) {
|
req.cookies.any((cookie) => cookie.name == "token")) {
|
||||||
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) {
|
} else if (allowTokenInQuery && req.query['token'] is String) {
|
||||||
return req.query['token'];
|
return req.query['token']?.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Applies security protections to a [cookie].
|
||||||
|
Cookie protectCookie(Cookie cookie) {
|
||||||
|
if (secureCookies != false) {
|
||||||
|
cookie.httpOnly = true;
|
||||||
|
cookie.secure = true;
|
||||||
|
cookie.domain ??= cookieDomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie.maxAge ??=
|
||||||
|
_jwtLifeSpan < 0 ? -1 : _jwtLifeSpan ~/ Duration.millisecondsPerSecond;
|
||||||
|
|
||||||
|
if (_jwtLifeSpan > 0) {
|
||||||
|
cookie.expires ??=
|
||||||
|
new DateTime.now().add(new Duration(milliseconds: _jwtLifeSpan));
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookie;
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempts to revive an expired (or still alive) JWT.
|
/// Attempts to revive an expired (or still alive) JWT.
|
||||||
Future<Map<String, dynamic>> reviveJwt(RequestContext req, ResponseContext res) async {
|
Future<Map<String, dynamic>> reviveJwt(
|
||||||
|
RequestContext req, ResponseContext res) async {
|
||||||
try {
|
try {
|
||||||
var jwt = getJwt(req);
|
var jwt = getJwt(req);
|
||||||
|
|
||||||
if (jwt == null) {
|
if (jwt == null) {
|
||||||
var body = await req.lazyBody();
|
var body = await req.lazyBody();
|
||||||
jwt = body['token'];
|
jwt = body['token']?.toString();
|
||||||
}
|
}
|
||||||
if (jwt == null) {
|
if (jwt == null) {
|
||||||
throw new AngelHttpException.forbidden(message: "No JWT provided");
|
throw new AngelHttpException.forbidden(message: "No JWT provided");
|
||||||
|
@ -172,7 +204,7 @@ class AngelAuth<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token.lifeSpan > -1) {
|
if (token.lifeSpan > -1) {
|
||||||
token.issuedAt.add(new Duration(milliseconds: token.lifeSpan));
|
token.issuedAt.add(new Duration(milliseconds: token.lifeSpan.toInt()));
|
||||||
|
|
||||||
if (!token.issuedAt.isAfter(new DateTime.now())) {
|
if (!token.issuedAt.isAfter(new DateTime.now())) {
|
||||||
print(
|
print(
|
||||||
|
@ -183,7 +215,8 @@ class AngelAuth<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allowCookie)
|
if (allowCookie)
|
||||||
res.cookies.add(new Cookie('token', token.serialize(_hs256)));
|
res.cookies
|
||||||
|
.add(protectCookie(new Cookie('token', token.serialize(_hs256))));
|
||||||
|
|
||||||
final data = await deserializer(token.userId);
|
final data = await deserializer(token.userId);
|
||||||
return {'data': data, 'token': token.serialize(_hs256)};
|
return {'data': data, 'token': token.serialize(_hs256)};
|
||||||
|
@ -207,7 +240,7 @@ class AngelAuth<T> {
|
||||||
List<String> names = [];
|
List<String> names = [];
|
||||||
var arr = type is Iterable ? type.toList() : [type];
|
var arr = type is Iterable ? type.toList() : [type];
|
||||||
|
|
||||||
for (var t in arr) {
|
for (String t in arr) {
|
||||||
var n = t
|
var n = t
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((s) => s.trim())
|
.map((s) => s.trim())
|
||||||
|
@ -227,7 +260,7 @@ class AngelAuth<T> {
|
||||||
if (result == true)
|
if (result == true)
|
||||||
return result;
|
return result;
|
||||||
else if (result != false) {
|
else if (result != false) {
|
||||||
var userId = await serializer(result);
|
var userId = await serializer(result as T);
|
||||||
|
|
||||||
// Create JWT
|
// Create JWT
|
||||||
var token = new AuthToken(
|
var token = new AuthToken(
|
||||||
|
@ -242,7 +275,8 @@ class AngelAuth<T> {
|
||||||
|
|
||||||
_apply(req, token, result);
|
_apply(req, token, result);
|
||||||
|
|
||||||
if (allowCookie) res.cookies.add(new Cookie("token", jwt));
|
if (allowCookie)
|
||||||
|
res.cookies.add(protectCookie(new Cookie("token", jwt)));
|
||||||
|
|
||||||
if (options?.callback != null) {
|
if (options?.callback != null) {
|
||||||
return await options.callback(req, res, jwt);
|
return await options.callback(req, res, jwt);
|
||||||
|
@ -256,7 +290,7 @@ class AngelAuth<T> {
|
||||||
(req.headers.value("accept").contains("application/json") ||
|
(req.headers.value("accept").contains("application/json") ||
|
||||||
req.headers.value("accept").contains("*/*") ||
|
req.headers.value("accept").contains("*/*") ||
|
||||||
req.headers.value("accept").contains("application/*"))) {
|
req.headers.value("accept").contains("application/*"))) {
|
||||||
var user = await deserializer(await serializer(result));
|
var user = await deserializer(await serializer(result as T));
|
||||||
_onLogin.add(user);
|
_onLogin.add(user);
|
||||||
return {"data": user, "token": jwt};
|
return {"data": user, "token": jwt};
|
||||||
}
|
}
|
||||||
|
@ -283,7 +317,8 @@ class AngelAuth<T> {
|
||||||
_onLogin.add(user);
|
_onLogin.add(user);
|
||||||
|
|
||||||
if (allowCookie)
|
if (allowCookie)
|
||||||
res.cookies.add(new Cookie('token', token.serialize(_hs256)));
|
res.cookies
|
||||||
|
.add(protectCookie(new Cookie('token', token.serialize(_hs256))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Log a user in on-demand.
|
/// Log a user in on-demand.
|
||||||
|
@ -295,7 +330,8 @@ class AngelAuth<T> {
|
||||||
_onLogin.add(user);
|
_onLogin.add(user);
|
||||||
|
|
||||||
if (allowCookie)
|
if (allowCookie)
|
||||||
res.cookies.add(new Cookie('token', token.serialize(_hs256)));
|
res.cookies
|
||||||
|
.add(protectCookie(new Cookie('token', token.serialize(_hs256))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Log an authenticated user out.
|
/// Log an authenticated user out.
|
||||||
|
@ -314,7 +350,7 @@ class AngelAuth<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
var user = req.grab('user');
|
var user = req.grab('user');
|
||||||
if (user != null) _onLogout.add(user);
|
if (user != null) _onLogout.add(user as T);
|
||||||
|
|
||||||
req.injections..remove(AuthToken)..remove('user');
|
req.injections..remove(AuthToken)..remove('user');
|
||||||
req.properties.remove('user');
|
req.properties.remove('user');
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'package:dart2_constant/convert.dart';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
import '../options.dart';
|
import '../options.dart';
|
||||||
|
@ -50,7 +50,7 @@ class LocalAuthStrategy extends AuthStrategy {
|
||||||
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 =
|
||||||
new String.fromCharCodes(BASE64.decode(base64AuthString));
|
new String.fromCharCodes(base64.decode(base64AuthString));
|
||||||
if (_rgxUsrPass.hasMatch(authString)) {
|
if (_rgxUsrPass.hasMatch(authString)) {
|
||||||
Match usrPassMatch = _rgxUsrPass.firstMatch(authString);
|
Match usrPassMatch = _rgxUsrPass.firstMatch(authString);
|
||||||
verificationResult =
|
verificationResult =
|
||||||
|
@ -73,10 +73,10 @@ class LocalAuthStrategy extends AuthStrategy {
|
||||||
|
|
||||||
if (verificationResult == null) {
|
if (verificationResult == null) {
|
||||||
await req.parse();
|
await req.parse();
|
||||||
if (_validateString(req.body[usernameField]) &&
|
if (_validateString(req.body[usernameField]?.toString()) &&
|
||||||
_validateString(req.body[passwordField])) {
|
_validateString(req.body[passwordField]?.toString())) {
|
||||||
verificationResult =
|
verificationResult =
|
||||||
await verifier(req.body[usernameField], req.body[passwordField]);
|
await verifier(req.body[usernameField]?.toString(), req.body[passwordField]?.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.1.0+2
|
version: 1.1.1
|
||||||
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
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -13,6 +13,7 @@ class User extends Model {
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
Angel app;
|
Angel app;
|
||||||
|
AngelHttp angelHttp;
|
||||||
AngelAuth auth;
|
AngelAuth auth;
|
||||||
http.Client client;
|
http.Client client;
|
||||||
HttpServer server;
|
HttpServer server;
|
||||||
|
@ -20,14 +21,15 @@ main() {
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
app = new Angel();
|
app = new Angel();
|
||||||
|
angelHttp = new AngelHttp(app, useZone: false);
|
||||||
app.use('/users', new TypedService<User>(new MapService()));
|
app.use('/users', new TypedService<User>(new MapService()));
|
||||||
|
|
||||||
await app
|
await app
|
||||||
.service('users')
|
.service('users')
|
||||||
.create({'username': 'jdoe1', 'password': 'password'});
|
.create({'username': 'jdoe1', 'password': 'password'});
|
||||||
|
|
||||||
auth = new AngelAuth();
|
auth = new AngelAuth<User>();
|
||||||
auth.serializer = (User user) async => user.id;
|
auth.serializer = (u) => u.id;
|
||||||
auth.deserializer = app.service('users').read;
|
auth.deserializer = app.service('users').read;
|
||||||
|
|
||||||
await app.configure(auth.configureServer);
|
await app.configure(auth.configureServer);
|
||||||
|
@ -52,13 +54,13 @@ main() {
|
||||||
})));
|
})));
|
||||||
|
|
||||||
client = new http.Client();
|
client = new http.Client();
|
||||||
server = await app.startServer();
|
server = await angelHttp.startServer();
|
||||||
url = 'http://${server.address.address}:${server.port}';
|
url = 'http://${server.address.address}:${server.port}';
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
client.close();
|
client.close();
|
||||||
await server.close(force: true);
|
await angelHttp.close();
|
||||||
app = null;
|
app = null;
|
||||||
client = null;
|
client = null;
|
||||||
url = null;
|
url = null;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
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';
|
||||||
import 'package:angel_auth/angel_auth.dart';
|
import 'package:angel_auth/angel_auth.dart';
|
||||||
|
import 'package:dart2_constant/convert.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';
|
||||||
|
|
||||||
final AngelAuth auth = new AngelAuth();
|
final AngelAuth auth = new AngelAuth();
|
||||||
Map headers = {HttpHeaders.ACCEPT: ContentType.JSON.mimeType};
|
var headers = <String, String>{HttpHeaders.ACCEPT: ContentType.JSON.mimeType};
|
||||||
AngelAuthOptions localOpts = new AngelAuthOptions(
|
AngelAuthOptions localOpts = new AngelAuthOptions(
|
||||||
failureRedirect: '/failure', successRedirect: '/success');
|
failureRedirect: '/failure', successRedirect: '/success');
|
||||||
Map sampleUser = {'hello': 'world'};
|
Map sampleUser = {'hello': 'world'};
|
||||||
|
@ -30,6 +30,7 @@ Future wireAuth(Angel app) async {
|
||||||
|
|
||||||
main() async {
|
main() async {
|
||||||
Angel app;
|
Angel app;
|
||||||
|
AngelHttp angelHttp;
|
||||||
http.Client client;
|
http.Client client;
|
||||||
String url;
|
String url;
|
||||||
String basicAuthUrl;
|
String basicAuthUrl;
|
||||||
|
@ -37,6 +38,7 @@ main() async {
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
client = new http.Client();
|
client = new http.Client();
|
||||||
app = new Angel();
|
app = new Angel();
|
||||||
|
angelHttp = new AngelHttp(app, useZone: false);
|
||||||
await app.configure(wireAuth);
|
await app.configure(wireAuth);
|
||||||
app.get('/hello', 'Woo auth', middleware: [auth.authenticate('local')]);
|
app.get('/hello', 'Woo auth', middleware: [auth.authenticate('local')]);
|
||||||
app.post('/login', 'This should not be shown',
|
app.post('/login', 'This should not be shown',
|
||||||
|
@ -45,14 +47,14 @@ main() async {
|
||||||
app.get('/failure', "nope");
|
app.get('/failure', "nope");
|
||||||
|
|
||||||
HttpServer server =
|
HttpServer server =
|
||||||
await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0);
|
await angelHttp.startServer('127.0.0.1', 0);
|
||||||
url = "http://${server.address.host}:${server.port}";
|
url = "http://${server.address.host}:${server.port}";
|
||||||
basicAuthUrl =
|
basicAuthUrl =
|
||||||
"http://username:password@${server.address.host}:${server.port}";
|
"http://username:password@${server.address.host}:${server.port}";
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
await app.httpServer.close(force: true);
|
await angelHttp.close();
|
||||||
client = null;
|
client = null;
|
||||||
url = null;
|
url = null;
|
||||||
basicAuthUrl = null;
|
basicAuthUrl = null;
|
||||||
|
@ -68,7 +70,7 @@ main() async {
|
||||||
test('successRedirect', () async {
|
test('successRedirect', () async {
|
||||||
Map postData = {'username': 'username', 'password': 'password'};
|
Map postData = {'username': 'username', 'password': 'password'};
|
||||||
var response = await client.post("$url/login",
|
var response = await client.post("$url/login",
|
||||||
body: JSON.encode(postData),
|
body: json.encode(postData),
|
||||||
headers: {HttpHeaders.CONTENT_TYPE: ContentType.JSON.mimeType});
|
headers: {HttpHeaders.CONTENT_TYPE: ContentType.JSON.mimeType});
|
||||||
expect(response.statusCode, equals(200));
|
expect(response.statusCode, equals(200));
|
||||||
expect(response.headers[HttpHeaders.LOCATION], equals('/success'));
|
expect(response.headers[HttpHeaders.LOCATION], equals('/success'));
|
||||||
|
@ -77,7 +79,7 @@ main() async {
|
||||||
test('failureRedirect', () async {
|
test('failureRedirect', () async {
|
||||||
Map postData = {'username': 'password', 'password': 'username'};
|
Map postData = {'username': 'password', 'password': 'username'};
|
||||||
var response = await client.post("$url/login",
|
var response = await client.post("$url/login",
|
||||||
body: JSON.encode(postData),
|
body: json.encode(postData),
|
||||||
headers: {HttpHeaders.CONTENT_TYPE: ContentType.JSON.mimeType});
|
headers: {HttpHeaders.CONTENT_TYPE: ContentType.JSON.mimeType});
|
||||||
print("Login response: ${response.body}");
|
print("Login response: ${response.body}");
|
||||||
expect(response.headers[HttpHeaders.LOCATION], equals('/failure'));
|
expect(response.headers[HttpHeaders.LOCATION], equals('/failure'));
|
||||||
|
@ -85,7 +87,7 @@ main() async {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('allow basic', () async {
|
test('allow basic', () async {
|
||||||
String authString = BASE64.encode("username:password".runes.toList());
|
String authString = base64.encode("username:password".runes.toList());
|
||||||
var response = await client.get("$url/hello",
|
var response = await client.get("$url/hello",
|
||||||
headers: {HttpHeaders.AUTHORIZATION: 'Basic $authString'});
|
headers: {HttpHeaders.AUTHORIZATION: 'Basic $authString'});
|
||||||
expect(response.body, equals('"Woo auth"'));
|
expect(response.body, equals('"Woo auth"'));
|
||||||
|
|
44
test/protect_cookie_test.dart
Normal file
44
test/protect_cookie_test.dart
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:angel_auth/angel_auth.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
const Duration threeDays = const Duration(days: 3);
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
Cookie defaultCookie;
|
||||||
|
var auth = new AngelAuth(
|
||||||
|
secureCookies: true,
|
||||||
|
cookieDomain: 'SECURE',
|
||||||
|
jwtLifeSpan: threeDays.inMilliseconds,
|
||||||
|
);
|
||||||
|
|
||||||
|
setUp(() => defaultCookie = new Cookie('a', 'b'));
|
||||||
|
|
||||||
|
test('sets maxAge', () {
|
||||||
|
expect(auth.protectCookie(defaultCookie).maxAge, threeDays.inSeconds);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sets expires', () {
|
||||||
|
var now = new DateTime.now();
|
||||||
|
var expiry = auth.protectCookie(defaultCookie).expires;
|
||||||
|
var diff = expiry.difference(now);
|
||||||
|
expect(diff.inSeconds, threeDays.inSeconds);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sets httpOnly', () {
|
||||||
|
expect(auth.protectCookie(defaultCookie).httpOnly, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sets secure', () {
|
||||||
|
expect(auth.protectCookie(defaultCookie).secure, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sets domain', () {
|
||||||
|
expect(auth.protectCookie(defaultCookie).domain, 'SECURE');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('preserves domain if present', () {
|
||||||
|
expect(auth.protectCookie(defaultCookie..domain = 'foo').domain, 'foo');
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue