Skip to content

Commit 0840ada

Browse files
committed
fix: add migration 018 for OAuth user_id columns
Root cause: createOAuthTables (which contained migrateOAuthUserColumns) was only called inside migration 012's guard. Migration 012 already ran on production, so the user_id column additions never executed. Now: - New migration 018_oauth_user_id_columns runs ALTER TABLE on existing oauth_codes, oauth_tokens, oauth_pending_requests tables - ReflectOAuthProvider constructor calls ensureOAuthUserColumns on every startup as a safety net - Diagnostic /health endpoint reports column existence and DB state Made-with: Cursor
1 parent 4b5e811 commit 0840ada

3 files changed

Lines changed: 65 additions & 33 deletions

File tree

src/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ if (!db.prepare(`SELECT 1 FROM _migrations WHERE name = ?`).get(ciTrashMigration
437437
}
438438

439439
// Migration: create OAuth tables for MCP native connector
440-
import { createOAuthTables } from "./oauth-store.js";
440+
import { createOAuthTables, ensureOAuthUserColumns } from "./oauth-store.js";
441441
const oauthMigrationName = "012_oauth_tables";
442442
if (!db.prepare(`SELECT 1 FROM _migrations WHERE name = ?`).get(oauthMigrationName)) {
443443
createOAuthTables(db);
@@ -550,6 +550,16 @@ if (!db.prepare(`SELECT 1 FROM _migrations WHERE name = ?`).get(phantomReadsMigr
550550
);
551551
}
552552

553+
const oauthUserIdMigration = "018_oauth_user_id_columns";
554+
if (!db.prepare(`SELECT 1 FROM _migrations WHERE name = ?`).get(oauthUserIdMigration)) {
555+
ensureOAuthUserColumns(db);
556+
db.prepare(`INSERT INTO _migrations (name, applied_at) VALUES (?, ?)`).run(
557+
oauthUserIdMigration,
558+
new Date().toISOString(),
559+
);
560+
console.log("[migration] Added user_id columns to OAuth tables and created agent_keys");
561+
}
562+
553563
const ownerEmail = optionalEnv("RM_OWNER_EMAIL", "").toLowerCase();
554564

555565
let userId: string;

src/mcp-server.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
1616
import { z } from "zod";
1717
import { jwtVerify } from "jose";
1818
import type Database from "better-sqlite3";
19-
import { ReflectOAuthProvider, resolveAgentKeyUser } from "./oauth-store.js";
19+
import { ReflectOAuthProvider, resolveAgentKeyUser, hasUserIdColumns } from "./oauth-store.js";
2020
import { authenticateApiKey } from "./api-key-service.js";
2121
import { findOrCreateUserByEmail } from "./user-service.js";
2222
import {
@@ -561,15 +561,39 @@ export function startMcpServer(config: McpServerConfig, port: number): void {
561561

562562
app.get("/health", (_req, res) => {
563563
const secretLen = dashboardJwtSecret?.length ?? 0;
564+
const svcKeyLen = dashboardServiceKey?.length ?? 0;
565+
566+
let dbDiag: Record<string, unknown> = {};
567+
try {
568+
const pendingCount = (db.prepare(`SELECT COUNT(*) as c FROM oauth_pending_requests`).get() as { c: number }).c;
569+
const clientCount = (db.prepare(`SELECT COUNT(*) as c FROM oauth_clients`).get() as { c: number }).c;
570+
const tokenCount = (db.prepare(`SELECT COUNT(*) as c FROM oauth_tokens`).get() as { c: number }).c;
571+
const codesCols = (db.prepare(`PRAGMA table_info(oauth_codes)`).all() as { name: string }[]).map(c => c.name);
572+
const tokensCols = (db.prepare(`PRAGMA table_info(oauth_tokens)`).all() as { name: string }[]).map(c => c.name);
573+
const pendingCols = (db.prepare(`PRAGMA table_info(oauth_pending_requests)`).all() as { name: string }[]).map(c => c.name);
574+
dbDiag = {
575+
pending_requests: pendingCount,
576+
clients: clientCount,
577+
tokens: tokenCount,
578+
codes_has_user_id: codesCols.includes("user_id"),
579+
tokens_has_user_id: tokensCols.includes("user_id"),
580+
pending_has_user_id: pendingCols.includes("user_id"),
581+
};
582+
} catch (err) {
583+
dbDiag = { error: (err as Error).message };
584+
}
585+
564586
res.json({
565587
service: "reflect-memory-mcp",
566588
status: "ok",
567589
oauth: {
568590
jwt_secret_configured: secretLen > 0,
569-
jwt_secret_length: secretLen,
591+
service_key_configured: svcKeyLen > 0,
592+
has_user_id_cols: hasUserIdColumns(),
570593
dashboard_url: dashboardUrl || "(not set)",
571594
public_url: publicUrl || "(not set)",
572595
},
596+
db: dbDiag,
573597
sessions: Object.keys(transports).length,
574598
});
575599
});

src/oauth-store.ts

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ import type {
2424
let _hasUserIdCols = false;
2525

2626
export function createOAuthTables(db: Database.Database): void {
27-
// Create core tables WITHOUT user_id so CREATE TABLE IF NOT EXISTS is a clean
28-
// no-op on databases that already have these tables from before the migration.
2927
db.exec(`
3028
CREATE TABLE IF NOT EXISTS oauth_clients (
3129
client_id TEXT NOT NULL PRIMARY KEY,
@@ -74,8 +72,27 @@ export function createOAuthTables(db: Database.Database): void {
7472
created_at TEXT NOT NULL
7573
) STRICT;
7674
`);
75+
}
76+
77+
/**
78+
* Add user_id columns to OAuth tables and create agent_keys table.
79+
* Safe to call multiple times -- each operation is idempotent.
80+
* Called from migration 018 in index.ts AND from ReflectOAuthProvider constructor.
81+
*/
82+
export function ensureOAuthUserColumns(db: Database.Database): void {
83+
const tables = ["oauth_codes", "oauth_tokens", "oauth_pending_requests"];
84+
for (const table of tables) {
85+
try {
86+
const cols = db.prepare(`PRAGMA table_info(${table})`).all() as { name: string }[];
87+
if (!cols.some((c) => c.name === "user_id")) {
88+
db.exec(`ALTER TABLE ${table} ADD COLUMN user_id TEXT`);
89+
console.log(`[oauth] Added user_id column to ${table}`);
90+
}
91+
} catch (err) {
92+
console.error(`[oauth] Failed to add user_id to ${table}: ${(err as Error).message}`);
93+
}
94+
}
7795

78-
// agent_keys table -- separate exec so failures don't block core tables
7996
try {
8097
db.exec(`
8198
CREATE TABLE IF NOT EXISTS agent_keys (
@@ -93,35 +110,14 @@ export function createOAuthTables(db: Database.Database): void {
93110
console.warn(`[oauth] agent_keys table setup: ${(err as Error).message}`);
94111
}
95112

96-
// Migrate user_id columns onto existing tables (each in its own try/catch)
97-
_hasUserIdCols = migrateOAuthUserColumns(db);
98-
}
99-
100-
function migrateOAuthUserColumns(db: Database.Database): boolean {
101-
const tables = ["oauth_codes", "oauth_tokens", "oauth_pending_requests"];
102-
let allOk = true;
103-
for (const table of tables) {
104-
try {
105-
const cols = db.prepare(`PRAGMA table_info(${table})`).all() as { name: string }[];
106-
if (!cols.some((c) => c.name === "user_id")) {
107-
db.exec(`ALTER TABLE ${table} ADD COLUMN user_id TEXT`);
108-
console.log(`[oauth] Added user_id column to ${table}`);
109-
}
110-
} catch (err) {
111-
console.error(`[oauth] Failed to add user_id to ${table}: ${(err as Error).message}`);
112-
allOk = false;
113-
}
114-
}
115-
// Final check: verify the columns actually exist
113+
// Set the flag based on actual column existence
116114
try {
117115
const cols = db.prepare(`PRAGMA table_info(oauth_codes)`).all() as { name: string }[];
118-
const hasIt = cols.some((c) => c.name === "user_id");
119-
if (!hasIt) {
120-
console.error(`[oauth] CRITICAL: user_id column still missing from oauth_codes after migration`);
121-
allOk = false;
122-
}
123-
} catch { allOk = false; }
124-
return allOk;
116+
_hasUserIdCols = cols.some((c) => c.name === "user_id");
117+
} catch {
118+
_hasUserIdCols = false;
119+
}
120+
console.log(`[oauth] user_id columns available: ${_hasUserIdCols}`);
125121
}
126122

127123
export function hasUserIdColumns(): boolean {
@@ -286,6 +282,8 @@ export class ReflectOAuthProvider implements OAuthServerProvider {
286282
this._clientsStore = new SqliteClientsStore(config.db);
287283
this.issuerUrl = config.issuerUrl;
288284
this.dashboardUrl = config.dashboardUrl || "https://reflectmemory.com";
285+
// Ensure user_id columns exist every time the provider starts
286+
ensureOAuthUserColumns(config.db);
289287
}
290288

291289
get clientsStore(): OAuthRegisteredClientsStore {

0 commit comments

Comments
 (0)