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,246 @@
1
+ export class PolicyRule {
2
+ constructor(name, evaluator, options = {}) {
3
+ this.name = name;
4
+ this.evaluator = evaluator;
5
+ this.options = {
6
+ priority: 0,
7
+ enabled: true,
8
+ description: '',
9
+ ...options
10
+ };
11
+ }
12
+
13
+ async evaluate(context) {
14
+ if (!this.options.enabled) {
15
+ return { allowed: true, rule: this.name };
16
+ }
17
+
18
+ try {
19
+ const result = await this.evaluator(context);
20
+
21
+ return {
22
+ allowed: result.allowed !== false,
23
+ reason: result.reason || '',
24
+ metadata: result.metadata || {},
25
+ rule: this.name,
26
+ timestamp: new Date()
27
+ };
28
+ } catch (error) {
29
+ return {
30
+ allowed: false,
31
+ reason: `Rule evaluation failed: ${error.message}`,
32
+ metadata: { error: error.message },
33
+ rule: this.name,
34
+ timestamp: new Date()
35
+ };
36
+ }
37
+ }
38
+
39
+ enable() {
40
+ this.options.enabled = true;
41
+ return this;
42
+ }
43
+
44
+ disable() {
45
+ this.options.enabled = false;
46
+ return this;
47
+ }
48
+
49
+ setPriority(priority) {
50
+ this.options.priority = priority;
51
+ return this;
52
+ }
53
+ }
54
+
55
+ // Built-in policy rules
56
+ export class BuiltInRules {
57
+ static methodRule(allowedMethods = ['GET', 'POST']) {
58
+ return new PolicyRule(
59
+ 'method_check',
60
+ async (ctx) => {
61
+ if (!allowedMethods.includes(ctx.method.toUpperCase())) {
62
+ return {
63
+ allowed: false,
64
+ reason: `Method ${ctx.method} not allowed. Allowed: ${allowedMethods.join(', ')}`
65
+ };
66
+ }
67
+ return { allowed: true };
68
+ },
69
+ { description: 'Check HTTP method' }
70
+ );
71
+ }
72
+
73
+ static originRule(allowedOrigins = []) {
74
+ return new PolicyRule(
75
+ 'origin_check',
76
+ async (ctx) => {
77
+ const origin = ctx.getHeader('origin');
78
+ if (!origin) return { allowed: true };
79
+
80
+ if (allowedOrigins.length === 0) return { allowed: true };
81
+ if (allowedOrigins.includes('*')) return { allowed: true };
82
+
83
+ if (!allowedOrigins.includes(origin)) {
84
+ return {
85
+ allowed: false,
86
+ reason: `Origin ${origin} not allowed`
87
+ };
88
+ }
89
+ return { allowed: true };
90
+ },
91
+ { description: 'Check request origin' }
92
+ );
93
+ }
94
+
95
+ static ipRule(allowedIPs = [], blockedIPs = []) {
96
+ return new PolicyRule(
97
+ 'ip_check',
98
+ async (ctx) => {
99
+ const ip = ctx.ip;
100
+
101
+ // Check blocked first
102
+ if (blockedIPs.includes(ip) || this.isIPInRange(ip, blockedIPs)) {
103
+ return {
104
+ allowed: false,
105
+ reason: `IP ${ip} is blocked`
106
+ };
107
+ }
108
+
109
+ // Check allowed (if specified)
110
+ if (allowedIPs.length > 0) {
111
+ if (!allowedIPs.includes(ip) && !this.isIPInRange(ip, allowedIPs)) {
112
+ return {
113
+ allowed: false,
114
+ reason: `IP ${ip} not allowed`
115
+ };
116
+ }
117
+ }
118
+
119
+ return { allowed: true };
120
+ },
121
+ { description: 'Check IP address' }
122
+ );
123
+ }
124
+
125
+ static timeWindowRule(startHour = 0, endHour = 24) {
126
+ return new PolicyRule(
127
+ 'time_window',
128
+ async (ctx) => {
129
+ const now = new Date();
130
+ const hour = now.getHours();
131
+
132
+ if (hour < startHour || hour >= endHour) {
133
+ return {
134
+ allowed: false,
135
+ reason: `Access only allowed between ${startHour}:00 and ${endHour}:00`
136
+ };
137
+ }
138
+ return { allowed: true };
139
+ },
140
+ { description: 'Check time window' }
141
+ );
142
+ }
143
+
144
+ static rateLimitRule(limit = 100, windowSeconds = 60) {
145
+ const requests = new Map();
146
+
147
+ return new PolicyRule(
148
+ 'rate_limit',
149
+ async (ctx) => {
150
+ const key = ctx.getKeypointId() || ctx.ip;
151
+ const now = Math.floor(Date.now() / 1000);
152
+ const windowStart = now - windowSeconds;
153
+
154
+ // Clean old requests
155
+ const entry = requests.get(key) || { count: 0, timestamps: [] };
156
+ entry.timestamps = entry.timestamps.filter(t => t > windowStart);
157
+
158
+ // Check limit
159
+ if (entry.timestamps.length >= limit) {
160
+ return {
161
+ allowed: false,
162
+ reason: 'Rate limit exceeded',
163
+ metadata: {
164
+ limit,
165
+ remaining: 0,
166
+ reset: entry.timestamps[0] + windowSeconds
167
+ }
168
+ };
169
+ }
170
+
171
+ // Add current request
172
+ entry.timestamps.push(now);
173
+ entry.count = entry.timestamps.length;
174
+ requests.set(key, entry);
175
+
176
+ return {
177
+ allowed: true,
178
+ metadata: {
179
+ limit,
180
+ remaining: limit - entry.count,
181
+ reset: now + windowSeconds
182
+ }
183
+ };
184
+ },
185
+ { description: 'Rate limiting' }
186
+ );
187
+ }
188
+
189
+ static scopeRule(requiredScope) {
190
+ return new PolicyRule(
191
+ 'scope_check',
192
+ async (ctx) => {
193
+ if (!ctx.hasScope(requiredScope)) {
194
+ return {
195
+ allowed: false,
196
+ reason: `Required scope: ${requiredScope}`
197
+ };
198
+ }
199
+ return { allowed: true };
200
+ },
201
+ { description: 'Check keypoint scope' }
202
+ );
203
+ }
204
+
205
+ static protocolRule(allowedProtocols = ['https']) {
206
+ return new PolicyRule(
207
+ 'protocol_check',
208
+ async (ctx) => {
209
+ if (!allowedProtocols.includes(ctx.protocol)) {
210
+ return {
211
+ allowed: false,
212
+ reason: `Protocol ${ctx.protocol} not allowed. Allowed: ${allowedProtocols.join(', ')}`
213
+ };
214
+ }
215
+ return { allowed: true };
216
+ },
217
+ { description: 'Check protocol' }
218
+ );
219
+ }
220
+
221
+ static isIPInRange(ip, ranges) {
222
+ for (const range of ranges) {
223
+ if (range.includes('/')) {
224
+ // CIDR notation
225
+ if (this.isIPInCIDR(ip, range)) return true;
226
+ } else if (range.includes('-')) {
227
+ // IP range
228
+ if (this.isIPInRangeNotation(ip, range)) return true;
229
+ } else if (ip === range) {
230
+ return true;
231
+ }
232
+ }
233
+ return false;
234
+ }
235
+
236
+ static isIPInCIDR(ip, cidr) {
237
+ // Simplified CIDR check - in production use a proper library
238
+ const [network, prefix] = cidr.split('/');
239
+ return ip.startsWith(network);
240
+ }
241
+
242
+ static isIPInRangeNotation(ip, range) {
243
+ const [start, end] = range.split('-');
244
+ return ip >= start && ip <= end;
245
+ }
246
+ }
@@ -0,0 +1,41 @@
1
+ export class MinimalRouter {
2
+ constructor() {
3
+ this.routes = new Map();
4
+ }
5
+
6
+ route(method, path, handler) {
7
+ const key = `${method}:${path}`;
8
+ this.routes.set(key, handler);
9
+ }
10
+
11
+ get(path, handler) {
12
+ this.route('GET', path, handler);
13
+ }
14
+
15
+ post(path, handler) {
16
+ this.route('POST', path, handler);
17
+ }
18
+
19
+ put(path, handler) {
20
+ this.route('PUT', path, handler);
21
+ }
22
+
23
+ delete(path, handler) {
24
+ this.route('DELETE', path, handler);
25
+ }
26
+
27
+ async handle(context) {
28
+ const { request } = context;
29
+ const key = `${request.method}:${request.url.pathname}`;
30
+
31
+ const handler = this.routes.get(key);
32
+ if (!handler) {
33
+ throw new Error(`Route not found: ${key}`, 404);
34
+ }
35
+
36
+ const result = await handler(context);
37
+ context.response = result;
38
+
39
+ return context;
40
+ }
41
+ }