From 8e9071a3369836a29434a1d6009c58da4cfcb64c Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 3 Jun 2026 14:41:43 -0700 Subject: [PATCH] Converting to use both inline and shared policy --- server/db/queries/verifySessionQueries.ts | 96 ++++++++++--- server/private/routers/hybrid.ts | 104 +++++++++++--- server/routers/badger/exchangeSession.ts | 3 + server/routers/badger/verifySession.ts | 4 + server/routers/resource/authWithPassword.ts | 59 ++++++-- server/routers/resource/authWithPincode.ts | 50 ++++++- server/routers/resource/authWithWhitelist.ts | 81 +++++++++-- .../routers/resource/getResourceAuthInfo.ts | 133 ++++++++++++++---- src/app/ssh/page.tsx | 3 +- 9 files changed, 444 insertions(+), 89 deletions(-) diff --git a/server/db/queries/verifySessionQueries.ts b/server/db/queries/verifySessionQueries.ts index d1f933979..82eec4495 100644 --- a/server/db/queries/verifySessionQueries.ts +++ b/server/db/queries/verifySessionQueries.ts @@ -35,6 +35,7 @@ import { resourcePolicyHeaderAuth, ResourcePolicyHeaderAuth } from "@server/db"; +import { alias } from "drizzle-orm/sqlite-core"; import { and, eq, inArray, or, sql } from "drizzle-orm"; export type ResourceWithAuth = { @@ -67,6 +68,33 @@ export async function getResourceByDomain( wildcardCandidates.push(`*.${parts.slice(i).join(".")}`); } + const sharedPolicy = alias(resourcePolicies, "sharedPolicy"); + const defaultPolicy = alias(resourcePolicies, "defaultPolicy"); + const sharedPolicyPincode = alias( + resourcePolicyPincode, + "sharedPolicyPincode" + ); + const defaultPolicyPincode = alias( + resourcePolicyPincode, + "defaultPolicyPincode" + ); + const sharedPolicyPassword = alias( + resourcePolicyPassword, + "sharedPolicyPassword" + ); + const defaultPolicyPassword = alias( + resourcePolicyPassword, + "defaultPolicyPassword" + ); + const sharedPolicyHeaderAuth = alias( + resourcePolicyHeaderAuth, + "sharedPolicyHeaderAuth" + ); + const defaultPolicyHeaderAuth = alias( + resourcePolicyHeaderAuth, + "defaultPolicyHeaderAuth" + ); + const potentialResults = await db .select() .from(resources) @@ -90,28 +118,56 @@ export async function getResourceByDomain( ) ) .leftJoin( - resourcePolicies, - eq(resourcePolicies.resourcePolicyId, resources.resourcePolicyId) + sharedPolicy, + eq(sharedPolicy.resourcePolicyId, resources.resourcePolicyId) ) .leftJoin( - resourcePolicyPincode, + sharedPolicyPincode, eq( - resourcePolicyPincode.resourcePolicyId, - resourcePolicies.resourcePolicyId + sharedPolicyPincode.resourcePolicyId, + sharedPolicy.resourcePolicyId ) ) .leftJoin( - resourcePolicyPassword, + sharedPolicyPassword, eq( - resourcePolicyPassword.resourcePolicyId, - resourcePolicies.resourcePolicyId + sharedPolicyPassword.resourcePolicyId, + sharedPolicy.resourcePolicyId ) ) .leftJoin( - resourcePolicyHeaderAuth, + sharedPolicyHeaderAuth, eq( - resourcePolicyHeaderAuth.resourcePolicyId, - resourcePolicies.resourcePolicyId + sharedPolicyHeaderAuth.resourcePolicyId, + sharedPolicy.resourcePolicyId + ) + ) + .leftJoin( + defaultPolicy, + eq( + defaultPolicy.resourcePolicyId, + resources.defaultResourcePolicyId + ) + ) + .leftJoin( + defaultPolicyPincode, + eq( + defaultPolicyPincode.resourcePolicyId, + defaultPolicy.resourcePolicyId + ) + ) + .leftJoin( + defaultPolicyPassword, + eq( + defaultPolicyPassword.resourcePolicyId, + defaultPolicy.resourcePolicyId + ) + ) + .leftJoin( + defaultPolicyHeaderAuth, + eq( + defaultPolicyHeaderAuth.resourcePolicyId, + defaultPolicy.resourcePolicyId ) ) .innerJoin(orgs, eq(orgs.orgId, resources.orgId)) @@ -143,18 +199,24 @@ export async function getResourceByDomain( return null; } + const effectivePolicyPincode = + result.sharedPolicyPincode ?? result.defaultPolicyPincode ?? null; + const effectivePolicyPassword = + result.sharedPolicyPassword ?? result.defaultPolicyPassword ?? null; + const effectivePolicyHeaderAuth = + result.sharedPolicyHeaderAuth ?? result.defaultPolicyHeaderAuth ?? null; + return { resource: result.resources, - pincode: result.resourcePolicyPincode ?? result.resourcePincode, - password: result.resourcePolicyPassword ?? result.resourcePassword, - headerAuth: - result.resourcePolicyHeaderAuth ?? result.resourceHeaderAuth, - headerAuthExtendedCompatibility: result.resourcePolicyHeaderAuth + pincode: effectivePolicyPincode ?? result.resourcePincode, + password: effectivePolicyPassword ?? result.resourcePassword, + headerAuth: effectivePolicyHeaderAuth ?? result.resourceHeaderAuth, + headerAuthExtendedCompatibility: effectivePolicyHeaderAuth ? ({ headerAuthExtendedCompatibilityId: 0, resourceId: result.resources.resourceId, extendedCompatibilityIsActivated: - result.resourcePolicyHeaderAuth.extendedCompatibility + effectivePolicyHeaderAuth.extendedCompatibility } as ResourceHeaderAuthExtendedCompatibility) : result.resourceHeaderAuthExtendedCompatibility, org: result.orgs diff --git a/server/private/routers/hybrid.ts b/server/private/routers/hybrid.ts index 27100c3eb..4528bc4ba 100644 --- a/server/private/routers/hybrid.ts +++ b/server/private/routers/hybrid.ts @@ -61,6 +61,7 @@ import { roles } from "@server/db"; import { eq, and, inArray, isNotNull, ne, or, sql } from "drizzle-orm"; +import { alias } from "drizzle-orm/sqlite-core"; import { response } from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import { NextFunction, Request, Response } from "express"; @@ -514,6 +515,33 @@ hybridRouter.get( wildcardCandidates.push(`*.${domainParts.slice(i).join(".")}`); } + const sharedPolicy = alias(resourcePolicies, "sharedPolicy"); + const defaultPolicy = alias(resourcePolicies, "defaultPolicy"); + const sharedPolicyPincode = alias( + resourcePolicyPincode, + "sharedPolicyPincode" + ); + const defaultPolicyPincode = alias( + resourcePolicyPincode, + "defaultPolicyPincode" + ); + const sharedPolicyPassword = alias( + resourcePolicyPassword, + "sharedPolicyPassword" + ); + const defaultPolicyPassword = alias( + resourcePolicyPassword, + "defaultPolicyPassword" + ); + const sharedPolicyHeaderAuth = alias( + resourcePolicyHeaderAuth, + "sharedPolicyHeaderAuth" + ); + const defaultPolicyHeaderAuth = alias( + resourcePolicyHeaderAuth, + "defaultPolicyHeaderAuth" + ); + const potentialResults = await db .select() .from(resources) @@ -537,31 +565,59 @@ hybridRouter.get( ) ) .leftJoin( - resourcePolicies, + sharedPolicy, eq( - resourcePolicies.resourcePolicyId, + sharedPolicy.resourcePolicyId, resources.resourcePolicyId ) ) .leftJoin( - resourcePolicyPincode, + sharedPolicyPincode, eq( - resourcePolicyPincode.resourcePolicyId, - resourcePolicies.resourcePolicyId + sharedPolicyPincode.resourcePolicyId, + sharedPolicy.resourcePolicyId ) ) .leftJoin( - resourcePolicyPassword, + sharedPolicyPassword, eq( - resourcePolicyPassword.resourcePolicyId, - resourcePolicies.resourcePolicyId + sharedPolicyPassword.resourcePolicyId, + sharedPolicy.resourcePolicyId ) ) .leftJoin( - resourcePolicyHeaderAuth, + sharedPolicyHeaderAuth, eq( - resourcePolicyHeaderAuth.resourcePolicyId, - resourcePolicies.resourcePolicyId + sharedPolicyHeaderAuth.resourcePolicyId, + sharedPolicy.resourcePolicyId + ) + ) + .leftJoin( + defaultPolicy, + eq( + defaultPolicy.resourcePolicyId, + resources.defaultResourcePolicyId + ) + ) + .leftJoin( + defaultPolicyPincode, + eq( + defaultPolicyPincode.resourcePolicyId, + defaultPolicy.resourcePolicyId + ) + ) + .leftJoin( + defaultPolicyPassword, + eq( + defaultPolicyPassword.resourcePolicyId, + defaultPolicy.resourcePolicyId + ) + ) + .leftJoin( + defaultPolicyHeaderAuth, + eq( + defaultPolicyHeaderAuth.resourcePolicyId, + defaultPolicy.resourcePolicyId ) ) .innerJoin(orgs, eq(orgs.orgId, resources.orgId)) @@ -614,21 +670,31 @@ hybridRouter.get( }); } + const effectivePolicyPincode = + result.sharedPolicyPincode ?? + result.defaultPolicyPincode ?? + null; + const effectivePolicyPassword = + result.sharedPolicyPassword ?? + result.defaultPolicyPassword ?? + null; + const effectivePolicyHeaderAuth = + result.sharedPolicyHeaderAuth ?? + result.defaultPolicyHeaderAuth ?? + null; + const resourceWithAuth: ResourceWithAuth = { resource: result.resources, - pincode: result.resourcePolicyPincode ?? result.resourcePincode, - password: - result.resourcePolicyPassword ?? result.resourcePassword, + pincode: effectivePolicyPincode ?? result.resourcePincode, + password: effectivePolicyPassword ?? result.resourcePassword, headerAuth: - result.resourcePolicyHeaderAuth ?? - result.resourceHeaderAuth, - headerAuthExtendedCompatibility: result.resourcePolicyHeaderAuth + effectivePolicyHeaderAuth ?? result.resourceHeaderAuth, + headerAuthExtendedCompatibility: effectivePolicyHeaderAuth ? ({ headerAuthExtendedCompatibilityId: 0, resourceId: result.resources.resourceId, extendedCompatibilityIsActivated: - result.resourcePolicyHeaderAuth - .extendedCompatibility + effectivePolicyHeaderAuth.extendedCompatibility } as ResourceHeaderAuthExtendedCompatibility) : result.resourceHeaderAuthExtendedCompatibility, org: result.orgs diff --git a/server/routers/badger/exchangeSession.ts b/server/routers/badger/exchangeSession.ts index 08987961d..6bac50c67 100644 --- a/server/routers/badger/exchangeSession.ts +++ b/server/routers/badger/exchangeSession.ts @@ -188,6 +188,9 @@ export async function exchangeSession( userSessionId: requestSession.userSessionId, whitelistId: requestSession.whitelistId, accessTokenId: requestSession.accessTokenId, + policyPasswordId: requestSession.policyPasswordId, + policyPincodeId: requestSession.policyPincodeId, + policyWhitelistId: requestSession.policyWhitelistId, doNotExtend: false, expiresAt: expires, sessionLength: RESOURCE_SESSION_COOKIE_EXPIRES diff --git a/server/routers/badger/verifySession.ts b/server/routers/badger/verifySession.ts index 2557a2678..9bdfdfa53 100644 --- a/server/routers/badger/verifySession.ts +++ b/server/routers/badger/verifySession.ts @@ -876,6 +876,10 @@ function allowed( message: "Access allowed", status: HttpCode.OK }; + logger.debug( + "++++++++++++++++++++++++++++++++++Access allowed, response data:", + data + ); return response(res, data); } diff --git a/server/routers/resource/authWithPassword.ts b/server/routers/resource/authWithPassword.ts index d556379b1..1ad409901 100644 --- a/server/routers/resource/authWithPassword.ts +++ b/server/routers/resource/authWithPassword.ts @@ -1,10 +1,17 @@ import { verify } from "@node-rs/argon2"; import { generateSessionToken } from "@server/auth/sessions/app"; import { db } from "@server/db"; -import { orgs, resourcePassword, resourcePolicies, resourcePolicyPassword, resources } from "@server/db"; +import { + orgs, + resourcePassword, + resourcePolicies, + resourcePolicyPassword, + resources +} from "@server/db"; import HttpCode from "@server/types/HttpCode"; import response from "@server/lib/response"; import { eq } from "drizzle-orm"; +import { alias } from "drizzle-orm/sqlite-core"; import { NextFunction, Request, Response } from "express"; import createHttpError from "http-errors"; import { z } from "zod"; @@ -58,17 +65,45 @@ export async function authWithPassword( const { password } = parsedBody.data; try { + const sharedPolicy = alias(resourcePolicies, "sharedPolicy"); + const defaultPolicy = alias(resourcePolicies, "defaultPolicy"); + const sharedPolicyPassword = alias( + resourcePolicyPassword, + "sharedPolicyPassword" + ); + const defaultPolicyPassword = alias( + resourcePolicyPassword, + "defaultPolicyPassword" + ); + const [result] = await db .select() .from(resources) .leftJoin(orgs, eq(orgs.orgId, resources.orgId)) .leftJoin( - resourcePolicies, - eq(resourcePolicies.resourcePolicyId, resources.resourcePolicyId) + sharedPolicy, + eq(sharedPolicy.resourcePolicyId, resources.resourcePolicyId) ) .leftJoin( - resourcePolicyPassword, - eq(resourcePolicyPassword.resourcePolicyId, resourcePolicies.resourcePolicyId) + sharedPolicyPassword, + eq( + sharedPolicyPassword.resourcePolicyId, + sharedPolicy.resourcePolicyId + ) + ) + .leftJoin( + defaultPolicy, + eq( + defaultPolicy.resourcePolicyId, + resources.defaultResourcePolicyId + ) + ) + .leftJoin( + defaultPolicyPassword, + eq( + defaultPolicyPassword.resourcePolicyId, + defaultPolicy.resourcePolicyId + ) ) .leftJoin( resourcePassword, @@ -80,9 +115,13 @@ export async function authWithPassword( const resource = result?.resources; const org = result?.orgs; - // Policy password takes precedence over resource-level password - const policyPassword = result?.resourcePolicyPassword ?? null; - const definedPassword = policyPassword ?? result?.resourcePassword ?? null; + // Shared policy takes precedence, then default (inline) policy, then resource-level + const policyPassword = + result?.sharedPolicyPassword ?? + result?.defaultPolicyPassword ?? + null; + const definedPassword = + policyPassword ?? result?.resourcePassword ?? null; const isPolicyPassword = !!policyPassword; if (!org) { @@ -136,7 +175,9 @@ export async function authWithPassword( resourceId, token, passwordId: isPolicyPassword ? null : definedPassword.passwordId, - policyPasswordId: isPolicyPassword ? definedPassword.passwordId : null, + policyPasswordId: isPolicyPassword + ? definedPassword.passwordId + : null, isRequestToken: true, expiresAt: Date.now() + 1000 * 30, // 30 seconds sessionLength: 1000 * 30, diff --git a/server/routers/resource/authWithPincode.ts b/server/routers/resource/authWithPincode.ts index 49b1fca4d..0e547f1eb 100644 --- a/server/routers/resource/authWithPincode.ts +++ b/server/routers/resource/authWithPincode.ts @@ -1,9 +1,16 @@ import { generateSessionToken } from "@server/auth/sessions/app"; import { db } from "@server/db"; -import { orgs, resourcePincode, resourcePolicies, resourcePolicyPincode, resources } from "@server/db"; +import { + orgs, + resourcePincode, + resourcePolicies, + resourcePolicyPincode, + resources +} from "@server/db"; import HttpCode from "@server/types/HttpCode"; import response from "@server/lib/response"; import { eq } from "drizzle-orm"; +import { alias } from "drizzle-orm/sqlite-core"; import { NextFunction, Request, Response } from "express"; import createHttpError from "http-errors"; import { z } from "zod"; @@ -57,17 +64,45 @@ export async function authWithPincode( const { pincode } = parsedBody.data; try { + const sharedPolicy = alias(resourcePolicies, "sharedPolicy"); + const defaultPolicy = alias(resourcePolicies, "defaultPolicy"); + const sharedPolicyPincode = alias( + resourcePolicyPincode, + "sharedPolicyPincode" + ); + const defaultPolicyPincode = alias( + resourcePolicyPincode, + "defaultPolicyPincode" + ); + const [result] = await db .select() .from(resources) .leftJoin(orgs, eq(orgs.orgId, resources.orgId)) .leftJoin( - resourcePolicies, - eq(resourcePolicies.resourcePolicyId, resources.resourcePolicyId) + sharedPolicy, + eq(sharedPolicy.resourcePolicyId, resources.resourcePolicyId) ) .leftJoin( - resourcePolicyPincode, - eq(resourcePolicyPincode.resourcePolicyId, resourcePolicies.resourcePolicyId) + sharedPolicyPincode, + eq( + sharedPolicyPincode.resourcePolicyId, + sharedPolicy.resourcePolicyId + ) + ) + .leftJoin( + defaultPolicy, + eq( + defaultPolicy.resourcePolicyId, + resources.defaultResourcePolicyId + ) + ) + .leftJoin( + defaultPolicyPincode, + eq( + defaultPolicyPincode.resourcePolicyId, + defaultPolicy.resourcePolicyId + ) ) .leftJoin( resourcePincode, @@ -79,8 +114,9 @@ export async function authWithPincode( const resource = result?.resources; const org = result?.orgs; - // Policy pincode takes precedence over resource-level pincode - const policyPincode = result?.resourcePolicyPincode ?? null; + // Shared policy takes precedence, then default (inline) policy, then resource-level + const policyPincode = + result?.sharedPolicyPincode ?? result?.defaultPolicyPincode ?? null; const definedPincode = policyPincode ?? result?.resourcePincode ?? null; const isPolicyPincode = !!policyPincode; diff --git a/server/routers/resource/authWithWhitelist.ts b/server/routers/resource/authWithWhitelist.ts index 08908abfe..aa9199a9d 100644 --- a/server/routers/resource/authWithWhitelist.ts +++ b/server/routers/resource/authWithWhitelist.ts @@ -1,6 +1,12 @@ import { generateSessionToken } from "@server/auth/sessions/app"; import { db } from "@server/db"; -import { orgs, resourceOtp, resources, resourceWhitelist, resourcePolicyWhiteList } from "@server/db"; +import { + orgs, + resourceOtp, + resources, + resourceWhitelist, + resourcePolicyWhiteList +} from "@server/db"; import HttpCode from "@server/types/HttpCode"; import response from "@server/lib/response"; import { eq, and } from "drizzle-orm"; @@ -84,15 +90,21 @@ export async function authWithWhitelist( const wildcard = "*@" + email.split("@")[1]; - // Check policy whitelist first (policy takes precedence over resource whitelist) - let policyWhitelistEntry: { whitelistId: number; email: string } | null = null; + // Check shared policy whitelist first, then default (inline) policy 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.resourcePolicyId, + resource.resourcePolicyId + ), eq(resourcePolicyWhiteList.email, email) ) ) @@ -101,13 +113,57 @@ export async function authWithWhitelist( if (exact) { policyWhitelistEntry = exact; } else { - logger.debug("Checking for wildcard email in policy: " + wildcard); + logger.debug( + "Checking for wildcard email in shared policy: " + wildcard + ); const [wildcardMatch] = await db .select() .from(resourcePolicyWhiteList) .where( and( - eq(resourcePolicyWhiteList.resourcePolicyId, resource.resourcePolicyId), + eq( + resourcePolicyWhiteList.resourcePolicyId, + resource.resourcePolicyId + ), + eq(resourcePolicyWhiteList.email, wildcard) + ) + ) + .limit(1); + if (wildcardMatch) policyWhitelistEntry = wildcardMatch; + } + } + + // Fall back to default (inline) policy whitelist if shared policy didn't match + if (!policyWhitelistEntry && resource.defaultResourcePolicyId) { + const [exact] = await db + .select() + .from(resourcePolicyWhiteList) + .where( + and( + eq( + resourcePolicyWhiteList.resourcePolicyId, + resource.defaultResourcePolicyId + ), + eq(resourcePolicyWhiteList.email, email) + ) + ) + .limit(1); + + if (exact) { + policyWhitelistEntry = exact; + } else { + logger.debug( + "Checking for wildcard email in default policy: " + wildcard + ); + const [wildcardMatch] = await db + .select() + .from(resourcePolicyWhiteList) + .where( + and( + eq( + resourcePolicyWhiteList.resourcePolicyId, + resource.defaultResourcePolicyId + ), eq(resourcePolicyWhiteList.email, wildcard) ) ) @@ -117,7 +173,10 @@ export async function authWithWhitelist( } // Fall back to resource whitelist if not found in policy - let resourceWhitelistEntry: { whitelistId: number; email: string } | null = null; + let resourceWhitelistEntry: { + whitelistId: number; + email: string; + } | null = null; if (!policyWhitelistEntry) { const [exact] = await db .select() @@ -241,8 +300,12 @@ export async function authWithWhitelist( await createResourceSession({ resourceId, token, - whitelistId: isPolicyWhitelist ? null : whitelistedEmail.whitelistId, - policyWhitelistId: isPolicyWhitelist ? whitelistedEmail.whitelistId : null, + whitelistId: isPolicyWhitelist + ? null + : whitelistedEmail.whitelistId, + policyWhitelistId: isPolicyWhitelist + ? whitelistedEmail.whitelistId + : null, isRequestToken: true, expiresAt: Date.now() + 1000 * 30, // 30 seconds sessionLength: 1000 * 30, diff --git a/server/routers/resource/getResourceAuthInfo.ts b/server/routers/resource/getResourceAuthInfo.ts index 5bd73626d..c5120736b 100644 --- a/server/routers/resource/getResourceAuthInfo.ts +++ b/server/routers/resource/getResourceAuthInfo.ts @@ -6,9 +6,13 @@ import { resourcePolicyHeaderAuth, resourcePolicyPassword, resourcePolicyPincode, + resourcePincode, + resourcePassword, + resourceHeaderAuth, resources } from "@server/db"; -import { eq, or } from "drizzle-orm"; +import { eq } from "drizzle-orm"; +import { alias } from "drizzle-orm/sqlite-core"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -60,42 +64,103 @@ export async function getResourceAuthInfo( const isGuidInteger = /^\d+$/.test(resourceGuid); + const sharedPolicy = alias(resourcePolicies, "sharedPolicy"); + const defaultPolicy = alias(resourcePolicies, "defaultPolicy"); + const sharedPolicyPincode = alias( + resourcePolicyPincode, + "sharedPolicyPincode" + ); + const defaultPolicyPincode = alias( + resourcePolicyPincode, + "defaultPolicyPincode" + ); + const sharedPolicyPassword = alias( + resourcePolicyPassword, + "sharedPolicyPassword" + ); + const defaultPolicyPassword = alias( + resourcePolicyPassword, + "defaultPolicyPassword" + ); + const sharedPolicyHeaderAuth = alias( + resourcePolicyHeaderAuth, + "sharedPolicyHeaderAuth" + ); + const defaultPolicyHeaderAuth = alias( + resourcePolicyHeaderAuth, + "defaultPolicyHeaderAuth" + ); + const buildQuery = (whereClause: ReturnType) => db .select() .from(resources) .leftJoin( - resourcePolicies, - or( - eq( - resourcePolicies.resourcePolicyId, - resources.resourcePolicyId - ), - eq( - resourcePolicies.resourcePolicyId, - resources.defaultResourcePolicyId - ) + resourcePincode, + eq(resourcePincode.resourceId, resources.resourceId) + ) + .leftJoin( + resourcePassword, + eq(resourcePassword.resourceId, resources.resourceId) + ) + .leftJoin( + resourceHeaderAuth, + eq(resourceHeaderAuth.resourceId, resources.resourceId) + ) + .leftJoin( + sharedPolicy, + eq( + sharedPolicy.resourcePolicyId, + resources.resourcePolicyId ) ) .leftJoin( - resourcePolicyPincode, + sharedPolicyPincode, eq( - resourcePolicyPincode.resourcePolicyId, - resourcePolicies.resourcePolicyId + sharedPolicyPincode.resourcePolicyId, + sharedPolicy.resourcePolicyId ) ) .leftJoin( - resourcePolicyPassword, + sharedPolicyPassword, eq( - resourcePolicyPassword.resourcePolicyId, - resourcePolicies.resourcePolicyId + sharedPolicyPassword.resourcePolicyId, + sharedPolicy.resourcePolicyId ) ) .leftJoin( - resourcePolicyHeaderAuth, + sharedPolicyHeaderAuth, eq( - resourcePolicyHeaderAuth.resourcePolicyId, - resourcePolicies.resourcePolicyId + sharedPolicyHeaderAuth.resourcePolicyId, + sharedPolicy.resourcePolicyId + ) + ) + .leftJoin( + defaultPolicy, + eq( + defaultPolicy.resourcePolicyId, + resources.defaultResourcePolicyId + ) + ) + .leftJoin( + defaultPolicyPincode, + eq( + defaultPolicyPincode.resourcePolicyId, + defaultPolicy.resourcePolicyId + ) + ) + .leftJoin( + defaultPolicyPassword, + eq( + defaultPolicyPassword.resourcePolicyId, + defaultPolicy.resourcePolicyId + ) + ) + .leftJoin( + defaultPolicyHeaderAuth, + eq( + defaultPolicyHeaderAuth.resourcePolicyId, + defaultPolicy.resourcePolicyId ) ) .where(whereClause) @@ -115,10 +180,24 @@ export async function getResourceAuthInfo( ); } - const policy = result?.resourcePolicies; - const pincode = result?.resourcePolicyPincode; - const password = result?.resourcePolicyPassword; - const headerAuth = result?.resourcePolicyHeaderAuth; + // Shared (custom) policy takes precedence over the default policy. + // For boolean fields (sso, whitelist), only fall back to defaultPolicy + // when there is no shared policy at all. + const effectivePolicyPincode = + result.sharedPolicyPincode ?? result.defaultPolicyPincode ?? null; + const effectivePolicyPassword = + result.sharedPolicyPassword ?? result.defaultPolicyPassword ?? null; + const effectivePolicyHeaderAuth = + result.sharedPolicyHeaderAuth ?? + result.defaultPolicyHeaderAuth ?? + null; + + const effectivePolicy = result.sharedPolicy ?? result.defaultPolicy; + + const pincode = effectivePolicyPincode ?? result.resourcePincode; + const password = effectivePolicyPassword ?? result.resourcePassword; + const headerAuth = + effectivePolicyHeaderAuth ?? result.resourceHeaderAuth; const url = resource.fullDomain ? `${resource.ssl ? "https" : "http"}://${resource.fullDomain}` @@ -134,13 +213,13 @@ export async function getResourceAuthInfo( pincode: pincode !== null, headerAuth: headerAuth !== null, headerAuthExtendedCompatibility: - headerAuth?.extendedCompatibility ?? false, - sso: policy?.sso ?? false, + effectivePolicyHeaderAuth?.extendedCompatibility ?? false, + sso: effectivePolicy?.sso ?? false, blockAccess: resource.blockAccess, url: url ?? "", wildcard: resource.wildcard ?? false, fullDomain: resource.fullDomain, - whitelist: policy?.emailWhitelistEnabled ?? false, + whitelist: effectivePolicy?.emailWhitelistEnabled ?? false, skipToIdpId: resource.skipToIdpId, orgId: resource.orgId, postAuthPath: resource.postAuthPath ?? null diff --git a/src/app/ssh/page.tsx b/src/app/ssh/page.tsx index c6f6dc16f..23cc9d908 100644 --- a/src/app/ssh/page.tsx +++ b/src/app/ssh/page.tsx @@ -150,7 +150,8 @@ export default async function SshPage() { await waitForRoundTripCompletion(messageIds, cookieHeader); } catch (err) { console.error("Error signing SSH key:", err); - error = "Failed to sign SSH key for PAM push authentication."; + error = + "Failed to sign SSH key for PAM push authentication. Did you sign in as a user?"; } } } catch (err) {