@guren/server 1.0.0-rc.9 → 1.1.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/dist/{Application-DtWDHXr1.d.ts → Application-BnsyCKXY.d.ts} +79 -8
- package/dist/AuthManager-SfhCNkAU.d.ts +79 -0
- package/dist/{BroadcastManager-AkIWUGJo.d.ts → BroadcastManager-CGWl9rUO.d.ts} +5 -0
- package/dist/{ConsoleKernel-CqCVrdZs.d.ts → ConsoleKernel-BDtBETjm.d.ts} +1 -1
- package/dist/{Gate-CNkBYf8m.d.ts → Gate-CynjZCtS.d.ts} +5 -0
- package/dist/{I18nManager-Dtgzsf5n.d.ts → I18nManager-BiSoczfV.d.ts} +6 -1
- package/dist/McpServiceProvider-JW6PDVMD.js +7 -0
- package/dist/api-token-BSSCLlFW.d.ts +541 -0
- package/dist/auth/index.d.ts +9 -327
- package/dist/auth/index.js +59 -6684
- package/dist/authorization/index.d.ts +2 -2
- package/dist/authorization/index.js +19 -604
- package/dist/broadcasting/index.d.ts +2 -2
- package/dist/broadcasting/index.js +12 -895
- package/dist/cache/index.js +8 -809
- package/dist/chunk-2T6JN4VR.js +1563 -0
- package/dist/chunk-44F7JQ7I.js +950 -0
- package/dist/chunk-74HTZG3V.js +331 -0
- package/dist/chunk-A3ISJVEV.js +598 -0
- package/dist/chunk-CSDKWLFD.js +652 -0
- package/dist/chunk-CSRQTEQA.js +839 -0
- package/dist/chunk-DAQKYKLH.js +182 -0
- package/dist/chunk-EDRGAM6G.js +647 -0
- package/dist/chunk-EGU5KB7V.js +818 -0
- package/dist/chunk-H32L2NE3.js +372 -0
- package/dist/chunk-HKQSAFSN.js +837 -0
- package/dist/chunk-IOTWFHZU.js +558 -0
- package/dist/chunk-ONSDE37A.js +125 -0
- package/dist/chunk-QH4NUKSV.js +255 -0
- package/dist/chunk-QQKTH5KX.js +114 -0
- package/dist/chunk-R2TCP7D7.js +409 -0
- package/dist/chunk-SIP34GBE.js +380 -0
- package/dist/chunk-THSX7OOR.js +454 -0
- package/dist/chunk-UY3AZSYL.js +14 -0
- package/dist/chunk-VT5KRDPH.js +134 -0
- package/dist/chunk-WJJ5CTNI.js +907 -0
- package/dist/chunk-WVY45EIW.js +359 -0
- package/dist/chunk-ZRBLZY3M.js +462 -0
- package/dist/client-CKXJLsTe.d.ts +232 -0
- package/dist/email-verification-CAeArjui.d.ts +327 -0
- package/dist/encryption/index.js +48 -556
- package/dist/errors-JOOPDDQ6.js +34 -0
- package/dist/events/index.js +14 -316
- package/dist/health/index.js +12 -367
- package/dist/i18n/index.d.ts +2 -2
- package/dist/i18n/index.js +14 -583
- package/dist/index.d.ts +37 -239
- package/dist/index.js +2931 -19222
- package/dist/lambda/index.d.ts +9 -7
- package/dist/lambda/index.js +4 -9
- package/dist/logging/index.js +12 -545
- package/dist/mail/index.d.ts +29 -1
- package/dist/mail/index.js +15 -684
- package/dist/mcp/index.d.ts +7 -5
- package/dist/mcp/index.js +5 -378
- package/dist/notifications/index.d.ts +8 -6
- package/dist/notifications/index.js +13 -730
- package/dist/queue/index.d.ts +37 -7
- package/dist/queue/index.js +22 -940
- package/dist/redis/index.d.ts +366 -0
- package/dist/redis/index.js +597 -0
- package/dist/runtime/index.d.ts +8 -6
- package/dist/runtime/index.js +26 -244
- package/dist/scheduling/index.js +14 -822
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +6 -824
- package/dist/vite/index.d.ts +2 -2
- package/dist/vite/index.js +5 -1
- package/package.json +15 -7
- package/dist/api-token-JOif2CtG.d.ts +0 -1792
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AuthorizationException
|
|
3
|
+
} from "./chunk-THSX7OOR.js";
|
|
4
|
+
|
|
5
|
+
// src/authorization/Gate.ts
|
|
6
|
+
var Response = {
|
|
7
|
+
allow(message) {
|
|
8
|
+
return { allowed: true, message };
|
|
9
|
+
},
|
|
10
|
+
deny(message, code) {
|
|
11
|
+
return { allowed: false, message: message ?? "This action is unauthorized.", code };
|
|
12
|
+
},
|
|
13
|
+
denyWithStatus(status, message) {
|
|
14
|
+
return { allowed: false, message: message ?? "This action is unauthorized.", status };
|
|
15
|
+
},
|
|
16
|
+
denyAsNotFound(message) {
|
|
17
|
+
return { allowed: false, message: message ?? "Not found.", status: 404 };
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var Gate = class {
|
|
21
|
+
/**
|
|
22
|
+
* Defined gates.
|
|
23
|
+
*/
|
|
24
|
+
gates = /* @__PURE__ */ new Map();
|
|
25
|
+
/**
|
|
26
|
+
* Registered policies.
|
|
27
|
+
*/
|
|
28
|
+
policies = /* @__PURE__ */ new Map();
|
|
29
|
+
/**
|
|
30
|
+
* Policy instances cache.
|
|
31
|
+
*/
|
|
32
|
+
policyInstances = /* @__PURE__ */ new Map();
|
|
33
|
+
/**
|
|
34
|
+
* Before callbacks.
|
|
35
|
+
*/
|
|
36
|
+
beforeCallbacks = [];
|
|
37
|
+
/**
|
|
38
|
+
* After callbacks.
|
|
39
|
+
*/
|
|
40
|
+
afterCallbacks = [];
|
|
41
|
+
/**
|
|
42
|
+
* User resolver function.
|
|
43
|
+
*/
|
|
44
|
+
userResolver;
|
|
45
|
+
/**
|
|
46
|
+
* Current user for checks.
|
|
47
|
+
*/
|
|
48
|
+
currentUser = null;
|
|
49
|
+
constructor(options = {}) {
|
|
50
|
+
this.userResolver = options.userResolver;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Define a new gate.
|
|
54
|
+
*/
|
|
55
|
+
define(ability, callback) {
|
|
56
|
+
this.gates.set(ability, { callback });
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Register a policy for a model class.
|
|
61
|
+
*/
|
|
62
|
+
policy(modelClass, policyClass) {
|
|
63
|
+
this.policies.set(modelClass, policyClass);
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Register a before callback.
|
|
68
|
+
* Return true to allow, false to deny, undefined to continue.
|
|
69
|
+
*/
|
|
70
|
+
before(callback) {
|
|
71
|
+
this.beforeCallbacks.push(callback);
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Register an after callback.
|
|
76
|
+
*/
|
|
77
|
+
after(callback) {
|
|
78
|
+
this.afterCallbacks.push(callback);
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Set the current user for authorization checks.
|
|
83
|
+
*/
|
|
84
|
+
forUser(user) {
|
|
85
|
+
const clone = Object.assign(
|
|
86
|
+
Object.create(Object.getPrototypeOf(this)),
|
|
87
|
+
this
|
|
88
|
+
);
|
|
89
|
+
clone.currentUser = user;
|
|
90
|
+
return clone;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Resolve the user from context.
|
|
94
|
+
*/
|
|
95
|
+
async resolveUser(ctx) {
|
|
96
|
+
if (this.currentUser !== null) {
|
|
97
|
+
return this.currentUser;
|
|
98
|
+
}
|
|
99
|
+
if (this.userResolver) {
|
|
100
|
+
return this.userResolver(ctx);
|
|
101
|
+
}
|
|
102
|
+
const user = ctx.get("user");
|
|
103
|
+
return user ?? null;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check if the user has the given ability.
|
|
107
|
+
*/
|
|
108
|
+
async allows(ability, ...args) {
|
|
109
|
+
return this.check(ability, this.currentUser, ...args);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Check if the user doesn't have the given ability.
|
|
113
|
+
*/
|
|
114
|
+
async denies(ability, ...args) {
|
|
115
|
+
return !await this.allows(ability, ...args);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Check if the user has any of the given abilities.
|
|
119
|
+
*/
|
|
120
|
+
async any(abilities, ...args) {
|
|
121
|
+
for (const ability of abilities) {
|
|
122
|
+
if (await this.allows(ability, ...args)) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Check if the user has all of the given abilities.
|
|
130
|
+
*/
|
|
131
|
+
async all(abilities, ...args) {
|
|
132
|
+
for (const ability of abilities) {
|
|
133
|
+
if (!await this.allows(ability, ...args)) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Check if the user has none of the given abilities.
|
|
141
|
+
*/
|
|
142
|
+
async none(abilities, ...args) {
|
|
143
|
+
return !await this.any(abilities, ...args);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Authorize the ability or throw an exception.
|
|
147
|
+
*/
|
|
148
|
+
async authorize(ability, ...args) {
|
|
149
|
+
const allowed = await this.allows(ability, ...args);
|
|
150
|
+
if (!allowed) {
|
|
151
|
+
throw new AuthorizationException(`This action is unauthorized.`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get the authorization response.
|
|
156
|
+
*/
|
|
157
|
+
async inspect(ability, ...args) {
|
|
158
|
+
const allowed = await this.check(ability, this.currentUser, ...args);
|
|
159
|
+
return allowed ? Response.allow() : Response.deny();
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Check the ability with a specific user.
|
|
163
|
+
*/
|
|
164
|
+
async check(ability, user, ...args) {
|
|
165
|
+
for (const beforeCallback of this.beforeCallbacks) {
|
|
166
|
+
const result = await beforeCallback(user, ability, ...args);
|
|
167
|
+
if (typeof result === "boolean") {
|
|
168
|
+
await this.runAfterCallbacks(user, ability, result, args);
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const model = args[0];
|
|
173
|
+
if (model !== void 0 && model !== null) {
|
|
174
|
+
const policyResult = await this.checkPolicy(ability, user, model, args.slice(1));
|
|
175
|
+
if (policyResult !== void 0) {
|
|
176
|
+
await this.runAfterCallbacks(user, ability, policyResult, args);
|
|
177
|
+
return policyResult;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const gate = this.gates.get(ability);
|
|
181
|
+
if (gate) {
|
|
182
|
+
const result = await gate.callback(user, ...args);
|
|
183
|
+
await this.runAfterCallbacks(user, ability, result, args);
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
await this.runAfterCallbacks(user, ability, false, args);
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Check a policy for the given ability.
|
|
191
|
+
*
|
|
192
|
+
* The subject may be a class instance (policy resolved via its constructor)
|
|
193
|
+
* or a `[ModelClass, record]` / `['key', record]` tuple. The tuple form is
|
|
194
|
+
* required for plain records returned by the ORM, which carry no constructor
|
|
195
|
+
* information.
|
|
196
|
+
*/
|
|
197
|
+
async checkPolicy(ability, user, model, additionalArgs) {
|
|
198
|
+
let policyKey;
|
|
199
|
+
let subject = model;
|
|
200
|
+
let hasSubject = true;
|
|
201
|
+
if (Array.isArray(model) && model.length === 2 && (typeof model[0] === "function" || typeof model[0] === "string")) {
|
|
202
|
+
policyKey = model[0];
|
|
203
|
+
subject = model[1];
|
|
204
|
+
} else if (typeof model === "function") {
|
|
205
|
+
policyKey = model;
|
|
206
|
+
hasSubject = false;
|
|
207
|
+
} else {
|
|
208
|
+
policyKey = model?.constructor;
|
|
209
|
+
}
|
|
210
|
+
if (!policyKey) {
|
|
211
|
+
return void 0;
|
|
212
|
+
}
|
|
213
|
+
const policyClass = this.policies.get(policyKey);
|
|
214
|
+
if (!policyClass) {
|
|
215
|
+
return void 0;
|
|
216
|
+
}
|
|
217
|
+
const policy = this.getPolicyInstance(policyClass);
|
|
218
|
+
if (policy.before) {
|
|
219
|
+
const beforeResult = await policy.before(user, ability);
|
|
220
|
+
if (typeof beforeResult === "boolean") {
|
|
221
|
+
return beforeResult;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const method = policy[ability];
|
|
225
|
+
if (typeof method === "function") {
|
|
226
|
+
return hasSubject ? method.call(policy, user, subject, ...additionalArgs) : method.call(policy, user, ...additionalArgs);
|
|
227
|
+
}
|
|
228
|
+
return void 0;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get or create a policy instance.
|
|
232
|
+
*/
|
|
233
|
+
getPolicyInstance(policyClass) {
|
|
234
|
+
let instance = this.policyInstances.get(policyClass);
|
|
235
|
+
if (!instance) {
|
|
236
|
+
instance = new policyClass();
|
|
237
|
+
this.policyInstances.set(policyClass, instance);
|
|
238
|
+
}
|
|
239
|
+
return instance;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Run after callbacks.
|
|
243
|
+
*/
|
|
244
|
+
async runAfterCallbacks(user, ability, result, args) {
|
|
245
|
+
for (const afterCallback of this.afterCallbacks) {
|
|
246
|
+
await afterCallback(user, ability, result, args);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Get all defined abilities.
|
|
251
|
+
*/
|
|
252
|
+
abilities() {
|
|
253
|
+
return Array.from(this.gates.keys());
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Check if an ability is defined.
|
|
257
|
+
*/
|
|
258
|
+
has(ability) {
|
|
259
|
+
return this.gates.has(ability);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Get the policy for a model.
|
|
263
|
+
*/
|
|
264
|
+
getPolicyFor(model) {
|
|
265
|
+
const modelConstructor = model?.constructor;
|
|
266
|
+
if (!modelConstructor) {
|
|
267
|
+
return void 0;
|
|
268
|
+
}
|
|
269
|
+
const policyClass = this.policies.get(modelConstructor);
|
|
270
|
+
if (!policyClass) {
|
|
271
|
+
return void 0;
|
|
272
|
+
}
|
|
273
|
+
return this.getPolicyInstance(policyClass);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
var globalGate = null;
|
|
277
|
+
function createGate(options) {
|
|
278
|
+
return new Gate(options);
|
|
279
|
+
}
|
|
280
|
+
function setGate(gate) {
|
|
281
|
+
globalGate = gate;
|
|
282
|
+
}
|
|
283
|
+
function getGate() {
|
|
284
|
+
if (!globalGate) {
|
|
285
|
+
throw new Error("Gate not initialized. Call setGate() first.");
|
|
286
|
+
}
|
|
287
|
+
return globalGate;
|
|
288
|
+
}
|
|
289
|
+
function defineGate(ability, callback) {
|
|
290
|
+
getGate().define(ability, callback);
|
|
291
|
+
}
|
|
292
|
+
async function can(ability, ...args) {
|
|
293
|
+
return getGate().allows(ability, ...args);
|
|
294
|
+
}
|
|
295
|
+
async function cannot(ability, ...args) {
|
|
296
|
+
return getGate().denies(ability, ...args);
|
|
297
|
+
}
|
|
298
|
+
async function authorize(ability, ...args) {
|
|
299
|
+
return getGate().authorize(ability, ...args);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/authorization/Policy.ts
|
|
303
|
+
var Policy = class {
|
|
304
|
+
/**
|
|
305
|
+
* Allow the action.
|
|
306
|
+
*/
|
|
307
|
+
allow(message) {
|
|
308
|
+
return Response.allow(message);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Deny the action.
|
|
312
|
+
*/
|
|
313
|
+
deny(message, code) {
|
|
314
|
+
return Response.deny(message, code);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Deny with a specific HTTP status.
|
|
318
|
+
*/
|
|
319
|
+
denyWithStatus(status, message) {
|
|
320
|
+
return Response.denyWithStatus(status, message);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Deny as not found (404).
|
|
324
|
+
*/
|
|
325
|
+
denyAsNotFound(message) {
|
|
326
|
+
return Response.denyAsNotFound(message);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
function definePolicy(definition) {
|
|
330
|
+
return class extends Policy {
|
|
331
|
+
before = definition.before;
|
|
332
|
+
viewAny(user) {
|
|
333
|
+
return definition.viewAny?.(user) ?? false;
|
|
334
|
+
}
|
|
335
|
+
view(user, model) {
|
|
336
|
+
return definition.view?.(user, model) ?? false;
|
|
337
|
+
}
|
|
338
|
+
create(user) {
|
|
339
|
+
return definition.create?.(user) ?? false;
|
|
340
|
+
}
|
|
341
|
+
update(user, model) {
|
|
342
|
+
return definition.update?.(user, model) ?? false;
|
|
343
|
+
}
|
|
344
|
+
delete(user, model) {
|
|
345
|
+
return definition.delete?.(user, model) ?? false;
|
|
346
|
+
}
|
|
347
|
+
restore(user, model) {
|
|
348
|
+
return definition.restore?.(user, model) ?? false;
|
|
349
|
+
}
|
|
350
|
+
forceDelete(user, model) {
|
|
351
|
+
return definition.forceDelete?.(user, model) ?? false;
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/authorization/middleware.ts
|
|
357
|
+
function authorizeMiddleware(ability, modelResolver, options = {}) {
|
|
358
|
+
return async (ctx, next) => {
|
|
359
|
+
const gate = getGate();
|
|
360
|
+
const user = await gate.resolveUser(ctx);
|
|
361
|
+
const gateForUser = gate.forUser(user);
|
|
362
|
+
const abilities = Array.isArray(ability) ? ability : [ability];
|
|
363
|
+
const model = modelResolver ? await modelResolver(ctx) : void 0;
|
|
364
|
+
let authorized = false;
|
|
365
|
+
if (Array.isArray(ability)) {
|
|
366
|
+
authorized = await gateForUser.any(abilities, model);
|
|
367
|
+
} else {
|
|
368
|
+
authorized = await gateForUser.allows(ability, model);
|
|
369
|
+
}
|
|
370
|
+
if (!authorized) {
|
|
371
|
+
throw new AuthorizationException(
|
|
372
|
+
options.message ?? "This action is unauthorized."
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
await next();
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function authorizeAllMiddleware(abilities, modelResolver, options = {}) {
|
|
379
|
+
return async (ctx, next) => {
|
|
380
|
+
const gate = getGate();
|
|
381
|
+
const user = await gate.resolveUser(ctx);
|
|
382
|
+
const gateForUser = gate.forUser(user);
|
|
383
|
+
const model = modelResolver ? await modelResolver(ctx) : void 0;
|
|
384
|
+
const authorized = await gateForUser.all(abilities, model);
|
|
385
|
+
if (!authorized) {
|
|
386
|
+
throw new AuthorizationException(
|
|
387
|
+
options.message ?? "This action is unauthorized."
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
await next();
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
function authorizeResourceMiddleware(modelResolver, options = {}) {
|
|
394
|
+
return async (ctx, next) => {
|
|
395
|
+
const method = ctx.req.method.toUpperCase();
|
|
396
|
+
let ability;
|
|
397
|
+
switch (method) {
|
|
398
|
+
case "GET":
|
|
399
|
+
case "HEAD":
|
|
400
|
+
ability = "view";
|
|
401
|
+
break;
|
|
402
|
+
case "POST":
|
|
403
|
+
ability = "create";
|
|
404
|
+
break;
|
|
405
|
+
case "PUT":
|
|
406
|
+
case "PATCH":
|
|
407
|
+
ability = "update";
|
|
408
|
+
break;
|
|
409
|
+
case "DELETE":
|
|
410
|
+
ability = "delete";
|
|
411
|
+
break;
|
|
412
|
+
default:
|
|
413
|
+
ability = "view";
|
|
414
|
+
}
|
|
415
|
+
const gate = getGate();
|
|
416
|
+
const user = await gate.resolveUser(ctx);
|
|
417
|
+
const gateForUser = gate.forUser(user);
|
|
418
|
+
const model = await modelResolver(ctx);
|
|
419
|
+
const authorized = await gateForUser.allows(ability, model);
|
|
420
|
+
if (!authorized) {
|
|
421
|
+
throw new AuthorizationException(
|
|
422
|
+
options.message ?? "This action is unauthorized."
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
await next();
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function withAuthorization(gate) {
|
|
429
|
+
return async (ctx, next) => {
|
|
430
|
+
const user = await gate.resolveUser(ctx);
|
|
431
|
+
const gateForUser = gate.forUser(user);
|
|
432
|
+
ctx.set("gate", gateForUser);
|
|
433
|
+
ctx.set("can", async (ability, ...args) => {
|
|
434
|
+
return gateForUser.allows(ability, ...args);
|
|
435
|
+
});
|
|
436
|
+
ctx.set("cannot", async (ability, ...args) => {
|
|
437
|
+
return gateForUser.denies(ability, ...args);
|
|
438
|
+
});
|
|
439
|
+
ctx.set("authorize", async (ability, ...args) => {
|
|
440
|
+
return gateForUser.authorize(ability, ...args);
|
|
441
|
+
});
|
|
442
|
+
await next();
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export {
|
|
447
|
+
Response,
|
|
448
|
+
Gate,
|
|
449
|
+
createGate,
|
|
450
|
+
setGate,
|
|
451
|
+
getGate,
|
|
452
|
+
defineGate,
|
|
453
|
+
can,
|
|
454
|
+
cannot,
|
|
455
|
+
authorize,
|
|
456
|
+
Policy,
|
|
457
|
+
definePolicy,
|
|
458
|
+
authorizeMiddleware,
|
|
459
|
+
authorizeAllMiddleware,
|
|
460
|
+
authorizeResourceMiddleware,
|
|
461
|
+
withAuthorization
|
|
462
|
+
};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { Context, MiddlewareHandler } from 'hono';
|
|
2
|
+
import Redis, { RedisOptions } from 'ioredis';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Rate limit entry stored in the backing store.
|
|
6
|
+
*/
|
|
7
|
+
interface RateLimitEntry {
|
|
8
|
+
count: number;
|
|
9
|
+
resetAt: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Store interface for rate limit data.
|
|
13
|
+
* Implement this for Redis or database-backed storage.
|
|
14
|
+
*/
|
|
15
|
+
interface RateLimitStore {
|
|
16
|
+
/**
|
|
17
|
+
* Get the current rate limit entry for a key.
|
|
18
|
+
*/
|
|
19
|
+
get(key: string): Promise<RateLimitEntry | null>;
|
|
20
|
+
/**
|
|
21
|
+
* Increment the count for a key and return the new entry.
|
|
22
|
+
* If the key doesn't exist, create it with count=1.
|
|
23
|
+
*/
|
|
24
|
+
increment(key: string, windowMs: number): Promise<RateLimitEntry>;
|
|
25
|
+
/**
|
|
26
|
+
* Reset the count for a key.
|
|
27
|
+
*/
|
|
28
|
+
reset(key: string): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Base class for in-memory rate limit stores with automatic cleanup.
|
|
32
|
+
*/
|
|
33
|
+
declare abstract class BaseMemoryStore implements RateLimitStore {
|
|
34
|
+
protected cleanupInterval?: ReturnType<typeof setInterval>;
|
|
35
|
+
constructor(cleanupIntervalMs?: number);
|
|
36
|
+
abstract get(key: string): Promise<RateLimitEntry | null>;
|
|
37
|
+
abstract increment(key: string, windowMs: number): Promise<RateLimitEntry>;
|
|
38
|
+
abstract reset(key: string): Promise<void>;
|
|
39
|
+
abstract cleanup(): void;
|
|
40
|
+
abstract clear(): void;
|
|
41
|
+
abstract get size(): number;
|
|
42
|
+
destroy(): void;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* In-memory rate limit store.
|
|
46
|
+
* Suitable for single-process development, not for production clusters.
|
|
47
|
+
*/
|
|
48
|
+
declare class MemoryRateLimitStore extends BaseMemoryStore {
|
|
49
|
+
private entries;
|
|
50
|
+
get(key: string): Promise<RateLimitEntry | null>;
|
|
51
|
+
increment(key: string, windowMs: number): Promise<RateLimitEntry>;
|
|
52
|
+
reset(key: string): Promise<void>;
|
|
53
|
+
cleanup(): void;
|
|
54
|
+
clear(): void;
|
|
55
|
+
get size(): number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Configuration options for rate limiting.
|
|
59
|
+
*/
|
|
60
|
+
interface RateLimitOptions {
|
|
61
|
+
/**
|
|
62
|
+
* Maximum number of requests allowed in the time window.
|
|
63
|
+
* @default 100
|
|
64
|
+
*/
|
|
65
|
+
limit?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Time window in milliseconds.
|
|
68
|
+
* @default 60000 (1 minute)
|
|
69
|
+
*/
|
|
70
|
+
windowMs?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Function to extract the rate limit key from the request.
|
|
73
|
+
* Defaults to using the client IP address.
|
|
74
|
+
*/
|
|
75
|
+
keyGenerator?: (ctx: Context) => string | Promise<string>;
|
|
76
|
+
/**
|
|
77
|
+
* Rate limit store implementation.
|
|
78
|
+
* Defaults to in-memory store.
|
|
79
|
+
*/
|
|
80
|
+
store?: RateLimitStore;
|
|
81
|
+
/**
|
|
82
|
+
* Whether to skip rate limiting for certain requests.
|
|
83
|
+
*/
|
|
84
|
+
skip?: (ctx: Context) => boolean | Promise<boolean>;
|
|
85
|
+
/**
|
|
86
|
+
* Custom handler when rate limit is exceeded.
|
|
87
|
+
*/
|
|
88
|
+
onRateLimited?: (ctx: Context, retryAfter: number) => Response | Promise<Response>;
|
|
89
|
+
/**
|
|
90
|
+
* Whether to add rate limit headers to all responses.
|
|
91
|
+
* @default true
|
|
92
|
+
*/
|
|
93
|
+
headers?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Custom message when rate limit is exceeded.
|
|
96
|
+
* @default 'Too many requests, please try again later.'
|
|
97
|
+
*/
|
|
98
|
+
message?: string;
|
|
99
|
+
/**
|
|
100
|
+
* HTTP status code when rate limit is exceeded.
|
|
101
|
+
* @default 429
|
|
102
|
+
*/
|
|
103
|
+
statusCode?: number;
|
|
104
|
+
/**
|
|
105
|
+
* Prefix for rate limit keys.
|
|
106
|
+
* @default 'rl:'
|
|
107
|
+
*/
|
|
108
|
+
keyPrefix?: string;
|
|
109
|
+
/**
|
|
110
|
+
* Trust reverse-proxy headers for client IP resolution, checked in order:
|
|
111
|
+
* `CF-Connecting-IP`, `True-Client-IP`, `X-Real-IP`, then the first entry
|
|
112
|
+
* of `X-Forwarded-For`. Falls back to `server.requestIP()` when none are set.
|
|
113
|
+
*
|
|
114
|
+
* Enable ONLY when every request passes through a proxy that sets or strips
|
|
115
|
+
* these headers — on direct deployments clients can spoof them to bypass
|
|
116
|
+
* per-client limits. Ignored when a custom `keyGenerator` is supplied.
|
|
117
|
+
* @default false
|
|
118
|
+
*/
|
|
119
|
+
trustProxy?: boolean;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Create a rate limiting middleware.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* // Basic usage - 100 requests per minute per IP
|
|
127
|
+
* app.use('*', createRateLimitMiddleware())
|
|
128
|
+
*
|
|
129
|
+
* // Stricter limit for login endpoint
|
|
130
|
+
* router.post('/login', [AuthController, 'login'],
|
|
131
|
+
* createRateLimitMiddleware({
|
|
132
|
+
* limit: 5,
|
|
133
|
+
* windowMs: 15 * 60 * 1000, // 15 minutes
|
|
134
|
+
* })
|
|
135
|
+
* )
|
|
136
|
+
*
|
|
137
|
+
* // Custom key based on authenticated user
|
|
138
|
+
* app.use('/api/*', createRateLimitMiddleware({
|
|
139
|
+
* limit: 1000,
|
|
140
|
+
* windowMs: 60 * 60 * 1000, // 1 hour
|
|
141
|
+
* keyGenerator: async (ctx) => {
|
|
142
|
+
* const user = await ctx.get('user')
|
|
143
|
+
* return user?.id?.toString() ?? defaultKeyGenerator(ctx)
|
|
144
|
+
* },
|
|
145
|
+
* }))
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
declare function createRateLimitMiddleware(options?: RateLimitOptions): MiddlewareHandler;
|
|
149
|
+
/**
|
|
150
|
+
* Rate limit result information.
|
|
151
|
+
*/
|
|
152
|
+
interface RateLimitInfo {
|
|
153
|
+
limit: number;
|
|
154
|
+
remaining: number;
|
|
155
|
+
resetAt: Date;
|
|
156
|
+
isLimited: boolean;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get rate limit information for a key without incrementing.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```ts
|
|
163
|
+
* const info = await getRateLimitInfo('user:123', store, { limit: 100 })
|
|
164
|
+
* console.log(`${info.remaining} requests remaining`)
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
declare function getRateLimitInfo(key: string, store: RateLimitStore, options?: {
|
|
168
|
+
limit: number;
|
|
169
|
+
keyPrefix?: string;
|
|
170
|
+
}): Promise<RateLimitInfo>;
|
|
171
|
+
/**
|
|
172
|
+
* Reset rate limit for a specific key.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```ts
|
|
176
|
+
* // Reset rate limit after successful captcha
|
|
177
|
+
* await resetRateLimit(clientIp, store)
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
declare function resetRateLimit(key: string, store: RateLimitStore, options?: {
|
|
181
|
+
keyPrefix?: string;
|
|
182
|
+
}): Promise<void>;
|
|
183
|
+
/**
|
|
184
|
+
* Sliding window rate limit store.
|
|
185
|
+
* More accurate than fixed window but uses more memory.
|
|
186
|
+
*/
|
|
187
|
+
declare class SlidingWindowRateLimitStore extends BaseMemoryStore {
|
|
188
|
+
private requests;
|
|
189
|
+
get(key: string): Promise<RateLimitEntry | null>;
|
|
190
|
+
increment(key: string, windowMs: number): Promise<RateLimitEntry>;
|
|
191
|
+
reset(key: string): Promise<void>;
|
|
192
|
+
cleanup(): void;
|
|
193
|
+
clear(): void;
|
|
194
|
+
get size(): number;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Options for creating a Redis client.
|
|
199
|
+
*/
|
|
200
|
+
interface RedisClientOptions extends RedisOptions {
|
|
201
|
+
/**
|
|
202
|
+
* Redis connection URL (e.g., 'redis://localhost:6379').
|
|
203
|
+
* If provided, overrides host/port/password.
|
|
204
|
+
*/
|
|
205
|
+
url?: string;
|
|
206
|
+
/**
|
|
207
|
+
* Key prefix for all operations.
|
|
208
|
+
* @default ''
|
|
209
|
+
*/
|
|
210
|
+
keyPrefix?: string;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Create a Redis client with the given options.
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```ts
|
|
217
|
+
* // Using URL
|
|
218
|
+
* const redis = createRedisClient({ url: 'redis://localhost:6379' })
|
|
219
|
+
*
|
|
220
|
+
* // Using host/port
|
|
221
|
+
* const redis = createRedisClient({ host: 'localhost', port: 6379 })
|
|
222
|
+
*
|
|
223
|
+
* // With key prefix
|
|
224
|
+
* const redis = createRedisClient({
|
|
225
|
+
* url: process.env.REDIS_URL,
|
|
226
|
+
* keyPrefix: 'myapp:',
|
|
227
|
+
* })
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
declare function createRedisClient(options?: RedisClientOptions): Redis;
|
|
231
|
+
|
|
232
|
+
export { MemoryRateLimitStore as M, type RateLimitStore as R, SlidingWindowRateLimitStore as S, type RateLimitEntry as a, type RedisClientOptions as b, createRedisClient as c, type RateLimitInfo as d, type RateLimitOptions as e, createRateLimitMiddleware as f, getRateLimitInfo as g, resetRateLimit as r };
|