mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-14 00:46:39 +00:00
Merge branch 'dev' into feat/update-popup
This commit is contained in:
@@ -2,7 +2,7 @@ import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
// This is a placeholder value replaced by the build process
|
||||
export const APP_VERSION = "1.12.0-rc.0";
|
||||
export const APP_VERSION = "1.12.1";
|
||||
|
||||
export const __FILENAME = fileURLToPath(import.meta.url);
|
||||
export const __DIRNAME = path.dirname(__FILENAME);
|
||||
|
||||
@@ -352,20 +352,38 @@ export async function validateOidcCallback(
|
||||
|
||||
if (!userOrgInfo.length) {
|
||||
if (existingUser) {
|
||||
// delete the user
|
||||
// cascade will also delete org users
|
||||
// get existing user orgs
|
||||
const existingUserOrgs = await db
|
||||
.select()
|
||||
.from(userOrgs)
|
||||
.where(
|
||||
and(
|
||||
eq(userOrgs.userId, existingUser.userId),
|
||||
eq(userOrgs.autoProvisioned, false)
|
||||
)
|
||||
);
|
||||
|
||||
await db
|
||||
.delete(users)
|
||||
.where(eq(users.userId, existingUser.userId));
|
||||
if (!existingUserOrgs.length) {
|
||||
// delete the user
|
||||
await db
|
||||
.delete(users)
|
||||
.where(eq(users.userId, existingUser.userId));
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.UNAUTHORIZED,
|
||||
`No policies matched for ${userIdentifier}. This user must be added to an organization before logging in.`
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// no orgs to provision and user doesn't exist
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.UNAUTHORIZED,
|
||||
`No policies matched for ${userIdentifier}. This user must be added to an organization before logging in.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.UNAUTHORIZED,
|
||||
`No policies matched for ${userIdentifier}. This user must be added to an organization before logging in.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const orgUserCounts: { orgId: string; userCount: number }[] = [];
|
||||
|
||||
@@ -92,7 +92,7 @@ export async function updateOrg(
|
||||
const { orgId } = parsedParams.data;
|
||||
|
||||
const isLicensed = await isLicensedOrSubscribed(orgId);
|
||||
if (!isLicensed) {
|
||||
if (build == "enterprise" && !isLicensed) {
|
||||
parsedBody.data.requireTwoFactor = undefined;
|
||||
parsedBody.data.maxSessionLengthHours = undefined;
|
||||
parsedBody.data.passwordExpiryDays = undefined;
|
||||
@@ -100,6 +100,7 @@ export async function updateOrg(
|
||||
|
||||
const { tier } = await getOrgTierData(orgId);
|
||||
if (
|
||||
build == "saas" &&
|
||||
tier != TierId.STANDARD &&
|
||||
parsedBody.data.settingsLogRetentionDaysRequest &&
|
||||
parsedBody.data.settingsLogRetentionDaysRequest > 30
|
||||
|
||||
@@ -6,7 +6,9 @@ import {
|
||||
userResources,
|
||||
roleResources,
|
||||
resourcePassword,
|
||||
resourcePincode
|
||||
resourcePincode,
|
||||
targets,
|
||||
targetHealthCheck,
|
||||
} from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -40,6 +42,59 @@ const listResourcesSchema = z.object({
|
||||
.pipe(z.number().int().nonnegative())
|
||||
});
|
||||
|
||||
// (resource fields + a single joined target)
|
||||
type JoinedRow = {
|
||||
resourceId: number;
|
||||
niceId: string;
|
||||
name: string;
|
||||
ssl: boolean;
|
||||
fullDomain: string | null;
|
||||
passwordId: number | null;
|
||||
sso: boolean;
|
||||
pincodeId: number | null;
|
||||
whitelist: boolean;
|
||||
http: boolean;
|
||||
protocol: string;
|
||||
proxyPort: number | null;
|
||||
enabled: boolean;
|
||||
domainId: string | null;
|
||||
headerAuthId: number | null;
|
||||
|
||||
targetId: number | null;
|
||||
targetIp: string | null;
|
||||
targetPort: number | null;
|
||||
targetEnabled: boolean | null;
|
||||
|
||||
hcHealth: string | null;
|
||||
hcEnabled: boolean | null;
|
||||
};
|
||||
|
||||
// grouped by resource with targets[])
|
||||
export type ResourceWithTargets = {
|
||||
resourceId: number;
|
||||
name: string;
|
||||
ssl: boolean;
|
||||
fullDomain: string | null;
|
||||
passwordId: number | null;
|
||||
sso: boolean;
|
||||
pincodeId: number | null;
|
||||
whitelist: boolean;
|
||||
http: boolean;
|
||||
protocol: string;
|
||||
proxyPort: number | null;
|
||||
enabled: boolean;
|
||||
domainId: string | null;
|
||||
niceId: string;
|
||||
headerAuthId: number | null;
|
||||
targets: Array<{
|
||||
targetId: number;
|
||||
ip: string;
|
||||
port: number;
|
||||
enabled: boolean;
|
||||
healthStatus?: 'healthy' | 'unhealthy' | 'unknown';
|
||||
}>;
|
||||
};
|
||||
|
||||
function queryResources(accessibleResourceIds: number[], orgId: string) {
|
||||
return db
|
||||
.select({
|
||||
@@ -57,7 +112,15 @@ function queryResources(accessibleResourceIds: number[], orgId: string) {
|
||||
enabled: resources.enabled,
|
||||
domainId: resources.domainId,
|
||||
niceId: resources.niceId,
|
||||
headerAuthId: resourceHeaderAuth.headerAuthId
|
||||
headerAuthId: resourceHeaderAuth.headerAuthId,
|
||||
|
||||
targetId: targets.targetId,
|
||||
targetIp: targets.ip,
|
||||
targetPort: targets.port,
|
||||
targetEnabled: targets.enabled,
|
||||
|
||||
hcHealth: targetHealthCheck.hcHealth,
|
||||
hcEnabled: targetHealthCheck.hcEnabled,
|
||||
})
|
||||
.from(resources)
|
||||
.leftJoin(
|
||||
@@ -72,6 +135,11 @@ function queryResources(accessibleResourceIds: number[], orgId: string) {
|
||||
resourceHeaderAuth,
|
||||
eq(resourceHeaderAuth.resourceId, resources.resourceId)
|
||||
)
|
||||
.leftJoin(targets, eq(targets.resourceId, resources.resourceId))
|
||||
.leftJoin(
|
||||
targetHealthCheck,
|
||||
eq(targetHealthCheck.targetId, targets.targetId)
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
inArray(resources.resourceId, accessibleResourceIds),
|
||||
@@ -81,7 +149,7 @@ function queryResources(accessibleResourceIds: number[], orgId: string) {
|
||||
}
|
||||
|
||||
export type ListResourcesResponse = {
|
||||
resources: NonNullable<Awaited<ReturnType<typeof queryResources>>>;
|
||||
resources: ResourceWithTargets[];
|
||||
pagination: { total: number; limit: number; offset: number };
|
||||
};
|
||||
|
||||
@@ -146,7 +214,7 @@ export async function listResources(
|
||||
);
|
||||
}
|
||||
|
||||
let accessibleResources;
|
||||
let accessibleResources: Array<{ resourceId: number }>;
|
||||
if (req.user) {
|
||||
accessibleResources = await db
|
||||
.select({
|
||||
@@ -183,9 +251,56 @@ export async function listResources(
|
||||
|
||||
const baseQuery = queryResources(accessibleResourceIds, orgId);
|
||||
|
||||
const resourcesList = await baseQuery!.limit(limit).offset(offset);
|
||||
const rows: JoinedRow[] = await baseQuery.limit(limit).offset(offset);
|
||||
|
||||
// avoids TS issues with reduce/never[]
|
||||
const map = new Map<number, ResourceWithTargets>();
|
||||
|
||||
for (const row of rows) {
|
||||
let entry = map.get(row.resourceId);
|
||||
if (!entry) {
|
||||
entry = {
|
||||
resourceId: row.resourceId,
|
||||
niceId: row.niceId,
|
||||
name: row.name,
|
||||
ssl: row.ssl,
|
||||
fullDomain: row.fullDomain,
|
||||
passwordId: row.passwordId,
|
||||
sso: row.sso,
|
||||
pincodeId: row.pincodeId,
|
||||
whitelist: row.whitelist,
|
||||
http: row.http,
|
||||
protocol: row.protocol,
|
||||
proxyPort: row.proxyPort,
|
||||
enabled: row.enabled,
|
||||
domainId: row.domainId,
|
||||
headerAuthId: row.headerAuthId,
|
||||
targets: [],
|
||||
};
|
||||
map.set(row.resourceId, entry);
|
||||
}
|
||||
|
||||
if (row.targetId != null && row.targetIp && row.targetPort != null && row.targetEnabled != null) {
|
||||
let healthStatus: 'healthy' | 'unhealthy' | 'unknown' = 'unknown';
|
||||
|
||||
if (row.hcEnabled && row.hcHealth) {
|
||||
healthStatus = row.hcHealth as 'healthy' | 'unhealthy' | 'unknown';
|
||||
}
|
||||
|
||||
entry.targets.push({
|
||||
targetId: row.targetId,
|
||||
ip: row.targetIp,
|
||||
port: row.targetPort,
|
||||
enabled: row.targetEnabled,
|
||||
healthStatus: healthStatus,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const resourcesList: ResourceWithTargets[] = Array.from(map.values());
|
||||
|
||||
const totalCountResult = await countQuery;
|
||||
const totalCount = totalCountResult[0].count;
|
||||
const totalCount = totalCountResult[0]?.count ?? 0;
|
||||
|
||||
return response<ListResourcesResponse>(res, {
|
||||
data: {
|
||||
|
||||
@@ -48,10 +48,12 @@ export async function deleteTarget(
|
||||
|
||||
const { targetId } = parsedParams.data;
|
||||
|
||||
const [deletedTarget] = await db
|
||||
.delete(targets)
|
||||
.where(eq(targets.targetId, targetId))
|
||||
.returning();
|
||||
const [deletedTarget] = await db.transaction(async (tx) => {
|
||||
return await tx
|
||||
.delete(targets)
|
||||
.where(eq(targets.targetId, targetId))
|
||||
.returning();
|
||||
});
|
||||
|
||||
if (!deletedTarget) {
|
||||
return next(
|
||||
|
||||
Reference in New Issue
Block a user