WIP! A BB-style forum, on the ATmosphere! We're still working... we'll be back soon when we have something to show off!
node typescript hono htmx atproto

fix(web): validate targetDid has did: prefix before proxying to AppView (ATB-49) (#77)

Add a startsWith("did:") guard in the POST /admin/members/:did/role handler
before the upstream fetch call. Malformed path parameters now return an inline
MemberRow error fragment without hitting the AppView. Covered by a new test.

authored by

Malpercio and committed by
GitHub
376837df 96062ce0

+35
+24
apps/web/src/routes/__tests__/admin.test.tsx
··· 718 718 // (setupPostSession consumed 2 calls, then we check no more were made) 719 719 expect(mockFetch).toHaveBeenCalledTimes(2); 720 720 }); 721 + 722 + it("returns error row and makes no AppView call when targetDid lacks did: prefix", async () => { 723 + setupPostSession(); 724 + 725 + const routes = await loadAdminRoutes(); 726 + const res = await routes.request("/admin/members/notadid/role", { 727 + method: "POST", 728 + headers: { 729 + "Content-Type": "application/x-www-form-urlencoded", 730 + cookie: "atbb_session=token", 731 + }, 732 + body: makeFormBody({ handle: "bob.bsky.social" }), 733 + }); 734 + 735 + expect(res.status).toBe(200); 736 + const html = await res.text(); 737 + expect(html).toContain("member-row__error"); 738 + expect(html).toContain("Invalid member identifier"); 739 + // Session fetch calls consumed (2), but no AppView role call made 740 + expect(mockFetch).not.toHaveBeenCalledWith( 741 + expect.stringContaining("/api/admin/members/notadid/role"), 742 + expect.anything() 743 + ); 744 + }); 721 745 });
+11
apps/web/src/routes/admin.tsx
··· 383 383 ); 384 384 } 385 385 386 + if (!targetDid.startsWith("did:")) { 387 + return c.html( 388 + <MemberRow 389 + member={{ did: targetDid, handle, role: currentRole, roleUri: currentRoleUri, joinedAt }} 390 + roles={roles} 391 + showRoleControls={showRoleControls} 392 + errorMsg="Invalid member identifier." 393 + /> 394 + ); 395 + } 396 + 386 397 let appviewRes: Response; 387 398 try { 388 399 appviewRes = await fetch(`${appviewUrl}/api/admin/members/${targetDid}/role`, {