Skip to Content
Telegram MCPPlan & Phases3 — User And Auth System

Phase 3 — User & Auth System

Goal

The bot enforces approval before any agent gets to see user messages, and admin tools let an admin gate access via the MCP interface. After this phase, “an arbitrary Telegram user can talk to my bot” is no longer true — only approved users get through.

Scope / Deliverables

  • On inbound update from unknown user: insert row status='pending', reply with a fixed “approval pending” message, do not forward the event to the agent
  • On inbound update from status='blocked' user: drop, audit-log the attempt
  • On inbound update from status='active' user: normalized event forwarded to the agent (as MCP server-sent event or via a polling tool — see open question on event-to-agent mechanism in operations-and-strategy.md)
  • Admin notification: when a new user appears as pending, the admin user gets a Telegram message with their userId and a hint to call approve_user
  • Tool: approve_user (admin-only)
  • Tool: set_role (admin-only; cannot demote the last admin)
  • auth/middleware.ts — runs before every MCP tool call; enforces caller-as-admin where required; emits audit_log rows for denials
  • Migration: audit_log table

Automated Unit Testing

  • auth/middleware.test.ts
    • admin-only tool called by non-admin → JSON-RPC UNAUTHORIZED error and an audit_log row with action='blocked_request'
    • admin-only tool called by admin → passes through
    • non-admin tool called by status='active' user → passes through
    • non-admin tool called by status='blocked' user → UNAUTHORIZED + audit row
  • users.flow.test.ts (webhook-level, with fake telegraf/outbound):
    • inbound from unknown user → DB row inserted with status='pending', “approval pending” reply sent, agent receives nothing, admin gets a notification message containing userId
    • inbound from status='blocked' user → no reply, no agent forwarding, audit row written
    • inbound from status='active' user → normalized event is forwarded to the agent stub
  • tools/approve_user.test.ts
    • admin call on a pending user → status flips to active, approved_at set to now-ish, approved_by set to admin’s userId, returns {userId, status: 'active', approvedAt}
    • non-admin caller → UNAUTHORIZED (audit-logged)
    • unknown userIdNOT_FOUND
  • tools/set_role.test.ts
    • admin promotes user → role='admin'
    • admin demotes self while another admin exists → succeeds
    • admin demotes the only remaining admin → refused with a clear error (e.g. INVALID_INPUT or a dedicated LAST_ADMIN code), no DB write
    • non-admin caller → UNAUTHORIZED
  • audit_log.test.ts — schema: every audit row has actor_id, action, subject_id?, details JSONB, created_at; insert helpers reject malformed actions.

Integration scope (deferred): end-to-end Telegram → webhook → MCP → agent forwarding is exercised manually with the MCP Inspector against a personal bot.

Definition of Done

  • New Telegram user → bot replies “pending”, DB row written, admin notified.
  • Admin calls approve_user via MCP → user can now use the bot.
  • Non-admin attempt to call approve_userUNAUTHORIZED, audit logged.
  • Blocking a user via set_role (or future block_user) stops their events from reaching the agent.