import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as smartdb from '../ts/index.js'; import { MongoClient } from 'mongodb'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; let server: smartdb.SmartdbServer; let authedClient: MongoClient; let openClient: MongoClient; let readerClient: MongoClient; let tmpDir: string; let usersPath: string; function makeTmpDir(): string { return fs.mkdtempSync(path.join(os.tmpdir(), 'smartdb-auth-test-')); } function cleanTmpDir(dir: string): void { if (fs.existsSync(dir)) { fs.rmSync(dir, { recursive: true, force: true }); } } tap.test('auth: should start server with SCRAM-SHA-256 auth enabled', async () => { tmpDir = makeTmpDir(); usersPath = path.join(tmpDir, 'users.json'); server = new smartdb.SmartdbServer({ port: 27118, auth: { enabled: true, usersPath, scramIterations: 4096, users: [ { username: 'root', password: 'secret', database: 'admin', roles: ['root'], }, ], }, }); await server.start(); expect(server.running).toBeTrue(); }); tap.test('auth: should reject protected commands before authentication', async () => { openClient = new MongoClient('mongodb://127.0.0.1:27118', { directConnection: true, serverSelectionTimeoutMS: 5000, }); await openClient.connect(); let threw = false; try { await openClient.db('admin').command({ ping: 1 }); } catch (err: any) { threw = true; expect(err.code).toEqual(13); } expect(threw).toBeTrue(); }); tap.test('auth: should reject invalid credentials', async () => { const badClient = new MongoClient('mongodb://root:wrong@127.0.0.1:27118/admin?authSource=admin', { directConnection: true, serverSelectionTimeoutMS: 5000, }); let threw = false; try { await badClient.connect(); await badClient.db('admin').command({ ping: 1 }); } catch { threw = true; } finally { await badClient.close().catch(() => undefined); } expect(threw).toBeTrue(); }); tap.test('auth: should authenticate valid credentials', async () => { authedClient = new MongoClient('mongodb://root:secret@127.0.0.1:27118/admin?authSource=admin', { directConnection: true, serverSelectionTimeoutMS: 5000, }); await authedClient.connect(); const result = await authedClient.db('admin').command({ ping: 1 }); expect(result.ok).toEqual(1); }); tap.test('auth: should allow CRUD after authentication', async () => { const coll = authedClient.db('securedb').collection('notes'); const inserted = await coll.insertOne({ title: 'enterprise auth' }); expect(inserted.acknowledged).toBeTrue(); const doc = await coll.findOne({ _id: inserted.insertedId }); expect(doc).toBeTruthy(); expect(doc!.title).toEqual('enterprise auth'); }); tap.test('auth: root should create a read-only user', async () => { const result = await authedClient.db('admin').command({ createUser: 'reader', pwd: 'readpass', roles: [{ role: 'read', db: 'securedb' }], }); expect(result.ok).toEqual(1); const usersInfo = await authedClient.db('admin').command({ usersInfo: 'reader' }); expect(usersInfo.ok).toEqual(1); expect(usersInfo.users.length).toEqual(1); expect(usersInfo.users[0].user).toEqual('reader'); }); tap.test('auth: read-only user should read but not write', async () => { readerClient = new MongoClient('mongodb://reader:readpass@127.0.0.1:27118/admin?authSource=admin', { directConnection: true, serverSelectionTimeoutMS: 5000, }); await readerClient.connect(); const doc = await readerClient.db('securedb').collection('notes').findOne({ title: 'enterprise auth' }); expect(doc).toBeTruthy(); let threw = false; try { await readerClient.db('securedb').collection('notes').insertOne({ title: 'denied write' }); } catch (err: any) { threw = true; expect(err.code).toEqual(13); } expect(threw).toBeTrue(); }); tap.test('auth: persisted users should survive server restart', async () => { await readerClient.close(); await authedClient.close(); await server.stop(); // Simulates a crash after writing the temporary auth metadata file but before rename. fs.writeFileSync(path.join(tmpDir, 'users.tmp'), '{ invalid json'); server = new smartdb.SmartdbServer({ port: 27118, auth: { enabled: true, usersPath, users: [], scramIterations: 4096, }, }); await server.start(); readerClient = new MongoClient('mongodb://reader:readpass@127.0.0.1:27118/admin?authSource=admin', { directConnection: true, serverSelectionTimeoutMS: 5000, }); await readerClient.connect(); const result = await readerClient.db('admin').command({ ping: 1 }); expect(result.ok).toEqual(1); }); tap.test('auth: cleanup', async () => { await openClient.close(); await readerClient.close(); await server.stop(); expect(server.running).toBeFalse(); cleanTmpDir(tmpDir); }); export default tap.start();