// server.js — Node.js + Express
import express from "express";
import cookieParser from "cookie-parser";
import jwt from "jsonwebtoken";
const app = express();
app.use(express.json());
app.use(cookieParser());
const APTLY_BASE = "https://core-api.getaptly.com";
const APTLY_API_KEY = process.env.APTLY_API_KEY; // never expose this to the browser
const APTLY_BOARD_ID = process.env.APTLY_BOARD_ID;
const SESSION_SECRET = process.env.SESSION_SECRET; // long random string, server-only
const SESSION_TTL = "4h";
function issueSessionToken(contact) {
return jwt.sign(
{ sub: contact._id, email: contact.email, name: contact.fullName },
SESSION_SECRET,
{ expiresIn: SESSION_TTL, algorithm: "HS256" },
);
}
function verifySessionToken(token) {
return jwt.verify(token, SESSION_SECRET, { algorithms: ["HS256"] });
}
function requireContact(req, res, next) {
const token = req.cookies.session;
if (!token) return res.status(401).json({ error: "Not authenticated." });
try {
req.contactId = verifySessionToken(token).sub;
next();
} catch {
res.status(401).json({ error: "Session expired or invalid." });
}
}
// Step 1 — initiate verification
app.post("/auth/start", async (req, res) => {
const { email } = req.body;
const aptlyRes = await fetch(`${APTLY_BASE}/api/contacts/verify-email`, {
method: "POST",
headers: { "x-token": APTLY_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ email }),
});
if (aptlyRes.status === 404)
return res.status(404).json({ error: "No account found." });
if (!aptlyRes.ok)
return res.status(500).json({ error: "Could not send code." });
const { requestId } = await aptlyRes.json();
res.json({ requestId });
});
// Step 2 — confirm code and issue session cookie
app.post("/auth/confirm", async (req, res) => {
const { requestId, code } = req.body;
const aptlyRes = await fetch(
`${APTLY_BASE}/api/contacts/verify-email/${requestId}/confirm`,
{
method: "POST",
headers: { "x-token": APTLY_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ code }),
},
);
if (!aptlyRes.ok)
return res.status(401).json({ error: "Invalid or expired code." });
const { contacts } = await aptlyRes.json();
const contact = contacts[0];
res.cookie("session", issueSessionToken(contact), {
httpOnly: true,
secure: true,
sameSite: "lax",
maxAge: 4 * 60 * 60 * 1000,
});
res.json({ name: contact.fullName, email: contact.email });
});
// Check existing session (called on page load)
app.get("/auth/me", requireContact, (req, res) => {
const payload = verifySessionToken(req.cookies.session);
res.json({ name: payload.name, email: payload.email, loggedIn: true });
});
// Logout
app.post("/auth/logout", (req, res) => {
res.clearCookie("session");
res.json({ loggedIn: false });
});
// Protected route — load cards for the verified contact
app.get("/portal/cards", requireContact, async (req, res) => {
const aptlyRes = await fetch(
`${APTLY_BASE}/api/board/${APTLY_BOARD_ID}?page=0&relatedId=${req.contactId}`,
{ headers: { "x-token": APTLY_API_KEY } },
);
if (!aptlyRes.ok)
return res.status(502).json({ error: "Failed to load cards." });
res.json(await aptlyRes.json());
});
app.listen(3000);