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-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-16 22:41:43 -05:00
|
|
|
|
|
|
|
const verifyResourceSessionSchema = z.object({
|
2024-11-17 22:44:11 -05:00
|
|
|
sessions: z.object({
|
2024-11-16 22:41:43 -05:00
|
|
|
session: z.string().nullable(),
|
|
|
|
resource_session: z.string().nullable(),
|
|
|
|
}),
|
|
|
|
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,
|
|
|
|
next: NextFunction
|
|
|
|
): Promise<any> {
|
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,
|
|
|
|
fromError(parsedBody.error).toString()
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
eq(resourcePincode.resourceId, resources.resourceId)
|
|
|
|
)
|
|
|
|
.leftJoin(
|
|
|
|
resourcePassword,
|
|
|
|
eq(resourcePassword.resourceId, resources.resourceId)
|
|
|
|
)
|
|
|
|
.where(eq(resources.fullDomain, host))
|
|
|
|
.limit(1);
|
|
|
|
|
|
|
|
const resource = result?.resources;
|
|
|
|
const pincode = result?.resourcePincode;
|
|
|
|
const password = result?.resourcePassword;
|
|
|
|
|
|
|
|
if (!resource) {
|
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) {
|
|
|
|
return notAllowed(res);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!resource.sso && !pincode && !password) {
|
2024-11-16 22:41:43 -05:00
|
|
|
return allowed(res);
|
|
|
|
}
|
|
|
|
|
2024-11-23 16:36:07 -05:00
|
|
|
const redirectUrl = `${config.app.base_url}/${resource.orgId}/auth/resource/${resource.resourceId}?r=${originalRequestURL}`;
|
2024-11-16 22:41:43 -05:00
|
|
|
|
2024-11-17 22:44:11 -05:00
|
|
|
if (sso && sessions.session) {
|
|
|
|
const { session, user } = await validateSessionToken(
|
|
|
|
sessions.session
|
2024-11-16 22:41:43 -05:00
|
|
|
);
|
2024-11-17 22:44:11 -05:00
|
|
|
if (session && user) {
|
|
|
|
const isAllowed = await isUserAllowedToAccessResource(
|
|
|
|
user.userId,
|
|
|
|
resource
|
|
|
|
);
|
|
|
|
|
|
|
|
if (isAllowed) {
|
|
|
|
return allowed(res);
|
|
|
|
}
|
2024-11-16 22:41:43 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-17 22:44:11 -05:00
|
|
|
if (password && sessions.resource_session) {
|
|
|
|
const { resourceSession } = await validateResourceSessionToken(
|
|
|
|
sessions.resource_session,
|
|
|
|
resource.resourceId
|
2024-11-16 22:41:43 -05:00
|
|
|
);
|
2024-11-17 22:44:11 -05:00
|
|
|
if (resourceSession) {
|
|
|
|
if (
|
|
|
|
pincode &&
|
|
|
|
resourceSession.pincodeId === pincode.pincodeId
|
|
|
|
) {
|
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-16 22:41:43 -05:00
|
|
|
return allowed(res);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return notAllowed(res, redirectUrl);
|
|
|
|
} catch (e) {
|
|
|
|
return next(
|
|
|
|
createHttpError(
|
|
|
|
HttpCode.INTERNAL_SERVER_ERROR,
|
|
|
|
"Failed to verify session"
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function notAllowed(res: Response, redirectUrl?: string) {
|
|
|
|
return response<VerifyUserResponse>(res, {
|
|
|
|
data: { valid: false, redirectUrl },
|
|
|
|
success: true,
|
|
|
|
error: false,
|
|
|
|
message: "Access denied",
|
|
|
|
status: HttpCode.OK,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function allowed(res: Response) {
|
|
|
|
return response<VerifyUserResponse>(res, {
|
|
|
|
data: { valid: true },
|
|
|
|
success: true,
|
|
|
|
error: false,
|
|
|
|
message: "Access allowed",
|
|
|
|
status: HttpCode.OK,
|
|
|
|
});
|
|
|
|
}
|
2024-11-17 22:44:11 -05:00
|
|
|
|
|
|
|
async function isUserAllowedToAccessResource(
|
|
|
|
userId: string,
|
|
|
|
resource: Resource
|
|
|
|
) {
|
|
|
|
const userOrgRole = await db
|
|
|
|
.select()
|
|
|
|
.from(userOrgs)
|
|
|
|
.where(
|
|
|
|
and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, resource.orgId))
|
|
|
|
)
|
|
|
|
.limit(1);
|
|
|
|
|
|
|
|
if (userOrgRole.length === 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const roleResourceAccess = await db
|
|
|
|
.select()
|
|
|
|
.from(roleResources)
|
|
|
|
.where(
|
|
|
|
and(
|
|
|
|
eq(roleResources.resourceId, resource.resourceId),
|
|
|
|
eq(roleResources.roleId, userOrgRole[0].roleId)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.limit(1);
|
|
|
|
|
|
|
|
if (roleResourceAccess.length > 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const userResourceAccess = await db
|
|
|
|
.select()
|
|
|
|
.from(userResources)
|
|
|
|
.where(
|
|
|
|
and(
|
|
|
|
eq(userResources.userId, userId),
|
|
|
|
eq(userResources.resourceId, resource.resourceId)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.limit(1);
|
|
|
|
|
|
|
|
if (userResourceAccess.length > 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|