Add per-app session timeout: max_session_hours column on apps, enforced in /verify
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3fae52c36b
commit
788628485b
3 changed files with 23 additions and 6 deletions
|
|
@ -110,6 +110,7 @@ export async function initDb() {
|
|||
await pool.query(`ALTER TABLE apps ADD COLUMN IF NOT EXISTS category VARCHAR(100)`)
|
||||
await pool.query(`ALTER TABLE apps ADD COLUMN IF NOT EXISTS internal_host TEXT`)
|
||||
await pool.query(`ALTER TABLE apps ADD COLUMN IF NOT EXISTS internal_port INTEGER DEFAULT 3080`)
|
||||
await pool.query(`ALTER TABLE apps ADD COLUMN IF NOT EXISTS max_session_hours INTEGER`)
|
||||
await pool.query(`ALTER TABLE users ADD COLUMN IF NOT EXISTS workforce_user_id TEXT`)
|
||||
await pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS users_workforce_user_id_idx ON users (workforce_user_id) WHERE workforce_user_id IS NOT NULL`)
|
||||
await pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS pending_reg_email_idx ON pending_registrations (email)`)
|
||||
|
|
|
|||
|
|
@ -129,13 +129,17 @@ export async function adminRoutes(app) {
|
|||
})
|
||||
|
||||
app.patch('/apps/:slug', async (request, reply) => {
|
||||
const { active } = request.body || {}
|
||||
const { rows: [app] } = await pool.query(
|
||||
'UPDATE apps SET active = $1 WHERE slug = $2 RETURNING *',
|
||||
[active, request.params.slug]
|
||||
const { active, max_session_hours } = request.body || {}
|
||||
const updates = []; const values = []
|
||||
if (active !== undefined) updates.push(`active = $${values.push(active)}`)
|
||||
if (max_session_hours !== undefined) updates.push(`max_session_hours = $${values.push(max_session_hours ?? null)}`)
|
||||
if (!updates.length) return reply.status(400).send({ error: 'Nothing to update' })
|
||||
values.push(request.params.slug)
|
||||
const { rows: [a] } = await pool.query(
|
||||
`UPDATE apps SET ${updates.join(', ')} WHERE slug = $${values.length} RETURNING *`, values
|
||||
)
|
||||
if (!app) return reply.status(404).send({ error: 'App not found' })
|
||||
return app
|
||||
if (!a) return reply.status(404).send({ error: 'App not found' })
|
||||
return a
|
||||
})
|
||||
|
||||
// ── Roles ──────────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -171,6 +171,18 @@ export async function authRoutes(app) {
|
|||
|
||||
if (!user) return reply.status(403).send({ error: 'Access denied' })
|
||||
|
||||
if (appSlug) {
|
||||
const { rows: [appRow] } = await pool.query(
|
||||
'SELECT max_session_hours FROM apps WHERE slug = $1 AND active = true', [appSlug]
|
||||
)
|
||||
if (appRow?.max_session_hours) {
|
||||
const ageHours = (Date.now() / 1000 - payload.iat) / 3600
|
||||
if (ageHours > appRow.max_session_hours) {
|
||||
return reply.status(401).send({ error: 'Session expired' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!user.offsite_allowed) {
|
||||
const clientIP = request.headers['x-real-ip'] || request.ip
|
||||
if (!(await isOnsite(clientIP))) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue