diff --git a/api/api/auth/login/index.ts b/api/api/auth/login/index.ts index 3d90d84e..bafecd6e 100644 --- a/api/api/auth/login/index.ts +++ b/api/api/auth/login/index.ts @@ -1,5 +1,6 @@ import { apiRoute, applyConfig } from "@/api"; -import { redirect } from "@/response"; +import type { Context } from "@hono/hono"; +import { setCookie } from "@hono/hono/cookie"; import { createRoute } from "@hono/zod-openapi"; import { eq, or } from "drizzle-orm"; import { SignJWT } from "jose"; @@ -87,11 +88,11 @@ const route = createRoute({ }, }); -const returnError = (query: object, error: string, description: string) => { +const returnError = (context: Context, error: string, description: string) => { const searchParams = new URLSearchParams(); // Add all data that is not undefined except email and password - for (const [key, value] of Object.entries(query)) { + for (const [key, value] of Object.entries(context.req.query())) { if (key !== "email" && key !== "password" && value !== undefined) { searchParams.append(key, value); } @@ -100,11 +101,11 @@ const returnError = (query: object, error: string, description: string) => { searchParams.append("error", error); searchParams.append("error_description", description); - return redirect( + return context.redirect( new URL( `${config.frontend.routes.login}?${searchParams.toString()}`, config.http.base_url, - ), + ).toString(), ); }; @@ -112,7 +113,7 @@ export default apiRoute((app) => app.openapi(route, async (context) => { if (config.oidc.forced) { return returnError( - context.req.query(), + context, "invalid_request", "Logging in with a password is disabled by the administrator. Please use a valid OpenID Connect provider.", ); @@ -136,14 +137,14 @@ export default apiRoute((app) => ) ) { return returnError( - context.req.query(), + context, "invalid_grant", "Invalid identifier or password", ); } if (user.data.passwordResetToken) { - return redirect( + return context.redirect( `${config.frontend.routes.password_reset}?${new URLSearchParams( { token: user.data.passwordResetToken ?? "", @@ -198,14 +199,15 @@ export default apiRoute((app) => } // Redirect to OAuth authorize with JWT - return redirect( + setCookie(context, "jwt", jwt, { + httpOnly: true, + secure: true, + sameSite: "Strict", + path: "/", + maxAge: 60 * 60, + }); + return context.redirect( `${config.frontend.routes.consent}?${searchParams.toString()}`, - 302, - { - "Set-Cookie": `jwt=${jwt}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=${ - 60 * 60 - }`, - }, ); }), ); diff --git a/api/api/auth/redirect/index.ts b/api/api/auth/redirect/index.ts index 5d045bce..e4a04371 100644 --- a/api/api/auth/redirect/index.ts +++ b/api/api/auth/redirect/index.ts @@ -51,12 +51,11 @@ export default apiRoute((app) => const { redirect_uri, client_id, code } = context.req.valid("query"); const redirectToLogin = (error: string) => - Response.redirect( + context.redirect( `${config.frontend.routes.login}?${new URLSearchParams({ ...context.req.query, error: encodeURIComponent(error), }).toString()}`, - 302, ); const foundToken = await db diff --git a/api/api/auth/reset/index.ts b/api/api/auth/reset/index.ts index 1146cd1b..89c94b47 100644 --- a/api/api/auth/reset/index.ts +++ b/api/api/auth/reset/index.ts @@ -1,7 +1,7 @@ import { apiRoute, applyConfig } from "@/api"; -import { response } from "@/response"; import { createRoute } from "@hono/zod-openapi"; import { eq } from "drizzle-orm"; +import type { Context } from "hono"; import { z } from "zod"; import { Users } from "~/drizzle/schema"; import { config } from "~/packages/config-manager"; @@ -50,21 +50,26 @@ const route = createRoute({ }, }); -const returnError = (token: string, error: string, description: string) => { +const returnError = ( + context: Context, + token: string, + error: string, + description: string, +) => { const searchParams = new URLSearchParams(); searchParams.append("error", error); searchParams.append("error_description", description); searchParams.append("token", token); - return response(null, 302, { - Location: new URL( + return context.redirect( + new URL( `${ config.frontend.routes.password_reset }?${searchParams.toString()}`, config.http.base_url, ).toString(), - }); + ); }; export default apiRoute((app) => @@ -74,7 +79,12 @@ export default apiRoute((app) => const user = await User.fromSql(eq(Users.passwordResetToken, token)); if (!user) { - return returnError(token, "invalid_token", "Invalid token"); + return returnError( + context, + token, + "invalid_token", + "Invalid token", + ); } await user.update({ @@ -82,8 +92,8 @@ export default apiRoute((app) => passwordResetToken: null, }); - return response(null, 302, { - Location: `${config.frontend.routes.password_reset}?success=true`, - }); + return context.redirect( + `${config.frontend.routes.password_reset}?success=true`, + ); }), ); diff --git a/api/api/v1/emojis/:id/index.ts b/api/api/v1/emojis/:id/index.ts index 5615410c..dc4f4a51 100644 --- a/api/api/v1/emojis/:id/index.ts +++ b/api/api/v1/emojis/:id/index.ts @@ -7,7 +7,6 @@ import { jsonOrForm, } from "@/api"; import { mimeLookup } from "@/content_types"; -import { response } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { eq } from "drizzle-orm"; import { z } from "zod"; @@ -110,7 +109,7 @@ export default apiRoute((app) => await db.delete(Emojis).where(eq(Emojis.id, id)); - return response(null, 204); + return context.newResponse(null, 204); } case "PATCH": { diff --git a/api/api/v1/media/:id/index.ts b/api/api/v1/media/:id/index.ts index eed060c2..9cd8a1c6 100644 --- a/api/api/v1/media/:id/index.ts +++ b/api/api/v1/media/:id/index.ts @@ -5,7 +5,6 @@ import { handleZodError, idValidator, } from "@/api"; -import { response } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { MediaManager } from "~/classes/media/media-manager"; @@ -71,7 +70,7 @@ export default apiRoute((app) => if (attachment.data.url) { return context.json(attachment.toApi()); } - return response(null, 206); + return context.newResponse(null, 206); } case "PUT": { const { description, thumbnail } = diff --git a/api/api/v1/roles/:id/index.ts b/api/api/v1/roles/:id/index.ts index 21cea149..04e7df2c 100644 --- a/api/api/v1/roles/:id/index.ts +++ b/api/api/v1/roles/:id/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { response } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { RolePermissions } from "~/drizzle/schema"; @@ -76,7 +75,7 @@ export default apiRoute((app) => await role.linkUser(user.id); - return response(null, 204); + return context.newResponse(null, 204); } case "DELETE": { const userHighestRole = userRoles.reduce((prev, current) => @@ -96,7 +95,7 @@ export default apiRoute((app) => await role.unlinkUser(user.id); - return response(null, 204); + return context.newResponse(null, 204); } } }, diff --git a/api/api/v1/sso/:id/index.ts b/api/api/v1/sso/:id/index.ts index 304cedc9..e9f57f53 100644 --- a/api/api/v1/sso/:id/index.ts +++ b/api/api/v1/sso/:id/index.ts @@ -1,5 +1,5 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { proxyUrl, response } from "@/response"; +import { proxyUrl } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { eq } from "drizzle-orm"; import { z } from "zod"; @@ -103,7 +103,7 @@ export default apiRoute((app) => .delete(OpenIdAccounts) .where(eq(OpenIdAccounts.id, account.id)); - return response(null, 204); + return context.newResponse(null, 204); } } }, diff --git a/api/media/:hash/:name/index.ts b/api/media/:hash/:name/index.ts index b6a4968f..8aef6dd7 100644 --- a/api/media/:hash/:name/index.ts +++ b/api/media/:hash/:name/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, handleZodError } from "@/api"; -import { response } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; @@ -54,7 +53,7 @@ export default apiRoute((app) => } // Can't directly copy file into Response because this crashes Bun for now - return response(buffer, 200, { + return context.newResponse(buffer, 200, { "Content-Type": file.type || "application/octet-stream", "Content-Length": `${file.size - start}`, "Content-Range": `bytes ${start}-${end}/${file.size}`, diff --git a/api/media/proxy/:id.ts b/api/media/proxy/:id.ts index 3b719782..9019b31a 100644 --- a/api/media/proxy/:id.ts +++ b/api/media/proxy/:id.ts @@ -1,6 +1,6 @@ import { apiRoute, applyConfig, handleZodError } from "@/api"; -import { response } from "@/response"; import { zValidator } from "@hono/zod-validator"; +import type { StatusCode } from "hono/utils/http-status"; import { z } from "zod"; import { config } from "~/packages/config-manager"; @@ -62,7 +62,7 @@ export default apiRoute((app) => .get("Content-Disposition") ?.match(/filename="(.+)"/)?.[1] || id.split("/").pop(); - return response(media.body, media.status, { + return context.newResponse(media.body, media.status as StatusCode, { "Content-Type": media.headers.get("Content-Type") || "application/octet-stream", diff --git a/api/oauth/authorize/index.ts b/api/oauth/authorize/index.ts index 4be5bc50..dad3d718 100644 --- a/api/oauth/authorize/index.ts +++ b/api/oauth/authorize/index.ts @@ -1,8 +1,8 @@ import { apiRoute, applyConfig, handleZodError, jsonOrForm } from "@/api"; import { randomString } from "@/math"; -import { response } from "@/response"; import { sentry } from "@/sentry"; import { zValidator } from "@hono/zod-validator"; +import type { Context } from "hono"; import { SignJWT, jwtVerify } from "jose"; import { z } from "zod"; import { TokenType } from "~/classes/functions/token"; @@ -59,11 +59,16 @@ export const schemas = { }), }; -const returnError = (query: object, error: string, description: string) => { +const returnError = ( + context: Context, + data: object, + error: string, + description: string, +) => { const searchParams = new URLSearchParams(); // Add all data that is not undefined except email and password - for (const [key, value] of Object.entries(query)) { + for (const [key, value] of Object.entries(data)) { if (key !== "email" && key !== "password" && value !== undefined) { searchParams.append(key, value); } @@ -72,9 +77,9 @@ const returnError = (query: object, error: string, description: string) => { searchParams.append("error", error); searchParams.append("error_description", description); - return response(null, 302, { - Location: `${config.frontend.routes.login}?${searchParams.toString()}`, - }); + return context.redirect( + `${config.frontend.routes.login}?${searchParams.toString()}`, + ); }; export default apiRoute((app) => @@ -94,6 +99,7 @@ export default apiRoute((app) => if (!cookie) { return returnError( + context, body, "invalid_request", "No cookies were sent with the request", @@ -107,6 +113,7 @@ export default apiRoute((app) => if (!jwt) { return returnError( + context, body, "invalid_request", "No jwt cookie was sent in the request", @@ -142,6 +149,7 @@ export default apiRoute((app) => if (!result) { return returnError( + context, body, "invalid_request", "Invalid JWT, could not verify", @@ -151,24 +159,45 @@ export default apiRoute((app) => const payload = result.payload; if (!payload.sub) { - return returnError(body, "invalid_request", "Invalid sub"); + return returnError( + context, + body, + "invalid_request", + "Invalid sub", + ); } if (!payload.aud) { - return returnError(body, "invalid_request", "Invalid aud"); + return returnError( + context, + body, + "invalid_request", + "Invalid aud", + ); } if (!payload.exp) { - return returnError(body, "invalid_request", "Invalid exp"); + return returnError( + context, + body, + "invalid_request", + "Invalid exp", + ); } // Check if the user is authenticated const user = await User.fromId(payload.sub); if (!user) { - return returnError(body, "invalid_request", "Invalid sub"); + return returnError( + context, + body, + "invalid_request", + "Invalid sub", + ); } if (!user.hasPermission(RolePermissions.OAuth)) { return returnError( + context, body, "invalid_request", `User is missing the ${RolePermissions.OAuth} permission`, @@ -183,6 +212,7 @@ export default apiRoute((app) => if (!(asksCode || asksToken || asksIdToken)) { return returnError( + context, body, "invalid_request", "Invalid response_type, must ask for code, token, or id_token", @@ -191,6 +221,7 @@ export default apiRoute((app) => if (asksCode && !redirect_uri) { return returnError( + context, body, "invalid_request", "Redirect URI is required for code flow (can be urn:ietf:wg:oauth:2.0:oob)", @@ -216,6 +247,7 @@ export default apiRoute((app) => if (!application) { return returnError( + context, body, "invalid_client", "Invalid client_id or client_secret", @@ -224,6 +256,7 @@ export default apiRoute((app) => if (application.redirectUri !== redirect_uri) { return returnError( + context, body, "invalid_request", "Redirect URI does not match client_id", @@ -237,7 +270,12 @@ export default apiRoute((app) => scope && !scope.split(" ").every((s) => applicationScopes.includes(s)) ) { - return returnError(body, "invalid_scope", "Invalid scope"); + return returnError( + context, + body, + "invalid_scope", + "Invalid scope", + ); } // Generate tokens @@ -323,11 +361,7 @@ export default apiRoute((app) => redirectUri.search = searchParams.toString(); - return response(null, 302, { - Location: redirectUri.toString(), - "Cache-Control": "no-store", - Pragma: "no-cache", - }); + return context.redirect(redirectUri.toString()); }, ), ); diff --git a/api/oauth/sso/:issuer/callback/index.ts b/api/oauth/sso/:issuer/callback/index.ts index a3103929..9a951193 100644 --- a/api/oauth/sso/:issuer/callback/index.ts +++ b/api/oauth/sso/:issuer/callback/index.ts @@ -1,8 +1,9 @@ import { apiRoute, applyConfig, handleZodError } from "@/api"; import { randomString } from "@/math"; -import { response } from "@/response"; +import { setCookie } from "@hono/hono/cookie"; import { zValidator } from "@hono/zod-validator"; import { and, eq, isNull } from "drizzle-orm"; +import type { Context } from "hono"; import { SignJWT } from "jose"; import { z } from "zod"; import { TokenType } from "~/classes/functions/token"; @@ -39,7 +40,12 @@ export const schemas = { }), }; -const returnError = (query: object, error: string, description: string) => { +const returnError = ( + context: Context, + query: object, + error: string, + description: string, +) => { const searchParams = new URLSearchParams(); // Add all data that is not undefined except email and password @@ -52,9 +58,9 @@ const returnError = (query: object, error: string, description: string) => { searchParams.append("error", error); searchParams.append("error_description", description); - return response(null, 302, { - Location: `${config.frontend.routes.login}?${searchParams.toString()}`, - }); + return context.redirect( + `${config.frontend.routes.login}?${searchParams.toString()}`, + ); }; /** @@ -99,9 +105,8 @@ export default apiRoute((app) => redirectUrl, (error, message, app) => returnError( - { - ...manager.processOAuth2Error(app), - }, + context, + manager.processOAuth2Error(app), error, message, ), @@ -117,7 +122,7 @@ export default apiRoute((app) => // If linking account if (link && user_id) { - return await manager.linkUser(user_id, userInfo); + return await manager.linkUser(user_id, context, userInfo); } let userId = ( @@ -191,6 +196,7 @@ export default apiRoute((app) => userId = user.id; } else { return returnError( + context, { redirect_uri: flow.application?.redirectUri, client_id: flow.application?.clientId, @@ -207,6 +213,7 @@ export default apiRoute((app) => if (!user) { return returnError( + context, { redirect_uri: flow.application?.redirectUri, client_id: flow.application?.clientId, @@ -220,6 +227,7 @@ export default apiRoute((app) => if (!user.hasPermission(RolePermissions.OAuth)) { return returnError( + context, { redirect_uri: flow.application?.redirectUri, client_id: flow.application?.clientId, @@ -268,8 +276,16 @@ export default apiRoute((app) => .sign(privateKey); // Redirect back to application - return response(null, 302, { - Location: new URL( + setCookie(context, "jwt", jwt, { + httpOnly: true, + secure: true, + sameSite: "strict", + path: "/", + maxAge: 60 * 60, + }); + + return context.redirect( + new URL( `${config.frontend.routes.consent}?${new URLSearchParams({ redirect_uri: flow.application.redirectUri, code, @@ -281,11 +297,7 @@ export default apiRoute((app) => }).toString()}`, config.http.base_url, ).toString(), - // Set cookie with JWT - "Set-Cookie": `jwt=${jwt}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=${ - 60 * 60 - }`, - }); + ); }, ), ); diff --git a/api/oauth/sso/index.ts b/api/oauth/sso/index.ts index 642b2260..b92b4868 100644 --- a/api/oauth/sso/index.ts +++ b/api/oauth/sso/index.ts @@ -1,7 +1,7 @@ import { apiRoute, applyConfig, handleZodError } from "@/api"; import { oauthRedirectUri } from "@/constants"; -import { redirect, response } from "@/response"; import { zValidator } from "@hono/zod-validator"; +import type { Context } from "hono"; import { calculatePKCECodeChallenge, discoveryRequest, @@ -35,7 +35,12 @@ export const schemas = { }), }; -const returnError = (query: object, error: string, description: string) => { +const returnError = ( + context: Context, + query: object, + error: string, + description: string, +) => { const searchParams = new URLSearchParams(); // Add all data that is not undefined except email and password @@ -48,9 +53,9 @@ const returnError = (query: object, error: string, description: string) => { searchParams.append("error", error); searchParams.append("error_description", description); - return response(null, 302, { - Location: `${config.frontend.routes.login}?${searchParams.toString()}`, - }); + return context.redirect( + `${config.frontend.routes.login}?${searchParams.toString()}`, + ); }; export default apiRoute((app) => @@ -65,6 +70,7 @@ export default apiRoute((app) => if (!client_id || client_id === "undefined") { return returnError( + context, body, "invalid_request", "client_id is required", @@ -77,6 +83,7 @@ export default apiRoute((app) => if (!issuer) { return returnError( + context, body, "invalid_request", "issuer is invalid", @@ -98,6 +105,7 @@ export default apiRoute((app) => if (!application) { return returnError( + context, body, "invalid_request", "client_id is invalid", @@ -119,7 +127,7 @@ export default apiRoute((app) => const codeChallenge = await calculatePKCECodeChallenge(codeVerifier); - return redirect( + return context.redirect( `${authServer.authorization_endpoint}?${new URLSearchParams({ client_id: issuer.client_id, redirect_uri: `${oauthRedirectUri(issuerId)}?flow=${ @@ -131,7 +139,6 @@ export default apiRoute((app) => code_challenge_method: "S256", code_challenge: codeChallenge, }).toString()}`, - 302, ); }, ), diff --git a/api/objects/:id/index.ts b/api/objects/:id/index.ts index a65306d2..d8dd64c0 100644 --- a/api/objects/:id/index.ts +++ b/api/objects/:id/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, handleZodError } from "@/api"; -import { response } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, eq, inArray, sql } from "drizzle-orm"; import { z } from "zod"; @@ -83,9 +82,6 @@ export default apiRoute((app) => 403, ); } - - const objectString = JSON.stringify(apiObject); - // If base_url uses https and request uses http, rewrite request to use https // This fixes reverse proxy errors const reqUrl = new URL(context.req.url); @@ -102,10 +98,7 @@ export default apiRoute((app) => "GET", ); - return response(objectString, 200, { - "Content-Type": "application/json", - ...headers.toJSON(), - }); + return context.json(apiObject, 200, headers.toJSON()); }, ), ); diff --git a/api/users/:uuid/inbox/index.ts b/api/users/:uuid/inbox/index.ts index 752871ac..734988dd 100644 --- a/api/users/:uuid/inbox/index.ts +++ b/api/users/:uuid/inbox/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, debugRequest, handleZodError } from "@/api"; -import { response } from "@/response"; import { sentry } from "@/sentry"; import { zValidator } from "@hono/zod-validator"; import { getLogger } from "@logtape/logtape"; @@ -153,7 +152,7 @@ export default apiRoute((app) => ) ) { // Pretend to accept request - return response(null, 201); + return context.newResponse(null, 201); } // Verify request signature @@ -200,7 +199,7 @@ export default apiRoute((app) => const handler = new RequestParserHandler(body, validator); try { - const result = await handler.parseBody({ + return await handler.parseBody({ note: async (note) => { const account = await User.resolve(note.author); @@ -227,7 +226,7 @@ export default apiRoute((app) => ); } - return response("Note created", 201); + return context.text("Note created", 201); }, follow: async (follow) => { const account = await User.resolve(follow.author); @@ -246,7 +245,7 @@ export default apiRoute((app) => ); if (foundRelationship.data.following) { - return response("Already following", 200); + return context.text("Already following", 200); } await foundRelationship.update({ @@ -269,7 +268,7 @@ export default apiRoute((app) => await sendFollowAccept(account, user); } - return response("Follow request sent", 200); + return context.text("Follow request sent", 200); }, followAccept: async (followAccept) => { const account = await User.resolve(followAccept.author); @@ -288,7 +287,7 @@ export default apiRoute((app) => ); if (!foundRelationship.data.requested) { - return response( + return context.text( "There is no follow request to accept", 200, ); @@ -299,7 +298,7 @@ export default apiRoute((app) => following: true, }); - return response("Follow request accepted", 200); + return context.text("Follow request accepted", 200); }, followReject: async (followReject) => { const account = await User.resolve(followReject.author); @@ -318,7 +317,7 @@ export default apiRoute((app) => ); if (!foundRelationship.data.requested) { - return response( + return context.text( "There is no follow request to reject", 200, ); @@ -329,7 +328,7 @@ export default apiRoute((app) => following: false, }); - return response("Follow request rejected", 200); + return context.text("Follow request rejected", 200); }, // "delete" is a reserved keyword in JS delete: async (delete_) => { @@ -345,7 +344,7 @@ export default apiRoute((app) => if (note) { await note.delete(); - return response("Note deleted", 200); + return context.text("Note deleted", 200); } break; @@ -357,7 +356,10 @@ export default apiRoute((app) => if (otherUser.id === user.id) { // Delete own account await user.delete(); - return response("Account deleted", 200); + return context.text( + "Account deleted", + 200, + ); } return context.json( { @@ -378,6 +380,11 @@ export default apiRoute((app) => ); } } + + return context.json( + { error: "Object not found or not owned by user" }, + 404, + ); }, user: async (user) => { // Refetch user to ensure we have the latest data @@ -392,18 +399,15 @@ export default apiRoute((app) => ); } - return response("User refreshed", 200); + return context.text("User refreshed", 200); + }, + unknown: () => { + return context.json( + { error: "Unknown entity type" }, + 400, + ); }, }); - - if (result) { - return result; - } - - return context.json( - { error: "Object has not been implemented" }, - 400, - ); } catch (e) { if (isValidationError(e)) { return context.json( diff --git a/api/users/:uuid/index.ts b/api/users/:uuid/index.ts index f6b1c711..82639980 100644 --- a/api/users/:uuid/index.ts +++ b/api/users/:uuid/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, handleZodError } from "@/api"; -import { redirect } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { User } from "~/packages/database-interface/user"; @@ -48,7 +47,7 @@ export default apiRoute((app) => context.req.header("user-agent")?.includes("Mozilla") && uuid !== "actor" ) { - return redirect(user.toApi().url); + return context.redirect(user.toApi().url); } const userJson = user.toVersia(); diff --git a/api/well-known/host-meta/index.ts b/api/well-known/host-meta/index.ts index 804c6023..d01f616a 100644 --- a/api/well-known/host-meta/index.ts +++ b/api/well-known/host-meta/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig } from "@/api"; -import { xmlResponse } from "@/response"; import { config } from "~/packages/config-manager"; export const meta = applyConfig({ @@ -15,8 +14,9 @@ export const meta = applyConfig({ }); export default apiRoute((app) => - app.on(meta.allowedMethods, meta.route, () => { - return xmlResponse( + app.on(meta.allowedMethods, meta.route, (context) => { + context.header("Content-Type", "application/xrd+xml"); + return context.body( ` - app.on(meta.allowedMethods, meta.route, () => { - return redirect( - new URL("/.well-known/nodeinfo/2.0", config.http.base_url), + app.on(meta.allowedMethods, meta.route, (context) => { + return context.redirect( + new URL( + "/.well-known/nodeinfo/2.0", + config.http.base_url, + ).toString(), 301, ); }), diff --git a/middlewares/bait.ts b/middlewares/bait.ts index 08ae8c40..cef2b55e 100644 --- a/middlewares/bait.ts +++ b/middlewares/bait.ts @@ -1,4 +1,3 @@ -import { response } from "@/response"; import { createMiddleware } from "@hono/hono/factory"; import { getLogger } from "@logtape/logtape"; import type { SocketAddress } from "bun"; @@ -34,7 +33,7 @@ export const bait = createMiddleware(async (context, next) => { if (requestIp?.address) { for (const ip of config.http.bait.bait_ips) { if (matches(ip, requestIp.address)) { - return response(file); + return context.newResponse(file.stream()); } } } @@ -44,7 +43,7 @@ export const bait = createMiddleware(async (context, next) => { for (const agent of config.http.bait.bait_user_agents) { if (new RegExp(agent).test(ua)) { - return response(file); + return context.newResponse(file.stream()); } } diff --git a/packages/database-interface/oauth.ts b/packages/database-interface/oauth.ts index 37760def..2d03ce20 100644 --- a/packages/database-interface/oauth.ts +++ b/packages/database-interface/oauth.ts @@ -1,5 +1,5 @@ -import { response } from "@/response"; import type { InferInsertModel } from "drizzle-orm"; +import type { Context } from "hono"; import { type AuthorizationServer, authorizationCodeGrantRequest, @@ -146,6 +146,7 @@ export class OAuthManager { async linkUser( userId: string, + context: Context, // Return value of automaticOidcFlow oidcFlowData: Exclude< Awaited< @@ -158,14 +159,14 @@ export class OAuthManager { // Check if userId is equal to application.clientId if (!flow.application?.clientId.startsWith(userId)) { - return response(null, 302, { - Location: `${config.http.base_url}${ + return context.redirect( + `${config.http.base_url}${ config.frontend.routes.home }?${new URLSearchParams({ oidc_account_linking_error: "Account linking error", oidc_account_linking_error_message: `User ID does not match application client ID (${userId} != ${flow.application?.clientId})`, })}`, - }); + ); } // Check if account is already linked @@ -178,27 +179,27 @@ export class OAuthManager { }); if (account) { - return response(null, 302, { - Location: `${config.http.base_url}${ + return context.redirect( + `${config.http.base_url}${ config.frontend.routes.home }?${new URLSearchParams({ oidc_account_linking_error: "Account already linked", oidc_account_linking_error_message: "This account has already been linked to this OpenID Connect provider.", })}`, - }); + ); } // Link the account await this.linkUserInDatabase(userId, userInfo.sub); - return response(null, 302, { - Location: `${config.http.base_url}${ + return context.redirect( + `${config.http.base_url}${ config.frontend.routes.home }?${new URLSearchParams({ oidc_account_linked: "true", })}`, - }); + ); } async automaticOidcFlow( diff --git a/utils/response.ts b/utils/response.ts index bf04e2b0..73b3d4a3 100644 --- a/utils/response.ts +++ b/utils/response.ts @@ -1,32 +1,5 @@ import { config } from "~/packages/config-manager"; -export const response = ( - data: BodyInit | null = null, - status = 200, - headers: Record = {}, -) => { - return new Response(data, { - headers: { - "X-Frame-Options": "DENY", - "X-Content-Type-Options": "nosniff", - "Referrer-Policy": "no-referrer", - "Strict-Transport-Security": "max-age=3153600", - "X-Permitted-Cross-Domain-Policies": "none", - "Access-Control-Allow-Credentials": "true", - "Access-Control-Allow-Headers": - "Authorization,Content-Type,Idempotency-Key", - "Access-Control-Allow-Methods": "POST,PUT,DELETE,GET,PATCH,OPTIONS", - "Access-Control-Allow-Origin": "*", - "Access-Control-Expose-Headers": - "Link,X-RateLimit-Reset,X-RateLimit-Limit,X-RateLimit-Remaining,X-Request-Id,Idempotency-Key", - "Content-Security-Policy": - "default-src 'none'; frame-ancestors 'none'; form-action 'none'", - ...headers, - }, - status, - }); -}; - export type Json = | string | number @@ -36,23 +9,6 @@ export type Json = | Json[] | { [key: string]: Json }; -export const xmlResponse = (data: string, status = 200) => { - return response(data, status, { - "Content-Type": "application/xml", - }); -}; - -export const redirect = ( - url: string | URL, - status = 302, - extraHeaders: Record = {}, -) => { - return response(null, status, { - Location: url.toString(), - ...extraHeaders, - }); -}; - export const proxyUrl = (url: string | null = null) => { const urlAsBase64Url = Buffer.from(url || "").toString("base64url"); return url