Client: useNostr.ts's auto-restore now calls GET /api/auth/me (a plain
authFetch, no body) instead of POSTing {pubkey} to /api/auth/login —
identity is derived server-side from the JWT alone, never claimed by
the client.
Server: POST /login is reduced to a pure, documented-deprecated read.
Removed the creator auto-create branch and the creator auto-upgrade
db.update block — an unauthenticated request can no longer mutate the
database via this endpoint. The identical creator auto-create/upgrade
logic already exists, correctly gated behind NIP-98 verification, in
POST /nostr/session, so a creator signing in with a real signer still
gets the same row created/upgraded. Added a handler doc comment plus a
new auth.test.ts case asserting an unregistered creator pubkey now
returns exists:false and leaves the bots table row count unchanged.
e2e/helpers/auth.ts: doc comments updated to describe loginWithPubkey
as a read-only test lookup helper, not a login; request/signature
unchanged so existing e2e specs keep working.
Verification: auth.test.ts + auth-edge.test.ts + auth-audit.test.ts +
auth-me.test.ts = 56/56 pass. Full server suite (bypassing pnpm's
install-gate via ./node_modules/.bin/vitest, since this environment's
pnpm needs an interactive build-approval step unrelated to this task)
= 810/817 pass, remaining 7 are pre-existing timing/perf flakes under
CPU load (lifecycle/speed-meta/tier-balance/bot-auth constant-time),
none touching auth. tsc (server) and vue-tsc (frontend) both exit 0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+11
-53
@@ -89,7 +89,17 @@ authRouter.get('/me', async (c) => {
|
||||
})
|
||||
})
|
||||
|
||||
// Login with Nostr pubkey (rate limited: 10 per minute per IP)
|
||||
// DEPRECATED — read-only lookup kept for backward compatibility only.
|
||||
// This endpoint establishes NO session and issues NO token; it never trusts
|
||||
// the pubkey it's given beyond looking up an existing row (D-01/BOT-01).
|
||||
// It used to auto-create/auto-upgrade the creator's bot row on an
|
||||
// unauthenticated request — that side effect has been removed. The
|
||||
// identical creator auto-create/auto-upgrade logic runs, correctly gated
|
||||
// behind NIP-98 signature verification, inside POST /nostr/session; a
|
||||
// creator who signs in with a real signer still gets the same row
|
||||
// created/upgraded there. Session establishment lives ONLY in
|
||||
// POST /nostr/session; session restoration lives ONLY in GET /me.
|
||||
// Rate limited: 10 per minute per IP.
|
||||
authRouter.post('/login', rateLimit(60_000, 10), async (c) => {
|
||||
const parsed = loginSchema.safeParse(await c.req.json().catch(() => ({})))
|
||||
if (!parsed.success) {
|
||||
@@ -117,62 +127,10 @@ authRouter.post('/login', rateLimit(60_000, 10), async (c) => {
|
||||
}).from(schema.bots).where(eq(schema.bots.publicKey, pubkey)).limit(1)
|
||||
|
||||
if (rows.length === 0) {
|
||||
// Auto-create bot for the Creator if not registered
|
||||
if (isCreatorPubkey(pubkey)) {
|
||||
const id = nanoid(12)
|
||||
const secret = randomBytes(32).toString('hex')
|
||||
await db.insert(schema.bots).values({
|
||||
id,
|
||||
name: 'the_creator',
|
||||
webhookUrl: 'http://poll.local/',
|
||||
avatarSeed: 'the_creator',
|
||||
archetype: 'the_creator',
|
||||
secretHash: createHash('sha256').update(secret).digest('hex'),
|
||||
publicKey: pubkey,
|
||||
profilePicUrl: null,
|
||||
customization: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
})
|
||||
return c.json({
|
||||
exists: true,
|
||||
bot: {
|
||||
id,
|
||||
name: 'the_creator',
|
||||
avatarSeed: 'the_creator',
|
||||
archetype: 'the_creator',
|
||||
profilePicUrl: null,
|
||||
eloRating: 1200,
|
||||
wins: 0,
|
||||
losses: 0,
|
||||
winStreak: 0,
|
||||
bestStreak: 0,
|
||||
tier: 0,
|
||||
isActive: true,
|
||||
isHuman: false,
|
||||
customization: null,
|
||||
satsWon: 0,
|
||||
satsWagered: 0,
|
||||
hasWallet: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
return c.json({ exists: false, pubkey })
|
||||
}
|
||||
|
||||
const bot = rows[0]
|
||||
|
||||
// Auto-upgrade: if creator logs in, ensure archetype + bot mode are correct
|
||||
if (isCreatorPubkey(pubkey)) {
|
||||
const fixes: Record<string, string> = {}
|
||||
if (bot.archetype !== "the_creator") fixes.archetype = "the_creator"
|
||||
if (bot.webhookUrl === "http://human.local/") fixes.webhookUrl = "http://poll.local/"
|
||||
if (Object.keys(fixes).length > 0) {
|
||||
await db.update(schema.bots).set(fixes).where(eq(schema.bots.id, bot.id))
|
||||
if (fixes.archetype) bot.archetype = "the_creator"
|
||||
if (fixes.webhookUrl) bot.webhookUrl = "http://poll.local/"
|
||||
}
|
||||
}
|
||||
|
||||
const isHuman = bot.webhookUrl === 'http://human.local/'
|
||||
|
||||
return c.json({
|
||||
|
||||
Reference in New Issue
Block a user