various small fixes

This commit is contained in:
miloschwartz 2025-04-29 22:59:38 -04:00
parent 3ebc01df8c
commit 237960fc5b
No known key found for this signature in database
11 changed files with 122 additions and 43 deletions

View file

@ -2,8 +2,9 @@ FROM node:20-alpine AS builder
WORKDIR /app WORKDIR /app
COPY package.json package-lock.json ./ # COPY package.json package-lock.json ./
RUN npm ci COPY package.json ./
RUN npm install
COPY . . COPY . .
@ -18,8 +19,9 @@ WORKDIR /app
# Curl used for the health checks # Curl used for the health checks
RUN apk add --no-cache curl RUN apk add --no-cache curl
COPY package.json package-lock.json ./ # COPY package.json package-lock.json ./
RUN npm ci --only=production && npm cache clean --force COPY package.json ./
RUN npm install --only=production && npm cache clean --force
COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static COPY --from=builder /app/.next/static ./.next/static

View file

@ -15,7 +15,6 @@ import {
} from "@server/middlewares"; } from "@server/middlewares";
import { authenticated, unauthenticated } from "@server/routers/integration"; import { authenticated, unauthenticated } from "@server/routers/integration";
import { logIncomingMiddleware } from "./middlewares/logIncoming"; import { logIncomingMiddleware } from "./middlewares/logIncoming";
import { csrfProtectionMiddleware } from "./middlewares/csrfProtection";
import helmet from "helmet"; import helmet from "helmet";
import swaggerUi from "swagger-ui-express"; import swaggerUi from "swagger-ui-express";
import { OpenApiGeneratorV3 } from "@asteasolutions/zod-to-openapi"; import { OpenApiGeneratorV3 } from "@asteasolutions/zod-to-openapi";
@ -37,7 +36,6 @@ export function createIntegrationApiServer() {
if (!dev) { if (!dev) {
apiServer.use(helmet()); apiServer.use(helmet());
apiServer.use(csrfProtectionMiddleware);
} }
apiServer.use(cookieParser()); apiServer.use(cookieParser());

View file

@ -2,7 +2,7 @@ import path from "path";
import { fileURLToPath } from "url"; import { fileURLToPath } from "url";
// This is a placeholder value replaced by the build process // This is a placeholder value replaced by the build process
export const APP_VERSION = "1.2.0"; export const APP_VERSION = "1.3.0";
export const __FILENAME = fileURLToPath(import.meta.url); export const __FILENAME = fileURLToPath(import.meta.url);
export const __DIRNAME = path.dirname(__FILENAME); export const __DIRNAME = path.dirname(__FILENAME);

View file

@ -23,7 +23,7 @@ import { oidcAutoProvision } from "./oidcAutoProvision";
import license from "@server/license/license"; import license from "@server/license/license";
const ensureTrailingSlash = (url: string): string => { const ensureTrailingSlash = (url: string): string => {
return url.endsWith('/') ? url : `${url}/`; return url.endsWith("/") ? url : `${url}/`;
}; };
const paramsSchema = z const paramsSchema = z
@ -228,6 +228,16 @@ export async function validateOidcCallback(
req, req,
res res
}); });
return response<ValidateOidcUrlCallbackResponse>(res, {
data: {
redirectUrl: postAuthRedirectUrl
},
success: true,
error: false,
message: "OIDC callback validated successfully",
status: HttpCode.CREATED
});
} else { } else {
if (!existingUser) { if (!existingUser) {
return next( return next(

View file

@ -27,7 +27,7 @@ const listOrgsSchema = z.object({
registry.registerPath({ registry.registerPath({
method: "get", method: "get",
path: "/user/:userId/orgs", path: "/orgs",
description: "List all organizations in the system.", description: "List all organizations in the system.",
tags: [OpenAPITags.Org], tags: [OpenAPITags.Org],
request: { request: {

View file

@ -29,16 +29,16 @@ const listOrgsSchema = z.object({
.pipe(z.number().int().nonnegative()) .pipe(z.number().int().nonnegative())
}); });
registry.registerPath({ // registry.registerPath({
method: "get", // method: "get",
path: "/user/{userId}/orgs", // path: "/user/{userId}/orgs",
description: "List all organizations for a user.", // description: "List all organizations for a user.",
tags: [OpenAPITags.Org, OpenAPITags.User], // tags: [OpenAPITags.Org, OpenAPITags.User],
request: { // request: {
query: listOrgsSchema // query: listOrgsSchema
}, // },
responses: {} // responses: {}
}); // });
export type ListUserOrgsResponse = { export type ListUserOrgsResponse = {
orgs: Org[]; orgs: Org[];

View file

@ -81,7 +81,10 @@ const updateHttpResourceBodySchema = z
} }
return true; return true;
}, },
{ message: "Invalid TLS Server Name. Use domain name format, or save empty to remove the TLS Server Name." } {
message:
"Invalid TLS Server Name. Use domain name format, or save empty to remove the TLS Server Name."
}
) )
.refine( .refine(
(data) => { (data) => {
@ -90,7 +93,10 @@ const updateHttpResourceBodySchema = z
} }
return true; return true;
}, },
{ message: "Invalid custom Host Header value. Use domain name format, or save empty to unset custom Host Header." } {
message:
"Invalid custom Host Header value. Use domain name format, or save empty to unset custom Host Header."
}
); );
export type UpdateResourceResponse = Resource; export type UpdateResourceResponse = Resource;
@ -300,7 +306,22 @@ async function updateHttpResource(
const updatedResource = await db const updatedResource = await db
.update(resources) .update(resources)
.set(updatePayload) .set({
name: updatePayload.name,
subdomain: updatePayload.subdomain,
ssl: updatePayload.ssl,
sso: updatePayload.sso,
blockAccess: updatePayload.blockAccess,
emailWhitelistEnabled: updatePayload.emailWhitelistEnabled,
isBaseDomain: updatePayload.isBaseDomain,
applyRules: updatePayload.applyRules,
domainId: updatePayload.domainId,
enabled: updatePayload.enabled,
stickySession: updatePayload.stickySession,
tlsServerName: updatePayload.tlsServerName || null,
setHostHeader: updatePayload.setHostHeader || null,
fullDomain: updatePayload.fullDomain
})
.where(eq(resources.resourceId, resource.resourceId)) .where(eq(resources.resourceId, resource.resourceId))
.returning(); .returning();

View file

@ -19,7 +19,15 @@ const paramsSchema = z
const bodySchema = z const bodySchema = z
.object({ .object({
email: z.string().email().optional(), email: z
.string()
.optional()
.refine((data) => {
if (data) {
return z.string().email().safeParse(data).success;
}
return true;
}),
username: z.string().nonempty(), username: z.string().nonempty(),
name: z.string().optional(), name: z.string().optional(),
type: z.enum(["internal", "oidc"]).optional(), type: z.enum(["internal", "oidc"]).optional(),

View file

@ -8,8 +8,6 @@ import { APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts";
const version = "1.3.0"; const version = "1.3.0";
const location = path.join(APP_PATH, "db", "db.sqlite"); const location = path.join(APP_PATH, "db", "db.sqlite");
await migration();
export default async function migration() { export default async function migration() {
console.log(`Running setup script ${version}...`); console.log(`Running setup script ${version}...`);

View file

@ -56,12 +56,16 @@ import { MinusCircle, PlusCircle } from "lucide-react";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { SitePriceCalculator } from "./components/SitePriceCalculator"; import { SitePriceCalculator } from "./components/SitePriceCalculator";
import Link from "next/link"; import Link from "next/link";
import { Checkbox } from "@app/components/ui/checkbox";
const formSchema = z.object({ const formSchema = z.object({
licenseKey: z licenseKey: z
.string() .string()
.nonempty({ message: "License key is required" }) .nonempty({ message: "License key is required" })
.max(255) .max(255),
agreeToTerms: z.boolean().refine((val) => val === true, {
message: "You must agree to the license terms"
})
}); });
function obfuscateLicenseKey(key: string): string { function obfuscateLicenseKey(key: string): string {
@ -95,7 +99,8 @@ export default function LicensePage() {
const form = useForm<z.infer<typeof formSchema>>({ const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
licenseKey: "" licenseKey: "",
agreeToTerms: false
} }
}); });
@ -265,6 +270,39 @@ export default function LicensePage() {
</FormItem> </FormItem>
)} )}
/> />
<FormField
control={form.control}
name="agreeToTerms"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={
field.onChange
}
/>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>
I have read and agree to the
Fossorial Commercial License
- Professional Edition
Subscription Terms.{" "}
<Link
href="https://docs.fossorial.io/license.html"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
View License & Terms
</Link>
</FormLabel>
<FormMessage />
</div>
</FormItem>
)}
/>
</form> </form>
</Form> </Form>
</CredenzaBody> </CredenzaBody>
@ -305,8 +343,7 @@ export default function LicensePage() {
<p> <p>
<b> <b>
This will remove the license key and all This will remove the license key and all
associated permissions. Any sites using this associated permissions granted by it.
license key will no longer be accessible.
</b> </b>
</p> </p>
<p> <p>

View file

@ -22,6 +22,7 @@ import { useUserContext } from "@app/hooks/useUserContext";
import Disable2FaForm from "./Disable2FaForm"; import Disable2FaForm from "./Disable2FaForm";
import Enable2FaForm from "./Enable2FaForm"; import Enable2FaForm from "./Enable2FaForm";
import SupporterStatus from "./SupporterStatus"; import SupporterStatus from "./SupporterStatus";
import { UserType } from "@server/types/UserTypes";
export default function ProfileIcon() { export default function ProfileIcon() {
const { setTheme, theme } = useTheme(); const { setTheme, theme } = useTheme();
@ -108,21 +109,25 @@ export default function ProfileIcon() {
)} )}
</DropdownMenuLabel> </DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
{!user.twoFactorEnabled && ( {user?.type === UserType.Internal && (
<DropdownMenuItem <>
onClick={() => setOpenEnable2fa(true)} {!user.twoFactorEnabled && (
> <DropdownMenuItem
<span>Enable Two-factor</span> onClick={() => setOpenEnable2fa(true)}
</DropdownMenuItem> >
<span>Enable Two-factor</span>
</DropdownMenuItem>
)}
{user.twoFactorEnabled && (
<DropdownMenuItem
onClick={() => setOpenDisable2fa(true)}
>
<span>Disable Two-factor</span>
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
</>
)} )}
{user.twoFactorEnabled && (
<DropdownMenuItem
onClick={() => setOpenDisable2fa(true)}
>
<span>Disable Two-factor</span>
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
<DropdownMenuLabel>Theme</DropdownMenuLabel> <DropdownMenuLabel>Theme</DropdownMenuLabel>
{(["light", "dark", "system"] as const).map( {(["light", "dark", "system"] as const).map(
(themeOption) => ( (themeOption) => (