Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot] ded7946193 Bump node in the docker-dependencies group across 1 directory
Bumps the docker-dependencies group with 1 update in the / directory: node.


Updates `node` from 24-alpine to 26-alpine

---
updated-dependencies:
- dependency-name: node
  dependency-version: 26-alpine
  dependency-type: direct:production
  dependency-group: docker-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-28 01:32:49 +00:00
11 changed files with 83 additions and 87 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:24-alpine
FROM node:26-alpine
WORKDIR /app
+14 -7
View File
@@ -1,5 +1,7 @@
import {
db,
idp,
idpOrg,
resourcePolicies,
resourcePolicyHeaderAuth,
resourcePolicyPassword,
@@ -18,7 +20,6 @@ import { Config, ResourcePolicyData } from "./types";
import logger from "@server/logger";
import { getUniqueResourcePolicyName } from "@server/db/names";
import { hashPassword } from "@server/auth/password";
import { idpExistsForOrg } from "@server/lib/idp/idpExistsForOrg";
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { tierMatrix } from "../billing/tierMatrix";
@@ -70,13 +71,19 @@ export async function updateResourcePolicies(
// Validate auto-login-idp if provided
if (policyData["auto-login-idp"]) {
const providerExists = await idpExistsForOrg(
policyData["auto-login-idp"],
orgId,
trx
);
const [provider] = await trx
.select()
.from(idp)
.innerJoin(idpOrg, eq(idpOrg.idpId, idp.idpId))
.where(
and(
eq(idp.idpId, policyData["auto-login-idp"]),
eq(idpOrg.orgId, orgId)
)
)
.limit(1);
if (!providerExists) {
if (!provider) {
throw new Error(
`Identity provider not found for policy '${policyNiceId}' in this organization`
);
+1 -1
View File
@@ -28,7 +28,7 @@ export const TargetHealthCheckSchema = z.object({
hostname: z.string(),
port: z.int().min(1).max(65535),
enabled: z.boolean().optional().default(true),
path: z.string().optional().default("/"),
path: z.string().optional(),
scheme: z.string().optional(),
mode: z.string().default("http"),
interval: z.int().default(30),
-36
View File
@@ -1,36 +0,0 @@
import { db, idp, idpOrg, Transaction } from "@server/db";
import { and, eq } from "drizzle-orm";
export function isOrgIdentityProviderMode(): boolean {
return process.env.IDENTITY_PROVIDER_MODE === "org";
}
/**
* Checks whether an identity provider can be used for the given org.
* In org IdP mode, the provider must be linked via idpOrg.
* In global IdP mode, the provider only needs to exist.
*/
export async function idpExistsForOrg(
idpId: number,
orgId: string,
dbOrTrx: typeof db | Transaction = db
): Promise<boolean> {
if (isOrgIdentityProviderMode()) {
const [provider] = await dbOrTrx
.select({ idpId: idp.idpId })
.from(idp)
.innerJoin(idpOrg, eq(idpOrg.idpId, idp.idpId))
.where(and(eq(idp.idpId, idpId), eq(idpOrg.orgId, orgId)))
.limit(1);
return !!provider;
}
const [provider] = await dbOrTrx
.select({ idpId: idp.idpId })
.from(idp)
.where(eq(idp.idpId, idpId))
.limit(1);
return !!provider;
}
@@ -14,6 +14,8 @@
import { hashPassword } from "@server/auth/password";
import {
db,
idp,
idpOrg,
orgs,
resourcePolicies,
resourcePolicyHeaderAuth,
@@ -29,7 +31,6 @@ import {
type ResourcePolicy
} from "@server/db";
import { getUniqueResourcePolicyName } from "@server/db/names";
import { idpExistsForOrg } from "@server/lib/idp/idpExistsForOrg";
import response from "@server/lib/response";
import {
getResourceRuleValueValidationError,
@@ -203,9 +204,14 @@ export async function createResourcePolicy(
// Check if Identity provider in `skipToIdpId` exists
if (skipToIdpId) {
const providerExists = await idpExistsForOrg(skipToIdpId, orgId);
const [provider] = await db
.select()
.from(idp)
.innerJoin(idpOrg, eq(idpOrg.idpId, idp.idpId))
.where(and(eq(idp.idpId, skipToIdpId), eq(idpOrg.orgId, orgId)))
.limit(1);
if (!providerExists) {
if (!provider) {
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
+1 -6
View File
@@ -1397,12 +1397,6 @@ authenticated.put(
// Auth routes
export const authRouter = Router();
unauthenticated.use("/auth", authRouter);
// Register setup-check BEFORE the global auth rate limiter.
// This endpoint is called on every dashboard root page load (pure boolean
// read, no secrets) and must not consume the auth rate-limit budget.
authRouter.get("/initial-setup-complete", auth.initialSetupComplete);
authRouter.use(
rateLimit({
windowMs:
@@ -1707,6 +1701,7 @@ authRouter.post("/idp/:idpId/oidc/generate-url", idp.generateOidcUrl);
authRouter.post("/idp/:idpId/oidc/validate-callback", idp.validateOidcCallback);
authRouter.put("/set-server-admin", auth.setServerAdmin);
authRouter.get("/initial-setup-complete", auth.initialSetupComplete);
authRouter.post("/validate-setup-token", auth.validateSetupToken);
// Security Key routes
@@ -2,6 +2,8 @@ import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import {
db,
idp,
idpOrg,
resourcePolicies,
rolePolicies,
roles,
@@ -16,7 +18,6 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { and, eq, inArray, ne } from "drizzle-orm";
import { OpenAPITags, registry } from "@server/openApi";
import { idpExistsForOrg } from "@server/lib/idp/idpExistsForOrg";
const setResourcePolicyAcccessControlBodySchema = z.strictObject({
sso: z.boolean(),
@@ -26,7 +27,7 @@ const setResourcePolicyAcccessControlBodySchema = z.strictObject({
}),
skipToIdpId: z.int().positive().optional().nullable().openapi({
type: "integer",
description: "Default identity provider ID to skip to on login"
description: "Page number to retrieve"
})
});
@@ -35,8 +36,8 @@ const setResourcePolicyAccessControlParamsSchema = z.strictObject({
});
registry.registerPath({
method: "put",
path: "/resource-policy/{resourcePolicyId}/access-control",
method: "post",
path: "/resource-policy/{resourceId}/access-control",
description:
"Set access control users for a resource policy, including SSO, users, roles, Identity provider.",
tags: [OpenAPITags.PublicResourcePolicyLegacy],
@@ -162,9 +163,16 @@ export async function setResourcePolicyAccessControl(
// Check if Identity provider in `skipToIdpId` exists
if (idpId) {
const providerExists = await idpExistsForOrg(idpId, policy.orgId);
const [provider] = await db
.select()
.from(idp)
.innerJoin(idpOrg, eq(idpOrg.idpId, idp.idpId))
.where(
and(eq(idp.idpId, idpId), eq(idpOrg.orgId, policy.orgId))
)
.limit(1);
if (!providerExists) {
if (!provider) {
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
@@ -40,8 +40,6 @@ import { NewtSiteInstallCommands } from "@app/components/newt-install-commands";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
import type { AxiosResponse } from "axios";
import { useQuery } from "@tanstack/react-query";
import { productUpdatesQueries } from "@app/lib/queries";
export default function CredentialsPage() {
const { env } = useEnvContext();
@@ -69,12 +67,6 @@ export default function CredentialsPage() {
const { isPaidUser } = usePaidStatus();
const { data: latestVersions } = useQuery(
productUpdatesQueries.latestVersion(true)
);
const newtVersion =
latestVersions?.data?.newt?.latestVersion ?? "latest";
// Fetch site defaults for wireguard sites to show in obfuscated config
useEffect(() => {
const fetchSiteDefaults = async () => {
@@ -310,7 +302,6 @@ export default function CredentialsPage() {
id={displayNewtId ?? "**********"}
secret={displaySecret ?? "**************"}
endpoint={env.app.dashboardUrl}
version={newtVersion}
/>
</>
)}
+40 -8
View File
@@ -56,8 +56,6 @@ import { QRCodeCanvas } from "qrcode.react";
import { useTranslations } from "next-intl";
import { build } from "@server/build";
import { NewtSiteInstallCommands } from "@app/components/newt-install-commands";
import { useQuery } from "@tanstack/react-query";
import { productUpdatesQueries } from "@app/lib/queries";
type SiteType = "newt" | "wireguard" | "local";
@@ -191,14 +189,9 @@ export default function Page() {
const [wgConfig, setWgConfig] = useState("");
const [createLoading, setCreateLoading] = useState(false);
const [newtVersion, setNewtVersion] = useState("latest");
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
const { data: latestVersions } = useQuery(
productUpdatesQueries.latestVersion(true)
);
const newtVersion =
latestVersions?.data?.newt?.latestVersion ?? "latest";
const [siteDefaults, setSiteDefaults] =
useState<PickSiteDefaultsResponse | null>(null);
@@ -309,6 +302,45 @@ export default function Page() {
const load = async () => {
setLoadingPage(true);
let currentNewtVersion = "latest";
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000);
const response = await fetch(
`https://api.github.com/repos/fosrl/newt/releases/latest`,
{ signal: controller.signal }
);
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(
t("newtErrorFetchReleases", {
err: response.statusText
})
);
}
const data = await response.json();
const latestVersion = data.tag_name;
currentNewtVersion = latestVersion;
setNewtVersion(latestVersion);
} catch (error) {
if (error instanceof Error && error.name === "AbortError") {
console.error(t("newtErrorFetchTimeout"));
} else {
console.error(
t("newtErrorFetchLatest", {
err:
error instanceof Error
? error.message
: String(error)
})
);
}
}
const generatedKeypair = generateKeypair();
const privateKey = generatedKeypair.privateKey;
+3 -8
View File
@@ -29,19 +29,14 @@ export default async function Page(props: {
const user = await verifySession({ skipCheckVerifyEmail: true });
let complete: boolean | null = null; // null means "unknown" (request errored)
let complete = false;
try {
const setupRes = await internal.get<
AxiosResponse<InitialSetupCompleteResponse>
>(`/auth/initial-setup-complete`, await authCookieHeader());
complete = setupRes.data.data.complete;
} catch (e) {
// Swallow errors (e.g. 429 rate limit, 500, network failure).
// Only redirect to initial-setup when the server *confirms* setup
// is incomplete (complete === false). If the request itself failed we
// cannot tell, so fall through to the login redirect instead.
}
if (complete === false) {
} catch (e) {}
if (!complete) {
redirect("/auth/initial-setup");
}
-2
View File
@@ -23,8 +23,6 @@ export default function IdpTypeIcon({
}: Props) {
const effectiveType = (variant || type || "").toLowerCase();
console.log(`[IdpTypeIcon]`, { effectiveType, variant, type });
let src: string | null = null;
let defaultAlt = "";