diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index a18d206..46dbe17 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,4 +1,4 @@ { "packages/flarelette-jwt-ts": "1.8.1", - "packages/flarelette-jwt-py": "1.8.2" + "packages/flarelette-jwt-py": "1.8.1" } diff --git a/THIRD_PARTY_LICENSES.md b/THIRD_PARTY_LICENSES.md index e831cb3..79607c0 100644 --- a/THIRD_PARTY_LICENSES.md +++ b/THIRD_PARTY_LICENSES.md @@ -14,10 +14,10 @@ The TypeScript package depends on the following NPM packages: ### TypeScript Package Dependencies Summary ``` -@flarelette/jwt-kit-env@1.8.0 +@flarelette/jwt-kit-env@1.8.1 │ C:\Users\chris\git\flarelette-jwt-kit │ -└─┬ @chrislyons-dev/flarelette-jwt@1.8.0 -> .\packages\flarelette-jwt-ts +└─┬ @chrislyons-dev/flarelette-jwt@1.8.1 -> .\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-10-31 +**Last generated**: 2025-11-01 diff --git a/docs/architecture/README.md b/docs/architecture/README.md index e5f752e..5f78862 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -1,7 +1,7 @@ # 🏗️ flarelette-jwt-kit **Architecture Documentation** -Generated 2025-10-31 12:57:04 +Generated 2025-11-01 16:48:43 ## Overview diff --git a/docs/architecture/flarelette-jwt-kit-ir.json b/docs/architecture/flarelette-jwt-kit-ir.json index 7396bbc..32b59ee 100644 --- a/docs/architecture/flarelette-jwt-kit-ir.json +++ b/docs/architecture/flarelette-jwt-kit-ir.json @@ -736,7 +736,7 @@ "type": "class", "documentation": { "summary": "Actor claim for service delegation (RFC 8693).", - "details": "Identifies a service acting on behalf of another principal.\nCan be nested for delegation chains.\n\nStructure:\n sub: Service identifier acting on behalf of original subject\n act: Nested ActorClaim with same structure (recursive delegation chain)" + "details": "Identifies a service acting on behalf of another principal.\nCan be nested for delegation chains.\n\nStructure:\n sub: Service identifier acting on behalf of original subject\n iss: The issuer of the actor token.\n act: Nested ActorClaim with same structure (recursive delegation chain)" }, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\env.py", "lineNumber": 49, @@ -764,7 +764,7 @@ "details": "Includes standard JWT claims, OIDC claims, and common custom claims.\nNote: At runtime, can contain any string key with JwtValue-compatible values,\nbut only defined fields get type checking." }, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\env.py", - "lineNumber": 66, + "lineNumber": 68, "metadata": { "language": "python", "baseClasses": [ @@ -789,7 +789,7 @@ "details": "Represents the complete configuration profile for JWT operations.\nEnvironment-driven: populated from JWT_* environment variables via profile() function.\nValidates against the JSON Schema at project root for consistency across languages." }, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\env.py", - "lineNumber": 108, + "lineNumber": 110, "metadata": { "language": "python", "baseClasses": [ @@ -814,7 +814,7 @@ "details": "Subset of JwtProfile containing the fields shared across all operations\n(signing, verification, policy checks). Extracted by common() function\nand merged with algorithm-specific configuration in profile()." }, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\env.py", - "lineNumber": 123, + "lineNumber": 125, "metadata": { "language": "python", "baseClasses": [ @@ -850,7 +850,7 @@ ], "isAsync": false, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\env.py", - "lineNumber": 137, + "lineNumber": 139, "metadata": { "language": "python", "decorators": [], @@ -876,7 +876,7 @@ "parameters": [], "isAsync": false, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\env.py", - "lineNumber": 167, + "lineNumber": 169, "metadata": { "language": "python", "decorators": [], @@ -909,7 +909,7 @@ ], "isAsync": false, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\env.py", - "lineNumber": 181, + "lineNumber": 183, "metadata": { "language": "python", "decorators": [], @@ -941,7 +941,7 @@ ], "isAsync": false, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\env.py", - "lineNumber": 204, + "lineNumber": 206, "metadata": { "language": "python", "decorators": [], @@ -962,7 +962,7 @@ "parameters": [], "isAsync": false, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\env.py", - "lineNumber": 211, + "lineNumber": 213, "metadata": { "language": "python", "decorators": [], @@ -983,7 +983,7 @@ "parameters": [], "isAsync": false, "filePath": "C:\\Users\\chris\\git\\flarelette-jwt-kit\\packages\\flarelette-jwt-py\\flarelette_jwt\\env.py", - "lineNumber": 226, + "lineNumber": 228, "metadata": { "language": "python", "decorators": [], @@ -1998,6 +1998,24 @@ "deployments": [], "containerRelationships": [], "componentRelationships": [ + { + "description": "copyFileSync | existsSync", + "source": "flarelette_jwt_ts", + "destination": "fs", + "stereotype": "import" + }, + { + "description": "dirname | join | resolve", + "source": "flarelette_jwt_ts", + "destination": "path", + "stereotype": "import" + }, + { + "description": "imports fileURLToPath", + "source": "flarelette_jwt_ts", + "destination": "url", + "stereotype": "import" + }, { "description": "imports generateSecret", "source": "chrislyons_dev_flarelette_jwt__core", @@ -2138,6 +2156,42 @@ } ], "codeRelationships": [ + { + "description": "imports copyFileSync", + "source": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/prepare.js", + "destination": "fs:copyFileSync", + "stereotype": "import" + }, + { + "description": "imports existsSync", + "source": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/prepare.js", + "destination": "fs:existsSync", + "stereotype": "import" + }, + { + "description": "imports dirname", + "source": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/prepare.js", + "destination": "path:dirname", + "stereotype": "import" + }, + { + "description": "imports join", + "source": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/prepare.js", + "destination": "path:join", + "stereotype": "import" + }, + { + "description": "imports resolve", + "source": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/prepare.js", + "destination": "path:resolve", + "stereotype": "import" + }, + { + "description": "imports fileURLToPath", + "source": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/prepare.js", + "destination": "url:fileURLToPath", + "stereotype": "import" + }, { "description": "imports generateSecret", "source": "C:/Users/chris/git/flarelette-jwt-kit/packages/flarelette-jwt-ts/src/cli.ts", diff --git a/docs/architecture/flarelette_jwt__util.md b/docs/architecture/flarelette_jwt__util.md index 4ac9f6d..ff86f4d 100644 --- a/docs/architecture/flarelette_jwt__util.md +++ b/docs/architecture/flarelette_jwt__util.md @@ -109,7 +109,7 @@ JWT token payload/claims structure. Location -C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\env.py:66 +C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\env.py:68 @@ -132,7 +132,7 @@ JWT Profile structure matching flarelette-jwt.profile.schema.json. Location -C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\env.py:108 +C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\env.py:110 @@ -155,7 +155,7 @@ Common JWT configuration from environment variables. Location -C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\env.py:123 +C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\env.py:125 @@ -276,7 +276,7 @@ Detect JWT algorithm mode from environment variables based on role. Location -C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\env.py:137 +C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\env.py:139 @@ -306,7 +306,7 @@ Get common JWT configuration from environment. Location -C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\env.py:167 +C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\env.py:169 @@ -334,7 +334,7 @@ Get JWT profile from environment. Location -C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\env.py:181 +C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\env.py:183 @@ -363,7 +363,7 @@ Get JWT profile from environment. Location -C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\env.py:204 +C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\env.py:206 @@ -392,7 +392,7 @@ Get JWT profile from environment. Location -C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\env.py:211 +C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\env.py:213 @@ -419,7 +419,7 @@ Get JWT profile from environment. Location -C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\env.py:226 +C:\Users\chris\git\flarelette-jwt-kit\packages\flarelette-jwt-py\flarelette_jwt\env.py:228 diff --git a/mkdocs.yml b/mkdocs.yml index ef7120e..7dd71f2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -115,3 +115,7 @@ nav: - Cloudflare Workers: cloudflare-workers.md - Security: - Security Guide: security-guide.md + - Architecture: + - Overview: architecture/README.md + - TypeScript Package: architecture/chrislyons_dev_flarelette_jwt.md + - Python Package: architecture/flarelette_jwt.md diff --git a/package.json b/package.json index 8587786..2bbb936 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { "name": "@flarelette/jwt-kit-env", - "version": "1.8.0", + "version": "1.8.1", "type": "module", "private": true, "scripts": { + "archlette": "npx archlette all -f docs\\archlette.config.yaml", "build": "npm run build --workspaces", "test": "npm run test:js && npm run test:py", "test:js": "vitest run", diff --git a/packages/flarelette-jwt-py/flarelette_jwt/env.py b/packages/flarelette-jwt-py/flarelette_jwt/env.py index da690f1..662175d 100644 --- a/packages/flarelette-jwt-py/flarelette_jwt/env.py +++ b/packages/flarelette-jwt-py/flarelette_jwt/env.py @@ -54,10 +54,12 @@ class ActorClaim(TypedDict, total=False): Structure: sub: Service identifier acting on behalf of original subject + iss: The issuer of the actor token. act: Nested ActorClaim with same structure (recursive delegation chain) """ sub: str + iss: str act: dict[ str, JwtValue ] # Nested ActorClaim (recursive, breaks TypedDict limitation) diff --git a/packages/flarelette-jwt-py/pyproject.toml b/packages/flarelette-jwt-py/pyproject.toml index f1313b3..4c2886d 100644 --- a/packages/flarelette-jwt-py/pyproject.toml +++ b/packages/flarelette-jwt-py/pyproject.toml @@ -4,9 +4,9 @@ build-backend = "setuptools.build_meta" [project] name = "flarelette-jwt" -version = "1.8.2" +version = "1.8.1" description = "Environment-driven JWT authentication for Cloudflare Workers Python with secret-name indirection" -readme = "docs/README.md" +readme = "README.md" requires-python = ">=3.11" license = { text = "MIT" } authors = [{ name = "Chris Lyons", email = "chris@chrislyons.dev" }] diff --git a/packages/flarelette-jwt-ts/.gitignore b/packages/flarelette-jwt-ts/.gitignore new file mode 100644 index 0000000..6200de7 --- /dev/null +++ b/packages/flarelette-jwt-ts/.gitignore @@ -0,0 +1,8 @@ +# Build output +dist/ + +# Copied documentation files (generated by prepare.js) +README.md +CONTRIBUTING.md +THIRD_PARTY_LICENSES.md +LICENSE diff --git a/packages/flarelette-jwt-ts/package.json b/packages/flarelette-jwt-ts/package.json index 04f0162..6646402 100644 --- a/packages/flarelette-jwt-ts/package.json +++ b/packages/flarelette-jwt-ts/package.json @@ -1,6 +1,7 @@ { "name": "@chrislyons-dev/flarelette-jwt", "version": "1.8.1", + "type": "module", "description": "Environment-driven JWT authentication for Cloudflare Workers with secret-name indirection", "keywords": [ "jwt", @@ -48,8 +49,9 @@ "flarelette-jwt-keygen": "dist/keygen.js" }, "scripts": { - "build": "rimraf dist && tsc -p tsconfig.json && cp ../../README.md dist/ && cp ../../CONTRIBUTING.md dist/ && cp ../../THIRD_PARTY_LICENSES.md dist/", - "prepublishOnly": "npm run build" + "prepare": "node prepare.js", + "build": "rimraf dist && tsc -p tsconfig.json", + "prepublishOnly": "npm run prepare && npm run build" }, "dependencies": { "jose": "^5.3.0" diff --git a/packages/flarelette-jwt-ts/prepare.js b/packages/flarelette-jwt-ts/prepare.js new file mode 100644 index 0000000..9ceeb68 --- /dev/null +++ b/packages/flarelette-jwt-ts/prepare.js @@ -0,0 +1,30 @@ +#!/usr/bin/env node +import { copyFileSync, existsSync } from 'fs' +import { dirname, join, resolve } from 'path' +import { fileURLToPath } from 'url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +const PKG_DIR = resolve(__dirname) +const MD_ROOT = resolve(PKG_DIR, '../..') + +const files = [ + { src: join(MD_ROOT, 'README.md'), dst: 'README.md' }, + { src: join(MD_ROOT, 'CONTRIBUTING.md'), dst: 'CONTRIBUTING.md' }, + { src: join(MD_ROOT, 'THIRD_PARTY_LICENSES.md'), dst: 'THIRD_PARTY_LICENSES.md' }, + { src: join(MD_ROOT, 'LICENSE'), dst: 'LICENSE' }, +] + +console.log(`Copying documentation files from ${MD_ROOT} to ${PKG_DIR} directory...`) + +for (const { src, dst } of files) { + if (!existsSync(src)) { + console.error(`ERROR: Missing ${src}`) + process.exit(1) + } + console.log(`Copying ${dst}...`) + copyFileSync(src, join(PKG_DIR, dst)) +} + +console.log('Copy complete') diff --git a/packages/flarelette-jwt-ts/src/types.ts b/packages/flarelette-jwt-ts/src/types.ts index 0c67937..a30db71 100644 --- a/packages/flarelette-jwt-ts/src/types.ts +++ b/packages/flarelette-jwt-ts/src/types.ts @@ -73,6 +73,8 @@ export interface JwtHeader { export interface ActorClaim { /** Service identifier acting on behalf of original subject */ sub: string + /** The issuer of the actor token. */ + iss?: string /** Nested actor for delegation chains (recursive) */ act?: ActorClaim } @@ -216,8 +218,8 @@ export interface Fetcher { */ export interface WorkerEnv extends Record { // Standard environment variables (strings) - JWT_ISS?: string - JWT_AUD?: string + JWT_ISS?: JwtValue + JWT_AUD?: JwtValue JWT_TTL_SECONDS?: string JWT_LEEWAY?: string JWT_KID?: string diff --git a/pyproject.toml b/pyproject.toml index 808d2eb..d5a4550 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "flarelette-jwt-kit-env" -version = "1.8.0" +version = "1.8.1" description = "Env-driven JWT kit with secret-name indirection (Cloudflare-friendly)" readme = "README.md" license = { text = "MIT" }