Persist user login/logout audit in ncue_user
Add first_login_at and last_logout_at, ensure table exists at runtime, upsert user on /api/auth/sync, and record logout via /api/auth/logout from the client. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
60
server.js
60
server.js
@@ -76,8 +76,31 @@ async function verifyIdToken(idToken, { issuer, audience }) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
async function ensureUserTable() {
|
||||
// Create table if missing + add columns for upgrades
|
||||
await pool.query(`
|
||||
create table if not exists public.${TABLE} (
|
||||
sub text primary key,
|
||||
email text,
|
||||
name text,
|
||||
picture text,
|
||||
provider text,
|
||||
first_login_at timestamptz,
|
||||
last_login_at timestamptz,
|
||||
last_logout_at timestamptz,
|
||||
can_manage boolean not null default false,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
)
|
||||
`);
|
||||
await pool.query(`create index if not exists idx_${TABLE}_email on public.${TABLE} (email)`);
|
||||
await pool.query(`alter table public.${TABLE} add column if not exists first_login_at timestamptz`);
|
||||
await pool.query(`alter table public.${TABLE} add column if not exists last_logout_at timestamptz`);
|
||||
}
|
||||
|
||||
app.post("/api/auth/sync", async (req, res) => {
|
||||
try {
|
||||
await ensureUserTable();
|
||||
const idToken = getBearer(req);
|
||||
if (!idToken) return res.status(401).json({ ok: false, error: "missing_token" });
|
||||
|
||||
@@ -97,22 +120,51 @@ app.post("/api/auth/sync", async (req, res) => {
|
||||
|
||||
const q = `
|
||||
insert into public.${TABLE}
|
||||
(sub, email, name, picture, provider, last_login_at, updated_at)
|
||||
(sub, email, name, picture, provider, first_login_at, last_login_at, updated_at)
|
||||
values
|
||||
($1, $2, $3, $4, $5, now(), now())
|
||||
($1, $2, $3, $4, $5, now(), now(), now())
|
||||
on conflict (sub) do update set
|
||||
email = excluded.email,
|
||||
name = excluded.name,
|
||||
picture = excluded.picture,
|
||||
provider = excluded.provider,
|
||||
first_login_at = coalesce(public.${TABLE}.first_login_at, excluded.first_login_at),
|
||||
last_login_at = now(),
|
||||
updated_at = now()
|
||||
returning can_manage
|
||||
returning can_manage, first_login_at, last_login_at, last_logout_at
|
||||
`;
|
||||
const r = await pool.query(q, [sub, email, name, picture, provider]);
|
||||
const canManage = Boolean(r.rows?.[0]?.can_manage);
|
||||
|
||||
res.json({ ok: true, canManage });
|
||||
res.json({ ok: true, canManage, user: r.rows?.[0] || null });
|
||||
} catch (e) {
|
||||
res.status(401).json({ ok: false, error: "verify_failed" });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/auth/logout", async (req, res) => {
|
||||
try {
|
||||
await ensureUserTable();
|
||||
const idToken = getBearer(req);
|
||||
if (!idToken) return res.status(401).json({ ok: false, error: "missing_token" });
|
||||
|
||||
const issuer = String(req.headers["x-auth0-issuer"] || "").trim();
|
||||
const audience = String(req.headers["x-auth0-clientid"] || "").trim();
|
||||
if (!issuer || !audience) return res.status(400).json({ ok: false, error: "missing_auth0_headers" });
|
||||
|
||||
const payload = await verifyIdToken(idToken, { issuer, audience });
|
||||
const sub = String(payload.sub || "").trim();
|
||||
if (!sub) return res.status(400).json({ ok: false, error: "missing_sub" });
|
||||
|
||||
const q = `
|
||||
update public.${TABLE}
|
||||
set last_logout_at = now(),
|
||||
updated_at = now()
|
||||
where sub = $1
|
||||
returning last_logout_at
|
||||
`;
|
||||
const r = await pool.query(q, [sub]);
|
||||
res.json({ ok: true, last_logout_at: r.rows?.[0]?.last_logout_at || null });
|
||||
} catch (e) {
|
||||
res.status(401).json({ ok: false, error: "verify_failed" });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user