├── .gitignore ├── .haxerc ├── .travis.yml ├── .vscode ├── settings.json └── tasks.json ├── LICENSE.md ├── README.md ├── completion.hxml ├── haxe_libraries ├── ansi.hxml ├── asys.hxml ├── crypto.hxml ├── hxcpp.hxml ├── hxcs.hxml ├── hxjava.hxml ├── hxnodejs.hxml ├── jsonwebtoken.hxml ├── tink_chunk.hxml ├── tink_cli.hxml ├── tink_core.hxml ├── tink_io.hxml ├── tink_macro.hxml ├── tink_priority.hxml ├── tink_streams.hxml ├── tink_stringly.hxml ├── tink_syntaxhub.hxml ├── tink_testrunner.hxml ├── tink_unittest.hxml └── travix.hxml ├── haxelib.json ├── src └── jsonwebtoken │ ├── Algorithm.hx │ ├── Claims.hx │ ├── Codec.hx │ ├── Crypto.hx │ ├── Header.hx │ ├── Sequence.hx │ ├── Signer.hx │ ├── Verifier.hx │ ├── crypto │ ├── CsCrypto.hx │ ├── DefaultCrypto.hx │ ├── JavaCrypto.hx │ ├── NodeCrypto.hx │ ├── OpensslCrypto.hx │ ├── PhpCrypto.hx │ ├── PythonCrypto.hx │ └── StdCrypto.hx │ ├── signer │ └── BasicSigner.hx │ └── verifier │ └── BasicVerifier.hx ├── submit.sh ├── tests.hxml └── tests ├── RunTests.hx ├── SignerTest.hx └── VerifierTest.hx /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | -------------------------------------------------------------------------------- /.haxerc: -------------------------------------------------------------------------------- 1 | { 2 | "version": "4.0.0-preview.5", 3 | "resolveLibs": "scoped" 4 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | 4 | language: node_js 5 | node_js: 8 6 | 7 | cache: 8 | directories: 9 | - $HOME/haxe 10 | 11 | os: 12 | - linux 13 | # - osx 14 | 15 | env: 16 | - HAXE_VERSION=3.4.7 17 | - HAXE_VERSION=latest 18 | 19 | before_install: 20 | - args=() 21 | - if [[ "$(haxe -version)" =~ ^4.* ]]; then args+=(-lib); args+=(crypto); fi 22 | 23 | install: 24 | - npm i -g lix 25 | - lix install haxe $HAXE_VERSION 26 | - lix download 27 | 28 | script: 29 | - lix run travix php "${args[@]}" 30 | - lix run travix interp "${args[@]}" 31 | - lix run travix neko "${args[@]}" 32 | - lix run travix python "${args[@]}" 33 | - lix run travix node "${args[@]}" 34 | - lix run travix js "${args[@]}" 35 | - lix run travix java "${args[@]}" 36 | - lix run travix cs "${args[@]}" 37 | - lix run travix cpp "${args[@]}" 38 | - lix run travix php -D openssl 39 | - lix run travix interp -D openssl 40 | - lix run travix neko -D openssl 41 | - lix run travix python -D openssl 42 | - lix run travix node -lib asys -D openssl 43 | - lix run travix java -D openssl 44 | - lix run travix cs -D openssl 45 | - lix run travix cpp -D openssl -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // These are configurations used for haxe completion. 3 | // 4 | // Each configuration is an array of arguments that will be passed to the Haxe completion server, 5 | // they should only contain arguments and/or hxml files that are needed for completion, 6 | // such as -cp, -lib, target output settings and defines. 7 | "haxe.displayConfigurations": [ 8 | ["completion.hxml"], // if a hxml file is safe to use, we can just pass it as argument 9 | // you can add more than one configuration and switch between them 10 | ["tests.hxml"] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "command": "travix", 4 | "args": ["node"], 5 | "problemMatcher": { 6 | "owner": "haxe", 7 | "pattern": { 8 | "regexp": "^(.+):(\\d+): (?:lines \\d+-(\\d+)|character(?:s (\\d+)-| )(\\d+)) : (?:(Warning) : )?(.*)$", 9 | "file": 1, 10 | "line": 2, 11 | "endLine": 3, 12 | "column": 4, 13 | "endColumn": 5, 14 | "severity": 6, 15 | "message": 7 16 | } 17 | }, 18 | "group": { 19 | "kind": "build", 20 | "isDefault": true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Kevin Leung 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsonwebtoken [![Build Status](https://travis-ci.org/kevinresol/jsonwebtoken.svg?branch=master)](https://travis-ci.org/kevinresol/jsonwebtoken) 2 | 3 | Use JsonWebToken in Haxe 4 | 5 | ##### Supported Algorithms 6 | 7 | 8 | | Target | HS256 | HS384 | HS512 | RS256 | RS384 | RS512 | Remarks| 9 | | --- | :---: | :---: | :---: | :---: | :---: | :---: | --- | 10 | | all sys targets | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Using openssl cli | 11 | | Node | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Using Node std lib | 12 | | PHP | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Using PHP std lib | 13 | | Java | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | Using Java std lib | 14 | | C# | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | Using C# std lib | 15 | | Python | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | Using Python std lib | 16 | | Interp | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | Using Haxe std lib | 17 | | Neko | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | Using Haxe std lib | 18 | | JS | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | Using Haxe std lib | 19 | | C++ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | Using Haxe std lib | 20 | 21 | 22 | ##### Supported Verifications 23 | 24 | - Issuer 25 | - Audience 26 | - Expiry 27 | 28 | # Install 29 | 30 | ``` 31 | haxelib install jsonwebtoken 32 | ``` 33 | 34 | ## Usage 35 | 36 | ### Signing 37 | 38 | ```haxe 39 | var crypto = new NodeCrypto(); // pick a crypto from the jsonwebtoken.crypto package 40 | var signer = new BasicSigner(HS256('secret'), crypto); 41 | var payload:Claims = {iss: 'issuer'} 42 | signer.sign(payload).handle(function(o) switch o { 43 | case Success(token): trace(token); 44 | case Failure(e): trace('Failed to sign: $e'); 45 | }); 46 | ``` 47 | 48 | 49 | ### Verifying 50 | 51 | ```haxe 52 | var crypto = new NodeCrypto(); // pick a crypto from the jsonwebtoken.crypto package 53 | var verifier = new BasicVerifier(HS256('secret'), crypto, {iss: 'issuer'}); 54 | var token = ...; 55 | verifier.verify(token).handle(function(o) switch o { 56 | case Success(_): trace('verified'); 57 | case Failure(e): trace('Invalid token: $e'); 58 | }); 59 | ``` 60 | -------------------------------------------------------------------------------- /completion.hxml: -------------------------------------------------------------------------------- 1 | tests.hxml 2 | # -neko bin/tests.n 3 | -cs bin/cs.n 4 | 5 | -lib travix 6 | -lib jsonwebtoken 7 | 8 | -lib tink_io 9 | 10 | -D openssl -------------------------------------------------------------------------------- /haxe_libraries/ansi.hxml: -------------------------------------------------------------------------------- 1 | # @install: lix --silent download haxelib:ansi#1.0.0 into ansi/1.0.0/haxelib 2 | -D ansi=1.0.0 3 | -cp ${HAXESHIM_LIBCACHE}/ansi/1.0.0/haxelib/src 4 | -------------------------------------------------------------------------------- /haxe_libraries/asys.hxml: -------------------------------------------------------------------------------- 1 | -D asys=0.3.0 2 | # @install: lix --silent download "gh://github.com/benmerckx/asys#faacf33864cd8d9aca3cd10c44621c108d6f3495" into asys/0.3.0/github/faacf33864cd8d9aca3cd10c44621c108d6f3495 3 | -lib tink_core 4 | -lib tink_macro 5 | -lib tink_io 6 | -lib tink_streams 7 | -cp ${HAXE_LIBCACHE}/asys/0.3.0/github/faacf33864cd8d9aca3cd10c44621c108d6f3495/ 8 | -------------------------------------------------------------------------------- /haxe_libraries/crypto.hxml: -------------------------------------------------------------------------------- 1 | -D crypto=0.0.0 2 | # @install: lix --silent download "gh://github.com/HaxeFoundation/crypto#b35b64435dd4cff9a32975200f0d3ddb05dbf3d3" into crypto/0.0.0/github/b35b64435dd4cff9a32975200f0d3ddb05dbf3d3 3 | -cp ${HAXE_LIBCACHE}/crypto/0.0.0/github/b35b64435dd4cff9a32975200f0d3ddb05dbf3d3/src 4 | -------------------------------------------------------------------------------- /haxe_libraries/hxcpp.hxml: -------------------------------------------------------------------------------- 1 | -D hxcpp=4.0.8 2 | # @install: lix --silent download "haxelib:/hxcpp#4.0.8" into hxcpp/4.0.8/haxelib 3 | # @run: haxelib run-dir hxcpp ${HAXE_LIBCACHE}/hxcpp/4.0.8/haxelib 4 | -cp ${HAXE_LIBCACHE}/hxcpp/4.0.8/haxelib/ 5 | -------------------------------------------------------------------------------- /haxe_libraries/hxcs.hxml: -------------------------------------------------------------------------------- 1 | -D hxcs=3.4.0 2 | # @install: lix --silent download "haxelib:/hxcs#3.4.0" into hxcs/3.4.0/haxelib 3 | # @run: haxelib run-dir hxcs ${HAXE_LIBCACHE}/hxcs/3.4.0/haxelib 4 | -cp ${HAXE_LIBCACHE}/hxcs/3.4.0/haxelib/ 5 | -------------------------------------------------------------------------------- /haxe_libraries/hxjava.hxml: -------------------------------------------------------------------------------- 1 | -D hxjava=3.2.0 2 | # @install: lix --silent download "haxelib:/hxjava#3.2.0" into hxjava/3.2.0/haxelib 3 | # @run: haxelib run-dir hxjava ${HAXE_LIBCACHE}/hxjava/3.2.0/haxelib 4 | -cp ${HAXE_LIBCACHE}/hxjava/3.2.0/haxelib/ 5 | -java-lib lib/hxjava-std.jar 6 | -------------------------------------------------------------------------------- /haxe_libraries/hxnodejs.hxml: -------------------------------------------------------------------------------- 1 | -D hxnodejs=6.9.1 2 | # @install: lix --silent download "gh://github.com/haxefoundation/hxnodejs#974f456cfa90c0ca1cc92681eca8665e40b4ab1f" into hxnodejs/6.9.1/github/974f456cfa90c0ca1cc92681eca8665e40b4ab1f 3 | -cp ${HAXE_LIBCACHE}/hxnodejs/6.9.1/github/974f456cfa90c0ca1cc92681eca8665e40b4ab1f/src 4 | --macro allowPackage('sys') 5 | # should behave like other target defines and not be defined in macro context 6 | --macro define('nodejs') 7 | -------------------------------------------------------------------------------- /haxe_libraries/jsonwebtoken.hxml: -------------------------------------------------------------------------------- 1 | -D jsonwebtoken 2 | -cp src 3 | -lib tink_core 4 | -lib crypto -------------------------------------------------------------------------------- /haxe_libraries/tink_chunk.hxml: -------------------------------------------------------------------------------- 1 | -D tink_chunk=0.2.0 2 | # @install: lix --silent download "gh://github.com/haxetink/tink_chunk#018ddccbac644a022864d5b80ae7a084e868cb5c" into tink_chunk/0.2.0/github/018ddccbac644a022864d5b80ae7a084e868cb5c 3 | -cp ${HAXE_LIBCACHE}/tink_chunk/0.2.0/github/018ddccbac644a022864d5b80ae7a084e868cb5c/src 4 | -------------------------------------------------------------------------------- /haxe_libraries/tink_cli.hxml: -------------------------------------------------------------------------------- 1 | -D tink_cli=0.4.1 2 | # @install: lix --silent download "gh://github.com/haxetink/tink_cli#e40960a7ddd72bdb3ca723aefb20591c514fae33" into tink_cli/0.4.1/github/e40960a7ddd72bdb3ca723aefb20591c514fae33 3 | -lib tink_io 4 | -lib tink_stringly 5 | -lib tink_macro 6 | -cp ${HAXE_LIBCACHE}/tink_cli/0.4.1/github/e40960a7ddd72bdb3ca723aefb20591c514fae33/src 7 | # Make sure docs are generated 8 | -D use-rtti-doc -------------------------------------------------------------------------------- /haxe_libraries/tink_core.hxml: -------------------------------------------------------------------------------- 1 | -D tink_core=1.18.0 2 | # @install: lix --silent download "gh://github.com/haxetink/tink_core#7b5acdbc54ddcd27bbdd5fcebd666c4ad150426e" into tink_core/1.18.0/github/7b5acdbc54ddcd27bbdd5fcebd666c4ad150426e 3 | -cp ${HAXE_LIBCACHE}/tink_core/1.18.0/github/7b5acdbc54ddcd27bbdd5fcebd666c4ad150426e/src 4 | -------------------------------------------------------------------------------- /haxe_libraries/tink_io.hxml: -------------------------------------------------------------------------------- 1 | -D tink_io=0.6.2 2 | # @install: lix --silent download "gh://github.com/haxetink/tink_io#b4e4b09801e27781091f66bcbd6acdd6e4763545" into tink_io/0.6.2/github/b4e4b09801e27781091f66bcbd6acdd6e4763545 3 | -lib tink_chunk 4 | -lib tink_streams 5 | -cp ${HAXE_LIBCACHE}/tink_io/0.6.2/github/b4e4b09801e27781091f66bcbd6acdd6e4763545/src 6 | -------------------------------------------------------------------------------- /haxe_libraries/tink_macro.hxml: -------------------------------------------------------------------------------- 1 | -D tink_macro=0.17.2 2 | # @install: lix --silent download "gh://github.com/haxetink/tink_macro#b7e413d839dbf8b81d4a064ae4e90013a2c8615b" into tink_macro/0.17.2/github/b7e413d839dbf8b81d4a064ae4e90013a2c8615b 3 | -lib tink_core 4 | -cp ${HAXE_LIBCACHE}/tink_macro/0.17.2/github/b7e413d839dbf8b81d4a064ae4e90013a2c8615b/src 5 | -------------------------------------------------------------------------------- /haxe_libraries/tink_priority.hxml: -------------------------------------------------------------------------------- 1 | # @install: lix --silent download haxelib:tink_priority#0.1.3 into tink_priority/0.1.3/haxelib 2 | -D tink_priority=0.1.3 3 | -cp ${HAXESHIM_LIBCACHE}/tink_priority/0.1.3/haxelib/src 4 | -------------------------------------------------------------------------------- /haxe_libraries/tink_streams.hxml: -------------------------------------------------------------------------------- 1 | -D tink_streams=0.3.2 2 | # @install: lix --silent download "gh://github.com/haxetink/tink_streams#01a2b3894ae7ee5eb583503282371e307c03f065" into tink_streams/0.3.2/github/01a2b3894ae7ee5eb583503282371e307c03f065 3 | -lib tink_core 4 | -cp ${HAXE_LIBCACHE}/tink_streams/0.3.2/github/01a2b3894ae7ee5eb583503282371e307c03f065/src 5 | # temp for development, delete this file when pure branch merged 6 | -D pure -------------------------------------------------------------------------------- /haxe_libraries/tink_stringly.hxml: -------------------------------------------------------------------------------- 1 | -D tink_stringly=0.3.1 2 | # @install: lix --silent download "haxelib:/tink_stringly#0.3.1" into tink_stringly/0.3.1/haxelib 3 | -lib tink_core 4 | -cp ${HAXE_LIBCACHE}/tink_stringly/0.3.1/haxelib/src 5 | -------------------------------------------------------------------------------- /haxe_libraries/tink_syntaxhub.hxml: -------------------------------------------------------------------------------- 1 | -D tink_syntaxhub=0.4.2 2 | # @install: lix --silent download "gh://github.com/haxetink/tink_syntaxhub#8e4aed73a6922bc3f9c299a7b1b9809a18559581" into tink_syntaxhub/0.4.2/github/8e4aed73a6922bc3f9c299a7b1b9809a18559581 3 | -lib tink_priority 4 | -lib tink_macro 5 | -cp ${HAXE_LIBCACHE}/tink_syntaxhub/0.4.2/github/8e4aed73a6922bc3f9c299a7b1b9809a18559581/src 6 | --macro tink.SyntaxHub.use() -------------------------------------------------------------------------------- /haxe_libraries/tink_testrunner.hxml: -------------------------------------------------------------------------------- 1 | -D tink_testrunner=0.6.5 2 | # @install: lix --silent download "gh://github.com/haxetink/tink_testrunner#25cd6413ea1577b82815466943d743fcee69723b" into tink_testrunner/0.6.5/github/25cd6413ea1577b82815466943d743fcee69723b 3 | -lib ansi 4 | -lib tink_macro 5 | -lib tink_streams 6 | -cp ${HAXE_LIBCACHE}/tink_testrunner/0.6.5/github/25cd6413ea1577b82815466943d743fcee69723b/src 7 | -------------------------------------------------------------------------------- /haxe_libraries/tink_unittest.hxml: -------------------------------------------------------------------------------- 1 | -D tink_unittest=0.5.7 2 | # @install: lix --silent download "gh://github.com/haxetink/tink_unittest#3f4ba8a51e274233de12afa57589667c6cf6423a" into tink_unittest/0.5.7/github/3f4ba8a51e274233de12afa57589667c6cf6423a 3 | -lib tink_syntaxhub 4 | -lib tink_testrunner 5 | -cp ${HAXE_LIBCACHE}/tink_unittest/0.5.7/github/3f4ba8a51e274233de12afa57589667c6cf6423a/src 6 | --macro tink.unit.AssertionBufferInjector.use() -------------------------------------------------------------------------------- /haxe_libraries/travix.hxml: -------------------------------------------------------------------------------- 1 | -D travix=0.12.2 2 | # @install: lix --silent download "gh://github.com/back2dos/travix#d9f15d14098deea42a89334b0473f088e670e7de" into travix/0.12.2/github/d9f15d14098deea42a89334b0473f088e670e7de 3 | # @post-install: cd ${HAXE_LIBCACHE}/travix/0.12.2/github/d9f15d14098deea42a89334b0473f088e670e7de && haxe -cp src --run travix.PostDownload 4 | # @run: haxelib run-dir travix ${HAXE_LIBCACHE}/travix/0.12.2/github/d9f15d14098deea42a89334b0473f088e670e7de 5 | -lib tink_cli 6 | -cp ${HAXE_LIBCACHE}/travix/0.12.2/github/d9f15d14098deea42a89334b0473f088e670e7de/src 7 | -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsonwebtoken", 3 | "license": "MIT", 4 | "tags": [ 5 | "cross", 6 | "jsonwebtoken" 7 | ], 8 | "classPath": "src", 9 | "description": "JsonWebToken for Haxe", 10 | "contributors": [ 11 | "kevinresol" 12 | ], 13 | "releasenote": "typ header is optional", 14 | "version": "2.0.3", 15 | "url": "https://github.com/kevinresol/jsonwebtoken", 16 | "dependencies": { 17 | "crypto": "", 18 | "tink_core": "" 19 | } 20 | } -------------------------------------------------------------------------------- /src/jsonwebtoken/Algorithm.hx: -------------------------------------------------------------------------------- 1 | package jsonwebtoken; 2 | 3 | import haxe.io.Bytes; 4 | 5 | enum Algorithm { 6 | None; 7 | HS256(secret:Secret); 8 | HS384(secret:Secret); 9 | HS512(secret:Secret); 10 | RS256(keys:Keys); 11 | RS384(keys:Keys); 12 | RS512(keys:Keys); 13 | } 14 | 15 | class AlgorithmTools { 16 | public static inline function toString(a:Algorithm) 17 | return switch a { 18 | case None: 'none'; 19 | default: a.getName(); 20 | } 21 | } 22 | 23 | @:forward 24 | abstract Secret(Bytes) from Bytes to Bytes { 25 | @:from 26 | public static inline function fromString(v:String):Secret 27 | return Bytes.ofString(v); 28 | 29 | @:to 30 | public inline function toString():String 31 | return this.toString(); 32 | 33 | #if nodejs 34 | @:to 35 | public inline function toBuffer():js.node.Buffer 36 | return js.node.Buffer.hxFromBytes(this); 37 | #end 38 | } 39 | 40 | typedef Keys = { 41 | ?publicKey:String, 42 | ?privateKey:String, 43 | ?passcode:String, 44 | } -------------------------------------------------------------------------------- /src/jsonwebtoken/Claims.hx: -------------------------------------------------------------------------------- 1 | package jsonwebtoken; 2 | 3 | import haxe.extern.EitherType; 4 | 5 | typedef Claims = { 6 | ?iss:String, 7 | ?sub:String, 8 | ?aud:EitherType, String>, 9 | ?exp:EpochTimeSeconds, 10 | ?nbf:EpochTimeSeconds, 11 | ?iat:EpochTimeSeconds, 12 | ?jti:String, 13 | } 14 | 15 | abstract EpochTimeSeconds(Int) to Int from Int { 16 | @:from 17 | public static inline function fromDate(date:Date):EpochTimeSeconds 18 | return Std.int(date.getTime() / 1000); 19 | 20 | @:to 21 | public inline function toDate():Date 22 | return Date.fromTime(this * 1000); 23 | 24 | @:to 25 | public inline function toInt():Int 26 | return this; 27 | } 28 | -------------------------------------------------------------------------------- /src/jsonwebtoken/Codec.hx: -------------------------------------------------------------------------------- 1 | package jsonwebtoken; 2 | 3 | using StringTools; 4 | using haxe.Json; 5 | using haxe.crypto.Base64; 6 | using haxe.io.Bytes; 7 | using tink.CoreApi; 8 | using jsonwebtoken.Codec; 9 | 10 | class Codec { 11 | 12 | @:noUsing public static function encodeSegment(segment:{}) { 13 | return segment.stringify().ofString().encode().sanitize(); 14 | } 15 | 16 | @:noUsing public static function decode(token:String) { 17 | return switch token.split('.') { 18 | case [h, p, _]: 19 | return Error.catchExceptions(function() return new Pair(decodeSegment(h), decodeSegment(p))); 20 | default: 21 | Failure(new Error('Invalid token')); 22 | } 23 | } 24 | 25 | @:noUsing public static function decodeSegment(segment:String):T { 26 | return segment.unsanitize().decode().toString().parse(); 27 | } 28 | 29 | public static function sanitize(s:String):String 30 | return s.replace('+', '-').replace('/', '_').replace('=', ''); 31 | 32 | public static function unsanitize(s:String):String 33 | return s.replace('-', '+').replace('_', '/'); // TODO: add complements? 34 | } -------------------------------------------------------------------------------- /src/jsonwebtoken/Crypto.hx: -------------------------------------------------------------------------------- 1 | package jsonwebtoken; 2 | 3 | import jsonwebtoken.Algorithm; 4 | 5 | using tink.CoreApi; 6 | 7 | interface Crypto { 8 | function sign(input:String, algorithm:Algorithm):Promise; 9 | function verify(input:String, algorithm:Algorithm, signature:String):Promise; 10 | } -------------------------------------------------------------------------------- /src/jsonwebtoken/Header.hx: -------------------------------------------------------------------------------- 1 | package jsonwebtoken; 2 | 3 | typedef Header = { 4 | alg:String, 5 | typ:String, 6 | } -------------------------------------------------------------------------------- /src/jsonwebtoken/Sequence.hx: -------------------------------------------------------------------------------- 1 | package jsonwebtoken; 2 | 3 | @:forward(concat, copy, filter, indexOf, iterator, join, lastIndexOf, map, slice, toString) 4 | abstract Sequence(Array) from Array to Array { 5 | @:from 6 | public static inline function ofSingle(v:T):Sequence 7 | return [v]; 8 | 9 | @:arrayAccess 10 | public inline function get(i:Int) 11 | return this[i]; 12 | } -------------------------------------------------------------------------------- /src/jsonwebtoken/Signer.hx: -------------------------------------------------------------------------------- 1 | package jsonwebtoken; 2 | 3 | using tink.CoreApi; 4 | 5 | interface Signer { 6 | function sign(claims:T):Promise; 7 | } -------------------------------------------------------------------------------- /src/jsonwebtoken/Verifier.hx: -------------------------------------------------------------------------------- 1 | package jsonwebtoken; 2 | 3 | using tink.CoreApi; 4 | 5 | interface Verifier { 6 | function verify(token:String):Promise; 7 | } -------------------------------------------------------------------------------- /src/jsonwebtoken/crypto/CsCrypto.hx: -------------------------------------------------------------------------------- 1 | package jsonwebtoken.crypto; 2 | 3 | import haxe.crypto.Base64; 4 | import haxe.io.Bytes; 5 | import jsonwebtoken.Algorithm; 6 | import cs.system.security.cryptography.HMAC; 7 | import cs.system.security.cryptography.HMACSHA256; 8 | import cs.system.security.cryptography.HMACSHA384; 9 | import cs.system.security.cryptography.HMACSHA512; 10 | 11 | using jsonwebtoken.Codec; 12 | using tink.CoreApi; 13 | 14 | @:require(cs) 15 | class CsCrypto implements Crypto { 16 | 17 | public function new() {} 18 | 19 | inline function unsupported():Outcome 20 | return Failure(new Error('Unsupported Algorithm')); 21 | 22 | public function sign(input:String, algorithm:Algorithm):Promise { 23 | 24 | function _hmac(h:HMAC) 25 | return hmac(input, h); 26 | 27 | return switch algorithm { 28 | case None: ''; 29 | case HS256(secret): _hmac(new HMACSHA256(secret.getData())); 30 | case HS384(secret): _hmac(new HMACSHA384(secret.getData())); 31 | case HS512(secret): _hmac(new HMACSHA512(secret.getData())); 32 | case RS256(keys): unsupported(); 33 | case RS384(keys): unsupported(); 34 | case RS512(keys): unsupported(); 35 | } 36 | } 37 | 38 | public function verify(input:String, algorithm:Algorithm, signature:String):Promise { 39 | 40 | function _result(success) 41 | return success ? Success(Noise) : Failure(new Error('Invalid signature')); 42 | 43 | function _hmac(h:HMAC) 44 | return hmac(input, h).flatMap(function(sig) return _result(sig == signature)); 45 | 46 | return switch algorithm { 47 | case None: _result(signature == ''); 48 | case HS256(secret): _hmac(new HMACSHA256(secret.getData())); 49 | case HS384(secret): _hmac(new HMACSHA384(secret.getData())); 50 | case HS512(secret): _hmac(new HMACSHA512(secret.getData())); 51 | case RS256(keys): unsupported(); 52 | case RS384(keys): unsupported(); 53 | case RS512(keys): unsupported(); 54 | } 55 | } 56 | 57 | function hmac(input:String, hmac:HMAC) { 58 | hmac.Initialize(); 59 | var digest = hmac.ComputeHash(Bytes.ofString(input).getData()); 60 | return Success(Base64.encode(Bytes.ofData(digest)).sanitize()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/jsonwebtoken/crypto/DefaultCrypto.hx: -------------------------------------------------------------------------------- 1 | package jsonwebtoken.crypto; 2 | 3 | typedef DefaultCrypto = 4 | #if cs CsCrypto 5 | #elseif java JavaCrypto 6 | #elseif nodejs NodeCrypto 7 | #elseif php PhpCrypto 8 | #elseif python PythonCrypto 9 | #else StdCrypto 10 | #end ; -------------------------------------------------------------------------------- /src/jsonwebtoken/crypto/JavaCrypto.hx: -------------------------------------------------------------------------------- 1 | package jsonwebtoken.crypto; 2 | 3 | import haxe.crypto.Base64; 4 | import haxe.io.Bytes; 5 | import jsonwebtoken.Algorithm; 6 | import java.NativeArray; 7 | import java.StdTypes.Int8; 8 | import java.security.Signature; 9 | import java.security.KeyPairGenerator; 10 | 11 | using jsonwebtoken.Codec; 12 | using tink.CoreApi; 13 | 14 | @:require(java) 15 | class JavaCrypto implements Crypto { 16 | 17 | public function new() {} 18 | 19 | inline function unsupported():Outcome 20 | return Failure(new Error('Unsupported Algorithm')); 21 | 22 | public function sign(input:String, algorithm:Algorithm):Promise { 23 | 24 | function _hmac(alg:String, key:Secret) 25 | return hmac(input, alg, key); 26 | 27 | return switch algorithm { 28 | case None: ''; 29 | case HS256(secret): _hmac('HmacSHA256', secret); 30 | case HS384(secret): _hmac('HmacSHA384', secret); 31 | case HS512(secret): _hmac('HmacSHA512', secret); 32 | case RS256(keys): unsupported(); 33 | case RS384(keys): unsupported(); 34 | case RS512(keys): unsupported(); 35 | } 36 | } 37 | 38 | public function verify(input:String, algorithm:Algorithm, signature:String):Promise { 39 | 40 | function _result(success) 41 | return success ? Success(Noise) : Failure(new Error('Invalid signature')); 42 | 43 | function _hmac(alg:String, key:Secret) 44 | return hmac(input, alg, key).flatMap(function(sig) return _result(sig == signature)); 45 | 46 | return switch algorithm { 47 | case None: _result(signature == ''); 48 | case HS256(secret): _hmac('HmacSHA256', secret); 49 | case HS384(secret): _hmac('HmacSHA384', secret); 50 | case HS512(secret): _hmac('HmacSHA512', secret); 51 | case RS256(keys): unsupported(); 52 | case RS384(keys): unsupported(); 53 | case RS512(keys): unsupported(); 54 | } 55 | } 56 | 57 | function hmac(input:String, alg:String, key:Secret) { 58 | return try { 59 | var signingKey = new SecretKeySpec(key.getData(), alg); 60 | var mac = Mac.getInstance(alg); 61 | mac.init(signingKey); 62 | var hmac = Bytes.ofData(mac.doFinal(Bytes.ofString(input).getData())); 63 | Success(Base64.encode(hmac).toString().sanitize()); 64 | } catch(e:Dynamic) { 65 | Failure(Error.withData('Native error', e)); 66 | } 67 | } 68 | } 69 | 70 | @:native('javax.crypto.Mac') 71 | extern class Mac { 72 | static function getInstance(alg:String):Mac; 73 | function init(key:SecretKeySpec):Void; 74 | function doFinal(data:NativeArray):NativeArray; 75 | } 76 | 77 | @:native('javax.crypto.spec.SecretKeySpec') 78 | extern class SecretKeySpec { 79 | function new(bytes:NativeArray, alg:String); 80 | } -------------------------------------------------------------------------------- /src/jsonwebtoken/crypto/NodeCrypto.hx: -------------------------------------------------------------------------------- 1 | package jsonwebtoken.crypto; 2 | 3 | import jsonwebtoken.Algorithm; 4 | import js.node.Crypto.*; 5 | import js.node.Buffer; 6 | 7 | using jsonwebtoken.Codec; 8 | using tink.CoreApi; 9 | 10 | @:require(nodejs) 11 | class NodeCrypto implements Crypto { 12 | 13 | public function new() {} 14 | 15 | public function sign(input:String, algorithm:Algorithm):Promise { 16 | 17 | inline function _hmac(alg:String, key:Buffer) 18 | return hmac(input, alg, key); 19 | 20 | function _rsa(alg:String, keys:Keys) { 21 | var sign = createSign(alg); 22 | sign.update(input); 23 | return Success(sign.sign(switch keys { 24 | case {privateKey: null}: return Failure(new Error('Private Key Missing')); 25 | case {privateKey: key, passcode: null}: key; 26 | case {privateKey: key, passcode: pass}: cast {key: key, passphrase: pass}; // FIXME: remove cast - https://github.com/HaxeFoundation/hxnodejs/pull/86 27 | }, 'base64').sanitize()); 28 | } 29 | 30 | return switch algorithm { 31 | case None: ''; 32 | case HS256(secret): _hmac('sha256', secret); 33 | case HS384(secret): _hmac('sha384', secret); 34 | case HS512(secret): _hmac('sha512', secret); 35 | case RS256(keys): _rsa('RSA-SHA256', keys); 36 | case RS384(keys): _rsa('RSA-SHA384', keys); 37 | case RS512(keys): _rsa('RSA-SHA512', keys); 38 | } 39 | } 40 | 41 | public function verify(input:String, algorithm:Algorithm, signature:String):Promise { 42 | 43 | function _result(success) 44 | return success ? Success(Noise) : Failure(new Error('Invalid signature')); 45 | 46 | function _hmac(alg, key) 47 | return hmac(input, alg, key).flatMap(function(sig) return _result(sig == signature)); 48 | 49 | function _rsa(alg:String, keys:Keys) { 50 | if(keys.publicKey == null) return Failure(new Error('Public Key Missing')); 51 | var verify = createVerify(alg); 52 | verify.update(input); 53 | return _result(verify.verify(keys.publicKey, signature.unsanitize(), 'base64')); 54 | } 55 | 56 | return switch algorithm { 57 | case None: _result(signature == ''); 58 | case HS256(secret): _hmac('sha256', secret); 59 | case HS384(secret): _hmac('sha384', secret); 60 | case HS512(secret): _hmac('sha512', secret); 61 | case RS256(keys): _rsa('RSA-SHA256', keys); 62 | case RS384(keys): _rsa('RSA-SHA384', keys); 63 | case RS512(keys): _rsa('RSA-SHA512', keys); 64 | } 65 | } 66 | 67 | function hmac(input:String, alg:String, key:Buffer) { 68 | if(key == null) return Failure(new Error('Secret Missing')); 69 | var hmac = createHmac(alg, key); 70 | hmac.update(input); 71 | return Success(hmac.digest('base64').sanitize()); 72 | } 73 | } -------------------------------------------------------------------------------- /src/jsonwebtoken/crypto/OpensslCrypto.hx: -------------------------------------------------------------------------------- 1 | package jsonwebtoken.crypto; 2 | 3 | import haxe.crypto.Md5; 4 | import jsonwebtoken.Algorithm; 5 | 6 | #if asys 7 | import asys.io.Process; 8 | import asys.io.File; 9 | import asys.FileSystem; 10 | #elseif sys 11 | import sys.io.Process; 12 | import sys.io.File; 13 | import sys.FileSystem; 14 | #end 15 | 16 | using jsonwebtoken.Codec; 17 | using haxe.io.Bytes; 18 | using haxe.io.Path; 19 | using haxe.crypto.Base64; 20 | using StringTools; 21 | using tink.CoreApi; 22 | using tink.io.Source; 23 | using tink.io.PipeResult; 24 | 25 | #if nodejs @:require(asys) /* hxnodejs does not support sys.io.Process yet */ #end 26 | class OpensslCrypto implements Crypto { 27 | 28 | public function new() {} 29 | 30 | public function sign(input:String, algorithm:Algorithm):Promise { 31 | 32 | function _hmac(alg:String, key:Secret) { 33 | var proc = new Process('openssl', ['dgst', '-binary', '-$alg', '-mac', 'HMAC', '-macopt', 'hexkey:' + key.toHex()]); 34 | #if asys 35 | (input:IdealSource).pipeTo(proc.stdin, {end: true}).handle(function(_) {}); 36 | return proc.stdout.all() 37 | .next(function(chunk) return Base64.encode(chunk).sanitize()); 38 | #elseif sys 39 | proc.stdin.write(Bytes.ofString(input)); 40 | proc.stdin.close(); 41 | return Base64.encode(proc.stdout.readAll()).sanitize(); 42 | #end 43 | } 44 | 45 | function _rsa(alg:String, keys:Keys) { 46 | if(keys.privateKey == null) return (new Error('Private Key Missing'):Promise); 47 | var keyPath = '/tmp/' + Md5.encode(keys.privateKey) + '-' + Date.now().getTime() + '-' + Std.random(99999) + '.pem'; 48 | var args = ['dgst', '-binary', '-$alg', '-sign', keyPath]; 49 | if(keys.passcode != null) { 50 | args.push('-passin'); 51 | args.push('pass:' + keys.passcode); 52 | } 53 | var proc = new Process('openssl', args); 54 | #if asys 55 | return File.saveContent(keyPath, keys.privateKey) 56 | .next(function(_) return (input:IdealSource).pipeTo(proc.stdin, {end: true})) 57 | .next(function(result) return result.toOutcome()) 58 | .next(function(allWritten) return allWritten ? Noise : new Error('Failed writing to stdin of the openssl process')) 59 | .next(function(_) return proc.stdout.all()) 60 | .next(function(chunk) return FileSystem.deleteFile(keyPath).swap(Base64.encode(chunk).sanitize())); 61 | #elseif sys 62 | File.saveContent(keyPath, keys.privateKey); 63 | proc.stdin.write(Bytes.ofString(input)); 64 | proc.stdin.close(); 65 | var result = Base64.encode(proc.stdout.readAll()).sanitize(); 66 | FileSystem.deleteFile(keyPath); 67 | return result; 68 | #end 69 | } 70 | 71 | return switch algorithm { 72 | case None: ''; 73 | case HS256(secret): _hmac('sha256', secret); 74 | case HS384(secret): _hmac('sha384', secret); 75 | case HS512(secret): _hmac('sha512', secret); 76 | case RS256(keys): _rsa('sha256', keys); 77 | case RS384(keys): _rsa('sha384', keys); 78 | case RS512(keys): _rsa('sha512', keys); 79 | } 80 | } 81 | 82 | public function verify(input:String, algorithm:Algorithm, signature:String):Promise { 83 | 84 | function _result(success) 85 | return success ? Success(Noise) : Failure(new Error('Invalid signature')); 86 | 87 | function _hmac() 88 | return sign(input, algorithm).next(function(sig) return _result(sig == signature)); 89 | 90 | function _rsa(alg:String, keys:Keys) { 91 | if(keys.publicKey == null) return (new Error('Public Key Missing'):Promise); 92 | var tmpDir = getTmpDir().addTrailingSlash(); 93 | var keyPath = tmpDir + Md5.encode(keys.publicKey) + '-' + Date.now().getTime() + '-' + Std.random(99999) + '.pem'; 94 | var sigPath = tmpDir + Md5.encode(signature) + '-' + Date.now().getTime() + '-' + Std.random(99999) + '.sig'; 95 | var args = ['dgst', '-$alg', '-verify', keyPath]; 96 | var proc = new Process('openssl', ['dgst', '-$alg', '-verify', keyPath, '-signature', sigPath]); 97 | #if asys 98 | return Promise.inParallel([ 99 | File.saveContent(keyPath, keys.publicKey), 100 | File.saveBytes(sigPath, Base64.decode(signature.unsanitize())), 101 | ]) 102 | .next(function(_) return (input:IdealSource).pipeTo(proc.stdin, {end: true})) 103 | .next(function(result) return result.toOutcome()) 104 | .next(function(allWritten) return allWritten ? Noise : new Error('Failed writing to stdin of the openssl process')) 105 | .next(function(_) return proc.exitCode()) 106 | .next(function(code) return Promise.inParallel([keyPath, sigPath].map(FileSystem.deleteFile)).swap(_result(code == 0))); 107 | #elseif sys 108 | File.saveContent(keyPath, keys.publicKey); 109 | File.saveBytes(sigPath, Base64.decode(signature.unsanitize())); 110 | proc.stdin.write(Bytes.ofString(input)); 111 | proc.stdin.close(); 112 | var code = proc.exitCode(); 113 | FileSystem.deleteFile(keyPath); 114 | FileSystem.deleteFile(sigPath); 115 | return _result(code == 0); 116 | #end 117 | } 118 | 119 | return switch algorithm { 120 | case None: _result(signature == ''); 121 | case HS256(secret): _hmac(); 122 | case HS384(secret): _hmac(); 123 | case HS512(secret): _hmac(); 124 | case RS256(keys): _rsa('sha256', keys); 125 | case RS384(keys): _rsa('sha384', keys); 126 | case RS512(keys): _rsa('sha512', keys); 127 | } 128 | } 129 | 130 | inline function getTmpDir():String { 131 | return 132 | #if nodejs 133 | js.node.Os.tmpdir(); 134 | #elseif php 135 | untyped __call__('sys_get_temp_dir'); 136 | #elseif cs 137 | cs.system.io.Path.GetTempPath(); 138 | #else 139 | switch Sys.systemName() { 140 | case 'Windows': 141 | switch Sys.getEnv('Temp') { 142 | case null: 'C:\\Temp'; 143 | case v: v; 144 | } 145 | default: 146 | switch Sys.getEnv('TMPDIR') { 147 | case null: '/tmp'; 148 | case v: v; 149 | } 150 | } 151 | #end 152 | } 153 | } -------------------------------------------------------------------------------- /src/jsonwebtoken/crypto/PhpCrypto.hx: -------------------------------------------------------------------------------- 1 | package jsonwebtoken.crypto; 2 | 3 | import jsonwebtoken.Algorithm; 4 | import jsonwebtoken.Codec; 5 | 6 | using tink.CoreApi; 7 | 8 | /** 9 | On Windows: 10 | Enable openssl extension by uncommenting `extension=php_openssl.dll` in php.ini 11 | **/ 12 | @:require(php) 13 | class PhpCrypto implements Crypto { 14 | 15 | public function new() {} 16 | 17 | public function sign(input:String, algorithm:Algorithm):Promise { 18 | 19 | function _hmac(alg:String, key:String) 20 | return hmac(input, alg, key); 21 | 22 | function _rsa(alg:String, keys:Keys) { 23 | if(keys.privateKey == null) return Failure(new Error('Private Key Missing')); 24 | var key = untyped __call__('openssl_pkey_get_private', keys.privateKey, keys.passcode); 25 | var signature = null; 26 | untyped __call__('openssl_sign', input, signature, key, alg); 27 | return Success(Codec.sanitize(untyped __call__('base64_encode', signature))); 28 | } 29 | 30 | return switch algorithm { 31 | case None: ''; 32 | case HS256(secret): _hmac('SHA256', secret); 33 | case HS384(secret): _hmac('SHA384', secret); 34 | case HS512(secret): _hmac('SHA512', secret); 35 | case RS256(keys): _rsa('SHA256', keys); 36 | case RS384(keys): _rsa('SHA384', keys); 37 | case RS512(keys): _rsa('SHA512', keys); 38 | } 39 | } 40 | 41 | public function verify(input:String, algorithm:Algorithm, signature:String):Promise { 42 | 43 | function _result(success) 44 | return success ? Success(Noise) : Failure(new Error('Invalid signature')); 45 | 46 | function _hmac(alg, key) 47 | return hmac(input, alg, key).flatMap(function(sig) return _result(sig == signature)); 48 | 49 | function _rsa(alg:String, keys:Keys) { 50 | if(keys.publicKey == null) return Failure(new Error('Public Key Missing')); 51 | var key = untyped __call__('openssl_pkey_get_public', keys.publicKey); 52 | var signature = untyped __call__('base64_decode', Codec.unsanitize(signature)); 53 | return _result(untyped __call__('openssl_verify', input, signature, key, alg) == 1); 54 | } 55 | 56 | return switch algorithm { 57 | case None: _result(signature == ''); 58 | case HS256(secret): _hmac('SHA256', secret); 59 | case HS384(secret): _hmac('SHA384', secret); 60 | case HS512(secret): _hmac('SHA512', secret); 61 | case RS256(keys): _rsa('SHA256', keys); 62 | case RS384(keys): _rsa('SHA384', keys); 63 | case RS512(keys): _rsa('SHA512', keys); 64 | } 65 | } 66 | 67 | function hmac(input:String, alg:String, key:String) { 68 | if(key == null) return Failure(new Error('Secret Missing')); 69 | var hmac = untyped __call__('hash_hmac', alg, input, key, true); 70 | return Success(Codec.sanitize(untyped __call__('base64_encode', hmac))); 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /src/jsonwebtoken/crypto/PythonCrypto.hx: -------------------------------------------------------------------------------- 1 | package jsonwebtoken.crypto; 2 | 3 | import haxe.crypto.Base64; 4 | import haxe.io.Bytes; 5 | import jsonwebtoken.Algorithm; 6 | import python.Bytearray; 7 | import python.KwArgs; 8 | 9 | using jsonwebtoken.Codec; 10 | using tink.CoreApi; 11 | 12 | @:require(python) 13 | class PythonCrypto implements Crypto { 14 | 15 | public function new() {} 16 | 17 | inline function unsupported():Outcome 18 | return Failure(new Error('Unsupported Algorithm')); 19 | 20 | public function sign(input:String, algorithm:Algorithm):Promise { 21 | 22 | function _hmac(alg:Void->Hashlib, key:Secret) 23 | return hmac(input, alg, key); 24 | 25 | return switch algorithm { 26 | case None: ''; 27 | case HS256(secret): _hmac(Hashlib.sha256, secret); 28 | case HS384(secret): _hmac(Hashlib.sha384, secret); 29 | case HS512(secret): _hmac(Hashlib.sha512, secret); 30 | case RS256(keys): unsupported(); 31 | case RS384(keys): unsupported(); 32 | case RS512(keys): unsupported(); 33 | } 34 | } 35 | 36 | public function verify(input:String, algorithm:Algorithm, signature:String):Promise { 37 | 38 | function _result(success) 39 | return success ? Success(Noise) : Failure(new Error('Invalid signature')); 40 | 41 | function _hmac(alg:Void->Hashlib, key:Secret) 42 | return hmac(input, alg, key).flatMap(function(sig) return _result(sig == signature)); 43 | 44 | return switch algorithm { 45 | case None: _result(signature == ''); 46 | case HS256(secret): _hmac(Hashlib.sha256, secret); 47 | case HS384(secret): _hmac(Hashlib.sha384, secret); 48 | case HS512(secret): _hmac(Hashlib.sha512, secret); 49 | case RS256(keys): unsupported(); 50 | case RS384(keys): unsupported(); 51 | case RS512(keys): unsupported(); 52 | } 53 | } 54 | 55 | function hmac(input:String, alg:Void->Hashlib, key:Secret) { 56 | var digest = Hmac.new_(key.getData(), {msg: Bytes.ofString(input).getData(), digestmod: alg}).digest(); 57 | return Success(Base64.encode(Bytes.ofData(digest)).sanitize()); 58 | } 59 | } 60 | 61 | @:pythonImport('hashlib') 62 | extern class Hashlib { 63 | static function sha256():Hashlib; 64 | static function sha384():Hashlib; 65 | static function sha512():Hashlib; 66 | } 67 | 68 | @:pythonImport('hmac') 69 | extern class Hmac { 70 | @:native('new') static function new_(key:Bytearray, ?options:KwArgs<{?msg:Bytearray, ?digestmod:Void->Hashlib}>):Hmac; 71 | function digest():Bytearray; 72 | } -------------------------------------------------------------------------------- /src/jsonwebtoken/crypto/StdCrypto.hx: -------------------------------------------------------------------------------- 1 | package jsonwebtoken.crypto; 2 | 3 | import haxe.crypto.Hmac; 4 | 5 | using jsonwebtoken.Codec; 6 | using haxe.io.Bytes; 7 | using haxe.crypto.Base64; 8 | using StringTools; 9 | using tink.CoreApi; 10 | 11 | class StdCrypto implements Crypto { 12 | 13 | public function new() {} 14 | 15 | inline function unsupported():Outcome 16 | return Failure(new Error('Unsupported Algorithm')); 17 | 18 | public function sign(input:String, algorithm:Algorithm):Promise { 19 | 20 | function _hmac(alg:HashMethod, key:Bytes) 21 | return hmac(input, alg, key); 22 | 23 | return switch algorithm { 24 | case None: ''; 25 | case HS256(secret): _hmac(SHA256, secret); 26 | case HS384(secret): _hmac(SHA384, secret); 27 | case HS512(secret): _hmac(SHA512, secret); 28 | case RS256(keys): unsupported(); 29 | case RS384(keys): unsupported(); 30 | case RS512(keys): unsupported(); 31 | } 32 | } 33 | 34 | public function verify(input:String, algorithm:Algorithm, signature:String):Promise { 35 | 36 | function _result(success) 37 | return success ? Success(Noise) : Failure(new Error('Invalid signature')); 38 | 39 | function _hmac(alg:HashMethod, key:Bytes) 40 | return hmac(input, alg, key).flatMap(function(sig) return _result(sig == signature)); 41 | 42 | return switch algorithm { 43 | case None: _result(signature == ''); 44 | case HS256(secret): _hmac(SHA256, secret); 45 | case HS384(secret): _hmac(SHA384, secret); 46 | case HS512(secret): _hmac(SHA512, secret); 47 | case RS256(keys): unsupported(); 48 | case RS384(keys): unsupported(); 49 | case RS512(keys): unsupported(); 50 | } 51 | } 52 | 53 | function hmac(input:String, alg, key) 54 | return Success(new Hmac(alg).make(key, input.ofString()).encode().toString().sanitize()); 55 | } -------------------------------------------------------------------------------- /src/jsonwebtoken/signer/BasicSigner.hx: -------------------------------------------------------------------------------- 1 | package jsonwebtoken.signer; 2 | 3 | import jsonwebtoken.Signer; 4 | import jsonwebtoken.Codec; 5 | import jsonwebtoken.Crypto; 6 | import jsonwebtoken.crypto.DefaultCrypto; 7 | 8 | using jsonwebtoken.Algorithm; 9 | using haxe.io.Bytes; 10 | using haxe.Json; 11 | using haxe.crypto.Base64; 12 | using tink.CoreApi; 13 | using StringTools; 14 | 15 | class BasicSigner implements Signer { 16 | var algorithm:Algorithm; 17 | var crypto:Crypto; 18 | 19 | public function new(algorithm, ?crypto) { 20 | this.algorithm = algorithm; 21 | this.crypto = switch crypto { 22 | case null: new DefaultCrypto(); 23 | case v: v; 24 | } 25 | } 26 | 27 | public function sign(claims:T):Promise { 28 | var header = Codec.encodeSegment({ 29 | alg: algorithm.toString(), 30 | typ: 'JWT', 31 | }); 32 | 33 | var payload = Codec.encodeSegment(claims); 34 | var input = '$header.$payload'; 35 | 36 | return crypto.sign(input, algorithm).next(function(sig) return '$input.$sig'); 37 | } 38 | } -------------------------------------------------------------------------------- /src/jsonwebtoken/verifier/BasicVerifier.hx: -------------------------------------------------------------------------------- 1 | package jsonwebtoken.verifier; 2 | 3 | import jsonwebtoken.Verifier; 4 | import jsonwebtoken.Sequence; 5 | import jsonwebtoken.Crypto; 6 | import jsonwebtoken.crypto.DefaultCrypto; 7 | 8 | using jsonwebtoken.Algorithm; 9 | using jsonwebtoken.Codec; 10 | using tink.CoreApi; 11 | 12 | class BasicVerifier implements Verifier { 13 | 14 | var algorithm:Algorithm; 15 | var crypto:Crypto; 16 | var options:VerifyOptions; 17 | 18 | public function new(algorithm, ?crypto, ?options) { 19 | this.algorithm = algorithm; 20 | this.crypto = switch crypto { 21 | case null: new DefaultCrypto(); 22 | case v: v; 23 | } 24 | this.options = options; 25 | } 26 | 27 | public function verify(token:String):Promise { 28 | if(token == null || token == '') return new Error('Token missing'); 29 | 30 | switch token.sanitize().split('.') { 31 | case [h, p, s]: 32 | var header:Header = try Codec.decodeSegment(h) catch(e:Dynamic) return Error.withData('Invalid JWT header', e); 33 | 34 | // if(header.typ != 'JWT') return new Error('Invalid typ header'); 35 | if(header.alg != algorithm.toString()) return new Error('Invalid algorithm'); 36 | 37 | return crypto.verify('$h.$p', algorithm, s).next(function(sig):Outcome { 38 | var payload:Claims = try Codec.decodeSegment(p) catch(e:Dynamic) return Failure(Error.withData('Invalid JWT payload', e)); 39 | 40 | if(payload.nbf != null && Std.is(payload.nbf, Float) && Date.now().getTime() / 1000 < payload.nbf.toInt()) 41 | return Failure(new Error('Not available yet (nbf)')); 42 | 43 | if(payload.exp != null && Std.is(payload.exp, Float) && Date.now().getTime() / 1000 > payload.exp.toInt()) 44 | return Failure(new Error('Expired (exp)')); 45 | 46 | if(options != null) { 47 | if(options.iss != null) { 48 | if(options.iss.indexOf(payload.iss) == -1) 49 | return Failure(new Error('Invalid issuer (iss)')); 50 | } 51 | if(options.aud != null) { 52 | var aud:Dynamic = payload.aud; 53 | if(aud == null) { 54 | return Failure(new Error('Missing audience (aud)')); 55 | } else if(Std.is(aud, String)) { 56 | if(options.aud != aud) 57 | return Failure(new Error('Invalid audience (aud)')); 58 | } else { 59 | if((aud:Array).indexOf(options.aud) == -1) 60 | return Failure(new Error('Invalid audience (aud)')); 61 | } 62 | } 63 | } 64 | return Success(cast payload); 65 | }); 66 | 67 | default: 68 | return new Error('Invalid token'); 69 | } 70 | } 71 | } 72 | 73 | typedef VerifyOptions = { 74 | ?aud:String, 75 | ?iss:Sequence, 76 | } -------------------------------------------------------------------------------- /submit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | zip -r jsonwebtoken.zip haxelib.json src README.md 4 | haxelib submit jsonwebtoken.zip 5 | rm jsonwebtoken.zip -------------------------------------------------------------------------------- /tests.hxml: -------------------------------------------------------------------------------- 1 | -cp ./tests/ 2 | -main RunTests 3 | -lib tink_unittest 4 | -dce full -------------------------------------------------------------------------------- /tests/RunTests.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | import tink.testrunner.*; 4 | import tink.unit.*; 5 | 6 | class RunTests { 7 | static function main() { 8 | Runner.run(TestBatch.make([ 9 | new SignerTest(), 10 | new VerifierTest(), 11 | ])).handle(Runner.exit); 12 | } 13 | } -------------------------------------------------------------------------------- /tests/SignerTest.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | import jsonwebtoken.Algorithm; 4 | import jsonwebtoken.signer.*; 5 | import jsonwebtoken.crypto.*; 6 | import tink.unit.Assert.*; 7 | 8 | class SignerTest { 9 | 10 | static var PUBLIC_KEY = '-----BEGIN PUBLIC KEY----- 11 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA33TqqLR3eeUmDtHS89qF 12 | 3p4MP7Wfqt2Zjj3lZjLjjCGDvwr9cJNlNDiuKboODgUiT4ZdPWbOiMAfDcDzlOxA 13 | 04DDnEFGAf+kDQiNSe2ZtqC7bnIc8+KSG/qOGQIVaay4Ucr6ovDkykO5Hxn7OU7s 14 | Jp9TP9H0JH8zMQA6YzijYH9LsupTerrY3U6zyihVEDXXOv08vBHk50BMFJbE9iwF 15 | wnxCsU5+UZUZYw87Uu0n4LPFS9BT8tUIvAfnRXIEWCha3KbFWmdZQZlyrFw0buUE 16 | f0YN3/Q0auBkdbDR/ES2PbgKTJdkjc/rEeM0TxvOUf7HuUNOhrtAVEN1D5uuxE1W 17 | SwIDAQAB 18 | -----END PUBLIC KEY----- 19 | '; 20 | 21 | static var PASSCODE = 'passwd'; 22 | static var PRIVATE_KEY = '-----BEGIN RSA PRIVATE KEY----- 23 | Proc-Type: 4,ENCRYPTED 24 | DEK-Info: DES-EDE3-CBC,2E65118E6C7B5207 25 | 26 | 7cYUTW4ZBdmVZ4ILB08hcTdm5ib0E0zcy+I7pHpNQfJHtI7BJ4omys5S19ufJPBJ 27 | IzYjeO7oTVqI37F6EUmjZqG4WVE2UQbQDkosZbZN82O4Ipu1lFAPEbwjqePMKufz 28 | snSQHKfnbyyDPEVNlJbs19NXC8v6g+pQay5rH/I6N2iBxgsTmuemZ54EhNQMZyEN 29 | R/CiheArWEH9H8/4hd2gc9Tb2s0MwGHILL4kbbNm5tp3xw4ik7OYWNrj3m+nG6Xb 30 | vKXh2xEanAZAyMXTqDJTHdn7/CEqusQPJjZGV+Mf1kjKu7p4qcXFnIXP5ILnTW7b 31 | lHoWC4eweDzKOMRzXmbABEVSUvx2SmPl4TcoC5L1SCAHEmZaKbaY7S5l53u6gl0f 32 | ULuQbt7Hr3THznlNFKkGT1/yVNt2QOm1emZd55LaNe8E7XsNSlhl0grYQ+Ue8Jba 33 | x85OapltVjxM9wVCwbgFyi04ihdKHo9e+uYKeTGKv0hU5O7HEH1ev6t/s2u/UG6h 34 | TqEsYrVp0CMHpt5uAF6nZyK6GZ/CHTxh/rz1hADMofem59+e6tVtjnPGA3EjnJT8 35 | BMOw/D2QIDxjxj2GUzz+YJp50ENhWrL9oSDkG2nzv4NVL77QIy+T/2/f4PgokUDO 36 | QJjIfxPWE40cHGHpnQtZvEPoxP0H3T0YhmEVwuJxX3uaWOY/8Fa1c7Ln0SwWdfV5 37 | gYvJV8o6c3sumcq1O3agPDlHC5O4IxG7AZQ8CHRDyASogzfkY6P579ZOGYaO4al7 38 | WA1YIpsHs3/1f4SByMuWe0NVkFfvXckjpqGrBQpTmqQzk6baa0VQ0cwU3XlkwHac 39 | WB/fQ4jylwFzZDcp5JAo53n6aU72zgNvDlGTNKwdXXZI5U3JPocH0AiZgFFWYJLd 40 | 63PJLDnjyE3i6XMVlxifXKkXVv0RYSz+ByS7Oz9aCgnQhNU8ycv+UxtfkPQih5zE 41 | /0Y2EEFknajmFJpNXczzF8OEzaswmR0AOjcCiklZKRf61rf5faJxJhhqKEEBJuL6 42 | oodDVRk3OGU1yQSBazT8nK3V+e6FMo3tWkra2BXFCD+pKxTy014Cp59S1w6F1Fjt 43 | WX7eMWSLWfQ56j2kLMBHq5gb2arqlqH3fsYOTD3TNjCYF3Sgx309kVPuOK5vw61P 44 | pnL/LN3iGY42WR+9lfAyNN2qj9zvwKwscyYs5+DPQoPmcPcVGc3v/u66bLcOGbEU 45 | OlGa/6gdD4GCp5E4fP/7GbnEY/PW2abquFhGB+pVdl3/4+1U/8kItlfWNZoG4FhE 46 | gjMd7glmrdFiNJFFpf5ks1lVXGqJ4mZxqtEZrxUEwciZjm4V27a+E2KyV9NnksZ6 47 | xF4tGPKIPsvNTV5o8ZqjiacxgbYmr2ywqDXKCgpU/RWSh1sLapqSQqbH/w0MquUj 48 | VhVX0RMYH/foKtjagZf/KO1/mnCITl86treIdachGgR4wr/qqMjrpPUaPLCRY3JQ 49 | 00XUP1Mu6YPE0SnMYAVxZheqKHly3a1pg4Xp7YWlM671oUORs3+VENfnbIxgr+2D 50 | TiJT9PxwpfK53Oh7RBSWHJZRuAdLUXE8DG+bl0N/QkJM6pFUxTI1AQ== 51 | -----END RSA PRIVATE KEY----- 52 | '; 53 | 54 | var secret:String; 55 | var keys:Keys; 56 | 57 | public function new() { 58 | secret = 'secret'; 59 | keys = { 60 | publicKey: PUBLIC_KEY, 61 | privateKey: PRIVATE_KEY, 62 | passcode: PASSCODE, 63 | } 64 | } 65 | 66 | public function testHS256() { 67 | var signer = getSigner(HS256(secret)); 68 | return signer.sign(cast {iss: 'iss'}) 69 | .next(function(token:String) return assert('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpc3MifQ.nVrY_yb-LcNKckLaXItppW57KQGiKXTEZVLqhptT6Do' == token)); 70 | } 71 | 72 | #if (crypto || !(cpp || neko || interp || (js && !nodejs))) 73 | public function testHS384() { 74 | var signer = getSigner(HS384(secret)); 75 | return signer.sign(cast {iss: 'iss'}) 76 | .next(function(token:String) return assert('eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpc3MifQ.X9DaLysdsHc-zZJNEffbWd2HRrWmX3qDKToWGGIJc_0s2SIbQJdHDV804m8LDLSW' == token)); 77 | } 78 | 79 | public function testHS512() { 80 | var signer = getSigner(HS512(secret)); 81 | return signer.sign(cast {iss: 'iss'}) 82 | .next(function(token:String) return assert('eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpc3MifQ.ltlcf72E1-V-u2qGJ7MRiUBngFzp5vXrmC6wuKiQac8l6bcfVspLOnW_pCl-h5QiBp2ckxz51BAZKM8HQZ_-6Q' == token)); 83 | } 84 | #end 85 | 86 | #if (nodejs || php || openssl) 87 | public function testRS256() { 88 | var signer = getSigner(RS256(keys)); 89 | return signer.sign(cast {iss: 'iss'}) 90 | .next(function(token:String) return assert('eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpc3MifQ.TehmtMaG975_ygPam2o1frZHkXBnLGNpZjJzAMiSu9IpDYVIFs9eRQmKWBEfPTWNL0VV6RhU2OYRsB8UZ-JUyddkiyTpVMcJ6V5ktLW5IeXtNp74-5FTeNIQYUvoSuoaJTs0wJ93PNxPvsfKUFdP80Slx4MTMXypi0ChxOQQmZ6vBKqJFx7kbdA_zlwVMfOPcYiPEBXciYVZXE6QHCz5zo-t9vQTTzZOpVz2o_fqaL0crWaZaKnMDWtCqQOHHPb7-Ir7GWBqOvmkaePiRXRCGTbeY2ISjsWERA2qaugrbKWJYFINUm62wtdmXubUiN9OeC9iirgZQGoewtuaxLM_KA' == token)); 91 | } 92 | 93 | public function testRS384() { 94 | var signer = getSigner(RS384(keys)); 95 | return signer.sign(cast {iss: 'iss'}) 96 | .next(function(token:String) return assert('eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpc3MifQ.djXH4r4en_PkmlBB3qV9kLTsArbQkrnY4eU0QG7e61Uy_xFbiJceNfVnj9VDjJsTRoa9JoT13gVgEAkREn98rqX9KLEQhTHDGxwrFp8zQpM3LGQAV4LB4ZQmXBfd0QNmlpjqFEmM-mBAl8tkmqhQ7bxLH1Kmo4O9c1p0ZMZueUnwumWe-z5E66tzK-NaF9y8YKIFIetsVrcka918lsON5e2yDxIc8HOH3-1yPeDcMf5-fptWa_Ti5WgVnjIflDulXVv672czqX4PZ3IwJU8NbHPFG22KB5v620CbbtvGflvq8-lgNL_qwFwJ49AZA9kvDXVoB4xBmMmcPOTPrrghBA' == token)); 97 | } 98 | 99 | public function testRS512() { 100 | var signer = getSigner(RS512(keys)); 101 | return signer.sign(cast {iss: 'iss'}) 102 | .next(function(token:String) return assert('eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpc3MifQ.afuqwd1UtSdSy8qGoCz5bmsAxbWpywfnf8n_FcrmrvP2caw2ZzpyBrTEGlzFS3Jl5LAAKho_RjPycZjHWfKN_lsGzBlmdLb8nGSER1nnlAV9sXt1XBky0dIEhAYbUEb5lAz2kXHKnt3uy_RgweV1jBOHWwNbf8i_-wy31MwFabYzLEj4tuALOI3jHsJ3-mZBFpeHnQRSSKVtOoiAddz4QHLIRTRcYMwBxAGvgFacaXIt9T-lbp-OHye9j6Lagz5JlbEoEDlsdVYOy0J44xNtwabdmRUJgadyacvHQfesg55algX4t5go_jyyDoEHECXr5veHXcbcRVFEkdgt1eKLqg' == token)); 103 | } 104 | #end 105 | 106 | function getSigner(alg:Algorithm) { 107 | var crypto = 108 | #if openssl new OpensslCrypto() 109 | #else null 110 | #end ; 111 | return new BasicSigner(alg, crypto); 112 | } 113 | } -------------------------------------------------------------------------------- /tests/VerifierTest.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | import jsonwebtoken.Algorithm; 4 | import jsonwebtoken.Claims; 5 | import jsonwebtoken.verifier.*; 6 | import jsonwebtoken.crypto.*; 7 | 8 | import tink.unit.Assert.*; 9 | 10 | using tink.CoreApi; 11 | 12 | class VerifierTest { 13 | static var PUBLIC_KEY = '-----BEGIN PUBLIC KEY----- 14 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA33TqqLR3eeUmDtHS89qF 15 | 3p4MP7Wfqt2Zjj3lZjLjjCGDvwr9cJNlNDiuKboODgUiT4ZdPWbOiMAfDcDzlOxA 16 | 04DDnEFGAf+kDQiNSe2ZtqC7bnIc8+KSG/qOGQIVaay4Ucr6ovDkykO5Hxn7OU7s 17 | Jp9TP9H0JH8zMQA6YzijYH9LsupTerrY3U6zyihVEDXXOv08vBHk50BMFJbE9iwF 18 | wnxCsU5+UZUZYw87Uu0n4LPFS9BT8tUIvAfnRXIEWCha3KbFWmdZQZlyrFw0buUE 19 | f0YN3/Q0auBkdbDR/ES2PbgKTJdkjc/rEeM0TxvOUf7HuUNOhrtAVEN1D5uuxE1W 20 | SwIDAQAB 21 | -----END PUBLIC KEY----- 22 | '; 23 | 24 | static var PASSCODE = 'passwd'; 25 | static var PRIVATE_KEY = '-----BEGIN RSA PRIVATE KEY----- 26 | Proc-Type: 4,ENCRYPTED 27 | DEK-Info: DES-EDE3-CBC,2E65118E6C7B5207 28 | 29 | 7cYUTW4ZBdmVZ4ILB08hcTdm5ib0E0zcy+I7pHpNQfJHtI7BJ4omys5S19ufJPBJ 30 | IzYjeO7oTVqI37F6EUmjZqG4WVE2UQbQDkosZbZN82O4Ipu1lFAPEbwjqePMKufz 31 | snSQHKfnbyyDPEVNlJbs19NXC8v6g+pQay5rH/I6N2iBxgsTmuemZ54EhNQMZyEN 32 | R/CiheArWEH9H8/4hd2gc9Tb2s0MwGHILL4kbbNm5tp3xw4ik7OYWNrj3m+nG6Xb 33 | vKXh2xEanAZAyMXTqDJTHdn7/CEqusQPJjZGV+Mf1kjKu7p4qcXFnIXP5ILnTW7b 34 | lHoWC4eweDzKOMRzXmbABEVSUvx2SmPl4TcoC5L1SCAHEmZaKbaY7S5l53u6gl0f 35 | ULuQbt7Hr3THznlNFKkGT1/yVNt2QOm1emZd55LaNe8E7XsNSlhl0grYQ+Ue8Jba 36 | x85OapltVjxM9wVCwbgFyi04ihdKHo9e+uYKeTGKv0hU5O7HEH1ev6t/s2u/UG6h 37 | TqEsYrVp0CMHpt5uAF6nZyK6GZ/CHTxh/rz1hADMofem59+e6tVtjnPGA3EjnJT8 38 | BMOw/D2QIDxjxj2GUzz+YJp50ENhWrL9oSDkG2nzv4NVL77QIy+T/2/f4PgokUDO 39 | QJjIfxPWE40cHGHpnQtZvEPoxP0H3T0YhmEVwuJxX3uaWOY/8Fa1c7Ln0SwWdfV5 40 | gYvJV8o6c3sumcq1O3agPDlHC5O4IxG7AZQ8CHRDyASogzfkY6P579ZOGYaO4al7 41 | WA1YIpsHs3/1f4SByMuWe0NVkFfvXckjpqGrBQpTmqQzk6baa0VQ0cwU3XlkwHac 42 | WB/fQ4jylwFzZDcp5JAo53n6aU72zgNvDlGTNKwdXXZI5U3JPocH0AiZgFFWYJLd 43 | 63PJLDnjyE3i6XMVlxifXKkXVv0RYSz+ByS7Oz9aCgnQhNU8ycv+UxtfkPQih5zE 44 | /0Y2EEFknajmFJpNXczzF8OEzaswmR0AOjcCiklZKRf61rf5faJxJhhqKEEBJuL6 45 | oodDVRk3OGU1yQSBazT8nK3V+e6FMo3tWkra2BXFCD+pKxTy014Cp59S1w6F1Fjt 46 | WX7eMWSLWfQ56j2kLMBHq5gb2arqlqH3fsYOTD3TNjCYF3Sgx309kVPuOK5vw61P 47 | pnL/LN3iGY42WR+9lfAyNN2qj9zvwKwscyYs5+DPQoPmcPcVGc3v/u66bLcOGbEU 48 | OlGa/6gdD4GCp5E4fP/7GbnEY/PW2abquFhGB+pVdl3/4+1U/8kItlfWNZoG4FhE 49 | gjMd7glmrdFiNJFFpf5ks1lVXGqJ4mZxqtEZrxUEwciZjm4V27a+E2KyV9NnksZ6 50 | xF4tGPKIPsvNTV5o8ZqjiacxgbYmr2ywqDXKCgpU/RWSh1sLapqSQqbH/w0MquUj 51 | VhVX0RMYH/foKtjagZf/KO1/mnCITl86treIdachGgR4wr/qqMjrpPUaPLCRY3JQ 52 | 00XUP1Mu6YPE0SnMYAVxZheqKHly3a1pg4Xp7YWlM671oUORs3+VENfnbIxgr+2D 53 | TiJT9PxwpfK53Oh7RBSWHJZRuAdLUXE8DG+bl0N/QkJM6pFUxTI1AQ== 54 | -----END RSA PRIVATE KEY----- 55 | '; 56 | 57 | var secret:String; 58 | var keys:Keys; 59 | 60 | public function new() { 61 | secret = 'secret'; 62 | keys = { 63 | publicKey: PUBLIC_KEY, 64 | privateKey: PRIVATE_KEY, 65 | passcode: PASSCODE, 66 | } 67 | } 68 | 69 | 70 | public function testHS256() { 71 | var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpc3MifQ.nVrY_yb-LcNKckLaXItppW57KQGiKXTEZVLqhptT6Do'; 72 | var verifier = getVerifier(HS256(secret)); 73 | return verifier.verify(token).map(function(o:Outcome) return assert(o)); 74 | } 75 | 76 | #if (crypto || !(cpp || neko || interp || (js && !nodejs))) 77 | public function testHS384() { 78 | var token = 'eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpc3MifQ.X9DaLysdsHc-zZJNEffbWd2HRrWmX3qDKToWGGIJc_0s2SIbQJdHDV804m8LDLSW'; 79 | var verifier = getVerifier(HS384(secret)); 80 | return verifier.verify(token).map(function(o:Outcome) return assert(o)); 81 | } 82 | 83 | public function testHS512() { 84 | var token = 'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpc3MifQ.ltlcf72E1-V-u2qGJ7MRiUBngFzp5vXrmC6wuKiQac8l6bcfVspLOnW_pCl-h5QiBp2ckxz51BAZKM8HQZ_-6Q'; 85 | var verifier = getVerifier(HS512(secret)); 86 | return verifier.verify(token).map(function(o:Outcome) return assert(o)); 87 | } 88 | #end 89 | 90 | #if (nodejs || php || openssl) 91 | public function testRS256() { 92 | var token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpc3MifQ.TehmtMaG975_ygPam2o1frZHkXBnLGNpZjJzAMiSu9IpDYVIFs9eRQmKWBEfPTWNL0VV6RhU2OYRsB8UZ-JUyddkiyTpVMcJ6V5ktLW5IeXtNp74-5FTeNIQYUvoSuoaJTs0wJ93PNxPvsfKUFdP80Slx4MTMXypi0ChxOQQmZ6vBKqJFx7kbdA_zlwVMfOPcYiPEBXciYVZXE6QHCz5zo-t9vQTTzZOpVz2o_fqaL0crWaZaKnMDWtCqQOHHPb7-Ir7GWBqOvmkaePiRXRCGTbeY2ISjsWERA2qaugrbKWJYFINUm62wtdmXubUiN9OeC9iirgZQGoewtuaxLM_KA'; 93 | var verifier = getVerifier(RS256(keys)); 94 | return verifier.verify(token).map(function(o:Outcome) return assert(o)); 95 | } 96 | 97 | public function testRS384() { 98 | var token = 'eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpc3MifQ.djXH4r4en_PkmlBB3qV9kLTsArbQkrnY4eU0QG7e61Uy_xFbiJceNfVnj9VDjJsTRoa9JoT13gVgEAkREn98rqX9KLEQhTHDGxwrFp8zQpM3LGQAV4LB4ZQmXBfd0QNmlpjqFEmM-mBAl8tkmqhQ7bxLH1Kmo4O9c1p0ZMZueUnwumWe-z5E66tzK-NaF9y8YKIFIetsVrcka918lsON5e2yDxIc8HOH3-1yPeDcMf5-fptWa_Ti5WgVnjIflDulXVv672czqX4PZ3IwJU8NbHPFG22KB5v620CbbtvGflvq8-lgNL_qwFwJ49AZA9kvDXVoB4xBmMmcPOTPrrghBA'; 99 | var verifier = getVerifier(RS384(keys)); 100 | return verifier.verify(token).map(function(o:Outcome) return assert(o)); 101 | } 102 | 103 | public function testRS512() { 104 | var token = 'eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpc3MifQ.afuqwd1UtSdSy8qGoCz5bmsAxbWpywfnf8n_FcrmrvP2caw2ZzpyBrTEGlzFS3Jl5LAAKho_RjPycZjHWfKN_lsGzBlmdLb8nGSER1nnlAV9sXt1XBky0dIEhAYbUEb5lAz2kXHKnt3uy_RgweV1jBOHWwNbf8i_-wy31MwFabYzLEj4tuALOI3jHsJ3-mZBFpeHnQRSSKVtOoiAddz4QHLIRTRcYMwBxAGvgFacaXIt9T-lbp-OHye9j6Lagz5JlbEoEDlsdVYOy0J44xNtwabdmRUJgadyacvHQfesg55algX4t5go_jyyDoEHECXr5veHXcbcRVFEkdgt1eKLqg'; 105 | var verifier = getVerifier(RS512(keys)); 106 | return verifier.verify(token).map(function(o:Outcome) return assert(o)); 107 | } 108 | #end 109 | 110 | public function testFailOnInvalidNumberOfSegments() { 111 | var verifier = getVerifier(HS256(secret)); 112 | return isFailure('Invalid token', verifier.verify('abc')); 113 | } 114 | 115 | public function testFailOnEmptyStringToken(){ 116 | var verifier = getVerifier(HS256(secret)); 117 | return isFailure('Token missing', verifier.verify('')); 118 | } 119 | 120 | public function testFailOnNullStringToken(){ 121 | var verifier = getVerifier(HS256(secret)); 122 | return isFailure('Token missing', verifier.verify(null)); 123 | } 124 | 125 | public function testFailOnInvalidSignature(){ 126 | var secret = "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"; 127 | var verifier = getVerifier(HS256(secret)); 128 | var token = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.suchsignature_plzvalidate_zomgtoken"; 129 | return isFailure('Invalid signature', verifier.verify(token)); 130 | } 131 | 132 | public function testVerifySignature(){ 133 | var secret = haxe.crypto.Base64.decode("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ+EstJQLr/T+1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"); 134 | var verifier = getVerifier(HS256(secret)); 135 | var token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0ZXN0In0.7WTHQLacmw0-FudKlwqw-3U4ILeHkDsVqQ-RTIPm5SU"; 136 | return verifier.verify(token).map(function(o:Outcome) return assert(o)); 137 | } 138 | 139 | public function testMatchedAlgorithm() { 140 | var token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.4BjJWnO3nkNiYiR1ECGmMnsrLzpTm4l0zvYWpiPAtKw"; 141 | var verifier = getVerifier(HS256('such secret')); 142 | return verifier.verify(token).map(function(o:Outcome) return assert(o)); 143 | } 144 | 145 | public function testFailOnUnmatchedAlgorithm() { 146 | var token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.4BjJWnO3nkNiYiR1ECGmMnsrLzpTm4l0zvYWpiPAtKw"; 147 | var verifier = getVerifier(HS384('such secret')); 148 | return isFailure('Invalid algorithm', verifier.verify(token)); 149 | } 150 | 151 | public function testUnexpired() { 152 | var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIxNDc0ODM2NDd9.49EWHIcHjviJjyGuJ3DObkvgZd61JjbHMgRKjTtrABw'; 153 | var verifier = getVerifier(HS256(secret)); 154 | return verifier.verify(token).map(function(o:Outcome) return assert(o)); 155 | } 156 | 157 | public function testFailWhenExpired() { 158 | var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjk0OTMzODA2MX0.zk_5pJ8NIxCas7ziMZHWEOh5ARcWc-PbxlAKzfKH0Wg'; 159 | var verifier = getVerifier(HS256(secret)); 160 | return isFailure('Expired (exp)', verifier.verify(token)); 161 | } 162 | 163 | public function testFailIssuer() { 164 | var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpc3N1ZXIifQ.FgSmi1aRikqCuBD_FwCa6yla30DVc9AgnyF-HAII--U'; 165 | var verifier = getVerifier(HS256(secret), {iss: 'issuer_'}); 166 | return isFailure('Invalid issuer (iss)', verifier.verify(token)); 167 | } 168 | 169 | public function testIssuer() { 170 | var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpc3N1ZXIifQ.FgSmi1aRikqCuBD_FwCa6yla30DVc9AgnyF-HAII--U'; 171 | var verifier = getVerifier(HS256(secret), {iss: 'issuer'}); 172 | return verifier.verify(token).map(function(o:Outcome) return assert(o)); 173 | } 174 | 175 | public function testIssuers() { 176 | var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpc3N1ZXIifQ.FgSmi1aRikqCuBD_FwCa6yla30DVc9AgnyF-HAII--U'; 177 | var verifier = getVerifier(HS256(secret), {iss: ['issuers_', 'issuer']}); 178 | return verifier.verify(token).map(function(o:Outcome) return assert(o)); 179 | } 180 | 181 | public function testFailAudience() { 182 | var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYXVkaSJdfQ.uRTaMJ0PDi61dkJ5ipM2HVeSPQlrOiddFP3np2E5k-M'; 183 | var verifier = getVerifier(HS256(secret), {aud: 'audi_'}); 184 | return isFailure('Invalid audience (aud)', verifier.verify(token)); 185 | } 186 | 187 | public function testAudience() { 188 | var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYXVkaSJdfQ.uRTaMJ0PDi61dkJ5ipM2HVeSPQlrOiddFP3np2E5k-M'; 189 | var verifier = getVerifier(HS256(secret), {aud: 'audi'}); 190 | return verifier.verify(token).map(function(o:Outcome) return assert(o)); 191 | } 192 | 193 | function isFailure(msg:String, result:Surprise, ?pos:haxe.PosInfos) 194 | return result.map(function(o) return switch o { 195 | case Failure(e): assert(msg == e.message, pos); 196 | case Success(_): assert(false, 'Expected Failure', pos); 197 | }); 198 | 199 | function getVerifier(alg:Algorithm, ?options) { 200 | var crypto = 201 | #if openssl new OpensslCrypto() 202 | #else null 203 | #end ; 204 | return new BasicVerifier(alg, crypto, options); 205 | } 206 | } --------------------------------------------------------------------------------