Skip to content

Commit dfc5b07

Browse files
committed
feat: add jwt adapter
1 parent 0f4ab1b commit dfc5b07

File tree

8 files changed

+441
-6
lines changed

8 files changed

+441
-6
lines changed

libs/jwt-adapter/package.json

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"name": "@palmares/jwt-auth",
3+
"version": "0.0.0",
4+
"description": "A robust and versatile JWT authentication module designed for seamless integration with Palmares applications",
5+
"main": "./dist/src/index.cjs",
6+
"module": "./dist/src/index.js",
7+
"types": "./dist/src/index.d.ts",
8+
"files": [
9+
"dist",
10+
"package.json",
11+
"README.md",
12+
"CHANGELOG.md"
13+
],
14+
"exports": {
15+
".": {
16+
"types": "./dist/src/index.d.ts",
17+
"import": "./dist/src/index.js",
18+
"require": "./dist/src/index.cjs"
19+
}
20+
},
21+
"scripts": {
22+
"clear": "rimraf ./dist",
23+
"build:types": "tsc --project tsconfig.types.json",
24+
"build:cjs:esm": "tsup ./src --out-dir ./dist/src --format cjs,esm --silent --no-splitting",
25+
"build": "pnpm run clear && pnpm run build:cjs:esm && pnpm run build:types",
26+
"build:types:watch": "tsc --project tsconfig.types.json --watch --preserveWatchOutput",
27+
"build:cjs:esm:watch": "tsup ./src --out-dir ./dist/src --format cjs,esm --watch --silent --no-splitting",
28+
"build:watch": "pnpm run build:types:watch & pnpm run build:cjs:esm:watch"
29+
},
30+
"repository": {
31+
"type": "git",
32+
"url": "git+https://github.com/palmaresHQ/palmares.git"
33+
},
34+
"keywords": [
35+
"palmares",
36+
"jwt",
37+
"authentication"
38+
],
39+
"type": "module",
40+
"author": "Vicente Sanchez",
41+
"license": "MIT",
42+
"bugs": {
43+
"url": "https://github.com/palmaresHQ/palmares/issues"
44+
},
45+
"homepage": "https://github.com/palmaresHQ/palmares#readme",
46+
"dependencies": {
47+
"@palmares/auth": "workspace:*",
48+
"@palmares/core": "workspace:*",
49+
"@palmares/events": "workspace:*",
50+
"@palmares/logging": "workspace:*"
51+
},
52+
"peerDependencies": {
53+
"jose": "^6.0.8",
54+
"jsonwebtoken": "^9.0.2"
55+
},
56+
"devDependencies": {
57+
"@types/jsonwebtoken": "^9.0.9"
58+
}
59+
}

libs/jwt-adapter/src/adapter.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { authAdapter } from '@palmares/auth';
2+
3+
import { sign } from './funcs/sign';
4+
import { verify } from './funcs/verify';
5+
6+
export interface JWTPayload {
7+
/**
8+
* JWT Issuer
9+
*
10+
* @see {@link https://www.rfc-editor.org/rfc/rfc7519#section-4.1.1 RFC7519#section-4.1.1}
11+
*/
12+
iss?: string;
13+
14+
/**
15+
* JWT Subject
16+
*
17+
* @see {@link https://www.rfc-editor.org/rfc/rfc7519#section-4.1.2 RFC7519#section-4.1.2}
18+
*/
19+
sub?: string;
20+
21+
/**
22+
* JWT Audience
23+
*
24+
* @see {@link https://www.rfc-editor.org/rfc/rfc7519#section-4.1.3 RFC7519#section-4.1.3}
25+
*/
26+
aud?: string | string[];
27+
28+
/**
29+
* JWT ID
30+
*
31+
* @see {@link https://www.rfc-editor.org/rfc/rfc7519#section-4.1.7 RFC7519#section-4.1.7}
32+
*/
33+
jti?: string;
34+
35+
/**
36+
* JWT Not Before
37+
*
38+
* @see {@link https://www.rfc-editor.org/rfc/rfc7519#section-4.1.5 RFC7519#section-4.1.5}
39+
*/
40+
nbf?: number;
41+
42+
/**
43+
* JWT Expiration Time
44+
*
45+
* @see {@link https://www.rfc-editor.org/rfc/rfc7519#section-4.1.4 RFC7519#section-4.1.4}
46+
*/
47+
exp?: number;
48+
49+
/**
50+
* JWT Issued At
51+
*
52+
* @see {@link https://www.rfc-editor.org/rfc/rfc7519#section-4.1.6 RFC7519#section-4.1.6}
53+
*/
54+
iat?: number;
55+
56+
/** Any other JWT Claim Set member. */
57+
[propName: string]: unknown;
58+
}
59+
60+
export type JWTAlgorithm =
61+
| 'HS256'
62+
| 'HS384'
63+
| 'HS512'
64+
| 'RS256'
65+
| 'RS384'
66+
| 'RS512'
67+
| 'ES256'
68+
| 'ES384'
69+
| 'ES512'
70+
| 'PS256'
71+
| 'PS384'
72+
| 'PS512';
73+
74+
export interface JWTOptions {
75+
alg: JWTAlgorithm;
76+
typ?: 'JWT';
77+
exp: number; // in seconds
78+
}
79+
80+
export const jwtAdapter = authAdapter(
81+
({ secret, library = 'jose' }: { secret: string; library?: 'jsonwebtoken' | 'jose' }) => ({
82+
name: 'jwt',
83+
methods: {
84+
sign: (payload: JWTPayload, options: JWTOptions) =>
85+
sign({
86+
secret,
87+
library,
88+
payload,
89+
options
90+
}),
91+
verify: (token: string, options: object) =>
92+
verify({
93+
secret,
94+
library,
95+
token,
96+
options
97+
})
98+
}
99+
})
100+
);

libs/jwt-adapter/src/funcs/sign.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import * as jose from 'jose';
2+
import * as jwt from 'jsonwebtoken';
3+
4+
import type { JWTOptions, JWTPayload } from '../adapter';
5+
6+
export async function sign({
7+
secret,
8+
library,
9+
payload,
10+
options
11+
}: {
12+
secret: string;
13+
library: 'jsonwebtoken' | 'jose';
14+
payload: JWTPayload;
15+
options: JWTOptions;
16+
}): Promise<string> {
17+
switch (library) {
18+
case 'jsonwebtoken': {
19+
const jwtOptions: jwt.SignOptions = {
20+
algorithm: options.alg as jwt.Algorithm,
21+
header: { alg: options.alg, typ: options.typ }
22+
};
23+
if (options.exp) {
24+
jwtOptions.expiresIn = options.exp;
25+
}
26+
27+
return new Promise((resolve, reject) => {
28+
jwt.sign(payload, secret, jwtOptions, (err, token) => {
29+
if (err) {
30+
reject(err);
31+
} else {
32+
resolve(token as string);
33+
}
34+
});
35+
});
36+
}
37+
38+
case 'jose': {
39+
const encoder = new TextEncoder();
40+
const jwtJose = await new jose.SignJWT(payload)
41+
.setProtectedHeader({ alg: options.alg, typ: options.typ })
42+
.setIssuedAt()
43+
.setExpirationTime(options.exp)
44+
.sign(encoder.encode(secret));
45+
return jwtJose;
46+
}
47+
default:
48+
throw new Error(`unsupported jwt library: ${library}`);
49+
}
50+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import * as jose from 'jose';
2+
import * as jwt from 'jsonwebtoken';
3+
4+
export async function verify({
5+
secret,
6+
library,
7+
token,
8+
options
9+
}: {
10+
secret: string;
11+
library: 'jsonwebtoken' | 'jose';
12+
token: string;
13+
options: object;
14+
}) {
15+
switch (library) {
16+
case 'jsonwebtoken':
17+
return new Promise((resolve, reject) => {
18+
jwt.verify(token, secret, options, (err, decoded) => {
19+
if (err) {
20+
reject(err);
21+
} else {
22+
resolve(decoded);
23+
}
24+
});
25+
});
26+
case 'jose':
27+
try {
28+
const { payload } = await jose.jwtVerify(token, new TextEncoder().encode(secret), options);
29+
return Promise.resolve(payload);
30+
} catch (error) {
31+
return Promise.reject(error);
32+
}
33+
default:
34+
throw new Error(`Unsupported JWT library: ${library}`);
35+
}
36+
}

libs/jwt-adapter/src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { jwtAdapter } from './adapter';
2+
3+
export { JWTAlgorithm, JWTOptions, JWTPayload } from './adapter';
4+
export { sign } from './funcs/sign';
5+
export { verify } from './funcs/verify';
6+
7+
export { jwtAdapter };
8+
export { jwtAdapter as default };

libs/jwt-adapter/tsconfig.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"compilerOptions": {
4+
"declaration": true,
5+
"noErrorTruncation": false,
6+
"outDir": "./dist",
7+
"types": ["node"]
8+
},
9+
"include": ["src/**/*"],
10+
"exclude": ["**/*.spec.ts"]
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"extends": "../../build/tsconfig.types.json",
3+
"compilerOptions": {
4+
"declaration": true, /* Generate d.ts files */
5+
"emitDeclarationOnly": true,
6+
"module": "system",
7+
"rootDir": "./src",
8+
"outDir": "./dist/src",
9+
"types": ["node"]
10+
},
11+
"include": ["src/**/*"]
12+
}

0 commit comments

Comments
 (0)