From 1f4c3e5fc069bab0a65baf2fd192d282ccc33203 Mon Sep 17 00:00:00 2001 From: dukedotdev Date: Tue, 13 Oct 2026 00:52:03 -0400 Subject: [PATCH] [cards] Authenticate channel routes and return channel lists (#121) --- apps/cards/package.json | 1 + apps/cards/src/cards.app.ts | 11 ++++++ apps/cards/src/test/integration/api.test.ts | 40 +++++++++++++++++++-- pnpm-lock.yaml | 3 ++ 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/apps/cards/package.json b/apps/cards/package.json index 9025b49..ab99f1d 100644 --- a/apps/cards/package.json +++ b/apps/cards/package.json @@ -16,6 +16,7 @@ }, "dependencies": { "@repo/hono-helpers": "workspace:*", + "@repo/jwt": "workspace:*", "hono": "4.13.5", "workers-tagged-logger": "1.0.1" }, diff --git a/apps/cards/src/cards.app.ts b/apps/cards/src/cards.app.ts index a562b1f..8a28df2 100644 --- a/apps/cards/src/cards.app.ts +++ b/apps/cards/src/cards.app.ts @@ -2,6 +2,7 @@ import { Hono } from 'hono' import { useWorkersLogger } from 'workers-tagged-logger' import { withNotFound, withOnError } from '@repo/hono-helpers' +import { validateAndGetAccountId } from '@repo/jwt' import type { App } from './context' @@ -22,5 +23,15 @@ const app = new Hono() .get('/', async (c) => { return c.text('hello, world!') }) + .get('/iam/me/channels/:channel', async (c) => { + const accountId = await validateAndGetAccountId(c.req.raw, await c.env.JWT_SECRET.get()) + if (accountId === null) return c.body(null, 401) + const channel = c.req.param('channel') + if (!['None', 'Announcements', 'JustInTimeTutorials'].includes(channel)) { + return c.body(null, 400) + } + // GetChannelMessages decodes a bare List. No campaigns are configured. + return c.json([]) + }) export default app diff --git a/apps/cards/src/test/integration/api.test.ts b/apps/cards/src/test/integration/api.test.ts index 47bcb0a..e3c0311 100644 --- a/apps/cards/src/test/integration/api.test.ts +++ b/apps/cards/src/test/integration/api.test.ts @@ -1,8 +1,44 @@ -import { SELF } from 'cloudflare:test' -import { expect, it } from 'vitest' +import { adminSecretsStore, env, SELF } from 'cloudflare:test' +import { sign } from 'hono/jwt' +import { beforeAll, expect, it } from 'vitest' + +import type { Env } from '../../context' + +declare module 'cloudflare:test' { + interface ProvidedEnv extends Env {} +} + +beforeAll(async () => { + await adminSecretsStore(env.JWT_SECRET).create('cards-test-secret') +}) + +async function bearer() { + const token = await sign( + { sub: '205', exp: Math.floor(Date.now() / 1000) + 60 }, + 'cards-test-secret', + 'HS256' + ) + return { Authorization: `Bearer ${token}` } +} it('response with hello world', async () => { const res = await SELF.fetch('https://example.com') expect(res.status).toBe(200) expect(await res.text()).toMatchInlineSnapshot(`"hello, world!"`) }) + +it('in-app message channels return a bare list and require authentication', async () => { + const url = 'https://example.com/iam/me/channels/Announcements' + expect((await SELF.fetch(url)).status).toBe(401) + for (const channel of ['Announcements', 'JustInTimeTutorials', 'None']) { + const response = await SELF.fetch(`https://example.com/iam/me/channels/${channel}`, { + headers: await bearer(), + }) + expect(response.status).toBe(200) + expect(await response.json()).toEqual([]) + } + expect( + (await SELF.fetch('https://example.com/iam/me/channels/Unknown', { headers: await bearer() })) + .status + ).toBe(400) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3e2f29f..86cf6dd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -267,6 +267,9 @@ importers: '@repo/hono-helpers': specifier: workspace:* version: link:../../packages/hono-helpers + '@repo/jwt': + specifier: workspace:* + version: link:../../packages/jwt hono: specifier: 4.13.5 version: 4.13.5