Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
sovrin committed Nov 12, 2022
1 parent 89692ec commit 80cbae3
Show file tree
Hide file tree
Showing 23 changed files with 486 additions and 0 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: publish

on:
push:
tags:
- 'v*'

env:
CI: true

jobs:
publish:
environment: production
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
registry-url: https://registry.npmjs.org/
- run: npm i
- run: npm run build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
24 changes: 24 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: test and coverage

on: [ push, pull_request ]
env:
CI: true

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- run: npm i
- run: npm run coverage

- name: publish coverage
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

/.idea
/.nyc_output
/node_modules
/coverage
/dist
/types

.DS_STORE
package-lock.json
15 changes: 15 additions & 0 deletions .nycrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"extension": [
".ts",
".tsx"
],
"exclude": [
"test",
"**/*.d.ts"
],
"reporter": [
"html",
"lcov",
"text-summary"
]
}
31 changes: 31 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "jted",
"version": "0.1.0",
"description": "just JWT en-/decoding",
"main": "./dist/index.js",
"exports": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc --project tsconfig.prod.json",
"test": "mocha -r ts-node/register/transpile-only --full-trace 'test/**/*.test.ts'",
"watch": "mocha -r ts-node/register/transpile-only --reporter dot --watch --watch-files 'src/**/*.ts' 'test/**/*.test.ts'",
"coverage": "nyc npm test"
},
"devDependencies": {
"@types/mocha": "^10.0.0",
"@types/node": "^18.11.9",
"mocha": "^10.1.0",
"nyc": "^15.1.0",
"ts-node": "^10.9.1",
"typescript": "^4.8.4"
},
"author": {
"name": "Oleg Kamlowski",
"email": "oleg.kamlowski@thomann.de"
},
"license": "Apache-2.0",
"keywords": [
"jwt",
"JSON Web Tokens"
]
}
35 changes: 35 additions & 0 deletions src/base64.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
*
* @param string
*/
export const decode = (string: string): string => (
Buffer.from(unescape(string), 'base64').toString()
);

/**
*
* @param string
*/
export const unescape = (string: string): string => {
string += new Array(5 - string.length % 4).join('=');

return string.replace(/-/g, '+').replace(/_/g, '/');
};

/**
*
* @param string
*/
export const encode = (string: string): string => (
escape(Buffer.from(string).toString('base64'))
);

/**
*
* @param string
*/
export const escape = (string: string): string => (
string.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '')
);
55 changes: 55 additions & 0 deletions src/decode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import verify from './verify';
import {decode} from './base64';
import {Header, Payload, Algorithm} from './types';
import {Algorithms} from './enums';

/**
* User: Oleg Kamlowski <oleg.kamlowski@thomann.de>
* Date: 16.12.2020
* Time: 00:18
*/
export default (token: string, key: string, algorithm: Algorithm = null, validate: boolean = true): Payload => {
if (!token) {
throw new Error('Token can\'t be empty');
}

const segments = token.split('.');
if (segments.length !== 3) {
throw new Error('Token doesn\'t consist of three segments');
}

let [header, payload, signature] = segments as any;

header = JSON.parse(decode(header)) as Header;
payload = JSON.parse(decode(payload)) as Payload;

if (!validate) {
return payload;
}

if (/BEGIN( RSA)? PUBLIC KEY/.test(key.toString())) {
algorithm = 'sha256';
}

algorithm = algorithm || header.alg;
if (!Algorithms.includes(algorithm)) {
throw new Error('Algorithm not supported');
}

const signing = segments.slice(0, 2)
.join('.')
;
if (!verify(signing, key, algorithm, signature)) {
throw new Error('Signature verification failed');
}

if (payload.nbf && Date.now() < payload.nbf * 1000) {
throw new Error('Token not yet active');
}

if (payload.exp && Date.now() > payload.exp * 1000) {
throw new Error('Token expired');
}

return payload;
}
38 changes: 38 additions & 0 deletions src/encode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import sign from './sign';
import {encode} from './base64';
import {Algorithm, Options, Header, Payload} from './types';
import {Algorithms} from './enums';

/**
* User: Oleg Kamlowski <oleg.kamlowski@thomann.de>
* Date: 16.12.2020
* Time: 00:16
*/
export default (payload: Payload, key: string, algorithm: Algorithm = 'sha256', options?: Options): string => {
if (!key) {
throw new Error('Key can\'t be empty');
}

if (!Algorithms.includes(algorithm)) {
throw new Error('Algorithm not supported');
}

const header: Header = {
typ: 'JWT',
alg: algorithm,
};

if (options && options.header) {
Object.assign(header, options.header);
}

const segments = [
encode(JSON.stringify(header)),
encode(JSON.stringify(payload)),
];

return [
...segments,
sign(segments.join('.'), key, algorithm),
].join('.');
}
6 changes: 6 additions & 0 deletions src/enums/Algorithms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const Algorithms = [
'sha256',
'sha384',
'sha512',
'RSA-SHA256',
] as const;
1 change: 1 addition & 0 deletions src/enums/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Algorithms';
14 changes: 14 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import sign from './sign';
import decode from './decode';
import encode from './encode';

/**
* User: Oleg Kamlowski <oleg.kamlowski@thomann.de>
* Date: 16.12.2020
* Time: 17:56
*/
export {
sign,
decode,
encode,
};
17 changes: 17 additions & 0 deletions src/sign.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {createHmac} from 'crypto';
import {escape} from './base64';
import {Algorithm} from './types';

/**
* User: Oleg Kamlowski <oleg.kamlowski@thomann.de>
* Date: 15.12.2020
* Time: 23:56
*/
export default (input: string, key: string, algorithm: Algorithm) => {
const str = createHmac(algorithm, key)
.update(input)
.digest('base64')
;

return escape(str);
}
3 changes: 3 additions & 0 deletions src/types/Algorithm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {Algorithms} from "../enums";

export type Algorithm = typeof Algorithms[number];
6 changes: 6 additions & 0 deletions src/types/Header.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {Algorithm} from "../types";

export type Header = {
typ: string,
alg?: Algorithm;
}
3 changes: 3 additions & 0 deletions src/types/Options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type Options = {
header: object
}
9 changes: 9 additions & 0 deletions src/types/Payload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type Payload = {
[key: string]: any,
sub?: string | number,
iss?: string,
aud?: string,
nbf?: number,
exp?: number,
iat?: number,
}
4 changes: 4 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './Algorithm';
export * from './Header';
export * from './Options';
export * from './Payload';
11 changes: 11 additions & 0 deletions src/verify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import sign from './sign';
import {Algorithm} from './types';

/**
* User: Oleg Kamlowski <oleg.kamlowski@thomann.de>
* Date: 16.12.2020
* Time: 00:11
*/
export default (input: string, key: string, algorithm: Algorithm, signature: string) => (
signature === sign(input, key, algorithm)
);
Loading

0 comments on commit 80cbae3

Please sign in to comment.