sc-idp/lib/oidc/adapter.js
2026-06-01 16:40:54 -05:00

88 lines
2.7 KiB
JavaScript

// Single-table storage adapter for oidc-provider, backed by _idp_oidc_store.
// oidc-provider instantiates one adapter per model (`new SaltcornAdapter(name)`).
// Rows are tenant-scoped because db.* runs in the request's tenant context.
// oidc-provider validates expiry/consumed itself from the payload, so find()
// returns the stored payload as-is; consume() records `consumed` inside it.
const db = require("@saltcorn/data/db");
const clients = require("../clients");
const { TABLE_OIDC_STORE } = require("../constants");
const epoch = () => {
return Math.floor(Date.now() / 1000);
};
class SaltcornAdapter {
constructor(name) {
this.name = name;
}
async upsert(id, payload, expiresIn) {
const row = {
model: this.name,
id: id,
payload: JSON.stringify(payload),
uid: payload.uid || null,
grant_id: payload.grantId || null,
user_code: payload.userCode || null,
expires_at: expiresIn ? epoch() + expiresIn : null
};
const existing = await db.selectMaybeOne(TABLE_OIDC_STORE, { model: this.name, id: id });
if (existing) {
await db.updateWhere(TABLE_OIDC_STORE, row, { model: this.name, id: id });
} else {
await db.insert(TABLE_OIDC_STORE, row, { noid: true });
}
}
async find(id) {
if (this.name === "Client") {
const row = await clients.getClient(id);
return row ? clients.toOidcMetadata(row) : undefined;
}
const row = await db.selectMaybeOne(TABLE_OIDC_STORE, { model: this.name, id: id });
return row ? JSON.parse(row.payload) : undefined;
}
async findByUid(uid) {
const row = await db.selectMaybeOne(TABLE_OIDC_STORE, { model: this.name, uid: uid });
return row ? JSON.parse(row.payload) : undefined;
}
async findByUserCode(userCode) {
const row = await db.selectMaybeOne(TABLE_OIDC_STORE, { model: this.name, user_code: userCode });
return row ? JSON.parse(row.payload) : undefined;
}
async consume(id) {
const row = await db.selectMaybeOne(TABLE_OIDC_STORE, { model: this.name, id: id });
if (!row) {
return;
}
const payload = JSON.parse(row.payload);
payload.consumed = epoch();
await db.updateWhere(TABLE_OIDC_STORE, { payload: JSON.stringify(payload) }, { model: this.name, id: id });
}
async destroy(id) {
await db.deleteWhere(TABLE_OIDC_STORE, { model: this.name, id: id });
}
async revokeByGrantId(grantId) {
await db.deleteWhere(TABLE_OIDC_STORE, { grant_id: grantId });
}
}
module.exports = SaltcornAdapter;