@aeriajs/security 0.0.57 → 0.0.59
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/rateLimiting.d.ts +57 -6
- package/dist/rateLimiting.js +53 -46
- package/dist/rateLimiting.mjs +53 -52
- package/package.json +7 -7
package/dist/rateLimiting.d.ts
CHANGED
|
@@ -1,6 +1,57 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import type { RouteContext, RateLimitingParams } from '@aeriajs/types';
|
|
2
|
+
import { RateLimitingErrors } from '@aeriajs/types';
|
|
3
|
+
export declare const getOrCreateUsageEntry: (params: RateLimitingParams, context: RouteContext) => Promise<import("@aeriajs/types").WithId<Omit<import("@aeriajs/types").PackReferences<import("@aeriajs/types").SchemaWithId<{
|
|
4
|
+
readonly $id: "resourceUsage";
|
|
5
|
+
readonly required: readonly ["usage"];
|
|
6
|
+
readonly properties: {
|
|
7
|
+
readonly user: {
|
|
8
|
+
readonly $ref: "user";
|
|
9
|
+
};
|
|
10
|
+
readonly address: {
|
|
11
|
+
readonly type: "string";
|
|
12
|
+
};
|
|
13
|
+
readonly usage: {
|
|
14
|
+
readonly type: "object";
|
|
15
|
+
readonly additionalProperties: {
|
|
16
|
+
readonly type: "object";
|
|
17
|
+
readonly properties: {
|
|
18
|
+
readonly hits: {
|
|
19
|
+
readonly type: "integer";
|
|
20
|
+
};
|
|
21
|
+
readonly points: {
|
|
22
|
+
readonly type: "integer";
|
|
23
|
+
};
|
|
24
|
+
readonly last_reach: {
|
|
25
|
+
readonly type: "string";
|
|
26
|
+
readonly format: "date-time";
|
|
27
|
+
};
|
|
28
|
+
readonly last_maximum_reach: {
|
|
29
|
+
readonly type: "string";
|
|
30
|
+
readonly format: "date-time";
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
}>>, "_id">>>;
|
|
37
|
+
export declare const limitRate: (params: RateLimitingParams, context: RouteContext) => Promise<import("@aeriajs/types").Left<RateLimitingErrors.LimitReached> | import("@aeriajs/types").Right<{} & Omit<Readonly<import("@aeriajs/types").FilterReadonlyProperties<{
|
|
38
|
+
readonly hits: {
|
|
39
|
+
readonly type: "integer";
|
|
40
|
+
};
|
|
41
|
+
readonly points: {
|
|
42
|
+
readonly type: "integer";
|
|
43
|
+
};
|
|
44
|
+
readonly last_reach: {
|
|
45
|
+
readonly type: "string";
|
|
46
|
+
readonly format: "date-time";
|
|
47
|
+
};
|
|
48
|
+
readonly last_maximum_reach: {
|
|
49
|
+
readonly type: "string";
|
|
50
|
+
readonly format: "date-time";
|
|
51
|
+
};
|
|
52
|
+
}>> & {
|
|
53
|
+
hits: never;
|
|
54
|
+
points: never;
|
|
55
|
+
last_reach: Date;
|
|
56
|
+
last_maximum_reach: Date;
|
|
57
|
+
}, never>>>;
|
package/dist/rateLimiting.js
CHANGED
|
@@ -1,59 +1,66 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.limitRate = exports.
|
|
3
|
+
exports.limitRate = exports.getOrCreateUsageEntry = void 0;
|
|
4
|
+
const types_1 = require("@aeriajs/types");
|
|
4
5
|
const common_1 = require("@aeriajs/common");
|
|
5
|
-
|
|
6
|
-
(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
const buildEntryFilter = (params, context) => {
|
|
7
|
+
if (params.strategy === 'ip') {
|
|
8
|
+
const address = context.response.socket.remoteAddress;
|
|
9
|
+
return {
|
|
10
|
+
address,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
11
13
|
if (!context.token.authenticated) {
|
|
12
|
-
throw new Error();
|
|
14
|
+
throw new Error('user is not authenticated');
|
|
13
15
|
}
|
|
14
|
-
return
|
|
15
|
-
|
|
16
|
+
return {
|
|
17
|
+
user: context.token.sub,
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
const getOrCreateUsageEntry = async (params, context) => {
|
|
21
|
+
const filters = buildEntryFilter(params, context);
|
|
22
|
+
const entry = await context.collections.resourceUsage.model.findOneAndUpdate(filters, {
|
|
23
|
+
$setOnInsert: {
|
|
24
|
+
usage: {},
|
|
25
|
+
},
|
|
16
26
|
}, {
|
|
17
|
-
|
|
27
|
+
upsert: true,
|
|
28
|
+
returnDocument: 'after',
|
|
18
29
|
});
|
|
30
|
+
if (!entry) {
|
|
31
|
+
throw new Error('there was an error creating the entry');
|
|
32
|
+
}
|
|
33
|
+
return entry;
|
|
19
34
|
};
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
35
|
+
exports.getOrCreateUsageEntry = getOrCreateUsageEntry;
|
|
36
|
+
const limitRate = async (params, context) => {
|
|
37
|
+
const { increment = 1 } = params;
|
|
38
|
+
const entry = await (0, exports.getOrCreateUsageEntry)(params, context);
|
|
39
|
+
const pathname = context.request.url.replace(new RegExp(`^${context.config.apiBase}`), '');
|
|
40
|
+
const resourceName = new URL(`http://0.com${pathname}`).pathname;
|
|
41
|
+
const resource = entry.usage[resourceName];
|
|
42
|
+
if (resource) {
|
|
43
|
+
if (params.scale) {
|
|
44
|
+
const now = new Date();
|
|
45
|
+
if (params.scale > now.getTime() / 1000 - resource.last_reach.getTime() / 1000) {
|
|
46
|
+
return (0, common_1.left)(types_1.RateLimitingErrors.LimitReached);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
24
49
|
}
|
|
25
|
-
const
|
|
26
|
-
|
|
50
|
+
const newEntry = await context.collections.resourceUsage.model.findOneAndUpdate({
|
|
51
|
+
_id: entry._id,
|
|
52
|
+
}, {
|
|
27
53
|
$inc: {
|
|
28
|
-
hits:
|
|
54
|
+
[`usage.${resourceName}.hits`]: 1,
|
|
55
|
+
[`usage.${resourceName}.points`]: increment,
|
|
29
56
|
},
|
|
30
|
-
$set: {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
_id: user._id,
|
|
39
|
-
}, {
|
|
40
|
-
$set: {
|
|
41
|
-
[`resources_usage.${context.functionPath}`]: entry.insertedId,
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
return (0, common_1.right)(null);
|
|
45
|
-
}
|
|
46
|
-
if (scale && (new Date().getTime() / 1000 - usage.updated_at.getTime() / 1000 < scale)) {
|
|
47
|
-
return (0, common_1.left)(RateLimitingErrors.LimitReached);
|
|
48
|
-
}
|
|
49
|
-
if (limit && (usage.hits % limit === 0)) {
|
|
50
|
-
payload.$set = {
|
|
51
|
-
last_maximum_reach: new Date(),
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
await context.collections.resourceUsage.model.updateOne({
|
|
55
|
-
_id: usage._id,
|
|
56
|
-
}, payload);
|
|
57
|
-
return (0, common_1.right)(null);
|
|
57
|
+
$set: {
|
|
58
|
+
[`usage.${resourceName}.last_reach`]: new Date(),
|
|
59
|
+
[`usage.${resourceName}.last_maximum_reach`]: new Date(),
|
|
60
|
+
},
|
|
61
|
+
}, {
|
|
62
|
+
returnDocument: 'after',
|
|
63
|
+
});
|
|
64
|
+
return (0, common_1.right)(newEntry.usage[resourceName]);
|
|
58
65
|
};
|
|
59
66
|
exports.limitRate = limitRate;
|
package/dist/rateLimiting.mjs
CHANGED
|
@@ -1,69 +1,70 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
import { RateLimitingErrors } from "@aeriajs/types";
|
|
2
3
|
import { left, right } from "@aeriajs/common";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
const buildEntryFilter = (params, context) => {
|
|
5
|
+
if (params.strategy === "ip") {
|
|
6
|
+
const address = context.response.socket.remoteAddress;
|
|
7
|
+
return {
|
|
8
|
+
address
|
|
9
|
+
};
|
|
10
|
+
}
|
|
9
11
|
if (!context.token.authenticated) {
|
|
10
|
-
throw new Error();
|
|
12
|
+
throw new Error("user is not authenticated");
|
|
11
13
|
}
|
|
12
|
-
return
|
|
14
|
+
return {
|
|
15
|
+
user: context.token.sub
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export const getOrCreateUsageEntry = async (params, context) => {
|
|
19
|
+
const filters = buildEntryFilter(params, context);
|
|
20
|
+
const entry = await context.collections.resourceUsage.model.findOneAndUpdate(
|
|
21
|
+
filters,
|
|
13
22
|
{
|
|
14
|
-
|
|
23
|
+
$setOnInsert: {
|
|
24
|
+
usage: {}
|
|
25
|
+
}
|
|
15
26
|
},
|
|
16
27
|
{
|
|
17
|
-
|
|
28
|
+
upsert: true,
|
|
29
|
+
returnDocument: "after"
|
|
18
30
|
}
|
|
19
31
|
);
|
|
32
|
+
if (!entry) {
|
|
33
|
+
throw new Error("there was an error creating the entry");
|
|
34
|
+
}
|
|
35
|
+
return entry;
|
|
20
36
|
};
|
|
21
|
-
export const limitRate = async (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
export const limitRate = async (params, context) => {
|
|
38
|
+
const { increment = 1 } = params;
|
|
39
|
+
const entry = await getOrCreateUsageEntry(params, context);
|
|
40
|
+
const pathname = context.request.url.replace(new RegExp(`^${context.config.apiBase}`), "");
|
|
41
|
+
const resourceName = new URL(`http://0.com${pathname}`).pathname;
|
|
42
|
+
const resource = entry.usage[resourceName];
|
|
43
|
+
if (resource) {
|
|
44
|
+
if (params.scale) {
|
|
45
|
+
const now = /* @__PURE__ */ new Date();
|
|
46
|
+
if (params.scale > now.getTime() / 1e3 - resource.last_reach.getTime() / 1e3) {
|
|
47
|
+
return left(RateLimitingErrors.LimitReached);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
25
50
|
}
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
scale
|
|
30
|
-
} = params;
|
|
31
|
-
const payload = {
|
|
32
|
-
$inc: {
|
|
33
|
-
hits: increment
|
|
51
|
+
const newEntry = await context.collections.resourceUsage.model.findOneAndUpdate(
|
|
52
|
+
{
|
|
53
|
+
_id: entry._id
|
|
34
54
|
},
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const entry = await context.collections.resourceUsage.model.insertOne({
|
|
40
|
-
hits: increment
|
|
41
|
-
});
|
|
42
|
-
await context.collections.user.model.updateOne(
|
|
43
|
-
{
|
|
44
|
-
_id: user._id
|
|
55
|
+
{
|
|
56
|
+
$inc: {
|
|
57
|
+
[`usage.${resourceName}.hits`]: 1,
|
|
58
|
+
[`usage.${resourceName}.points`]: increment
|
|
45
59
|
},
|
|
46
|
-
{
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
60
|
+
$set: {
|
|
61
|
+
[`usage.${resourceName}.last_reach`]: /* @__PURE__ */ new Date(),
|
|
62
|
+
[`usage.${resourceName}.last_maximum_reach`]: /* @__PURE__ */ new Date()
|
|
50
63
|
}
|
|
51
|
-
);
|
|
52
|
-
return right(null);
|
|
53
|
-
}
|
|
54
|
-
if (scale && (/* @__PURE__ */ new Date()).getTime() / 1e3 - usage.updated_at.getTime() / 1e3 < scale) {
|
|
55
|
-
return left("LIMIT_REACHED" /* LimitReached */);
|
|
56
|
-
}
|
|
57
|
-
if (limit && usage.hits % limit === 0) {
|
|
58
|
-
payload.$set = {
|
|
59
|
-
last_maximum_reach: /* @__PURE__ */ new Date()
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
await context.collections.resourceUsage.model.updateOne(
|
|
63
|
-
{
|
|
64
|
-
_id: usage._id
|
|
65
64
|
},
|
|
66
|
-
|
|
65
|
+
{
|
|
66
|
+
returnDocument: "after"
|
|
67
|
+
}
|
|
67
68
|
);
|
|
68
|
-
return right(
|
|
69
|
+
return right(newEntry.usage[resourceName]);
|
|
69
70
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aeriajs/security",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.59",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -22,15 +22,15 @@
|
|
|
22
22
|
"dist"
|
|
23
23
|
],
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@aeriajs/api": "^0.0.
|
|
26
|
-
"@aeriajs/common": "^0.0.
|
|
27
|
-
"@aeriajs/types": "^0.0.
|
|
25
|
+
"@aeriajs/api": "^0.0.59",
|
|
26
|
+
"@aeriajs/common": "^0.0.31",
|
|
27
|
+
"@aeriajs/types": "^0.0.28",
|
|
28
28
|
"mongodb": "^6.5.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@aeriajs/api": "^0.0.
|
|
32
|
-
"@aeriajs/common": "^0.0.
|
|
33
|
-
"@aeriajs/types": "^0.0.
|
|
31
|
+
"@aeriajs/api": "^0.0.59",
|
|
32
|
+
"@aeriajs/common": "^0.0.31",
|
|
33
|
+
"@aeriajs/types": "^0.0.28",
|
|
34
34
|
"mongodb": "^6.1.0"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|