@dws-std/jwt 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dominus Web Services (DWS)
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.
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # 🔐 DWS JWT
2
+
3
+ Signing and verifying JWTs shouldn't require boilerplate.
4
+ `@dws-std/jwt` wraps [jose](https://github.com/panva/jose) with sane defaults — HS256, standard claims pre-filled, human-readable expiration - so you can focus on what matters instead of re-reading the JWT spec.
5
+
6
+ ## 📌 Table of Contents
7
+
8
+ - [Installation](#-installation)
9
+ - [Usage](#-usage)
10
+ - [signJWT](#signjwt)
11
+ - [verifyJWT](#verifyjwt)
12
+ - [Error handling](#-error-handling)
13
+ - [License](#-license)
14
+
15
+ ## 🔧 Installation
16
+
17
+ ```bash
18
+ bun add @dws-std/jwt
19
+ ```
20
+
21
+ ## ⚙️ Usage
22
+
23
+ ### `signJWT`
24
+
25
+ Signs a payload and returns a JWT string.
26
+ All standard claims (`iss`, `sub`, `aud`, `jti`, `nbf`, `iat`, `exp`) are included automatically - just pass your custom data and let the function handle the rest.
27
+
28
+ ```ts
29
+ import { signJWT } from '@dws-std/jwt';
30
+
31
+ // Default expiration: 15 minutes
32
+ const token = await signJWT(secret, { userId: 42, role: 'admin' });
33
+
34
+ // Numeric offset in seconds
35
+ const token = await signJWT(secret, { userId: 42 }, 3600);
36
+
37
+ // Date object
38
+ const token = await signJWT(secret, { userId: 42 }, new Date(Date.now() + 3600_000));
39
+
40
+ // Human-readable — powered by @dws-std/common
41
+ const token = await signJWT(secret, { userId: 42 }, '1 hour');
42
+ const token = await signJWT(secret, { userId: 42 }, '30 minutes');
43
+ const token = await signJWT(secret, { userId: 42 }, '7 days');
44
+ ```
45
+
46
+ The secret must be at least 32 characters long (HS256 requirement). Any shorter and it throws immediately — better to catch it at startup than in production.
47
+
48
+ Default claims can be overridden by providing them in the payload:
49
+
50
+ ```ts
51
+ const token = await signJWT(secret, {
52
+ iss: 'my-service',
53
+ sub: 'user-123',
54
+ aud: ['my-api'],
55
+ userId: 42
56
+ });
57
+ ```
58
+
59
+ ### `verifyJWT`
60
+
61
+ Verifies a token and returns the decoded payload. Throws if anything is wrong.
62
+
63
+ ```ts
64
+ import { verifyJWT } from '@dws-std/jwt';
65
+
66
+ const { payload } = await verifyJWT(token, secret);
67
+ console.log(payload.userId);
68
+ ```
69
+
70
+ Optionally validate `iss` and `aud` claims:
71
+
72
+ ```ts
73
+ const { payload } = await verifyJWT(token, secret, {
74
+ issuer: 'my-service',
75
+ audience: 'my-api'
76
+ });
77
+ ```
78
+
79
+ ## 🚨 Error handling
80
+
81
+ All errors are `Exception` instances from `@dws-std/error` with a `key` you can check:
82
+
83
+ | Key | When |
84
+ | ----------------------- | --------------------------------------------------------------- |
85
+ | `jwt.secret_too_weak` | Secret is shorter than 32 characters |
86
+ | `jwt.expiration_passed` | Expiration is in the past or equals now |
87
+ | `jwt.sign_error` | jose failed to sign the token |
88
+ | `jwt.token_expired` | Token is valid but past its expiry date |
89
+ | `jwt.unauthorized` | Invalid signature, malformed token, or claim validation failure |
90
+
91
+ ```ts
92
+ import { Exception } from '@dws-std/error';
93
+ import { JWT_ERROR_KEYS, verifyJWT } from '@dws-std/jwt';
94
+
95
+ try {
96
+ const { payload } = await verifyJWT(token, secret);
97
+ } catch (error) {
98
+ if (error instanceof Exception) {
99
+ if (error.key === JWT_ERROR_KEYS.JWT_EXPIRED) {
100
+ // Token expired — trigger a refresh
101
+ }
102
+ // Everything else is unauthorized
103
+ }
104
+ }
105
+ ```
106
+
107
+ ## ⚖️ License
108
+
109
+ MIT - Feel free to use it.
@@ -0,0 +1,7 @@
1
+ export declare const JWT_ERROR_KEYS: {
2
+ readonly JWT_SECRET_TOO_WEAK: "jwt.secret_too_weak";
3
+ readonly JWT_EXPIRATION_PASSED: "jwt.expiration_passed";
4
+ readonly JWT_SIGN_ERROR: "jwt.sign_error";
5
+ readonly JWT_EXPIRED: "jwt.token_expired";
6
+ readonly JWT_UNAUTHORIZED: "jwt.unauthorized";
7
+ };
@@ -0,0 +1,2 @@
1
+ export { JWT_ERROR_KEYS } from './constant/jwt-error-keys';
2
+ export { signJWT, verifyJWT, type VerifyOptions } from './jwt';
package/dist/index.js ADDED
@@ -0,0 +1,68 @@
1
+ // @bun
2
+ // src/constant/jwt-error-keys.ts
3
+ var JWT_ERROR_KEYS = {
4
+ JWT_SECRET_TOO_WEAK: "jwt.secret_too_weak",
5
+ JWT_EXPIRATION_PASSED: "jwt.expiration_passed",
6
+ JWT_SIGN_ERROR: "jwt.sign_error",
7
+ JWT_EXPIRED: "jwt.token_expired",
8
+ JWT_UNAUTHORIZED: "jwt.unauthorized"
9
+ };
10
+ // src/jwt.ts
11
+ import { parseHumanTimeToSeconds } from "@dws-std/common";
12
+ import { Exception } from "@dws-std/error";
13
+ import { SignJWT, errors, jwtVerify } from "jose";
14
+ var _textEncoder = new TextEncoder;
15
+ var signJWT = (secret, payload, expiration = 60 * 15) => {
16
+ if (secret.length < 32)
17
+ throw new Exception("Secret key must be at least 32 characters long", {
18
+ key: JWT_ERROR_KEYS.JWT_SECRET_TOO_WEAK,
19
+ cause: { providedLength: secret.length }
20
+ });
21
+ const nowSeconds = Math.floor(Date.now() / 1000);
22
+ const exp = expiration instanceof Date ? Math.floor(expiration.getTime() / 1000) : typeof expiration === "number" ? nowSeconds + expiration : nowSeconds + parseHumanTimeToSeconds(expiration);
23
+ if (exp <= nowSeconds)
24
+ throw new Exception("Expiration time must be in the future", {
25
+ key: JWT_ERROR_KEYS.JWT_EXPIRATION_PASSED,
26
+ cause: { providedExpiration: expiration }
27
+ });
28
+ const finalPayload = {
29
+ iss: "DWS-Issuer",
30
+ sub: "",
31
+ aud: ["DWS-Audience"],
32
+ jti: Bun.randomUUIDv7(),
33
+ nbf: nowSeconds,
34
+ iat: nowSeconds,
35
+ exp,
36
+ ...payload
37
+ };
38
+ try {
39
+ return new SignJWT(finalPayload).setProtectedHeader({ alg: "HS256", typ: "JWT" }).sign(_textEncoder.encode(secret));
40
+ } catch (error) {
41
+ throw new Exception("Failed to sign JWT", {
42
+ key: JWT_ERROR_KEYS.JWT_SIGN_ERROR,
43
+ cause: error
44
+ });
45
+ }
46
+ };
47
+ var verifyJWT = async (token, secret, options) => {
48
+ try {
49
+ return await jwtVerify(token, _textEncoder.encode(secret), {
50
+ algorithms: ["HS256"],
51
+ ...options?.issuer && { issuer: options.issuer },
52
+ ...options?.audience && { audience: options.audience }
53
+ });
54
+ } catch (error) {
55
+ if (error instanceof errors.JWTExpired)
56
+ throw new Exception("JWT token has expired", {
57
+ key: JWT_ERROR_KEYS.JWT_EXPIRED
58
+ });
59
+ throw new Exception("Unauthorized", {
60
+ key: JWT_ERROR_KEYS.JWT_UNAUTHORIZED
61
+ });
62
+ }
63
+ };
64
+ export {
65
+ verifyJWT,
66
+ signJWT,
67
+ JWT_ERROR_KEYS
68
+ };
package/dist/jwt.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { type JWTPayload, type JWTVerifyResult } from 'jose';
2
+ export interface VerifyOptions {
3
+ issuer?: string;
4
+ audience?: string | string[];
5
+ }
6
+ /**
7
+ * Signs a JWT with the given payload and expiration
8
+ *
9
+ * @param secret - The secret key used for HS256 signing (minimum 32 characters)
10
+ * @param payload - The JWT payload claims
11
+ * @param expiration - Token expiration as seconds offset, Date, or human-readable string (default: 15 minutes)
12
+ *
13
+ * @throws ({@link Exception}) – If secret is too short, expiration is in the past, or signing fails
14
+ *
15
+ * @returns A Promise resolving to the signed JWT string
16
+ */
17
+ export declare const signJWT: (secret: string, payload: JWTPayload, expiration?: number | string | Date) => Promise<string>;
18
+ /**
19
+ * Verifies a JWT token and throws Exception on failure
20
+ *
21
+ * @param token - The JWT token string to verify
22
+ * @param secret - The secret key used for HS256 verification
23
+ * @param options - Optional verification options for issuer/audience validation
24
+ *
25
+ * @throws ({@link Exception}) – 401 if token is expired, invalid signature, malformed, or claim validation fails
26
+ *
27
+ * @returns The verification result with payload and protected header
28
+ */
29
+ export declare const verifyJWT: (token: string, secret: string, options?: VerifyOptions) => Promise<JWTVerifyResult>;
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@dws-std/jwt",
3
+ "version": "1.0.0",
4
+ "description": "JWT utilities for Dominus Web Services (DWS) projects.",
5
+ "keywords": [
6
+ "bun",
7
+ "dws",
8
+ "jwt",
9
+ "types",
10
+ "utilities"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "Dominus Web Services (DWS)",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/Dominus-Web-Service/std",
17
+ "directory": "packages/common"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "type": "module",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.js"
27
+ }
28
+ },
29
+ "scripts": {
30
+ "build": "bun builder.ts",
31
+ "docs": "bunx typedoc --tsconfig tsconfig.build.json",
32
+ "fmt:check": "oxfmt --check",
33
+ "fmt": "oxfmt",
34
+ "lint:fix": "oxlint --type-aware --type-check --fix ./src",
35
+ "lint:github": "oxlint --type-aware --type-check --format=github ./src",
36
+ "lint": "oxlint --type-aware --type-check ./src",
37
+ "test": "bun test --pass-with-no-tests --coverage"
38
+ },
39
+ "dependencies": {
40
+ "@dws-std/common": "^1.0.0",
41
+ "@dws-std/error": "^2.1.0",
42
+ "jose": "^6.2.2"
43
+ },
44
+ "devDependencies": {
45
+ "@types/bun": "^1.3.11",
46
+ "oxfmt": "0.41.0",
47
+ "oxlint": "1.56.0",
48
+ "oxlint-tsgolint": "0.17.0",
49
+ "typescript": "^5.9.3"
50
+ },
51
+ "peerDependencies": {
52
+ "@dws-std/common": "^1.0.0",
53
+ "@dws-std/error": "^2.1.0"
54
+ }
55
+ }