mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-08 20:16:50 +02:00
Support pin,pass,whitelist correctly on login
This commit is contained in:
@@ -19,6 +19,9 @@ export async function createResourceSession(opts: {
|
|||||||
userSessionId?: string | null;
|
userSessionId?: string | null;
|
||||||
whitelistId?: number | null;
|
whitelistId?: number | null;
|
||||||
accessTokenId?: string | null;
|
accessTokenId?: string | null;
|
||||||
|
policyPasswordId?: number | null;
|
||||||
|
policyPincodeId?: number | null;
|
||||||
|
policyWhitelistId?: number | null;
|
||||||
doNotExtend?: boolean;
|
doNotExtend?: boolean;
|
||||||
expiresAt?: number | null;
|
expiresAt?: number | null;
|
||||||
sessionLength?: number | null;
|
sessionLength?: number | null;
|
||||||
@@ -28,7 +31,10 @@ export async function createResourceSession(opts: {
|
|||||||
!opts.pincodeId &&
|
!opts.pincodeId &&
|
||||||
!opts.whitelistId &&
|
!opts.whitelistId &&
|
||||||
!opts.accessTokenId &&
|
!opts.accessTokenId &&
|
||||||
!opts.userSessionId
|
!opts.userSessionId &&
|
||||||
|
!opts.policyPasswordId &&
|
||||||
|
!opts.policyPincodeId &&
|
||||||
|
!opts.policyWhitelistId
|
||||||
) {
|
) {
|
||||||
throw new Error("Auth method must be provided");
|
throw new Error("Auth method must be provided");
|
||||||
}
|
}
|
||||||
@@ -49,6 +55,9 @@ export async function createResourceSession(opts: {
|
|||||||
whitelistId: opts.whitelistId || null,
|
whitelistId: opts.whitelistId || null,
|
||||||
doNotExtend: opts.doNotExtend || false,
|
doNotExtend: opts.doNotExtend || false,
|
||||||
accessTokenId: opts.accessTokenId || null,
|
accessTokenId: opts.accessTokenId || null,
|
||||||
|
policyPasswordId: opts.policyPasswordId || null,
|
||||||
|
policyPincodeId: opts.policyPincodeId || null,
|
||||||
|
policyWhitelistId: opts.policyWhitelistId || null,
|
||||||
isRequestToken: opts.isRequestToken || false,
|
isRequestToken: opts.isRequestToken || false,
|
||||||
userSessionId: opts.userSessionId || null,
|
userSessionId: opts.userSessionId || null,
|
||||||
issuedAt: new Date().getTime()
|
issuedAt: new Date().getTime()
|
||||||
|
|||||||
@@ -820,6 +820,24 @@ export const resourceSessions = pgTable("resourceSessions", {
|
|||||||
onDelete: "cascade"
|
onDelete: "cascade"
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
policyPasswordId: integer("policyPasswordId").references(
|
||||||
|
() => resourcePolicyPassword.passwordId,
|
||||||
|
{
|
||||||
|
onDelete: "cascade"
|
||||||
|
}
|
||||||
|
),
|
||||||
|
policyPincodeId: integer("policyPincodeId").references(
|
||||||
|
() => resourcePolicyPincode.pincodeId,
|
||||||
|
{
|
||||||
|
onDelete: "cascade"
|
||||||
|
}
|
||||||
|
),
|
||||||
|
policyWhitelistId: integer("policyWhitelistId").references(
|
||||||
|
() => resourcePolicyWhiteList.whitelistId,
|
||||||
|
{
|
||||||
|
onDelete: "cascade"
|
||||||
|
}
|
||||||
|
),
|
||||||
issuedAt: bigint("issuedAt", { mode: "number" })
|
issuedAt: bigint("issuedAt", { mode: "number" })
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1148,6 +1148,24 @@ export const resourceSessions = sqliteTable("resourceSessions", {
|
|||||||
onDelete: "cascade"
|
onDelete: "cascade"
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
policyPasswordId: integer("policyPasswordId").references(
|
||||||
|
() => resourcePolicyPassword.passwordId,
|
||||||
|
{
|
||||||
|
onDelete: "cascade"
|
||||||
|
}
|
||||||
|
),
|
||||||
|
policyPincodeId: integer("policyPincodeId").references(
|
||||||
|
() => resourcePolicyPincode.pincodeId,
|
||||||
|
{
|
||||||
|
onDelete: "cascade"
|
||||||
|
}
|
||||||
|
),
|
||||||
|
policyWhitelistId: integer("policyWhitelistId").references(
|
||||||
|
() => resourcePolicyWhiteList.whitelistId,
|
||||||
|
{
|
||||||
|
onDelete: "cascade"
|
||||||
|
}
|
||||||
|
),
|
||||||
issuedAt: integer("issuedAt")
|
issuedAt: integer("issuedAt")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -520,7 +520,8 @@ export class TraefikConfigManager {
|
|||||||
build != "oss", // generate the login pages on the cloud and hybrid,
|
build != "oss", // generate the login pages on the cloud and hybrid,
|
||||||
build == "saas"
|
build == "saas"
|
||||||
? false
|
? false
|
||||||
: config.getRawConfig().traefik.allow_raw_resources // dont allow raw resources on saas otherwise use config
|
: config.getRawConfig().traefik.allow_raw_resources, // dont allow raw resources on saas otherwise use config
|
||||||
|
build != "oss" // generate browser gateway targets on cloud and enterprise
|
||||||
);
|
);
|
||||||
|
|
||||||
const domains = new Set<string>();
|
const domains = new Set<string>();
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ export async function getTraefikConfig(
|
|||||||
filterOutNamespaceDomains = false,
|
filterOutNamespaceDomains = false,
|
||||||
generateLoginPageRouters = false,
|
generateLoginPageRouters = false,
|
||||||
allowRawResources = true,
|
allowRawResources = true,
|
||||||
allowMaintenancePage = true
|
allowMaintenancePage = true,
|
||||||
|
allowBrowserGatewayResources = true
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
// Get resources with their targets and sites in a single optimized query
|
// Get resources with their targets and sites in a single optimized query
|
||||||
// Start from sites on this exit node, then join to targets and resources
|
// Start from sites on this exit node, then join to targets and resources
|
||||||
@@ -276,6 +277,39 @@ export async function getTraefikConfig(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Group browser gateway targets by resource
|
||||||
|
type BrowserGatewayResourceEntry = {
|
||||||
|
resourceId: number;
|
||||||
|
name: string;
|
||||||
|
fullDomain: string | null;
|
||||||
|
ssl: boolean | null;
|
||||||
|
subdomain: string | null;
|
||||||
|
domainId: string | null;
|
||||||
|
enabled: boolean | null;
|
||||||
|
wildcard: boolean | null;
|
||||||
|
domainCertResolver: string | null;
|
||||||
|
preferWildcardCert: boolean | null;
|
||||||
|
maintenanceModeEnabled: boolean | null;
|
||||||
|
maintenanceModeType: string | null;
|
||||||
|
maintenanceTitle: string | null;
|
||||||
|
maintenanceMessage: string | null;
|
||||||
|
maintenanceEstimatedTime: string | null;
|
||||||
|
targets: {
|
||||||
|
browserGatewayTargetId: number;
|
||||||
|
bgType: string;
|
||||||
|
siteId: number;
|
||||||
|
siteType: string;
|
||||||
|
siteOnline: boolean | null;
|
||||||
|
subnet: string | null;
|
||||||
|
siteExitNodeId: number | null;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
const browserGatewayResourcesMap = new Map<
|
||||||
|
number,
|
||||||
|
BrowserGatewayResourceEntry
|
||||||
|
>();
|
||||||
|
|
||||||
|
if (allowBrowserGatewayResources) {
|
||||||
// Query browser gateway targets for this exit node
|
// Query browser gateway targets for this exit node
|
||||||
const browserGatewayRows = await db
|
const browserGatewayRows = await db
|
||||||
.select({
|
.select({
|
||||||
@@ -298,7 +332,8 @@ export async function getTraefikConfig(
|
|||||||
maintenanceMessage: resources.maintenanceMessage,
|
maintenanceMessage: resources.maintenanceMessage,
|
||||||
maintenanceEstimatedTime: resources.maintenanceEstimatedTime,
|
maintenanceEstimatedTime: resources.maintenanceEstimatedTime,
|
||||||
// Browser gateway target fields
|
// Browser gateway target fields
|
||||||
browserGatewayTargetId: browserGatewayTarget.browserGatewayTargetId,
|
browserGatewayTargetId:
|
||||||
|
browserGatewayTarget.browserGatewayTargetId,
|
||||||
bgType: browserGatewayTarget.type,
|
bgType: browserGatewayTarget.type,
|
||||||
// Site fields
|
// Site fields
|
||||||
siteId: sites.siteId,
|
siteId: sites.siteId,
|
||||||
@@ -334,38 +369,6 @@ export async function getTraefikConfig(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Group browser gateway targets by resource
|
|
||||||
type BrowserGatewayResourceEntry = {
|
|
||||||
resourceId: number;
|
|
||||||
name: string;
|
|
||||||
fullDomain: string | null;
|
|
||||||
ssl: boolean | null;
|
|
||||||
subdomain: string | null;
|
|
||||||
domainId: string | null;
|
|
||||||
enabled: boolean | null;
|
|
||||||
wildcard: boolean | null;
|
|
||||||
domainCertResolver: string | null;
|
|
||||||
preferWildcardCert: boolean | null;
|
|
||||||
maintenanceModeEnabled: boolean | null;
|
|
||||||
maintenanceModeType: string | null;
|
|
||||||
maintenanceTitle: string | null;
|
|
||||||
maintenanceMessage: string | null;
|
|
||||||
maintenanceEstimatedTime: string | null;
|
|
||||||
targets: {
|
|
||||||
browserGatewayTargetId: number;
|
|
||||||
bgType: string;
|
|
||||||
siteId: number;
|
|
||||||
siteType: string;
|
|
||||||
siteOnline: boolean | null;
|
|
||||||
subnet: string | null;
|
|
||||||
siteExitNodeId: number | null;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
const browserGatewayResourcesMap = new Map<
|
|
||||||
number,
|
|
||||||
BrowserGatewayResourceEntry
|
|
||||||
>();
|
|
||||||
|
|
||||||
for (const row of browserGatewayRows) {
|
for (const row of browserGatewayRows) {
|
||||||
if (filterOutNamespaceDomains && row.domainNamespaceId) {
|
if (filterOutNamespaceDomains && row.domainNamespaceId) {
|
||||||
continue;
|
continue;
|
||||||
@@ -400,6 +403,7 @@ export async function getTraefikConfig(
|
|||||||
siteExitNodeId: row.siteExitNodeId
|
siteExitNodeId: row.siteExitNodeId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let siteResourcesWithFullDomain: {
|
let siteResourcesWithFullDomain: {
|
||||||
siteResourceId: number;
|
siteResourceId: number;
|
||||||
@@ -1055,6 +1059,7 @@ export async function getTraefikConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (allowBrowserGatewayResources) {
|
||||||
// Generate Traefik config for browser gateway resources
|
// Generate Traefik config for browser gateway resources
|
||||||
const browserGatewayPort = 39999;
|
const browserGatewayPort = 39999;
|
||||||
for (const [, bgResource] of browserGatewayResourcesMap.entries()) {
|
for (const [, bgResource] of browserGatewayResourcesMap.entries()) {
|
||||||
@@ -1123,7 +1128,9 @@ export async function getTraefikConfig(
|
|||||||
if (bgResource.ssl) {
|
if (bgResource.ssl) {
|
||||||
const redirectRouterName = `bg-r${bgResource.resourceId}-redirect`;
|
const redirectRouterName = `bg-r${bgResource.resourceId}-redirect`;
|
||||||
config_output.http.routers![redirectRouterName] = {
|
config_output.http.routers![redirectRouterName] = {
|
||||||
entryPoints: [config.getRawConfig().traefik.http_entrypoint],
|
entryPoints: [
|
||||||
|
config.getRawConfig().traefik.http_entrypoint
|
||||||
|
],
|
||||||
middlewares: [redirectHttpsMiddlewareName],
|
middlewares: [redirectHttpsMiddlewareName],
|
||||||
service: bgUiServiceName,
|
service: bgUiServiceName,
|
||||||
rule: hostRule,
|
rule: hostRule,
|
||||||
@@ -1158,15 +1165,19 @@ export async function getTraefikConfig(
|
|||||||
const maintenanceHost =
|
const maintenanceHost =
|
||||||
config.getRawConfig().server.internal_hostname;
|
config.getRawConfig().server.internal_hostname;
|
||||||
|
|
||||||
if (!config_output.http.services) config_output.http.services = {};
|
if (!config_output.http.services)
|
||||||
|
config_output.http.services = {};
|
||||||
if (!config_output.http.middlewares)
|
if (!config_output.http.middlewares)
|
||||||
config_output.http.middlewares = {};
|
config_output.http.middlewares = {};
|
||||||
if (!config_output.http.routers) config_output.http.routers = {};
|
if (!config_output.http.routers)
|
||||||
|
config_output.http.routers = {};
|
||||||
|
|
||||||
config_output.http.services![bgMaintenanceServiceName] = {
|
config_output.http.services![bgMaintenanceServiceName] = {
|
||||||
loadBalancer: {
|
loadBalancer: {
|
||||||
servers: [
|
servers: [
|
||||||
{ url: `http://${maintenanceHost}:${maintenancePort}` }
|
{
|
||||||
|
url: `http://${maintenanceHost}:${maintenancePort}`
|
||||||
|
}
|
||||||
],
|
],
|
||||||
passHostHeader: true
|
passHostHeader: true
|
||||||
}
|
}
|
||||||
@@ -1190,7 +1201,9 @@ export async function getTraefikConfig(
|
|||||||
...(bgResource.ssl ? { tls } : {})
|
...(bgResource.ssl ? { tls } : {})
|
||||||
};
|
};
|
||||||
|
|
||||||
config_output.http.routers![`${bgMaintenanceRouterName}-assets`] = {
|
config_output.http.routers![
|
||||||
|
`${bgMaintenanceRouterName}-assets`
|
||||||
|
] = {
|
||||||
entryPoints: [
|
entryPoints: [
|
||||||
bgResource.ssl ? entrypointHttps : entrypointHttp
|
bgResource.ssl ? entrypointHttps : entrypointHttp
|
||||||
],
|
],
|
||||||
@@ -1225,7 +1238,9 @@ export async function getTraefikConfig(
|
|||||||
.map((t) => ({
|
.map((t) => ({
|
||||||
url: `http://${t.subnet!.split("/")[0]}:${browserGatewayPort}`
|
url: `http://${t.subnet!.split("/")[0]}:${browserGatewayPort}`
|
||||||
}))
|
}))
|
||||||
.filter((v, i, a) => a.findIndex((u) => u.url === v.url) === i);
|
.filter(
|
||||||
|
(v, i, a) => a.findIndex((u) => u.url === v.url) === i
|
||||||
|
);
|
||||||
|
|
||||||
config_output.http.routers![bgRouterName] = {
|
config_output.http.routers![bgRouterName] = {
|
||||||
entryPoints: [
|
entryPoints: [
|
||||||
@@ -1292,8 +1307,9 @@ export async function getTraefikConfig(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Catch-all router rewrites everything on the domain to /{primaryType}
|
// Catch-all router rewrites everything on the domain to /{primaryType}
|
||||||
config_output.http.routers![`bg-r${bgResource.resourceId}-ui-router`] =
|
config_output.http.routers![
|
||||||
{
|
`bg-r${bgResource.resourceId}-ui-router`
|
||||||
|
] = {
|
||||||
entryPoints: [entrypoint],
|
entryPoints: [entrypoint],
|
||||||
middlewares: [...routerMiddlewares, uiRewriteMiddlewareName],
|
middlewares: [...routerMiddlewares, uiRewriteMiddlewareName],
|
||||||
service: bgUiServiceName,
|
service: bgUiServiceName,
|
||||||
@@ -1302,6 +1318,7 @@ export async function getTraefikConfig(
|
|||||||
...(bgResource.ssl ? { tls } : {})
|
...(bgResource.ssl ? { tls } : {})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add Traefik routes for siteResource aliases (HTTP mode + SSL) so that
|
// Add Traefik routes for siteResource aliases (HTTP mode + SSL) so that
|
||||||
// Traefik generates TLS certificates for those domains even when no
|
// Traefik generates TLS certificates for those domains even when no
|
||||||
|
|||||||
@@ -270,7 +270,8 @@ hybridRouter.get(
|
|||||||
true, // But don't allow domain namespace resources
|
true, // But don't allow domain namespace resources
|
||||||
false, // Dont include login pages,
|
false, // Dont include login pages,
|
||||||
true, // allow raw resources
|
true, // allow raw resources
|
||||||
false // dont generate maintenance page
|
false, // dont generate maintenance page
|
||||||
|
false // dont generate browser gateway targets
|
||||||
);
|
);
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { verify } from "@node-rs/argon2";
|
import { verify } from "@node-rs/argon2";
|
||||||
import { generateSessionToken } from "@server/auth/sessions/app";
|
import { generateSessionToken } from "@server/auth/sessions/app";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { orgs, resourcePassword, resources } from "@server/db";
|
import { orgs, resourcePassword, resourcePolicies, resourcePolicyPassword, resources } from "@server/db";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
@@ -61,17 +61,29 @@ export async function authWithPassword(
|
|||||||
const [result] = await db
|
const [result] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(resources)
|
.from(resources)
|
||||||
|
.leftJoin(orgs, eq(orgs.orgId, resources.orgId))
|
||||||
|
.leftJoin(
|
||||||
|
resourcePolicies,
|
||||||
|
eq(resourcePolicies.resourcePolicyId, resources.resourcePolicyId)
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
resourcePolicyPassword,
|
||||||
|
eq(resourcePolicyPassword.resourcePolicyId, resourcePolicies.resourcePolicyId)
|
||||||
|
)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
resourcePassword,
|
resourcePassword,
|
||||||
eq(resourcePassword.resourceId, resources.resourceId)
|
eq(resourcePassword.resourceId, resources.resourceId)
|
||||||
)
|
)
|
||||||
.leftJoin(orgs, eq(orgs.orgId, resources.orgId))
|
|
||||||
.where(eq(resources.resourceId, resourceId))
|
.where(eq(resources.resourceId, resourceId))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
const resource = result?.resources;
|
const resource = result?.resources;
|
||||||
const org = result?.orgs;
|
const org = result?.orgs;
|
||||||
const definedPassword = result?.resourcePassword;
|
|
||||||
|
// Policy password takes precedence over resource-level password
|
||||||
|
const policyPassword = result?.resourcePolicyPassword ?? null;
|
||||||
|
const definedPassword = policyPassword ?? result?.resourcePassword ?? null;
|
||||||
|
const isPolicyPassword = !!policyPassword;
|
||||||
|
|
||||||
if (!org) {
|
if (!org) {
|
||||||
return next(
|
return next(
|
||||||
@@ -89,11 +101,8 @@ export async function authWithPassword(
|
|||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.UNAUTHORIZED,
|
HttpCode.UNAUTHORIZED,
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
"Resource has no password protection"
|
"Resource has no password protection"
|
||||||
)
|
)
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +135,8 @@ export async function authWithPassword(
|
|||||||
await createResourceSession({
|
await createResourceSession({
|
||||||
resourceId,
|
resourceId,
|
||||||
token,
|
token,
|
||||||
passwordId: definedPassword.passwordId,
|
passwordId: isPolicyPassword ? null : definedPassword.passwordId,
|
||||||
|
policyPasswordId: isPolicyPassword ? definedPassword.passwordId : null,
|
||||||
isRequestToken: true,
|
isRequestToken: true,
|
||||||
expiresAt: Date.now() + 1000 * 30, // 30 seconds
|
expiresAt: Date.now() + 1000 * 30, // 30 seconds
|
||||||
sessionLength: 1000 * 30,
|
sessionLength: 1000 * 30,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { generateSessionToken } from "@server/auth/sessions/app";
|
import { generateSessionToken } from "@server/auth/sessions/app";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { orgs, resourcePincode, resources } from "@server/db";
|
import { orgs, resourcePincode, resourcePolicies, resourcePolicyPincode, resources } from "@server/db";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
@@ -60,17 +60,29 @@ export async function authWithPincode(
|
|||||||
const [result] = await db
|
const [result] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(resources)
|
.from(resources)
|
||||||
|
.leftJoin(orgs, eq(orgs.orgId, resources.orgId))
|
||||||
|
.leftJoin(
|
||||||
|
resourcePolicies,
|
||||||
|
eq(resourcePolicies.resourcePolicyId, resources.resourcePolicyId)
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
resourcePolicyPincode,
|
||||||
|
eq(resourcePolicyPincode.resourcePolicyId, resourcePolicies.resourcePolicyId)
|
||||||
|
)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
resourcePincode,
|
resourcePincode,
|
||||||
eq(resourcePincode.resourceId, resources.resourceId)
|
eq(resourcePincode.resourceId, resources.resourceId)
|
||||||
)
|
)
|
||||||
.leftJoin(orgs, eq(orgs.orgId, resources.orgId))
|
|
||||||
.where(eq(resources.resourceId, resourceId))
|
.where(eq(resources.resourceId, resourceId))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
const resource = result?.resources;
|
const resource = result?.resources;
|
||||||
const org = result?.orgs;
|
const org = result?.orgs;
|
||||||
const definedPincode = result?.resourcePincode;
|
|
||||||
|
// Policy pincode takes precedence over resource-level pincode
|
||||||
|
const policyPincode = result?.resourcePolicyPincode ?? null;
|
||||||
|
const definedPincode = policyPincode ?? result?.resourcePincode ?? null;
|
||||||
|
const isPolicyPincode = !!policyPincode;
|
||||||
|
|
||||||
if (!org) {
|
if (!org) {
|
||||||
return next(
|
return next(
|
||||||
@@ -125,7 +137,8 @@ export async function authWithPincode(
|
|||||||
await createResourceSession({
|
await createResourceSession({
|
||||||
resourceId,
|
resourceId,
|
||||||
token,
|
token,
|
||||||
pincodeId: definedPincode.pincodeId,
|
pincodeId: isPolicyPincode ? null : definedPincode.pincodeId,
|
||||||
|
policyPincodeId: isPolicyPincode ? definedPincode.pincodeId : null,
|
||||||
isRequestToken: true,
|
isRequestToken: true,
|
||||||
expiresAt: Date.now() + 1000 * 30, // 30 seconds
|
expiresAt: Date.now() + 1000 * 30, // 30 seconds
|
||||||
sessionLength: 1000 * 30,
|
sessionLength: 1000 * 30,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { generateSessionToken } from "@server/auth/sessions/app";
|
import { generateSessionToken } from "@server/auth/sessions/app";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { orgs, resourceOtp, resources, resourceWhitelist } from "@server/db";
|
import { orgs, resourceOtp, resources, resourceWhitelist, resourcePolicyWhiteList } from "@server/db";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
@@ -59,7 +59,67 @@ export async function authWithWhitelist(
|
|||||||
const { email, otp } = parsedBody.data;
|
const { email, otp } = parsedBody.data;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [result] = await db
|
// Fetch resource and org first
|
||||||
|
const [resourceResult] = await db
|
||||||
|
.select()
|
||||||
|
.from(resources)
|
||||||
|
.leftJoin(orgs, eq(orgs.orgId, resources.orgId))
|
||||||
|
.where(eq(resources.resourceId, resourceId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
const resource = resourceResult?.resources;
|
||||||
|
const org = resourceResult?.orgs;
|
||||||
|
|
||||||
|
if (!resource) {
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.BAD_REQUEST, "Resource does not exist")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!org) {
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.BAD_REQUEST, "Resource does not exist")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wildcard = "*@" + email.split("@")[1];
|
||||||
|
|
||||||
|
// Check policy whitelist first (policy takes precedence over resource whitelist)
|
||||||
|
let policyWhitelistEntry: { whitelistId: number; email: string } | null = null;
|
||||||
|
if (resource.resourcePolicyId) {
|
||||||
|
const [exact] = await db
|
||||||
|
.select()
|
||||||
|
.from(resourcePolicyWhiteList)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(resourcePolicyWhiteList.resourcePolicyId, resource.resourcePolicyId),
|
||||||
|
eq(resourcePolicyWhiteList.email, email)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (exact) {
|
||||||
|
policyWhitelistEntry = exact;
|
||||||
|
} else {
|
||||||
|
logger.debug("Checking for wildcard email in policy: " + wildcard);
|
||||||
|
const [wildcardMatch] = await db
|
||||||
|
.select()
|
||||||
|
.from(resourcePolicyWhiteList)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(resourcePolicyWhiteList.resourcePolicyId, resource.resourcePolicyId),
|
||||||
|
eq(resourcePolicyWhiteList.email, wildcard)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
if (wildcardMatch) policyWhitelistEntry = wildcardMatch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to resource whitelist if not found in policy
|
||||||
|
let resourceWhitelistEntry: { whitelistId: number; email: string } | null = null;
|
||||||
|
if (!policyWhitelistEntry) {
|
||||||
|
const [exact] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(resourceWhitelist)
|
.from(resourceWhitelist)
|
||||||
.where(
|
.where(
|
||||||
@@ -68,24 +128,13 @@ export async function authWithWhitelist(
|
|||||||
eq(resourceWhitelist.email, email)
|
eq(resourceWhitelist.email, email)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.leftJoin(
|
|
||||||
resources,
|
|
||||||
eq(resources.resourceId, resourceWhitelist.resourceId)
|
|
||||||
)
|
|
||||||
.leftJoin(orgs, eq(orgs.orgId, resources.orgId))
|
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
let resource = result?.resources;
|
if (exact) {
|
||||||
let org = result?.orgs;
|
resourceWhitelistEntry = exact;
|
||||||
let whitelistedEmail = result?.resourceWhitelist;
|
} else {
|
||||||
|
|
||||||
if (!whitelistedEmail) {
|
|
||||||
// if email is not found, check for wildcard email
|
|
||||||
const wildcard = "*@" + email.split("@")[1];
|
|
||||||
|
|
||||||
logger.debug("Checking for wildcard email: " + wildcard);
|
logger.debug("Checking for wildcard email: " + wildcard);
|
||||||
|
const [wildcardMatch] = await db
|
||||||
const [result] = await db
|
|
||||||
.select()
|
.select()
|
||||||
.from(resourceWhitelist)
|
.from(resourceWhitelist)
|
||||||
.where(
|
.where(
|
||||||
@@ -94,18 +143,14 @@ export async function authWithWhitelist(
|
|||||||
eq(resourceWhitelist.email, wildcard)
|
eq(resourceWhitelist.email, wildcard)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.leftJoin(
|
|
||||||
resources,
|
|
||||||
eq(resources.resourceId, resourceWhitelist.resourceId)
|
|
||||||
)
|
|
||||||
.leftJoin(orgs, eq(orgs.orgId, resources.orgId))
|
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
if (wildcardMatch) resourceWhitelistEntry = wildcardMatch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resource = result?.resources;
|
const isPolicyWhitelist = !!policyWhitelistEntry;
|
||||||
org = result?.orgs;
|
const whitelistedEmail = policyWhitelistEntry ?? resourceWhitelistEntry;
|
||||||
whitelistedEmail = result?.resourceWhitelist;
|
|
||||||
|
|
||||||
// if wildcard is still not found, return unauthorized
|
|
||||||
if (!whitelistedEmail) {
|
if (!whitelistedEmail) {
|
||||||
if (config.getRawConfig().app.log_failed_attempts) {
|
if (config.getRawConfig().app.log_failed_attempts) {
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -113,7 +158,6 @@ export async function authWithWhitelist(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (org && resource) {
|
|
||||||
logAccessAudit({
|
logAccessAudit({
|
||||||
orgId: org.orgId,
|
orgId: org.orgId,
|
||||||
resourceId: resource.resourceId,
|
resourceId: resource.resourceId,
|
||||||
@@ -123,7 +167,6 @@ export async function authWithWhitelist(
|
|||||||
userAgent: req.headers["user-agent"],
|
userAgent: req.headers["user-agent"],
|
||||||
requestIp: req.ip
|
requestIp: req.ip
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
@@ -135,19 +178,6 @@ export async function authWithWhitelist(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!org) {
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.BAD_REQUEST, "Resource does not exist")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!resource) {
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.BAD_REQUEST, "Resource does not exist")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (otp && email) {
|
if (otp && email) {
|
||||||
const isValidCode = await isValidOtp(
|
const isValidCode = await isValidOtp(
|
||||||
@@ -211,7 +241,8 @@ export async function authWithWhitelist(
|
|||||||
await createResourceSession({
|
await createResourceSession({
|
||||||
resourceId,
|
resourceId,
|
||||||
token,
|
token,
|
||||||
whitelistId: whitelistedEmail.whitelistId,
|
whitelistId: isPolicyWhitelist ? null : whitelistedEmail.whitelistId,
|
||||||
|
policyWhitelistId: isPolicyWhitelist ? whitelistedEmail.whitelistId : null,
|
||||||
isRequestToken: true,
|
isRequestToken: true,
|
||||||
expiresAt: Date.now() + 1000 * 30, // 30 seconds
|
expiresAt: Date.now() + 1000 * 30, // 30 seconds
|
||||||
sessionLength: 1000 * 30,
|
sessionLength: 1000 * 30,
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ export async function traefikConfigProvider(
|
|||||||
config.getRawConfig().traefik.site_types,
|
config.getRawConfig().traefik.site_types,
|
||||||
build == "oss", // filter out the namespace domains in open source
|
build == "oss", // filter out the namespace domains in open source
|
||||||
build != "oss", // generate the login pages on the cloud and and enterprise,
|
build != "oss", // generate the login pages on the cloud and and enterprise,
|
||||||
config.getRawConfig().traefik.allow_raw_resources
|
config.getRawConfig().traefik.allow_raw_resources,
|
||||||
|
build != "oss" // generate browser gateway resources on cloud and enterprise
|
||||||
);
|
);
|
||||||
|
|
||||||
if (traefikConfig?.http?.middlewares) {
|
if (traefikConfig?.http?.middlewares) {
|
||||||
|
|||||||
@@ -583,6 +583,24 @@ export default async function migration() {
|
|||||||
DELETE FROM "resourceWhitelist"
|
DELETE FROM "resourceWhitelist"
|
||||||
WHERE "resourceId" = ${resource.resourceId}
|
WHERE "resourceId" = ${resource.resourceId}
|
||||||
`);
|
`);
|
||||||
|
await db.execute(sql`
|
||||||
|
ALTER TABLE "resourceSessions" ADD COLUMN "policyPasswordId" integer;
|
||||||
|
`);
|
||||||
|
await db.execute(sql`
|
||||||
|
ALTER TABLE "resourceSessions" ADD COLUMN "policyPincodeId" integer;
|
||||||
|
`);
|
||||||
|
await db.execute(sql`
|
||||||
|
ALTER TABLE "resourceSessions" ADD COLUMN "policyWhitelistId" integer;
|
||||||
|
`);
|
||||||
|
await db.execute(sql`
|
||||||
|
ALTER TABLE "resourceSessions" ADD CONSTRAINT "resourceSessions_policyPasswordId_resourcePolicyPassword_passwordId_fk" FOREIGN KEY ("policyPasswordId") REFERENCES "public"."resourcePolicyPassword"("passwordId") ON DELETE cascade ON UPDATE no action;
|
||||||
|
`);
|
||||||
|
await db.execute(sql`
|
||||||
|
ALTER TABLE "resourceSessions" ADD CONSTRAINT "resourceSessions_policyPincodeId_resourcePolicyPincode_pincodeId_fk" FOREIGN KEY ("policyPincodeId") REFERENCES "public"."resourcePolicyPincode"("pincodeId") ON DELETE cascade ON UPDATE no action;
|
||||||
|
`);
|
||||||
|
await db.execute(sql`
|
||||||
|
ALTER TABLE "resourceSessions" ADD CONSTRAINT "resourceSessions_policyWhitelistId_resourcePolicyWhitelist_id_fk" FOREIGN KEY ("policyWhitelistId") REFERENCES "public"."resourcePolicyWhitelist"("id") ON DELETE cascade ON UPDATE no action;
|
||||||
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.execute(sql`COMMIT`);
|
await db.execute(sql`COMMIT`);
|
||||||
|
|||||||
@@ -337,6 +337,21 @@ export default async function migration() {
|
|||||||
ALTER TABLE 'sites' ADD 'autoUpdateOverrideOrg' integer DEFAULT false NOT NULL;
|
ALTER TABLE 'sites' ADD 'autoUpdateOverrideOrg' integer DEFAULT false NOT NULL;
|
||||||
`
|
`
|
||||||
).run();
|
).run();
|
||||||
|
db.prepare(
|
||||||
|
`
|
||||||
|
ALTER TABLE 'resourceSessions' ADD 'policyPasswordId' integer REFERENCES resourcePolicyPassword(passwordId);
|
||||||
|
`
|
||||||
|
).run();
|
||||||
|
db.prepare(
|
||||||
|
`
|
||||||
|
ALTER TABLE 'resourceSessions' ADD 'policyPincodeId' integer REFERENCES resourcePolicyPincode(pincodeId);
|
||||||
|
`
|
||||||
|
).run();
|
||||||
|
db.prepare(
|
||||||
|
`
|
||||||
|
ALTER TABLE 'resourceSessions' ADD 'policyWhitelistId' integer REFERENCES resourcePolicyWhitelist(id);
|
||||||
|
`
|
||||||
|
).run();
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const existingResources = db
|
const existingResources = db
|
||||||
|
|||||||
Reference in New Issue
Block a user