this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(oauth): defensive JWK env parsing + clearer key gen output

- Auto-strip surrounding single/double quotes from OAUTH_PRIVATE_JWK and
OAUTH_PUBLIC_JWK before parsing, and reject values that aren't shaped
like a JSON object with a clear actionable error (length, head, tail,
fix instructions). This catches the common mistake where shell quotes
get stripped along with the leading/trailing braces during copy/paste
into hosted env UIs.
- Apply the same parser to the public JWKS route so /oauth/jwks.json
surfaces the same descriptive error.
- generate-oauth-key.ts now prints values WITHOUT shell quotes so what
the user copies is exactly what should be stored, removing the temptation
to strip quotes (and accidentally take the braces with them).
- .env.example clarifies the format and warns against extra wrapping
quotes.

Made-with: Cursor

+76 -12
+7 -2
.env.example
··· 11 11 # ATMOSPHERE_DID= 12 12 13 13 # OAuth confidential client — run: deno task gen:oauth-key 14 - # OAUTH_PRIVATE_JWK= 15 - # OAUTH_PUBLIC_JWK= 14 + # 15 + # IMPORTANT: paste the values EXACTLY as printed by the script. The JWK 16 + # values are JSON objects starting with `{` and ending with `}`. Do NOT 17 + # wrap them in extra quotes. For Deno Deploy / hosted env UIs, paste the 18 + # raw JSON object — leave the braces in place. 19 + # OAUTH_PRIVATE_JWK={"kty":"EC",...,"kid":"..."} 20 + # OAUTH_PUBLIC_JWK={"kty":"EC",...,"kid":"..."} 16 21 # OAUTH_KID= 17 22 # SESSION_SECRET= 18 23
+46 -1
lib/jose.ts
··· 69 69 ); 70 70 } 71 71 72 + /** 73 + * Strip common wrapping artifacts that creep in when env values are 74 + * pasted between shells, .env files, and provider UIs: 75 + * - leading/trailing whitespace 76 + * - matched surrounding single or double quotes 77 + * Then validate the result looks like a JSON object before parsing. 78 + * 79 + * This catches the most common production foot-gun where a JWK env 80 + * value loses its outer quotes (and sometimes its braces) during 81 + * copy/paste, and replaces an opaque `JSON.parse` error with a clear, 82 + * actionable one. 83 + */ 84 + export function parseJwkEnv(varName: string, raw: string): JsonWebKey { 85 + let s = raw.trim(); 86 + if ( 87 + s.length >= 2 && 88 + ((s.startsWith("'") && s.endsWith("'")) || 89 + (s.startsWith('"') && s.endsWith('"'))) 90 + ) { 91 + s = s.slice(1, -1).trim(); 92 + } 93 + if (!s.startsWith("{") || !s.endsWith("}")) { 94 + const head = s.slice(0, 30); 95 + const tail = s.slice(-30); 96 + throw new Error( 97 + `${varName} is not a valid JSON object. ` + 98 + `Expected it to start with '{' and end with '}'. ` + 99 + `Got length=${s.length}, starts with: ${JSON.stringify(head)}, ` + 100 + `ends with: ${JSON.stringify(tail)}. ` + 101 + `If you copied this from \`deno task gen:oauth-key\`, paste the ` + 102 + `raw JSON object including the surrounding braces, with no extra ` + 103 + `quotes around it.`, 104 + ); 105 + } 106 + try { 107 + return JSON.parse(s) as JsonWebKey; 108 + } catch (err) { 109 + throw new Error( 110 + `${varName} could not be parsed as JSON: ${ 111 + err instanceof Error ? err.message : String(err) 112 + }`, 113 + ); 114 + } 115 + } 116 + 72 117 export function loadClientPrivateKey( 73 118 privateJwkJson: string, 74 119 ): Promise<CryptoKey> { 75 120 if (!_privateKeyPromise) { 76 - const jwk = JSON.parse(privateJwkJson) as JsonWebKey; 121 + const jwk = parseJwkEnv("OAUTH_PRIVATE_JWK", privateJwkJson); 77 122 _privateKeyPromise = importEs256PrivateKey(jwk); 78 123 } 79 124 return _privateKeyPromise;
+6 -3
routes/oauth/jwks.json.ts
··· 5 5 */ 6 6 import { define } from "../../utils.ts"; 7 7 import { OAUTH_PUBLIC_JWK } from "../../lib/env.ts"; 8 + import { parseJwkEnv } from "../../lib/jose.ts"; 8 9 9 10 export const handler = define.handlers({ 10 11 GET(): Response { ··· 19 20 } 20 21 let key: unknown; 21 22 try { 22 - key = JSON.parse(OAUTH_PUBLIC_JWK); 23 - } catch { 23 + key = parseJwkEnv("OAUTH_PUBLIC_JWK", OAUTH_PUBLIC_JWK); 24 + } catch (err) { 24 25 return new Response( 25 - JSON.stringify({ error: "OAUTH_PUBLIC_JWK is not valid JSON" }), 26 + JSON.stringify({ 27 + error: err instanceof Error ? err.message : String(err), 28 + }), 26 29 { status: 500, headers: { "content-type": "application/json" } }, 27 30 ); 28 31 }
+17 -6
scripts/generate-oauth-key.ts
··· 44 44 const priv = enrich(privateJwk); 45 45 const pub = enrich(publicJwk); 46 46 47 - console.log("# Add the following to your environment (.env.local for dev,"); 48 - console.log("# Deno Deploy project secrets for production)."); 47 + console.log("# Add the following to your environment."); 48 + console.log("# - For .env / .env.local: paste the lines below as-is."); 49 + console.log( 50 + "# - For Deno Deploy / Vercel project secrets: paste only the value", 51 + ); 52 + console.log( 53 + "# to the right of '=' (the raw JSON object including the braces).", 54 + ); 49 55 console.log("# DO NOT commit OAUTH_PRIVATE_JWK to source control.\n"); 50 - console.log(`OAUTH_PRIVATE_JWK='${JSON.stringify(priv)}'`); 51 - console.log(`OAUTH_PUBLIC_JWK='${JSON.stringify(pub)}'`); 52 - console.log(`OAUTH_KID='${kid}'`); 56 + /** Print values WITHOUT shell quotes. The raw JSON has no whitespace 57 + * (JSON.stringify packs tightly) so it's a valid bare value for any 58 + * reasonable .env parser, and it removes any temptation to strip 59 + * surrounding quotes (which several users have accidentally done by 60 + * also stripping the leading `{` / trailing `}`). */ 61 + console.log(`OAUTH_PRIVATE_JWK=${JSON.stringify(priv)}`); 62 + console.log(`OAUTH_PUBLIC_JWK=${JSON.stringify(pub)}`); 63 + console.log(`OAUTH_KID=${kid}`); 53 64 console.log(); 54 65 console.log( 55 66 "# Optional: a 32+ byte random string for signing session cookies.", ··· 57 68 const secret = bufToBase64Url( 58 69 crypto.getRandomValues(new Uint8Array(32)).buffer, 59 70 ); 60 - console.log(`SESSION_SECRET='${secret}'`); 71 + console.log(`SESSION_SECRET=${secret}`); 61 72 } 62 73 63 74 if (import.meta.main) {