2024-11-16 22:41:43 -05:00
|
|
|
import HttpCode from "@server/types/HttpCode";
|
|
|
|
import { NextFunction, Request, Response } from "express";
|
|
|
|
import createHttpError from "http-errors";
|
|
|
|
import { z } from "zod";
|
|
|
|
import { fromError } from "zod-validation-error";
|
|
|
|
import { response } from "@server/utils/response";
|
|
|
|
import { validateSessionToken } from "@server/auth";
|
|
|
|
import db from "@server/db";
|
|
|
|
import {
|
|
|
|
resourcePassword,
|
|
|
|
resourcePincode,
|
|
|
|
resources,
|
2024-11-29 21:48:48 -05:00
|
|
|
User,
|
2024-11-17 22:44:11 -05:00
|
|
|
userOrgs,
|
2024-11-16 22:41:43 -05:00
|
|
|
} from "@server/db/schema";
|
2024-11-17 22:44:11 -05:00
|
|
|
import { and, eq } from "drizzle-orm";
|
2024-11-16 22:41:43 -05:00
|
|
|
import config from "@server/config";
|
|
|
|
import { validateResourceSessionToken } from "@server/auth/resource";
|
2024-11-17 22:44:11 -05:00
|
|
|
import { Resource, roleResources, userResources } from "@server/db/schema";
|
2024-11-23 17:56:21 -05:00
|
|
|
import logger from "@server/logger";
|
2024-11-16 22:41:43 -05:00
|
|
|
|
|
|
|
const verifyResourceSessionSchema = z.object({
|
2024-11-27 00:07:40 -05:00
|
|
|
sessions: z.record(z.string()).optional(),
|
2024-11-16 22:41:43 -05:00
|
|
|
originalRequestURL: z.string().url(),
|
|
|
|
scheme: z.string(),
|
|
|
|
host: z.string(),
|
|
|
|
path: z.string(),
|
|
|
|
method: z.string(),
|
|
|
|
tls: z.boolean(),
|
|
|
|
});
|
|
|
|
|
|
|
|
export type VerifyResourceSessionSchema = z.infer<
|
|
|
|
typeof verifyResourceSessionSchema
|
|
|
|
>;
|
|
|
|
|
|
|
|
export type VerifyUserResponse = {
|
|
|
|
valid: boolean;
|
|
|
|
redirectUrl?: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
export async function verifyResourceSession(
|
|
|
|
req: Request,
|
|
|
|
res: Response,
|
2024-11-24 14:28:23 -05:00
|
|
|
next: NextFunction,
|
2024-11-16 22:41:43 -05:00
|
|
|
): Promise<any> {
|
2024-11-23 17:56:21 -05:00
|
|
|
logger.debug("Badger sent", req.body); // remove when done testing
|
|
|
|
|
2024-11-17 22:44:11 -05:00
|
|
|
const parsedBody = verifyResourceSessionSchema.safeParse(req.body);
|
2024-11-16 22:41:43 -05:00
|
|
|
|
|
|
|
if (!parsedBody.success) {
|
|
|
|
return next(
|
|
|
|
createHttpError(
|
|
|
|
HttpCode.BAD_REQUEST,
|
2024-11-24 14:28:23 -05:00
|
|
|
fromError(parsedBody.error).toString(),
|
|
|
|
),
|
2024-11-16 22:41:43 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2024-11-17 22:44:11 -05:00
|
|
|
const { sessions, host, originalRequestURL } = parsedBody.data;
|
2024-11-16 22:41:43 -05:00
|
|
|
|
|
|
|
const [result] = await db
|
|
|
|
.select()
|
|
|
|
.from(resources)
|
|
|
|
.leftJoin(
|
|
|
|
resourcePincode,
|
2024-11-24 14:28:23 -05:00
|
|
|
eq(resourcePincode.resourceId, resources.resourceId),
|
2024-11-16 22:41:43 -05:00
|
|
|
)
|
|
|
|
.leftJoin(
|
|
|
|
resourcePassword,
|
2024-11-24 14:28:23 -05:00
|
|
|
eq(resourcePassword.resourceId, resources.resourceId),
|
2024-11-16 22:41:43 -05:00
|
|
|
)
|
|
|
|
.where(eq(resources.fullDomain, host))
|
|
|
|
.limit(1);
|
|
|
|
|
|
|
|
const resource = result?.resources;
|
|
|
|
const pincode = result?.resourcePincode;
|
|
|
|
const password = result?.resourcePassword;
|
|
|
|
|
|
|
|
if (!resource) {
|
2024-11-24 14:28:23 -05:00
|
|
|
logger.debug("Resource not found", host);
|
2024-11-17 22:44:11 -05:00
|
|
|
return notAllowed(res);
|
2024-11-16 22:41:43 -05:00
|
|
|
}
|
|
|
|
|
2024-11-17 22:44:11 -05:00
|
|
|
const { sso, blockAccess } = resource;
|
|
|
|
|
|
|
|
if (blockAccess) {
|
2024-11-24 14:28:23 -05:00
|
|
|
logger.debug("Resource blocked", host);
|
2024-11-17 22:44:11 -05:00
|
|
|
return notAllowed(res);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!resource.sso && !pincode && !password) {
|
2024-11-24 14:28:23 -05:00
|
|
|
logger.debug("Resource allowed because no auth");
|
2024-11-16 22:41:43 -05:00
|
|
|
return allowed(res);
|
|
|
|
}
|
|
|
|
|
2024-11-24 14:28:23 -05:00
|
|
|
const redirectUrl = `${config.app.base_url}/auth/resource/${encodeURIComponent(resource.resourceId)}?redirect=${encodeURIComponent(originalRequestURL)}`;
|
2024-11-16 22:41:43 -05:00
|
|
|
|
2024-11-27 00:07:40 -05:00
|
|
|
if (!sessions) {
|
|
|
|
return notAllowed(res);
|
|
|
|
}
|
|
|
|
|
|
|
|
const sessionToken = sessions[config.server.session_cookie_name];
|
|
|
|
|
|
|
|
// check for unified login
|
|
|
|
if (sso && sessionToken) {
|
|
|
|
const { session, user } = await validateSessionToken(sessionToken);
|
2024-11-17 22:44:11 -05:00
|
|
|
if (session && user) {
|
|
|
|
const isAllowed = await isUserAllowedToAccessResource(
|
2024-11-29 21:48:48 -05:00
|
|
|
user,
|
2024-11-24 14:28:23 -05:00
|
|
|
resource,
|
2024-11-17 22:44:11 -05:00
|
|
|
);
|
|
|
|
|
|
|
|
if (isAllowed) {
|
2024-11-24 14:28:23 -05:00
|
|
|
logger.debug(
|
|
|
|
"Resource allowed because user session is valid",
|
|
|
|
);
|
2024-11-17 22:44:11 -05:00
|
|
|
return allowed(res);
|
|
|
|
}
|
2024-11-16 22:41:43 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-27 00:07:40 -05:00
|
|
|
const resourceSessionToken =
|
|
|
|
sessions[
|
2024-11-27 14:35:38 -05:00
|
|
|
`${config.server.resource_session_cookie_name}_${resource.resourceId}`
|
2024-11-27 00:07:40 -05:00
|
|
|
];
|
|
|
|
|
|
|
|
if ((pincode || password) && resourceSessionToken) {
|
2024-11-17 22:44:11 -05:00
|
|
|
const { resourceSession } = await validateResourceSessionToken(
|
2024-11-27 00:07:40 -05:00
|
|
|
resourceSessionToken,
|
2024-11-24 14:28:23 -05:00
|
|
|
resource.resourceId,
|
2024-11-16 22:41:43 -05:00
|
|
|
);
|
2024-11-27 00:07:40 -05:00
|
|
|
|
2024-11-17 22:44:11 -05:00
|
|
|
if (resourceSession) {
|
|
|
|
if (
|
|
|
|
pincode &&
|
|
|
|
resourceSession.pincodeId === pincode.pincodeId
|
|
|
|
) {
|
2024-11-24 14:28:23 -05:00
|
|
|
logger.debug(
|
|
|
|
"Resource allowed because pincode session is valid",
|
|
|
|
);
|
2024-11-16 22:41:43 -05:00
|
|
|
return allowed(res);
|
|
|
|
}
|
|
|
|
|
2024-11-17 22:44:11 -05:00
|
|
|
if (
|
|
|
|
password &&
|
|
|
|
resourceSession.passwordId === password.passwordId
|
|
|
|
) {
|
2024-11-24 14:28:23 -05:00
|
|
|
logger.debug(
|
|
|
|
"Resource allowed because password session is valid",
|
|
|
|
);
|
2024-11-16 22:41:43 -05:00
|
|
|
return allowed(res);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-24 14:28:23 -05:00
|
|
|
logger.debug("No more auth to check, resource not allowed");
|
2024-11-16 22:41:43 -05:00
|
|
|
return notAllowed(res, redirectUrl);
|
|
|
|
} catch (e) {
|
2024-11-24 14:28:23 -05:00
|
|
|
console.error(e);
|
2024-11-16 22:41:43 -05:00
|
|
|
return next(
|
|
|
|
createHttpError(
|
|
|
|
HttpCode.INTERNAL_SERVER_ERROR,
|
2024-11-24 14:28:23 -05:00
|
|
|
"Failed to verify session",
|
|
|
|
),
|
2024-11-16 22:41:43 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function notAllowed(res: Response, redirectUrl?: string) {
|
2024-11-24 14:28:23 -05:00
|
|
|
const data = {
|
2024-11-16 22:41:43 -05:00
|
|
|
data: { valid: false, redirectUrl },
|
|
|
|
success: true,
|
|
|
|
error: false,
|
|
|
|
message: "Access denied",
|
|
|
|
status: HttpCode.OK,
|
2024-11-24 22:34:11 -05:00
|
|
|
};
|
2024-11-24 14:28:23 -05:00
|
|
|
logger.debug(JSON.stringify(data));
|
|
|
|
return response<VerifyUserResponse>(res, data);
|
2024-11-16 22:41:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function allowed(res: Response) {
|
2024-11-24 14:28:23 -05:00
|
|
|
const data = {
|
2024-11-16 22:41:43 -05:00
|
|
|
data: { valid: true },
|
|
|
|
success: true,
|
|
|
|
error: false,
|
|
|
|
message: "Access allowed",
|
|
|
|
status: HttpCode.OK,
|
2024-11-24 22:34:11 -05:00
|
|
|
};
|
2024-11-24 14:28:23 -05:00
|
|
|
logger.debug(JSON.stringify(data));
|
|
|
|
return response<VerifyUserResponse>(res, data);
|
2024-11-16 22:41:43 -05:00
|
|
|
}
|
2024-11-17 22:44:11 -05:00
|
|
|
|
|
|
|
async function isUserAllowedToAccessResource(
|
2024-11-29 21:48:48 -05:00
|
|
|
user: User,
|
2024-11-24 14:28:23 -05:00
|
|
|
resource: Resource,
|
2024-11-29 21:48:48 -05:00
|
|
|
): Promise<boolean> {
|
|
|
|
if (config.flags?.require_email_verification && !user.emailVerified) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-11-17 22:44:11 -05:00
|
|
|
const userOrgRole = await db
|
|
|
|
.select()
|
|
|
|
.from(userOrgs)
|
|
|
|
.where(
|
2024-11-24 14:28:23 -05:00
|
|
|
and(
|
2024-11-29 21:48:48 -05:00
|
|
|
eq(userOrgs.userId, user.userId),
|
2024-11-24 14:28:23 -05:00
|
|
|
eq(userOrgs.orgId, resource.orgId),
|
|
|
|
),
|
2024-11-17 22:44:11 -05:00
|
|
|
)
|
|
|
|
.limit(1);
|
|
|
|
|
|
|
|
if (userOrgRole.length === 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const roleResourceAccess = await db
|
|
|
|
.select()
|
|
|
|
.from(roleResources)
|
|
|
|
.where(
|
|
|
|
and(
|
|
|
|
eq(roleResources.resourceId, resource.resourceId),
|
2024-11-24 14:28:23 -05:00
|
|
|
eq(roleResources.roleId, userOrgRole[0].roleId),
|
|
|
|
),
|
2024-11-17 22:44:11 -05:00
|
|
|
)
|
|
|
|
.limit(1);
|
|
|
|
|
|
|
|
if (roleResourceAccess.length > 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const userResourceAccess = await db
|
|
|
|
.select()
|
|
|
|
.from(userResources)
|
|
|
|
.where(
|
|
|
|
and(
|
2024-11-29 21:48:48 -05:00
|
|
|
eq(userResources.userId, user.userId),
|
2024-11-24 14:28:23 -05:00
|
|
|
eq(userResources.resourceId, resource.resourceId),
|
|
|
|
),
|
2024-11-17 22:44:11 -05:00
|
|
|
)
|
|
|
|
.limit(1);
|
|
|
|
|
|
|
|
if (userResourceAccess.length > 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|