Skip to content

Commit be132e2

Browse files
feat: initial merge of flarelette-jwt-kit (ts+py) - env-driven hs512/eddsa, jwks, policy builder
- Monorepo (npm workspaces) with TypeScript and Python packages - Environment-driven config with secret-name indirection (JWT_*_NAME) - Dual-source env reading for Node.js and Cloudflare Workers - HS512 sign/verify (both); EdDSA sign (TS) + verify (TS/Py); JWKS via service binding (5-min cooldown) - Authorization policy builder (roles/permissions/predicates) with fail-silent verify - RFC 8693 “actor” delegation (`act`) utilities for service-on-behalf-of patterns - CLIs: secret generation + Ed25519 keygen - Docs: security baseline, usage, architecture notes; examples + schemas Notes: - Python Workers: EdDSA signing and remote JWKS fetching not supported (verify-only via inline JWK) Written-by: Chris Lyons
1 parent a3e162e commit be132e2

159 files changed

Lines changed: 27806 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"packages/flarelette-jwt-ts": "1.7.0",
3+
"packages/flarelette-jwt-py": "0.1.0"
4+
}

.github/COPILOT_INSTRUCTIONS.md

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
# GitHub Copilot Instructions
2+
3+
## Project Overview
4+
5+
**Flarelette JWT** is a polyglot JWT authentication toolkit with identical APIs for TypeScript and Python. Environment-driven JWT authentication for Cloudflare Workers. Like Starlette, but for the edge.
6+
7+
**Key Principles:**
8+
9+
- Environment-driven configuration (no config files)
10+
- Cross-language API parity (TypeScript ↔ Python)
11+
- Secret-name indirection for Cloudflare Workers bindings
12+
- Security-first design with explicit trade-offs
13+
14+
## Architecture
15+
16+
### Mode Detection (Automatic)
17+
18+
- **HS512 (default):** Used when `JWT_SECRET` or `JWT_SECRET_NAME` is set
19+
- **EdDSA:** Used when `JWT_PRIVATE_JWK*` or `JWT_PUBLIC_JWK*` or `JWT_JWKS_URL*` is set
20+
- **Do not add `JWT_ALG` configuration** — mode is auto-detected from environment
21+
22+
### Secret-Name Indirection Pattern
23+
24+
```typescript
25+
// Instead of: JWT_SECRET=<actual-secret>
26+
// Use: JWT_SECRET_NAME=FLARELETTE_JWT_SECRET
27+
// Then: wrangler secret put FLARELETTE_JWT_SECRET
28+
```
29+
30+
Applies to: `JWT_SECRET_NAME`, `JWT_PRIVATE_JWK_NAME`, `JWT_PUBLIC_JWK_NAME`, `JWT_JWKS_URL_NAME`
31+
32+
### Platform Compatibility
33+
34+
| Feature | Node.js | CF Workers (TS) | CF Workers (Python) |
35+
| ----------------- | ------- | --------------- | --------------------- |
36+
| HS512 sign/verify ||||
37+
| EdDSA sign ||| ❌ (use Node gateway) |
38+
| EdDSA verify ||| ✅ (inline JWK only) |
39+
| JWKS fetch ||| ❌ (inline JWK only) |
40+
41+
**Critical:** Python cannot sign EdDSA tokens due to WebCrypto API limitations in Pyodide.
42+
43+
## Code Guidelines
44+
45+
### When Adding Features
46+
47+
1. **Maintain cross-language parity:** Implement in both TypeScript and Python with identical APIs
48+
2. **Respect platform limitations:** Python cannot sign EdDSA or fetch remote JWKS
49+
3. **Use environment-driven config:** No configuration files or hardcoded defaults
50+
4. **Preserve secret-name indirection:** New secrets must support `*_NAME` pattern
51+
5. **Keep fail-silent verification:** Return `null`/`None` on errors, don't throw exceptions
52+
53+
### TypeScript Style
54+
55+
- Enable `strict` mode
56+
- Use explicit types for public APIs
57+
- Prefer type narrowing over type assertions
58+
- Dependencies: Only `jose` library for cryptography
59+
60+
### Python Style
61+
62+
- Use type hints for all public functions
63+
- Follow PEP 8
64+
- **Zero external dependencies** (use WebCrypto via `js` module only)
65+
- All JWT operations are `async` in Python
66+
67+
### Error Handling
68+
69+
- **Fail-silent pattern:** Verification functions return `null`/`None` on failure
70+
- Never swallow exceptions in signing operations
71+
- Validate inputs explicitly with runtime checks
72+
73+
## Security Model
74+
75+
### Algorithms
76+
77+
- **HS512:** HMAC-SHA-512 with 64-byte (512-bit) secrets — for trusted producer-consumer pairs
78+
- **EdDSA:** Ed25519 — for public verification scenarios (asymmetric trust)
79+
80+
### Claims Validation
81+
82+
Both implementations validate:
83+
84+
- `iss` (issuer) — must match `JWT_ISS`
85+
- `aud` (audience) — must match `JWT_AUD`
86+
- `exp` (expiration) — with configurable leeway (default 90s)
87+
- `nbf` (not before) — if present
88+
- `iat` (issued at) — automatically set on signing
89+
90+
## Common Patterns
91+
92+
### Cloudflare Worker (TypeScript + Hono)
93+
94+
```typescript
95+
import { makeKit } from '@flarelette/jwt-ts/adapters/hono'
96+
97+
app.use('*', async (c, next) => {
98+
const jwt = makeKit(c.env) // Must inject Worker env
99+
c.set('jwt', jwt)
100+
await next()
101+
})
102+
103+
app.get('/secure', async c => {
104+
const jwt = c.get('jwt')
105+
const auth = await jwt.checkAuth(token, jwt.policy().rolesAny('admin').build())
106+
if (!auth) return c.json({ error: 'Unauthorized' }, 401)
107+
return c.json({ data: 'secure' })
108+
})
109+
```
110+
111+
### Cloudflare Worker (Python)
112+
113+
```python
114+
from flarelette_jwt import sign, verify, check_auth, policy
115+
from flarelette_jwt.adapters import apply_env_bindings
116+
117+
async def on_fetch(request, env, ctx):
118+
apply_env_bindings(env) # Must be first — injects env into os.environ
119+
120+
# All operations are async
121+
token = await sign({"sub": "user123", "permissions": ["read:data"]})
122+
auth = await check_auth(token, **policy().roles_any('admin').build())
123+
124+
if not auth:
125+
return Response.new('Unauthorized', status=401)
126+
return Response.new(f'Hello {auth["sub"]}')
127+
```
128+
129+
## Documentation Standards
130+
131+
**Target audience:** Software architects and engineers (assume technical fluency)
132+
133+
**Tone:** Security-conscious, clear, and trustworthy — like a security engineer explaining best practices to a colleague
134+
135+
**Style:**
136+
137+
- Concise sections: 3–7 bullets or ≤120 words
138+
- **Exception:** Security features may expand as needed (authentication, cryptography, key management)
139+
- Explain "why" behind patterns, not just "how"
140+
- Use tables and code examples for clarity
141+
- Keep examples executable and realistic
142+
143+
**What to avoid:**
144+
145+
- Marketing speak ("Revolutionary!" "Easiest ever!")
146+
- Vague promises without specifics
147+
- Academic abstractions that obscure practical meaning
148+
- Downplaying security complexity
149+
- Apologetic or overly casual tone
150+
151+
## File Structure
152+
153+
```
154+
flarelette-jwt-kit/
155+
├── packages/
156+
│ ├── flarelette-jwt-ts/src/ # TypeScript implementation
157+
│ │ ├── index.ts # Public API
158+
│ │ ├── config.ts # Mode detection, env reading
159+
│ │ ├── sign.ts # HS512 + EdDSA signing
160+
│ │ ├── verify.ts # HS512 + EdDSA verification
161+
│ │ ├── jwks.ts # Remote JWKS (5-min cooldown)
162+
│ │ ├── high.ts # createToken, checkAuth, policy
163+
│ │ └── adapters/hono.ts # Worker env injection
164+
│ └── flarelette-jwt-py/ # Python implementation (zero deps)
165+
├── examples/ # Usage examples for both languages
166+
├── notes/
167+
│ ├── coding.md # Code quality standards
168+
│ └── tone-of-voice.md # Documentation voice guidelines
169+
└── docs/
170+
├── security.md # Security baseline and threat model
171+
└── usage.md # User-facing usage guide
172+
```
173+
174+
## Environment Variables Reference
175+
176+
**Common:**
177+
178+
- `JWT_ISS` — Token issuer (required)
179+
- `JWT_AUD` — Token audience (required)
180+
- `JWT_TTL_SECONDS` — Token lifetime (default: 900)
181+
- `JWT_LEEWAY` — Clock skew tolerance in seconds (default: 90)
182+
183+
**HS512:**
184+
185+
- `JWT_SECRET` or `JWT_SECRET_NAME` — Symmetric secret (64+ bytes, base64url)
186+
187+
**EdDSA Producer:**
188+
189+
- `JWT_PRIVATE_JWK` or `JWT_PRIVATE_JWK_NAME` — Ed25519 private key (JSON)
190+
- `JWT_KID` — Key ID for rotation (required)
191+
192+
**EdDSA Consumer:**
193+
194+
- `JWT_PUBLIC_JWK` or `JWT_PUBLIC_JWK_NAME` — Ed25519 public key (JSON, inline)
195+
- `JWT_JWKS_SERVICE` or `JWT_JWKS_SERVICE_NAME` — Service binding for JWKS (TypeScript only)
196+
- `JWT_ALLOWED_THUMBPRINTS` — Comma-separated thumbprint whitelist (optional)
197+
198+
## Testing
199+
200+
**Current state:** No test suite exists yet.
201+
202+
**When implementing:**
203+
204+
- Test both HS512 and EdDSA modes via environment injection
205+
- Verify cross-language parity (same inputs → same outputs)
206+
- Cover authorization policy evaluation (roles, permissions, predicates)
207+
- Test secret-name indirection resolution
208+
- Test JWKS caching (TypeScript only)
209+
- Validate claim enforcement
210+
- Test signature verification (positive and negative cases)
211+
212+
## Additional Resources
213+
214+
- **docs/security.md** — Security baseline and threat model
215+
- **docs/usage.md** — User-facing usage documentation

.github/release-please-config.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"packages": {
3+
"packages/flarelette-jwt-ts": {
4+
"release-type": "node",
5+
"package-name": "@chrislyons-dev/flarelette-jwt",
6+
"bump-minor-pre-major": true,
7+
"bump-patch-for-minor-pre-major": true,
8+
"changelog-sections": [
9+
{ "type": "feat", "section": "Features" },
10+
{ "type": "fix", "section": "Bug Fixes" },
11+
{ "type": "perf", "section": "Performance Improvements" },
12+
{ "type": "revert", "section": "Reverts" },
13+
{ "type": "docs", "section": "Documentation" },
14+
{ "type": "refactor", "section": "Code Refactoring" },
15+
{ "type": "test", "section": "Tests", "hidden": false },
16+
{ "type": "chore", "section": "Miscellaneous", "hidden": false }
17+
]
18+
},
19+
"packages/flarelette-jwt-py": {
20+
"release-type": "python",
21+
"package-name": "flarelette-jwt",
22+
"bump-minor-pre-major": true,
23+
"bump-patch-for-minor-pre-major": true,
24+
"changelog-sections": [
25+
{ "type": "feat", "section": "Features" },
26+
{ "type": "fix", "section": "Bug Fixes" },
27+
{ "type": "perf", "section": "Performance Improvements" },
28+
{ "type": "revert", "section": "Reverts" },
29+
{ "type": "docs", "section": "Documentation" },
30+
{ "type": "refactor", "section": "Code Refactoring" },
31+
{ "type": "test", "section": "Tests", "hidden": false },
32+
{ "type": "chore", "section": "Miscellaneous", "hidden": false }
33+
]
34+
}
35+
},
36+
"bootstrap-sha": "HEAD"
37+
}

0 commit comments

Comments
 (0)