Monorepo for Aesthetic.Computer aesthetic.computer
at main 194 lines 6.5 kB view raw
1// auth-cli-callback.mjs, 25.01.11 2// OAuth callback endpoint for CLI authentication 3// Receives authorization code and returns tokens via simple HTML page 4 5import { respond } from "../../backend/http.mjs"; 6 7export async function handler(event, context) { 8 const params = new URLSearchParams(event.rawQuery || ""); 9 const code = params.get("code"); 10 const state = params.get("state"); 11 const error = params.get("error"); 12 const errorDescription = params.get("error_description"); 13 14 // Handle OAuth errors 15 if (error) { 16 return respond(400, null, ` 17 <!DOCTYPE html> 18 <html> 19 <head> 20 <title>Auth Failed - Aesthetic Computer</title> 21 <style> 22 body { font-family: monospace; max-width: 600px; margin: 50px auto; padding: 20px; } 23 .error { color: red; } 24 code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; } 25 </style> 26 </head> 27 <body> 28 <h1>❌ Authentication Failed</h1> 29 <p class="error"><strong>Error:</strong> ${error}</p> 30 <p><strong>Description:</strong> ${errorDescription || "Unknown error"}</p> 31 <hr> 32 <p>You can close this window and try again in your terminal.</p> 33 </body> 34 </html> 35 `, { "content-type": "text/html" }); 36 } 37 38 // Validate required parameters 39 if (!code || !state) { 40 return respond(400, null, ` 41 <!DOCTYPE html> 42 <html> 43 <head> 44 <title>Invalid Request - Aesthetic Computer</title> 45 <style> 46 body { font-family: monospace; max-width: 600px; margin: 50px auto; padding: 20px; } 47 </style> 48 </head> 49 <body> 50 <h1>⚠️ Invalid Request</h1> 51 <p>Missing required parameters (code or state).</p> 52 <p>This endpoint should only be accessed via OAuth redirect.</p> 53 </body> 54 </html> 55 `, { "content-type": "text/html" }); 56 } 57 58 // Exchange code for tokens 59 const AUTH0_DOMAIN = process.env.AUTH0_DOMAIN; 60 const AUTH0_CLIENT_ID = process.env.AUTH0_CLIENT_ID; 61 const AUTH0_CLIENT_SECRET = process.env.AUTH0_CLIENT_SECRET; 62 const REDIRECT_URI = `https://aesthetic.computer/api/auth/cli-callback`; 63 64 try { 65 const tokenResponse = await fetch(`https://${AUTH0_DOMAIN}/oauth/token`, { 66 method: "POST", 67 headers: { "Content-Type": "application/json" }, 68 body: JSON.stringify({ 69 grant_type: "authorization_code", 70 client_id: AUTH0_CLIENT_ID, 71 client_secret: AUTH0_CLIENT_SECRET, 72 code: code, 73 redirect_uri: REDIRECT_URI, 74 }), 75 }); 76 77 if (!tokenResponse.ok) { 78 const errorData = await tokenResponse.text(); 79 console.error("Token exchange failed:", errorData); 80 81 return respond(500, null, ` 82 <!DOCTYPE html> 83 <html> 84 <head> 85 <title>Token Exchange Failed - Aesthetic Computer</title> 86 <style> 87 body { font-family: monospace; max-width: 600px; margin: 50px auto; padding: 20px; } 88 .error { color: red; } 89 </style> 90 </head> 91 <body> 92 <h1>❌ Token Exchange Failed</h1> 93 <p class="error">Failed to exchange authorization code for tokens.</p> 94 <p>Please try again or check the server logs.</p> 95 </body> 96 </html> 97 `, { "content-type": "text/html" }); 98 } 99 100 const tokens = await tokenResponse.json(); 101 102 // Get user info 103 const userResponse = await fetch(`https://${AUTH0_DOMAIN}/userinfo`, { 104 headers: { Authorization: `Bearer ${tokens.access_token}` }, 105 }); 106 107 const user = userResponse.ok ? await userResponse.json() : {}; 108 109 // Return HTML page with tokens embedded (CLI will parse this) 110 return respond(200, null, ` 111 <!DOCTYPE html> 112 <html> 113 <head> 114 <title>Authentication Success - Aesthetic Computer</title> 115 <style> 116 body { 117 font-family: monospace; 118 max-width: 600px; 119 margin: 50px auto; 120 padding: 20px; 121 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 122 color: white; 123 } 124 .success { 125 background: rgba(255,255,255,0.1); 126 padding: 20px; 127 border-radius: 8px; 128 backdrop-filter: blur(10px); 129 } 130 h1 { margin-top: 0; } 131 code { 132 background: rgba(0,0,0,0.3); 133 padding: 2px 6px; 134 border-radius: 3px; 135 display: inline-block; 136 } 137 .token-data { 138 display: none; 139 } 140 </style> 141 </head> 142 <body> 143 <div class="success"> 144 <h1>✅ Authentication Successful</h1> 145 <p><strong>Welcome!</strong> ${user.email || user.name || "User"}</p> 146 <p>You are now logged in to <strong>Aesthetic Computer</strong>.</p> 147 <hr style="border-color: rgba(255,255,255,0.3);"> 148 <p>You can now <strong>close this window</strong> and return to your terminal.</p> 149 <p style="font-size: 0.9em; opacity: 0.8;">Your CLI tool has received the authentication tokens.</p> 150 </div> 151 152 <!-- Token data for CLI parsing (hidden from user) --> 153 <div class="token-data"> 154 <pre id="token-payload">${JSON.stringify({ tokens, user, state }, null, 2)}</pre> 155 </div> 156 157 <script> 158 // Optionally send token data to parent window if CLI opens in iframe 159 if (window.opener) { 160 try { 161 window.opener.postMessage({ 162 type: 'ac-auth-success', 163 data: ${JSON.stringify({ tokens, user, state })} 164 }, '*'); 165 } catch (e) { 166 console.error('Failed to post message to opener:', e); 167 } 168 } 169 </script> 170 </body> 171 </html> 172 `, { "content-type": "text/html" }); 173 174 } catch (err) { 175 console.error("Callback error:", err); 176 return respond(500, null, ` 177 <!DOCTYPE html> 178 <html> 179 <head> 180 <title>Server Error - Aesthetic Computer</title> 181 <style> 182 body { font-family: monospace; max-width: 600px; margin: 50px auto; padding: 20px; } 183 .error { color: red; } 184 </style> 185 </head> 186 <body> 187 <h1>❌ Server Error</h1> 188 <p class="error">An unexpected error occurred during authentication.</p> 189 <p>Please try again or contact support.</p> 190 </body> 191 </html> 192 `, { "content-type": "text/html" }); 193 } 194}