← back to AbramsOS
routes/auth.js
82 lines
const express = require('express');
const crypto = require('crypto');
const db = require('../lib/db');
const audit = require('../lib/audit');
const { id } = require('../lib/ids');
const { encrypt } = require('../lib/crypto');
const { authUrl, exchange, SCOPES } = require('../lib/google-oauth');
const router = express.Router();
const DEV_USER_ID = 'user_steve';
router.get('/auth/google/start', (req, res) => {
try {
const state = crypto.randomBytes(16).toString('hex');
req.session = req.session || {};
req.session.oauthState = state;
res.redirect(authUrl(state));
} catch (err) {
res.status(500).render('error', { error: err.message });
}
});
router.get('/auth/google/callback', async (req, res) => {
const { code, state, error } = req.query;
if (error) return res.status(400).render('error', { error: `Google returned: ${error}` });
if (!code) return res.status(400).render('error', { error: 'Missing authorization code' });
if (req.session?.oauthState && req.session.oauthState !== state) {
return res.status(400).render('error', { error: 'OAuth state mismatch (possible CSRF)' });
}
try {
const tokens = await exchange(code);
if (!tokens.refreshToken) {
return res.status(400).render('error', {
error: 'Google did not return a refresh token. Revoke the app from your Google Account → Security → Third-party access, then try again.',
});
}
const consentId = id('consent');
const connectorId = id('connector');
const enc = encrypt(tokens.refreshToken);
await db.tx(async (client) => {
await client.query(
`INSERT INTO consent_grant (id, user_id, connector_type, scopes_json)
VALUES ($1, $2, $3, $4)`,
[consentId, DEV_USER_ID, 'gmail', JSON.stringify(SCOPES)]
);
await client.query(
`INSERT INTO connector_account
(id, user_id, consent_grant_id, provider, external_subject_id,
refresh_token_encrypted, refresh_token_iv, refresh_token_tag)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT (provider, external_subject_id) DO UPDATE SET
refresh_token_encrypted = EXCLUDED.refresh_token_encrypted,
refresh_token_iv = EXCLUDED.refresh_token_iv,
refresh_token_tag = EXCLUDED.refresh_token_tag,
consent_grant_id = EXCLUDED.consent_grant_id`,
[connectorId, DEV_USER_ID, consentId, 'google', tokens.email, enc.ciphertext, enc.iv, enc.tag]
);
});
await audit.log({
actorType: 'user',
actorId: DEV_USER_ID,
objectType: 'connector_account',
objectId: connectorId,
eventType: 'oauth_grant',
metadata: { provider: 'google', email: tokens.email, scopes: SCOPES },
});
res.redirect('/connectors');
} catch (err) {
console.error('[oauth callback]', err);
res.status(500).render('error', { error: err.message });
}
});
module.exports = router;