keypointjs 1.0.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/LICENSE +201 -0
- package/README.md +808 -0
- package/package.json +72 -0
- package/src/core/Context.js +104 -0
- package/src/core/ProtocolEngine.js +144 -0
- package/src/keypoint/Keypoint.js +36 -0
- package/src/keypoint/KeypointContext.js +88 -0
- package/src/keypoint/KeypointStorage.js +236 -0
- package/src/keypoint/KeypointValidator.js +51 -0
- package/src/keypoint/ScopeManager.js +206 -0
- package/src/keypointJS.js +779 -0
- package/src/plugins/AuditLogger.js +294 -0
- package/src/plugins/PluginManager.js +303 -0
- package/src/plugins/RateLimiter.js +24 -0
- package/src/plugins/WebSocketGuard.js +351 -0
- package/src/policy/AccessDecision.js +104 -0
- package/src/policy/PolicyEngine.js +82 -0
- package/src/policy/PolicyRule.js +246 -0
- package/src/router/MinimalRouter.js +41 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export class KeypointValidator {
|
|
2
|
+
constructor(storage) {
|
|
3
|
+
this.storage = storage || new KeypointStorage();
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
async validate(context) {
|
|
7
|
+
const { request } = context;
|
|
8
|
+
|
|
9
|
+
// Extract keypoint from request
|
|
10
|
+
const keypointId = this.extractKeypointId(request);
|
|
11
|
+
if (!keypointId) {
|
|
12
|
+
throw new KeypointError('Keypoint header required', 401);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Load keypoint from storage
|
|
16
|
+
const keypoint = await this.storage.get(keypointId);
|
|
17
|
+
if (!keypoint) {
|
|
18
|
+
throw new KeypointError('Invalid keypoint', 401);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Validate keypoint
|
|
22
|
+
if (keypoint.isExpired()) {
|
|
23
|
+
throw new KeypointError('Keypoint expired', 401);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Verify secret if provided
|
|
27
|
+
if (request.headers['x-keypoint-secret']) {
|
|
28
|
+
const isValid = await this.verifySecret(
|
|
29
|
+
keypoint,
|
|
30
|
+
request.headers['x-keypoint-secret']
|
|
31
|
+
);
|
|
32
|
+
if (!isValid) {
|
|
33
|
+
throw new KeypointError('Invalid secret', 401);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Attach keypoint to context
|
|
38
|
+
context.keypoint = keypoint;
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
extractKeypointId(request) {
|
|
43
|
+
return request.headers['x-keypoint-id'] ||
|
|
44
|
+
request.query.keypointId;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async verifySecret(keypoint, providedSecret) {
|
|
48
|
+
// Implement secure secret verification
|
|
49
|
+
return keypoint.secret === providedSecret;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
export class ScopeManager {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.scopeDefinitions = new Map();
|
|
4
|
+
this.scopeHierarchy = new Map();
|
|
5
|
+
this.initializeDefaultScopes();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
initializeDefaultScopes() {
|
|
9
|
+
// Built-in scopes
|
|
10
|
+
this.defineScope('*', 'Full access to all resources');
|
|
11
|
+
this.defineScope('read', 'Read-only access');
|
|
12
|
+
this.defineScope('write', 'Read and write access');
|
|
13
|
+
this.defineScope('admin', 'Administrative access');
|
|
14
|
+
|
|
15
|
+
// API scopes
|
|
16
|
+
this.defineScope('api:public', 'Public API access');
|
|
17
|
+
this.defineScope('api:private', 'Private API access');
|
|
18
|
+
this.defineScope('api:internal', 'Internal API access');
|
|
19
|
+
|
|
20
|
+
// Resource-specific scopes
|
|
21
|
+
this.defineScope('user:read', 'Read user data');
|
|
22
|
+
this.defineScope('user:write', 'Write user data');
|
|
23
|
+
this.defineScope('post:read', 'Read posts');
|
|
24
|
+
this.defineScope('post:write', 'Write posts');
|
|
25
|
+
|
|
26
|
+
// Define hierarchy
|
|
27
|
+
this.addInheritance('admin', ['read', 'write', '*']);
|
|
28
|
+
this.addInheritance('write', ['read']);
|
|
29
|
+
this.addInheritance('api:internal', ['api:private', 'api:public']);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
defineScope(name, description, metadata = {}) {
|
|
33
|
+
this.scopeDefinitions.set(name, {
|
|
34
|
+
name,
|
|
35
|
+
description,
|
|
36
|
+
metadata,
|
|
37
|
+
createdAt: new Date()
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
addInheritance(parentScope, childScopes) {
|
|
42
|
+
if (!this.scopeHierarchy.has(parentScope)) {
|
|
43
|
+
this.scopeHierarchy.set(parentScope, new Set());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const parentSet = this.scopeHierarchy.get(parentScope);
|
|
47
|
+
for (const child of childScopes) {
|
|
48
|
+
parentSet.add(child);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
validateScope(scope) {
|
|
53
|
+
return this.scopeDefinitions.has(scope);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getScopeDefinition(scope) {
|
|
57
|
+
return this.scopeDefinitions.get(scope) || null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getInheritedScopes(scope) {
|
|
61
|
+
const inherited = new Set();
|
|
62
|
+
const queue = [scope];
|
|
63
|
+
|
|
64
|
+
while (queue.length > 0) {
|
|
65
|
+
const current = queue.shift();
|
|
66
|
+
const children = this.scopeHierarchy.get(current);
|
|
67
|
+
|
|
68
|
+
if (children) {
|
|
69
|
+
for (const child of children) {
|
|
70
|
+
if (!inherited.has(child)) {
|
|
71
|
+
inherited.add(child);
|
|
72
|
+
queue.push(child);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return Array.from(inherited);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
hasScope(availableScopes, requiredScope) {
|
|
82
|
+
if (availableScopes.includes('*')) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (availableScopes.includes(requiredScope)) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check inheritance
|
|
91
|
+
const inherited = this.getInheritedScopes(requiredScope);
|
|
92
|
+
return inherited.some(inheritedScope =>
|
|
93
|
+
availableScopes.includes(inheritedScope)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
hasAnyScope(availableScopes, requiredScopes) {
|
|
98
|
+
return requiredScopes.some(scope =>
|
|
99
|
+
this.hasScope(availableScopes, scope)
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
hasAllScopes(availableScopes, requiredScopes) {
|
|
104
|
+
return requiredScopes.every(scope =>
|
|
105
|
+
this.hasScope(availableScopes, scope)
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
expandScopes(scopes) {
|
|
110
|
+
const expanded = new Set();
|
|
111
|
+
|
|
112
|
+
for (const scope of scopes) {
|
|
113
|
+
expanded.add(scope);
|
|
114
|
+
|
|
115
|
+
// Add inherited scopes
|
|
116
|
+
const inherited = this.getInheritedScopes(scope);
|
|
117
|
+
for (const inheritedScope of inherited) {
|
|
118
|
+
expanded.add(inheritedScope);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return Array.from(expanded);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
reduceScopes(scopes) {
|
|
126
|
+
const expanded = this.expandScopes(scopes);
|
|
127
|
+
const reduced = new Set();
|
|
128
|
+
|
|
129
|
+
for (const scope of scopes) {
|
|
130
|
+
// Check if this scope is covered by another scope
|
|
131
|
+
const inherited = this.getInheritedScopes(scope);
|
|
132
|
+
const isCovered = inherited.some(inheritedScope =>
|
|
133
|
+
scope !== inheritedScope && scopes.includes(inheritedScope)
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
if (!isCovered) {
|
|
137
|
+
reduced.add(scope);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return Array.from(reduced);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
validateScopeRequest(requestedScopes, allowedScopes) {
|
|
145
|
+
const invalid = [];
|
|
146
|
+
const denied = [];
|
|
147
|
+
|
|
148
|
+
for (const scope of requestedScopes) {
|
|
149
|
+
if (!this.validateScope(scope)) {
|
|
150
|
+
invalid.push(scope);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!this.hasScope(allowedScopes, scope)) {
|
|
155
|
+
denied.push(scope);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
valid: invalid.length === 0 && denied.length === 0,
|
|
161
|
+
invalid,
|
|
162
|
+
denied,
|
|
163
|
+
granted: requestedScopes.filter(scope =>
|
|
164
|
+
!invalid.includes(scope) && !denied.includes(scope)
|
|
165
|
+
)
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
createScopePattern(pattern) {
|
|
170
|
+
if (pattern === '*') {
|
|
171
|
+
return () => true; // Matches all scopes
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (pattern.includes('*')) {
|
|
175
|
+
const regex = new RegExp('^' + pattern.replace('*', '.*') + '$');
|
|
176
|
+
return (scope) => regex.test(scope);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return (scope) => scope === pattern;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
matchScopes(pattern, scopes) {
|
|
183
|
+
const matcher = this.createScopePattern(pattern);
|
|
184
|
+
return scopes.filter(scope => matcher(scope));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
getAllScopes() {
|
|
188
|
+
return Array.from(this.scopeDefinitions.keys());
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
getScopeTree() {
|
|
192
|
+
const tree = {};
|
|
193
|
+
|
|
194
|
+
for (const [scope, definition] of this.scopeDefinitions) {
|
|
195
|
+
const inherited = this.getInheritedScopes(scope);
|
|
196
|
+
|
|
197
|
+
tree[scope] = {
|
|
198
|
+
...definition,
|
|
199
|
+
inherits: inherited,
|
|
200
|
+
children: Array.from(this.scopeHierarchy.get(scope) || [])
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return tree;
|
|
205
|
+
}
|
|
206
|
+
}
|