endpoint-permissions-kit 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -20
- package/bin/pkit.mjs +2 -2
- package/dist/cjs/index.cjs +716 -473
- package/dist/cjs/index.cjs.map +16 -14
- package/dist/esm/cli/child.js +131 -376
- package/dist/esm/cli/child.js.map +10 -11
- package/dist/esm/cli/generate.js +78 -420
- package/dist/esm/cli/generate.js.map +5 -9
- package/dist/esm/index.js +716 -473
- package/dist/esm/index.js.map +16 -14
- package/dist/types/cli/generate.d.ts +6 -4
- package/dist/types/cli/protocol.d.ts +10 -7
- package/dist/types/constants.d.ts +9 -6
- package/dist/types/context.d.ts +5 -6
- package/dist/types/definitions.d.ts +15 -0
- package/dist/types/errors.d.ts +12 -11
- package/dist/types/identifiers.d.ts +21 -0
- package/dist/types/index.d.ts +16 -17
- package/dist/types/permissions.d.ts +10 -5
- package/dist/types/properties.d.ts +17 -0
- package/dist/types/registry.d.ts +4 -1
- package/dist/types/resolve.d.ts +50 -6
- package/dist/types/seal.d.ts +8 -1
- package/dist/types/state.d.ts +12 -9
- package/dist/types/types.d.ts +25 -34
- package/dist/types/validate.d.ts +10 -3
- package/package.json +1 -1
- package/dist/types/validators.d.ts +0 -53
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Data, Properties } from './types';
|
|
2
|
+
declare const properties: {
|
|
3
|
+
/**
|
|
4
|
+
* Checks one path declared in `registerActions`.
|
|
5
|
+
* Array indexes never appear in the paths of `data` (elements share their
|
|
6
|
+
* container's path), and a leading wildcard would grant that leaf under every
|
|
7
|
+
* root key.
|
|
8
|
+
*/
|
|
9
|
+
checkDeclaredPath(property: string, permissionPath: string): void;
|
|
10
|
+
/**
|
|
11
|
+
* Compares the paths of `data` with the fields the permission allows.
|
|
12
|
+
* Denies the request with `PROPERTIES_NOT_ALLOWED`, or returns a cropped copy
|
|
13
|
+
* when the `cropper` option is on. `data` is never mutated.
|
|
14
|
+
*/
|
|
15
|
+
resolve(data: Data, allowed: Properties, cropper: boolean): Data;
|
|
16
|
+
};
|
|
17
|
+
export default properties;
|
package/dist/types/registry.d.ts
CHANGED
|
@@ -16,4 +16,7 @@ export interface RoleBuilder {
|
|
|
16
16
|
export interface GrantBuilder {
|
|
17
17
|
registerActions(actions: GrantDefs): GrantBuilder;
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
declare const registry: {
|
|
20
|
+
defineModule(segment: string): ModuleBuilder;
|
|
21
|
+
};
|
|
22
|
+
export default registry;
|
package/dist/types/resolve.d.ts
CHANGED
|
@@ -1,13 +1,57 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
|
|
1
|
+
import type { ModuleEntry, NameEntry, Snapshot } from './state';
|
|
2
|
+
import type { Method, MethodAccessMap, Properties, Role, UserAssignments } from './types';
|
|
3
|
+
/** The caller, with every assignment already checked against the sealed catalog. */
|
|
4
|
+
interface Identity {
|
|
5
|
+
readonly role: Role;
|
|
6
|
+
readonly assigned: ReadonlySet<string>;
|
|
7
|
+
readonly names: ReadonlyMap<string, string>;
|
|
8
|
+
}
|
|
9
|
+
/** The single permission of a module an identity resolves to. */
|
|
10
|
+
interface ChosenPermission {
|
|
11
|
+
readonly permissionId: string;
|
|
12
|
+
readonly entry: NameEntry;
|
|
13
|
+
}
|
|
14
|
+
type Access = {
|
|
4
15
|
readonly status: 'granted';
|
|
5
16
|
readonly properties: Properties;
|
|
6
|
-
readonly authorization: Authorization;
|
|
7
17
|
} | {
|
|
8
18
|
readonly status: 'disabled';
|
|
9
19
|
} | {
|
|
10
20
|
readonly status: 'unassigned';
|
|
11
21
|
};
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
declare const resolve: {
|
|
23
|
+
/**
|
|
24
|
+
* Checks the identity a request or a view arrives with. Every assignment must be
|
|
25
|
+
* well formed, belong to the authenticated role and exist as assignable: one
|
|
26
|
+
* stale row denies everything, so it cannot silently widen or narrow access.
|
|
27
|
+
*
|
|
28
|
+
* It also indexes the assignments by module. A request names no permission name,
|
|
29
|
+
* so two names of the same module would leave the choice to the library: that is
|
|
30
|
+
* `AMBIGUOUS_PERMISSION` and it denies everything.
|
|
31
|
+
*/
|
|
32
|
+
identity(assignments: UserAssignments, snapshot: Snapshot, roles: ReadonlySet<string>): Identity;
|
|
33
|
+
/**
|
|
34
|
+
* The permission of a module this identity resolves to. The request carries no
|
|
35
|
+
* name, so a direct assignment on that module decides, and without one the only
|
|
36
|
+
* name its grants reach does. `undefined` means the module is out of reach.
|
|
37
|
+
*
|
|
38
|
+
* Two reachable names deny with `AMBIGUOUS_PERMISSION`: picking one would widen
|
|
39
|
+
* or narrow the access the user was given, without anyone asking for it.
|
|
40
|
+
*/
|
|
41
|
+
permission(registeredModule: ModuleEntry, action: string, identity: Identity): ChosenPermission | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Decides one `(role, module, name, method)`. A direct assignment decides
|
|
44
|
+
* completely and ignores grants; without it the applicable grants are combined.
|
|
45
|
+
*/
|
|
46
|
+
access(nameEntry: NameEntry, permissionId: string, role: string, method: Method, assigned: ReadonlySet<string>): Access;
|
|
47
|
+
/**
|
|
48
|
+
* The four methods of one permission, for the read-only views.
|
|
49
|
+
* Returns `undefined` when the permission is out of reach, which is how a view
|
|
50
|
+
* distinguishes "not yours" from "yours but nothing enabled".
|
|
51
|
+
*
|
|
52
|
+
* Mirrors the precedence of `access` in a single pass over the grants, because a
|
|
53
|
+
* view needs no fields. Any change to `access` belongs here too.
|
|
54
|
+
*/
|
|
55
|
+
methodAccess(nameEntry: NameEntry, permissionId: string, role: string, assigned: ReadonlySet<string>): MethodAccessMap | undefined;
|
|
56
|
+
};
|
|
57
|
+
export default resolve;
|
package/dist/types/seal.d.ts
CHANGED
package/dist/types/state.d.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
+
import type { PermissionReference } from './identifiers';
|
|
1
2
|
import type { ActionDefs, GrantDefs, HookFn, Method, NamedPermissionCatalog } from './types';
|
|
2
|
-
|
|
3
|
-
readonly role: string;
|
|
4
|
-
readonly action: string;
|
|
5
|
-
readonly name: string;
|
|
6
|
-
}
|
|
7
|
-
export interface GrantEntry {
|
|
3
|
+
interface GrantEntry {
|
|
8
4
|
readonly source: PermissionReference;
|
|
9
5
|
readonly actions: GrantDefs;
|
|
10
6
|
}
|
|
@@ -17,14 +13,21 @@ export interface ModuleEntry {
|
|
|
17
13
|
readonly names: Map<string, NameEntry>;
|
|
18
14
|
readonly hooks: Map<Method, HookFn[]>;
|
|
19
15
|
}
|
|
20
|
-
interface Snapshot {
|
|
16
|
+
export interface Snapshot {
|
|
21
17
|
readonly named: NamedPermissionCatalog;
|
|
22
18
|
readonly assignable: ReadonlySet<string>;
|
|
23
19
|
}
|
|
24
20
|
export interface State {
|
|
25
21
|
roles: Set<string>;
|
|
22
|
+
cropper: boolean;
|
|
26
23
|
readonly modules: Map<string, ModuleEntry>;
|
|
27
24
|
snapshot: Snapshot | null;
|
|
28
25
|
}
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
declare const state: {
|
|
27
|
+
getOrCreate(): State;
|
|
28
|
+
/** Every write to the registry goes through this: sealing freezes the configuration. */
|
|
29
|
+
requireOpen(currentState: State): void;
|
|
30
|
+
/** Every read of a materialized view goes through this. */
|
|
31
|
+
requireSnapshot(currentState: State): Snapshot;
|
|
32
|
+
};
|
|
33
|
+
export default state;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type constants from './constants';
|
|
2
2
|
export interface RoleRegistry {
|
|
3
3
|
general: true;
|
|
4
4
|
}
|
|
5
5
|
export type Role = keyof RoleRegistry & string;
|
|
6
|
-
export type Method = (typeof METHODS)[number];
|
|
7
|
-
export type Properties = readonly string[] | typeof ALL_FIELDS;
|
|
6
|
+
export type Method = (typeof constants.METHODS)[number];
|
|
7
|
+
export type Properties = readonly string[] | typeof constants.ALL_FIELDS;
|
|
8
8
|
export type PermissionId = `${Role}::${string}::${string}`;
|
|
9
9
|
export interface ActionDef {
|
|
10
10
|
enabled: boolean;
|
|
@@ -18,21 +18,12 @@ export interface GrantDef {
|
|
|
18
18
|
export type GrantDefs = Partial<Record<Method, GrantDef>>;
|
|
19
19
|
export type Data = Record<string, unknown>;
|
|
20
20
|
export type Context = Record<string, unknown>;
|
|
21
|
-
export
|
|
22
|
-
readonly
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
export
|
|
26
|
-
|
|
27
|
-
readonly action: string;
|
|
28
|
-
readonly name: string;
|
|
29
|
-
readonly permissionId: PermissionId;
|
|
30
|
-
readonly method: Method;
|
|
31
|
-
readonly enabled: true;
|
|
32
|
-
readonly properties: Properties;
|
|
33
|
-
readonly authorization: Authorization;
|
|
34
|
-
}
|
|
35
|
-
export type HookFn = (data: Data | undefined, context: Context | undefined, permission: ResolvedPermission) => unknown;
|
|
21
|
+
export type ContextValues = {
|
|
22
|
+
roles: readonly string[];
|
|
23
|
+
cropper: boolean;
|
|
24
|
+
};
|
|
25
|
+
export type ContextKey = keyof ContextValues;
|
|
26
|
+
export type HookFn = (data: Data, context: Context, permissions: readonly string[]) => unknown;
|
|
36
27
|
export type ValidationError = {
|
|
37
28
|
readonly code: Exclude<PkitErrorCode, 'PROPERTIES_NOT_ALLOWED'>;
|
|
38
29
|
readonly message: string;
|
|
@@ -46,29 +37,30 @@ export type ValidationError = {
|
|
|
46
37
|
readonly cause: unknown;
|
|
47
38
|
};
|
|
48
39
|
export type ValidationErrorCode = ValidationError['code'];
|
|
49
|
-
export type
|
|
40
|
+
export type PkitError = Error & {
|
|
41
|
+
name: 'PkitError';
|
|
42
|
+
} & ({
|
|
43
|
+
code: Exclude<PkitErrorCode, 'PROPERTIES_NOT_ALLOWED'>;
|
|
44
|
+
} | {
|
|
45
|
+
code: 'PROPERTIES_NOT_ALLOWED';
|
|
46
|
+
fields: readonly string[];
|
|
47
|
+
});
|
|
48
|
+
export type PkitErrorCode = 'ROLE_NOT_DECLARED' | 'DUPLICATE_REGISTRATION' | 'INVALID_DEFINITION' | 'INVALID_INPUT' | 'SEALED' | 'NOT_SEALED' | 'UNKNOWN_ROLE' | 'UNKNOWN_ACTION' | 'UNKNOWN_PERMISSION' | 'AMBIGUOUS_PERMISSION' | 'PERMISSION_ROLE_MISMATCH' | 'PERMISSION_NOT_ASSIGNED' | 'METHOD_DISABLED' | 'PROPERTIES_NOT_ALLOWED';
|
|
50
49
|
export interface UserAssignments {
|
|
51
50
|
role: Role;
|
|
52
51
|
permissions: readonly string[];
|
|
53
52
|
}
|
|
54
|
-
interface
|
|
53
|
+
export interface ValidateInput extends UserAssignments {
|
|
55
54
|
action: string;
|
|
56
|
-
|
|
57
|
-
context?: Context;
|
|
58
|
-
}
|
|
59
|
-
export interface FindInput extends PermissionInput {
|
|
60
|
-
method: 'find';
|
|
61
|
-
select?: readonly string[];
|
|
55
|
+
method: Method;
|
|
62
56
|
data?: Data;
|
|
57
|
+
context?: Context;
|
|
63
58
|
}
|
|
64
|
-
export interface
|
|
65
|
-
|
|
66
|
-
data: RequestData;
|
|
59
|
+
export interface ValidateData {
|
|
60
|
+
readonly data: Data;
|
|
67
61
|
}
|
|
68
|
-
export type
|
|
69
|
-
|
|
70
|
-
export type ValidateResult<Result> = {
|
|
71
|
-
readonly result: Result;
|
|
62
|
+
export type ValidateResult = {
|
|
63
|
+
readonly result: ValidateData;
|
|
72
64
|
readonly errors: readonly [];
|
|
73
65
|
} | {
|
|
74
66
|
readonly result: null;
|
|
@@ -78,4 +70,3 @@ export type PermissionEntry = Readonly<ActionDef>;
|
|
|
78
70
|
export type NamedPermissionCatalog = Readonly<Record<PermissionId, Readonly<Partial<Record<Method, PermissionEntry>>>>>;
|
|
79
71
|
export type MethodAccessMap = Readonly<Record<Method, boolean>>;
|
|
80
72
|
export type UserPermissionMap = Readonly<Record<PermissionId, MethodAccessMap>>;
|
|
81
|
-
export {};
|
package/dist/types/validate.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { ValidateInput, ValidateResult } from './types';
|
|
2
|
+
declare const validator: {
|
|
3
|
+
/**
|
|
4
|
+
* The request boundary. Every failure of every phase becomes one entry of the
|
|
5
|
+
* returned contract, so this never throws and never answers with a partial
|
|
6
|
+
* success: on any failure `result` is `null`.
|
|
7
|
+
*/
|
|
8
|
+
validate(input: ValidateInput): Promise<ValidateResult>;
|
|
9
|
+
};
|
|
10
|
+
export default validator;
|
package/package.json
CHANGED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import type { GrantEntry, ModuleEntry, NameEntry, PermissionReference, State } from './state';
|
|
2
|
-
import type { ActionDefs, Context, Data, FindResult, HookFn, Method, PkitErrorCode, ResolvedPermission, Role, UserAssignments, ValidateInput } from './types';
|
|
3
|
-
interface ValidationFailure {
|
|
4
|
-
code: PkitErrorCode;
|
|
5
|
-
message: string;
|
|
6
|
-
}
|
|
7
|
-
interface StringRules extends ValidationFailure {
|
|
8
|
-
minimumLength: number;
|
|
9
|
-
}
|
|
10
|
-
interface DirectRegistration {
|
|
11
|
-
action: string;
|
|
12
|
-
name: string;
|
|
13
|
-
role: string;
|
|
14
|
-
}
|
|
15
|
-
interface GrantRegistration {
|
|
16
|
-
action: string;
|
|
17
|
-
name: string;
|
|
18
|
-
permissionId: string;
|
|
19
|
-
}
|
|
20
|
-
interface HookRegistration {
|
|
21
|
-
action: string;
|
|
22
|
-
name?: string;
|
|
23
|
-
role?: string;
|
|
24
|
-
method: Method;
|
|
25
|
-
}
|
|
26
|
-
interface Identity {
|
|
27
|
-
role: Role;
|
|
28
|
-
assigned: ReadonlySet<string>;
|
|
29
|
-
}
|
|
30
|
-
interface ValidatedRequest {
|
|
31
|
-
registeredModule: ModuleEntry;
|
|
32
|
-
nameEntry: NameEntry;
|
|
33
|
-
permission: ResolvedPermission;
|
|
34
|
-
data: Data | undefined;
|
|
35
|
-
context: Context | undefined;
|
|
36
|
-
result: FindResult | Data;
|
|
37
|
-
}
|
|
38
|
-
export declare function validateStrings(value: unknown, rules: StringRules): asserts value is readonly string[];
|
|
39
|
-
export declare function validateContextKey(key: unknown): asserts key is 'roles';
|
|
40
|
-
export declare function validateSnapshot<Snapshot>(snapshot: Snapshot | null): asserts snapshot is Snapshot;
|
|
41
|
-
export declare function validateRole(role: unknown, roles: ReadonlySet<string>, code: 'ROLE_NOT_DECLARED' | 'UNKNOWN_ROLE'): asserts role is Role;
|
|
42
|
-
export declare function validateRoleCatalog(roles: unknown, state: State): asserts roles is readonly string[];
|
|
43
|
-
export declare function validateRoleSegment(role: unknown): asserts role is string;
|
|
44
|
-
export declare function validateModuleName(moduleName: unknown): asserts moduleName is string;
|
|
45
|
-
export declare function validatePermissionName(name: unknown): asserts name is string;
|
|
46
|
-
export declare function parsePermissionId(value: unknown, failure: ValidationFailure): PermissionReference;
|
|
47
|
-
export declare function validateActions(actions: unknown, registration: DirectRegistration, state: State): ActionDefs;
|
|
48
|
-
export declare function validateGrantActions(actions: unknown, registration: GrantRegistration, state: State): GrantEntry;
|
|
49
|
-
export declare function validateHook(hook: unknown, registration: HookRegistration, state: State): asserts hook is HookFn;
|
|
50
|
-
export declare function validateSealedRegistry(modules: ReadonlyMap<string, ModuleEntry>): void;
|
|
51
|
-
export declare function validateIdentity(assignments: UserAssignments, state: State): Identity;
|
|
52
|
-
export declare function validateRequest(input: ValidateInput, state: State): ValidatedRequest;
|
|
53
|
-
export {};
|