-
Notifications
You must be signed in to change notification settings - Fork 13k
feat: adds Federation canAccessMedia middleware #36926
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,64 @@ | ||||||||||||||||||||||||||||||||||||||||
| import type { EventAuthorizationService } from '@hs/federation-sdk'; | ||||||||||||||||||||||||||||||||||||||||
| import type { Context, Next } from 'hono'; | ||||||||||||||||||||||||||||||||||||||||
| import type { ContentfulStatusCode } from 'hono/utils/http-status'; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const errCodes: Record<string, { errcode: string; error: string; status: ContentfulStatusCode }> = { | ||||||||||||||||||||||||||||||||||||||||
| M_UNAUTHORIZED: { | ||||||||||||||||||||||||||||||||||||||||
| errcode: 'M_UNAUTHORIZED', | ||||||||||||||||||||||||||||||||||||||||
| error: 'Invalid or missing signature', | ||||||||||||||||||||||||||||||||||||||||
| status: 401, | ||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||
| M_FORBIDDEN: { | ||||||||||||||||||||||||||||||||||||||||
| errcode: 'M_FORBIDDEN', | ||||||||||||||||||||||||||||||||||||||||
| error: 'Access denied', | ||||||||||||||||||||||||||||||||||||||||
| status: 403, | ||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||
| M_UNKNOWN: { | ||||||||||||||||||||||||||||||||||||||||
| errcode: 'M_UNKNOWN', | ||||||||||||||||||||||||||||||||||||||||
| error: 'Internal server error while processing request', | ||||||||||||||||||||||||||||||||||||||||
| status: 500, | ||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| export const canAccessMedia = (federationAuth: EventAuthorizationService) => { | ||||||||||||||||||||||||||||||||||||||||
| return async (c: Context, next: Next) => { | ||||||||||||||||||||||||||||||||||||||||
| const mediaId = c.req.param('mediaId'); | ||||||||||||||||||||||||||||||||||||||||
| const authHeader = c.req.header('Authorization') || ''; | ||||||||||||||||||||||||||||||||||||||||
| const { method } = c.req; | ||||||||||||||||||||||||||||||||||||||||
| const { path } = c.req; | ||||||||||||||||||||||||||||||||||||||||
| const query = c.req.query(); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| let uriForSignature = path; | ||||||||||||||||||||||||||||||||||||||||
| const queryString = new URLSearchParams(query).toString(); | ||||||||||||||||||||||||||||||||||||||||
| if (queryString) { | ||||||||||||||||||||||||||||||||||||||||
| uriForSignature = `${path}?${queryString}`; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+28
to
+35
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preserve raw query string (ordering and duplicates) for signature verification. Rebuilding the query via URLSearchParams can reorder keys and drop multi-valued params, causing signature mismatches. Use the raw URL. Apply: - const { path } = c.req;
- const query = c.req.query();
-
- let uriForSignature = path;
- const queryString = new URLSearchParams(query).toString();
- if (queryString) {
- uriForSignature = `${path}?${queryString}`;
- }
+ const url = new URL(c.req.url);
+ const uriForSignature = `${url.pathname}${url.search}`;
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||
| const body = method === 'GET' ? undefined : await c.req.json(); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const verificationResult = await federationAuth.canAccessMediaFromAuthorizationHeader( | ||||||||||||||||||||||||||||||||||||||||
| mediaId, | ||||||||||||||||||||||||||||||||||||||||
| authHeader, | ||||||||||||||||||||||||||||||||||||||||
| method, | ||||||||||||||||||||||||||||||||||||||||
| uriForSignature, // use URI with query params for signature verification | ||||||||||||||||||||||||||||||||||||||||
| body, | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if (!verificationResult.authorized) { | ||||||||||||||||||||||||||||||||||||||||
| const errorResponse = errCodes[verificationResult.errorCode]; | ||||||||||||||||||||||||||||||||||||||||
| return c.json( | ||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||
| errcode: errorResponse.errcode, | ||||||||||||||||||||||||||||||||||||||||
| error: errorResponse.error, | ||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||
| errorResponse.status, | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+48
to
+56
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle unknown errorCode to avoid runtime crash. If the service returns an unexpected code, errorResponse is undefined and accessing its fields throws. Apply: - if (!verificationResult.authorized) {
- const errorResponse = errCodes[verificationResult.errorCode];
+ if (!verificationResult.authorized) {
+ const errorResponse = errCodes[verificationResult.errorCode] ?? errCodes.M_FORBIDDEN;
return c.json(
{
errcode: errorResponse.errcode,
error: errorResponse.error,
},
errorResponse.status,
);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| return next(); | ||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||
| return c.json(errCodes.M_UNKNOWN, 500); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+60
to
+62
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return only Matrix error fields; drop extra “status” property. Body currently includes “status,” which violates the route error schema. Apply: - } catch (error) {
- return c.json(errCodes.M_UNKNOWN, 500);
- }
+ } catch (error) {
+ return c.json({ errcode: errCodes.M_UNKNOWN.errcode, error: errCodes.M_UNKNOWN.error }, 500);
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thumbnail route schema missing 500 while handler returns 500.
This mismatches declared responses and can fail validation.
Apply:
response: { + 500: isErrorResponseProps, },📝 Committable suggestion
🤖 Prompt for AI Agents