server for refapp and refbot and other stuff
0

Configure Feed

Select the types of activity you want to include in your feed.

feat(refserver): seed admin API key on first boot via REFSERVER_BOOTSTRAP_API_KEY

Breaks the chicken-and-egg of needing an admin key to create keys via the
API. On startup, if the ApiKey collection is empty, refserver seeds an
admin-scoped key from REFSERVER_BOOTSTRAP_API_KEY (idempotent; refuses if other
keys already exist). Lets the combined stack deploy refserver for the first time
without manual key insertion.

alpine (Jul 15, 2026, 10:33 PM +0200) 3cd3b467 43f0ad29

+91
+50
src/bootstrap.test.ts
··· 1 + import { test, before, after, beforeEach } from 'node:test'; 2 + import assert from 'node:assert/strict'; 3 + import { setupTestEnv, teardownTestEnv, clearTestDb } from './routes/_testHelpers.js'; 4 + import { ApiKey } from './models/index.js'; 5 + import { hashToken } from './middleware/auth.js'; 6 + import { config } from './config.js'; 7 + import { ensureBootstrapApiKey } from './bootstrap.js'; 8 + 9 + before(async () => { await setupTestEnv(); }); 10 + after(async () => { await teardownTestEnv(); }); 11 + beforeEach(async () => { await clearTestDb(); }); 12 + 13 + test('ensureBootstrapApiKey seeds an admin key when none exist', async () => { 14 + await ApiKey.deleteMany({}); 15 + config.bootstrapApiKey = 'rs_testbootstrap'; 16 + await ensureBootstrapApiKey(); 17 + 18 + const doc = await ApiKey.findOne({ tokenHash: hashToken('rs_testbootstrap') }); 19 + assert.ok(doc, 'bootstrap key should be created'); 20 + assert.deepEqual(doc!.scopes.sort(), ['admin', 'read', 'referee']); 21 + }); 22 + 23 + test('ensureBootstrapApiKey is idempotent', async () => { 24 + await ApiKey.deleteMany({}); 25 + config.bootstrapApiKey = 'rs_testbootstrap'; 26 + await ensureBootstrapApiKey(); 27 + await ensureBootstrapApiKey(); 28 + 29 + const count = await ApiKey.countDocuments(); 30 + assert.equal(count, 1, 'should not create a second key'); 31 + }); 32 + 33 + test('ensureBootstrapApiKey refuses when other keys exist', async () => { 34 + // beforeEach already seeded keys; ensure must not add a bootstrap key. 35 + config.bootstrapApiKey = 'rs_testbootstrap'; 36 + await ensureBootstrapApiKey(); 37 + 38 + const count = await ApiKey.countDocuments(); 39 + assert.ok(count >= 1, 'existing keys should remain'); 40 + const bootstrap = await ApiKey.findOne({ tokenHash: hashToken('rs_testbootstrap') }); 41 + assert.equal(bootstrap, null, 'should not add a bootstrap key when keys exist'); 42 + }); 43 + 44 + test('ensureBootstrapApiKey is a no-op without a token', async () => { 45 + await ApiKey.deleteMany({}); 46 + config.bootstrapApiKey = ''; 47 + await ensureBootstrapApiKey(); 48 + const count = await ApiKey.countDocuments(); 49 + assert.equal(count, 0); 50 + });
+35
src/bootstrap.ts
··· 1 + import { ApiKey } from './models/index.js'; 2 + import { hashToken } from './middleware/auth.js'; 3 + import { config } from './config.js'; 4 + import { logger } from './logger.js'; 5 + 6 + // Seed an admin API key on first boot when none exist yet, using the token from 7 + // REFSERVER_BOOTSTRAP_API_KEY. This breaks the chicken-and-egg of needing an 8 + // admin key to create keys via the API. Idempotent: skips if a key with the same 9 + // token already exists, and refuses to create one if other keys are present 10 + // (so it can't clobber keys created through the normal flow). 11 + export async function ensureBootstrapApiKey(): Promise<void> { 12 + const token = config.bootstrapApiKey; 13 + if (!token) return; 14 + 15 + const hash = hashToken(token); 16 + const existing = await ApiKey.findOne({ tokenHash: hash }); 17 + if (existing) return; 18 + 19 + const count = await ApiKey.countDocuments(); 20 + if (count > 0) { 21 + logger.warn( 22 + 'REFSERVER_BOOTSTRAP_API_KEY set but other API keys already exist; ' 23 + + 'not creating a bootstrap key', 24 + ); 25 + return; 26 + } 27 + 28 + await ApiKey.create({ 29 + name: 'bootstrap', 30 + tokenHash: hash, 31 + scopes: ['read', 'referee', 'admin'], 32 + createdBy: 'bootstrap', 33 + }); 34 + logger.info('seeded bootstrap admin API key'); 35 + }
+4
src/config.ts
··· 58 58 .map(s => s.trim()) 59 59 .filter(Boolean), 60 60 }, 61 + // Optional. On first boot (empty ApiKey collection), refserver seeds an 62 + // admin-scoped key from this token so clients (e.g. refapp) can authenticate 63 + // before any key exists. Must match the token the client sends as its API key. 64 + bootstrapApiKey: str('REFSERVER_BOOTSTRAP_API_KEY', ''), 61 65 }; 62 66 63 67 export type AppConfig = typeof config;
+2
src/index.ts
··· 3 3 import { createApp } from './app.js'; 4 4 import { connectDB, disconnectDB } from './db.js'; 5 5 import { createWsServer } from './ws.js'; 6 + import { ensureBootstrapApiKey } from './bootstrap.js'; 6 7 import { logger } from './logger.js'; 7 8 8 9 async function main(): Promise<void> { ··· 12 13 ); 13 14 14 15 await connectDB(); 16 + await ensureBootstrapApiKey(); 15 17 16 18 const app = createApp(); 17 19 const httpServer = createServer(app);