File size: 3,916 Bytes
cf87afd
 
 
 
 
bed4e30
 
adb3b9e
 
bed4e30
cf87afd
 
bed4e30
2b3949f
 
 
 
cf87afd
bed4e30
cf87afd
 
bed4e30
cf87afd
 
 
bed4e30
cf87afd
 
 
 
 
 
bed4e30
cf87afd
bed4e30
cf87afd
 
bed4e30
 
cf87afd
 
 
 
 
 
 
 
bed4e30
 
 
cf87afd
bed4e30
cf87afd
 
 
bed4e30
cf87afd
 
 
 
6ce04ff
cf87afd
87d6c49
cf87afd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87d6c49
cf87afd
 
bed4e30
d798d35
cf87afd
 
 
 
 
 
d798d35
cf87afd
 
d798d35
 
cf87afd
 
 
 
bed4e30
cf87afd
 
 
 
 
 
 
bed4e30
cf87afd
 
 
6ce04ff
bed4e30
 
adb3b9e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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"));