import express from 'express';
import session from 'express-session';
import crypto from 'crypto';
import 'dotenv/config';
const app = express();
app.use(express.json());
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
}));
const MONEI_BASE = 'https://api.monei.cc/api/v1';
// ─── Step 1: Redirect user to Monei ──────────────────────────────────────────
app.get('/connect-monei', (req, res) => {
// Generate a random state value to prevent CSRF attacks
const state = crypto.randomBytes(16).toString('hex');
req.session.oauthState = state;
const params = new URLSearchParams({
client_id: process.env.MONEI_CLIENT_ID,
redirect_uri: process.env.MONEI_REDIRECT_URI,
scope: 'wallet:read profile:read',
state,
});
res.redirect(`https://monei.cc/connect/authorize?${params}`);
});
// ─── Step 2: Handle the callback ─────────────────────────────────────────────
app.get('/monei/callback', async (req, res) => {
const { code, state, error } = req.query;
// User denied access
if (error) {
return res.redirect('/dashboard?error=access_denied');
}
// Always validate state to prevent CSRF
if (state !== req.session.oauthState) {
return res.status(400).send('Invalid state parameter');
}
// ─── Step 3: Exchange code for tokens (server-side only) ─────────────────
const tokenRes = await fetch(`${MONEI_BASE}/connect/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
code,
client_id: process.env.MONEI_CLIENT_ID,
client_secret: process.env.MONEI_CLIENT_SECRET,
redirect_uri: process.env.MONEI_REDIRECT_URI,
}),
});
const tokens = await tokenRes.json();
if (!tokenRes.ok) {
console.error('Token exchange failed:', tokens);
return res.redirect('/dashboard?error=token_exchange_failed');
}
// Store exactly what was granted — user may have approved fewer scopes
req.session.moneiAccessToken = tokens.access_token;
req.session.moneiRefreshToken = tokens.refresh_token;
req.session.moneiTokenExpiry = Date.now() + (tokens.expires_in * 1000);
req.session.moneiScopes = tokens.scopes; // what was actually granted
res.redirect('/dashboard?connected=true');
});
// ─── Step 4: Call the Monei API on behalf of the user ────────────────────────
app.get('/dashboard', async (req, res) => {
const token = req.session.moneiAccessToken;
if (!token) {
return res.json({ connected: false });
}
const walletRes = await fetch(`${MONEI_BASE}/wallet/me`, {
headers: { Authorization: `Bearer ${token}` },
});
const wallet = await walletRes.json();
res.json({
connected: true,
scopes: req.session.moneiScopes,
wallet: wallet.data,
});
});
app.listen(3000, () => console.log('Running on http://localhost:3000'));