scrum4me-mcp/src/auth.ts
Madhura68 d6423ffc24 feat: add wait_for_job and update_job_status tools (M13 agent worker mode)
- wait_for_job: blocks ≤600s, claims QUEUED job atomically via FOR UPDATE
  SKIP LOCKED, resets stale CLAIMED jobs (>30min), registers ClaudeWorker
  presence with heartbeat, emits worker_connected/disconnected via NOTIFY
- update_job_status: agent reports running|done|failed, validates token
  ownership (claimed_by_token_id), emits claude_job_status via NOTIFY
- auth.ts extended with tokenId so tools can set claimed_by_token_id

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 20:05:09 +02:00

53 lines
1.2 KiB
TypeScript

import { createHash } from 'crypto'
import { prisma } from './prisma.js'
export type AuthContext = {
userId: string
tokenId: string
username: string
isDemo: boolean
}
let cached: AuthContext | null = null
export async function getAuth(): Promise<AuthContext> {
if (cached) return cached
const token = process.env.SCRUM4ME_TOKEN
if (!token) {
throw new Error('SCRUM4ME_TOKEN is not set — see .env.example')
}
const tokenHash = createHash('sha256').update(token).digest('hex')
const apiToken = await prisma.apiToken.findUnique({
where: { token_hash: tokenHash },
include: { user: true },
})
if (!apiToken || apiToken.revoked_at) {
throw new Error('SCRUM4ME_TOKEN is invalid or revoked')
}
cached = {
userId: apiToken.user_id,
tokenId: apiToken.id,
username: apiToken.user.username,
isDemo: apiToken.user.is_demo,
}
return cached
}
export class PermissionDeniedError extends Error {
constructor(message = 'Demo accounts cannot perform write operations') {
super(message)
this.name = 'PermissionDeniedError'
}
}
export async function requireWriteAccess(): Promise<AuthContext> {
const auth = await getAuth()
if (auth.isDemo) {
throw new PermissionDeniedError()
}
return auth
}