|
1 | | -// Demo script: simulates user sign-in, session, proxy call, stats fetch |
2 | | -// Usage: deno run -A backend/scripts/demo_user_flow.ts |
3 | | -// Reads backend/.env or prompts for BASE_URL/APP_BASE_URL and USER_JWT. |
| 1 | +import { loadCombinedEnv, normalizeBackendBase, getJwt, headersWithJwt } from './lib/env_auth.ts'; |
4 | 2 |
|
5 | | -type EnvMap = Record<string, string>; |
6 | | -const parseDotenv = (txt: string): EnvMap => { |
7 | | - const out: EnvMap = {}; |
8 | | - const lines = txt.split(/\r?\n/); |
9 | | - for (let raw of lines) { |
10 | | - if (!raw) continue; |
11 | | - raw = raw.replace(/^\s*/, ''); |
12 | | - if (!raw || raw.startsWith('#')) continue; |
13 | | - if (raw.startsWith('export ')) raw = raw.slice(7); |
14 | | - const i = raw.indexOf('='); |
15 | | - if (i < 0) continue; |
16 | | - const key = raw.slice(0, i).trim(); |
17 | | - let val = raw.slice(i + 1); |
18 | | - val = val.replace(/^\s+/, ''); |
19 | | - if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith('\'') && val.endsWith('\''))) { |
20 | | - val = val.slice(1, -1) |
21 | | - .replace(/\\n/g, '\n') |
22 | | - .replace(/\\r/g, '\r') |
23 | | - .replace(/\\t/g, '\t') |
24 | | - .replace(/\\"/g, '"') |
25 | | - .replace(/\\'/g, '\'') |
26 | | - .replace(/\\\\/g, '\\'); |
27 | | - } else { |
28 | | - val = val.replace(/\s+$/, ''); |
29 | | - } |
30 | | - out[key] = val; |
31 | | - } |
32 | | - return out; |
33 | | -}; |
34 | | -const loadEnvFile = async (path: string): Promise<EnvMap> => { |
35 | | - try { return parseDotenv(await Deno.readTextFile(path)); } catch { return {}; } |
36 | | -}; |
37 | | -const envFiles = [ './backend/.env', './.env' ]; |
38 | | -const envFromFiles: EnvMap = (await Promise.all(envFiles.map(loadEnvFile))).reduce((a,b)=>({...a,...b}),{}); |
39 | | -const get = (k: string) => Deno.env.get(k) ?? envFromFiles[k]; |
40 | | - |
41 | | -let BASE = (get('BASE_URL') || '').replace(/\/$/, ''); |
42 | | -const appBase = (get('APP_BASE_URL') || '').replace(/\/$/, ''); |
43 | | -if (BASE.endsWith('/openai')) BASE = BASE.slice(0, -'/openai'.length); |
44 | | -if (!BASE && appBase) BASE = appBase.replace(/\/openai$/, ''); |
45 | | -let USER_JWT = get('USER_JWT'); |
46 | | -let SUPABASE_URL = get('SUPABASE_URL'); |
47 | | -let SUPABASE_ANON_KEY = get('SUPABASE_ANON_KEY'); |
48 | | -let EMAIL = get('EMAIL'); |
49 | | -let PASSWORD = get('PASSWORD'); |
| 3 | +const env = await loadCombinedEnv(); |
| 4 | +let BASE = normalizeBackendBase(env); |
50 | 5 | if (!BASE) BASE = (prompt('Backend base URL (e.g., https://<ref>.functions.supabase.co):') ?? '').replace(/\/$/, ''); |
51 | | -if (!USER_JWT) { |
52 | | - // Try to login interactively |
53 | | - if (!SUPABASE_URL) SUPABASE_URL = prompt('Supabase URL (https://<ref>.supabase.co):') ?? ''; |
54 | | - if (!SUPABASE_ANON_KEY) SUPABASE_ANON_KEY = prompt('Supabase anon key:') ?? ''; |
55 | | - if (!EMAIL) EMAIL = prompt('Email:') ?? ''; |
56 | | - if (!PASSWORD) PASSWORD = prompt('Password (input hidden not supported):') ?? ''; |
57 | | -} |
58 | | -if (!USER_JWT && SUPABASE_URL && SUPABASE_ANON_KEY && EMAIL && PASSWORD) { |
59 | | - const endpoint = `${SUPABASE_URL.replace(/\/$/, '')}/auth/v1/token?grant_type=password`; |
60 | | - const authRes = await fetch(endpoint, { |
61 | | - method: 'POST', |
62 | | - headers: { 'Content-Type': 'application/json', apikey: SUPABASE_ANON_KEY }, |
63 | | - body: JSON.stringify({ email: EMAIL, password: PASSWORD }), |
64 | | - }); |
65 | | - if (authRes.ok) { |
66 | | - const data = await authRes.json(); |
67 | | - USER_JWT = data?.access_token ?? ''; |
68 | | - } |
69 | | -} |
70 | | -if (!BASE || !USER_JWT) { |
71 | | - console.error('Missing inputs. Need BASE_URL (or APP_BASE_URL) and USER_JWT.'); |
72 | | - Deno.exit(1); |
73 | | -} |
| 6 | +let USER_JWT = await getJwt(env, 'user'); |
| 7 | +if (!BASE || !USER_JWT) { console.error('Missing inputs. Need BASE and USER_JWT.'); Deno.exit(1); } |
74 | 8 |
|
75 | 9 | console.log('Using BASE =', BASE); |
76 | 10 | console.log('USER_JWT prefix =', USER_JWT.slice(0, 12) + '…'); |
77 | 11 |
|
78 | | -const sessionHeaders: Record<string,string> = { Authorization: `Bearer ${USER_JWT}` }; |
79 | | -if (SUPABASE_ANON_KEY) sessionHeaders['apikey'] = SUPABASE_ANON_KEY; |
80 | | -let sessionRes = await fetch(`${BASE}/session`, { method: 'POST', headers: sessionHeaders }); |
| 12 | +let sessionRes = await fetch(`${BASE}/session`, { method: 'POST', headers: headersWithJwt(USER_JWT, env) }); |
81 | 13 | if (!sessionRes.ok) { |
82 | 14 | const body = await sessionRes.text(); |
83 | | - // If Invalid JWT and we have login inputs, attempt one retry to refresh token |
84 | | - if (sessionRes.status === 401 && body.includes('Invalid JWT') && SUPABASE_URL && SUPABASE_ANON_KEY && EMAIL && PASSWORD) { |
85 | | - const endpoint = `${SUPABASE_URL.replace(/\/$/, '')}/auth/v1/token?grant_type=password`; |
86 | | - const authRes = await fetch(endpoint, { |
87 | | - method: 'POST', |
88 | | - headers: { 'Content-Type': 'application/json', apikey: SUPABASE_ANON_KEY }, |
89 | | - body: JSON.stringify({ email: EMAIL, password: PASSWORD }), |
90 | | - }); |
91 | | - if (authRes.ok) { |
92 | | - const data = await authRes.json(); |
93 | | - USER_JWT = data?.access_token ?? USER_JWT; |
94 | | - const hdrs: Record<string,string> = { Authorization: `Bearer ${USER_JWT}` }; |
95 | | - if (SUPABASE_ANON_KEY) hdrs['apikey'] = SUPABASE_ANON_KEY; |
96 | | - sessionRes = await fetch(`${BASE}/session`, { method: 'POST', headers: hdrs }); |
97 | | - } |
| 15 | + if (sessionRes.status === 401 && body.includes('Invalid JWT')) { |
| 16 | + USER_JWT = await getJwt(env, 'user'); |
| 17 | + sessionRes = await fetch(`${BASE}/session`, { method: 'POST', headers: headersWithJwt(USER_JWT, env) }); |
98 | 18 | } |
99 | 19 | if (!sessionRes.ok) { |
100 | 20 | console.error('Session failed:', sessionRes.status, body); |
|
0 commit comments