mirror of
https://github.com/fosrl/pangolin.git
synced 2026-07-06 12:19:50 +00:00
Support pin,pass,whitelist correctly on login
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { verify } from "@node-rs/argon2";
|
||||
import { generateSessionToken } from "@server/auth/sessions/app";
|
||||
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 response from "@server/lib/response";
|
||||
import { eq } from "drizzle-orm";
|
||||
@@ -61,17 +61,29 @@ export async function authWithPassword(
|
||||
const [result] = await db
|
||||
.select()
|
||||
.from(resources)
|
||||
.leftJoin(orgs, eq(orgs.orgId, resources.orgId))
|
||||
.leftJoin(
|
||||
resourcePolicies,
|
||||
eq(resourcePolicies.resourcePolicyId, resources.resourcePolicyId)
|
||||
)
|
||||
.leftJoin(
|
||||
resourcePolicyPassword,
|
||||
eq(resourcePolicyPassword.resourcePolicyId, resourcePolicies.resourcePolicyId)
|
||||
)
|
||||
.leftJoin(
|
||||
resourcePassword,
|
||||
eq(resourcePassword.resourceId, resources.resourceId)
|
||||
)
|
||||
.leftJoin(orgs, eq(orgs.orgId, resources.orgId))
|
||||
.where(eq(resources.resourceId, resourceId))
|
||||
.limit(1);
|
||||
|
||||
const resource = result?.resources;
|
||||
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) {
|
||||
return next(
|
||||
@@ -89,10 +101,7 @@ export async function authWithPassword(
|
||||
return next(
|
||||
createHttpError(
|
||||
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({
|
||||
resourceId,
|
||||
token,
|
||||
passwordId: definedPassword.passwordId,
|
||||
passwordId: isPolicyPassword ? null : definedPassword.passwordId,
|
||||
policyPasswordId: isPolicyPassword ? definedPassword.passwordId : null,
|
||||
isRequestToken: true,
|
||||
expiresAt: Date.now() + 1000 * 30, // 30 seconds
|
||||
sessionLength: 1000 * 30,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { generateSessionToken } from "@server/auth/sessions/app";
|
||||
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 response from "@server/lib/response";
|
||||
import { eq } from "drizzle-orm";
|
||||
@@ -60,17 +60,29 @@ export async function authWithPincode(
|
||||
const [result] = await db
|
||||
.select()
|
||||
.from(resources)
|
||||
.leftJoin(orgs, eq(orgs.orgId, resources.orgId))
|
||||
.leftJoin(
|
||||
resourcePolicies,
|
||||
eq(resourcePolicies.resourcePolicyId, resources.resourcePolicyId)
|
||||
)
|
||||
.leftJoin(
|
||||
resourcePolicyPincode,
|
||||
eq(resourcePolicyPincode.resourcePolicyId, resourcePolicies.resourcePolicyId)
|
||||
)
|
||||
.leftJoin(
|
||||
resourcePincode,
|
||||
eq(resourcePincode.resourceId, resources.resourceId)
|
||||
)
|
||||
.leftJoin(orgs, eq(orgs.orgId, resources.orgId))
|
||||
.where(eq(resources.resourceId, resourceId))
|
||||
.limit(1);
|
||||
|
||||
const resource = result?.resources;
|
||||
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) {
|
||||
return next(
|
||||
@@ -125,7 +137,8 @@ export async function authWithPincode(
|
||||
await createResourceSession({
|
||||
resourceId,
|
||||
token,
|
||||
pincodeId: definedPincode.pincodeId,
|
||||
pincodeId: isPolicyPincode ? null : definedPincode.pincodeId,
|
||||
policyPincodeId: isPolicyPincode ? definedPincode.pincodeId : null,
|
||||
isRequestToken: true,
|
||||
expiresAt: Date.now() + 1000 * 30, // 30 seconds
|
||||
sessionLength: 1000 * 30,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { generateSessionToken } from "@server/auth/sessions/app";
|
||||
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 response from "@server/lib/response";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
@@ -59,82 +59,21 @@ export async function authWithWhitelist(
|
||||
const { email, otp } = parsedBody.data;
|
||||
|
||||
try {
|
||||
const [result] = await db
|
||||
// Fetch resource and org first
|
||||
const [resourceResult] = await db
|
||||
.select()
|
||||
.from(resourceWhitelist)
|
||||
.where(
|
||||
and(
|
||||
eq(resourceWhitelist.resourceId, resourceId),
|
||||
eq(resourceWhitelist.email, email)
|
||||
)
|
||||
)
|
||||
.leftJoin(
|
||||
resources,
|
||||
eq(resources.resourceId, resourceWhitelist.resourceId)
|
||||
)
|
||||
.from(resources)
|
||||
.leftJoin(orgs, eq(orgs.orgId, resources.orgId))
|
||||
.where(eq(resources.resourceId, resourceId))
|
||||
.limit(1);
|
||||
|
||||
let resource = result?.resources;
|
||||
let org = result?.orgs;
|
||||
let whitelistedEmail = result?.resourceWhitelist;
|
||||
const resource = resourceResult?.resources;
|
||||
const org = resourceResult?.orgs;
|
||||
|
||||
if (!whitelistedEmail) {
|
||||
// if email is not found, check for wildcard email
|
||||
const wildcard = "*@" + email.split("@")[1];
|
||||
|
||||
logger.debug("Checking for wildcard email: " + wildcard);
|
||||
|
||||
const [result] = await db
|
||||
.select()
|
||||
.from(resourceWhitelist)
|
||||
.where(
|
||||
and(
|
||||
eq(resourceWhitelist.resourceId, resourceId),
|
||||
eq(resourceWhitelist.email, wildcard)
|
||||
)
|
||||
)
|
||||
.leftJoin(
|
||||
resources,
|
||||
eq(resources.resourceId, resourceWhitelist.resourceId)
|
||||
)
|
||||
.leftJoin(orgs, eq(orgs.orgId, resources.orgId))
|
||||
.limit(1);
|
||||
|
||||
resource = result?.resources;
|
||||
org = result?.orgs;
|
||||
whitelistedEmail = result?.resourceWhitelist;
|
||||
|
||||
// if wildcard is still not found, return unauthorized
|
||||
if (!whitelistedEmail) {
|
||||
if (config.getRawConfig().app.log_failed_attempts) {
|
||||
logger.info(
|
||||
`Email is not whitelisted. Email: ${email}. IP: ${req.ip}.`
|
||||
);
|
||||
}
|
||||
|
||||
if (org && resource) {
|
||||
logAccessAudit({
|
||||
orgId: org.orgId,
|
||||
resourceId: resource.resourceId,
|
||||
action: false,
|
||||
type: "whitelistedEmail",
|
||||
metadata: { email },
|
||||
userAgent: req.headers["user-agent"],
|
||||
requestIp: req.ip
|
||||
});
|
||||
}
|
||||
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.UNAUTHORIZED,
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Email is not whitelisted"
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
if (!resource) {
|
||||
return next(
|
||||
createHttpError(HttpCode.BAD_REQUEST, "Resource does not exist")
|
||||
);
|
||||
}
|
||||
|
||||
if (!org) {
|
||||
@@ -143,9 +82,100 @@ export async function authWithWhitelist(
|
||||
);
|
||||
}
|
||||
|
||||
if (!resource) {
|
||||
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()
|
||||
.from(resourceWhitelist)
|
||||
.where(
|
||||
and(
|
||||
eq(resourceWhitelist.resourceId, resourceId),
|
||||
eq(resourceWhitelist.email, email)
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (exact) {
|
||||
resourceWhitelistEntry = exact;
|
||||
} else {
|
||||
logger.debug("Checking for wildcard email: " + wildcard);
|
||||
const [wildcardMatch] = await db
|
||||
.select()
|
||||
.from(resourceWhitelist)
|
||||
.where(
|
||||
and(
|
||||
eq(resourceWhitelist.resourceId, resourceId),
|
||||
eq(resourceWhitelist.email, wildcard)
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
if (wildcardMatch) resourceWhitelistEntry = wildcardMatch;
|
||||
}
|
||||
}
|
||||
|
||||
const isPolicyWhitelist = !!policyWhitelistEntry;
|
||||
const whitelistedEmail = policyWhitelistEntry ?? resourceWhitelistEntry;
|
||||
|
||||
if (!whitelistedEmail) {
|
||||
if (config.getRawConfig().app.log_failed_attempts) {
|
||||
logger.info(
|
||||
`Email is not whitelisted. Email: ${email}. IP: ${req.ip}.`
|
||||
);
|
||||
}
|
||||
|
||||
logAccessAudit({
|
||||
orgId: org.orgId,
|
||||
resourceId: resource.resourceId,
|
||||
action: false,
|
||||
type: "whitelistedEmail",
|
||||
metadata: { email },
|
||||
userAgent: req.headers["user-agent"],
|
||||
requestIp: req.ip
|
||||
});
|
||||
|
||||
return next(
|
||||
createHttpError(HttpCode.BAD_REQUEST, "Resource does not exist")
|
||||
createHttpError(
|
||||
HttpCode.UNAUTHORIZED,
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Email is not whitelisted"
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -211,7 +241,8 @@ export async function authWithWhitelist(
|
||||
await createResourceSession({
|
||||
resourceId,
|
||||
token,
|
||||
whitelistId: whitelistedEmail.whitelistId,
|
||||
whitelistId: isPolicyWhitelist ? null : whitelistedEmail.whitelistId,
|
||||
policyWhitelistId: isPolicyWhitelist ? whitelistedEmail.whitelistId : null,
|
||||
isRequestToken: true,
|
||||
expiresAt: Date.now() + 1000 * 30, // 30 seconds
|
||||
sessionLength: 1000 * 30,
|
||||
|
||||
@@ -22,7 +22,8 @@ export async function traefikConfigProvider(
|
||||
config.getRawConfig().traefik.site_types,
|
||||
build == "oss", // filter out the namespace domains in open source
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user