@molecule/api-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 ADDED
@@ -0,0 +1,115 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work.
38
+
39
+ "Derivative Works" shall mean any work, whether in Source or Object
40
+ form, that is based on (or derived from) the Work and for which the
41
+ editorial revisions, annotations, elaborations, or other modifications
42
+ represent, as a whole, an original work of authorship.
43
+
44
+ "Contribution" shall mean any work of authorship, including the
45
+ original version of the Work and any modifications or additions
46
+ to that Work, that is intentionally submitted to the Licensor for
47
+ inclusion in the Work by the copyright owner or by an individual or
48
+ Legal Entity authorized to submit on behalf of the copyright owner.
49
+
50
+ "Contributor" shall mean Licensor and any individual or Legal Entity
51
+ on behalf of whom a Contribution has been received by the Licensor and
52
+ subsequently incorporated within the Work.
53
+
54
+ 2. Grant of Copyright License. Subject to the terms and conditions of
55
+ this License, each Contributor hereby grants to You a perpetual,
56
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
57
+ copyright license to reproduce, prepare Derivative Works of,
58
+ publicly display, publicly perform, sublicense, and distribute the
59
+ Work and such Derivative Works in Source or Object form.
60
+
61
+ 3. Grant of Patent License. Subject to the terms and conditions of
62
+ this License, each Contributor hereby grants to You a perpetual,
63
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
64
+ patent license to make, have made, use, offer to sell, sell, import,
65
+ and otherwise transfer the Work.
66
+
67
+ 4. Redistribution. You may reproduce and distribute copies of the
68
+ Work or Derivative Works thereof in any medium, with or without
69
+ modifications, and in Source or Object form, provided that You
70
+ meet the following conditions:
71
+
72
+ (a) You must give any other recipients of the Work or
73
+ Derivative Works a copy of this License; and
74
+
75
+ (b) You must cause any modified files to carry prominent notices
76
+ stating that You changed the files; and
77
+
78
+ (c) You must retain, in the Source form of any Derivative Works
79
+ that You distribute, all copyright, patent, trademark, and
80
+ attribution notices from the Source form of the Work,
81
+ excluding those notices that do not pertain to any part of
82
+ the Derivative Works; and
83
+
84
+ (d) If the Work includes a "NOTICE" text file as part of its
85
+ distribution, then any Derivative Works that You distribute must
86
+ include a readable copy of the attribution notices contained
87
+ within such NOTICE file.
88
+
89
+ 5. Submission of Contributions.
90
+
91
+ 6. Trademarks. This License does not grant permission to use the trade
92
+ names, trademarks, service marks, or product names of the Licensor.
93
+
94
+ 7. Disclaimer of Warranty. Unless required by applicable law or
95
+ agreed to in writing, Licensor provides the Work on an "AS IS" BASIS,
96
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND.
97
+
98
+ 8. Limitation of Liability. In no event and under no legal theory shall
99
+ any Contributor be liable to You for damages.
100
+
101
+ 9. Accepting Warranty or Additional Liability.
102
+
103
+ Copyright 2026 Molecule Dev, Inc.
104
+
105
+ Licensed under the Apache License, Version 2.0 (the "License");
106
+ you may not use this file except in compliance with the License.
107
+ You may obtain a copy of the License at
108
+
109
+ http://www.apache.org/licenses/LICENSE-2.0
110
+
111
+ Unless required by applicable law or agreed to in writing, software
112
+ distributed under the License is distributed on an "AS IS" BASIS,
113
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
114
+ See the License for the specific language governing permissions and
115
+ limitations under the License.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=browser-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-guard.d.ts","sourceRoot":"","sources":["../src/browser-guard.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,CAAA"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Browser guard — `@molecule/api-jwt` is SERVER-ONLY.
3
+ *
4
+ * Generated by scripts/gen-browser-guards.mjs (workspace root) — edit THAT, not this.
5
+ * Evaluating a server package in a browser bundle is always an import-graph mistake
6
+ * (node APIs, secrets); without this guard it surfaces as a cryptic downstream crash
7
+ * ("Buffer is not defined") far from the culprit. Throwing here names the package and
8
+ * the fix at the exact moment the client bundle evaluates it. jsdom tests and SSR are
9
+ * unaffected: the throw requires browser globals AND the absence of a node runtime.
10
+ */
11
+ const g = globalThis;
12
+ if (g.window !== undefined && g.document !== undefined && !g.process?.versions?.node) {
13
+ throw new Error('@molecule/api-jwt is SERVER-ONLY: it was bundled into browser/client code. Import it only ' +
14
+ 'from server code (a server route/function or your API), or dynamic-import it inside ' +
15
+ 'the server handler — never from components or shared client modules, and never ' +
16
+ 'polyfill Buffer/process to silence this.');
17
+ }
18
+ export {};
19
+ //# sourceMappingURL=browser-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-guard.js","sourceRoot":"","sources":["../src/browser-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,GAAG,UAIT,CAAA;AACD,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACrF,MAAM,IAAI,KAAK,CACb,4FAA4F;QAC1F,sFAAsF;QACtF,iFAAiF;QACjF,0CAA0C,CAC7C,CAAA;AACH,CAAC"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * JWT interface for molecule.dev.
3
+ *
4
+ * Provides an abstract JWT interface that can be backed by any JWT library.
5
+ * Use `setProvider` to provide a concrete implementation
6
+ * such as `@molecule/api-jwt-jsonwebtoken`.
7
+ *
8
+ * @remarks
9
+ * **{@link verify} is the ONLY way to trust a token — {@link decode} does NOT check the
10
+ * signature.** Never make an auth decision from `decode()`: an attacker can forge any
11
+ * payload that `decode()` will happily return. Use `verify()` (it throws — catch it) for
12
+ * anything security-relevant; `decode()` is only for reading a token you do NOT trust.
13
+ *
14
+ * - The signing key (private key / secret) is SERVER-SIDE only — never ship it to the
15
+ * browser. Only an asymmetric PUBLIC key may be published.
16
+ * - A JWT payload is READABLE by anyone (base64, not encrypted) — never put a password,
17
+ * secret, or sensitive PII in it.
18
+ * - Always set + honor expiry ({@link JWT_EXPIRES_TIME}); a non-expiring token can't be
19
+ * revoked.
20
+ * - In a molecule app auth is ALREADY wired: the global `verifyMiddleware` verifies the JWT
21
+ * and populates `res.locals.session`, so a handler calls `getUserId(res)` — do NOT call
22
+ * `verify()`/`sign()` by hand for the session (see the `auth` skill). Use these directly
23
+ * only for a CUSTOM token, e.g. a signed email/reset link.
24
+ * - **Re-signing decoded claims (refresh flows): strip `exp`/`iat` first.** `sign()`
25
+ * always sets `expiresIn` (default {@link JWT_EXPIRES_TIME}), and the underlying library
26
+ * throws (`Bad "options.expiresIn" option the payload already has an "exp" property`)
27
+ * when the payload still carries the old `exp` — so `const { exp, iat, ...claims } =
28
+ * verify(oldToken) as JwtPayload; sign(claims)` is the correct refresh shape.
29
+ * - Set `JWT_PRIVATE_KEY` and `JWT_PUBLIC_KEY` together (or neither). If only the private
30
+ * key is set, the matching public key is DERIVED from it automatically; setting only the
31
+ * public key is for verify-only deployments.
32
+ * - When neither key env var is set, a key pair is auto-generated on disk at
33
+ * `{JWT_KEYS_DIR}/{NODE_ENV}/` — default `JWT_KEYS_DIR`: `process.cwd() + '/.keys'`, a
34
+ * stable app-level directory (NOT inside `node_modules`, so `npm ci`/reinstall never
35
+ * wipes it). Set `JWT_KEYS_DIR` to relocate it (e.g. a persistent volume in production).
36
+ * A pre-existing pair at the legacy `node_modules`-relative location is migrated forward
37
+ * automatically (with a logged warning) instead of being silently regenerated.
38
+ * - `JWT_ALGORITHM` (default `RS256`) is validated at module load against the
39
+ * {@link JwtAlgorithm} union; an unrecognized value (e.g. a typo like `rs256`) logs an
40
+ * actionable warning and falls back to `RS256` instead of failing every `sign()`/`verify()`
41
+ * call later with an opaque "invalid algorithm" error.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * import { sign, verify, decode } from '@molecule/api-jwt'
46
+ *
47
+ * const token = sign({ userId }, { expiresIn: '15m' }) // server-side; expiry set
48
+ *
49
+ * try {
50
+ * const claims = verify(token) as JwtPayload // signature CHECKED — safe to trust
51
+ * grantAccess(claims.userId)
52
+ * } catch {
53
+ * res.status(401).json({ error: 'Invalid or expired token.' })
54
+ * }
55
+ *
56
+ * decode(token) // NOT verified — never use its output for an auth decision
57
+ * ```
58
+ *
59
+ * @module
60
+ */
61
+ export * from './browser-guard.js';
62
+ export * from './keys.js';
63
+ export * from './provider.js';
64
+ export * from './types.js';
65
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AAEH,cAAc,oBAAoB,CAAA;AAClC,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * JWT interface for molecule.dev.
3
+ *
4
+ * Provides an abstract JWT interface that can be backed by any JWT library.
5
+ * Use `setProvider` to provide a concrete implementation
6
+ * such as `@molecule/api-jwt-jsonwebtoken`.
7
+ *
8
+ * @remarks
9
+ * **{@link verify} is the ONLY way to trust a token — {@link decode} does NOT check the
10
+ * signature.** Never make an auth decision from `decode()`: an attacker can forge any
11
+ * payload that `decode()` will happily return. Use `verify()` (it throws — catch it) for
12
+ * anything security-relevant; `decode()` is only for reading a token you do NOT trust.
13
+ *
14
+ * - The signing key (private key / secret) is SERVER-SIDE only — never ship it to the
15
+ * browser. Only an asymmetric PUBLIC key may be published.
16
+ * - A JWT payload is READABLE by anyone (base64, not encrypted) — never put a password,
17
+ * secret, or sensitive PII in it.
18
+ * - Always set + honor expiry ({@link JWT_EXPIRES_TIME}); a non-expiring token can't be
19
+ * revoked.
20
+ * - In a molecule app auth is ALREADY wired: the global `verifyMiddleware` verifies the JWT
21
+ * and populates `res.locals.session`, so a handler calls `getUserId(res)` — do NOT call
22
+ * `verify()`/`sign()` by hand for the session (see the `auth` skill). Use these directly
23
+ * only for a CUSTOM token, e.g. a signed email/reset link.
24
+ * - **Re-signing decoded claims (refresh flows): strip `exp`/`iat` first.** `sign()`
25
+ * always sets `expiresIn` (default {@link JWT_EXPIRES_TIME}), and the underlying library
26
+ * throws (`Bad "options.expiresIn" option the payload already has an "exp" property`)
27
+ * when the payload still carries the old `exp` — so `const { exp, iat, ...claims } =
28
+ * verify(oldToken) as JwtPayload; sign(claims)` is the correct refresh shape.
29
+ * - Set `JWT_PRIVATE_KEY` and `JWT_PUBLIC_KEY` together (or neither). If only the private
30
+ * key is set, the matching public key is DERIVED from it automatically; setting only the
31
+ * public key is for verify-only deployments.
32
+ * - When neither key env var is set, a key pair is auto-generated on disk at
33
+ * `{JWT_KEYS_DIR}/{NODE_ENV}/` — default `JWT_KEYS_DIR`: `process.cwd() + '/.keys'`, a
34
+ * stable app-level directory (NOT inside `node_modules`, so `npm ci`/reinstall never
35
+ * wipes it). Set `JWT_KEYS_DIR` to relocate it (e.g. a persistent volume in production).
36
+ * A pre-existing pair at the legacy `node_modules`-relative location is migrated forward
37
+ * automatically (with a logged warning) instead of being silently regenerated.
38
+ * - `JWT_ALGORITHM` (default `RS256`) is validated at module load against the
39
+ * {@link JwtAlgorithm} union; an unrecognized value (e.g. a typo like `rs256`) logs an
40
+ * actionable warning and falls back to `RS256` instead of failing every `sign()`/`verify()`
41
+ * call later with an opaque "invalid algorithm" error.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * import { sign, verify, decode } from '@molecule/api-jwt'
46
+ *
47
+ * const token = sign({ userId }, { expiresIn: '15m' }) // server-side; expiry set
48
+ *
49
+ * try {
50
+ * const claims = verify(token) as JwtPayload // signature CHECKED — safe to trust
51
+ * grantAccess(claims.userId)
52
+ * } catch {
53
+ * res.status(401).json({ error: 'Invalid or expired token.' })
54
+ * }
55
+ *
56
+ * decode(token) // NOT verified — never use its output for an auth decision
57
+ * ```
58
+ *
59
+ * @module
60
+ */
61
+ export * from './browser-guard.js';
62
+ export * from './keys.js';
63
+ export * from './provider.js';
64
+ export * from './types.js';
65
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AAEH,cAAc,oBAAoB,CAAA;AAClC,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA"}
package/dist/keys.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * RSA key pair generation and loading for JWT signing/verification.
3
+ *
4
+ * Keys are loaded from environment variables (`JWT_PRIVATE_KEY`, `JWT_PUBLIC_KEY`)
5
+ * or from PEM files on disk at `{JWT_KEYS_DIR}/{NODE_ENV}/` (default
6
+ * `JWT_KEYS_DIR`: `process.cwd() + '/.keys'` — a stable, app-level directory,
7
+ * NOT inside `node_modules`). If no keys exist, a new RSA-2048 key pair is
8
+ * generated automatically on first import.
9
+ *
10
+ * @module
11
+ */
12
+ /**
13
+ * Generates an RSA-2048 key pair in PEM format for JWT signing and verification.
14
+ *
15
+ * @returns An object containing `publicKey` and `privateKey` as PEM strings.
16
+ */
17
+ export declare const generateKeyPairSync: () => {
18
+ publicKey: string;
19
+ privateKey: string;
20
+ };
21
+ /**
22
+ * Writes a freshly generated RSA key pair to disk as PEM files. Creates
23
+ * the output directory if it does not exist.
24
+ *
25
+ * @param outputPath - Directory to write the PEM files into; defaults to `{JWT_KEYS_DIR}/{NODE_ENV}/`.
26
+ */
27
+ export declare const writeKeys: (outputPath?: string) => void;
28
+ /**
29
+ * The RSA private key for signing JWTs. Read from the `JWT_PRIVATE_KEY`
30
+ * environment variable, or loaded from the PEM file on disk.
31
+ *
32
+ * Throws at startup if neither source provides a key — running with an
33
+ * empty secret would allow anyone to forge valid JWTs.
34
+ */
35
+ export declare const JWT_PRIVATE_KEY: string | Buffer<ArrayBufferLike>;
36
+ /**
37
+ * The RSA public key for verifying JWTs. Read from the `JWT_PUBLIC_KEY`
38
+ * environment variable; when only `JWT_PRIVATE_KEY` is set, the matching
39
+ * public key is DERIVED from it (a disk fallback could not match an
40
+ * env-provided private key and would make every signed token fail
41
+ * verification); otherwise loaded from the PEM file on disk.
42
+ *
43
+ * Throws at startup if no source provides a key.
44
+ */
45
+ export declare const JWT_PUBLIC_KEY: string | Buffer<ArrayBufferLike>;
46
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAoCH;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,QAAO;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAW1E,CAAA;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GAAI,mBAAqB,KAAG,IAejD,CAAA;AA4GD;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,kCAOtB,CAAA;AAEN;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,kCAQrB,CAAA"}
package/dist/keys.js ADDED
@@ -0,0 +1,198 @@
1
+ /**
2
+ * RSA key pair generation and loading for JWT signing/verification.
3
+ *
4
+ * Keys are loaded from environment variables (`JWT_PRIVATE_KEY`, `JWT_PUBLIC_KEY`)
5
+ * or from PEM files on disk at `{JWT_KEYS_DIR}/{NODE_ENV}/` (default
6
+ * `JWT_KEYS_DIR`: `process.cwd() + '/.keys'` — a stable, app-level directory,
7
+ * NOT inside `node_modules`). If no keys exist, a new RSA-2048 key pair is
8
+ * generated automatically on first import.
9
+ *
10
+ * @module
11
+ */
12
+ import crypto from 'crypto';
13
+ import fs from 'fs';
14
+ import path from 'path';
15
+ import process from 'process';
16
+ import { fileURLToPath } from 'url';
17
+ import { getLogger } from '@molecule/api-bond';
18
+ const logger = getLogger();
19
+ const __dirname = fileURLToPath(path.dirname(import.meta.url));
20
+ const nodeEnv = process.env.NODE_ENV || 'development';
21
+ // Stable, app-level default: `<app root>/.keys/{NODE_ENV}`. Explicitly
22
+ // overridable via `JWT_KEYS_DIR` (absolute, or relative to `process.cwd()`)
23
+ // for deployments that want keys on a persistent volume outside the app
24
+ // checkout.
25
+ const keysDir = process.env.JWT_KEYS_DIR
26
+ ? path.resolve(process.cwd(), process.env.JWT_KEYS_DIR)
27
+ : path.join(process.cwd(), `.keys`);
28
+ const keysPath = path.join(keysDir, nodeEnv);
29
+ const privateKeyPath = `${keysPath}/jwt_private_key.pem`;
30
+ const publicKeyPath = `${keysPath}/jwt_public_key.pem`;
31
+ // Legacy default location (one level ABOVE this package root — inside
32
+ // `node_modules` for an installed app). Reinstalling/`npm ci` wipes
33
+ // `node_modules`, so any key pair generated there is silently lost,
34
+ // invalidating every session and signed link (email verification/reset)
35
+ // on the next boot. Kept ONLY to migrate a pre-existing pair forward to the
36
+ // new stable location below — never written to again.
37
+ const legacyKeysPath = path.join(__dirname, `../../.keys/${nodeEnv}`);
38
+ const legacyPrivateKeyPath = `${legacyKeysPath}/jwt_private_key.pem`;
39
+ const legacyPublicKeyPath = `${legacyKeysPath}/jwt_public_key.pem`;
40
+ /**
41
+ * Generates an RSA-2048 key pair in PEM format for JWT signing and verification.
42
+ *
43
+ * @returns An object containing `publicKey` and `privateKey` as PEM strings.
44
+ */
45
+ export const generateKeyPairSync = () => crypto.generateKeyPairSync(`rsa`, {
46
+ modulusLength: 2048,
47
+ publicKeyEncoding: {
48
+ type: `spki`,
49
+ format: `pem`,
50
+ },
51
+ privateKeyEncoding: {
52
+ type: `pkcs8`,
53
+ format: `pem`,
54
+ },
55
+ });
56
+ /**
57
+ * Writes a freshly generated RSA key pair to disk as PEM files. Creates
58
+ * the output directory if it does not exist.
59
+ *
60
+ * @param outputPath - Directory to write the PEM files into; defaults to `{JWT_KEYS_DIR}/{NODE_ENV}/`.
61
+ */
62
+ export const writeKeys = (outputPath = keysPath) => {
63
+ try {
64
+ const { publicKey, privateKey } = generateKeyPairSync();
65
+ if (!fs.existsSync(outputPath)) {
66
+ fs.mkdirSync(outputPath, { recursive: true });
67
+ }
68
+ fs.writeFileSync(path.join(outputPath, 'jwt_private_key.pem'), privateKey, `utf8`);
69
+ fs.writeFileSync(path.join(outputPath, 'jwt_public_key.pem'), publicKey, `utf8`);
70
+ logger.info(`JWT key pair successfully written to disk.`);
71
+ }
72
+ catch (error) {
73
+ logger.error(`Error writing JWT key pair:`, error);
74
+ }
75
+ };
76
+ // Ensure key files exist ONLY when the private key must come from disk.
77
+ // When `JWT_PRIVATE_KEY` is provided via env, generating a fresh on-disk pair
78
+ // here would be worse than useless: with `JWT_PUBLIC_KEY` unset, the public
79
+ // key would then be read from the freshly generated file — a key that does
80
+ // NOT match the env private key — and every token this process signs would
81
+ // fail verification with an unexplained "invalid signature". (The public key
82
+ // for an env-provided private key is DERIVED from it below instead.)
83
+ if (!process.env.JWT_PRIVATE_KEY) {
84
+ if (!fs.existsSync(privateKeyPath) || !fs.existsSync(publicKeyPath)) {
85
+ // One-release migration: a pair generated by a PRE-fix version of this
86
+ // package lives at the legacy `node_modules`-relative path. Copy it
87
+ // forward to the new stable location instead of silently generating a
88
+ // brand-new pair — regenerating here would invalidate every existing
89
+ // session and signed link (email verification/reset) for a deployment
90
+ // that already has working keys. Once copied, this branch never runs
91
+ // again (the new location is populated).
92
+ if (fs.existsSync(legacyPrivateKeyPath) && fs.existsSync(legacyPublicKeyPath)) {
93
+ try {
94
+ fs.mkdirSync(keysPath, { recursive: true });
95
+ fs.copyFileSync(legacyPrivateKeyPath, privateKeyPath);
96
+ fs.copyFileSync(legacyPublicKeyPath, publicKeyPath);
97
+ logger.warn(`Migrated JWT keys from the legacy node_modules-relative location (${legacyKeysPath}) ` +
98
+ `to the stable location ${keysPath}. The legacy location is wiped by every ` +
99
+ `reinstall/npm ci — set JWT_PRIVATE_KEY/JWT_PUBLIC_KEY (recommended) or JWT_KEYS_DIR ` +
100
+ `explicitly to control where keys live going forward.`);
101
+ }
102
+ catch (error) {
103
+ logger.error(`Failed to migrate JWT keys from the legacy location (${legacyKeysPath}) to ` +
104
+ `${keysPath} — generating a fresh pair instead. This INVALIDATES every existing ` +
105
+ `session and signed link.`, error);
106
+ writeKeys();
107
+ }
108
+ }
109
+ else {
110
+ writeKeys();
111
+ }
112
+ }
113
+ }
114
+ /**
115
+ * Convert PEM read from a process.env value back into a real PEM string.
116
+ *
117
+ * Node's `--env-file` (and many other dotenv readers) deliberately do NOT
118
+ * interpret backslash-escapes inside quoted values, so a `.env` line like
119
+ * JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END..."
120
+ * arrives in `process.env` as a string containing literal `\n` characters
121
+ * (backslash + n) instead of newlines. Crypto libraries like `jsonwebtoken`
122
+ * then reject the value with "secretOrPrivateKey must be an asymmetric key
123
+ * when using RS256" because the OpenSSL parser can't find PEM headers
124
+ * separated by real newlines.
125
+ *
126
+ * Normalize here so any consumer of this module gets a usable PEM
127
+ * regardless of how the key was stored in the env source.
128
+ */
129
+ const normalizePem = (value) => {
130
+ if (typeof value !== 'string')
131
+ return value;
132
+ // Only rewrite if the literal escape is present and there are no real
133
+ // newlines yet — preserves keys that arrived correctly (multi-line .env
134
+ // values, files on disk, etc.).
135
+ if (value.includes('\\n') && !value.includes('\n')) {
136
+ return value.replace(/\\n/g, '\n');
137
+ }
138
+ return value;
139
+ };
140
+ const envPrivateKey = normalizePem(process.env.JWT_PRIVATE_KEY || false);
141
+ const envPublicKey = normalizePem(process.env.JWT_PUBLIC_KEY || false);
142
+ /**
143
+ * Derive the SPKI public key from a private key PEM. Works for any
144
+ * asymmetric key type (RSA, EC, Ed25519) — the public half is embedded
145
+ * in the private key material.
146
+ *
147
+ * Used when `JWT_PRIVATE_KEY` is set but `JWT_PUBLIC_KEY` is not: deriving
148
+ * guarantees the pair MATCHES. Reading a public key from disk (or generating
149
+ * a fresh one) in that half-configured state would silently produce a
150
+ * mismatched pair — every token signed by this process would then fail
151
+ * verification with an unexplained "invalid signature".
152
+ *
153
+ * @param privateKey - The private key PEM (string or Buffer).
154
+ * @returns The matching public key PEM, or `false` when derivation fails
155
+ * (e.g. the private key PEM is malformed — signing would fail anyway).
156
+ */
157
+ const derivePublicKey = (privateKey) => {
158
+ try {
159
+ return crypto.createPublicKey(privateKey).export({ type: 'spki', format: 'pem' }).toString();
160
+ }
161
+ catch (error) {
162
+ logger.error('JWT_PRIVATE_KEY is set but a public key could not be derived from it — the PEM is likely malformed:', error);
163
+ return false;
164
+ }
165
+ };
166
+ if (envPublicKey && !envPrivateKey) {
167
+ logger.warn('JWT_PUBLIC_KEY is set but JWT_PRIVATE_KEY is not. The private key will be loaded/generated ' +
168
+ 'from disk, so tokens signed by THIS process will NOT verify against JWT_PUBLIC_KEY. ' +
169
+ 'Set both env vars (or neither) unless this deployment only verifies tokens.');
170
+ }
171
+ /**
172
+ * The RSA private key for signing JWTs. Read from the `JWT_PRIVATE_KEY`
173
+ * environment variable, or loaded from the PEM file on disk.
174
+ *
175
+ * Throws at startup if neither source provides a key — running with an
176
+ * empty secret would allow anyone to forge valid JWTs.
177
+ */
178
+ export const JWT_PRIVATE_KEY = envPrivateKey ||
179
+ (fs.existsSync(privateKeyPath) && fs.readFileSync(privateKeyPath)) ||
180
+ (() => {
181
+ throw new Error('JWT private key not found. Set JWT_PRIVATE_KEY env var or ensure key files exist.');
182
+ })();
183
+ /**
184
+ * The RSA public key for verifying JWTs. Read from the `JWT_PUBLIC_KEY`
185
+ * environment variable; when only `JWT_PRIVATE_KEY` is set, the matching
186
+ * public key is DERIVED from it (a disk fallback could not match an
187
+ * env-provided private key and would make every signed token fail
188
+ * verification); otherwise loaded from the PEM file on disk.
189
+ *
190
+ * Throws at startup if no source provides a key.
191
+ */
192
+ export const JWT_PUBLIC_KEY = envPublicKey ||
193
+ (envPrivateKey && derivePublicKey(envPrivateKey)) ||
194
+ (fs.existsSync(publicKeyPath) && fs.readFileSync(publicKeyPath)) ||
195
+ (() => {
196
+ throw new Error('JWT public key not found. Set JWT_PUBLIC_KEY env var or ensure key files exist.');
197
+ })();
198
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * JWT provider bond accessor and convenience functions.
3
+ *
4
+ * Bond packages (e.g. `@molecule/api-jwt-jsonwebtoken`) call `setProvider()`
5
+ * during setup. Application code uses `sign()`, `verify()`, and `decode()` directly.
6
+ *
7
+ * @module
8
+ */
9
+ import type { JSONObject, JwtAlgorithm, JwtDecodeOptions, JwtPayload, JwtProvider, JwtSignOptions, JwtVerifyOptions } from './types.js';
10
+ /**
11
+ * The signing algorithm used for JWT operations. Read from the `JWT_ALGORITHM`
12
+ * environment variable, defaulting to `RS256`. An unrecognized value logs an
13
+ * actionable warning at module load and falls back to `RS256` rather than
14
+ * failing every `sign()`/`verify()` call later with an unexplained
15
+ * "invalid algorithm" error.
16
+ */
17
+ export declare const JWT_ALGORITHM: JwtAlgorithm;
18
+ /**
19
+ * Token lifetime in seconds. Read from the `JWT_EXPIRES_TIME` environment
20
+ * variable, defaulting to 604800 (1 week).
21
+ */
22
+ export declare const JWT_EXPIRES_TIME: number;
23
+ /**
24
+ * Refresh window in seconds — tokens are refreshed if they will expire within
25
+ * this period. Read from the `JWT_REFRESH_TIME` environment variable,
26
+ * defaulting to 3600 (1 hour).
27
+ */
28
+ export declare const JWT_REFRESH_TIME: number;
29
+ /**
30
+ * Registers a JWT provider as the active singleton. Called by bond
31
+ * packages during application startup.
32
+ *
33
+ * @param provider - The JWT provider implementation to bond.
34
+ */
35
+ export declare const setProvider: (provider: JwtProvider) => void;
36
+ /**
37
+ * Retrieves the bonded JWT provider, throwing if none is configured.
38
+ *
39
+ * @returns The bonded JWT provider.
40
+ * @throws {Error} If no JWT provider has been bonded.
41
+ */
42
+ export declare const getProvider: () => JwtProvider;
43
+ /**
44
+ * Checks whether a JWT provider is currently bonded.
45
+ *
46
+ * @returns `true` if a JWT provider is bonded.
47
+ */
48
+ export declare const hasProvider: () => boolean;
49
+ /**
50
+ * Signs a payload into a JWT string using the bonded provider. Uses the
51
+ * configured algorithm, expiry, and private key as defaults.
52
+ *
53
+ * @param object - The JSON payload to sign.
54
+ * @param options - Signing options; `algorithm` defaults to `JWT_ALGORITHM`, `expiresIn` defaults to `JWT_EXPIRES_TIME`.
55
+ * @param options.algorithm - The signing algorithm (e.g. `RS256`, `HS256`).
56
+ * @param options.expiresIn - Token lifetime in seconds.
57
+ * @param privateKey - The private key for signing; defaults to `JWT_PRIVATE_KEY`.
58
+ * @returns The signed JWT string.
59
+ */
60
+ export declare const sign: (object: JSONObject, { algorithm, expiresIn, ...rest }?: JwtSignOptions, privateKey?: string | Buffer) => string;
61
+ /**
62
+ * Verifies a JWT string and returns the decoded payload. Uses the configured
63
+ * algorithm and public key as defaults.
64
+ *
65
+ * @param token - The JWT string to verify.
66
+ * @param options - Verification options; `algorithms` defaults to `[JWT_ALGORITHM]`.
67
+ * @param options.algorithms - The allowed signing algorithms for verification.
68
+ * @param publicKey - The public key for verification; defaults to `JWT_PUBLIC_KEY`.
69
+ * @returns The decoded payload string or object.
70
+ * @throws {Error} If the token is invalid, expired, or verification fails.
71
+ */
72
+ export declare const verify: (token: string, { algorithms, ...rest }?: JwtVerifyOptions, publicKey?: string | Buffer) => string | JwtPayload;
73
+ /**
74
+ * Decodes a JWT string without verifying its signature. Useful for inspecting
75
+ * token contents when verification is handled elsewhere.
76
+ *
77
+ * @param token - The JWT string to decode.
78
+ * @param options - Decode options such as `complete` for full header+payload output.
79
+ * @returns The decoded payload, or `null` if the token cannot be decoded.
80
+ */
81
+ export declare const decode: (token: string, options?: JwtDecodeOptions) => string | JwtPayload | null;
82
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EACV,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,cAAc,EACd,gBAAgB,EACjB,MAAM,YAAY,CAAA;AAkDnB;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,cAAwB,CAAA;AAElD;;;GAGG;AACH,eAAO,MAAM,gBAAgB,QAA2D,CAAA;AAExF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAAkD,CAAA;AAE/E;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,UAAU,WAAW,KAAG,IAEnD,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,WAAW,QAAO,WAE9B,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,WAAW,QAAO,OAE9B,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,IAAI,GACf,QAAQ,UAAU,EAClB,oCAAsE,cAAmB,EACzF,aAAY,MAAM,GAAG,MAAwB,KAC5C,MAAmF,CAAA;AAEtF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,MAAM,GACjB,OAAO,MAAM,EACb,0BAA2C,gBAAqB,EAChE,YAAW,MAAM,GAAG,MAAuB,KAC1C,MAAM,GAAG,UAA6E,CAAA;AAEzF;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,MAAM,EAAE,UAAU,gBAAgB,KAAG,MAAM,GAAG,UAAU,GAAG,IACnD,CAAA"}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * JWT provider bond accessor and convenience functions.
3
+ *
4
+ * Bond packages (e.g. `@molecule/api-jwt-jsonwebtoken`) call `setProvider()`
5
+ * during setup. Application code uses `sign()`, `verify()`, and `decode()` directly.
6
+ *
7
+ * @module
8
+ */
9
+ import { bond, expectBond, getLogger, isBonded, require as bondRequire } from '@molecule/api-bond';
10
+ import { JWT_PRIVATE_KEY, JWT_PUBLIC_KEY } from './keys.js';
11
+ const logger = getLogger();
12
+ const BOND_TYPE = 'jwt';
13
+ expectBond(BOND_TYPE);
14
+ /** Every algorithm the {@link JwtAlgorithm} union accepts. */
15
+ const VALID_JWT_ALGORITHMS = [
16
+ 'RS256',
17
+ 'RS384',
18
+ 'RS512',
19
+ 'HS256',
20
+ 'HS384',
21
+ 'HS512',
22
+ 'ES256',
23
+ 'ES384',
24
+ 'ES512',
25
+ 'PS256',
26
+ 'PS384',
27
+ 'PS512',
28
+ ];
29
+ /**
30
+ * Resolves and validates `JWT_ALGORITHM` at module load. An unset env var
31
+ * silently defaults to `RS256` (already-supported behavior). A SET-but-invalid
32
+ * value (a typo like `rs256` or `RS-256`) would otherwise produce no boot-time
33
+ * signal at all — every `sign()`/`verify()` call would instead fail later, at
34
+ * request time, with the underlying library's generic "invalid algorithm"
35
+ * error, which reads like a token problem rather than a config problem. Warn
36
+ * here, naming the bad value, and fall back to `RS256` so the process still
37
+ * boots and serves traffic while the misconfiguration gets fixed.
38
+ *
39
+ * @returns A valid `JwtAlgorithm`.
40
+ */
41
+ const resolveJwtAlgorithm = () => {
42
+ const raw = process.env.JWT_ALGORITHM;
43
+ if (!raw) {
44
+ return `RS256`;
45
+ }
46
+ if (VALID_JWT_ALGORITHMS.includes(raw)) {
47
+ return raw;
48
+ }
49
+ logger.warn(`JWT_ALGORITHM is set to "${raw}", which is not a supported JWT algorithm. ` +
50
+ `Falling back to RS256. Supported values: ${VALID_JWT_ALGORITHMS.join(', ')}.`);
51
+ return `RS256`;
52
+ };
53
+ /**
54
+ * The signing algorithm used for JWT operations. Read from the `JWT_ALGORITHM`
55
+ * environment variable, defaulting to `RS256`. An unrecognized value logs an
56
+ * actionable warning at module load and falls back to `RS256` rather than
57
+ * failing every `sign()`/`verify()` call later with an unexplained
58
+ * "invalid algorithm" error.
59
+ */
60
+ export const JWT_ALGORITHM = resolveJwtAlgorithm();
61
+ /**
62
+ * Token lifetime in seconds. Read from the `JWT_EXPIRES_TIME` environment
63
+ * variable, defaulting to 604800 (1 week).
64
+ */
65
+ export const JWT_EXPIRES_TIME = Number(process.env.JWT_EXPIRES_TIME) || 60 * 60 * 24 * 7; // 1 week
66
+ /**
67
+ * Refresh window in seconds — tokens are refreshed if they will expire within
68
+ * this period. Read from the `JWT_REFRESH_TIME` environment variable,
69
+ * defaulting to 3600 (1 hour).
70
+ */
71
+ export const JWT_REFRESH_TIME = Number(process.env.JWT_REFRESH_TIME) || 60 * 60; // 1 hour
72
+ /**
73
+ * Registers a JWT provider as the active singleton. Called by bond
74
+ * packages during application startup.
75
+ *
76
+ * @param provider - The JWT provider implementation to bond.
77
+ */
78
+ export const setProvider = (provider) => {
79
+ bond(BOND_TYPE, provider);
80
+ };
81
+ /**
82
+ * Retrieves the bonded JWT provider, throwing if none is configured.
83
+ *
84
+ * @returns The bonded JWT provider.
85
+ * @throws {Error} If no JWT provider has been bonded.
86
+ */
87
+ export const getProvider = () => {
88
+ return bondRequire(BOND_TYPE);
89
+ };
90
+ /**
91
+ * Checks whether a JWT provider is currently bonded.
92
+ *
93
+ * @returns `true` if a JWT provider is bonded.
94
+ */
95
+ export const hasProvider = () => {
96
+ return isBonded(BOND_TYPE);
97
+ };
98
+ /**
99
+ * Signs a payload into a JWT string using the bonded provider. Uses the
100
+ * configured algorithm, expiry, and private key as defaults.
101
+ *
102
+ * @param object - The JSON payload to sign.
103
+ * @param options - Signing options; `algorithm` defaults to `JWT_ALGORITHM`, `expiresIn` defaults to `JWT_EXPIRES_TIME`.
104
+ * @param options.algorithm - The signing algorithm (e.g. `RS256`, `HS256`).
105
+ * @param options.expiresIn - Token lifetime in seconds.
106
+ * @param privateKey - The private key for signing; defaults to `JWT_PRIVATE_KEY`.
107
+ * @returns The signed JWT string.
108
+ */
109
+ export const sign = (object, { algorithm = JWT_ALGORITHM, expiresIn = JWT_EXPIRES_TIME, ...rest } = {}, privateKey = JWT_PRIVATE_KEY) => getProvider().sign(object, { algorithm, expiresIn, ...rest }, privateKey);
110
+ /**
111
+ * Verifies a JWT string and returns the decoded payload. Uses the configured
112
+ * algorithm and public key as defaults.
113
+ *
114
+ * @param token - The JWT string to verify.
115
+ * @param options - Verification options; `algorithms` defaults to `[JWT_ALGORITHM]`.
116
+ * @param options.algorithms - The allowed signing algorithms for verification.
117
+ * @param publicKey - The public key for verification; defaults to `JWT_PUBLIC_KEY`.
118
+ * @returns The decoded payload string or object.
119
+ * @throws {Error} If the token is invalid, expired, or verification fails.
120
+ */
121
+ export const verify = (token, { algorithms = [JWT_ALGORITHM], ...rest } = {}, publicKey = JWT_PUBLIC_KEY) => getProvider().verify(token, { algorithms, ...rest }, publicKey);
122
+ /**
123
+ * Decodes a JWT string without verifying its signature. Useful for inspecting
124
+ * token contents when verification is handled elsewhere.
125
+ *
126
+ * @param token - The JWT string to decode.
127
+ * @param options - Decode options such as `complete` for full header+payload output.
128
+ * @returns The decoded payload, or `null` if the token cannot be decoded.
129
+ */
130
+ export const decode = (token, options) => getProvider().decode(token, options);
131
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAElG,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAW3D,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;AAE1B,MAAM,SAAS,GAAG,KAAK,CAAA;AACvB,UAAU,CAAC,SAAS,CAAC,CAAA;AAErB,8DAA8D;AAC9D,MAAM,oBAAoB,GAA4B;IACpD,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;CACR,CAAA;AAED;;;;;;;;;;;GAWG;AACH,MAAM,mBAAmB,GAAG,GAAiB,EAAE;IAC7C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAA;IACrC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,OAAO,CAAA;IAChB,CAAC;IACD,IAAK,oBAA0C,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9D,OAAO,GAAmB,CAAA;IAC5B,CAAC;IACD,MAAM,CAAC,IAAI,CACT,4BAA4B,GAAG,6CAA6C;QAC1E,4CAA4C,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACjF,CAAA;IACD,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,mBAAmB,EAAE,CAAA;AAElD;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA,CAAC,SAAS;AAElG;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,CAAA,CAAC,SAAS;AAEzF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,QAAqB,EAAQ,EAAE;IACzD,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;AAC3B,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,GAAgB,EAAE;IAC3C,OAAO,WAAW,CAAc,SAAS,CAAC,CAAA;AAC5C,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,GAAY,EAAE;IACvC,OAAO,QAAQ,CAAC,SAAS,CAAC,CAAA;AAC5B,CAAC,CAAA;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAClB,MAAkB,EAClB,EAAE,SAAS,GAAG,aAAa,EAAE,SAAS,GAAG,gBAAgB,EAAE,GAAG,IAAI,KAAqB,EAAE,EACzF,aAA8B,eAAe,EACrC,EAAE,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,UAAU,CAAC,CAAA;AAEtF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CACpB,KAAa,EACb,EAAE,UAAU,GAAG,CAAC,aAAa,CAAC,EAAE,GAAG,IAAI,KAAuB,EAAE,EAChE,YAA6B,cAAc,EACtB,EAAE,CAAC,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,EAAE,SAAS,CAAC,CAAA;AAEzF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,OAA0B,EAA8B,EAAE,CAC9F,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Type definitions for the JWT core interface.
3
+ *
4
+ * @module
5
+ */
6
+ /**
7
+ * Supported JWT signing algorithms.
8
+ */
9
+ export type JwtAlgorithm = 'RS256' | 'RS384' | 'RS512' | 'HS256' | 'HS384' | 'HS512' | 'ES256' | 'ES384' | 'ES512' | 'PS256' | 'PS384' | 'PS512';
10
+ /**
11
+ * Options for signing a JWT.
12
+ */
13
+ export interface JwtSignOptions {
14
+ algorithm?: JwtAlgorithm;
15
+ expiresIn?: number | string;
16
+ notBefore?: number | string;
17
+ audience?: string | string[];
18
+ issuer?: string;
19
+ subject?: string;
20
+ jwtid?: string;
21
+ keyid?: string;
22
+ header?: Record<string, unknown>;
23
+ }
24
+ /**
25
+ * Options for verifying a JWT.
26
+ *
27
+ * Note: security-hardened bonds (e.g. `@molecule/api-jwt-jsonwebtoken`)
28
+ * REFUSE to honor `ignoreExpiration`/`ignoreNotBefore` — an expired token
29
+ * always fails verification regardless of these flags. To tolerate clock
30
+ * skew or slow flows, use `clockTolerance` (seconds) instead.
31
+ */
32
+ export interface JwtVerifyOptions {
33
+ algorithms?: JwtAlgorithm[];
34
+ audience?: string | string[];
35
+ issuer?: string | string[];
36
+ subject?: string;
37
+ clockTolerance?: number;
38
+ maxAge?: string | number;
39
+ complete?: boolean;
40
+ ignoreExpiration?: boolean;
41
+ ignoreNotBefore?: boolean;
42
+ }
43
+ /**
44
+ * Options for decoding a JWT (without verification).
45
+ */
46
+ export interface JwtDecodeOptions {
47
+ complete?: boolean;
48
+ json?: boolean;
49
+ }
50
+ /**
51
+ * Decoded JWT payload.
52
+ */
53
+ export interface JwtPayload {
54
+ [key: string]: unknown;
55
+ iss?: string;
56
+ sub?: string;
57
+ aud?: string | string[];
58
+ exp?: number;
59
+ nbf?: number;
60
+ iat?: number;
61
+ jti?: string;
62
+ }
63
+ /**
64
+ * Recursive JSON value type representing any valid JSON primitive, array, or object.
65
+ */
66
+ export type JSONValue = string | number | boolean | null | JSONValue[] | {
67
+ [key: string]: JSONValue;
68
+ };
69
+ /**
70
+ * A plain JSON object whose values are `JSONValue`s. Used as the payload
71
+ * type for JWT signing operations.
72
+ */
73
+ export type JSONObject = {
74
+ [key: string]: JSONValue;
75
+ };
76
+ /**
77
+ * JWT provider interface that all JWT bond packages must implement.
78
+ *
79
+ * Provides `sign`, `verify`, and `decode` operations. Key management
80
+ * and algorithm configuration are handled by the core package.
81
+ */
82
+ export interface JwtProvider {
83
+ sign(payload: JSONObject, options?: JwtSignOptions, privateKey?: string | Buffer): string;
84
+ verify(token: string, options?: JwtVerifyOptions, publicKey?: string | Buffer): string | JwtPayload;
85
+ decode(token: string, options?: JwtDecodeOptions): string | JwtPayload | null;
86
+ }
87
+ declare global {
88
+ namespace NodeJS {
89
+ /** JWT-related environment variables. */
90
+ interface ProcessEnv {
91
+ JWT_PRIVATE_KEY?: string;
92
+ JWT_PUBLIC_KEY?: string;
93
+ JWT_ALGORITHM?: string;
94
+ JWT_EXPIRES_TIME?: string;
95
+ JWT_REFRESH_TIME?: string;
96
+ /**
97
+ * Directory to store auto-generated JWT key pairs in (only used when
98
+ * `JWT_PRIVATE_KEY`/`JWT_PUBLIC_KEY` are unset). Absolute, or resolved
99
+ * relative to `process.cwd()`. Defaults to `process.cwd() + '/.keys'`.
100
+ */
101
+ JWT_KEYS_DIR?: string;
102
+ }
103
+ }
104
+ }
105
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Type definitions for the JWT core interface.
3
+ *
4
+ * @module
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@molecule/api-jwt",
3
+ "version": "1.0.0",
4
+ "description": "JWT interface and key management for molecule.dev",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "test": "vitest run",
11
+ "test:watch": "vitest"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "keywords": [
23
+ "molecule",
24
+ "jwt",
25
+ "json-web-token",
26
+ "auth"
27
+ ],
28
+ "license": "Apache-2.0",
29
+ "peerDependencies": {
30
+ "@molecule/api-bond": "^1.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "@molecule/api-bond": "1.0.0",
34
+ "@types/node": "26.1.2",
35
+ "typescript": "6.0.3",
36
+ "vitest": "4.1.10"
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/molecule-dev/molecule.git",
41
+ "directory": "packages/api/core/jwt"
42
+ },
43
+ "homepage": "https://github.com/molecule-dev/molecule/tree/main/packages/api/core/jwt",
44
+ "bugs": "https://github.com/molecule-dev/molecule/issues",
45
+ "publishConfig": {
46
+ "access": "public"
47
+ }
48
+ }