Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions packages/server/rpc/engine/grant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/**
* Engine RPC grant methods.
*
* Implements:
* - grant.create: Grant tree access to a user
* - grant.list: List grants (optionally filter by user)
* - grant.get: Get a specific grant
* - grant.revoke: Revoke tree access
* - grant.check: Check if user has access to a tree path for an action
*/
import type { TreeGrant } from "@memory-engine/engine";
import { AppError } from "../errors";
import { buildRegistry } from "../registry";
import type { HandlerContext } from "../types";
import {
type GrantCheckParams,
type GrantCreateParams,
type GrantGetParams,
type GrantListParams,
type GrantRevokeParams,
grantCheckSchema,
grantCreateSchema,
grantGetSchema,
grantListSchema,
grantRevokeSchema,
} from "./schemas";
import { assertEngineContext, type EngineContext } from "./types";

// =============================================================================
// Response Types
// =============================================================================

/**
* Grant response (serializable).
*/
interface GrantResponse {
id: string;
userId: string;
treePath: string;
actions: string[];
grantedBy: string | null;
withGrantOption: boolean;
createdAt: string;
}

/**
* Convert a TreeGrant to a serializable response.
*/
function toGrantResponse(grant: TreeGrant): GrantResponse {
return {
id: grant.id,
userId: grant.userId,
treePath: grant.treePath,
actions: grant.actions,
grantedBy: grant.grantedBy,
withGrantOption: grant.withGrantOption,
createdAt: grant.createdAt.toISOString(),
};
}

// =============================================================================
// Method Handlers
// =============================================================================

/**
* grant.create - Grant tree access to a user.
*/
async function grantCreate(
params: GrantCreateParams,
context: HandlerContext,
): Promise<{ created: boolean }> {
assertEngineContext(context);
const { db, userId } = context as EngineContext;

await db.grantTreeAccess({
userId: params.userId,
treePath: params.treePath,
actions: params.actions,
grantedBy: userId,
withGrantOption: params.withGrantOption,
});

return { created: true };
}

/**
* grant.list - List grants.
*/
async function grantList(
params: GrantListParams,
context: HandlerContext,
): Promise<{ grants: GrantResponse[] }> {
assertEngineContext(context);
const { db } = context as EngineContext;

const grants = await db.listTreeGrants(params.userId);
return { grants: grants.map(toGrantResponse) };
}

/**
* grant.get - Get a specific grant.
*/
async function grantGet(
params: GrantGetParams,
context: HandlerContext,
): Promise<GrantResponse> {
assertEngineContext(context);
const { db } = context as EngineContext;

const grant = await db.getTreeGrant(params.userId, params.treePath);
if (!grant) {
throw new AppError(
"NOT_FOUND",
`Grant not found for user ${params.userId} at path ${params.treePath}`,
);
}

return toGrantResponse(grant);
}

/**
* grant.revoke - Revoke tree access.
*/
async function grantRevoke(
params: GrantRevokeParams,
context: HandlerContext,
): Promise<{ revoked: boolean }> {
assertEngineContext(context);
const { db } = context as EngineContext;

const revoked = await db.revokeTreeAccess(params.userId, params.treePath);
if (!revoked) {
throw new AppError(
"NOT_FOUND",
`Grant not found for user ${params.userId} at path ${params.treePath}`,
);
}

return { revoked };
}

/**
* grant.check - Check if user has access to a tree path for an action.
*/
async function grantCheck(
params: GrantCheckParams,
context: HandlerContext,
): Promise<{ allowed: boolean }> {
assertEngineContext(context);
const { db } = context as EngineContext;

const allowed = await db.checkTreeAccess(
params.userId,
params.treePath,
params.action,
);

return { allowed };
}

// =============================================================================
// Registry
// =============================================================================

/**
* Build the grant methods registry.
*/
export const grantMethods = buildRegistry()
.register("grant.create", grantCreateSchema, grantCreate)
.register("grant.list", grantListSchema, grantList)
.register("grant.get", grantGetSchema, grantGet)
.register("grant.revoke", grantRevokeSchema, grantRevoke)
.register("grant.check", grantCheckSchema, grantCheck)
.build();
22 changes: 15 additions & 7 deletions packages/server/rpc/engine/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
import { buildRegistry } from "../registry";
import { grantMethods } from "./grant";
import { memoryMethods } from "./memory";
import { roleMethods } from "./role";
import { userMethods } from "./user";

/**
* Engine RPC method registry.
*
* Chunk 3 (current) - Memory methods:
* Memory methods (chunk 3):
* - memory.create, memory.batchCreate, memory.get, memory.update, memory.delete
* - memory.search, memory.tree, memory.move, memory.deleteTree
*
* Chunk 4 - User, grant, role methods:
* - user.create, user.get, user.list, user.update, user.delete
* - grant.create, grant.list, grant.revoke
* - role.create, role.addMember, role.removeMember, role.listMembers
* User, grant, role methods (chunk 4):
* - user.create, user.get, user.getByName, user.list, user.rename, user.delete
* - grant.create, grant.list, grant.get, grant.revoke, grant.check
* - role.create, role.addMember, role.removeMember, role.listMembers, role.listForUser
*
* Chunk 5 - API key methods:
* API key methods (chunk 5):
* - apiKey.create, apiKey.list, apiKey.revoke
*/
export const engineMethods = buildRegistry().merge(memoryMethods).build();
export const engineMethods = buildRegistry()
.merge(memoryMethods)
.merge(userMethods)
.merge(grantMethods)
.merge(roleMethods)
.build();

// Re-export types for consumers
export type { EngineContext } from "./types";
Expand Down
Loading