Prechádzať zdrojové kódy

rename to resource rules and add api endpoints

Owen 5 mesiacov pred
rodič
commit
da3c8823f8

+ 10 - 7
server/auth/actions.ts

@@ -51,13 +51,16 @@ export enum ActionsEnum {
     // removeUserAction = "removeUserAction",
     // removeUserSite = "removeUserSite",
     getOrgUser = "getOrgUser",
-    "setResourcePassword" = "setResourcePassword",
-    "setResourcePincode" = "setResourcePincode",
-    "setResourceWhitelist" = "setResourceWhitelist",
-    "getResourceWhitelist" = "getResourceWhitelist",
-    "generateAccessToken" = "generateAccessToken",
-    "deleteAcessToken" = "deleteAcessToken",
-    "listAccessTokens" = "listAccessTokens"
+    setResourcePassword = "setResourcePassword",
+    setResourcePincode = "setResourcePincode",
+    setResourceWhitelist = "setResourceWhitelist",
+    getResourceWhitelist = "getResourceWhitelist",
+    generateAccessToken = "generateAccessToken",
+    deleteAcessToken = "deleteAcessToken",
+    listAccessTokens = "listAccessTokens",
+    createResourceRule = "createResourceRule",
+    deleteResourceRule = "deleteResourceRule",
+    listResourceRules = "listResourceRules"
 }
 
 export async function checkUserActionPermission(

+ 2 - 2
server/db/schema.ts

@@ -372,7 +372,7 @@ export const versionMigrations = sqliteTable("versionMigrations", {
     executedAt: integer("executedAt").notNull()
 });
 
-export const badgerRules = sqliteTable("badgerRules", {
+export const resourceRules = sqliteTable("resourceRules", {
     ruleId: integer("ruleId").primaryKey({ autoIncrement: true }),
     resourceId: integer("resourceId")
         .notNull()
@@ -414,4 +414,4 @@ export type ResourceOtp = InferSelectModel<typeof resourceOtp>;
 export type ResourceAccessToken = InferSelectModel<typeof resourceAccessToken>;
 export type ResourceWhitelist = InferSelectModel<typeof resourceWhitelist>;
 export type VersionMigration = InferSelectModel<typeof versionMigrations>;
-export type BadgerRule = InferSelectModel<typeof badgerRules>;
+export type ResourceRule = InferSelectModel<typeof resourceRules>;

+ 6 - 6
server/routers/badger/verifySession.ts

@@ -6,8 +6,7 @@ import { fromError } from "zod-validation-error";
 import { response } from "@server/lib/response";
 import db from "@server/db";
 import {
-    BadgerRule,
-    badgerRules,
+    resourceRules,
     ResourceAccessToken,
     ResourcePassword,
     resourcePassword,
@@ -16,7 +15,8 @@ import {
     resources,
     sessions,
     userOrgs,
-    users
+    users,
+    ResourceRule
 } from "@server/db/schema";
 import { and, eq } from "drizzle-orm";
 import config from "@server/lib/config";
@@ -459,13 +459,13 @@ async function checkRules(
 ): Promise<boolean> {
     const ruleCacheKey = `rules:${resourceId}`;
 
-    let rules: BadgerRule[] | undefined = cache.get(ruleCacheKey);
+    let rules: ResourceRule[] | undefined = cache.get(ruleCacheKey);
 
     if (!rules) {
         rules = await db
             .select()
-            .from(badgerRules)
-            .where(eq(badgerRules.resourceId, resourceId));
+            .from(resourceRules)
+            .where(eq(resourceRules.resourceId, resourceId));
 
         cache.set(ruleCacheKey, rules);
     }

+ 21 - 0
server/routers/external.ts

@@ -186,6 +186,26 @@ authenticated.get(
     verifyUserHasAction(ActionsEnum.listTargets),
     target.listTargets
 );
+
+authenticated.put(
+    "/resource/:resourceId/:ruleId",
+    verifyResourceAccess,
+    verifyUserHasAction(ActionsEnum.createResourceRule),
+    resource.createResourceRule
+);
+authenticated.get(
+    "/resource/:resourceId/rules",
+    verifyResourceAccess,
+    verifyUserHasAction(ActionsEnum.listResourceRules),
+    resource.listResourceRules
+);
+authenticated.delete(
+    "/resource/:resourceId/:ruleId",
+    verifyResourceAccess,
+    verifyUserHasAction(ActionsEnum.deleteResourceRule),
+    resource.deleteResourceRule
+);
+
 authenticated.get(
     "/target/:targetId",
     verifyTargetAccess,
@@ -205,6 +225,7 @@ authenticated.delete(
     target.deleteTarget
 );
 
+
 authenticated.put(
     "/org/:orgId/role",
     verifyOrgAccess,

+ 79 - 0
server/routers/resource/createResourceRule.ts

@@ -0,0 +1,79 @@
+import { Request, Response, NextFunction } from "express";
+import { z } from "zod";
+import { db } from "@server/db";
+import { resourceRules, resources } from "@server/db/schema";
+import { eq } from "drizzle-orm";
+import response from "@server/lib/response";
+import HttpCode from "@server/types/HttpCode";
+import createHttpError from "http-errors";
+import logger from "@server/logger";
+import { fromError } from "zod-validation-error";
+
+const createResourceRuleSchema = z
+    .object({
+        resourceId: z.number().int().positive(),
+        action: z.enum(["ACCEPT", "DROP"]),
+        match: z.enum(["CIDR", "PATH"]),
+        value: z.string().min(1)
+    })
+    .strict();
+
+export async function createResourceRule(
+    req: Request,
+    res: Response,
+    next: NextFunction
+): Promise<any> {
+    try {
+        const parsedBody = createResourceRuleSchema.safeParse(req.body);
+        if (!parsedBody.success) {
+            return next(
+                createHttpError(
+                    HttpCode.BAD_REQUEST,
+                    fromError(parsedBody.error).toString()
+                )
+            );
+        }
+
+        const { resourceId, action, match, value } = parsedBody.data;
+
+        // Verify that the referenced resource exists
+        const [resource] = await db
+            .select()
+            .from(resources)
+            .where(eq(resources.resourceId, resourceId))
+            .limit(1);
+
+        if (!resource) {
+            return next(
+                createHttpError(
+                    HttpCode.NOT_FOUND,
+                    `Resource with ID ${resourceId} not found`
+                )
+            );
+        }
+
+        // Create the new resource rule
+        const [newRule] = await db
+            .insert(resourceRules)
+            .values({
+                resourceId,
+                action,
+                match,
+                value
+            })
+            .returning();
+
+        return response(res, {
+            data: newRule,
+            success: true,
+            error: false,
+            message: "Resource rule created successfully",
+            status: HttpCode.CREATED
+        });
+    } catch (error) {
+        logger.error(error);
+        return next(
+            createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
+        );
+    }
+}

+ 67 - 0
server/routers/resource/deleteResourceRule.ts

@@ -0,0 +1,67 @@
+import { Request, Response, NextFunction } from "express";
+import { z } from "zod";
+import { db } from "@server/db";
+import { resourceRules, resources } from "@server/db/schema";
+import { eq } from "drizzle-orm";
+import response from "@server/lib/response";
+import HttpCode from "@server/types/HttpCode";
+import createHttpError from "http-errors";
+import logger from "@server/logger";
+import { fromError } from "zod-validation-error";
+
+const deleteResourceRuleSchema = z
+    .object({
+        ruleId: z
+            .string()
+            .transform(Number)
+            .pipe(z.number().int().positive())
+    })
+    .strict();
+
+export async function deleteResourceRule(
+    req: Request,
+    res: Response,
+    next: NextFunction
+): Promise<any> {
+    try {
+        const parsedParams = deleteResourceRuleSchema.safeParse(req.params);
+        if (!parsedParams.success) {
+            return next(
+                createHttpError(
+                    HttpCode.BAD_REQUEST,
+                    fromError(parsedParams.error).toString()
+                )
+            );
+        }
+
+        const { ruleId } = parsedParams.data;
+
+        // Delete the rule and return the deleted record
+        const [deletedRule] = await db
+            .delete(resourceRules)
+            .where(eq(resourceRules.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
+        });
+    } catch (error) {
+        logger.error(error);
+        return next(
+            createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
+        );
+    }
+}

+ 3 - 0
server/routers/resource/index.ts

@@ -18,3 +18,6 @@ export * from "./authWithWhitelist";
 export * from "./authWithAccessToken";
 export * from "./transferResource";
 export * from "./getExchangeToken";
+export * from "./createResourceRule";
+export * from "./deleteResourceRule";
+export * from "./listResourceRules";

+ 133 - 0
server/routers/resource/listResourceRules.ts

@@ -0,0 +1,133 @@
+import { db } from "@server/db";
+import { resourceRules, resources } from "@server/db/schema";
+import HttpCode from "@server/types/HttpCode";
+import response from "@server/lib/response";
+import { eq, sql } from "drizzle-orm";
+import { NextFunction, Request, Response } from "express";
+import createHttpError from "http-errors";
+import { z } from "zod";
+import { fromError } from "zod-validation-error";
+import logger from "@server/logger";
+
+const listResourceRulesParamsSchema = z
+    .object({
+        resourceId: z
+            .string()
+            .transform(Number)
+            .pipe(z.number().int().positive())
+    })
+    .strict();
+
+const listResourceRulesSchema = z.object({
+    limit: z
+        .string()
+        .optional()
+        .default("1000")
+        .transform(Number)
+        .pipe(z.number().int().positive()),
+    offset: z
+        .string()
+        .optional()
+        .default("0")
+        .transform(Number)
+        .pipe(z.number().int().nonnegative())
+});
+
+function queryResourceRules(resourceId: number) {
+    let baseQuery = db
+        .select({
+            ruleId: resourceRules.ruleId,
+            resourceId: resourceRules.resourceId,
+            action: resourceRules.action,
+            match: resourceRules.match,
+            value: resourceRules.value,
+            resourceName: resources.name,
+        })
+        .from(resourceRules)
+        .leftJoin(resources, eq(resourceRules.resourceId, resources.resourceId))
+        .where(eq(resourceRules.resourceId, resourceId));
+    
+    return baseQuery;
+}
+
+export type ListResourceRulesResponse = {
+    rules: Awaited<ReturnType<typeof queryResourceRules>>;
+    pagination: { total: number; limit: number; offset: number };
+};
+
+export async function listResourceRules(
+    req: Request,
+    res: Response,
+    next: NextFunction
+): Promise<any> {
+    try {
+        const parsedQuery = listResourceRulesSchema.safeParse(req.query);
+        if (!parsedQuery.success) {
+            return next(
+                createHttpError(
+                    HttpCode.BAD_REQUEST,
+                    fromError(parsedQuery.error)
+                )
+            );
+        }
+        const { limit, offset } = parsedQuery.data;
+
+        const parsedParams = listResourceRulesParamsSchema.safeParse(req.params);
+        if (!parsedParams.success) {
+            return next(
+                createHttpError(
+                    HttpCode.BAD_REQUEST,
+                    fromError(parsedParams.error)
+                )
+            );
+        }
+        const { resourceId } = parsedParams.data;
+
+        // Verify the resource exists
+        const [resource] = await db
+            .select()
+            .from(resources)
+            .where(eq(resources.resourceId, resourceId))
+            .limit(1);
+
+        if (!resource) {
+            return next(
+                createHttpError(
+                    HttpCode.NOT_FOUND,
+                    `Resource with ID ${resourceId} not found`
+                )
+            );
+        }
+
+        const baseQuery = queryResourceRules(resourceId);
+        
+        let countQuery = db
+            .select({ count: sql<number>`cast(count(*) as integer)` })
+            .from(resourceRules)
+            .where(eq(resourceRules.resourceId, resourceId));
+
+        const rulesList = await baseQuery.limit(limit).offset(offset);
+        const totalCountResult = await countQuery;
+        const totalCount = totalCountResult[0].count;
+
+        return response<ListResourceRulesResponse>(res, {
+            data: {
+                rules: rulesList,
+                pagination: {
+                    total: totalCount,
+                    limit,
+                    offset
+                }
+            },
+            success: true,
+            error: false,
+            message: "Resource rules retrieved successfully",
+            status: HttpCode.OK
+        });
+    } catch (error) {
+        logger.error(error);
+        return next(
+            createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
+        );
+    }
+}