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.
@@ -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
+ }