everydaycats's picture
Update app.js
2b3949f verified
import express from 'express';
import { createClient } from '@supabase/supabase-js';
import jwt from 'jsonwebtoken';
import { v4 as uuidv4 } from 'uuid';
import cors from 'cors';
const app = express();
const supabase = createClient(process.env.SUPABASE_URL,
process.env.SUPABASE_SERVICE_ROLE_KEY);
app.use(cors());
app.use(express.json());
app.get('/', (req, res) => res.send('Gateway Active'));
const tempKeys = new Map(); // key -> { uid, projectId, createdAt }
// --- MIDDLEWARE: AUTHENTICATION ---
const verifySupabaseSession = async (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) return res.status(401).json({ error: 'Unauthorized' });
const idToken = authHeader.split(' ')[1];
try {
const { data: { user }, error } = await supabase.auth.getUser(idToken);
if (error || !user) throw new Error("Invalid Session");
req.user = user;
next();
} catch (err) {
return res.status(403).json({ error: 'Session Expired', details: err.message });
}
};
// 1. GENERATE TEMP KEY (Called by Auth Page)
app.post('/key', verifySupabaseSession, (req, res) => {
const { projectId } = req.body;
const key = `key_${uuidv4().replace(/-/g, '')}`;
// Store key with 5-minute expiry
tempKeys.set(key, {
uid: req.user.id,
projectId: projectId || 'default',
createdAt: Date.now()
});
res.json({ key, expiresIn: 300 });
});
// 2. REDEEM KEY (Called by Local CLI)
app.post('/redeem', async (req, res) => {
const { key, deviceName } = req.body;
if (!tempKeys.has(key)) return res.status(404).json({ error: 'Key invalid' });
const data = tempKeys.get(key);
if (Date.now() - data.createdAt > 300000) {
tempKeys.delete(key);
return res.status(410).json({ error: 'Key expired' });
}
const sessionSecret = uuidv4();
// Create persistent session in DB
const { data: session, error } = await supabase
.from('user_sessions')
.insert({
user_id: data.uid,
project_id: data.projectId,
session_secret: sessionSecret,
device_name: deviceName || 'Unknown Device'
})
.select()
.single();
if (error) return res.status(500).json({ error: "Failed to create session" });
// Sign JWT using the unique session secret
// Payload contains the session ID so Gateway can verify DB existence
const token = jwt.sign(
{ uid: data.uid, projectId: data.projectId, sid: session.id },
sessionSecret,
{ expiresIn: '30d' }
);
tempKeys.delete(key);
res.json({ token });
});
// 3. LIST ACTIVE SESSIONS (Called by Web Console)
app.get('/sessions', verifySupabaseSession, async (req, res) => {
const { data, error } = await supabase
.from('user_sessions')
.select('id, project_id, device_name, created_at, last_used_at')
.eq('user_id', req.user.id);
if (error) return res.status(500).json({ error: error.message });
res.json(data);
});
// 4. REVOKE SESSION (Called by Web Console)
app.post('/revoke', verifySupabaseSession, async (req, res) => {
const { sessionId } = req.body;
if (!sessionId) return res.status(400).json({ error: "Session ID required" });
// RLS ensures the user can only delete their own,
// but we add an explicit check for safety.
const { error } = await supabase
.from('user_sessions')
.delete()
.eq('id', sessionId)
.eq('user_id', req.user.id);
if (error) return res.status(500).json({ error: "Revocation failed" });
console.log(`๐Ÿ—‘๏ธ Session ${sessionId} revoked by user ${req.user.id}`);
res.json({ success: true });
});
app.listen(7860, () => console.log("๐Ÿš€ Auth Proxy: Secure & Revocable"));