mirror of
https://github.com/versia-pub/server.git
synced 2026-03-13 13:59:16 +01:00
refactor(api): ♻️ Remove old redirect() and response() in favour of Hono's builtins
This commit is contained in:
parent
691716f7eb
commit
69d7d50239
20 changed files with 188 additions and 174 deletions
|
|
@ -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());
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}`,
|
||||
});
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue