ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto

fix: CORS for extension credentialed requests

Updated CORS headers to support credentials from Chrome extensions:
- Added getCorsHeaders() to detect chrome-extension:// origins
- Changed from wildcard Access-Control-Allow-Origin to specific origin
- Added Access-Control-Allow-Credentials: true for credentialed requests
- Updated session endpoint to pass event for CORS header detection

byarielm.fyi 603cf0a1 bd3aabb7

verified
Changed files
+205 -15
docs
packages
functions
+18 -10
docs/git-history.json
··· 1 1 [ 2 2 { 3 + "hash": "bd3aabb75abb1875aef125610fcdccb14967a8e3", 4 + "short_hash": "bd3aabb", 5 + "author": "Ariel M. Lighty", 6 + "date": "2025-12-27T22:10:11-05:00", 7 + "message": "fix: extension dark mode and build mode messaging\n\n- Changed darkMode from 'class' to 'media' for automatic system preference detection\n- Made server offline message conditional on build mode (dev vs prod)\n- Hide dev server instructions in production builds", 8 + "files_changed": 5 9 + }, 10 + { 3 11 "hash": "d07180cd3a19328b82b35118e525b59d4e2e060b", 4 12 "short_hash": "d07180c", 5 13 "author": "Ariel M. Lighty", ··· 8 16 "files_changed": 9 9 17 }, 10 18 { 11 - "hash": "6ac877ee6d1990bdab4ef3b03ac59f4682afe0a5", 12 - "short_hash": "6ac877e", 19 + "hash": "d07180cd3a19328b82b35118e525b59d4e2e060b", 20 + "short_hash": "d07180c", 13 21 "author": "Ariel M. Lighty", 14 - "date": "2025-12-27T16:03:56-05:00", 15 - "message": "docs: update decision graph after markdown updates", 16 - "files_changed": 2 22 + "date": "2025-12-27T18:38:39-05:00", 23 + "message": "feat: add Tailwind CSS to extension\n\nReplaced 299 lines of vanilla CSS with Tailwind for design consistency with web app. Production build minified to 13KB.", 24 + "files_changed": 9 17 25 }, 18 26 { 19 27 "hash": "fe29bb3e5faa0151f63c14724f7509af669860de", ··· 32 40 "files_changed": 4 33 41 }, 34 42 { 35 - "hash": "46bab1202f69d8d03093a20bc8ada5ad6d365401", 36 - "short_hash": "46bab12", 43 + "hash": "fcf682bb8969aca108262348e7e17531077713be", 44 + "short_hash": "fcf682b", 37 45 "author": "Ariel M. Lighty", 38 - "date": "2025-12-26T22:10:11-05:00", 39 - "message": "docs: update decision graph after Vite optimization", 40 - "files_changed": 2 46 + "date": "2025-12-27T15:48:44-05:00", 47 + "message": "docs: improve decision graph workflow with lifecycle management\n\nUpdated CLAUDE.md with comprehensive node lifecycle management:\n- Added node status transitions (pending → in_progress → completed)\n- Correct orphan detection commands (awk instead of cut)\n- Common mistakes section with examples\n- Enhanced audit checklist with status verification\n- Verification workflow after node creation\n\nAlso updated extension popup with ATmosphere branding.\n\nDecision graph now at 331 nodes, 332 edges - all orphans resolved.", 48 + "files_changed": 4 41 49 }, 42 50 { 43 51 "hash": "e04934ffb5e2d78791fcd23bc3afeb4d438a5546",
+143
docs/graph-data.json
··· 3937 3937 "created_at": "2025-12-27T22:09:28.843864300-05:00", 3938 3938 "updated_at": "2025-12-27T22:09:32.017503200-05:00", 3939 3939 "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3940 + }, 3941 + { 3942 + "id": 359, 3943 + "change_id": "4a7d5885-1713-4ba7-ad13-bb12b58c9410", 3944 + "node_type": "outcome", 3945 + "title": "Committed fixes to git", 3946 + "description": null, 3947 + "status": "completed", 3948 + "created_at": "2025-12-27T22:10:25.576235500-05:00", 3949 + "updated_at": "2025-12-27T22:10:28.961887300-05:00", 3950 + "metadata_json": "{\"branch\":\"master\",\"commit\":\"bd3aabb\",\"confidence\":95}" 3951 + }, 3952 + { 3953 + "id": 360, 3954 + "change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a", 3955 + "node_type": "goal", 3956 + "title": "Fix extension not detecting login session despite dev server running", 3957 + "description": null, 3958 + "status": "completed", 3959 + "created_at": "2025-12-27T22:23:13.072419900-05:00", 3960 + "updated_at": "2025-12-27T22:41:49.160848100-05:00", 3961 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90,\"prompt\":\"dark mode is fixed, but the extension in /chrome/ uploaded still is saying login with atlast and dev server is running\"}" 3962 + }, 3963 + { 3964 + "id": 361, 3965 + "change_id": "aecf2327-d20d-4c6c-b6b0-06ccf26a2b27", 3966 + "node_type": "observation", 3967 + "title": "Extension dist/chrome contains production build, not dev build. User ran build:prod last.", 3968 + "description": null, 3969 + "status": "completed", 3970 + "created_at": "2025-12-27T22:23:45.918832500-05:00", 3971 + "updated_at": "2025-12-27T22:23:48.919570500-05:00", 3972 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3973 + }, 3974 + { 3975 + "id": 362, 3976 + "change_id": "e897db97-44d8-4993-b4c3-0d829265b2f8", 3977 + "node_type": "observation", 3978 + "title": "Dev build now deployed. Extension will check session at http://127.0.0.1:8888/.netlify/functions/session with credentials:include", 3979 + "description": null, 3980 + "status": "completed", 3981 + "created_at": "2025-12-27T22:24:17.767230200-05:00", 3982 + "updated_at": "2025-12-27T22:24:20.981953100-05:00", 3983 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 3984 + }, 3985 + { 3986 + "id": 363, 3987 + "change_id": "2c62bfa3-d148-4448-8c2b-d0cf1e94ceb0", 3988 + "node_type": "observation", 3989 + "title": "Found CORS issue: successResponse uses 'Access-Control-Allow-Origin: *' which blocks credentialed requests from extension", 3990 + "description": null, 3991 + "status": "completed", 3992 + "created_at": "2025-12-27T22:24:51.861265800-05:00", 3993 + "updated_at": "2025-12-27T22:24:55.482724500-05:00", 3994 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3995 + }, 3996 + { 3997 + "id": 364, 3998 + "change_id": "560d6bea-47ec-408d-919b-15ca7198aac9", 3999 + "node_type": "action", 4000 + "title": "Updating CORS headers to support credentialed requests from extension", 4001 + "description": null, 4002 + "status": "completed", 4003 + "created_at": "2025-12-27T22:25:23.035212700-05:00", 4004 + "updated_at": "2025-12-27T22:26:03.046221900-05:00", 4005 + "metadata_json": "{\"branch\":\"master\",\"confidence\":90}" 4006 + }, 4007 + { 4008 + "id": 365, 4009 + "change_id": "3ef0c9e9-aa40-4914-a5f4-32bcfaf68d04", 4010 + "node_type": "outcome", 4011 + "title": "Fixed CORS to support credentialed requests from extensions", 4012 + "description": null, 4013 + "status": "completed", 4014 + "created_at": "2025-12-27T22:41:38.430661200-05:00", 4015 + "updated_at": "2025-12-27T22:41:48.981429600-05:00", 4016 + "metadata_json": "{\"branch\":\"master\",\"confidence\":95}" 3940 4017 } 3941 4018 ], 3942 4019 "edges": [ ··· 7866 7943 "weight": 1.0, 7867 7944 "rationale": "Final outcome of fixes", 7868 7945 "created_at": "2025-12-27T22:09:30.425884400-05:00" 7946 + }, 7947 + { 7948 + "id": 358, 7949 + "from_node_id": 352, 7950 + "to_node_id": 359, 7951 + "from_change_id": "b852ce18-1747-4c26-a65e-acfbbed2b1a5", 7952 + "to_change_id": "4a7d5885-1713-4ba7-ad13-bb12b58c9410", 7953 + "edge_type": "leads_to", 7954 + "weight": 1.0, 7955 + "rationale": "Git commit for fixes", 7956 + "created_at": "2025-12-27T22:10:27.225192300-05:00" 7957 + }, 7958 + { 7959 + "id": 359, 7960 + "from_node_id": 360, 7961 + "to_node_id": 361, 7962 + "from_change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a", 7963 + "to_change_id": "aecf2327-d20d-4c6c-b6b0-06ccf26a2b27", 7964 + "edge_type": "leads_to", 7965 + "weight": 1.0, 7966 + "rationale": "Root cause analysis", 7967 + "created_at": "2025-12-27T22:23:47.445630900-05:00" 7968 + }, 7969 + { 7970 + "id": 360, 7971 + "from_node_id": 360, 7972 + "to_node_id": 362, 7973 + "from_change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a", 7974 + "to_change_id": "e897db97-44d8-4993-b4c3-0d829265b2f8", 7975 + "edge_type": "leads_to", 7976 + "weight": 1.0, 7977 + "rationale": "Rebuilt dev version", 7978 + "created_at": "2025-12-27T22:24:19.438433600-05:00" 7979 + }, 7980 + { 7981 + "id": 361, 7982 + "from_node_id": 360, 7983 + "to_node_id": 363, 7984 + "from_change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a", 7985 + "to_change_id": "2c62bfa3-d148-4448-8c2b-d0cf1e94ceb0", 7986 + "edge_type": "leads_to", 7987 + "weight": 1.0, 7988 + "rationale": "Root cause: CORS configuration", 7989 + "created_at": "2025-12-27T22:24:53.741163700-05:00" 7990 + }, 7991 + { 7992 + "id": 362, 7993 + "from_node_id": 360, 7994 + "to_node_id": 364, 7995 + "from_change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a", 7996 + "to_change_id": "560d6bea-47ec-408d-919b-15ca7198aac9", 7997 + "edge_type": "leads_to", 7998 + "weight": 1.0, 7999 + "rationale": "Implementation of CORS fix", 8000 + "created_at": "2025-12-27T22:25:24.843330900-05:00" 8001 + }, 8002 + { 8003 + "id": 363, 8004 + "from_node_id": 360, 8005 + "to_node_id": 365, 8006 + "from_change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a", 8007 + "to_change_id": "3ef0c9e9-aa40-4914-a5f4-32bcfaf68d04", 8008 + "edge_type": "leads_to", 8009 + "weight": 1.0, 8010 + "rationale": "CORS fix completed", 8011 + "created_at": "2025-12-27T22:41:44.160528300-05:00" 7869 8012 } 7870 8013 ] 7871 8014 }
+2 -2
packages/functions/src/session.ts
··· 30 30 return successResponse(cached, 200, { 31 31 "Cache-Control": "private, max-age=300", 32 32 "X-Cache-Status": "HIT", 33 - }); 33 + }, event); 34 34 } 35 35 36 36 const { agent } = await SessionService.getAgentForSession(sessionId, event); ··· 50 50 return successResponse(profileData, 200, { 51 51 "Cache-Control": "private, max-age=300", 52 52 "X-Cache-Status": "MISS", 53 - }); 53 + }, event); 54 54 }; 55 55 56 56 export const handler = withErrorHandling(sessionHandler);
+42 -3
packages/functions/src/utils/response.utils.ts
··· 1 - import { HandlerResponse } from "@netlify/functions"; 1 + import { HandlerResponse, HandlerEvent } from "@netlify/functions"; 2 2 import { ApiResponse } from "../core/types"; 3 3 4 + /** 5 + * Get CORS headers based on request origin 6 + * Supports credentialed requests from extensions and localhost 7 + */ 8 + function getCorsHeaders(event?: HandlerEvent): Record<string, string> { 9 + const origin = event?.headers?.origin || event?.headers?.Origin; 10 + 11 + // Allow all origins for non-credentialed requests (backward compatibility) 12 + if (!origin) { 13 + return { 14 + "Access-Control-Allow-Origin": "*", 15 + }; 16 + } 17 + 18 + // Check if origin is allowed for credentialed requests 19 + const allowedOrigins = [ 20 + 'http://localhost:8888', 21 + 'http://127.0.0.1:8888', 22 + 'https://atlast.byarielm.fyi', 23 + ]; 24 + 25 + const isExtension = origin.startsWith('chrome-extension://'); 26 + const isAllowedOrigin = allowedOrigins.includes(origin); 27 + 28 + if (isExtension || isAllowedOrigin) { 29 + return { 30 + "Access-Control-Allow-Origin": origin, 31 + "Access-Control-Allow-Credentials": "true", 32 + }; 33 + } 34 + 35 + // Default to wildcard for unknown origins 36 + return { 37 + "Access-Control-Allow-Origin": "*", 38 + }; 39 + } 40 + 4 41 export function successResponse<T>( 5 42 data: T, 6 43 statusCode: number = 200, 7 44 additionalHeaders: Record<string, string> = {}, 45 + event?: HandlerEvent, 8 46 ): HandlerResponse { 9 47 const response: ApiResponse<T> = { 10 48 success: true, ··· 15 53 statusCode, 16 54 headers: { 17 55 "Content-Type": "application/json", 18 - "Access-Control-Allow-Origin": "*", 56 + ...getCorsHeaders(event), 19 57 ...additionalHeaders, 20 58 }, 21 59 body: JSON.stringify(response), ··· 26 64 error: string, 27 65 statusCode: number = 500, 28 66 details?: string, 67 + event?: HandlerEvent, 29 68 ): HandlerResponse { 30 69 const response: ApiResponse = { 31 70 success: false, ··· 37 76 statusCode, 38 77 headers: { 39 78 "Content-Type": "application/json", 40 - "Access-Control-Allow-Origin": "*", 79 + ...getCorsHeaders(event), 41 80 }, 42 81 body: JSON.stringify(response), 43 82 };