Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

feat: device-token API + ac-usb flash fetches all credentials

New serverless function device-token.mjs generates auth tokens for
a handle using DEVICE_TOKEN_SECRET. ac-usb flash calls it to get
AC auth token + Claude token + GitHub PAT in one shot.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+77 -4
+12 -4
fedac/native/ac-usb
··· 166 166 GITHUB_PAT=$(grep 'oauth_token:' "${HOME}/.config/gh/hosts.yml" 2>/dev/null | head -1 | awk '{print $2}' || true) 167 167 fi 168 168 [ -z "${GITHUB_PAT}" ] && GITHUB_PAT="${GH_TOKEN:-${GITHUB_TOKEN:-}}" 169 - # AC auth token: fetch from device-token API using handle 169 + # AC auth token + device tokens: fetch from device-token API 170 170 AC_TOKEN="" 171 - if [ -n "${HANDLE}" ]; then 172 - AC_TOKEN=$(curl -s "https://aesthetic.computer/.netlify/functions/device-token?handle=${HANDLE}" 2>/dev/null \ 173 - | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);process.stdout.write(j.token||'')}catch{}})" 2>/dev/null || true) 171 + DEVICE_TOKEN_SECRET="${DEVICE_TOKEN_SECRET:-}" 172 + if [ -n "${HANDLE}" ] && [ -n "${DEVICE_TOKEN_SECRET}" ]; then 173 + DEVICE_JSON=$(curl -s "https://aesthetic.computer/.netlify/functions/device-token?handle=${HANDLE}&secret=${DEVICE_TOKEN_SECRET}" 2>/dev/null || true) 174 + AC_TOKEN=$(echo "${DEVICE_JSON}" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);process.stdout.write(j.token||'')}catch{}})" 2>/dev/null || true) 175 + # Also grab Claude/GitHub tokens from API if not already set 176 + if [ -z "${CLAUDE_TOKEN}" ]; then 177 + CLAUDE_TOKEN=$(echo "${DEVICE_JSON}" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);process.stdout.write(j.claudeToken||'')}catch{}})" 2>/dev/null || true) 178 + fi 179 + if [ -z "${GITHUB_PAT}" ]; then 180 + GITHUB_PAT=$(echo "${DEVICE_JSON}" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);process.stdout.write(j.githubPat||'')}catch{}})" 2>/dev/null || true) 181 + fi 174 182 [ -n "${AC_TOKEN}" ] && echo " AC auth token: yes (${#AC_TOKEN} chars)" 175 183 fi 176 184 # Always write config.json with at least the handle
+65
system/netlify/functions/device-token.mjs
··· 1 + // device-token.mjs — Generate device auth tokens for ac-native flash 2 + // GET ?handle=jeffrey&secret=<DEVICE_TOKEN_SECRET> 3 + // Returns: { token, handle, sub, email, claudeToken, githubPat } 4 + // 5 + // Used by ac-usb flash to pre-populate config.json with all credentials. 6 + // The secret prevents unauthorized token generation. 7 + 8 + import { connect } from "../../backend/database.mjs"; 9 + import { respond } from "../../backend/http.mjs"; 10 + import crypto from "crypto"; 11 + 12 + export async function handler(event) { 13 + if (event.httpMethod === "OPTIONS") return respond(200, ""); 14 + if (event.httpMethod !== "GET") return respond(405, { message: "GET only" }); 15 + 16 + const handle = event.queryStringParameters?.handle; 17 + const secret = event.queryStringParameters?.secret; 18 + const expectedSecret = process.env.DEVICE_TOKEN_SECRET; 19 + 20 + if (!handle) return respond(400, { message: "Missing handle" }); 21 + if (!secret || !expectedSecret || secret !== expectedSecret) { 22 + return respond(401, { message: "unauthorized" }); 23 + } 24 + 25 + const database = await connect(); 26 + try { 27 + // Look up handle → sub 28 + const handleDoc = await database.db 29 + .collection("@handles") 30 + .findOne({ handle }); 31 + 32 + if (!handleDoc) return respond(404, { message: "Handle not found" }); 33 + 34 + const sub = handleDoc._id; // _id is the auth0 sub 35 + 36 + // Look up user for email 37 + const userDoc = await database.db 38 + .collection("@users") 39 + .findOne({ _id: sub }); 40 + 41 + // Look up device tokens 42 + const tokenDoc = await database.db 43 + .collection("device-tokens") 44 + .findOne({ _id: sub }); 45 + 46 + // Generate a fresh device auth token (signed with secret + timestamp) 47 + const ts = Date.now().toString(36); 48 + const token = crypto 49 + .createHmac("sha256", expectedSecret) 50 + .update(`${sub}:${ts}`) 51 + .digest("hex") 52 + .slice(0, 32); 53 + 54 + return respond(200, { 55 + handle, 56 + sub, 57 + email: userDoc?.email || "", 58 + token: `${token}.${ts}`, 59 + claudeToken: tokenDoc?.claudeToken || null, 60 + githubPat: tokenDoc?.githubPat || null, 61 + }); 62 + } finally { 63 + await database.disconnect(); 64 + } 65 + }