Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge to develop for final release #9

Merged
merged 51 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
cf9bc61
refactor: bulk of v6 changes
fareeda0 Oct 26, 2023
429ec4d
feat: add enabled property to config
fareeda0 Oct 27, 2023
7ab3b55
test: update tests with new config
fareeda0 Oct 27, 2023
362ef72
feat: add intial stubs
fareeda0 Oct 27, 2023
3d5425d
refactor: move types file
fareeda0 Oct 27, 2023
4013daa
refactor: update throttle middleware
fareeda0 Oct 27, 2023
2505430
test: throttle middleware tests
fareeda0 Oct 27, 2023
03f20b4
fix(tests): remove duplicate test
fareeda0 Oct 27, 2023
2c76fa4
refactor: clean up files
fareeda0 Oct 27, 2023
bc7e2a5
fix: build script
fareeda0 Oct 27, 2023
af716af
feat: split stores, add in-memory store
fareeda0 Oct 29, 2023
d1f5f49
tests: cleanup tests, add in memory tests
fareeda0 Oct 29, 2023
d17ba70
refactor: update meta files
fareeda0 Nov 15, 2023
bc20f37
feat: finalise stubs, remove templates
fareeda0 Nov 15, 2023
767bcd9
chore: clean up types
fareeda0 Nov 15, 2023
c71be80
feat: skip when disabled in config
fareeda0 Nov 15, 2023
178371c
test: add more tests, improve coverage
fareeda0 Nov 15, 2023
746fe8d
ci: update workflows
fareeda0 Nov 15, 2023
f966759
fix: indentation in workflow
fareeda0 Nov 15, 2023
7c75761
fix: take db name from env
fareeda0 Nov 15, 2023
36d3f4a
ci: update action versions
fareeda0 Nov 15, 2023
858b654
chore: update config keys
fareeda0 Nov 15, 2023
455ebc1
docs: typo in docs
fareeda0 Nov 15, 2023
071da55
chore: cleanup imports
fareeda0 Nov 15, 2023
bb0d23a
chore: remove types from tsconfig
fareeda0 Nov 15, 2023
8bcd27f
fix: pass redis connection name
fareeda0 Nov 16, 2023
7e2583b
refactor: reorder limiter store args
fareeda0 Nov 16, 2023
629293b
feat: infer types
fareeda0 Nov 16, 2023
abeb2e5
test: add configure tests
fareeda0 Nov 16, 2023
aa83377
fix: temp workaround for tsconfig extend
fareeda0 Nov 29, 2023
90dbdac
Merge pull request #8 from fareeda0/next
thetutlage Dec 1, 2023
46b095e
refactor: remove existing code temporarily and get stores in working …
thetutlage Feb 1, 2024
48f1c8c
feat: implement limiter
thetutlage Feb 1, 2024
e179cf3
feat: add limiter manager
thetutlage Feb 1, 2024
235b68d
feat: implement http limiter and throttle middleware
thetutlage Feb 1, 2024
92e529c
ci: update workflow file
thetutlage Feb 1, 2024
8e70667
style: format source code
thetutlage Feb 1, 2024
ae2981b
ci: fix db name
thetutlage Feb 1, 2024
e9fa96d
feat: add helper to disable limits for the given request
thetutlage Feb 1, 2024
d7650b0
test: fix linter issues
thetutlage Feb 1, 2024
613ae20
feat: add define config helper
thetutlage Feb 5, 2024
5979a71
feat: add support for clearing rate limits
thetutlage Feb 5, 2024
707803e
refactor: cleanup middleware logic
thetutlage Feb 5, 2024
f1a41d5
feat: add provider and limiter service
thetutlage Feb 5, 2024
c6bb70d
chore: update bundling process
thetutlage Feb 5, 2024
1fe6273
feat: add configure hook
thetutlage Feb 5, 2024
7d0a3cb
feat: add support for clearing stores
thetutlage Feb 5, 2024
4968eb5
ci: rename test.yml to checks.yml
thetutlage Feb 5, 2024
3d5cbe4
docs: update readme
thetutlage Feb 5, 2024
098d74c
refactor: http limiter api
thetutlage Feb 5, 2024
b47affa
tests: improve coverage
thetutlage Feb 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor: update throttle middleware
  • Loading branch information
fareeda0 committed Oct 27, 2023
commit 4013daa6a62a55e2139fb9b2a7eca7df9f8b6e22
12 changes: 12 additions & 0 deletions providers/limiter_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import { ApplicationService } from '@adonisjs/core/types'
import { LimiterService } from '../src/types.js'
import { LimiterManager } from '../src/limiter_manager.js'
import ThrottleMiddleware from '../src/throttle_middleware.js'
import { configProvider } from '@adonisjs/core'
import { RuntimeException } from '@poppinss/utils'

Expand Down Expand Up @@ -45,10 +46,21 @@ export default class LimiterProvider {
})
}

/**
* Register throttle middleware
*/
#registerThrottleMiddleware() {
this.app.container.singleton(ThrottleMiddleware, async (resolver) => {
const manager = await resolver.make('limiter')
return new ThrottleMiddleware(manager)
})
}

/**
* Register bindings
*/
register() {
this.#registerLimiterManager()
this.#registerThrottleMiddleware()
}
}
22 changes: 22 additions & 0 deletions src/exceptions/invalid_http_limiter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* @adonisjs/limiter
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { Exception } from '@poppinss/utils'

/**
* Exception raised when the specified limiter does not exist
*/
export class InvalidHttpLimiterException extends Exception {
static invoke(httpLimiter: string, route?: string) {
return new this(`Invalid limiter "${httpLimiter}" applied on "${route}" route`, {
status: 500,
code: 'E_INVALID_HTTP_LIMITER',
})
}
}
39 changes: 21 additions & 18 deletions throttle/index.ts → src/throttle_middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,35 @@
* file that was distributed with this source code.
*/

import { inject } from '@adonisjs/core'
import { HttpContext } from '@adonisjs/http-server'
import type { NextFn } from '@adonisjs/core/types/http'
import { HttpContext } from '@adonisjs/core/http'

import type { LimiterManager } from '../src/limiter_manager.js'
import type { HttpLimiterConfigBuilder } from '../src/config_builder.js'
import { ThrottleException } from '../src/exceptions/throttle_exception.js'
import type { LimiterResponse, LimitExceededCallback, RuntimeConfig } from '../src/types.js'
import type { LimiterManager } from './limiter_manager.js'
import type { HttpLimiterConfigBuilder } from './config_builder.js'
import { ThrottleException } from './exceptions/throttle_exception.js'
import type { LimiterResponse, LimitExceededCallback, RuntimeConfig } from './types.js'
import { InvalidHttpLimiterException } from './exceptions/invalid_http_limiter.js'

/**
* Throttle middleware
*/
@inject()
export default class ThrottleMiddleware {
constructor(private limiter: LimiterManager<any, any>) {}
#limiter: LimiterManager<any, any>
constructor(limiter: LimiterManager<any, any>) {
this.#limiter = limiter
}

/**
* Creates a limiter for the given store and config
*/
private getLimiter(config: RuntimeConfig, store?: string) {
return store ? this.limiter.use(store, config) : this.limiter.use(config)
#getLimiter(config: RuntimeConfig, store?: string) {
return store ? this.#limiter.use(store, config) : this.#limiter.use(config)
}

/**
* Abort request
*/
private abort(limiterResponse: LimiterResponse, limitedExceededCallback?: LimitExceededCallback) {
#abort(limiterResponse: LimiterResponse, limitedExceededCallback?: LimitExceededCallback) {
const error = ThrottleException.invoke(limiterResponse)
if (limitedExceededCallback) {
limitedExceededCallback(error)
Expand All @@ -44,22 +47,22 @@ export default class ThrottleMiddleware {
/**
* Rate limit request using a specific limiter
*/
private async rateLimitRequest(
async #rateLimitRequest(
httpLimiter: string,
{ request, response }: HttpContext,
configBuilder: HttpLimiterConfigBuilder<any>
) {
const { config, store, key, limitedExceededCallback } = configBuilder.toJSON()
const throttleKey = `${httpLimiter}_${key || request.ip()}`
const limiter = this.getLimiter(config, store as string | undefined)
const limiter = this.#getLimiter(config, store as string | undefined)

const limiterResponse = await limiter.get(throttleKey)

/**
* Abort when user has exhausted all the requests
*/
if (limiterResponse && limiterResponse.remaining < 0) {
this.abort(limiterResponse, limitedExceededCallback)
this.#abort(limiterResponse, limitedExceededCallback)
}

/**
Expand All @@ -81,14 +84,14 @@ export default class ThrottleMiddleware {
/**
* Middleware handler for throttling HTTP requests
*/
async handle(ctx: HttpContext, next: () => Promise<any>, httpLimiter: string) {
const configFactory = this.limiter.httpLimiters[httpLimiter]
async handle(ctx: HttpContext, next: NextFn, httpLimiter: string) {
const configFactory = this.#limiter.httpLimiters[httpLimiter]

/**
* Make sure the limiter factory was registered in first place
*/
if (!configFactory) {
throw new Error(`Invalid limiter "${httpLimiter}" applied on "${ctx.route!.pattern}" route`)
throw InvalidHttpLimiterException.invoke(httpLimiter, ctx.route?.pattern!)
}

const configBuilder = await configFactory(ctx)
Expand All @@ -102,7 +105,7 @@ export default class ThrottleMiddleware {
return next()
}

await this.rateLimitRequest(httpLimiter, ctx, configBuilder)
await this.#rateLimitRequest(httpLimiter, ctx, configBuilder)
return next()
}
}