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
|
||||
|
||||
[![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)
|
||||
|
||||
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: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 {
|
||||
final SplayTreeMap<String, String> _header =
|
||||
new SplayTreeMap.from({"alg": "HS256", "typ": "JWT"});
|
||||
|
@ -35,14 +56,24 @@ class AuthToken {
|
|||
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) {
|
||||
var split = jwt.split(".");
|
||||
|
||||
if (split.length != 3)
|
||||
throw new AngelHttpException.notAuthenticated(message: "Invalid JWT.");
|
||||
|
||||
// var headerString = new String.fromCharCodes(BASE64URL.decode(split[0]));
|
||||
var payloadString = new String.fromCharCodes(BASE64URL.decode(split[1]));
|
||||
// var headerString = decodeBase64(split[0]);
|
||||
var payloadString = decodeBase64(split[1]);
|
||||
var data = split[0] + "." + split[1];
|
||||
var signature = BASE64URL.encode(hmac.convert(data.codeUnits).bytes);
|
||||
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'auth_token.dart';
|
||||
|
||||
typedef AngelAuthCallback(
|
||||
RequestContext req, ResponseContext res, String token);
|
||||
|
||||
typedef AngelAuthTokenCallback(
|
||||
RequestContext req, ResponseContext res, AuthToken token, user);
|
||||
|
||||
class AngelAuthOptions {
|
||||
AngelAuthCallback callback;
|
||||
AngelAuthTokenCallback tokenCallback;
|
||||
bool canRespondWithJson;
|
||||
String successRedirect;
|
||||
String failureRedirect;
|
||||
|
||||
AngelAuthOptions(
|
||||
{this.callback,
|
||||
this.tokenCallback,
|
||||
this.canRespondWithJson: true,
|
||||
this.successRedirect,
|
||||
String this.failureRedirect});
|
||||
|
|
|
@ -26,6 +26,8 @@ class AngelAuth extends AngelPlugin {
|
|||
UserSerializer serializer;
|
||||
UserDeserializer deserializer;
|
||||
|
||||
Hmac get hmac => _hs256;
|
||||
|
||||
String _randomString(
|
||||
{int length: 32,
|
||||
String validChars:
|
||||
|
@ -234,6 +236,13 @@ class AngelAuth extends AngelPlugin {
|
|||
var token = new AuthToken(
|
||||
userId: userId, lifeSpan: _jwtLifeSpan, ipAddress: req.ip);
|
||||
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
|
||||
..inject(AuthToken, req.properties['token'] = token)
|
||||
..inject(result.runtimeType, req.properties["user"] = result);
|
||||
|
@ -265,6 +274,32 @@ class AngelAuth extends AngelPlugin {
|
|||
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]) {
|
||||
return (RequestContext req, ResponseContext res) async {
|
||||
for (AuthStrategy strategy in strategies) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
name: angel_auth
|
||||
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>
|
||||
homepage: https://github.com/angel-dart/angel_auth
|
||||
environment:
|
||||
|
|
Loading…
Reference in a new issue