import { describe, it, expect } from 'vitest' import { Hono } from 'hono' import { authRouter } from './auth.js' import { generateSecretKey, getPublicKey } from 'nostr-tools' import { createJwt, blacklistJwt } from '../middleware/jwt.js' import { db, schema } from '../db/index.js' import { createHash, randomBytes } from 'crypto' import { nanoid } from 'nanoid' const app = new Hono() app.route('/api/auth', authRouter) async function seedBot(pubkey: string, overrides: Partial = {}) { const id = overrides.id || nanoid(12) const secret = randomBytes(32).toString('hex') await db.insert(schema.bots).values({ id, name: overrides.name || `t${Date.now().toString(36).slice(-8)}`, webhookUrl: overrides.webhookUrl || 'http://poll.local/', avatarSeed: overrides.avatarSeed || 'seed', archetype: overrides.archetype || 'standard', secretHash: createHash('sha256').update(secret).digest('hex'), publicKey: pubkey, profilePicUrl: overrides.profilePicUrl ?? null, customization: overrides.customization ?? null, createdAt: overrides.createdAt || new Date().toISOString(), }) return id } describe('GET /api/auth/me', () => { it('rejects when no Authorization header is present', async () => { const res = await app.request('/api/auth/me') expect(res.status).toBe(401) const body = await res.json() as { error: string } expect(body.error).toBeDefined() }) it('rejects a malformed / garbage Bearer value', async () => { const res = await app.request('/api/auth/me', { headers: { Authorization: 'Bearer not-a-real-jwt' }, }) expect(res.status).toBe(401) const body = await res.json() as { error: string } expect(body.error).toBeDefined() }) it('rejects a token whose signature does not verify', async () => { const pk = getPublicKey(generateSecretKey()) const token = createJwt(pk) const parts = token.split('.') // tamper one character of the signature segment const tamperedSig = (parts[2][0] === 'a' ? 'b' : 'a') + parts[2].slice(1) const tampered = `${parts[0]}.${parts[1]}.${tamperedSig}` const res = await app.request('/api/auth/me', { headers: { Authorization: `Bearer ${tampered}` }, }) expect(res.status).toBe(401) }) it('rejects a blacklisted token', async () => { const pk = getPublicKey(generateSecretKey()) const token = createJwt(pk) blacklistJwt(token) const res = await app.request('/api/auth/me', { headers: { Authorization: `Bearer ${token}` }, }) expect(res.status).toBe(401) }) it('returns exists:false for a valid token with no bot row', async () => { const pk = getPublicKey(generateSecretKey()) const token = createJwt(pk) const res = await app.request('/api/auth/me', { headers: { Authorization: `Bearer ${token}` }, }) expect(res.status).toBe(200) const body = await res.json() as { exists: boolean } expect(body.exists).toBe(false) }) it('returns exists:true with the bot for a valid token owning a bot row', async () => { const pk = getPublicKey(generateSecretKey()) const name = `me${Date.now().toString(36).slice(-8)}` const botId = await seedBot(pk, { name }) const token = createJwt(pk, botId) const res = await app.request('/api/auth/me', { headers: { Authorization: `Bearer ${token}` }, }) expect(res.status).toBe(200) const body = await res.json() as { exists: boolean; bot: { id: string; name: string; isHuman: boolean; hasWallet: boolean } } expect(body.exists).toBe(true) expect(body.bot.id).toBe(botId) expect(body.bot.name).toBe(name) // Same key set as POST /login's 200 body expect(body.bot.isHuman).toBe(false) expect(body.bot.hasWallet).toBe(false) }) it('performs no writes: querying /me for an unregistered creator pubkey does not create a row', async () => { // A non-creator pubkey with a valid token and no bot row must stay exists:false // with zero side effects (GET /me never inserts/updates). const pk = getPublicKey(generateSecretKey()) const token = createJwt(pk) const before = await db.select({ id: schema.bots.id }).from(schema.bots) await app.request('/api/auth/me', { headers: { Authorization: `Bearer ${token}` } }) const after = await db.select({ id: schema.bots.id }).from(schema.bots) expect(after.length).toBe(before.length) }) })