mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-21 22:46:32 +00:00
7b11680cc3
The method-to-permission association exists twice today: declaratively in METHOD_PERMISSION, and imperatively as 44 if (has(...)) blocks in rpc-host.ts, with nothing asserting the two agree. protocol-paths.test.ts checks that each method has some permission, never that it is the same one the router gates on, and plugin-audit.ts inherits any drift silently. @PluginMethod binds the two together. Its permission argument is typed as MethodPermission<M>, so it resolves to the single literal the table assigns to that method and a mismatch does not compile. validate() re-checks the same argument against the same table at boot, because types evaporate under an as cast. @PluginHook does the same against HOOK_PERMISSION. UNCONDITIONAL_METHODS was missing from envelope.ts, so the three methods the router registers outside every grant check had no vocabulary of their own. Adding it here, with a compile-time proof that it stays disjoint from KNOWN_METHODS: giving those three a METHOD_PERMISSION row would change which methods isAuditable considers, and settings.get is deliberately the one unconditional method that is not audited. The kit is written to stay extractable into its own package, so an ESLint rule pins what it may import. Note the rule is a deny list on purpose: these patterns are gitignore-style, and a leading ** would exclude the parent directory of every allowed path, which makes the ! re-includes silently ineffective.
48 lines
1.6 KiB
TypeScript
48 lines
1.6 KiB
TypeScript
import type { PluginRpcEntry } from './types';
|
|
|
|
/**
|
|
* Metadata store. A package-private WeakMap/WeakSet keyed by class constructor
|
|
* instead of reflect-metadata: it has zero runtime prerequisites (no polyfill
|
|
* import order to get right), works in bare vitest workers and in
|
|
* `createTestPluginRegistry` without a Nest app, and is trivially
|
|
* extraction-clean. Same reasoning as nest-mcp's metadata store.
|
|
*/
|
|
|
|
/** A class constructor, without using the banned bare `Function` type. */
|
|
export type ClassRef = abstract new (...args: never[]) => unknown;
|
|
|
|
const controllers = new WeakSet<ClassRef>();
|
|
const entries = new WeakMap<ClassRef, Map<string, PluginRpcEntry>>();
|
|
|
|
export function markController(ctor: ClassRef): void {
|
|
controllers.add(ctor);
|
|
}
|
|
|
|
export function isPluginController(ctor: unknown): ctor is ClassRef {
|
|
return typeof ctor === 'function' && controllers.has(ctor as ClassRef);
|
|
}
|
|
|
|
export function addEntry(ctor: ClassRef, entry: PluginRpcEntry): void {
|
|
let map = entries.get(ctor);
|
|
if (!map) {
|
|
map = new Map();
|
|
entries.set(ctor, map);
|
|
}
|
|
map.set(entry.methodName, entry);
|
|
}
|
|
|
|
/**
|
|
* Look up the entry recorded for `methodName`, walking the constructor prototype
|
|
* chain so inherited decorated methods are found on subclasses.
|
|
*/
|
|
export function getEntry(ctor: ClassRef, methodName: string): PluginRpcEntry | undefined {
|
|
let current: ClassRef | null = ctor;
|
|
while (current) {
|
|
const found = entries.get(current)?.get(methodName);
|
|
if (found) return found;
|
|
const parent: unknown = Object.getPrototypeOf(current);
|
|
current = typeof parent === 'function' ? (parent as ClassRef) : null;
|
|
}
|
|
return undefined;
|
|
}
|