mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
refactor(api): ♻️ Remove old redirect() and response() in favour of Hono's builtins
This commit is contained in:
parent
691716f7eb
commit
69d7d50239
|
|
@ -1,5 +1,6 @@
|
||||||
import { apiRoute, applyConfig } from "@/api";
|
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 { createRoute } from "@hono/zod-openapi";
|
||||||
import { eq, or } from "drizzle-orm";
|
import { eq, or } from "drizzle-orm";
|
||||||
import { SignJWT } from "jose";
|
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();
|
const searchParams = new URLSearchParams();
|
||||||
|
|
||||||
// Add all data that is not undefined except email and password
|
// 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) {
|
if (key !== "email" && key !== "password" && value !== undefined) {
|
||||||
searchParams.append(key, value);
|
searchParams.append(key, value);
|
||||||
}
|
}
|
||||||
|
|
@ -100,11 +101,11 @@ const returnError = (query: object, error: string, description: string) => {
|
||||||
searchParams.append("error", error);
|
searchParams.append("error", error);
|
||||||
searchParams.append("error_description", description);
|
searchParams.append("error_description", description);
|
||||||
|
|
||||||
return redirect(
|
return context.redirect(
|
||||||
new URL(
|
new URL(
|
||||||
`${config.frontend.routes.login}?${searchParams.toString()}`,
|
`${config.frontend.routes.login}?${searchParams.toString()}`,
|
||||||
config.http.base_url,
|
config.http.base_url,
|
||||||
),
|
).toString(),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -112,7 +113,7 @@ export default apiRoute((app) =>
|
||||||
app.openapi(route, async (context) => {
|
app.openapi(route, async (context) => {
|
||||||
if (config.oidc.forced) {
|
if (config.oidc.forced) {
|
||||||
return returnError(
|
return returnError(
|
||||||
context.req.query(),
|
context,
|
||||||
"invalid_request",
|
"invalid_request",
|
||||||
"Logging in with a password is disabled by the administrator. Please use a valid OpenID Connect provider.",
|
"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(
|
return returnError(
|
||||||
context.req.query(),
|
context,
|
||||||
"invalid_grant",
|
"invalid_grant",
|
||||||
"Invalid identifier or password",
|
"Invalid identifier or password",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.data.passwordResetToken) {
|
if (user.data.passwordResetToken) {
|
||||||
return redirect(
|
return context.redirect(
|
||||||
`${config.frontend.routes.password_reset}?${new URLSearchParams(
|
`${config.frontend.routes.password_reset}?${new URLSearchParams(
|
||||||
{
|
{
|
||||||
token: user.data.passwordResetToken ?? "",
|
token: user.data.passwordResetToken ?? "",
|
||||||
|
|
@ -198,14 +199,15 @@ export default apiRoute((app) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to OAuth authorize with JWT
|
// 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()}`,
|
`${config.frontend.routes.consent}?${searchParams.toString()}`,
|
||||||
302,
|
|
||||||
{
|
|
||||||
"Set-Cookie": `jwt=${jwt}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=${
|
|
||||||
60 * 60
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -51,12 +51,11 @@ export default apiRoute((app) =>
|
||||||
const { redirect_uri, client_id, code } = context.req.valid("query");
|
const { redirect_uri, client_id, code } = context.req.valid("query");
|
||||||
|
|
||||||
const redirectToLogin = (error: string) =>
|
const redirectToLogin = (error: string) =>
|
||||||
Response.redirect(
|
context.redirect(
|
||||||
`${config.frontend.routes.login}?${new URLSearchParams({
|
`${config.frontend.routes.login}?${new URLSearchParams({
|
||||||
...context.req.query,
|
...context.req.query,
|
||||||
error: encodeURIComponent(error),
|
error: encodeURIComponent(error),
|
||||||
}).toString()}`,
|
}).toString()}`,
|
||||||
302,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const foundToken = await db
|
const foundToken = await db
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { apiRoute, applyConfig } from "@/api";
|
import { apiRoute, applyConfig } from "@/api";
|
||||||
import { response } from "@/response";
|
|
||||||
import { createRoute } from "@hono/zod-openapi";
|
import { createRoute } from "@hono/zod-openapi";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
import type { Context } from "hono";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Users } from "~/drizzle/schema";
|
import { Users } from "~/drizzle/schema";
|
||||||
import { config } from "~/packages/config-manager";
|
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();
|
const searchParams = new URLSearchParams();
|
||||||
|
|
||||||
searchParams.append("error", error);
|
searchParams.append("error", error);
|
||||||
searchParams.append("error_description", description);
|
searchParams.append("error_description", description);
|
||||||
searchParams.append("token", token);
|
searchParams.append("token", token);
|
||||||
|
|
||||||
return response(null, 302, {
|
return context.redirect(
|
||||||
Location: new URL(
|
new URL(
|
||||||
`${
|
`${
|
||||||
config.frontend.routes.password_reset
|
config.frontend.routes.password_reset
|
||||||
}?${searchParams.toString()}`,
|
}?${searchParams.toString()}`,
|
||||||
config.http.base_url,
|
config.http.base_url,
|
||||||
).toString(),
|
).toString(),
|
||||||
});
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default apiRoute((app) =>
|
export default apiRoute((app) =>
|
||||||
|
|
@ -74,7 +79,12 @@ export default apiRoute((app) =>
|
||||||
const user = await User.fromSql(eq(Users.passwordResetToken, token));
|
const user = await User.fromSql(eq(Users.passwordResetToken, token));
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return returnError(token, "invalid_token", "Invalid token");
|
return returnError(
|
||||||
|
context,
|
||||||
|
token,
|
||||||
|
"invalid_token",
|
||||||
|
"Invalid token",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await user.update({
|
await user.update({
|
||||||
|
|
@ -82,8 +92,8 @@ export default apiRoute((app) =>
|
||||||
passwordResetToken: null,
|
passwordResetToken: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
return response(null, 302, {
|
return context.redirect(
|
||||||
Location: `${config.frontend.routes.password_reset}?success=true`,
|
`${config.frontend.routes.password_reset}?success=true`,
|
||||||
});
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import {
|
||||||
jsonOrForm,
|
jsonOrForm,
|
||||||
} from "@/api";
|
} from "@/api";
|
||||||
import { mimeLookup } from "@/content_types";
|
import { mimeLookup } from "@/content_types";
|
||||||
import { response } from "@/response";
|
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
@ -110,7 +109,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
await db.delete(Emojis).where(eq(Emojis.id, id));
|
await db.delete(Emojis).where(eq(Emojis.id, id));
|
||||||
|
|
||||||
return response(null, 204);
|
return context.newResponse(null, 204);
|
||||||
}
|
}
|
||||||
|
|
||||||
case "PATCH": {
|
case "PATCH": {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import {
|
||||||
handleZodError,
|
handleZodError,
|
||||||
idValidator,
|
idValidator,
|
||||||
} from "@/api";
|
} from "@/api";
|
||||||
import { response } from "@/response";
|
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { MediaManager } from "~/classes/media/media-manager";
|
import { MediaManager } from "~/classes/media/media-manager";
|
||||||
|
|
@ -71,7 +70,7 @@ export default apiRoute((app) =>
|
||||||
if (attachment.data.url) {
|
if (attachment.data.url) {
|
||||||
return context.json(attachment.toApi());
|
return context.json(attachment.toApi());
|
||||||
}
|
}
|
||||||
return response(null, 206);
|
return context.newResponse(null, 206);
|
||||||
}
|
}
|
||||||
case "PUT": {
|
case "PUT": {
|
||||||
const { description, thumbnail } =
|
const { description, thumbnail } =
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { apiRoute, applyConfig, auth, handleZodError } from "@/api";
|
import { apiRoute, applyConfig, auth, handleZodError } from "@/api";
|
||||||
import { response } from "@/response";
|
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { RolePermissions } from "~/drizzle/schema";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
|
|
@ -76,7 +75,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
await role.linkUser(user.id);
|
await role.linkUser(user.id);
|
||||||
|
|
||||||
return response(null, 204);
|
return context.newResponse(null, 204);
|
||||||
}
|
}
|
||||||
case "DELETE": {
|
case "DELETE": {
|
||||||
const userHighestRole = userRoles.reduce((prev, current) =>
|
const userHighestRole = userRoles.reduce((prev, current) =>
|
||||||
|
|
@ -96,7 +95,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
await role.unlinkUser(user.id);
|
await role.unlinkUser(user.id);
|
||||||
|
|
||||||
return response(null, 204);
|
return context.newResponse(null, 204);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { apiRoute, applyConfig, auth, handleZodError } from "@/api";
|
import { apiRoute, applyConfig, auth, handleZodError } from "@/api";
|
||||||
import { proxyUrl, response } from "@/response";
|
import { proxyUrl } from "@/response";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
@ -103,7 +103,7 @@ export default apiRoute((app) =>
|
||||||
.delete(OpenIdAccounts)
|
.delete(OpenIdAccounts)
|
||||||
.where(eq(OpenIdAccounts.id, account.id));
|
.where(eq(OpenIdAccounts.id, account.id));
|
||||||
|
|
||||||
return response(null, 204);
|
return context.newResponse(null, 204);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { apiRoute, applyConfig, handleZodError } from "@/api";
|
import { apiRoute, applyConfig, handleZodError } from "@/api";
|
||||||
import { response } from "@/response";
|
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { z } from "zod";
|
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
|
// 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-Type": file.type || "application/octet-stream",
|
||||||
"Content-Length": `${file.size - start}`,
|
"Content-Length": `${file.size - start}`,
|
||||||
"Content-Range": `bytes ${start}-${end}/${file.size}`,
|
"Content-Range": `bytes ${start}-${end}/${file.size}`,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { apiRoute, applyConfig, handleZodError } from "@/api";
|
import { apiRoute, applyConfig, handleZodError } from "@/api";
|
||||||
import { response } from "@/response";
|
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
|
import type { StatusCode } from "hono/utils/http-status";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { config } from "~/packages/config-manager";
|
import { config } from "~/packages/config-manager";
|
||||||
|
|
||||||
|
|
@ -62,7 +62,7 @@ export default apiRoute((app) =>
|
||||||
.get("Content-Disposition")
|
.get("Content-Disposition")
|
||||||
?.match(/filename="(.+)"/)?.[1] || id.split("/").pop();
|
?.match(/filename="(.+)"/)?.[1] || id.split("/").pop();
|
||||||
|
|
||||||
return response(media.body, media.status, {
|
return context.newResponse(media.body, media.status as StatusCode, {
|
||||||
"Content-Type":
|
"Content-Type":
|
||||||
media.headers.get("Content-Type") ||
|
media.headers.get("Content-Type") ||
|
||||||
"application/octet-stream",
|
"application/octet-stream",
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { apiRoute, applyConfig, handleZodError, jsonOrForm } from "@/api";
|
import { apiRoute, applyConfig, handleZodError, jsonOrForm } from "@/api";
|
||||||
import { randomString } from "@/math";
|
import { randomString } from "@/math";
|
||||||
import { response } from "@/response";
|
|
||||||
import { sentry } from "@/sentry";
|
import { sentry } from "@/sentry";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
|
import type { Context } from "hono";
|
||||||
import { SignJWT, jwtVerify } from "jose";
|
import { SignJWT, jwtVerify } from "jose";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { TokenType } from "~/classes/functions/token";
|
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();
|
const searchParams = new URLSearchParams();
|
||||||
|
|
||||||
// Add all data that is not undefined except email and password
|
// 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) {
|
if (key !== "email" && key !== "password" && value !== undefined) {
|
||||||
searchParams.append(key, value);
|
searchParams.append(key, value);
|
||||||
}
|
}
|
||||||
|
|
@ -72,9 +77,9 @@ const returnError = (query: object, error: string, description: string) => {
|
||||||
searchParams.append("error", error);
|
searchParams.append("error", error);
|
||||||
searchParams.append("error_description", description);
|
searchParams.append("error_description", description);
|
||||||
|
|
||||||
return response(null, 302, {
|
return context.redirect(
|
||||||
Location: `${config.frontend.routes.login}?${searchParams.toString()}`,
|
`${config.frontend.routes.login}?${searchParams.toString()}`,
|
||||||
});
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default apiRoute((app) =>
|
export default apiRoute((app) =>
|
||||||
|
|
@ -94,6 +99,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
if (!cookie) {
|
if (!cookie) {
|
||||||
return returnError(
|
return returnError(
|
||||||
|
context,
|
||||||
body,
|
body,
|
||||||
"invalid_request",
|
"invalid_request",
|
||||||
"No cookies were sent with the request",
|
"No cookies were sent with the request",
|
||||||
|
|
@ -107,6 +113,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
if (!jwt) {
|
if (!jwt) {
|
||||||
return returnError(
|
return returnError(
|
||||||
|
context,
|
||||||
body,
|
body,
|
||||||
"invalid_request",
|
"invalid_request",
|
||||||
"No jwt cookie was sent in the request",
|
"No jwt cookie was sent in the request",
|
||||||
|
|
@ -142,6 +149,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return returnError(
|
return returnError(
|
||||||
|
context,
|
||||||
body,
|
body,
|
||||||
"invalid_request",
|
"invalid_request",
|
||||||
"Invalid JWT, could not verify",
|
"Invalid JWT, could not verify",
|
||||||
|
|
@ -151,24 +159,45 @@ export default apiRoute((app) =>
|
||||||
const payload = result.payload;
|
const payload = result.payload;
|
||||||
|
|
||||||
if (!payload.sub) {
|
if (!payload.sub) {
|
||||||
return returnError(body, "invalid_request", "Invalid sub");
|
return returnError(
|
||||||
|
context,
|
||||||
|
body,
|
||||||
|
"invalid_request",
|
||||||
|
"Invalid sub",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!payload.aud) {
|
if (!payload.aud) {
|
||||||
return returnError(body, "invalid_request", "Invalid aud");
|
return returnError(
|
||||||
|
context,
|
||||||
|
body,
|
||||||
|
"invalid_request",
|
||||||
|
"Invalid aud",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!payload.exp) {
|
if (!payload.exp) {
|
||||||
return returnError(body, "invalid_request", "Invalid exp");
|
return returnError(
|
||||||
|
context,
|
||||||
|
body,
|
||||||
|
"invalid_request",
|
||||||
|
"Invalid exp",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user is authenticated
|
// Check if the user is authenticated
|
||||||
const user = await User.fromId(payload.sub);
|
const user = await User.fromId(payload.sub);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return returnError(body, "invalid_request", "Invalid sub");
|
return returnError(
|
||||||
|
context,
|
||||||
|
body,
|
||||||
|
"invalid_request",
|
||||||
|
"Invalid sub",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.hasPermission(RolePermissions.OAuth)) {
|
if (!user.hasPermission(RolePermissions.OAuth)) {
|
||||||
return returnError(
|
return returnError(
|
||||||
|
context,
|
||||||
body,
|
body,
|
||||||
"invalid_request",
|
"invalid_request",
|
||||||
`User is missing the ${RolePermissions.OAuth} permission`,
|
`User is missing the ${RolePermissions.OAuth} permission`,
|
||||||
|
|
@ -183,6 +212,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
if (!(asksCode || asksToken || asksIdToken)) {
|
if (!(asksCode || asksToken || asksIdToken)) {
|
||||||
return returnError(
|
return returnError(
|
||||||
|
context,
|
||||||
body,
|
body,
|
||||||
"invalid_request",
|
"invalid_request",
|
||||||
"Invalid response_type, must ask for code, token, or id_token",
|
"Invalid response_type, must ask for code, token, or id_token",
|
||||||
|
|
@ -191,6 +221,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
if (asksCode && !redirect_uri) {
|
if (asksCode && !redirect_uri) {
|
||||||
return returnError(
|
return returnError(
|
||||||
|
context,
|
||||||
body,
|
body,
|
||||||
"invalid_request",
|
"invalid_request",
|
||||||
"Redirect URI is required for code flow (can be urn:ietf:wg:oauth:2.0:oob)",
|
"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) {
|
if (!application) {
|
||||||
return returnError(
|
return returnError(
|
||||||
|
context,
|
||||||
body,
|
body,
|
||||||
"invalid_client",
|
"invalid_client",
|
||||||
"Invalid client_id or client_secret",
|
"Invalid client_id or client_secret",
|
||||||
|
|
@ -224,6 +256,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
if (application.redirectUri !== redirect_uri) {
|
if (application.redirectUri !== redirect_uri) {
|
||||||
return returnError(
|
return returnError(
|
||||||
|
context,
|
||||||
body,
|
body,
|
||||||
"invalid_request",
|
"invalid_request",
|
||||||
"Redirect URI does not match client_id",
|
"Redirect URI does not match client_id",
|
||||||
|
|
@ -237,7 +270,12 @@ export default apiRoute((app) =>
|
||||||
scope &&
|
scope &&
|
||||||
!scope.split(" ").every((s) => applicationScopes.includes(s))
|
!scope.split(" ").every((s) => applicationScopes.includes(s))
|
||||||
) {
|
) {
|
||||||
return returnError(body, "invalid_scope", "Invalid scope");
|
return returnError(
|
||||||
|
context,
|
||||||
|
body,
|
||||||
|
"invalid_scope",
|
||||||
|
"Invalid scope",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate tokens
|
// Generate tokens
|
||||||
|
|
@ -323,11 +361,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
redirectUri.search = searchParams.toString();
|
redirectUri.search = searchParams.toString();
|
||||||
|
|
||||||
return response(null, 302, {
|
return context.redirect(redirectUri.toString());
|
||||||
Location: redirectUri.toString(),
|
|
||||||
"Cache-Control": "no-store",
|
|
||||||
Pragma: "no-cache",
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { apiRoute, applyConfig, handleZodError } from "@/api";
|
import { apiRoute, applyConfig, handleZodError } from "@/api";
|
||||||
import { randomString } from "@/math";
|
import { randomString } from "@/math";
|
||||||
import { response } from "@/response";
|
import { setCookie } from "@hono/hono/cookie";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { and, eq, isNull } from "drizzle-orm";
|
import { and, eq, isNull } from "drizzle-orm";
|
||||||
|
import type { Context } from "hono";
|
||||||
import { SignJWT } from "jose";
|
import { SignJWT } from "jose";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { TokenType } from "~/classes/functions/token";
|
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();
|
const searchParams = new URLSearchParams();
|
||||||
|
|
||||||
// Add all data that is not undefined except email and password
|
// 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", error);
|
||||||
searchParams.append("error_description", description);
|
searchParams.append("error_description", description);
|
||||||
|
|
||||||
return response(null, 302, {
|
return context.redirect(
|
||||||
Location: `${config.frontend.routes.login}?${searchParams.toString()}`,
|
`${config.frontend.routes.login}?${searchParams.toString()}`,
|
||||||
});
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -99,9 +105,8 @@ export default apiRoute((app) =>
|
||||||
redirectUrl,
|
redirectUrl,
|
||||||
(error, message, app) =>
|
(error, message, app) =>
|
||||||
returnError(
|
returnError(
|
||||||
{
|
context,
|
||||||
...manager.processOAuth2Error(app),
|
manager.processOAuth2Error(app),
|
||||||
},
|
|
||||||
error,
|
error,
|
||||||
message,
|
message,
|
||||||
),
|
),
|
||||||
|
|
@ -117,7 +122,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
// If linking account
|
// If linking account
|
||||||
if (link && user_id) {
|
if (link && user_id) {
|
||||||
return await manager.linkUser(user_id, userInfo);
|
return await manager.linkUser(user_id, context, userInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
let userId = (
|
let userId = (
|
||||||
|
|
@ -191,6 +196,7 @@ export default apiRoute((app) =>
|
||||||
userId = user.id;
|
userId = user.id;
|
||||||
} else {
|
} else {
|
||||||
return returnError(
|
return returnError(
|
||||||
|
context,
|
||||||
{
|
{
|
||||||
redirect_uri: flow.application?.redirectUri,
|
redirect_uri: flow.application?.redirectUri,
|
||||||
client_id: flow.application?.clientId,
|
client_id: flow.application?.clientId,
|
||||||
|
|
@ -207,6 +213,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return returnError(
|
return returnError(
|
||||||
|
context,
|
||||||
{
|
{
|
||||||
redirect_uri: flow.application?.redirectUri,
|
redirect_uri: flow.application?.redirectUri,
|
||||||
client_id: flow.application?.clientId,
|
client_id: flow.application?.clientId,
|
||||||
|
|
@ -220,6 +227,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
if (!user.hasPermission(RolePermissions.OAuth)) {
|
if (!user.hasPermission(RolePermissions.OAuth)) {
|
||||||
return returnError(
|
return returnError(
|
||||||
|
context,
|
||||||
{
|
{
|
||||||
redirect_uri: flow.application?.redirectUri,
|
redirect_uri: flow.application?.redirectUri,
|
||||||
client_id: flow.application?.clientId,
|
client_id: flow.application?.clientId,
|
||||||
|
|
@ -268,8 +276,16 @@ export default apiRoute((app) =>
|
||||||
.sign(privateKey);
|
.sign(privateKey);
|
||||||
|
|
||||||
// Redirect back to application
|
// Redirect back to application
|
||||||
return response(null, 302, {
|
setCookie(context, "jwt", jwt, {
|
||||||
Location: new URL(
|
httpOnly: true,
|
||||||
|
secure: true,
|
||||||
|
sameSite: "strict",
|
||||||
|
path: "/",
|
||||||
|
maxAge: 60 * 60,
|
||||||
|
});
|
||||||
|
|
||||||
|
return context.redirect(
|
||||||
|
new URL(
|
||||||
`${config.frontend.routes.consent}?${new URLSearchParams({
|
`${config.frontend.routes.consent}?${new URLSearchParams({
|
||||||
redirect_uri: flow.application.redirectUri,
|
redirect_uri: flow.application.redirectUri,
|
||||||
code,
|
code,
|
||||||
|
|
@ -281,11 +297,7 @@ export default apiRoute((app) =>
|
||||||
}).toString()}`,
|
}).toString()}`,
|
||||||
config.http.base_url,
|
config.http.base_url,
|
||||||
).toString(),
|
).toString(),
|
||||||
// Set cookie with JWT
|
);
|
||||||
"Set-Cookie": `jwt=${jwt}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=${
|
|
||||||
60 * 60
|
|
||||||
}`,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { apiRoute, applyConfig, handleZodError } from "@/api";
|
import { apiRoute, applyConfig, handleZodError } from "@/api";
|
||||||
import { oauthRedirectUri } from "@/constants";
|
import { oauthRedirectUri } from "@/constants";
|
||||||
import { redirect, response } from "@/response";
|
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
|
import type { Context } from "hono";
|
||||||
import {
|
import {
|
||||||
calculatePKCECodeChallenge,
|
calculatePKCECodeChallenge,
|
||||||
discoveryRequest,
|
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();
|
const searchParams = new URLSearchParams();
|
||||||
|
|
||||||
// Add all data that is not undefined except email and password
|
// 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", error);
|
||||||
searchParams.append("error_description", description);
|
searchParams.append("error_description", description);
|
||||||
|
|
||||||
return response(null, 302, {
|
return context.redirect(
|
||||||
Location: `${config.frontend.routes.login}?${searchParams.toString()}`,
|
`${config.frontend.routes.login}?${searchParams.toString()}`,
|
||||||
});
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default apiRoute((app) =>
|
export default apiRoute((app) =>
|
||||||
|
|
@ -65,6 +70,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
if (!client_id || client_id === "undefined") {
|
if (!client_id || client_id === "undefined") {
|
||||||
return returnError(
|
return returnError(
|
||||||
|
context,
|
||||||
body,
|
body,
|
||||||
"invalid_request",
|
"invalid_request",
|
||||||
"client_id is required",
|
"client_id is required",
|
||||||
|
|
@ -77,6 +83,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
if (!issuer) {
|
if (!issuer) {
|
||||||
return returnError(
|
return returnError(
|
||||||
|
context,
|
||||||
body,
|
body,
|
||||||
"invalid_request",
|
"invalid_request",
|
||||||
"issuer is invalid",
|
"issuer is invalid",
|
||||||
|
|
@ -98,6 +105,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
if (!application) {
|
if (!application) {
|
||||||
return returnError(
|
return returnError(
|
||||||
|
context,
|
||||||
body,
|
body,
|
||||||
"invalid_request",
|
"invalid_request",
|
||||||
"client_id is invalid",
|
"client_id is invalid",
|
||||||
|
|
@ -119,7 +127,7 @@ export default apiRoute((app) =>
|
||||||
const codeChallenge =
|
const codeChallenge =
|
||||||
await calculatePKCECodeChallenge(codeVerifier);
|
await calculatePKCECodeChallenge(codeVerifier);
|
||||||
|
|
||||||
return redirect(
|
return context.redirect(
|
||||||
`${authServer.authorization_endpoint}?${new URLSearchParams({
|
`${authServer.authorization_endpoint}?${new URLSearchParams({
|
||||||
client_id: issuer.client_id,
|
client_id: issuer.client_id,
|
||||||
redirect_uri: `${oauthRedirectUri(issuerId)}?flow=${
|
redirect_uri: `${oauthRedirectUri(issuerId)}?flow=${
|
||||||
|
|
@ -131,7 +139,6 @@ export default apiRoute((app) =>
|
||||||
code_challenge_method: "S256",
|
code_challenge_method: "S256",
|
||||||
code_challenge: codeChallenge,
|
code_challenge: codeChallenge,
|
||||||
}).toString()}`,
|
}).toString()}`,
|
||||||
302,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { apiRoute, applyConfig, handleZodError } from "@/api";
|
import { apiRoute, applyConfig, handleZodError } from "@/api";
|
||||||
import { response } from "@/response";
|
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { and, eq, inArray, sql } from "drizzle-orm";
|
import { and, eq, inArray, sql } from "drizzle-orm";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
@ -83,9 +82,6 @@ export default apiRoute((app) =>
|
||||||
403,
|
403,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const objectString = JSON.stringify(apiObject);
|
|
||||||
|
|
||||||
// If base_url uses https and request uses http, rewrite request to use https
|
// If base_url uses https and request uses http, rewrite request to use https
|
||||||
// This fixes reverse proxy errors
|
// This fixes reverse proxy errors
|
||||||
const reqUrl = new URL(context.req.url);
|
const reqUrl = new URL(context.req.url);
|
||||||
|
|
@ -102,10 +98,7 @@ export default apiRoute((app) =>
|
||||||
"GET",
|
"GET",
|
||||||
);
|
);
|
||||||
|
|
||||||
return response(objectString, 200, {
|
return context.json(apiObject, 200, headers.toJSON());
|
||||||
"Content-Type": "application/json",
|
|
||||||
...headers.toJSON(),
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { apiRoute, applyConfig, debugRequest, handleZodError } from "@/api";
|
import { apiRoute, applyConfig, debugRequest, handleZodError } from "@/api";
|
||||||
import { response } from "@/response";
|
|
||||||
import { sentry } from "@/sentry";
|
import { sentry } from "@/sentry";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { getLogger } from "@logtape/logtape";
|
import { getLogger } from "@logtape/logtape";
|
||||||
|
|
@ -153,7 +152,7 @@ export default apiRoute((app) =>
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
// Pretend to accept request
|
// Pretend to accept request
|
||||||
return response(null, 201);
|
return context.newResponse(null, 201);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify request signature
|
// Verify request signature
|
||||||
|
|
@ -200,7 +199,7 @@ export default apiRoute((app) =>
|
||||||
const handler = new RequestParserHandler(body, validator);
|
const handler = new RequestParserHandler(body, validator);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await handler.parseBody({
|
return await handler.parseBody<Response>({
|
||||||
note: async (note) => {
|
note: async (note) => {
|
||||||
const account = await User.resolve(note.author);
|
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) => {
|
follow: async (follow) => {
|
||||||
const account = await User.resolve(follow.author);
|
const account = await User.resolve(follow.author);
|
||||||
|
|
@ -246,7 +245,7 @@ export default apiRoute((app) =>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (foundRelationship.data.following) {
|
if (foundRelationship.data.following) {
|
||||||
return response("Already following", 200);
|
return context.text("Already following", 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
await foundRelationship.update({
|
await foundRelationship.update({
|
||||||
|
|
@ -269,7 +268,7 @@ export default apiRoute((app) =>
|
||||||
await sendFollowAccept(account, user);
|
await sendFollowAccept(account, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response("Follow request sent", 200);
|
return context.text("Follow request sent", 200);
|
||||||
},
|
},
|
||||||
followAccept: async (followAccept) => {
|
followAccept: async (followAccept) => {
|
||||||
const account = await User.resolve(followAccept.author);
|
const account = await User.resolve(followAccept.author);
|
||||||
|
|
@ -288,7 +287,7 @@ export default apiRoute((app) =>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!foundRelationship.data.requested) {
|
if (!foundRelationship.data.requested) {
|
||||||
return response(
|
return context.text(
|
||||||
"There is no follow request to accept",
|
"There is no follow request to accept",
|
||||||
200,
|
200,
|
||||||
);
|
);
|
||||||
|
|
@ -299,7 +298,7 @@ export default apiRoute((app) =>
|
||||||
following: true,
|
following: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return response("Follow request accepted", 200);
|
return context.text("Follow request accepted", 200);
|
||||||
},
|
},
|
||||||
followReject: async (followReject) => {
|
followReject: async (followReject) => {
|
||||||
const account = await User.resolve(followReject.author);
|
const account = await User.resolve(followReject.author);
|
||||||
|
|
@ -318,7 +317,7 @@ export default apiRoute((app) =>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!foundRelationship.data.requested) {
|
if (!foundRelationship.data.requested) {
|
||||||
return response(
|
return context.text(
|
||||||
"There is no follow request to reject",
|
"There is no follow request to reject",
|
||||||
200,
|
200,
|
||||||
);
|
);
|
||||||
|
|
@ -329,7 +328,7 @@ export default apiRoute((app) =>
|
||||||
following: false,
|
following: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return response("Follow request rejected", 200);
|
return context.text("Follow request rejected", 200);
|
||||||
},
|
},
|
||||||
// "delete" is a reserved keyword in JS
|
// "delete" is a reserved keyword in JS
|
||||||
delete: async (delete_) => {
|
delete: async (delete_) => {
|
||||||
|
|
@ -345,7 +344,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
if (note) {
|
if (note) {
|
||||||
await note.delete();
|
await note.delete();
|
||||||
return response("Note deleted", 200);
|
return context.text("Note deleted", 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -357,7 +356,10 @@ export default apiRoute((app) =>
|
||||||
if (otherUser.id === user.id) {
|
if (otherUser.id === user.id) {
|
||||||
// Delete own account
|
// Delete own account
|
||||||
await user.delete();
|
await user.delete();
|
||||||
return response("Account deleted", 200);
|
return context.text(
|
||||||
|
"Account deleted",
|
||||||
|
200,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return context.json(
|
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) => {
|
user: async (user) => {
|
||||||
// Refetch user to ensure we have the latest data
|
// 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: () => {
|
||||||
|
|
||||||
if (result) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return context.json(
|
return context.json(
|
||||||
{ error: "Object has not been implemented" },
|
{ error: "Unknown entity type" },
|
||||||
400,
|
400,
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (isValidationError(e)) {
|
if (isValidationError(e)) {
|
||||||
return context.json(
|
return context.json(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { apiRoute, applyConfig, handleZodError } from "@/api";
|
import { apiRoute, applyConfig, handleZodError } from "@/api";
|
||||||
import { redirect } from "@/response";
|
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
|
|
@ -48,7 +47,7 @@ export default apiRoute((app) =>
|
||||||
context.req.header("user-agent")?.includes("Mozilla") &&
|
context.req.header("user-agent")?.includes("Mozilla") &&
|
||||||
uuid !== "actor"
|
uuid !== "actor"
|
||||||
) {
|
) {
|
||||||
return redirect(user.toApi().url);
|
return context.redirect(user.toApi().url);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userJson = user.toVersia();
|
const userJson = user.toVersia();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { apiRoute, applyConfig } from "@/api";
|
import { apiRoute, applyConfig } from "@/api";
|
||||||
import { xmlResponse } from "@/response";
|
|
||||||
import { config } from "~/packages/config-manager";
|
import { config } from "~/packages/config-manager";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -15,8 +14,9 @@ export const meta = applyConfig({
|
||||||
});
|
});
|
||||||
|
|
||||||
export default apiRoute((app) =>
|
export default apiRoute((app) =>
|
||||||
app.on(meta.allowedMethods, meta.route, () => {
|
app.on(meta.allowedMethods, meta.route, (context) => {
|
||||||
return xmlResponse(
|
context.header("Content-Type", "application/xrd+xml");
|
||||||
|
return context.body(
|
||||||
`<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" template="${new URL(
|
`<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" template="${new URL(
|
||||||
"/.well-known/webfinger",
|
"/.well-known/webfinger",
|
||||||
config.http.base_url,
|
config.http.base_url,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { apiRoute, applyConfig } from "@/api";
|
import { apiRoute, applyConfig } from "@/api";
|
||||||
import { redirect } from "@/response";
|
|
||||||
import { config } from "~/packages/config-manager";
|
import { config } from "~/packages/config-manager";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -15,9 +14,12 @@ export const meta = applyConfig({
|
||||||
});
|
});
|
||||||
|
|
||||||
export default apiRoute((app) =>
|
export default apiRoute((app) =>
|
||||||
app.on(meta.allowedMethods, meta.route, () => {
|
app.on(meta.allowedMethods, meta.route, (context) => {
|
||||||
return redirect(
|
return context.redirect(
|
||||||
new URL("/.well-known/nodeinfo/2.0", config.http.base_url),
|
new URL(
|
||||||
|
"/.well-known/nodeinfo/2.0",
|
||||||
|
config.http.base_url,
|
||||||
|
).toString(),
|
||||||
301,
|
301,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { response } from "@/response";
|
|
||||||
import { createMiddleware } from "@hono/hono/factory";
|
import { createMiddleware } from "@hono/hono/factory";
|
||||||
import { getLogger } from "@logtape/logtape";
|
import { getLogger } from "@logtape/logtape";
|
||||||
import type { SocketAddress } from "bun";
|
import type { SocketAddress } from "bun";
|
||||||
|
|
@ -34,7 +33,7 @@ export const bait = createMiddleware(async (context, next) => {
|
||||||
if (requestIp?.address) {
|
if (requestIp?.address) {
|
||||||
for (const ip of config.http.bait.bait_ips) {
|
for (const ip of config.http.bait.bait_ips) {
|
||||||
if (matches(ip, requestIp.address)) {
|
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) {
|
for (const agent of config.http.bait.bait_user_agents) {
|
||||||
if (new RegExp(agent).test(ua)) {
|
if (new RegExp(agent).test(ua)) {
|
||||||
return response(file);
|
return context.newResponse(file.stream());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { response } from "@/response";
|
|
||||||
import type { InferInsertModel } from "drizzle-orm";
|
import type { InferInsertModel } from "drizzle-orm";
|
||||||
|
import type { Context } from "hono";
|
||||||
import {
|
import {
|
||||||
type AuthorizationServer,
|
type AuthorizationServer,
|
||||||
authorizationCodeGrantRequest,
|
authorizationCodeGrantRequest,
|
||||||
|
|
@ -146,6 +146,7 @@ export class OAuthManager {
|
||||||
|
|
||||||
async linkUser(
|
async linkUser(
|
||||||
userId: string,
|
userId: string,
|
||||||
|
context: Context,
|
||||||
// Return value of automaticOidcFlow
|
// Return value of automaticOidcFlow
|
||||||
oidcFlowData: Exclude<
|
oidcFlowData: Exclude<
|
||||||
Awaited<
|
Awaited<
|
||||||
|
|
@ -158,14 +159,14 @@ export class OAuthManager {
|
||||||
|
|
||||||
// Check if userId is equal to application.clientId
|
// Check if userId is equal to application.clientId
|
||||||
if (!flow.application?.clientId.startsWith(userId)) {
|
if (!flow.application?.clientId.startsWith(userId)) {
|
||||||
return response(null, 302, {
|
return context.redirect(
|
||||||
Location: `${config.http.base_url}${
|
`${config.http.base_url}${
|
||||||
config.frontend.routes.home
|
config.frontend.routes.home
|
||||||
}?${new URLSearchParams({
|
}?${new URLSearchParams({
|
||||||
oidc_account_linking_error: "Account linking error",
|
oidc_account_linking_error: "Account linking error",
|
||||||
oidc_account_linking_error_message: `User ID does not match application client ID (${userId} != ${flow.application?.clientId})`,
|
oidc_account_linking_error_message: `User ID does not match application client ID (${userId} != ${flow.application?.clientId})`,
|
||||||
})}`,
|
})}`,
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if account is already linked
|
// Check if account is already linked
|
||||||
|
|
@ -178,27 +179,27 @@ export class OAuthManager {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (account) {
|
if (account) {
|
||||||
return response(null, 302, {
|
return context.redirect(
|
||||||
Location: `${config.http.base_url}${
|
`${config.http.base_url}${
|
||||||
config.frontend.routes.home
|
config.frontend.routes.home
|
||||||
}?${new URLSearchParams({
|
}?${new URLSearchParams({
|
||||||
oidc_account_linking_error: "Account already linked",
|
oidc_account_linking_error: "Account already linked",
|
||||||
oidc_account_linking_error_message:
|
oidc_account_linking_error_message:
|
||||||
"This account has already been linked to this OpenID Connect provider.",
|
"This account has already been linked to this OpenID Connect provider.",
|
||||||
})}`,
|
})}`,
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link the account
|
// Link the account
|
||||||
await this.linkUserInDatabase(userId, userInfo.sub);
|
await this.linkUserInDatabase(userId, userInfo.sub);
|
||||||
|
|
||||||
return response(null, 302, {
|
return context.redirect(
|
||||||
Location: `${config.http.base_url}${
|
`${config.http.base_url}${
|
||||||
config.frontend.routes.home
|
config.frontend.routes.home
|
||||||
}?${new URLSearchParams({
|
}?${new URLSearchParams({
|
||||||
oidc_account_linked: "true",
|
oidc_account_linked: "true",
|
||||||
})}`,
|
})}`,
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async automaticOidcFlow(
|
async automaticOidcFlow(
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,5 @@
|
||||||
import { config } from "~/packages/config-manager";
|
import { config } from "~/packages/config-manager";
|
||||||
|
|
||||||
export const response = (
|
|
||||||
data: BodyInit | null = null,
|
|
||||||
status = 200,
|
|
||||||
headers: Record<string, string> = {},
|
|
||||||
) => {
|
|
||||||
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 =
|
export type Json =
|
||||||
| string
|
| string
|
||||||
| number
|
| number
|
||||||
|
|
@ -36,23 +9,6 @@ export type Json =
|
||||||
| Json[]
|
| Json[]
|
||||||
| { [key: string]: 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<string, string> = {},
|
|
||||||
) => {
|
|
||||||
return response(null, status, {
|
|
||||||
Location: url.toString(),
|
|
||||||
...extraHeaders,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const proxyUrl = (url: string | null = null) => {
|
export const proxyUrl = (url: string | null = null) => {
|
||||||
const urlAsBase64Url = Buffer.from(url || "").toString("base64url");
|
const urlAsBase64Url = Buffer.from(url || "").toString("base64url");
|
||||||
return url
|
return url
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue