-
Notifications
You must be signed in to change notification settings - Fork 610
chore: migrate single rate limits to multi rate limits #3328
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
Changes from all commits
732e3cd
b58fcfb
8b2df8e
0f5d549
0fa7682
b0d0113
85b45ab
37b3919
60ba33b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -200,6 +200,7 @@ export const registerV1ApisListKeys = (app: App) => | |
| permission: true, | ||
| }, | ||
| }, | ||
| ratelimits: true, | ||
| }, | ||
| limit: limit, | ||
| orderBy: schema.keys.id, | ||
|
|
@@ -233,6 +234,7 @@ export const registerV1ApisListKeys = (app: App) => | |
| : null, | ||
| permissions: Array.from(permissions.values()), | ||
| roles: k.roles.map((r) => r.role.name), | ||
| ratelimits: k.ratelimits, | ||
| }; | ||
| }), | ||
| total: keySpace.sizeApprox, | ||
|
|
@@ -293,50 +295,53 @@ export const registerV1ApisListKeys = (app: App) => | |
| }), | ||
| ); | ||
| } | ||
|
|
||
| return c.json({ | ||
| keys: data.keys.map((k) => ({ | ||
| id: k.id, | ||
| start: k.start, | ||
| apiId: api.id, | ||
| workspaceId: k.workspaceId, | ||
| name: k.name ?? undefined, | ||
| ownerId: k.ownerId ?? undefined, | ||
| meta: k.meta ? JSON.parse(k.meta) : undefined, | ||
| createdAt: k.createdAtM ?? undefined, | ||
| updatedAt: k.updatedAtM ?? undefined, | ||
| expires: k.expires?.getTime() ?? undefined, | ||
| ratelimit: | ||
| k.ratelimitAsync !== null && k.ratelimitLimit !== null && k.ratelimitDuration !== null | ||
| keys: data.keys.map((k) => { | ||
| const ratelimit = k.ratelimits.find((rl) => rl.name === "default"); | ||
| return { | ||
| id: k.id, | ||
| start: k.start, | ||
| apiId: api.id, | ||
| workspaceId: k.workspaceId, | ||
| name: k.name ?? undefined, | ||
| ownerId: k.ownerId ?? undefined, | ||
| meta: k.meta ? JSON.parse(k.meta) : undefined, | ||
| createdAt: k.createdAtM ?? undefined, | ||
| updatedAt: k.updatedAtM ?? undefined, | ||
| expires: k.expires?.getTime() ?? undefined, | ||
| ratelimit: ratelimit | ||
| ? { | ||
| async: false, | ||
| limit: ratelimit.limit, | ||
| duration: ratelimit.duration, | ||
| refillRate: ratelimit.limit, | ||
| refillInterval: ratelimit.duration, | ||
| } | ||
| : undefined, | ||
|
Comment on lines
+314
to
+321
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Avoid legacy alias duplication
Consider deprecating the aliases in the response or gating them behind a version flag. 🤖 Prompt for AI Agents |
||
|
|
||
| remaining: k.remaining ?? undefined, | ||
| refill: k.refillAmount | ||
| ? { | ||
| async: k.ratelimitAsync, | ||
| type: k.ratelimitAsync ? "fast" : ("consistent" as unknown), | ||
| limit: k.ratelimitLimit, | ||
| duration: k.ratelimitDuration, | ||
| refillRate: k.ratelimitLimit, | ||
| refillInterval: k.ratelimitDuration, | ||
| interval: k.refillDay ? ("monthly" as const) : ("daily" as const), | ||
| amount: k.refillAmount, | ||
| refillDay: k.refillDay, | ||
| lastRefillAt: k.lastRefillAt?.getTime(), | ||
| } | ||
| : undefined, | ||
| remaining: k.remaining ?? undefined, | ||
| refill: k.refillAmount | ||
| ? { | ||
| interval: k.refillDay ? ("monthly" as const) : ("daily" as const), | ||
| amount: k.refillAmount, | ||
| refillDay: k.refillDay, | ||
| lastRefillAt: k.lastRefillAt?.getTime(), | ||
| } | ||
| : undefined, | ||
| environment: k.environment ?? undefined, | ||
| plaintext: plaintext[k.id] ?? undefined, | ||
| roles: k.roles, | ||
| permissions: k.permissions, | ||
| identity: k.identity | ||
| ? { | ||
| id: k.identity.id, | ||
| externalId: k.identity.externalId, | ||
| meta: k.identity.meta ?? undefined, | ||
| } | ||
| : undefined, | ||
| })), | ||
| environment: k.environment ?? undefined, | ||
| plaintext: plaintext[k.id] ?? undefined, | ||
| roles: k.roles, | ||
| permissions: k.permissions, | ||
| identity: k.identity | ||
| ? { | ||
| id: k.identity.id, | ||
| externalId: k.identity.externalId, | ||
| meta: k.identity.meta ?? undefined, | ||
| } | ||
| : undefined, | ||
| }; | ||
| }), | ||
| total: data.total, | ||
| cursor: data.keys.at(-1)?.id ?? undefined, | ||
| }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -373,9 +373,6 @@ export const registerV1KeysCreateKey = (app: App) => | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expires: req.expires ? new Date(req.expires) : null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| createdAtM: Date.now(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updatedAtM: null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ratelimitAsync: req.ratelimit?.async ?? req.ratelimit?.type === "fast", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ratelimitLimit: req.ratelimit?.limit ?? req.ratelimit?.refillRate, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ratelimitDuration: req.ratelimit?.duration ?? req.ratelimit?.refillInterval, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| remaining: req.remaining, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| refillDay: req.refill?.interval === "daily" ? null : (req?.refill?.refillDay ?? 1), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| refillAmount: req.refill?.amount, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -384,6 +381,19 @@ export const registerV1KeysCreateKey = (app: App) => | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| environment: req.environment ?? null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| identityId: identity?.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (req.ratelimit) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await db.primary.insert(schema.ratelimits).values({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: newId("ratelimit"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| keyId: kId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| limit: req.ratelimit.limit ?? req.ratelimit.refillRate!, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| duration: req.ratelimit.duration ?? req.ratelimit.refillInterval!, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| workspaceId: authorizedWorkspaceId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: "default", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| autoApply: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| identityId: null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| createdAt: Date.now(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+384
to
+396
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Insert + ratelimit must be atomic & validated
- await db.primary.insert(schema.keys).values({ … });
- if (req.ratelimit) {
- await db.primary.insert(schema.ratelimits).values({ … });
- }
+ await db.primary.transaction(async (tx) => {
+ await tx.insert(schema.keys).values({ … });
+ if (req.ratelimit) {
+ if (
+ req.ratelimit.limit === undefined &&
+ req.ratelimit.refillRate === undefined
+ ) {
+ throw new UnkeyApiError({
+ code: "BAD_REQUEST",
+ message: "`ratelimit.limit` is required",
+ });
+ }
+ if (
+ req.ratelimit.duration === undefined &&
+ req.ratelimit.refillInterval === undefined
+ ) {
+ throw new UnkeyApiError({
+ code: "BAD_REQUEST",
+ message: "`ratelimit.duration` is required",
+ });
+ }
+ await tx.insert(schema.ratelimits).values({ … });
+ }
+ });This keeps the DB consistent and surfaces invalid payloads early. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (req.recoverable && api.keyAuth?.storeEncryptedKeys) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const perm = rbac.evaluatePermissions( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -81,6 +81,7 @@ export const registerV1KeysDeleteKey = (app: App) => | |
| api: true, | ||
| }, | ||
| }, | ||
| ratelimits: true, | ||
| }, | ||
|
Comment on lines
+84
to
85
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Loading ratelimits eagerly is OK but might be overkill For a delete operation we never touch ratelimit rows. Fetching them adds an extra join with no functional gain and bloats the cache entry. 🤖 Prompt for AI Agents |
||
| }); | ||
| if (!dbRes) { | ||
|
|
@@ -98,6 +99,7 @@ export const registerV1KeysDeleteKey = (app: App) => | |
| meta: dbRes.identity.meta, | ||
| } | ||
| : null, | ||
| ratelimits: dbRes.ratelimits, | ||
| }; | ||
chronark marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Duplicate ratelimit representations increase cache size
Both
key.ratelimits: Ratelimit[]and a flattenedratelimitsmap live in the same entry.Unless consumers need both shapes, keeping one reduces memory and invalidation surface.
Evaluate dropping the redundant field or deriving it on demand.
🤖 Prompt for AI Agents