Import all the source code.
This commit is contained in:
parent
b25abdf8a0
commit
a548fc58f6
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# Visual Studio Code
|
||||||
|
#.vscode/
|
||||||
|
.suo
|
||||||
|
|
||||||
|
# Compiled Object files
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# Compiled Dynamic libraries
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
|
||||||
|
# Compiled Static libraries
|
||||||
|
*.a
|
||||||
|
*.lib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
|
||||||
|
# DUB
|
||||||
|
.dub
|
||||||
|
dub.*.json
|
||||||
|
docs.json
|
||||||
|
__dummy.html
|
||||||
|
*-test-*
|
||||||
|
mixin_output.d
|
||||||
|
|
||||||
|
|
||||||
|
# Code coverage
|
||||||
|
*.lst
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
examples/simple/simple
|
||||||
|
|
||||||
|
# others
|
||||||
|
.DS_Store
|
||||||
|
*.zip
|
156
README.md
Normal file
156
README.md
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
<a href="https://code.dlang.org/packages/jwt" title="Go to jwt"><img src="https://img.shields.io/dub/v/jwt.svg" alt="Dub version"></a>
|
||||||
|
|
||||||
|
# JWT
|
||||||
|
|
||||||
|
A Simple D implementation of JSON Web Tokens. It's forked from https://github.com/zolamk/jwt.
|
||||||
|
|
||||||
|
# Supported Algorithms
|
||||||
|
- none
|
||||||
|
- HS256
|
||||||
|
- HS384
|
||||||
|
- HS512
|
||||||
|
|
||||||
|
#### This library uses [semantic versioning 2.0.0][3]
|
||||||
|
|
||||||
|
# What's New
|
||||||
|
- added support for `arrays` and `objects` in claims
|
||||||
|
- removed `verify` function that doesn't take algorithm type, see why [here][4]
|
||||||
|
- changed `verify` function to take an array of algorithms to support multiple algorithms
|
||||||
|
- renamed `InvalidSignature` to `InvalidSignatureException`
|
||||||
|
|
||||||
|
# How To Use
|
||||||
|
## Encoding
|
||||||
|
|
||||||
|
import jwt.jwt;
|
||||||
|
import jwt.algorithms;
|
||||||
|
import std.json;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
JSONValue user = ["id": JSONValue(60119), "uri": JSONValue("https://api.we.are/60119")];
|
||||||
|
|
||||||
|
Token token = new Token(JWTAlgorithm.HS512);
|
||||||
|
|
||||||
|
token.claims.exp = Clock.currTime.toUnixTime();
|
||||||
|
|
||||||
|
token.claims.set("user", user);
|
||||||
|
|
||||||
|
token.claims.set("data", [JSONValue("zm"), JSONValue(58718)]);
|
||||||
|
|
||||||
|
string encodedToken = token.encode("supersecret");
|
||||||
|
|
||||||
|
// work with the encoded token
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
## Verifying
|
||||||
|
|
||||||
|
import jwt.jwt;
|
||||||
|
import jwt.exceptions;
|
||||||
|
import jwt.algorithms;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
// get encoded token from header or ...
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
Token token = verify(encodedToken, "supersecret", [JWTAlgorithm.HS512, JWTAlgorithm.HS256]);
|
||||||
|
|
||||||
|
writeln(token.claims.getInt("id"));
|
||||||
|
|
||||||
|
JSONValue user = token.claims.getObject("user");
|
||||||
|
|
||||||
|
JSONValue[] a = token.claims.getArray("data");
|
||||||
|
|
||||||
|
long userID = user["id"].integer();
|
||||||
|
|
||||||
|
string uri = user["uri"].str();
|
||||||
|
|
||||||
|
writeln(userID);
|
||||||
|
|
||||||
|
writeln(uri);
|
||||||
|
|
||||||
|
writeln(a[0].str());
|
||||||
|
|
||||||
|
writeln(a[1].integer());
|
||||||
|
|
||||||
|
} catch (InvalidAlgorithmException e) {
|
||||||
|
|
||||||
|
writeln("token has an invalid algorithm");
|
||||||
|
|
||||||
|
} catch (InvalidSignatureException e) {
|
||||||
|
|
||||||
|
writeln("This token has been tampered with");
|
||||||
|
|
||||||
|
} catch (NotBeforeException e) {
|
||||||
|
|
||||||
|
writeln("Token is not valid yet");
|
||||||
|
|
||||||
|
} catch (ExpiredException e) {
|
||||||
|
|
||||||
|
writeln("Token has expired");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
## Encoding without signature
|
||||||
|
|
||||||
|
|
||||||
|
import jwt.jwt;
|
||||||
|
import jwt.algorithms;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
Token token = new Token(JWTAlgorithm.NONE);
|
||||||
|
|
||||||
|
token.claims.exp = Clock.currTime.toUnixTime();
|
||||||
|
|
||||||
|
token.claims.set("id", 60119);
|
||||||
|
|
||||||
|
string encodedToken = token.encode();
|
||||||
|
|
||||||
|
// work with the encoded token
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
## Verifying without signature
|
||||||
|
|
||||||
|
import jwt.jwt;
|
||||||
|
import jwt.exceptions;
|
||||||
|
import jwt.algorithms;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
// get encoded token from header or ...
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
Token token = verify(encodedToken);
|
||||||
|
|
||||||
|
writeln(token.claims.getInt("id"));
|
||||||
|
|
||||||
|
} catch (NotBeforeException e) {
|
||||||
|
|
||||||
|
writeln("Token is not valid yet");
|
||||||
|
|
||||||
|
} catch (ExpiredException e) {
|
||||||
|
|
||||||
|
writeln("Token has expired");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# Limitations
|
||||||
|
|
||||||
|
- ##### Since Phobos doesn't(hopefully yet) support RSA algorithms this library only provides HMAC signing.
|
||||||
|
|
||||||
|
# Note
|
||||||
|
this library uses code and ideas from [jwtd][1] and [jwt-go][2]
|
||||||
|
|
||||||
|
[1]: https://github.com/olehlong/jwtd
|
||||||
|
[2]: https://github.com/dgrijalva/jwt-go
|
||||||
|
[3]: http://semver.org
|
||||||
|
[4]: https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
|
9
dub.json
Normal file
9
dub.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"name": "hunt-jwt",
|
||||||
|
"authors": [
|
||||||
|
"Zelalem Mekonen"
|
||||||
|
],
|
||||||
|
"description": "A Simple D implementation of JSON Web Tokens",
|
||||||
|
"copyright": "Copyright © 2016, Zelalem Mekonen",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
11
examples/simple/dub.json
Normal file
11
examples/simple/dub.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"Zhang"
|
||||||
|
],
|
||||||
|
"description": "A minimal D application.",
|
||||||
|
"license": "proprietary",
|
||||||
|
"name": "simple",
|
||||||
|
"dependencies": {
|
||||||
|
"hunt-jwt" : {"path": "../../"}
|
||||||
|
}
|
||||||
|
}
|
17
examples/simple/source/app.d
Normal file
17
examples/simple/source/app.d
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import std.stdio;
|
||||||
|
|
||||||
|
import jwt;
|
||||||
|
import std.datetime;
|
||||||
|
import std.exception;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
string tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJuYW1lIjoiYWxpY2UiLCJlbWFpbCI6ImFsaWNlQGdtYWlsLmNvbSIsInBob25lX251bWJlciI6IjE4ODAwMDAwMDAxIiwibmJmIjoxNTA5NDY0MzQwLCJleHAiOjE1MTAwNjkxNDAsImlhdCI6MTUwOTQ2NDM0MH0.nV7duR2gWHA3TB9xPhP1WWhDpXRn1GA_k8_zBBirT6g";
|
||||||
|
|
||||||
|
Token tk = decode(tokenString);
|
||||||
|
|
||||||
|
writeln(tk.header.json());
|
||||||
|
writeln(tk.claims.json());
|
||||||
|
|
||||||
|
tk = verify(tokenString, "secret", []);
|
||||||
|
}
|
72
source/jwt/algorithms.d
Normal file
72
source/jwt/algorithms.d
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
module jwt.algorithms;
|
||||||
|
|
||||||
|
import std.digest.hmac;
|
||||||
|
import std.digest.sha;
|
||||||
|
import std.string : representation;
|
||||||
|
import std.base64;
|
||||||
|
import std.stdio;
|
||||||
|
|
||||||
|
import jwt.exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* string literal used to represent signing algorithm type
|
||||||
|
*/
|
||||||
|
enum JWTAlgorithm : string {
|
||||||
|
NONE = "none", // string representation of the none algorithm
|
||||||
|
HS256 = "HS256", // string representation of hmac algorithm with sha256
|
||||||
|
HS384 = "HS384", // string representation of hmac algorithm with sha348
|
||||||
|
HS512 = "HS512" //string representation of hmac algorithm with sha512
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* an alias for base64 encoding that is url safe and removes the '=' padding character
|
||||||
|
*/
|
||||||
|
alias URLSafeBase64 = Base64Impl!('-', '_', Base64.NoPadding);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* signs the given data with the secret using the given algorithm
|
||||||
|
* Params:
|
||||||
|
* secret = the secret used to sign the data
|
||||||
|
* data = the data that is to be signed
|
||||||
|
* alg = the algorithm to be used to sign the data
|
||||||
|
* Returns: signature of the data
|
||||||
|
*/
|
||||||
|
string sign(string secret, string data, JWTAlgorithm alg) {
|
||||||
|
switch(alg) {
|
||||||
|
case JWTAlgorithm.HS256:
|
||||||
|
auto signature = HMAC!SHA256(secret.representation);
|
||||||
|
signature.put(data.representation);
|
||||||
|
return URLSafeBase64.encode(signature.finish());
|
||||||
|
|
||||||
|
case JWTAlgorithm.HS384:
|
||||||
|
auto signature = HMAC!SHA384(secret.representation);
|
||||||
|
signature.put(data.representation);
|
||||||
|
return URLSafeBase64.encode(signature.finish());
|
||||||
|
|
||||||
|
case JWTAlgorithm.HS512:
|
||||||
|
auto signature = HMAC!SHA512(secret.representation);
|
||||||
|
signature.put(data.representation);
|
||||||
|
return URLSafeBase64.encode(signature.finish());
|
||||||
|
|
||||||
|
case JWTAlgorithm.NONE:
|
||||||
|
return "";
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new UnsupportedAlgorithmException(alg ~ " algorithm is not supported!");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
///
|
||||||
|
unittest {
|
||||||
|
string secret = "supersecret";
|
||||||
|
|
||||||
|
string data = "an unstoppable force crashes into an unmovable body";
|
||||||
|
|
||||||
|
string signature = sign(secret, data, JWTAlgorithm.HS512);
|
||||||
|
|
||||||
|
assert(signature.length > 0);
|
||||||
|
|
||||||
|
signature = sign(secret, data, JWTAlgorithm.NONE);
|
||||||
|
|
||||||
|
assert(signature.length == 0);
|
||||||
|
}
|
84
source/jwt/exceptions.d
Normal file
84
source/jwt/exceptions.d
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
module jwt.exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* thrown when there are issues with token verification
|
||||||
|
*/
|
||||||
|
class VerifyException : Exception {
|
||||||
|
this(string s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* thrown when attempting to encode or decode a token with an unsupported algorithm
|
||||||
|
*/
|
||||||
|
class UnsupportedAlgorithmException : Exception {
|
||||||
|
this(string s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* thrown when there are issues with the token
|
||||||
|
*/
|
||||||
|
class InvalidTokenException : VerifyException {
|
||||||
|
this(string s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* thrown when the tokens signature doesn't match the data signature
|
||||||
|
*/
|
||||||
|
class InvalidSignatureException : VerifyException {
|
||||||
|
this(string s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* thrown when the algorithm used to sign the token is invalid
|
||||||
|
*/
|
||||||
|
class InvalidAlgorithmException : VerifyException {
|
||||||
|
this(string s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* thrown when the tokens is expired
|
||||||
|
*/
|
||||||
|
class ExpiredException : VerifyException {
|
||||||
|
this(string s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* thrown when the token is not valid yet
|
||||||
|
* or in other words when the nbf claim time is before the current time
|
||||||
|
*/
|
||||||
|
class NotBeforeException : VerifyException {
|
||||||
|
this(string s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* thrown when the token has an incorrect format
|
||||||
|
*/
|
||||||
|
class MalformedToken : InvalidTokenException {
|
||||||
|
this(string s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* thrown when the tokens will expire before it becomes valid
|
||||||
|
* usually when the nbf claim is greater than the exp claim
|
||||||
|
*/
|
||||||
|
class ExpiresBeforeValidException : Exception {
|
||||||
|
this(string s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
567
source/jwt/jwt.d
Normal file
567
source/jwt/jwt.d
Normal file
|
@ -0,0 +1,567 @@
|
||||||
|
module jwt.jwt;
|
||||||
|
|
||||||
|
import std.json;
|
||||||
|
import std.base64;
|
||||||
|
import std.stdio;
|
||||||
|
import std.conv;
|
||||||
|
import std.string;
|
||||||
|
import std.datetime;
|
||||||
|
import std.exception;
|
||||||
|
import std.array : split;
|
||||||
|
import std.algorithm : count;
|
||||||
|
|
||||||
|
import jwt.algorithms;
|
||||||
|
import jwt.exceptions;
|
||||||
|
|
||||||
|
private class Component {
|
||||||
|
abstract @property string json();
|
||||||
|
|
||||||
|
@property string base64() {
|
||||||
|
ubyte[] data = cast(ubyte[])this.json;
|
||||||
|
|
||||||
|
return URLSafeBase64.encode(data);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Header : Component {
|
||||||
|
|
||||||
|
public:
|
||||||
|
JWTAlgorithm alg;
|
||||||
|
string typ;
|
||||||
|
|
||||||
|
this(in JWTAlgorithm alg, in string typ) {
|
||||||
|
this.alg = alg;
|
||||||
|
this.typ = typ;
|
||||||
|
}
|
||||||
|
|
||||||
|
this(in JSONValue headers) {
|
||||||
|
try {
|
||||||
|
this.alg = to!(JWTAlgorithm)(toUpper(headers["alg"].str()));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new UnsupportedAlgorithmException(alg ~ " algorithm is not supported!");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this.typ = headers["typ"].str();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@property override string json() {
|
||||||
|
JSONValue headers = ["alg": cast(string)this.alg, "typ": this.typ];
|
||||||
|
|
||||||
|
return headers.toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* represents the claims component of a JWT
|
||||||
|
*/
|
||||||
|
private class Claims : Component {
|
||||||
|
private:
|
||||||
|
JSONValue data;
|
||||||
|
|
||||||
|
this(in JSONValue claims) {
|
||||||
|
this.data = claims;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
this() {
|
||||||
|
this.data = JSONValue(["iat": JSONValue(Clock.currTime.toUnixTime())]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(T)(string name, T data) {
|
||||||
|
this.data.object[name] = JSONValue(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* name = the name of the claim
|
||||||
|
* Returns: returns a string representation of the claim if it exists and is a string or an empty string if doesn't exist or is not a string
|
||||||
|
*/
|
||||||
|
string get(string name) {
|
||||||
|
try {
|
||||||
|
return this.data[name].str();
|
||||||
|
|
||||||
|
} catch (JSONException e) {
|
||||||
|
return string.init;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* name = the name of the claim
|
||||||
|
* Returns: an array of JSONValue
|
||||||
|
*/
|
||||||
|
JSONValue[] getArray(string name) {
|
||||||
|
try {
|
||||||
|
return this.data[name].array();
|
||||||
|
|
||||||
|
} catch (JSONException e) {
|
||||||
|
return JSONValue.Store.array.init;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* name = the name of the claim
|
||||||
|
* Returns: a JSONValue
|
||||||
|
*/
|
||||||
|
JSONValue[string] getObject(string name) {
|
||||||
|
try {
|
||||||
|
return this.data[name].object();
|
||||||
|
|
||||||
|
} catch (JSONException e) {
|
||||||
|
return JSONValue.Store.object.init;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* name = the name of the claim
|
||||||
|
* Returns: returns a long representation of the claim if it exists and is an
|
||||||
|
* integer or the initial value for long if doesn't exist or is not an integer
|
||||||
|
*/
|
||||||
|
long getInt(string name) {
|
||||||
|
try {
|
||||||
|
return this.data[name].integer();
|
||||||
|
|
||||||
|
} catch (JSONException e) {
|
||||||
|
return long.init;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* name = the name of the claim
|
||||||
|
* Returns: returns a double representation of the claim if it exists and is a
|
||||||
|
* double or the initial value for double if doesn't exist or is not a double
|
||||||
|
*/
|
||||||
|
double getDouble(string name) {
|
||||||
|
try {
|
||||||
|
return this.data[name].floating();
|
||||||
|
|
||||||
|
} catch (JSONException e) {
|
||||||
|
return double.init;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* name = the name of the claim
|
||||||
|
* Returns: returns a boolean representation of the claim if it exists and is a
|
||||||
|
* boolean or the initial value for bool if doesn't exist or is not a boolean
|
||||||
|
*/
|
||||||
|
bool getBool(string name) {
|
||||||
|
try {
|
||||||
|
return this.data[name].type == JSONType.true_;
|
||||||
|
|
||||||
|
} catch (JSONException e) {
|
||||||
|
return bool.init;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* name = the name of the claim
|
||||||
|
* Returns: returns a boolean value if the claim exists and is null or
|
||||||
|
* the initial value for bool it it doesn't exist or is not null
|
||||||
|
*/
|
||||||
|
bool isNull(string name) {
|
||||||
|
try {
|
||||||
|
return this.data[name].isNull();
|
||||||
|
|
||||||
|
} catch (JSONException) {
|
||||||
|
return bool.init;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@property void iss(string s) {
|
||||||
|
this.data.object["iss"] = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@property string iss() {
|
||||||
|
try {
|
||||||
|
return this.data["iss"].str();
|
||||||
|
|
||||||
|
} catch (JSONException e) {
|
||||||
|
return "";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@property void sub(string s) {
|
||||||
|
this.data.object["sub"] = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property string sub() {
|
||||||
|
try {
|
||||||
|
return this.data["sub"].str();
|
||||||
|
|
||||||
|
} catch (JSONException e) {
|
||||||
|
return "";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@property void aud(string s) {
|
||||||
|
this.data.object["aud"] = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property string aud() {
|
||||||
|
try {
|
||||||
|
return this.data["aud"].str();
|
||||||
|
|
||||||
|
} catch (JSONException e) {
|
||||||
|
return "";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@property void exp(long n) {
|
||||||
|
this.data.object["exp"] = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property long exp() {
|
||||||
|
try {
|
||||||
|
return this.data["exp"].integer;
|
||||||
|
|
||||||
|
} catch (JSONException) {
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@property void nbf(long n) {
|
||||||
|
this.data.object["nbf"] = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property long nbf() {
|
||||||
|
try {
|
||||||
|
return this.data["nbf"].integer;
|
||||||
|
|
||||||
|
} catch (JSONException) {
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@property void iat(long n) {
|
||||||
|
this.data.object["iat"] = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property long iat() {
|
||||||
|
try {
|
||||||
|
return this.data["iat"].integer;
|
||||||
|
|
||||||
|
} catch (JSONException) {
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@property void jit(string s) {
|
||||||
|
this.data.object["jit"] = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property string jit() {
|
||||||
|
try {
|
||||||
|
return this.data["jit"].str();
|
||||||
|
|
||||||
|
} catch(JSONException e) {
|
||||||
|
return "";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gives json encoded claims
|
||||||
|
* Returns: json encoded claims
|
||||||
|
*/
|
||||||
|
@property override string json() {
|
||||||
|
return this.data.toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* represents a token
|
||||||
|
*/
|
||||||
|
class Token {
|
||||||
|
|
||||||
|
private:
|
||||||
|
Claims _claims;
|
||||||
|
Header _header;
|
||||||
|
|
||||||
|
this(Claims claims, Header header) {
|
||||||
|
this._claims = claims;
|
||||||
|
this._header = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property string data() {
|
||||||
|
return this.header.base64 ~ "." ~ this.claims.base64;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
this(in JWTAlgorithm alg, in string typ = "JWT") {
|
||||||
|
this._claims = new Claims();
|
||||||
|
|
||||||
|
this._header = new Header(alg, typ);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@property Claims claims() {
|
||||||
|
return this._claims;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property Header header() {
|
||||||
|
return this._header;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* used to get the signature of the token
|
||||||
|
* Parmas:
|
||||||
|
* secret = the secret key used to sign the token
|
||||||
|
* Returns: the signature of the token
|
||||||
|
*/
|
||||||
|
string signature(string secret) {
|
||||||
|
return sign(secret, this.data, this.header.alg);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* encodes the token
|
||||||
|
* Params:
|
||||||
|
* secret = the secret key used to sign the token
|
||||||
|
*Returns: base64 representation of the token including signature
|
||||||
|
*/
|
||||||
|
string encode(string secret) {
|
||||||
|
if ((this.claims.exp != ulong.init && this.claims.iat != ulong.init) && this.claims.exp < this.claims.iat) {
|
||||||
|
throw new ExpiredException("Token has already expired");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((this.claims.exp != ulong.init && this.claims.nbf != ulong.init) && this.claims.exp < this.claims.nbf) {
|
||||||
|
throw new ExpiresBeforeValidException("Token will expire before it becomes valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.data ~ "." ~ this.signature(secret);
|
||||||
|
|
||||||
|
}
|
||||||
|
///
|
||||||
|
unittest {
|
||||||
|
Token token = new Token(JWTAlgorithm.HS512);
|
||||||
|
|
||||||
|
long now = Clock.currTime.toUnixTime();
|
||||||
|
|
||||||
|
string secret = "super_secret";
|
||||||
|
|
||||||
|
token.claims.exp = now - 3600;
|
||||||
|
|
||||||
|
assertThrown!ExpiredException(token.encode(secret));
|
||||||
|
|
||||||
|
token.claims.exp = now + 3600;
|
||||||
|
|
||||||
|
token.claims.nbf = now + 7200;
|
||||||
|
|
||||||
|
assertThrown!ExpiresBeforeValidException(token.encode(secret));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* overload of the encode(string secret) function to simplify encoding of token without algorithm none
|
||||||
|
* Returns: base64 representation of the token
|
||||||
|
*/
|
||||||
|
string encode() {
|
||||||
|
assert(this.header.alg == JWTAlgorithm.NONE);
|
||||||
|
return this.encode("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Token decode(string encodedToken) {
|
||||||
|
string[] tokenParts = split(encodedToken, ".");
|
||||||
|
|
||||||
|
if(tokenParts.length != 3) {
|
||||||
|
throw new MalformedToken("Malformed Token");
|
||||||
|
}
|
||||||
|
|
||||||
|
string component = tokenParts[0];
|
||||||
|
|
||||||
|
string jsonComponent = cast(string)URLSafeBase64.decode(component);
|
||||||
|
|
||||||
|
JSONValue parsedComponent = parseJSON(jsonComponent);
|
||||||
|
|
||||||
|
Header header = new Header(parsedComponent);
|
||||||
|
|
||||||
|
component = tokenParts[1];
|
||||||
|
|
||||||
|
jsonComponent = cast(string)URLSafeBase64.decode(component);
|
||||||
|
|
||||||
|
parsedComponent = parseJSON(jsonComponent);
|
||||||
|
|
||||||
|
Claims claims = new Claims(parsedComponent);
|
||||||
|
|
||||||
|
return new Token(claims, header);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* verifies the tokens is valid, using the algorithm given instead of the alg field in the claims
|
||||||
|
* Params:
|
||||||
|
* encodedToken = the encoded token
|
||||||
|
* secret = the secret key used to sign the token
|
||||||
|
* alg = the algorithm to be used to verify the token
|
||||||
|
* Returns: a decoded Token
|
||||||
|
*/
|
||||||
|
Token verify(string encodedToken, string secret, JWTAlgorithm[] algs) {
|
||||||
|
Token token = decode(encodedToken);
|
||||||
|
|
||||||
|
long now = Clock.currTime.toUnixTime();
|
||||||
|
|
||||||
|
bool algorithmAllowed = false;
|
||||||
|
|
||||||
|
foreach(index, alg; algs) {
|
||||||
|
if(token.header.alg == alg) {
|
||||||
|
algorithmAllowed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!algorithmAllowed) {
|
||||||
|
throw new InvalidAlgorithmException("Algorithm " ~ token.header.alg ~ " is not in the allowed algorithms field");
|
||||||
|
}
|
||||||
|
|
||||||
|
string signature = split(encodedToken, ".")[2];
|
||||||
|
|
||||||
|
if (signature != token.signature(secret)) {
|
||||||
|
throw new InvalidSignatureException("Signature Match Failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.header.alg == JWTAlgorithm.NONE) {
|
||||||
|
throw new VerifyException("Algorithm set to none while secret is provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.claims.exp != ulong.init && token.claims.exp < now) {
|
||||||
|
throw new ExpiredException("Token has expired");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.claims.nbf != ulong.init && token.claims.nbf > now) {
|
||||||
|
throw new NotBeforeException("Token is not valid yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest {
|
||||||
|
string secret = "super_secret";
|
||||||
|
|
||||||
|
long now = Clock.currTime.toUnixTime();
|
||||||
|
|
||||||
|
Token token = new Token(JWTAlgorithm.HS512);
|
||||||
|
|
||||||
|
token.claims.nbf = now + (60 * 60);
|
||||||
|
|
||||||
|
string encodedToken = token.encode(secret);
|
||||||
|
|
||||||
|
assertThrown!NotBeforeException(verify(encodedToken, secret, [JWTAlgorithm.HS512]));
|
||||||
|
|
||||||
|
token = new Token(JWTAlgorithm.HS512);
|
||||||
|
|
||||||
|
token.claims.iat = now - 3600;
|
||||||
|
|
||||||
|
token.claims.exp = now - 60;
|
||||||
|
|
||||||
|
encodedToken = token.encode(secret);
|
||||||
|
|
||||||
|
assertThrown!ExpiredException(verify(encodedToken, secret, [JWTAlgorithm.HS512]));
|
||||||
|
|
||||||
|
token = new Token(JWTAlgorithm.NONE);
|
||||||
|
|
||||||
|
encodedToken = token.encode(secret);
|
||||||
|
|
||||||
|
assertThrown!VerifyException(verify(encodedToken, secret, [JWTAlgorithm.HS512]));
|
||||||
|
|
||||||
|
token = new Token(JWTAlgorithm.HS512);
|
||||||
|
|
||||||
|
encodedToken = token.encode(secret) ~ "we_are";
|
||||||
|
|
||||||
|
assertThrown!InvalidSignatureException(verify(encodedToken, secret, [JWTAlgorithm.HS512]));
|
||||||
|
|
||||||
|
token = new Token(JWTAlgorithm.HS512);
|
||||||
|
|
||||||
|
encodedToken = token.encode(secret);
|
||||||
|
|
||||||
|
assertThrown!InvalidAlgorithmException(verify(encodedToken, secret, [JWTAlgorithm.HS256, JWTAlgorithm.HS384]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* verifies the tokens is valid, used in case the token was signed with "none" as algorithm
|
||||||
|
* Params:
|
||||||
|
* encodedToken = the encoded token
|
||||||
|
* Returns: a decoded Token
|
||||||
|
*/
|
||||||
|
Token verify(string encodedToken) {
|
||||||
|
Token token = decode(encodedToken);
|
||||||
|
|
||||||
|
long now = Clock.currTime.toUnixTime();
|
||||||
|
|
||||||
|
if (token.claims.exp != ulong.init && token.claims.exp < now) {
|
||||||
|
throw new ExpiredException("Token has expired");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.claims.nbf != ulong.init && token.claims.nbf > now) {
|
||||||
|
throw new NotBeforeException("Token is not valid yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
///
|
||||||
|
unittest {
|
||||||
|
long now = Clock.currTime.toUnixTime();
|
||||||
|
|
||||||
|
Token token = new Token(JWTAlgorithm.NONE);
|
||||||
|
|
||||||
|
token.claims.nbf = now + (60 * 60);
|
||||||
|
|
||||||
|
string encodedToken = token.encode();
|
||||||
|
|
||||||
|
assertThrown!NotBeforeException(verify(encodedToken));
|
||||||
|
|
||||||
|
token = new Token(JWTAlgorithm.NONE);
|
||||||
|
|
||||||
|
token.claims.iat = now - 3600;
|
||||||
|
|
||||||
|
token.claims.exp = now - 60;
|
||||||
|
|
||||||
|
encodedToken = token.encode();
|
||||||
|
|
||||||
|
assertThrown!ExpiredException(token = verify(encodedToken));
|
||||||
|
}
|
5
source/jwt/package.d
Normal file
5
source/jwt/package.d
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module jwt;
|
||||||
|
|
||||||
|
public import jwt.algorithms;
|
||||||
|
public import jwt.exceptions;
|
||||||
|
public import jwt.jwt;
|
Loading…
Reference in a new issue