Resource policy api backward compatability

This commit is contained in:
Owen
2026-06-04 21:59:34 -07:00
parent def1e9c851
commit 84fef5f1d6
16 changed files with 851 additions and 239 deletions
+56 -23
View File
@@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { db, resources } from "@server/db"; import { db, resources } from "@server/db";
import { roleResources, roles } from "@server/db"; import { roleResources, roles, rolePolicies } from "@server/db";
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
@@ -131,31 +131,64 @@ export async function addRoleToResource(
); );
} }
// Check if role already exists in resource const isInlinePolicy =
const existingEntry = await db resource.resourcePolicyId === null &&
.select() resource.defaultResourcePolicyId !== null;
.from(roleResources)
.where(
and(
eq(roleResources.resourceId, resourceId),
eq(roleResources.roleId, roleId)
)
);
if (existingEntry.length > 0) { if (isInlinePolicy) {
return next( const policyId = resource.defaultResourcePolicyId!;
createHttpError(
HttpCode.CONFLICT, // Check if role already exists in the inline policy
"Role already assigned to resource" const existingEntry = await db
) .select()
); .from(rolePolicies)
.where(
and(
eq(rolePolicies.resourcePolicyId, policyId),
eq(rolePolicies.roleId, roleId)
)
);
if (existingEntry.length > 0) {
return next(
createHttpError(
HttpCode.CONFLICT,
"Role already assigned to resource"
)
);
}
await db.insert(rolePolicies).values({
roleId,
resourcePolicyId: policyId
});
} else {
// Check if role already exists in resource
const existingEntry = await db
.select()
.from(roleResources)
.where(
and(
eq(roleResources.resourceId, resourceId),
eq(roleResources.roleId, roleId)
)
);
if (existingEntry.length > 0) {
return next(
createHttpError(
HttpCode.CONFLICT,
"Role already assigned to resource"
)
);
}
await db.insert(roleResources).values({
roleId,
resourceId
});
} }
await db.insert(roleResources).values({
roleId,
resourceId
});
return response(res, { return response(res, {
data: {}, data: {},
success: true, success: true,
+56 -23
View File
@@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { db, resources } from "@server/db"; import { db, resources } from "@server/db";
import { userResources } from "@server/db"; import { userResources, userPolicies } from "@server/db";
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
@@ -103,31 +103,64 @@ export async function addUserToResource(
); );
} }
// Check if user already exists in resource const isInlinePolicy =
const existingEntry = await db resource.resourcePolicyId === null &&
.select() resource.defaultResourcePolicyId !== null;
.from(userResources)
.where(
and(
eq(userResources.resourceId, resourceId),
eq(userResources.userId, userId)
)
);
if (existingEntry.length > 0) { if (isInlinePolicy) {
return next( const policyId = resource.defaultResourcePolicyId!;
createHttpError(
HttpCode.CONFLICT, // Check if user already exists in the inline policy
"User already assigned to resource" const existingEntry = await db
) .select()
); .from(userPolicies)
.where(
and(
eq(userPolicies.resourcePolicyId, policyId),
eq(userPolicies.userId, userId)
)
);
if (existingEntry.length > 0) {
return next(
createHttpError(
HttpCode.CONFLICT,
"User already assigned to resource"
)
);
}
await db.insert(userPolicies).values({
userId,
resourcePolicyId: policyId
});
} else {
// Check if user already exists in resource
const existingEntry = await db
.select()
.from(userResources)
.where(
and(
eq(userResources.resourceId, resourceId),
eq(userResources.userId, userId)
)
);
if (existingEntry.length > 0) {
return next(
createHttpError(
HttpCode.CONFLICT,
"User already assigned to resource"
)
);
}
await db.insert(userResources).values({
userId,
resourceId
});
} }
await db.insert(userResources).values({
userId,
resourceId
});
return response(res, { return response(res, {
data: {}, data: {},
success: true, success: true,
+29 -1
View File
@@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { db } from "@server/db"; import { db } from "@server/db";
import { resourceRules, resources } from "@server/db"; import { resourceRules, resourcePolicyRules, resources } from "@server/db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
@@ -153,6 +153,34 @@ export async function createResourceRule(
} }
} }
// Create the new resource rule
const isInlinePolicy =
resource.resourcePolicyId === null &&
resource.defaultResourcePolicyId !== null;
if (isInlinePolicy) {
const policyId = resource.defaultResourcePolicyId!;
const [newRule] = await db
.insert(resourcePolicyRules)
.values({
resourcePolicyId: policyId,
action,
match,
value,
priority,
enabled
})
.returning();
return response(res, {
data: newRule,
success: true,
error: false,
message: "Resource rule created successfully",
status: HttpCode.CREATED
});
}
// Create the new resource rule // Create the new resource rule
const [newRule] = await db const [newRule] = await db
.insert(resourceRules) .insert(resourceRules)
+43 -1
View File
@@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { db } from "@server/db"; import { db } from "@server/db";
import { resourceRules, resources } from "@server/db"; import { resourceRules, resourcePolicyRules, resources } from "@server/db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
@@ -59,6 +59,48 @@ export async function deleteResourceRule(
const { ruleId } = parsedParams.data; const { ruleId } = parsedParams.data;
// Look up resource to determine which table to use
const { resourceId } = parsedParams.data;
const [resource] = await db
.select()
.from(resources)
.where(eq(resources.resourceId, resourceId))
.limit(1);
if (!resource) {
return next(
createHttpError(HttpCode.NOT_FOUND, "Resource not found")
);
}
const isInlinePolicy =
resource.resourcePolicyId === null &&
resource.defaultResourcePolicyId !== null;
if (isInlinePolicy) {
const [deletedRule] = await db
.delete(resourcePolicyRules)
.where(eq(resourcePolicyRules.ruleId, ruleId))
.returning();
if (!deletedRule) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource rule with ID ${ruleId} not found`
)
);
}
return response(res, {
data: null,
success: true,
error: false,
message: "Resource rule deleted successfully",
status: HttpCode.OK
});
}
// Delete the rule and return the deleted record // Delete the rule and return the deleted record
const [deletedRule] = await db const [deletedRule] = await db
.delete(resourceRules) .delete(resourceRules)
@@ -1,7 +1,11 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { db } from "@server/db"; import { db } from "@server/db";
import { resourceWhitelist, users } from "@server/db"; // Assuming these are the correct tables import {
resourceWhitelist,
resourcePolicyWhiteList,
resources
} from "@server/db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
@@ -23,6 +27,15 @@ async function queryWhitelist(resourceId: number) {
.where(eq(resourceWhitelist.resourceId, resourceId)); .where(eq(resourceWhitelist.resourceId, resourceId));
} }
async function queryPolicyWhitelist(policyId: number) {
return await db
.select({
email: resourcePolicyWhiteList.email
})
.from(resourcePolicyWhiteList)
.where(eq(resourcePolicyWhiteList.resourcePolicyId, policyId));
}
export type GetResourceWhitelistResponse = { export type GetResourceWhitelistResponse = {
whitelist: NonNullable<Awaited<ReturnType<typeof queryWhitelist>>>; whitelist: NonNullable<Awaited<ReturnType<typeof queryWhitelist>>>;
}; };
@@ -71,7 +84,25 @@ export async function getResourceWhitelist(
const { resourceId } = parsedParams.data; const { resourceId } = parsedParams.data;
const whitelist = await queryWhitelist(resourceId); const [resource] = await db
.select()
.from(resources)
.where(eq(resources.resourceId, resourceId))
.limit(1);
if (!resource) {
return next(
createHttpError(HttpCode.NOT_FOUND, "Resource not found")
);
}
const isInlinePolicy =
resource.resourcePolicyId === null &&
resource.defaultResourcePolicyId !== null;
const whitelist = isInlinePolicy
? await queryPolicyWhitelist(resource.defaultResourcePolicyId!)
: await queryWhitelist(resourceId);
return response<GetResourceWhitelistResponse>(res, { return response<GetResourceWhitelistResponse>(res, {
data: { data: {
+33 -2
View File
@@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { db } from "@server/db"; import { db } from "@server/db";
import { roleResources, roles } from "@server/db"; import { roleResources, roles, rolePolicies, resources } from "@server/db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
@@ -27,6 +27,19 @@ async function query(resourceId: number) {
.where(eq(roleResources.resourceId, resourceId)); .where(eq(roleResources.resourceId, resourceId));
} }
async function queryInlinePolicy(policyId: number) {
return await db
.select({
roleId: roles.roleId,
name: roles.name,
description: roles.description,
isAdmin: roles.isAdmin
})
.from(rolePolicies)
.innerJoin(roles, eq(rolePolicies.roleId, roles.roleId))
.where(eq(rolePolicies.resourcePolicyId, policyId));
}
export type ListResourceRolesResponse = { export type ListResourceRolesResponse = {
roles: NonNullable<Awaited<ReturnType<typeof query>>>; roles: NonNullable<Awaited<ReturnType<typeof query>>>;
}; };
@@ -75,7 +88,25 @@ export async function listResourceRoles(
const { resourceId } = parsedParams.data; const { resourceId } = parsedParams.data;
const resourceRolesList = await query(resourceId); const [resource] = await db
.select()
.from(resources)
.where(eq(resources.resourceId, resourceId))
.limit(1);
if (!resource) {
return next(
createHttpError(HttpCode.NOT_FOUND, "Resource not found")
);
}
const isInlinePolicy =
resource.resourcePolicyId === null &&
resource.defaultResourcePolicyId !== null;
const resourceRolesList = isInlinePolicy
? await queryInlinePolicy(resource.defaultResourcePolicyId!)
: await query(resourceId);
return response<ListResourceRolesResponse>(res, { return response<ListResourceRolesResponse>(res, {
data: { data: {
+42 -9
View File
@@ -1,5 +1,5 @@
import { db } from "@server/db"; import { db } from "@server/db";
import { resourceRules, resources } from "@server/db"; import { resourceRules, resourcePolicyRules, resources } from "@server/db";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import response from "@server/lib/response"; import response from "@server/lib/response";
import { eq, sql } from "drizzle-orm"; import { eq, sql } from "drizzle-orm";
@@ -47,6 +47,21 @@ function queryResourceRules(resourceId: number) {
return baseQuery; return baseQuery;
} }
function queryPolicyRules(policyId: number) {
return db
.select({
ruleId: resourcePolicyRules.ruleId,
resourceId: sql<number | null>`null`,
action: resourcePolicyRules.action,
match: resourcePolicyRules.match,
value: resourcePolicyRules.value,
priority: resourcePolicyRules.priority,
enabled: resourcePolicyRules.enabled
})
.from(resourcePolicyRules)
.where(eq(resourcePolicyRules.resourcePolicyId, policyId));
}
export type ListResourceRulesResponse = { export type ListResourceRulesResponse = {
rules: Awaited<ReturnType<typeof queryResourceRules>>; rules: Awaited<ReturnType<typeof queryResourceRules>>;
pagination: { total: number; limit: number; offset: number }; pagination: { total: number; limit: number; offset: number };
@@ -125,16 +140,34 @@ export async function listResourceRules(
); );
} }
const baseQuery = queryResourceRules(resourceId); const isInlinePolicy =
resource.resourcePolicyId === null &&
resource.defaultResourcePolicyId !== null;
const countQuery = db let rulesList: Awaited<ReturnType<typeof queryResourceRules>>;
.select({ count: sql<number>`cast(count(*) as integer)` }) let totalCount: number;
.from(resourceRules)
.where(eq(resourceRules.resourceId, resourceId));
let rulesList = await baseQuery.limit(limit).offset(offset); if (isInlinePolicy) {
const totalCountResult = await countQuery; const policyId = resource.defaultResourcePolicyId!;
const totalCount = totalCountResult[0].count; const policyRules = await queryPolicyRules(policyId)
.limit(limit)
.offset(offset);
const countResult = await db
.select({ count: sql<number>`cast(count(*) as integer)` })
.from(resourcePolicyRules)
.where(eq(resourcePolicyRules.resourcePolicyId, policyId));
rulesList = policyRules as typeof rulesList;
totalCount = countResult[0].count;
} else {
const baseQuery = queryResourceRules(resourceId);
const countQuery = db
.select({ count: sql<number>`cast(count(*) as integer)` })
.from(resourceRules)
.where(eq(resourceRules.resourceId, resourceId));
rulesList = await baseQuery.limit(limit).offset(offset);
const totalCountResult = await countQuery;
totalCount = totalCountResult[0].count;
}
// sort rules list by the priority in ascending order // sort rules list by the priority in ascending order
rulesList = rulesList.sort((a, b) => a.priority - b.priority); rulesList = rulesList.sort((a, b) => a.priority - b.priority);
@@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { db, resources } from "@server/db"; import { db, resources } from "@server/db";
import { roleResources, roles } from "@server/db"; import { roleResources, roles, rolePolicies } from "@server/db";
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
@@ -130,35 +130,71 @@ export async function removeRoleFromResource(
); );
} }
// Check if role exists in resource const isInlinePolicy =
const existingEntry = await db resource.resourcePolicyId === null &&
.select() resource.defaultResourcePolicyId !== null;
.from(roleResources)
.where(
and(
eq(roleResources.resourceId, resourceId),
eq(roleResources.roleId, roleId)
)
);
if (existingEntry.length === 0) { if (isInlinePolicy) {
return next( const policyId = resource.defaultResourcePolicyId!;
createHttpError(
HttpCode.NOT_FOUND, const existingEntry = await db
"Role not found in resource" .select()
) .from(rolePolicies)
); .where(
and(
eq(rolePolicies.resourcePolicyId, policyId),
eq(rolePolicies.roleId, roleId)
)
);
if (existingEntry.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
"Role not found in resource"
)
);
}
await db
.delete(rolePolicies)
.where(
and(
eq(rolePolicies.resourcePolicyId, policyId),
eq(rolePolicies.roleId, roleId)
)
);
} else {
// Check if role exists in resource
const existingEntry = await db
.select()
.from(roleResources)
.where(
and(
eq(roleResources.resourceId, resourceId),
eq(roleResources.roleId, roleId)
)
);
if (existingEntry.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
"Role not found in resource"
)
);
}
await db
.delete(roleResources)
.where(
and(
eq(roleResources.resourceId, resourceId),
eq(roleResources.roleId, roleId)
)
);
} }
await db
.delete(roleResources)
.where(
and(
eq(roleResources.resourceId, resourceId),
eq(roleResources.roleId, roleId)
)
);
return response(res, { return response(res, {
data: {}, data: {},
success: true, success: true,
@@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { db, resources } from "@server/db"; import { db, resources } from "@server/db";
import { userResources } from "@server/db"; import { userResources, userPolicies } from "@server/db";
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
@@ -103,35 +103,71 @@ export async function removeUserFromResource(
); );
} }
// Check if user exists in resource const isInlinePolicy =
const existingEntry = await db resource.resourcePolicyId === null &&
.select() resource.defaultResourcePolicyId !== null;
.from(userResources)
.where(
and(
eq(userResources.resourceId, resourceId),
eq(userResources.userId, userId)
)
);
if (existingEntry.length === 0) { if (isInlinePolicy) {
return next( const policyId = resource.defaultResourcePolicyId!;
createHttpError(
HttpCode.NOT_FOUND, const existingEntry = await db
"User not found in resource" .select()
) .from(userPolicies)
); .where(
and(
eq(userPolicies.resourcePolicyId, policyId),
eq(userPolicies.userId, userId)
)
);
if (existingEntry.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
"User not found in resource"
)
);
}
await db
.delete(userPolicies)
.where(
and(
eq(userPolicies.resourcePolicyId, policyId),
eq(userPolicies.userId, userId)
)
);
} else {
// Check if user exists in resource
const existingEntry = await db
.select()
.from(userResources)
.where(
and(
eq(userResources.resourceId, resourceId),
eq(userResources.userId, userId)
)
);
if (existingEntry.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
"User not found in resource"
)
);
}
await db
.delete(userResources)
.where(
and(
eq(userResources.resourceId, resourceId),
eq(userResources.userId, userId)
)
);
} }
await db
.delete(userResources)
.where(
and(
eq(userResources.resourceId, resourceId),
eq(userResources.userId, userId)
)
);
return response(res, { return response(res, {
data: {}, data: {},
success: true, success: true,
@@ -3,7 +3,9 @@ import { z } from "zod";
import { import {
db, db,
resourceHeaderAuth, resourceHeaderAuth,
resourceHeaderAuthExtendedCompatibility resourceHeaderAuthExtendedCompatibility,
resourcePolicyHeaderAuth,
resources
} from "@server/db"; } from "@server/db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
@@ -89,36 +91,73 @@ export async function setResourceHeaderAuth(
const { resourceId } = parsedParams.data; const { resourceId } = parsedParams.data;
const { user, password, extendedCompatibility } = parsedBody.data; const { user, password, extendedCompatibility } = parsedBody.data;
const [resource] = await db
.select()
.from(resources)
.where(eq(resources.resourceId, resourceId))
.limit(1);
if (!resource) {
return next(
createHttpError(HttpCode.NOT_FOUND, "Resource not found")
);
}
const isInlinePolicy =
resource.resourcePolicyId === null &&
resource.defaultResourcePolicyId !== null;
await db.transaction(async (trx) => { await db.transaction(async (trx) => {
await trx if (isInlinePolicy) {
.delete(resourceHeaderAuth) const policyId = resource.defaultResourcePolicyId!;
.where(eq(resourceHeaderAuth.resourceId, resourceId)); await trx
await trx .delete(resourcePolicyHeaderAuth)
.delete(resourceHeaderAuthExtendedCompatibility) .where(
.where( eq(resourcePolicyHeaderAuth.resourcePolicyId, policyId)
eq( );
resourceHeaderAuthExtendedCompatibility.resourceId,
resourceId
)
);
if (user && password && extendedCompatibility !== null) { if (user && password && extendedCompatibility !== null) {
const headerAuthHash = await hashPassword( const headerAuthHash = await hashPassword(
Buffer.from(`${user}:${password}`).toString("base64") Buffer.from(`${user}:${password}`).toString("base64")
); );
await Promise.all([ await trx.insert(resourcePolicyHeaderAuth).values({
trx resourcePolicyId: policyId,
.insert(resourceHeaderAuth) headerAuthHash,
.values({ resourceId, headerAuthHash }), extendedCompatibility: extendedCompatibility!
trx });
.insert(resourceHeaderAuthExtendedCompatibility) }
.values({ } else {
resourceId, await trx
extendedCompatibilityIsActivated: .delete(resourceHeaderAuth)
extendedCompatibility .where(eq(resourceHeaderAuth.resourceId, resourceId));
}) await trx
]); .delete(resourceHeaderAuthExtendedCompatibility)
.where(
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resourceId
)
);
if (user && password && extendedCompatibility !== null) {
const headerAuthHash = await hashPassword(
Buffer.from(`${user}:${password}`).toString("base64")
);
await Promise.all([
trx
.insert(resourceHeaderAuth)
.values({ resourceId, headerAuthHash }),
trx
.insert(resourceHeaderAuthExtendedCompatibility)
.values({
resourceId,
extendedCompatibilityIsActivated:
extendedCompatibility
})
]);
}
} }
}); });
+46 -10
View File
@@ -1,7 +1,11 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { db } from "@server/db"; import { db } from "@server/db";
import { resourcePassword } from "@server/db"; import {
resourcePassword,
resourcePolicyPassword,
resources
} from "@server/db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
@@ -85,17 +89,49 @@ export async function setResourcePassword(
const { resourceId } = parsedParams.data; const { resourceId } = parsedParams.data;
const { password } = parsedBody.data; const { password } = parsedBody.data;
const [resource] = await db
.select()
.from(resources)
.where(eq(resources.resourceId, resourceId))
.limit(1);
if (!resource) {
return next(
createHttpError(HttpCode.NOT_FOUND, "Resource not found")
);
}
const isInlinePolicy =
resource.resourcePolicyId === null &&
resource.defaultResourcePolicyId !== null;
await db.transaction(async (trx) => { await db.transaction(async (trx) => {
await trx if (isInlinePolicy) {
.delete(resourcePassword) const policyId = resource.defaultResourcePolicyId!;
.where(eq(resourcePassword.resourceId, resourceId));
if (password) {
const passwordHash = await hashPassword(password);
await trx await trx
.insert(resourcePassword) .delete(resourcePolicyPassword)
.values({ resourceId, passwordHash }); .where(
eq(resourcePolicyPassword.resourcePolicyId, policyId)
);
if (password) {
const passwordHash = await hashPassword(password);
await trx
.insert(resourcePolicyPassword)
.values({ resourcePolicyId: policyId, passwordHash });
}
} else {
await trx
.delete(resourcePassword)
.where(eq(resourcePassword.resourceId, resourceId));
if (password) {
const passwordHash = await hashPassword(password);
await trx
.insert(resourcePassword)
.values({ resourceId, passwordHash });
}
} }
}); });
+44 -10
View File
@@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { db } from "@server/db"; import { db } from "@server/db";
import { resourcePincode } from "@server/db"; import { resourcePincode, resourcePolicyPincode, resources } from "@server/db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
@@ -89,17 +89,51 @@ export async function setResourcePincode(
const { resourceId } = parsedParams.data; const { resourceId } = parsedParams.data;
const { pincode } = parsedBody.data; const { pincode } = parsedBody.data;
const [resource] = await db
.select()
.from(resources)
.where(eq(resources.resourceId, resourceId))
.limit(1);
if (!resource) {
return next(
createHttpError(HttpCode.NOT_FOUND, "Resource not found")
);
}
const isInlinePolicy =
resource.resourcePolicyId === null &&
resource.defaultResourcePolicyId !== null;
await db.transaction(async (trx) => { await db.transaction(async (trx) => {
await trx if (isInlinePolicy) {
.delete(resourcePincode) const policyId = resource.defaultResourcePolicyId!;
.where(eq(resourcePincode.resourceId, resourceId));
if (pincode) {
const pincodeHash = await hashPassword(pincode);
await trx await trx
.insert(resourcePincode) .delete(resourcePolicyPincode)
.values({ resourceId, pincodeHash, digitLength: 6 }); .where(
eq(resourcePolicyPincode.resourcePolicyId, policyId)
);
if (pincode) {
const pincodeHash = await hashPassword(pincode);
await trx.insert(resourcePolicyPincode).values({
resourcePolicyId: policyId,
pincodeHash,
digitLength: 6
});
}
} else {
await trx
.delete(resourcePincode)
.where(eq(resourcePincode.resourceId, resourceId));
if (pincode) {
const pincodeHash = await hashPassword(pincode);
await trx
.insert(resourcePincode)
.values({ resourceId, pincodeHash, digitLength: 6 });
}
} }
}); });
+51 -18
View File
@@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { db, resources } from "@server/db"; import { db, resources } from "@server/db";
import { apiKeys, roleResources, roles } from "@server/db"; import { apiKeys, roleResources, roles, rolePolicies } from "@server/db";
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
@@ -129,28 +129,61 @@ export async function setResourceRoles(
); );
const adminRoleIds = adminRoles.map((role) => role.roleId); const adminRoleIds = adminRoles.map((role) => role.roleId);
const isInlinePolicy =
resource.resourcePolicyId === null &&
resource.defaultResourcePolicyId !== null;
await db.transaction(async (trx) => { await db.transaction(async (trx) => {
if (adminRoleIds.length > 0) { if (isInlinePolicy) {
await trx.delete(roleResources).where( const policyId = resource.defaultResourcePolicyId!;
and(
eq(roleResources.resourceId, resourceId), // For inline policy, preserve admin roles by only deleting non-admin entries
ne(roleResources.roleId, adminRoleIds[0]) // delete all but the admin role if (adminRoleIds.length > 0) {
await trx
.delete(rolePolicies)
.where(
and(
eq(rolePolicies.resourcePolicyId, policyId),
ne(rolePolicies.roleId, adminRoleIds[0])
)
);
} else {
await trx
.delete(rolePolicies)
.where(eq(rolePolicies.resourcePolicyId, policyId));
}
await Promise.all(
roleIds.map((roleId) =>
trx
.insert(rolePolicies)
.values({ roleId, resourcePolicyId: policyId })
.returning()
) )
); );
} else { } else {
await trx if (adminRoleIds.length > 0) {
.delete(roleResources) await trx.delete(roleResources).where(
.where(eq(roleResources.resourceId, resourceId)); and(
} eq(roleResources.resourceId, resourceId),
ne(roleResources.roleId, adminRoleIds[0]) // delete all but the admin role
)
);
} else {
await trx
.delete(roleResources)
.where(eq(roleResources.resourceId, resourceId));
}
const newRoleResources = await Promise.all( await Promise.all(
roleIds.map((roleId) => roleIds.map((roleId) =>
trx trx
.insert(roleResources) .insert(roleResources)
.values({ roleId, resourceId }) .values({ roleId, resourceId })
.returning() .returning()
) )
); );
}
return response(res, { return response(res, {
data: {}, data: {},
+44 -12
View File
@@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { db } from "@server/db"; import { db } from "@server/db";
import { userResources } from "@server/db"; import { userResources, userPolicies, resources } from "@server/db";
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
@@ -82,19 +82,51 @@ export async function setResourceUsers(
const { resourceId } = parsedParams.data; const { resourceId } = parsedParams.data;
await db.transaction(async (trx) => { const [resource] = await db
await trx .select()
.delete(userResources) .from(resources)
.where(eq(userResources.resourceId, resourceId)); .where(eq(resources.resourceId, resourceId))
.limit(1);
const newUserResources = await Promise.all( if (!resource) {
userIds.map((userId) => return next(
trx createHttpError(HttpCode.NOT_FOUND, "Resource not found")
.insert(userResources)
.values({ userId, resourceId })
.returning()
)
); );
}
const isInlinePolicy =
resource.resourcePolicyId === null &&
resource.defaultResourcePolicyId !== null;
await db.transaction(async (trx) => {
if (isInlinePolicy) {
const policyId = resource.defaultResourcePolicyId!;
await trx
.delete(userPolicies)
.where(eq(userPolicies.resourcePolicyId, policyId));
await Promise.all(
userIds.map((userId) =>
trx
.insert(userPolicies)
.values({ userId, resourcePolicyId: policyId })
.returning()
)
);
} else {
await trx
.delete(userResources)
.where(eq(userResources.resourceId, resourceId));
await Promise.all(
userIds.map((userId) =>
trx
.insert(userResources)
.values({ userId, resourceId })
.returning()
)
);
}
return response(res, { return response(res, {
data: {}, data: {},
+129 -46
View File
@@ -1,7 +1,12 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { db } from "@server/db"; import { db } from "@server/db";
import { resources, resourceWhitelist } from "@server/db"; import {
resources,
resourceWhitelist,
resourcePolicies,
resourcePolicyWhiteList
} from "@server/db";
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
@@ -104,57 +109,135 @@ export async function setResourceWhitelist(
); );
} }
if (!resource.emailWhitelistEnabled) { const isInlinePolicy =
return next( resource.resourcePolicyId === null &&
createHttpError( resource.defaultResourcePolicyId !== null;
HttpCode.BAD_REQUEST,
"Email whitelist is not enabled for this resource"
)
);
}
const whitelist = await db if (isInlinePolicy) {
.select() const policyId = resource.defaultResourcePolicyId!;
.from(resourceWhitelist)
.where(eq(resourceWhitelist.resourceId, resourceId));
await db.transaction(async (trx) => { const [policy] = await db
// diff the emails .select()
const existingEmails = whitelist.map((w) => w.email); .from(resourcePolicies)
.where(eq(resourcePolicies.resourcePolicyId, policyId));
const emailsToAdd = emails.filter( if (!policy) {
(e) => !existingEmails.includes(e) return next(
); createHttpError(
const emailsToRemove = existingEmails.filter( HttpCode.NOT_FOUND,
(e) => !emails.includes(e) "Resource policy not found"
); )
);
}
for (const email of emailsToAdd) { if (!policy.emailWhitelistEnabled) {
await trx.insert(resourceWhitelist).values({ return next(
email, createHttpError(
resourceId HttpCode.BAD_REQUEST,
"Email whitelist is not enabled for this resource"
)
);
}
const existingPolicyWhitelist = await db
.select()
.from(resourcePolicyWhiteList)
.where(eq(resourcePolicyWhiteList.resourcePolicyId, policyId));
await db.transaction(async (trx) => {
const existingEmails = existingPolicyWhitelist.map(
(w) => w.email
);
const emailsToAdd = emails.filter(
(e) => !existingEmails.includes(e)
);
const emailsToRemove = existingEmails.filter(
(e) => !emails.includes(e)
);
for (const email of emailsToAdd) {
await trx.insert(resourcePolicyWhiteList).values({
email,
resourcePolicyId: policyId
});
}
for (const email of emailsToRemove) {
await trx
.delete(resourcePolicyWhiteList)
.where(
and(
eq(
resourcePolicyWhiteList.resourcePolicyId,
policyId
),
eq(resourcePolicyWhiteList.email, email)
)
);
}
return response(res, {
data: {},
success: true,
error: false,
message: "Whitelist set for resource successfully",
status: HttpCode.CREATED
}); });
}
for (const email of emailsToRemove) {
await trx
.delete(resourceWhitelist)
.where(
and(
eq(resourceWhitelist.resourceId, resourceId),
eq(resourceWhitelist.email, email)
)
);
}
return response(res, {
data: {},
success: true,
error: false,
message: "Whitelist set for resource successfully",
status: HttpCode.CREATED
}); });
}); } else {
if (!resource.emailWhitelistEnabled) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Email whitelist is not enabled for this resource"
)
);
}
const whitelist = await db
.select()
.from(resourceWhitelist)
.where(eq(resourceWhitelist.resourceId, resourceId));
await db.transaction(async (trx) => {
// diff the emails
const existingEmails = whitelist.map((w) => w.email);
const emailsToAdd = emails.filter(
(e) => !existingEmails.includes(e)
);
const emailsToRemove = existingEmails.filter(
(e) => !emails.includes(e)
);
for (const email of emailsToAdd) {
await trx.insert(resourceWhitelist).values({
email,
resourceId
});
}
for (const email of emailsToRemove) {
await trx
.delete(resourceWhitelist)
.where(
and(
eq(resourceWhitelist.resourceId, resourceId),
eq(resourceWhitelist.email, email)
)
);
}
return response(res, {
data: {},
success: true,
error: false,
message: "Whitelist set for resource successfully",
status: HttpCode.CREATED
});
});
}
} catch (error) { } catch (error) {
logger.error(error); logger.error(error);
return next( return next(
+52
View File
@@ -549,6 +549,58 @@ async function updateHttpResource(
updateData.maintenanceEstimatedTime = undefined; updateData.maintenanceEstimatedTime = undefined;
} }
const isInlinePolicy =
resource.resourcePolicyId === null &&
resource.defaultResourcePolicyId !== null;
if (isInlinePolicy) {
const policyId = resource.defaultResourcePolicyId!;
const {
sso,
emailWhitelistEnabled,
applyRules,
skipToIdpId,
...resourceOnlyData
} = updateData;
const policyUpdate: Record<string, unknown> = {};
if (sso !== undefined) policyUpdate.sso = sso;
if (emailWhitelistEnabled !== undefined)
policyUpdate.emailWhitelistEnabled = emailWhitelistEnabled;
if (applyRules !== undefined) policyUpdate.applyRules = applyRules;
if (skipToIdpId !== undefined) policyUpdate.idpId = skipToIdpId;
if (Object.keys(policyUpdate).length > 0) {
await db
.update(resourcePolicies)
.set(policyUpdate)
.where(eq(resourcePolicies.resourcePolicyId, policyId));
}
const updatedResource = await db
.update(resources)
.set({ ...resourceOnlyData, headers })
.where(eq(resources.resourceId, resource.resourceId))
.returning();
if (updatedResource.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource with ID ${resource.resourceId} not found`
)
);
}
return response(res, {
data: updatedResource[0],
success: true,
error: false,
message: "HTTP resource updated successfully",
status: HttpCode.OK
});
}
const updatedResource = await db const updatedResource = await db
.update(resources) .update(resources)
.set({ ...updateData, headers }) .set({ ...updateData, headers })