18
This commit is contained in:
parent
771a4e8a5c
commit
e163c1b9e9
6 changed files with 80 additions and 4 deletions
|
@ -1,6 +1,6 @@
|
||||||
# angel_auth
|
# angel_auth
|
||||||
|
|
||||||
[![version 1.1.0-dev+17](https://img.shields.io/badge/version-1.1.0--dev+17-red.svg)](https://pub.dartlang.org/packages/angel_auth)
|
[![version 1.1.0-dev+18](https://img.shields.io/badge/version-1.1.0--dev+18-red.svg)](https://pub.dartlang.org/packages/angel_auth)
|
||||||
![build status](https://travis-ci.org/angel-dart/auth.svg?branch=master)
|
![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.
|
||||||
|
|
4
lib/auth_token.dart
Normal file
4
lib/auth_token.dart
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/// Stand-alone JWT library.
|
||||||
|
library angel_auth.auth_token;
|
||||||
|
|
||||||
|
export 'src/auth_token.dart';
|
|
@ -3,6 +3,27 @@ import 'dart:convert';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
|
|
||||||
|
/// Calls [BASE64URL], but also works for strings with lengths
|
||||||
|
/// that are *not* multiples of 4.
|
||||||
|
String decodeBase64(String str) {
|
||||||
|
var output = str.replaceAll('-', '+').replaceAll('_', '/');
|
||||||
|
|
||||||
|
switch (output.length % 4) {
|
||||||
|
case 0:
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
output += '==';
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
output += '=';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw 'Illegal base64url string!"';
|
||||||
|
}
|
||||||
|
|
||||||
|
return UTF8.decode(BASE64URL.decode(output));
|
||||||
|
}
|
||||||
|
|
||||||
class AuthToken {
|
class AuthToken {
|
||||||
final SplayTreeMap<String, String> _header =
|
final SplayTreeMap<String, String> _header =
|
||||||
new SplayTreeMap.from({"alg": "HS256", "typ": "JWT"});
|
new SplayTreeMap.from({"alg": "HS256", "typ": "JWT"});
|
||||||
|
@ -35,14 +56,24 @@ class AuthToken {
|
||||||
payload: data["pld"] ?? {});
|
payload: data["pld"] ?? {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
factory AuthToken.parse(String jwt) {
|
||||||
|
var split = jwt.split(".");
|
||||||
|
|
||||||
|
if (split.length != 3)
|
||||||
|
throw new AngelHttpException.notAuthenticated(message: "Invalid JWT.");
|
||||||
|
|
||||||
|
var payloadString = decodeBase64(split[1]);
|
||||||
|
return new AuthToken.fromMap(JSON.decode(payloadString));
|
||||||
|
}
|
||||||
|
|
||||||
factory AuthToken.validate(String jwt, Hmac hmac) {
|
factory AuthToken.validate(String jwt, Hmac hmac) {
|
||||||
var split = jwt.split(".");
|
var split = jwt.split(".");
|
||||||
|
|
||||||
if (split.length != 3)
|
if (split.length != 3)
|
||||||
throw new AngelHttpException.notAuthenticated(message: "Invalid JWT.");
|
throw new AngelHttpException.notAuthenticated(message: "Invalid JWT.");
|
||||||
|
|
||||||
// var headerString = new String.fromCharCodes(BASE64URL.decode(split[0]));
|
// var headerString = decodeBase64(split[0]);
|
||||||
var payloadString = new String.fromCharCodes(BASE64URL.decode(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);
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'auth_token.dart';
|
||||||
|
|
||||||
typedef AngelAuthCallback(
|
typedef AngelAuthCallback(
|
||||||
RequestContext req, ResponseContext res, String token);
|
RequestContext req, ResponseContext res, String token);
|
||||||
|
|
||||||
|
typedef AngelAuthTokenCallback(
|
||||||
|
RequestContext req, ResponseContext res, AuthToken token, user);
|
||||||
|
|
||||||
class AngelAuthOptions {
|
class AngelAuthOptions {
|
||||||
AngelAuthCallback callback;
|
AngelAuthCallback callback;
|
||||||
|
AngelAuthTokenCallback tokenCallback;
|
||||||
bool canRespondWithJson;
|
bool canRespondWithJson;
|
||||||
String successRedirect;
|
String successRedirect;
|
||||||
String failureRedirect;
|
String failureRedirect;
|
||||||
|
|
||||||
AngelAuthOptions(
|
AngelAuthOptions(
|
||||||
{this.callback,
|
{this.callback,
|
||||||
|
this.tokenCallback,
|
||||||
this.canRespondWithJson: true,
|
this.canRespondWithJson: true,
|
||||||
this.successRedirect,
|
this.successRedirect,
|
||||||
String this.failureRedirect});
|
String this.failureRedirect});
|
||||||
|
|
|
@ -26,6 +26,8 @@ class AngelAuth extends AngelPlugin {
|
||||||
UserSerializer serializer;
|
UserSerializer serializer;
|
||||||
UserDeserializer deserializer;
|
UserDeserializer deserializer;
|
||||||
|
|
||||||
|
Hmac get hmac => _hs256;
|
||||||
|
|
||||||
String _randomString(
|
String _randomString(
|
||||||
{int length: 32,
|
{int length: 32,
|
||||||
String validChars:
|
String validChars:
|
||||||
|
@ -234,6 +236,13 @@ class AngelAuth extends AngelPlugin {
|
||||||
var token = new AuthToken(
|
var token = new AuthToken(
|
||||||
userId: userId, lifeSpan: _jwtLifeSpan, ipAddress: req.ip);
|
userId: userId, lifeSpan: _jwtLifeSpan, ipAddress: req.ip);
|
||||||
var jwt = token.serialize(_hs256);
|
var jwt = token.serialize(_hs256);
|
||||||
|
|
||||||
|
if (options?.tokenCallback != null) {
|
||||||
|
var r = await options.tokenCallback(
|
||||||
|
req, res, token, req.properties["user"] = result);
|
||||||
|
if (r != null) return r;
|
||||||
|
}
|
||||||
|
|
||||||
req
|
req
|
||||||
..inject(AuthToken, req.properties['token'] = token)
|
..inject(AuthToken, req.properties['token'] = token)
|
||||||
..inject(result.runtimeType, req.properties["user"] = result);
|
..inject(result.runtimeType, req.properties["user"] = result);
|
||||||
|
@ -265,6 +274,32 @@ class AngelAuth extends AngelPlugin {
|
||||||
throw new AngelHttpException.notAuthenticated();
|
throw new AngelHttpException.notAuthenticated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Log a user in on-demand.
|
||||||
|
Future login(AuthToken token, RequestContext req, ResponseContext res) async {
|
||||||
|
var user = await deserializer(token.userId);
|
||||||
|
|
||||||
|
req
|
||||||
|
..inject(AuthToken, req.properties['token'] = token)
|
||||||
|
..inject(user.runtimeType, req.properties["user"] = user);
|
||||||
|
|
||||||
|
if (allowCookie)
|
||||||
|
res.cookies.add(new Cookie('token', token.serialize(_hs256)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log a user in on-demand.
|
||||||
|
Future loginById(userId, RequestContext req, ResponseContext res) async {
|
||||||
|
var user = await deserializer(userId);
|
||||||
|
var token = new AuthToken(
|
||||||
|
userId: userId, lifeSpan: _jwtLifeSpan, ipAddress: req.ip);
|
||||||
|
|
||||||
|
req
|
||||||
|
..inject(AuthToken, req.properties['token'] = token)
|
||||||
|
..inject(user.runtimeType, req.properties["user"] = user);
|
||||||
|
|
||||||
|
if (allowCookie)
|
||||||
|
res.cookies.add(new Cookie('token', token.serialize(_hs256)));
|
||||||
|
}
|
||||||
|
|
||||||
logout([AngelAuthOptions options]) {
|
logout([AngelAuthOptions options]) {
|
||||||
return (RequestContext req, ResponseContext res) async {
|
return (RequestContext req, ResponseContext res) async {
|
||||||
for (AuthStrategy strategy in strategies) {
|
for (AuthStrategy strategy in strategies) {
|
||||||
|
|
|
@ -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+17
|
version: 1.0.0-dev+18
|
||||||
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:
|
||||||
|
|
Loading…
Reference in a new issue