Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | 28x 28x 28x | import { RateLimitBinding } from '@/core/bindings/RateLimitBinding'
import type { RateLimiter } from '@bayudwiyansatria/core'
import type { CloudflareEnv } from '@/types/CloudflareEnv'
/**
* Rate limiter binding.
*
* The binding name and the fail-open choice arrive resolved from the
* configuration layer; the binding itself is read from `env` per call, which is
* what makes one module-scope instance safe to share.
*/
const limiter = new RateLimitBinding()
/**
* Rate limiting capability, backed by Cloudflare's rate limiting binding.
*
* @remarks
* What this is for is the endpoint that cannot be addressed by an API key — a
* browser-facing route, or anything named in `excludedRoutes` — where there is
* no caller identity to hold accountable and the only lever left is how often
* one client may knock. The origin allowlist in
* [`SecurityMiddleware`](../../middlewares/SecurityMiddleware.ts) says *who* may
* call; this says *how often*.
*
* Enforcement is per Cloudflare location and eventually consistent, so treat
* the configured rate as a floor rather than a ceiling. That imprecision is
* fine for shedding abuse and wrong for anything a customer is billed against.
*
* By default the limiter is optional infrastructure: with no binding declared,
* every request is admitted. Override `rateLimit.failOpen` to `false` where an
* absent limiter would leave something genuinely exposed.
*
* @example
* ```ts
* const limits = new RateLimitService()
*
* api.post('/', async ctx => {
* if (!(await limits.admit(ctx.env, ctx.get('clientIp')))) {
* return ctx.json({ message: 'Too many requests', success: false, data: null }, 429)
* }
* // …
* })
* ```
*
* @class
*
* @author Bayu Dwiyan Satria
* @version 1.0.0
* @since 1.0.0
*/
export class RateLimitService<E extends CloudflareEnv = CloudflareEnv> implements RateLimiter<E> {
/**
* Counts one request against a key and says whether to serve it.
*
* @remarks
* Calling this is what increments the counter, so call it once per request
* and act on the answer. Checking twice charges the caller twice.
*
* @param env The Worker environment.
* @param key What to count against — a client IP, a user id, a route name.
* Prefix it when one binding limits more than one thing, since the
* namespace is shared.
* @returns `true` when the request should be served, `false` when the caller
* is over its allowance and the route should answer `429`.
*/
public async admit(env: E, key: string): Promise<boolean> {
if (!key) {
return true
}
return await limiter.limit(env, key)
}
/**
* Reports whether a limiter is bound to this Worker.
*
* @remarks
* Worth logging at startup of a route that depends on one: with the default
* fail-open, an unbound limiter is indistinguishable from a limit nobody has
* reached yet.
*
* @param env The Worker environment.
* @returns `true` when the binding is present.
*/
public isAvailable(env: E): boolean {
return limiter.isBound(env)
}
}
|