mirror of
https://github.com/fosrl/pangolin.git
synced 2025-05-12 21:30:35 +01:00
various small fixes
This commit is contained in:
parent
3ebc01df8c
commit
237960fc5b
11 changed files with 122 additions and 43 deletions
10
Dockerfile
10
Dockerfile
|
@ -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
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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[];
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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}...`);
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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) => (
|
||||||
|
|
Loading…
Reference in a new issue