@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.
@@ -1,6 +1,57 @@
1
- import type { Context, Description, RateLimitingParams } from '@aeriajs/types';
2
- export declare enum RateLimitingErrors {
3
- Unauthenticated = "UNAUTHENTICATED",
4
- LimitReached = "LIMIT_REACHED"
5
- }
6
- export declare const limitRate: <TDescription extends Description>(context: Context<TDescription>, params: RateLimitingParams) => Promise<import("@aeriajs/types").Left<RateLimitingErrors.Unauthenticated> | import("@aeriajs/types").Right<null> | import("@aeriajs/types").Left<RateLimitingErrors.LimitReached>>;
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>>>;
@@ -1,59 +1,66 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.limitRate = exports.RateLimitingErrors = void 0;
3
+ exports.limitRate = exports.getOrCreateUsageEntry = void 0;
4
+ const types_1 = require("@aeriajs/types");
4
5
  const common_1 = require("@aeriajs/common");
5
- var RateLimitingErrors;
6
- (function (RateLimitingErrors) {
7
- RateLimitingErrors["Unauthenticated"] = "UNAUTHENTICATED";
8
- RateLimitingErrors["LimitReached"] = "LIMIT_REACHED";
9
- })(RateLimitingErrors || (exports.RateLimitingErrors = RateLimitingErrors = {}));
10
- const getUser = (context) => {
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 context.collections.user.model.findOne({
15
- _id: context.token.sub,
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
- resources_usage: 1,
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
- const limitRate = async (context, params) => {
21
- let user;
22
- if (!context.token.authenticated || !(user = await getUser(context))) {
23
- return (0, common_1.left)(RateLimitingErrors.Unauthenticated);
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 { increment = 1, limit, scale, } = params;
26
- const payload = {
50
+ const newEntry = await context.collections.resourceUsage.model.findOneAndUpdate({
51
+ _id: entry._id,
52
+ }, {
27
53
  $inc: {
28
- hits: increment,
54
+ [`usage.${resourceName}.hits`]: 1,
55
+ [`usage.${resourceName}.points`]: increment,
29
56
  },
30
- $set: {},
31
- };
32
- const usage = user.resources_usage?.get(context.functionPath);
33
- if (!usage) {
34
- const entry = await context.collections.resourceUsage.model.insertOne({
35
- hits: increment,
36
- });
37
- await context.collections.user.model.updateOne({
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;
@@ -1,69 +1,70 @@
1
1
  "use strict";
2
+ import { RateLimitingErrors } from "@aeriajs/types";
2
3
  import { left, right } from "@aeriajs/common";
3
- export var RateLimitingErrors = /* @__PURE__ */ ((RateLimitingErrors2) => {
4
- RateLimitingErrors2["Unauthenticated"] = "UNAUTHENTICATED";
5
- RateLimitingErrors2["LimitReached"] = "LIMIT_REACHED";
6
- return RateLimitingErrors2;
7
- })(RateLimitingErrors || {});
8
- const getUser = (context) => {
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 context.collections.user.model.findOne(
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
- _id: context.token.sub
23
+ $setOnInsert: {
24
+ usage: {}
25
+ }
15
26
  },
16
27
  {
17
- resources_usage: 1
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 (context, params) => {
22
- let user;
23
- if (!context.token.authenticated || !(user = await getUser(context))) {
24
- return left("UNAUTHENTICATED" /* Unauthenticated */);
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
- increment = 1,
28
- limit,
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
- $set: {}
36
- };
37
- const usage = user.resources_usage?.get(context.functionPath);
38
- if (!usage) {
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
- $set: {
48
- [`resources_usage.${context.functionPath}`]: entry.insertedId
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
- payload
65
+ {
66
+ returnDocument: "after"
67
+ }
67
68
  );
68
- return right(null);
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.57",
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.57",
26
- "@aeriajs/common": "^0.0.29",
27
- "@aeriajs/types": "^0.0.26",
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.57",
32
- "@aeriajs/common": "^0.0.29",
33
- "@aeriajs/types": "^0.0.26",
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": {