,
{
...size,
fonts: [
diff --git a/app/src/app/icon.tsx b/app/src/app/icon.tsx
index cc9807962..8bbbd3c7e 100644
--- a/app/src/app/icon.tsx
+++ b/app/src/app/icon.tsx
@@ -9,25 +9,27 @@ export const contentType = "image/png";
export default function icon() {
return new ImageResponse(
- (
-
+
+ ,
size,
);
}
diff --git a/app/src/app/layout.tsx b/app/src/app/layout.tsx
index f351dc9c2..dd2abdd8b 100644
--- a/app/src/app/layout.tsx
+++ b/app/src/app/layout.tsx
@@ -14,7 +14,7 @@ const robotMono = Roboto_Mono({
});
export const metadata: Metadata = {
- metadataBase: new URL(env.BASE_URL),
+ metadataBase: new URL(env.NEXT_PUBLIC_BASE_URL),
title: {
default: "SAM - Sinister Incorporated",
template: "%s | SAM - Sinister Incorporated",
diff --git a/app/src/app/manifest.ts b/app/src/app/manifest.ts
index e72683625..d688553b7 100644
--- a/app/src/app/manifest.ts
+++ b/app/src/app/manifest.ts
@@ -1,10 +1,10 @@
import { env } from "@/env";
import { type MetadataRoute } from "next";
import faviconSrc from "../assets/favicon.svg";
-import screenshotDashboardMobileSrc from "../assets/screenshots/screenshot-dashboard-mobile.avif";
-import screenshotDashboardSrc from "../assets/screenshots/screenshot-dashboard.avif";
import screenshotAppsMobileSrc from "../assets/screenshots/screenshot-apps-mobile.avif";
import screenshotAppsSrc from "../assets/screenshots/screenshot-apps.avif";
+import screenshotDashboardMobileSrc from "../assets/screenshots/screenshot-dashboard-mobile.avif";
+import screenshotDashboardSrc from "../assets/screenshots/screenshot-dashboard.avif";
export default function manifest(): MetadataRoute.Manifest {
return {
@@ -13,8 +13,8 @@ export default function manifest(): MetadataRoute.Manifest {
description:
"Sinister Administration Module (SAM) for the Star Citizen organization Sinister Incorporated",
categories: ["entertainment", "games"], // https://github.com/w3c/manifest/wiki/Categories
- scope: env.BASE_URL, // Will open links outside the app in the browser
- start_url: `${env.BASE_URL}/app`,
+ scope: env.NEXT_PUBLIC_BASE_URL, // Will open links outside the app in the browser
+ start_url: `${env.NEXT_PUBLIC_BASE_URL}/app`,
shortcuts: [
// Can't be individualized based on permissions
{
diff --git a/app/src/app/opengraph-image.tsx b/app/src/app/opengraph-image.tsx
index 1c88b7d98..283fecb01 100644
--- a/app/src/app/opengraph-image.tsx
+++ b/app/src/app/opengraph-image.tsx
@@ -14,12 +14,10 @@ export default async function og() {
).then((res) => res.arrayBuffer());
return new ImageResponse(
- (
-
,
{
...size,
fonts: [
diff --git a/app/src/app/preview-channel/_opengraph-image.tsx b/app/src/app/preview-channel/_opengraph-image.tsx
index 48c15638a..35aaf62c0 100644
--- a/app/src/app/preview-channel/_opengraph-image.tsx
+++ b/app/src/app/preview-channel/_opengraph-image.tsx
@@ -14,16 +14,14 @@ export default async function og() {
).then((res) => res.arrayBuffer());
return new ImageResponse(
- (
-
-
Preview Channel
+
+
Preview Channel
-
-
Sinister Inc
-
Hoist the Black
-
+
+
Sinister Inc
+
Hoist the Black
- ),
+
,
{
...size,
fonts: [
diff --git a/app/src/env.ts b/app/src/env.ts
index a5b510724..a69fa6f0b 100644
--- a/app/src/env.ts
+++ b/app/src/env.ts
@@ -39,19 +39,6 @@ export const env = createEnv({
UNLEASH_SERVER_API_URL: z.url().optional(),
/** Unleash (or any other Unleash-compatible feature flag provider like GitLab) */
UNLEASH_SERVER_API_TOKEN: z.string().optional(),
- BASE_URL: z.preprocess(
- // Uses VERCEL_URL if BASE_URL is not set, e.g. on Vercel's preview deployments
- (str) => {
- if (str) {
- return str;
- } else if (process.env.VERCEL_URL) {
- return `https://${process.env.VERCEL_URL}`;
- }
-
- return "http://localhost:3000";
- },
- z.string().url(),
- ),
COMMIT_SHA: z.preprocess(
// Uses VERCEL_GIT_COMMIT_SHA if COMMIT_SHA is not set
(str) => str || process.env.VERCEL_GIT_COMMIT_SHA,
@@ -101,8 +88,8 @@ export const env = createEnv({
(str) => {
if (str) {
return str;
- } else if (process.env.BASE_URL) {
- return process.env.BASE_URL.replace(/https?:\/\//, "");
+ } else if (process.env.NEXT_PUBLIC_BASE_URL) {
+ return process.env.NEXT_PUBLIC_BASE_URL.replace(/https?:\/\//, "");
} else if (process.env.VERCEL_URL) {
return process.env.VERCEL_URL;
}
@@ -111,6 +98,19 @@ export const env = createEnv({
},
z.string(),
),
+ NEXT_PUBLIC_BASE_URL: z.preprocess(
+ // Uses VERCEL_URL if BASE_URL is not set, e.g. on Vercel's preview deployments
+ (str) => {
+ if (str) {
+ return str;
+ } else if (process.env.VERCEL_URL) {
+ return `https://${process.env.VERCEL_URL}`;
+ }
+
+ return "http://localhost:3000";
+ },
+ z.url(),
+ ),
},
/*
@@ -143,7 +143,7 @@ export const env = createEnv({
COMMIT_SHA: process.env.COMMIT_SHA,
NEXT_PUBLIC_CARE_BEAR_SHOOTER_BUILD_URL:
process.env.NEXT_PUBLIC_CARE_BEAR_SHOOTER_BUILD_URL,
- BASE_URL: process.env.BASE_URL,
+ NEXT_PUBLIC_BASE_URL: process.env.NEXT_PUBLIC_BASE_URL,
AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY,
AWS_EVENT_BUS_ARN: process.env.AWS_EVENT_BUS_ARN,
diff --git a/app/src/modules/auth/components/AdminEnabler.tsx b/app/src/modules/auth/components/AdminEnabler.tsx
index 7664c36cd..8d22c0344 100644
--- a/app/src/modules/auth/components/AdminEnabler.tsx
+++ b/app/src/modules/auth/components/AdminEnabler.tsx
@@ -13,9 +13,9 @@ export const AdminEnabler = ({ className, enabled = false }: Props) => {
const handleClick = () => {
if (enabled) {
- document.cookie = `enable_admin=; path=/; max-age=0;`;
+ document.cookie = `enable_admin=; path=/; samesite=lax; max-age=0;`;
} else {
- document.cookie = `enable_admin=1; path=/; max-age=${60 * 60 * 24 * 7};`;
+ document.cookie = `enable_admin=1; path=/; samesite=lax; max-age=${60 * 60 * 24 * 7};`;
}
router.refresh();
diff --git a/app/src/modules/career/nodes/Markdown/client/schema.ts b/app/src/modules/career/nodes/Markdown/client/schema.ts
index b58bf4787..2cc16895e 100644
--- a/app/src/modules/career/nodes/Markdown/client/schema.ts
+++ b/app/src/modules/career/nodes/Markdown/client/schema.ts
@@ -1,11 +1,12 @@
"use client";
+
import { FlowNodeMarkdownPosition } from "@prisma/client";
import z from "zod";
export const schema = z.object({
id: z.cuid2(),
- markdown: z.string(),
- markdownPosition: z.nativeEnum(FlowNodeMarkdownPosition),
- backgroundColor: z.string(),
+ markdown: z.string().max(5000),
+ markdownPosition: z.enum(FlowNodeMarkdownPosition),
+ backgroundColor: z.string().optional(),
backgroundTransparency: z.coerce.number().min(0).max(1),
});
diff --git a/app/src/modules/career/nodes/Markdown/server/updateFlowSchema.ts b/app/src/modules/career/nodes/Markdown/server/updateFlowSchema.ts
index 27434c749..f657446df 100644
--- a/app/src/modules/career/nodes/Markdown/server/updateFlowSchema.ts
+++ b/app/src/modules/career/nodes/Markdown/server/updateFlowSchema.ts
@@ -11,8 +11,8 @@ export const updateFlowSchema = z.object({
width: z.number(),
height: z.number(),
data: z.object({
- markdown: z.string(),
- markdownPosition: z.nativeEnum(FlowNodeMarkdownPosition),
+ markdown: z.string().max(5000),
+ markdownPosition: z.enum(FlowNodeMarkdownPosition),
backgroundColor: z.string().optional(),
backgroundTransparency: z.number().min(0).max(1).optional(),
}),
diff --git a/app/src/modules/career/nodes/Role/client/schema.ts b/app/src/modules/career/nodes/Role/client/schema.ts
index c42b3bb86..154f2401e 100644
--- a/app/src/modules/career/nodes/Role/client/schema.ts
+++ b/app/src/modules/career/nodes/Role/client/schema.ts
@@ -6,7 +6,7 @@ import z from "zod";
export const schema = z.object({
id: z.cuid2(),
roleId: z.string(),
- roleImage: z.nativeEnum(FlowNodeRoleImage),
+ roleImage: z.enum(FlowNodeRoleImage),
backgroundColor: z.string(),
backgroundTransparency: z.coerce.number().min(0).max(1),
showUnlocked: z.preprocess((value) => value === "true", z.boolean()),
diff --git a/app/src/modules/career/nodes/Role/server/updateFlowSchema.ts b/app/src/modules/career/nodes/Role/server/updateFlowSchema.ts
index e5f0c91a8..faad04ace 100644
--- a/app/src/modules/career/nodes/Role/server/updateFlowSchema.ts
+++ b/app/src/modules/career/nodes/Role/server/updateFlowSchema.ts
@@ -14,7 +14,7 @@ export const updateFlowSchema = z.object({
role: z.object({
id: z.cuid(),
}),
- roleImage: z.nativeEnum(FlowNodeRoleImage),
+ roleImage: z.enum(FlowNodeRoleImage),
backgroundColor: z.string().optional(),
backgroundTransparency: z.number().min(0).max(1).optional(),
showUnlocked: z.boolean().nullish(),
diff --git a/app/src/modules/career/nodes/RoleCitizens/client/schema.ts b/app/src/modules/career/nodes/RoleCitizens/client/schema.ts
index e94c03fda..b5e9d51c8 100644
--- a/app/src/modules/career/nodes/RoleCitizens/client/schema.ts
+++ b/app/src/modules/career/nodes/RoleCitizens/client/schema.ts
@@ -6,7 +6,7 @@ import z from "zod";
export const schema = z.object({
id: z.cuid2(),
roleId: z.string(),
- roleCitizensAlignment: z.nativeEnum(FlowNodeRoleCitizensAlignment),
+ roleCitizensAlignment: z.enum(FlowNodeRoleCitizensAlignment),
roleCitizensHideRole: z.preprocess((value) => value === "true", z.boolean()),
backgroundColor: z.string(),
backgroundTransparency: z.coerce.number().min(0).max(1),
diff --git a/app/src/modules/career/nodes/RoleCitizens/server/updateFlowSchema.ts b/app/src/modules/career/nodes/RoleCitizens/server/updateFlowSchema.ts
index 8bece926c..16fd6a1bc 100644
--- a/app/src/modules/career/nodes/RoleCitizens/server/updateFlowSchema.ts
+++ b/app/src/modules/career/nodes/RoleCitizens/server/updateFlowSchema.ts
@@ -14,7 +14,7 @@ export const updateFlowSchema = z.object({
role: z.object({
id: z.cuid(),
}),
- roleCitizensAlignment: z.nativeEnum(FlowNodeRoleCitizensAlignment),
+ roleCitizensAlignment: z.enum(FlowNodeRoleCitizensAlignment),
roleCitizensHideRole: z.boolean(),
backgroundColor: z.string().optional(),
backgroundTransparency: z.number().min(0).max(1).optional(),
diff --git a/app/src/modules/citizen/actions/updateRoleAssignment.ts b/app/src/modules/citizen/actions/updateRoleAssignment.ts
index 61b53e6ea..d60a3c754 100644
--- a/app/src/modules/citizen/actions/updateRoleAssignment.ts
+++ b/app/src/modules/citizen/actions/updateRoleAssignment.ts
@@ -28,6 +28,15 @@ export const updateRoleAssignments = createAuthenticatedAction(
requestPayload: formData,
};
+ /**
+ * Further validate the request
+ */
+ if (Array.from(formData.keys()).length > 500)
+ return {
+ error: t("Common.badRequest"),
+ requestPayload: formData,
+ };
+
/**
*
*/
diff --git a/app/src/modules/common/components/CreateContext.tsx b/app/src/modules/common/components/CreateContext.tsx
index ea4ece630..08f0ae3fe 100644
--- a/app/src/modules/common/components/CreateContext.tsx
+++ b/app/src/modules/common/components/CreateContext.tsx
@@ -20,9 +20,9 @@ const CreateCitizenForm = dynamic(() =>
);
const CreateOrganizationForm = dynamic(() =>
- import(
- "@/modules/spynet/components/CreateOrganization/CreateOrganizationForm"
- ).then((mod) => mod.CreateOrganizationForm),
+ import("@/modules/spynet/components/CreateOrganization/CreateOrganizationForm").then(
+ (mod) => mod.CreateOrganizationForm,
+ ),
);
const CreateRoleForm = dynamic(() =>
@@ -32,9 +32,9 @@ const CreateRoleForm = dynamic(() =>
);
const CreatePenaltyEntryForm = dynamic(() =>
- import(
- "@/modules/penalty-points/components/CreatePenaltyEntry/CreatePenaltyEntryForm"
- ).then((mod) => mod.CreatePenaltyEntryForm),
+ import("@/modules/penalty-points/components/CreatePenaltyEntry/CreatePenaltyEntryForm").then(
+ (mod) => mod.CreatePenaltyEntryForm,
+ ),
);
const CreateTaskForm = dynamic(() =>
@@ -44,15 +44,15 @@ const CreateTaskForm = dynamic(() =>
);
const CreateProfitDistributionCycleForm = dynamic(() =>
- import(
- "@/modules/profit-distribution/components/CreateProfitDistributionCycleForm"
- ).then((mod) => mod.CreateProfitDistributionCycleForm),
+ import("@/modules/profit-distribution/components/CreateProfitDistributionCycleForm").then(
+ (mod) => mod.CreateProfitDistributionCycleForm,
+ ),
);
const CreateSilcTransactionForm = dynamic(() =>
- import(
- "@/modules/silc/components/CreateSilcTransactionForm"
- ).then((mod) => mod.CreateSilcTransactionForm),
+ import("@/modules/silc/components/CreateSilcTransactionForm").then(
+ (mod) => mod.CreateSilcTransactionForm,
+ ),
);
export const createForms = {
diff --git a/app/src/modules/common/utils/api.ts b/app/src/modules/common/utils/api.ts
index 43159f51c..f70228f41 100644
--- a/app/src/modules/common/utils/api.ts
+++ b/app/src/modules/common/utils/api.ts
@@ -4,6 +4,7 @@
*
* We also create a few inference helpers for input and output types.
*/
+import { env } from "@/env";
import type { AppRouter } from "@/server/api/root";
import { httpBatchLink, loggerLink } from "@trpc/client";
import { createTRPCNext } from "@trpc/next";
@@ -12,8 +13,7 @@ import superjson from "superjson";
const getBaseUrl = () => {
if (typeof window !== "undefined") return ""; // browser should use relative url
- if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url
- return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost
+ return env.NEXT_PUBLIC_BASE_URL; // SSR should use the provided BASE_URL
};
/** A set of type-safe react-query hooks for your tRPC API. */
diff --git a/app/src/modules/notifications/actions/updateMyNotificationSettings.ts b/app/src/modules/notifications/actions/updateMyNotificationSettings.ts
index 3da7049ac..6f37104af 100644
--- a/app/src/modules/notifications/actions/updateMyNotificationSettings.ts
+++ b/app/src/modules/notifications/actions/updateMyNotificationSettings.ts
@@ -30,6 +30,15 @@ export const updateMyNotificationSettings = createAuthenticatedAction(
requestPayload: formData,
};
+ /**
+ * Further validate the request
+ */
+ if (Array.from(formData.keys()).length > 100)
+ return {
+ error: t("Common.badRequest"),
+ requestPayload: formData,
+ };
+
/**
*
*/
diff --git a/app/src/modules/notifications/utils/type-handlers/email_confirmation.ts b/app/src/modules/notifications/utils/type-handlers/email_confirmation.ts
index 4227d8bce..d95410b93 100644
--- a/app/src/modules/notifications/utils/type-handlers/email_confirmation.ts
+++ b/app/src/modules/notifications/utils/type-handlers/email_confirmation.ts
@@ -30,7 +30,7 @@ export const emailConfirmationHandler = async (payload: Payload) => {
{
to: payload.userEmail,
templateProps: {
- baseUrl: env.BASE_URL,
+ baseUrl: env.NEXT_PUBLIC_BASE_URL,
host: env.NEXT_PUBLIC_HOST,
token: emailConfirmationToken,
},
diff --git a/app/src/modules/profit-distribution/actions/updateParticipantAttribute.ts b/app/src/modules/profit-distribution/actions/updateParticipantAttribute.ts
index a188bcc67..f8dd2bcb9 100644
--- a/app/src/modules/profit-distribution/actions/updateParticipantAttribute.ts
+++ b/app/src/modules/profit-distribution/actions/updateParticipantAttribute.ts
@@ -33,6 +33,15 @@ export const updateParticipantAttribute = createAuthenticatedAction(
requestPayload: formData,
};
+ /**
+ * Further validate the request
+ */
+ if (Array.from(formData.keys()).length > 100)
+ return {
+ error: t("Common.badRequest"),
+ requestPayload: formData,
+ };
+
/**
*
*/
diff --git a/app/src/modules/tasks/actions/createTask.ts b/app/src/modules/tasks/actions/createTask.ts
index 0dc6766bd..4ff05952f 100644
--- a/app/src/modules/tasks/actions/createTask.ts
+++ b/app/src/modules/tasks/actions/createTask.ts
@@ -12,13 +12,13 @@ import { serializeError } from "serialize-error";
import { z } from "zod";
const schema = z.object({
- visibility: z.nativeEnum(TaskVisibility),
+ visibility: z.enum(TaskVisibility),
assignmentLimit: z.coerce.number().min(1).nullable(),
assignedToIds: z.array(z.cuid()).max(250).optional(), // Arbitrary (untested) limit to prevent DDoS
title: z.string().trim().max(64),
description: z.string().trim().max(2048).optional(),
expiresAt: z.coerce.date().optional(),
- rewardType: z.nativeEnum(TaskRewardType),
+ rewardType: z.enum(TaskRewardType),
rewardTypeTextValue: z.string().trim().max(2048).optional(),
rewardTypeSilcValue: z.coerce.number().optional(),
rewardTypeNewSilcValue: z.coerce.number().optional(),
diff --git a/app/src/proxy.ts b/app/src/proxy.ts
index 9a0930b31..8edb0fcd4 100644
--- a/app/src/proxy.ts
+++ b/app/src/proxy.ts
@@ -7,7 +7,11 @@ export function proxy(request: NextRequest) {
request.cookies.get("next-auth.session-token") ||
request.cookies.get("__Secure-next-auth.session-token");
- // Early return. Make sure to use `authenticatePage()` on individual pages in order to fully authenticate the user.
+ /**
+ * This is only an early return. Actual verification of the session is done
+ * on the individual pages. Use `authenticatePage()` in order to fully
+ * authenticate the user.
+ */
if (pathname.startsWith("/app") && !sessionCookie) {
return NextResponse.redirect(new URL(`/`, request.url));
}
diff --git a/app/src/server/api/routers/ai.ts b/app/src/server/api/routers/ai.ts
index 4fe7acf88..0ceaa2782 100644
--- a/app/src/server/api/routers/ai.ts
+++ b/app/src/server/api/routers/ai.ts
@@ -1,10 +1,10 @@
+import { env } from "@/env";
import { isOpenAIEnabled } from "@/modules/common/utils/isOpenAIEnabled";
import { log } from "@/modules/logging";
import { getRoles } from "@/modules/roles/queries";
import { TRPCError } from "@trpc/server";
import OpenAI from "openai";
import { type ChatCompletionMessageParam } from "openai/resources/index.mjs";
-import { env } from "process";
import { serializeError } from "serialize-error";
import { z } from "zod";
import { createTRPCRouter, protectedProcedure } from "../trpc";