Support pin,pass,whitelist correctly on login

This commit is contained in:
Owen
2026-06-01 21:32:07 -07:00
parent 8a57d8dd9c
commit b6d688f15e
12 changed files with 547 additions and 395 deletions

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,