From e747930f51e227af62385f05fae48b1d35c37afb Mon Sep 17 00:00:00 2001 From: Tony <222686154+tony8713@users.noreply.github.com> Date: Tue, 26 May 2026 17:55:39 +0700 Subject: [PATCH] feat: create named API keys from the UI Allow the whitelist method to be called without the apps secret so the Snapshot UI can create named API keys directly, and fix whitelist test isolation so leftover rows no longer cause flaky 409s. Co-Authored-By: Claude Opus 4.7 --- README.md | 6 +++--- src/helpers/auth.ts | 4 +++- test/e2e/whitelist.test.ts | 19 ++++++++++++++++--- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d57f879..f0972c6 100644 --- a/README.md +++ b/README.md @@ -111,18 +111,18 @@ curl --location 'https://keycard.snapshot.org/' \ ### whitelist -This method is used by laser to whitelist new address +This method whitelists a new address and returns a generated API key. It does +not require the apps secret, so it can be called directly from the Snapshot UI. ```sh curl --location 'https://keycard.snapshot.org/' \ --header 'accept: application/json' \ --header 'content-type: application/json' \ ---header 'secret: ' \ --data '{ "jsonrpc": "2.0", "method": "whitelist", "params": { - "owner": "", + "address": "", "name": "" }, "id": "123456789" diff --git a/src/helpers/auth.ts b/src/helpers/auth.ts index d5c8651..4c408ba 100644 --- a/src/helpers/auth.ts +++ b/src/helpers/auth.ts @@ -6,7 +6,9 @@ export const authChecker = async (req, res, next) => { const { id = null, method } = req.body; const { secret = '' } = req.headers; - if (method !== 'generate_key' && secret !== APPS_SECRET) { + const publicMethods = ['generate_key', 'whitelist']; + + if (!publicMethods.includes(method) && secret !== APPS_SECRET) { console.log('[Received] method:', method, id); return rpcError(res, 401, 'Wrong secret', id); } diff --git a/test/e2e/whitelist.test.ts b/test/e2e/whitelist.test.ts index 9fb85bd..c00e19a 100644 --- a/test/e2e/whitelist.test.ts +++ b/test/e2e/whitelist.test.ts @@ -7,16 +7,16 @@ const ADDRESS = '0x0000000000000000000000000000000000000001'; describe('POST / { method: whitelist }', () => { beforeEach(async () => { - await cleanupDb(); + await cleanupDb(NAME); }); afterAll(async () => { - await cleanupDb(); + await cleanupDb(NAME); return db.endAsync(); }); describe('on a valid payload', () => { - it('whitelists the given address', async () => { + it('whitelists the given address and returns a key', async () => { const response = await request(HOST) .post('/') .set({ secret: process.env.SECRET }) @@ -27,6 +27,19 @@ describe('POST / { method: whitelist }', () => { expect(response.status).toBe(200); expect(response.body.result.success).toBe(true); + expect(response.body.result.key).toHaveLength(64); + }); + + it('whitelists without the apps secret', async () => { + const response = await request(HOST) + .post('/') + .send({ + method: 'whitelist', + params: { name: NAME, address: ADDRESS } + }); + + expect(response.status).toBe(200); + expect(response.body.result.success).toBe(true); }); });