// 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;