diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index 094f779..270ccab 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,4 +1,4 @@ { - "packages/flarelette-jwt-ts": "1.8.2", + "packages/flarelette-jwt-ts": "1.8.3", "packages/flarelette-jwt-py": "1.8.3" } diff --git a/.github/release-please-config.json b/.github/release-please-config.json index b25a9d9..8919965 100644 --- a/.github/release-please-config.json +++ b/.github/release-please-config.json @@ -7,6 +7,14 @@ "component": "flarelette-jwt-ts", "bump-minor-pre-major": true, "bump-patch-for-minor-pre-major": true, + "extra-files": [ + { + "type": "json", + "path": "packages/flarelette-jwt-py/pyproject.toml", + "jsonpath": "$.project.version" + }, + "packages/flarelette-jwt-py/flarelette_jwt/__init__.py" + ], "changelog-sections": [ { "type": "feat", "section": "Features" }, { "type": "fix", "section": "Bug Fixes" }, @@ -24,6 +32,13 @@ "component": "flarelette-jwt-py", "bump-minor-pre-major": true, "bump-patch-for-minor-pre-major": true, + "extra-files": [ + { + "type": "json", + "path": "packages/flarelette-jwt-ts/package.json", + "jsonpath": "$.version" + } + ], "changelog-sections": [ { "type": "feat", "section": "Features" }, { "type": "fix", "section": "Bug Fixes" }, diff --git a/THIRD_PARTY_LICENSES.md b/THIRD_PARTY_LICENSES.md index 79607c0..fb6e7a1 100644 --- a/THIRD_PARTY_LICENSES.md +++ b/THIRD_PARTY_LICENSES.md @@ -17,7 +17,7 @@ The TypeScript package depends on the following NPM packages: @flarelette/jwt-kit-env@1.8.1 │ C:\Users\chris\git\flarelette-jwt-kit │ -└─┬ @chrislyons-dev/flarelette-jwt@1.8.1 -> .\packages\flarelette-jwt-ts +└─┬ @chrislyons-dev/flarelette-jwt@1.8.3 -> .\packages\flarelette-jwt-ts │ Environment-driven JWT authentication for Cloudflare Workers with secret-name indirection └── jose@5.10.0 JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes @@ -77,4 +77,4 @@ This script: --- -**Last generated**: 2025-11-01 +**Last generated**: 2025-11-02 diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 5f78862..ae3b71a 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -1,7 +1,7 @@ # 🏗️ flarelette-jwt-kit **Architecture Documentation** -Generated 2025-11-01 16:48:43 +Generated 2025-11-02 06:03:12 ## Overview diff --git a/docs/architecture/chrislyons_dev_flarelette_jwt__adapters.md b/docs/architecture/chrislyons_dev_flarelette_jwt__adapters.md index a4aaee2..38a08f2 100644 --- a/docs/architecture/chrislyons_dev_flarelette_jwt__adapters.md +++ b/docs/architecture/chrislyons_dev_flarelette_jwt__adapters.md @@ -121,7 +121,7 @@ Automatically injects JWKS service binding if configured. Returns -{ sign: typeof import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/sign").sign; verify: (token: string, opts?: Partial<{ iss: string; aud: string; leeway: number; }>) => Promise; createToken: typeof import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high").createToken; checkAuth: (token: string, opts?: import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high").AuthzOpts) => Promise; policy: typeof import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high").policy; parse: typeof import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/util").parse; isExpiringSoon: typeof import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/util").isExpiringSoon; } +{ sign: typeof import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/sign").sign; verify: (token: string, opts?: Partial<{ iss: string; aud: string; leeway: number; }>) => Promise; createToken: typeof import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high").createToken; checkAuth: (token: string, opts?: import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high").AuthzOpts) => Promise; policy: typeof import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high").policy; parse: typeof import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/util").parse; isExpiringSoon: typeof import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/util").isExpiringSoon; } Location diff --git a/docs/architecture/chrislyons_dev_flarelette_jwt__core.md b/docs/architecture/chrislyons_dev_flarelette_jwt__core.md index 8b04428..1838eca 100644 --- a/docs/architecture/chrislyons_dev_flarelette_jwt__core.md +++ b/docs/architecture/chrislyons_dev_flarelette_jwt__core.md @@ -310,7 +310,7 @@ Sign a JWT token with HS512 or EdDSA algorithm **Parameters:** -- `payload`: import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/types").ClaimsDict — - Claims to include in the token (can include custom claims beyond standard JWT fields)- `opts`: Partial<{ iss: string; aud: string | string[]; ttlSeconds: number; }> — - Optional overrides for iss, aud, ttlSeconds +- `payload`: import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/types").JwtPayload — - Claims to include in the token (can include custom claims beyond standard JWT fields)- `opts`: Partial<{ iss: string; aud: string | string[]; ttlSeconds: number; }> — - Optional overrides for iss, aud, ttlSeconds --- diff --git a/docs/architecture/chrislyons_dev_flarelette_jwt__util.md b/docs/architecture/chrislyons_dev_flarelette_jwt__util.md index cfa16a3..754cdbe 100644 --- a/docs/architecture/chrislyons_dev_flarelette_jwt__util.md +++ b/docs/architecture/chrislyons_dev_flarelette_jwt__util.md @@ -83,14 +83,14 @@ Create a signed JWT token with optional claims Location -C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high.ts:19 +C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high.ts:18 **Parameters:** -- `claims`: import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/types").ClaimsDict — - Claims to include in the token (can include custom claims beyond standard JWT fields)- `opts`: Partial<{ iss: string; aud: string | string[]; ttlSeconds: number; }> — - Optional overrides for iss, aud, ttlSeconds +- `claims`: import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/types").JwtPayload — - Claims to include in the token (can include custom claims beyond standard JWT fields)- `opts`: Partial<{ iss: string; aud: string | string[]; ttlSeconds: number; }> — - Optional overrides for iss, aud, ttlSeconds --- ##### `createDelegatedToken()` @@ -125,14 +125,14 @@ Pattern: "I'm doing work on behalf of " Location -C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high.ts:62 +C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high.ts:61 **Parameters:** -- `originalPayload`: import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/types").ClaimsDict — - The verified JWT payload from external auth (e.g., Auth0)- `actorService`: string — - Identifier of the service creating this delegated token- `opts`: Partial<{ iss: string; aud: string | string[]; ttlSeconds: number; }> — - Optional overrides for iss, aud, ttlSeconds +- `originalPayload`: import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/types").JwtPayload — - The verified JWT payload from external auth (e.g., Auth0)- `actorService`: string — - Identifier of the service creating this delegated token- `opts`: Partial<{ iss: string; aud: string | string[]; ttlSeconds: number; }> — - Optional overrides for iss, aud, ttlSeconds **Examples:** ```typescript @@ -163,7 +163,7 @@ Verify and authorize a JWT token with policy enforcement Location -C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high.ts:149 +C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high.ts:142 @@ -189,11 +189,11 @@ Fluent builder for creating authorization policies Returns -{ base(b: Partial<{ iss: string; aud: string | string[]; leeway: number; }>): any; needAll(...perms: string[]): any; needAny(...perms: string[]): any; rolesAll(...roles: string[]): any; rolesAny(...roles: string[]): any; where(fn: (payload: JWTPayload) => boolean): any; build(): import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high").AuthzOpts; } — Policy builder with chainable methods +{ base(b: Partial<{ iss: string; aud: string | string[]; leeway: number; }>): any; needAll(...perms: string[]): any; needAny(...perms: string[]): any; rolesAll(...roles: string[]): any; rolesAny(...roles: string[]): any; where(fn: (payload: import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/types").JwtPayload) => boolean): any; build(): import("C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high").AuthzOpts; } — Policy builder with chainable methods Location -C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high.ts:184 +C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high.ts:177 @@ -525,11 +525,11 @@ Verify a JWT token with HS512 or EdDSA algorithm Returns -Promise — Decoded payload if valid, null otherwise +Promise — Decoded payload if valid, null otherwise Location -C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/verify.ts:29 +C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/verify.ts:28 diff --git a/docs/architecture/flarelette-jwt-kit-ir.json b/docs/architecture/flarelette-jwt-kit-ir.json index 32b59ee..9a59a29 100644 --- a/docs/architecture/flarelette-jwt-kit-ir.json +++ b/docs/architecture/flarelette-jwt-kit-ir.json @@ -223,7 +223,7 @@ "parameters": [ { "name": "claims", - "type": "import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/types\").ClaimsDict", + "type": "import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/types\").JwtPayload", "description": "- Claims to include in the token (can include custom claims beyond standard JWT fields)", "optional": false }, @@ -237,7 +237,7 @@ "visibility": "public", "isAsync": true, "filePath": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high.ts", - "lineNumber": 19 + "lineNumber": 18 }, { "description": "Create a delegated JWT token following RFC 8693 actor claim pattern\n\nMints a new short-lived token for use within service boundaries where a service\nacts on behalf of the original end user. This implements zero-trust delegation:\n- Preserves original user identity (sub) and permissions\n- Identifies the acting service via 'act' claim\n- Prevents permission escalation by copying original permissions\n\nPattern: \"I'm doing work on behalf of \"", @@ -260,7 +260,7 @@ "parameters": [ { "name": "originalPayload", - "type": "import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/types\").ClaimsDict", + "type": "import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/types\").JwtPayload", "description": "- The verified JWT payload from external auth (e.g., Auth0)", "optional": false }, @@ -280,7 +280,7 @@ "visibility": "public", "isAsync": true, "filePath": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high.ts", - "lineNumber": 62 + "lineNumber": 61 }, { "description": "Verify and authorize a JWT token with policy enforcement", @@ -310,7 +310,7 @@ "visibility": "public", "isAsync": true, "filePath": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high.ts", - "lineNumber": 149 + "lineNumber": 142 }, { "description": "Fluent builder for creating authorization policies", @@ -321,13 +321,13 @@ "documentation": { "summary": "Fluent builder for creating authorization policies" }, - "returnType": "{ base(b: Partial<{ iss: string; aud: string | string[]; leeway: number; }>): any; needAll(...perms: string[]): any; needAny(...perms: string[]): any; rolesAll(...roles: string[]): any; rolesAny(...roles: string[]): any; where(fn: (payload: JWTPayload) => boolean): any; build(): import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high\").AuthzOpts; }", + "returnType": "{ base(b: Partial<{ iss: string; aud: string | string[]; leeway: number; }>): any; needAll(...perms: string[]): any; needAny(...perms: string[]): any; rolesAll(...roles: string[]): any; rolesAny(...roles: string[]): any; where(fn: (payload: import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/types\").JwtPayload) => boolean): any; build(): import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high\").AuthzOpts; }", "returnDescription": "Policy builder with chainable methods", "parameters": [], "visibility": "public", "isAsync": false, "filePath": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high.ts", - "lineNumber": 184 + "lineNumber": 177 }, { "description": "Clear the JWKS cache (for testing purposes)", @@ -479,7 +479,7 @@ "parameters": [ { "name": "payload", - "type": "import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/types\").ClaimsDict", + "type": "import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/types\").JwtPayload", "description": "- Claims to include in the token (can include custom claims beyond standard JWT fields)", "optional": false }, @@ -582,7 +582,7 @@ "documentation": { "summary": "Verify a JWT token with HS512 or EdDSA algorithm" }, - "returnType": "Promise", + "returnType": "Promise", "returnDescription": "Decoded payload if valid, null otherwise", "parameters": [ { @@ -601,7 +601,7 @@ "visibility": "public", "isAsync": true, "filePath": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/verify.ts", - "lineNumber": 29 + "lineNumber": 28 }, { "description": "Store both environment variables and service bindings globally", @@ -656,7 +656,7 @@ "documentation": { "summary": "Returns a namespaced kit whose calls use the provided env bag.\nAutomatically injects JWKS service binding if configured." }, - "returnType": "{ sign: typeof import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/sign\").sign; verify: (token: string, opts?: Partial<{ iss: string; aud: string; leeway: number; }>) => Promise; createToken: typeof import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high\").createToken; checkAuth: (token: string, opts?: import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high\").AuthzOpts) => Promise; policy: typeof import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high\").policy; parse: typeof import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/util\").parse; isExpiringSoon: typeof import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/util\").isExpiringSoon; }", + "returnType": "{ sign: typeof import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/sign\").sign; verify: (token: string, opts?: Partial<{ iss: string; aud: string; leeway: number; }>) => Promise; createToken: typeof import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high\").createToken; checkAuth: (token: string, opts?: import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high\").AuthzOpts) => Promise; policy: typeof import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high\").policy; parse: typeof import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/util\").parse; isExpiringSoon: typeof import(\"C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/util\").isExpiringSoon; }", "parameters": [ { "name": "env", @@ -1258,7 +1258,7 @@ "parameters": [ { "name": "fn", - "type": "Callable[[dict[str, JwtValue]], bool]", + "type": "Callable[[JwtPayload], bool]", "optional": false } ], @@ -1308,7 +1308,7 @@ "name": "Builder", "type": "class", "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\high.py", - "lineNumber": 217, + "lineNumber": 227, "metadata": { "language": "python", "baseClasses": [], @@ -1339,7 +1339,7 @@ "isStatic": false, "isAbstract": false, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\high.py", - "lineNumber": 218, + "lineNumber": 228, "metadata": { "language": "python", "decorators": [], @@ -1369,7 +1369,7 @@ "isStatic": false, "isAbstract": false, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\high.py", - "lineNumber": 222, + "lineNumber": 232, "metadata": { "language": "python", "decorators": [], @@ -1399,7 +1399,7 @@ "isStatic": false, "isAbstract": false, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\high.py", - "lineNumber": 227, + "lineNumber": 237, "metadata": { "language": "python", "decorators": [], @@ -1429,7 +1429,7 @@ "isStatic": false, "isAbstract": false, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\high.py", - "lineNumber": 232, + "lineNumber": 242, "metadata": { "language": "python", "decorators": [], @@ -1459,7 +1459,7 @@ "isStatic": false, "isAbstract": false, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\high.py", - "lineNumber": 237, + "lineNumber": 247, "metadata": { "language": "python", "decorators": [], @@ -1480,7 +1480,7 @@ "parameters": [ { "name": "fn", - "type": "Callable[[dict[str, JwtValue]], bool]", + "type": "Callable[[JwtPayload], bool]", "optional": false } ], @@ -1489,7 +1489,7 @@ "isStatic": false, "isAbstract": false, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\high.py", - "lineNumber": 242, + "lineNumber": 252, "metadata": { "language": "python", "decorators": [], @@ -1513,7 +1513,7 @@ "isStatic": false, "isAbstract": false, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\high.py", - "lineNumber": 247, + "lineNumber": 257, "metadata": { "language": "python", "decorators": [], @@ -1539,8 +1539,8 @@ "parameters": [ { "name": "claims", - "type": "dict", - "description": "Custom claims to include in the token", + "type": "JwtPayload", + "description": "Claims to include in the token (can include custom claims beyond standard JWT fields)", "optional": false } ], @@ -1575,7 +1575,7 @@ "parameters": [ { "name": "original_payload", - "type": "dict[str, JwtValue]", + "type": "JwtPayload", "description": "The verified JWT payload from external auth (e.g., Auth0)", "optional": false }, @@ -1621,7 +1621,7 @@ ], "isAsync": true, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\high.py", - "lineNumber": 156, + "lineNumber": 166, "metadata": { "language": "python", "decorators": [], @@ -1647,7 +1647,7 @@ "parameters": [], "isAsync": false, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\high.py", - "lineNumber": 209, + "lineNumber": 219, "metadata": { "language": "python", "decorators": [], @@ -1789,8 +1789,8 @@ "parameters": [ { "name": "payload", - "type": "dict", - "description": "Claims to include in the token", + "type": "JwtPayload", + "description": "Claims to include in the token (can include custom claims beyond standard JWT fields)", "optional": false } ], @@ -2023,7 +2023,7 @@ "stereotype": "import" }, { - "description": "AlgType | ClaimsDict", + "description": "AlgType | JwtPayload", "source": "chrislyons_dev_flarelette_jwt__core", "destination": "./types.js", "stereotype": "type-import" @@ -2041,16 +2041,16 @@ "stereotype": "import" }, { - "description": "ClaimsDict | Fetcher | JWKSResponse", + "description": "Fetcher | JwtPayload | JWKSResponse", "source": "chrislyons_dev_flarelette_jwt__util", "destination": "./types.js", "stereotype": "type-import" }, { - "description": "JWTPayload | importJWK | generateKeyPair | exportJWK | jwtVerify | calculateJwkThumbprint | decodeProtectedHeader", + "description": "importJWK | generateKeyPair | exportJWK | jwtVerify | calculateJwkThumbprint | decodeProtectedHeader", "source": "chrislyons_dev_flarelette_jwt__util", "destination": "jose", - "stereotype": "type-import | import | import | import | import | import | import | import | type-import" + "stereotype": "import" }, { "description": "imports webcrypto", @@ -2216,12 +2216,6 @@ "destination": "./verify.js:verify", "stereotype": "import" }, - { - "description": "imports ClaimsDict", - "source": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high.ts", - "destination": "./types.js:ClaimsDict", - "stereotype": "type-import" - }, { "description": "imports Fetcher", "source": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high.ts", @@ -2229,9 +2223,9 @@ "stereotype": "type-import" }, { - "description": "imports JWTPayload", + "description": "imports JwtPayload", "source": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/high.ts", - "destination": "jose:JWTPayload", + "destination": "./types.js:JwtPayload", "stereotype": "type-import" }, { @@ -2313,9 +2307,9 @@ "stereotype": "type-import" }, { - "description": "imports ClaimsDict", + "description": "imports JwtPayload", "source": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/sign.ts", - "destination": "./types.js:ClaimsDict", + "destination": "./types.js:JwtPayload", "stereotype": "type-import" }, { @@ -2396,12 +2390,6 @@ "destination": "./jwks.js:allowedThumbprints", "stereotype": "import" }, - { - "description": "imports JWTPayload", - "source": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/verify.ts", - "destination": "jose:JWTPayload", - "stereotype": "type-import" - }, { "description": "imports AlgType", "source": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/verify.ts", @@ -2414,6 +2402,12 @@ "destination": "./types.js:Fetcher", "stereotype": "type-import" }, + { + "description": "imports JwtPayload", + "source": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/verify.ts", + "destination": "./types.js:JwtPayload", + "stereotype": "type-import" + }, { "description": "imports * as kit", "source": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/adapters/hono.ts", diff --git a/docs/architecture/flarelette_jwt__util.md b/docs/architecture/flarelette_jwt__util.md index ff86f4d..069cad5 100644 --- a/docs/architecture/flarelette_jwt__util.md +++ b/docs/architecture/flarelette_jwt__util.md @@ -223,7 +223,7 @@ Builder interface for creating JWT authorization policies. Location -C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\high.py:217 +C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\high.py:227 @@ -458,7 +458,7 @@ Create a signed JWT token with optional claims. **Parameters:** -- `claims`: dict — Custom claims to include in the token +- `claims`: JwtPayload — Claims to include in the token (can include custom claims beyond standard JWT fields) --- ##### `create_delegated_token()` @@ -496,7 +496,7 @@ See Also: **Parameters:** -- `original_payload`: dict[str, JwtValue] — The verified JWT payload from external auth (e.g., Auth0)- `actor_service`: str — Identifier of the service creating this delegated token +- `original_payload`: JwtPayload — The verified JWT payload from external auth (e.g., Auth0)- `actor_service`: str — Identifier of the service creating this delegated token **Examples:** ```typescript @@ -527,7 +527,7 @@ Verify and authorize a JWT token with policy enforcement. Location -C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\high.py:156 +C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\high.py:166 @@ -557,7 +557,7 @@ Fluent builder for creating authorization policies. Location -C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\high.py:209 +C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\high.py:219 @@ -712,7 +712,7 @@ Sign a JWT token with HS512 or EdDSA algorithm. **Parameters:** -- `payload`: dict — Claims to include in the token +- `payload`: JwtPayload — Claims to include in the token (can include custom claims beyond standard JWT fields) --- ##### `parse()` diff --git a/packages/flarelette-jwt-py/flarelette_jwt/__init__.py b/packages/flarelette-jwt-py/flarelette_jwt/__init__.py index 9bf3e60..18e3e76 100644 --- a/packages/flarelette-jwt-py/flarelette_jwt/__init__.py +++ b/packages/flarelette-jwt-py/flarelette_jwt/__init__.py @@ -8,7 +8,6 @@ from .env import ( ActorClaim, AlgType, - ClaimsDict, JwtCommonConfig, JwtHeader, JwtPayload, @@ -30,7 +29,6 @@ # Types "AlgType", "JwtValue", - "ClaimsDict", "JwtProfile", "JwtCommonConfig", "JwtHeader", diff --git a/packages/flarelette-jwt-py/flarelette_jwt/high.py b/packages/flarelette-jwt-py/flarelette_jwt/high.py index 1b04142..30e4234 100644 --- a/packages/flarelette-jwt-py/flarelette_jwt/high.py +++ b/packages/flarelette-jwt-py/flarelette_jwt/high.py @@ -18,7 +18,7 @@ if TYPE_CHECKING: from collections.abc import Callable - from .env import JwtPayload, JwtValue + from .env import JwtPayload class AuthUser(TypedDict, total=False): @@ -51,12 +51,12 @@ def need_all(self, *p: str) -> PolicyBuilder: ... def need_any(self, *p: str) -> PolicyBuilder: ... def roles_all(self, *r: str) -> PolicyBuilder: ... def roles_any(self, *r: str) -> PolicyBuilder: ... - def where(self, fn: Callable[[dict[str, JwtValue]], bool]) -> PolicyBuilder: ... + def where(self, fn: Callable[[JwtPayload], bool]) -> PolicyBuilder: ... def build(self) -> dict[str, Any]: ... async def create_token( - claims: dict, + claims: JwtPayload, *, iss: str | None = None, aud: str | list[str] | None = None, @@ -65,7 +65,7 @@ async def create_token( """Create a signed JWT token with optional claims. Args: - claims: Custom claims to include in the token + claims: Claims to include in the token (can include custom claims beyond standard JWT fields) iss: Optional issuer override aud: Optional audience override (string or list) ttl_seconds: Optional TTL override in seconds @@ -77,7 +77,7 @@ async def create_token( async def create_delegated_token( - original_payload: dict[str, JwtValue], + original_payload: JwtPayload, actor_service: str, *, iss: str | None = None, @@ -127,7 +127,7 @@ async def create_delegated_token( - security.md: Service Delegation Pattern section """ # Preserve original user context and permissions - delegated_claims: dict[str, JwtValue] = { + delegated_claims: dict[str, Any] = { "sub": original_payload.get("sub"), # Original end user "permissions": original_payload.get("permissions", []), # NO escalation "roles": original_payload.get("roles", []), @@ -146,11 +146,21 @@ async def create_delegated_token( delegated_claims["act"] = {"sub": actor_service} # Preserve additional context fields if present - for field in ["email", "name", "groups", "tid", "org_id", "department"]: - if field in original_payload: - delegated_claims[field] = original_payload[field] - - return await sign(delegated_claims, iss=iss, aud=aud, ttl_seconds=ttl_seconds) + if original_payload.get("email"): + delegated_claims["email"] = original_payload["email"] + if original_payload.get("name"): + delegated_claims["name"] = original_payload["name"] + if original_payload.get("groups"): + delegated_claims["groups"] = original_payload["groups"] + if original_payload.get("tid"): + delegated_claims["tid"] = original_payload["tid"] + if original_payload.get("org_id"): + delegated_claims["org_id"] = original_payload["org_id"] + if original_payload.get("department"): + delegated_claims["department"] = original_payload["department"] + + # Type cast to JwtPayload for type checking - safe because we control the structure + return await sign(delegated_claims, iss=iss, aud=aud, ttl_seconds=ttl_seconds) # type: ignore[arg-type] async def check_auth( @@ -163,7 +173,7 @@ async def check_auth( require_any_permission: list[str] | None = None, require_roles_all: list[str] | None = None, require_roles_any: list[str] | None = None, - predicates: list[Callable[[dict[str, JwtValue]], bool]] | None = None, + predicates: list[Callable[[JwtPayload], bool]] | None = None, ) -> AuthUser | None: """Verify and authorize a JWT token with policy enforcement. @@ -196,7 +206,7 @@ async def check_auth( return None if predicates: for fn in predicates: - if not fn(payload): # type: ignore[arg-type] + if not fn(payload): return None return { "sub": payload.get("sub"), @@ -239,7 +249,7 @@ def roles_any(self, *r: str) -> PolicyBuilder: opts["require_roles_any"].extend(r) return self - def where(self, fn: Callable[[dict[str, JwtValue]], bool]) -> PolicyBuilder: + def where(self, fn: Callable[[JwtPayload], bool]) -> PolicyBuilder: opts.setdefault("predicates", []) opts["predicates"].append(fn) return self diff --git a/packages/flarelette-jwt-py/flarelette_jwt/sign.py b/packages/flarelette-jwt-py/flarelette_jwt/sign.py index d60cdae..9e13553 100644 --- a/packages/flarelette-jwt-py/flarelette_jwt/sign.py +++ b/packages/flarelette-jwt-py/flarelette_jwt/sign.py @@ -15,7 +15,7 @@ import time # NOTE: 'js' module imported lazily inside functions - only available in Cloudflare Workers -from .env import AlgType, common, get_hs_secret_bytes, mode +from .env import AlgType, JwtPayload, common, get_hs_secret_bytes, mode def _b64url(b: bytes) -> str: @@ -23,7 +23,7 @@ def _b64url(b: bytes) -> str: async def sign( - payload: dict, + payload: JwtPayload, *, iss: str | None = None, aud: str | list[str] | None = None, @@ -32,7 +32,7 @@ async def sign( """Sign a JWT token with HS512 or EdDSA algorithm. Args: - payload: Claims to include in the token + payload: Claims to include in the token (can include custom claims beyond standard JWT fields) iss: Optional issuer override aud: Optional audience override (string or list) ttl_seconds: Optional TTL override in seconds diff --git a/packages/flarelette-jwt-py/tests/test_sign_verify_mocked.py b/packages/flarelette-jwt-py/tests/test_sign_verify_mocked.py index 4f7e3ec..f0d89e7 100644 --- a/packages/flarelette-jwt-py/tests/test_sign_verify_mocked.py +++ b/packages/flarelette-jwt-py/tests/test_sign_verify_mocked.py @@ -7,13 +7,15 @@ from __future__ import annotations import os -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast import pytest if TYPE_CHECKING: from collections.abc import Generator + from flarelette_jwt import JwtPayload + # Install js mock BEFORE importing anything from flarelette_jwt from .mock_js import install_js_mock, uninstall_js_mock @@ -57,7 +59,9 @@ def setup_env(self) -> Generator[None, None, None]: @pytest.mark.asyncio async def test_sign_creates_token(self) -> None: """Should sign a token with HS512.""" - payload = {"sub": "123", "permissions": ["test@example.com"]} + payload = cast( + "JwtPayload", {"sub": "123", "permissions": ["test@example.com"]} + ) token = await sign(payload) @@ -68,7 +72,7 @@ async def test_sign_creates_token(self) -> None: @pytest.mark.asyncio async def test_sign_includes_claims(self) -> None: """Should include standard JWT claims.""" - payload = {"sub": "123"} + payload = cast("JwtPayload", {"sub": "123"}) token = await sign(payload) verified = await verify(token) @@ -83,7 +87,7 @@ async def test_sign_includes_claims(self) -> None: @pytest.mark.asyncio async def test_verify_accepts_valid_token(self) -> None: """Should verify a valid token.""" - payload = {"sub": "456", "roles": ["admin"]} + payload = cast("JwtPayload", {"sub": "456", "roles": ["admin"]}) token = await sign(payload) verified = await verify(token) @@ -104,7 +108,7 @@ async def test_verify_rejects_invalid_token(self) -> None: @pytest.mark.asyncio async def test_verify_with_custom_options(self) -> None: """Should accept custom issuer and audience.""" - payload = {"permissions": ["test"]} + payload = cast("JwtPayload", {"permissions": ["test"]}) token = await sign(payload) # Verify with same issuer/audience @@ -116,7 +120,7 @@ async def test_verify_with_custom_options(self) -> None: @pytest.mark.asyncio async def test_sign_with_custom_ttl(self) -> None: """Should sign with custom TTL.""" - payload = {"sub": "789"} + payload = cast("JwtPayload", {"sub": "789"}) token = await sign(payload, ttl_seconds=7200) verified = await verify(token) @@ -128,7 +132,7 @@ async def test_sign_with_custom_ttl(self) -> None: @pytest.mark.asyncio async def test_create_token_basic(self) -> None: """Should create token with basic payload.""" - token = await create_token({"sub": "123"}) + token = await create_token(cast("JwtPayload", {"sub": "123"})) verified = await verify(token) @@ -139,7 +143,7 @@ async def test_create_token_basic(self) -> None: async def test_create_token_with_options(self) -> None: """Should create token with custom options.""" token = await create_token( - {"sub": "123"}, iss="custom-issuer", ttl_seconds=7200 + cast("JwtPayload", {"sub": "123"}), iss="custom-issuer", ttl_seconds=7200 ) verified = await verify(token, iss="custom-issuer") @@ -152,9 +156,9 @@ async def test_create_token_with_options(self) -> None: async def test_multiple_sign_verify_cycles(self) -> None: """Should handle multiple sign/verify operations.""" payloads = [ - {"sub": "1", "roles": ["Alice"]}, - {"sub": "2", "roles": ["Bob"]}, - {"sub": "3", "roles": ["Charlie"]}, + cast("JwtPayload", {"sub": "1", "roles": ["Alice"]}), + cast("JwtPayload", {"sub": "2", "roles": ["Bob"]}), + cast("JwtPayload", {"sub": "3", "roles": ["Charlie"]}), ] for payload in payloads: diff --git a/packages/flarelette-jwt-ts/package.json b/packages/flarelette-jwt-ts/package.json index 86d98bf..93857d0 100644 --- a/packages/flarelette-jwt-ts/package.json +++ b/packages/flarelette-jwt-ts/package.json @@ -1,6 +1,6 @@ { "name": "@chrislyons-dev/flarelette-jwt", - "version": "1.8.2", + "version": "1.8.3", "type": "module", "description": "Environment-driven JWT authentication for Cloudflare Workers with secret-name indirection", "keywords": [ diff --git a/packages/flarelette-jwt-ts/src/high.ts b/packages/flarelette-jwt-ts/src/high.ts index 11a7ad7..0a875f4 100644 --- a/packages/flarelette-jwt-ts/src/high.ts +++ b/packages/flarelette-jwt-ts/src/high.ts @@ -6,8 +6,7 @@ */ import { sign } from './sign.js' import { verify } from './verify.js' -import type { ClaimsDict, Fetcher } from './types.js' -import type { JWTPayload } from 'jose' +import type { Fetcher, JwtPayload } from './types.js' /** * Create a signed JWT token with optional claims @@ -17,7 +16,7 @@ import type { JWTPayload } from 'jose' * @returns Signed JWT token string */ export async function createToken( - claims: ClaimsDict, + claims: JwtPayload, opts?: Partial<{ iss: string; aud: string | string[]; ttlSeconds: number }> ) { return sign(claims, opts) @@ -60,12 +59,12 @@ export async function createToken( * @see security.md - Service Delegation Pattern section */ export async function createDelegatedToken( - originalPayload: ClaimsDict, + originalPayload: JwtPayload, actorService: string, opts?: Partial<{ iss: string; aud: string | string[]; ttlSeconds: number }> ): Promise { // Preserve original user context and permissions - const delegatedClaims: ClaimsDict = { + const delegatedClaims: JwtPayload = { sub: originalPayload.sub, // Original end user permissions: originalPayload.permissions || [], // NO escalation roles: originalPayload.roles || [], @@ -85,19 +84,13 @@ export async function createDelegatedToken( } // Preserve additional context fields if present - const contextFields = [ - 'email', - 'name', - 'groups', - 'tid', - 'org_id', - 'department', - ] as const - for (const field of contextFields) { - if (field in originalPayload) { - delegatedClaims[field] = originalPayload[field] - } - } + if (originalPayload.email) delegatedClaims.email = originalPayload.email + if (originalPayload.name) delegatedClaims.name = originalPayload.name + if (originalPayload.groups) delegatedClaims.groups = originalPayload.groups + if (originalPayload.tid) delegatedClaims.tid = originalPayload.tid + if (originalPayload.org_id) delegatedClaims.org_id = originalPayload.org_id + if (originalPayload.department) + delegatedClaims.department = originalPayload.department return sign(delegatedClaims, opts) } @@ -120,7 +113,7 @@ export type AuthzOpts = Partial<{ require_any_permission?: string[] require_roles_all?: string[] require_roles_any?: string[] - predicates?: Array<(payload: JWTPayload) => boolean> + predicates?: Array<(payload: JwtPayload) => boolean> } /** @@ -136,7 +129,7 @@ export type AuthUser = { permissions: string[] roles: string[] jti: string | undefined - payload: JWTPayload + payload: JwtPayload } /** @@ -204,7 +197,7 @@ export function policy() { opts.require_roles_any = [...(opts.require_roles_any || []), ...roles] return this }, - where(fn: (payload: JWTPayload) => boolean) { + where(fn: (payload: JwtPayload) => boolean) { opts.predicates = [...(opts.predicates || []), fn] return this }, diff --git a/packages/flarelette-jwt-ts/src/index.ts b/packages/flarelette-jwt-ts/src/index.ts index 888074c..b4734ef 100644 --- a/packages/flarelette-jwt-ts/src/index.ts +++ b/packages/flarelette-jwt-ts/src/index.ts @@ -37,7 +37,6 @@ export { generateSecret } from './secret.js' export type { AlgType, JwtValue, - ClaimsDict, JwtProfile, JwtHeader, JwtPayload, diff --git a/packages/flarelette-jwt-ts/src/sign.ts b/packages/flarelette-jwt-ts/src/sign.ts index d0f2029..16e4180 100644 --- a/packages/flarelette-jwt-ts/src/sign.ts +++ b/packages/flarelette-jwt-ts/src/sign.ts @@ -10,7 +10,7 @@ import { SignJWT, importJWK } from 'jose' import { envMode, getCommon, getHSSecret, getPrivateJwkString } from './config.js' -import type { AlgType, ClaimsDict } from './types.js' +import type { AlgType, JwtPayload } from './types.js' /** * Sign a JWT token with HS512 or EdDSA algorithm @@ -20,7 +20,7 @@ import type { AlgType, ClaimsDict } from './types.js' * @returns Signed JWT token string */ export async function sign( - payload: ClaimsDict, + payload: JwtPayload, opts?: Partial<{ iss: string; aud: string | string[]; ttlSeconds: number }> ): Promise { const mode: AlgType = envMode('producer') diff --git a/packages/flarelette-jwt-ts/src/verify.ts b/packages/flarelette-jwt-ts/src/verify.ts index f41ac5d..39b8854 100644 --- a/packages/flarelette-jwt-ts/src/verify.ts +++ b/packages/flarelette-jwt-ts/src/verify.ts @@ -16,8 +16,7 @@ import { } from 'jose' import { envMode, getCommon, getHSSecret, getPublicJwkString } from './config.js' import { fetchJwksFromService, getKeyFromJwks, allowedThumbprints } from './jwks.js' -import type { JWTPayload } from 'jose' -import type { AlgType, Fetcher } from './types.js' +import type { AlgType, Fetcher, JwtPayload } from './types.js' /** * Verify a JWT token with HS512 or EdDSA algorithm @@ -34,7 +33,7 @@ export async function verify( leeway: number jwksService: Fetcher }> -): Promise { +): Promise { const mode: AlgType = envMode('consumer') const { iss, aud, leeway } = { ...getCommon(), ...(opts || {}) }