Merge branch 'emoji-unauth' into 'main'

Allow custom_emojis endpoint to be accessed without a user

See merge request soapbox-pub/ditto!722
This commit is contained in:
Alex Gleason 2025-03-15 18:24:52 +00:00
commit ed0a8dc36b
5 changed files with 34 additions and 10 deletions

View file

@ -10,6 +10,14 @@ Deno.test('customEmojisRoute', async (t) => {
await using test = new TestApp(route); await using test = new TestApp(route);
const { relay } = test.var; const { relay } = test.var;
await t.step('unauth', async () => {
const response = await test.api.get('/');
const body = await response.json();
assertEquals(response.status, 200);
assertEquals(body, []);
});
const sk = generateSecretKey(); const sk = generateSecretKey();
const user = test.user({ relay, signer: new NSecSigner(sk) }); const user = test.user({ relay, signer: new NSecSigner(sk) });
const pubkey = await user.signer.getPublicKey(); const pubkey = await user.signer.getPublicKey();

View file

@ -13,9 +13,13 @@ interface MastodonCustomEmoji {
category?: string; category?: string;
} }
route.get('/', userMiddleware(), async (c) => { route.get('/', userMiddleware({ required: false }), async (c) => {
const { user } = c.var; const { user } = c.var;
if (!user) {
return c.json([]);
}
const pubkey = await user.signer.getPublicKey(); const pubkey = await user.signer.getPublicKey();
const emojis = await getCustomEmojis(pubkey, c.var); const emojis = await getCustomEmojis(pubkey, c.var);

View file

@ -58,13 +58,11 @@ export async function getCustomEmojis(
const d = event.tags.find(([name]) => name === 'd')?.[1]; const d = event.tags.find(([name]) => name === 'd')?.[1];
for (const [t, shortcode, url] of event.tags) { for (const [t, shortcode, url] of event.tags) {
if (t === 'emoji') { if (t === 'emoji' && /^\w+$/.test(shortcode) && !emojis.has(shortcode)) {
if (!emojis.has(shortcode)) { try {
try { emojis.set(shortcode, { url: new URL(url), category: d });
emojis.set(shortcode, { url: new URL(url), category: d }); } catch {
} catch { // continue
// continue
}
} }
} }
} }

View file

@ -10,6 +10,18 @@ Deno.test('no user 401', async () => {
assertEquals(response.status, 401); assertEquals(response.status, 401);
}); });
Deno.test('no user required false', async () => {
await using app = new TestApp();
app
.use(userMiddleware({ required: false }))
.get('/', (c) => c.text('ok'));
const response = await app.request('/');
assertEquals(response.status, 200);
});
Deno.test('unsupported signer 400', async () => { Deno.test('unsupported signer 400', async () => {
await using app = new TestApp(); await using app = new TestApp();

View file

@ -12,6 +12,7 @@ interface UserMiddlewareOpts {
enc?: 'nip04' | 'nip44'; enc?: 'nip04' | 'nip44';
role?: string; role?: string;
verify?: boolean; verify?: boolean;
required?: boolean;
} }
export function userMiddleware(): DittoMiddleware<{ user: User }>; export function userMiddleware(): DittoMiddleware<{ user: User }>;
@ -19,13 +20,14 @@ export function userMiddleware(): DittoMiddleware<{ user: User }>;
export function userMiddleware( export function userMiddleware(
opts: UserMiddlewareOpts & { enc: 'nip44' }, opts: UserMiddlewareOpts & { enc: 'nip44' },
): DittoMiddleware<{ user: User<Nip44Signer> }>; ): DittoMiddleware<{ user: User<Nip44Signer> }>;
export function userMiddleware(opts: UserMiddlewareOpts & { required: false }): DittoMiddleware<{ user?: User }>;
export function userMiddleware(opts: UserMiddlewareOpts): DittoMiddleware<{ user: User }>; export function userMiddleware(opts: UserMiddlewareOpts): DittoMiddleware<{ user: User }>;
export function userMiddleware(opts: UserMiddlewareOpts = {}): DittoMiddleware<{ user: User }> { export function userMiddleware(opts: UserMiddlewareOpts = {}): DittoMiddleware<{ user: User }> {
return async (c, next) => { return async (c, next) => {
const { conf, user, relay } = c.var; const { conf, user, relay } = c.var;
const { enc, role, verify } = opts; const { enc, role, verify, required = true } = opts;
if (!user) { if (!user && required) {
throw new HTTPException(401, { message: 'Authorization required' }); throw new HTTPException(401, { message: 'Authorization required' });
} }