summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/call.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend/src/server/api/call.ts')
-rw-r--r--packages/backend/src/server/api/call.ts44
1 files changed, 30 insertions, 14 deletions
diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts
index 9a85e4565b..cd3e0abc06 100644
--- a/packages/backend/src/server/api/call.ts
+++ b/packages/backend/src/server/api/call.ts
@@ -2,10 +2,11 @@ import Koa from 'koa';
import { performance } from 'perf_hooks';
import { limiter } from './limiter.js';
import { CacheableLocalUser, User } from '@/models/entities/user.js';
-import endpoints, { IEndpoint } from './endpoints.js';
+import endpoints, { IEndpointMeta } from './endpoints.js';
import { ApiError } from './error.js';
import { apiLogger } from './logger.js';
import { AccessToken } from '@/models/entities/access-token.js';
+import { getIpHash } from '@/misc/get-ip-hash.js';
const accessDenied = {
message: 'Access denied.',
@@ -15,6 +16,7 @@ const accessDenied = {
export default async (endpoint: string, user: CacheableLocalUser | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => {
const isSecure = user != null && token == null;
+ const isModerator = user != null && (user.isModerator || user.isAdmin);
const ep = endpoints.find(e => e.name === endpoint);
@@ -31,6 +33,32 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
throw new ApiError(accessDenied);
}
+ if (ep.meta.limit && !isModerator) {
+ // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
+ let limitActor: string;
+ if (user) {
+ limitActor = user.id;
+ } else {
+ limitActor = getIpHash(ctx!.ip);
+ }
+
+ const limit = Object.assign({}, ep.meta.limit);
+
+ if (!limit.key) {
+ limit.key = ep.name;
+ }
+
+ // Rate limit
+ await limiter(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor).catch(e => {
+ throw new ApiError({
+ message: 'Rate limit exceeded. Please try again later.',
+ code: 'RATE_LIMIT_EXCEEDED',
+ id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
+ httpStatusCode: 429,
+ });
+ });
+ }
+
if (ep.meta.requireCredential && user == null) {
throw new ApiError({
message: 'Credential required.',
@@ -53,7 +81,7 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
throw new ApiError(accessDenied, { reason: 'You are not the admin.' });
}
- if (ep.meta.requireModerator && !user!.isAdmin && !user!.isModerator) {
+ if (ep.meta.requireModerator && !isModerator) {
throw new ApiError(accessDenied, { reason: 'You are not a moderator.' });
}
@@ -65,18 +93,6 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
});
}
- if (ep.meta.requireCredential && ep.meta.limit && !user!.isAdmin && !user!.isModerator) {
- // Rate limit
- await limiter(ep as IEndpoint & { meta: { limit: NonNullable<IEndpoint['meta']['limit']> } }, user!).catch(e => {
- throw new ApiError({
- message: 'Rate limit exceeded. Please try again later.',
- code: 'RATE_LIMIT_EXCEEDED',
- id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
- httpStatusCode: 429,
- });
- });
- }
-
// Cast non JSON input
if (ep.meta.requireFile && ep.params.properties) {
for (const k of Object.keys(ep.params.properties)) {