@owlmeans/server-auth-otp 0.1.18-rc.51 → 0.1.18-rc.53
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/README.md +2 -2
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/server-auth-otp/SKILL.md +1 -1
- package/build/challenge.d.ts +1 -2
- package/build/challenge.d.ts.map +1 -1
- package/build/challenge.js +2 -23
- package/build/challenge.js.map +1 -1
- package/build/consts.d.ts +6 -0
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +7 -0
- package/build/consts.js.map +1 -1
- package/build/consts.local.d.ts +4 -0
- package/build/consts.local.d.ts.map +1 -0
- package/build/consts.local.js +50 -0
- package/build/consts.local.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/keys/types.d.ts +10 -0
- package/build/keys/types.d.ts.map +1 -0
- package/build/keys/types.js +2 -0
- package/build/keys/types.js.map +1 -0
- package/build/keys.d.ts +4 -0
- package/build/keys.d.ts.map +1 -0
- package/build/keys.js +10 -0
- package/build/keys.js.map +1 -0
- package/build/plugin.d.ts.map +1 -1
- package/build/plugin.js +1 -2
- package/build/plugin.js.map +1 -1
- package/build/service.d.ts +1 -4
- package/build/service.d.ts.map +1 -1
- package/build/service.js +15 -19
- package/build/service.js.map +1 -1
- package/build/throttle-keys/types.d.ts +8 -0
- package/build/throttle-keys/types.d.ts.map +1 -0
- package/build/throttle-keys/types.js +2 -0
- package/build/throttle-keys/types.js.map +1 -0
- package/build/throttle-keys.d.ts +4 -0
- package/build/throttle-keys.d.ts.map +1 -0
- package/build/throttle-keys.js +9 -0
- package/build/throttle-keys.js.map +1 -0
- package/build/throttle.d.ts +5 -6
- package/build/throttle.d.ts.map +1 -1
- package/build/throttle.js +8 -34
- package/build/throttle.js.map +1 -1
- package/build/types.d.ts +1 -6
- package/build/types.d.ts.map +1 -1
- package/build/types.js +1 -7
- package/build/types.js.map +1 -1
- package/build/types.local.d.ts +16 -0
- package/build/types.local.d.ts.map +1 -0
- package/build/types.local.js +2 -0
- package/build/types.local.js.map +1 -0
- package/package.json +14 -14
- package/src/challenge.ts +4 -32
- package/src/consts.local.ts +51 -0
- package/src/consts.ts +7 -0
- package/src/index.ts +4 -0
- package/src/keys/types.ts +9 -0
- package/src/keys.ts +16 -0
- package/src/plugin.ts +5 -10
- package/src/service.ts +16 -29
- package/src/throttle-keys/types.ts +7 -0
- package/src/throttle-keys.ts +17 -0
- package/src/throttle.ts +11 -46
- package/src/types.local.ts +10 -0
- package/src/types.ts +1 -7
- package/tests/keys.spec.ts +8 -8
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Email OTP `AuthPlugin` for OwlMeans servers — passwordless login via time-limi
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
bun add @owlmeans/server-auth-otp@^0.1.18-rc.
|
|
15
|
+
bun add @owlmeans/server-auth-otp@^0.1.18-rc.53
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
@@ -47,7 +47,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
47
47
|
your project's skill store (`.agents/skills/`):
|
|
48
48
|
|
|
49
49
|
```sh
|
|
50
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
50
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.49
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/server-auth-otp",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-10-
|
|
4
|
+
"version": "0.1.18-rc.53",
|
|
5
|
+
"generatedAt": "2026-10-05T21:03:06.391Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -8,7 +8,7 @@ metadata:
|
|
|
8
8
|
|
|
9
9
|
# Using `@owlmeans/server-auth-otp`
|
|
10
10
|
|
|
11
|
-
**Install:** `"@owlmeans/server-auth-otp": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/server-auth-otp": "^0.1.18-rc.53"` in `dependencies`
|
|
12
12
|
|
|
13
13
|
Email OTP authentication plugin for the OwlMeans auth-manager plugin system. Relies on `@owlmeans/auth-otp` for the OTP service interface, a Redis resource for code storage, and a `MailerService` to send codes.
|
|
14
14
|
|
package/build/challenge.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { OtpChallengeStore } from './types.js';
|
|
2
|
-
type Clock
|
|
2
|
+
import type { Clock } from './types.local.js';
|
|
3
3
|
/** Real in-memory challenge store with synchronous consume/update semantics. */
|
|
4
4
|
export declare const makeMemoryOtpChallengeStore: (alias?: string, now?: Clock) => OtpChallengeStore;
|
|
5
5
|
/** Redis challenge store: compare, attempt increment, exhaustion, and consume are one operation. */
|
|
6
6
|
export declare const makeRedisOtpChallengeStore: (alias?: string, resourceAlias?: string) => OtpChallengeStore;
|
|
7
|
-
export {};
|
|
8
7
|
//# sourceMappingURL=challenge.d.ts.map
|
package/build/challenge.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"challenge.d.ts","sourceRoot":"","sources":["../src/challenge.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"challenge.d.ts","sourceRoot":"","sources":["../src/challenge.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAyB,MAAM,YAAY,CAAA;AAE1E,OAAO,KAAK,EAA2B,KAAK,EAAmB,MAAM,kBAAkB,CAAA;AAGvF,gFAAgF;AAChF,eAAO,MAAM,2BAA2B,yBACJ,KAAK,KACtC,iBA+BF,CAAA;AAED,oGAAoG;AACpG,eAAO,MAAM,0BAA0B,8CAEpC,iBAqCF,CAAA"}
|
package/build/challenge.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { appendContextual, createService } from '@owlmeans/context';
|
|
2
2
|
import { OtpUnavailable } from './errors.js';
|
|
3
|
-
import { OTP_CHALLENGE_STORE, OTP_RESOURCE } from './consts.js';
|
|
4
|
-
import {
|
|
3
|
+
import { OTP_CHALLENGE_STORE, OTP_RESOURCE, OtpChallengeOutcome as Outcome } from './consts.js';
|
|
4
|
+
import { REDIS_VERIFY_SCRIPT } from './consts.local.js';
|
|
5
5
|
/** Real in-memory challenge store with synchronous consume/update semantics. */
|
|
6
6
|
export const makeMemoryOtpChallengeStore = (alias = OTP_CHALLENGE_STORE, now = Date.now) => {
|
|
7
7
|
const records = new Map();
|
|
@@ -34,27 +34,6 @@ export const makeMemoryOtpChallengeStore = (alias = OTP_CHALLENGE_STORE, now = D
|
|
|
34
34
|
},
|
|
35
35
|
});
|
|
36
36
|
};
|
|
37
|
-
const REDIS_VERIFY_SCRIPT = `
|
|
38
|
-
local raw = redis.call('GET', KEYS[1])
|
|
39
|
-
if not raw then return 0 end
|
|
40
|
-
local record = cjson.decode(raw)
|
|
41
|
-
if record.emailKey == ARGV[1] and record.codeHash == ARGV[2] then
|
|
42
|
-
redis.call('DEL', KEYS[1])
|
|
43
|
-
return 1
|
|
44
|
-
end
|
|
45
|
-
record.failedAttempts = tonumber(record.failedAttempts or 0) + 1
|
|
46
|
-
if record.failedAttempts >= tonumber(ARGV[3]) then
|
|
47
|
-
redis.call('DEL', KEYS[1])
|
|
48
|
-
return -2
|
|
49
|
-
end
|
|
50
|
-
local ttl = redis.call('PTTL', KEYS[1])
|
|
51
|
-
if ttl <= 0 then
|
|
52
|
-
redis.call('DEL', KEYS[1])
|
|
53
|
-
return 0
|
|
54
|
-
end
|
|
55
|
-
redis.call('SET', KEYS[1], cjson.encode(record), 'XX', 'PX', ttl)
|
|
56
|
-
return -1
|
|
57
|
-
`;
|
|
58
37
|
/** Redis challenge store: compare, attempt increment, exhaustion, and consume are one operation. */
|
|
59
38
|
export const makeRedisOtpChallengeStore = (alias = OTP_CHALLENGE_STORE, resourceAlias = OTP_RESOURCE) => {
|
|
60
39
|
const service = appendContextual(alias, {
|
package/build/challenge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"challenge.js","sourceRoot":"","sources":["../src/challenge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"challenge.js","sourceRoot":"","sources":["../src/challenge.ts"],"names":[],"mappings":"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"}
|
package/build/consts.d.ts
CHANGED
|
@@ -10,4 +10,10 @@ export declare const OTP_EMAIL_THROTTLE_RULES: readonly (Readonly<{
|
|
|
10
10
|
}>)[];
|
|
11
11
|
/** The OTP auth plugin type key — passed as credential.type in init requests */
|
|
12
12
|
export { OTP_AUTH_TYPE, OTP_SERVICE, OTP_RESOURCE, OTP_TTL_SECONDS, OTP_CODE_LENGTH, OTP_MAX_FAILED_ATTEMPTS, } from '@owlmeans/auth-otp';
|
|
13
|
+
export declare enum OtpChallengeOutcome {
|
|
14
|
+
Verified = "verified",
|
|
15
|
+
Invalid = "invalid",
|
|
16
|
+
Exhausted = "exhausted",
|
|
17
|
+
Missing = "missing"
|
|
18
|
+
}
|
|
13
19
|
//# sourceMappingURL=consts.d.ts.map
|
package/build/consts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,oBAAoB,CAAA;AAChD,eAAO,MAAM,mBAAmB,6BAA6B,CAAA;AAC7D,eAAO,MAAM,oBAAoB,sBAAsB,CAAA;AAEvD,eAAO,MAAM,wBAAwB;;;;;;KAGnC,CAAA;AAEF,gFAAgF;AAChF,OAAO,EACL,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAC1E,uBAAuB,GACxB,MAAM,oBAAoB,CAAA"}
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,oBAAoB,CAAA;AAChD,eAAO,MAAM,mBAAmB,6BAA6B,CAAA;AAC7D,eAAO,MAAM,oBAAoB,sBAAsB,CAAA;AAEvD,eAAO,MAAM,wBAAwB;;;;;;KAGnC,CAAA;AAEF,gFAAgF;AAChF,OAAO,EACL,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAC1E,uBAAuB,GACxB,MAAM,oBAAoB,CAAA;AAE3B,oBAAY,mBAAmB;IAC7B,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,SAAS,cAAc;IACvB,OAAO,YAAY;CACpB"}
|
package/build/consts.js
CHANGED
|
@@ -7,4 +7,11 @@ export const OTP_EMAIL_THROTTLE_RULES = Object.freeze([
|
|
|
7
7
|
]);
|
|
8
8
|
/** The OTP auth plugin type key — passed as credential.type in init requests */
|
|
9
9
|
export { OTP_AUTH_TYPE, OTP_SERVICE, OTP_RESOURCE, OTP_TTL_SECONDS, OTP_CODE_LENGTH, OTP_MAX_FAILED_ATTEMPTS, } from '@owlmeans/auth-otp';
|
|
10
|
+
export var OtpChallengeOutcome;
|
|
11
|
+
(function (OtpChallengeOutcome) {
|
|
12
|
+
OtpChallengeOutcome["Verified"] = "verified";
|
|
13
|
+
OtpChallengeOutcome["Invalid"] = "invalid";
|
|
14
|
+
OtpChallengeOutcome["Exhausted"] = "exhausted";
|
|
15
|
+
OtpChallengeOutcome["Missing"] = "missing";
|
|
16
|
+
})(OtpChallengeOutcome || (OtpChallengeOutcome = {}));
|
|
10
17
|
//# sourceMappingURL=consts.js.map
|
package/build/consts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAA;AAChD,MAAM,CAAC,MAAM,mBAAmB,GAAG,0BAA0B,CAAA;AAC7D,MAAM,CAAC,MAAM,oBAAoB,GAAG,mBAAmB,CAAA;AAEvD,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC,MAAM,CAAC;IACpD,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC;IAC9C,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC;CACrD,CAAC,CAAA;AAEF,gFAAgF;AAChF,OAAO,EACL,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAC1E,uBAAuB,GACxB,MAAM,oBAAoB,CAAA"}
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAA;AAChD,MAAM,CAAC,MAAM,mBAAmB,GAAG,0BAA0B,CAAA;AAC7D,MAAM,CAAC,MAAM,oBAAoB,GAAG,mBAAmB,CAAA;AAEvD,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC,MAAM,CAAC;IACpD,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC;IAC9C,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC;CACrD,CAAC,CAAA;AAEF,gFAAgF;AAChF,OAAO,EACL,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAC1E,uBAAuB,GACxB,MAAM,oBAAoB,CAAA;AAE3B,MAAM,CAAN,IAAY,mBAKX;AALD,WAAY,mBAAmB;IAC7B,4CAAqB,CAAA;IACrB,0CAAmB,CAAA;IACnB,8CAAuB,CAAA;IACvB,0CAAmB,CAAA;AACrB,CAAC,EALW,mBAAmB,KAAnB,mBAAmB,QAK9B"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const REDIS_VERIFY_SCRIPT = "\nlocal raw = redis.call('GET', KEYS[1])\nif not raw then return 0 end\nlocal record = cjson.decode(raw)\nif record.emailKey == ARGV[1] and record.codeHash == ARGV[2] then\n redis.call('DEL', KEYS[1])\n return 1\nend\nrecord.failedAttempts = tonumber(record.failedAttempts or 0) + 1\nif record.failedAttempts >= tonumber(ARGV[3]) then\n redis.call('DEL', KEYS[1])\n return -2\nend\nlocal ttl = redis.call('PTTL', KEYS[1])\nif ttl <= 0 then\n redis.call('DEL', KEYS[1])\n return 0\nend\nredis.call('SET', KEYS[1], cjson.encode(record), 'XX', 'PX', ttl)\nreturn -1\n";
|
|
2
|
+
export declare const CHALLENGE_DELIMITER = "::";
|
|
3
|
+
export declare const REDIS_THROTTLE_SCRIPT = "\nlocal time = redis.call('TIME')\nlocal now = tonumber(time[1]) * 1000 + math.floor(tonumber(time[2]) / 1000)\nlocal retry = 0\nfor index = 1, #KEYS do\n local offset = (index - 1) * 2\n local limit = tonumber(ARGV[offset + 1])\n local window = tonumber(ARGV[offset + 2])\n redis.call('ZREMRANGEBYSCORE', KEYS[index], '-inf', now - window)\n local count = redis.call('ZCARD', KEYS[index])\n if count >= limit then\n local oldest = redis.call('ZRANGE', KEYS[index], 0, 0, 'WITHSCORES')\n if #oldest == 2 then\n retry = math.max(retry, math.ceil((tonumber(oldest[2]) + window - now) / 1000))\n end\n end\nend\nif retry > 0 then return {0, retry} end\nlocal token = ARGV[#KEYS * 2 + 1]\nfor index = 1, #KEYS do\n local window = tonumber(ARGV[(index - 1) * 2 + 2])\n redis.call('ZADD', KEYS[index], now, token .. ':' .. index)\n redis.call('PEXPIRE', KEYS[index], window)\nend\nreturn {1, 0}\n";
|
|
4
|
+
//# sourceMappingURL=consts.local.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.local.d.ts","sourceRoot":"","sources":["../src/consts.local.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,+jBAoB/B,CAAA;AAGD,eAAO,MAAM,mBAAmB,OAAO,CAAA;AAEvC,eAAO,MAAM,qBAAqB,u5BAyBjC,CAAA"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export const REDIS_VERIFY_SCRIPT = `
|
|
2
|
+
local raw = redis.call('GET', KEYS[1])
|
|
3
|
+
if not raw then return 0 end
|
|
4
|
+
local record = cjson.decode(raw)
|
|
5
|
+
if record.emailKey == ARGV[1] and record.codeHash == ARGV[2] then
|
|
6
|
+
redis.call('DEL', KEYS[1])
|
|
7
|
+
return 1
|
|
8
|
+
end
|
|
9
|
+
record.failedAttempts = tonumber(record.failedAttempts or 0) + 1
|
|
10
|
+
if record.failedAttempts >= tonumber(ARGV[3]) then
|
|
11
|
+
redis.call('DEL', KEYS[1])
|
|
12
|
+
return -2
|
|
13
|
+
end
|
|
14
|
+
local ttl = redis.call('PTTL', KEYS[1])
|
|
15
|
+
if ttl <= 0 then
|
|
16
|
+
redis.call('DEL', KEYS[1])
|
|
17
|
+
return 0
|
|
18
|
+
end
|
|
19
|
+
redis.call('SET', KEYS[1], cjson.encode(record), 'XX', 'PX', ttl)
|
|
20
|
+
return -1
|
|
21
|
+
`;
|
|
22
|
+
// Separates the email from the opaque issuance id inside the signed challenge.
|
|
23
|
+
export const CHALLENGE_DELIMITER = '::';
|
|
24
|
+
export const REDIS_THROTTLE_SCRIPT = `
|
|
25
|
+
local time = redis.call('TIME')
|
|
26
|
+
local now = tonumber(time[1]) * 1000 + math.floor(tonumber(time[2]) / 1000)
|
|
27
|
+
local retry = 0
|
|
28
|
+
for index = 1, #KEYS do
|
|
29
|
+
local offset = (index - 1) * 2
|
|
30
|
+
local limit = tonumber(ARGV[offset + 1])
|
|
31
|
+
local window = tonumber(ARGV[offset + 2])
|
|
32
|
+
redis.call('ZREMRANGEBYSCORE', KEYS[index], '-inf', now - window)
|
|
33
|
+
local count = redis.call('ZCARD', KEYS[index])
|
|
34
|
+
if count >= limit then
|
|
35
|
+
local oldest = redis.call('ZRANGE', KEYS[index], 0, 0, 'WITHSCORES')
|
|
36
|
+
if #oldest == 2 then
|
|
37
|
+
retry = math.max(retry, math.ceil((tonumber(oldest[2]) + window - now) / 1000))
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
if retry > 0 then return {0, retry} end
|
|
42
|
+
local token = ARGV[#KEYS * 2 + 1]
|
|
43
|
+
for index = 1, #KEYS do
|
|
44
|
+
local window = tonumber(ARGV[(index - 1) * 2 + 2])
|
|
45
|
+
redis.call('ZADD', KEYS[index], now, token .. ':' .. index)
|
|
46
|
+
redis.call('PEXPIRE', KEYS[index], window)
|
|
47
|
+
end
|
|
48
|
+
return {1, 0}
|
|
49
|
+
`;
|
|
50
|
+
//# sourceMappingURL=consts.local.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.local.js","sourceRoot":"","sources":["../src/consts.local.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;CAoBlC,CAAA;AAED,+EAA+E;AAC/E,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,CAAA;AAEvC,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;CAyBpC,CAAA"}
|
package/build/index.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
|
+
export type * from './keys/types.js';
|
|
3
|
+
export type * from './throttle-keys/types.js';
|
|
2
4
|
export * from './consts.js';
|
|
3
5
|
export * from './service.js';
|
|
4
6
|
export * from './plugin.js';
|
|
5
7
|
export * from './errors.js';
|
|
6
8
|
export * from './throttle.js';
|
|
7
9
|
export * from './challenge.js';
|
|
10
|
+
export * from './keys.js';
|
|
11
|
+
export * from './throttle-keys.js';
|
|
8
12
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,mBAAmB,iBAAiB,CAAA;AACpC,mBAAmB,0BAA0B,CAAA;AAC7C,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,WAAW,CAAA;AACzB,cAAc,oBAAoB,CAAA"}
|
package/build/index.js
CHANGED
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAG1B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,WAAW,CAAA;AACzB,cAAc,oBAAoB,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The storage keys of one OTP issuance: digests, never the raw address, id or code. */
|
|
2
|
+
export interface OtpKeyHelper {
|
|
3
|
+
/** The challenge record key of an issuance id. */
|
|
4
|
+
otpChallengeKey: (issuanceId: string) => string;
|
|
5
|
+
/** The case-insensitive digest of the address a code was sent to. */
|
|
6
|
+
otpEmailKey: (email: string) => string;
|
|
7
|
+
/** The digest of a code, bound to its issuance. */
|
|
8
|
+
otpCodeHash: (issuanceId: string, code: string) => string;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/keys/types.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,MAAM,WAAW,YAAY;IAC3B,kDAAkD;IAClD,eAAe,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAA;IAC/C,qEAAqE;IACrE,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC,mDAAmD;IACnD,WAAW,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;CAC1D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/keys/types.ts"],"names":[],"mappings":""}
|
package/build/keys.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAKnD,eAAO,MAAM,kBAAkB,QAAO,YAOrC,CAAA;AAED,eAAO,MAAM,YAAY,cAAuB,CAAA"}
|
package/build/keys.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
const digest = (scope, value) => createHash('sha256').update(`owlmeans:otp:${scope}\0${value}`).digest('hex');
|
|
3
|
+
export const createOtpKeyHelper = () => {
|
|
4
|
+
const otpChallengeKey = (issuanceId) => `challenge:${digest('issuance', issuanceId)}`;
|
|
5
|
+
const otpEmailKey = (email) => digest('email', email.trim().toLowerCase());
|
|
6
|
+
const otpCodeHash = (issuanceId, code) => digest('code', `${issuanceId}\0${code.trim()}`);
|
|
7
|
+
return { otpChallengeKey, otpEmailKey, otpCodeHash };
|
|
8
|
+
};
|
|
9
|
+
export const otpKeyHelper = createOtpKeyHelper();
|
|
10
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGxC,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,KAAa,EAAU,EAAE,CACtD,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,gBAAgB,KAAK,KAAK,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAE9E,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAiB,EAAE;IACnD,MAAM,eAAe,GAAG,CAAC,UAAkB,EAAU,EAAE,CAAC,aAAa,MAAM,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,CAAA;IACrG,MAAM,WAAW,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAA;IAC1F,MAAM,WAAW,GAAG,CAAC,UAAkB,EAAE,IAAY,EAAU,EAAE,CAC/D,MAAM,CAAC,MAAM,EAAE,GAAG,UAAU,KAAK,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IAEjD,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,CAAA;AACtD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,kBAAkB,EAAE,CAAA"}
|
package/build/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAmEvD,sGAAsG;AACtG,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,KAAG,CAG1F,CAAA"}
|
package/build/plugin.js
CHANGED
|
@@ -2,8 +2,7 @@ import { ALL_SCOPES, AuthenFailed, AuthRole, AuthenticationType } from '@owlmean
|
|
|
2
2
|
import { AuthChallengeReplayPolicy, registerPlugin } from '@owlmeans/server-auth/manager/plugins';
|
|
3
3
|
import { OTP_AUTH_TYPE, OTP_SERVICE } from '@owlmeans/auth-otp';
|
|
4
4
|
import { AUTH_IDENTITY_LINKING } from '@owlmeans/server-auth-identity';
|
|
5
|
-
|
|
6
|
-
const CHALLENGE_DELIMITER = '::';
|
|
5
|
+
import { CHALLENGE_DELIMITER } from './consts.local.js';
|
|
7
6
|
/** Factory that creates the email-OTP AuthPlugin bound to the given context. */
|
|
8
7
|
const otpPlugin = (context) => ({
|
|
9
8
|
type: OTP_AUTH_TYPE,
|
package/build/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/build/service.d.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import type
|
|
2
|
-
export declare const otpChallengeKey: (issuanceId: string) => string;
|
|
3
|
-
export declare const otpEmailKey: (email: string) => string;
|
|
4
|
-
export declare const otpCodeHash: (issuanceId: string, code: string) => string;
|
|
1
|
+
import { type OtpService } from '@owlmeans/auth-otp';
|
|
5
2
|
export declare const makeOtpService: (alias?: string) => OtpService;
|
|
6
3
|
//# sourceMappingURL=service.d.ts.map
|
package/build/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,UAAU,EAAwF,MAAM,oBAAoB,CAAA;AAqB1I,eAAO,MAAM,cAAc,sBAA0B,UA0EpD,CAAA"}
|
package/build/service.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { appendContextual } from '@owlmeans/context';
|
|
2
2
|
import { AuthenFailed } from '@owlmeans/auth';
|
|
3
|
-
import { OTP_SERVICE, OTP_TTL_SECONDS, OTP_CODE_LENGTH, OTP_MAX_FAILED_ATTEMPTS, OTP_RESOURCE
|
|
3
|
+
import { OTP_SERVICE, OTP_TTL_SECONDS, OTP_CODE_LENGTH, OTP_MAX_FAILED_ATTEMPTS, OTP_RESOURCE } from '@owlmeans/auth-otp';
|
|
4
4
|
import { MAILER_SERVICE } from '@owlmeans/mailer';
|
|
5
|
-
import {
|
|
6
|
-
import { OtpChallengeOutcome } from './
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { randomBytes, randomInt } from 'node:crypto';
|
|
6
|
+
import { OtpChallengeOutcome, OTP_CHALLENGE_STORE } from './consts.js';
|
|
7
|
+
import { throttleKeyHelper } from './throttle-keys.js';
|
|
8
|
+
import { otpKeyHelper } from './keys.js';
|
|
9
9
|
import { OtpThrottled, OtpUnavailable } from './errors.js';
|
|
10
10
|
import { makeRedisOtpChallengeStore } from './challenge.js';
|
|
11
11
|
import { logger, logThrottle } from '@owlmeans/log';
|
|
12
12
|
const log = logger('server-auth-otp');
|
|
13
13
|
/** An e-mail as a log may carry it: its domain only. */
|
|
14
14
|
const domainOf = (email) => email.slice(email.lastIndexOf('@') + 1);
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
const generateCode = () => {
|
|
16
|
+
const digits = randomInt(0, Math.pow(10, OTP_CODE_LENGTH));
|
|
17
|
+
return String(digits).padStart(OTP_CODE_LENGTH, '0');
|
|
18
|
+
};
|
|
19
19
|
export const makeOtpService = (alias = OTP_SERVICE) => {
|
|
20
20
|
let fallbackChallenges;
|
|
21
21
|
const challengeStore = (ctx) => {
|
|
@@ -42,9 +42,9 @@ export const makeOtpService = (alias = OTP_SERVICE) => {
|
|
|
42
42
|
const throttleRules = ctx.cfg.otp?.throttleRules;
|
|
43
43
|
if (throttleAlias != null && throttleRules != null) {
|
|
44
44
|
const decision = await ctx.service(throttleAlias)
|
|
45
|
-
.consume(emailThrottleKey(email), throttleRules, issuanceId);
|
|
45
|
+
.consume(throttleKeyHelper.emailThrottleKey(email), throttleRules, issuanceId);
|
|
46
46
|
if (!decision.allowed) {
|
|
47
|
-
if (logThrottle(`otp-throttled:${otpEmailKey(email)}`, 60_000)) {
|
|
47
|
+
if (logThrottle(`otp-throttled:${otpKeyHelper.otpEmailKey(email)}`, 60_000)) {
|
|
48
48
|
log.warn('Login code refused: throttled', {
|
|
49
49
|
reason: 'otp-throttled', emailDomain: domainOf(email), retryAfter: decision.retryAfter,
|
|
50
50
|
}, { event: 'auth.refused' });
|
|
@@ -53,9 +53,9 @@ export const makeOtpService = (alias = OTP_SERVICE) => {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
const issued = await challenges.issue({
|
|
56
|
-
id: otpChallengeKey(issuanceId),
|
|
57
|
-
emailKey: otpEmailKey(email),
|
|
58
|
-
codeHash: otpCodeHash(issuanceId, code),
|
|
56
|
+
id: otpKeyHelper.otpChallengeKey(issuanceId),
|
|
57
|
+
emailKey: otpKeyHelper.otpEmailKey(email),
|
|
58
|
+
codeHash: otpKeyHelper.otpCodeHash(issuanceId, code),
|
|
59
59
|
}, OTP_TTL_SECONDS);
|
|
60
60
|
if (!issued)
|
|
61
61
|
throw new OtpUnavailable('issuance-collision');
|
|
@@ -69,7 +69,7 @@ export const makeOtpService = (alias = OTP_SERVICE) => {
|
|
|
69
69
|
},
|
|
70
70
|
verifyChallenge: async (email, issuanceId, code) => {
|
|
71
71
|
const ctx = service.ctx;
|
|
72
|
-
const outcome = await challengeStore(ctx).verify(otpChallengeKey(issuanceId), otpEmailKey(email), otpCodeHash(issuanceId, code), OTP_MAX_FAILED_ATTEMPTS);
|
|
72
|
+
const outcome = await challengeStore(ctx).verify(otpKeyHelper.otpChallengeKey(issuanceId), otpKeyHelper.otpEmailKey(email), otpKeyHelper.otpCodeHash(issuanceId, code), OTP_MAX_FAILED_ATTEMPTS);
|
|
73
73
|
if (outcome !== OtpChallengeOutcome.Verified) {
|
|
74
74
|
// The sign-in refusal itself (`auth.refused`) is logged where the manager decides sign-in.
|
|
75
75
|
log.debug('OTP challenge refused', { outcome, emailDomain: domainOf(email) });
|
|
@@ -79,8 +79,4 @@ export const makeOtpService = (alias = OTP_SERVICE) => {
|
|
|
79
79
|
});
|
|
80
80
|
return service;
|
|
81
81
|
};
|
|
82
|
-
const generateCode = () => {
|
|
83
|
-
const digits = randomInt(0, Math.pow(10, OTP_CODE_LENGTH));
|
|
84
|
-
return String(digits).padStart(OTP_CODE_LENGTH, '0');
|
|
85
|
-
};
|
|
86
82
|
//# sourceMappingURL=service.js.map
|
package/build/service.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Throttle subjects as non-raw keys: digests of what a request is throttled by. */
|
|
2
|
+
export interface ThrottleKeyHelper {
|
|
3
|
+
/** Collision-resistant, non-raw key for case-insensitive email throttles. */
|
|
4
|
+
emailThrottleKey: (email: string) => string;
|
|
5
|
+
/** Non-raw key for request-source throttles. Callers remain responsible for trusted-proxy parsing. */
|
|
6
|
+
ipThrottleKey: (ip: string) => string;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/throttle-keys/types.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,MAAM,WAAW,iBAAiB;IAChC,6EAA6E;IAC7E,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAC3C,sGAAsG;IACtG,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAA;CACtC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/throttle-keys/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throttle-keys.d.ts","sourceRoot":"","sources":["../src/throttle-keys.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAKjE,eAAO,MAAM,uBAAuB,QAAO,iBAQ1C,CAAA;AAED,eAAO,MAAM,iBAAiB,mBAA4B,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
const digestKey = (kind, value) => createHash('sha256').update(`owlmeans:throttle:${kind}\0${value}`).digest('hex');
|
|
3
|
+
export const createThrottleKeyHelper = () => {
|
|
4
|
+
const emailThrottleKey = (email) => `email:${digestKey('email', email.trim().toLowerCase())}`;
|
|
5
|
+
const ipThrottleKey = (ip) => `ip:${digestKey('ip', ip.trim().toLowerCase())}`;
|
|
6
|
+
return { emailThrottleKey, ipThrottleKey };
|
|
7
|
+
};
|
|
8
|
+
export const throttleKeyHelper = createThrottleKeyHelper();
|
|
9
|
+
//# sourceMappingURL=throttle-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throttle-keys.js","sourceRoot":"","sources":["../src/throttle-keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGxC,MAAM,SAAS,GAAG,CAAC,IAAY,EAAE,KAAa,EAAU,EAAE,CACxD,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,qBAAqB,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAElF,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAsB,EAAE;IAC7D,MAAM,gBAAgB,GAAG,CAAC,KAAa,EAAU,EAAE,CACjD,SAAS,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,CAAA;IAE3D,MAAM,aAAa,GAAG,CAAC,EAAU,EAAU,EAAE,CAC3C,MAAM,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,CAAA;IAElD,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,CAAA;AAC5C,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG,uBAAuB,EAAE,CAAA"}
|
package/build/throttle.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import type { AuthThrottleService } from './types.js';
|
|
2
|
-
type Clock
|
|
3
|
-
/** Collision-resistant, non-raw key for case-insensitive email throttles. */
|
|
4
|
-
export declare const emailThrottleKey: (email: string) => string;
|
|
5
|
-
/** Non-raw key for request-source throttles. Callers remain responsible for trusted-proxy parsing. */
|
|
6
|
-
export declare const ipThrottleKey: (ip: string) => string;
|
|
2
|
+
import type { Clock } from './types.local.js';
|
|
7
3
|
/** Real in-memory implementation for single-process development and deterministic unit tests. */
|
|
8
4
|
export declare const makeMemoryThrottleService: (alias?: string, now?: Clock) => AuthThrottleService;
|
|
9
5
|
/** Redis implementation: every configured window is checked and consumed in one Lua operation. */
|
|
10
6
|
export declare const makeRedisThrottleService: (alias?: string, resourceAlias?: string) => AuthThrottleService;
|
|
11
|
-
|
|
7
|
+
/** @deprecated compat:factory-refactor — use `throttleKeyHelper.emailThrottleKey(…)` */
|
|
8
|
+
export declare const emailThrottleKey: (email: string) => string;
|
|
9
|
+
/** @deprecated compat:factory-refactor — use `throttleKeyHelper.ipThrottleKey(…)` */
|
|
10
|
+
export declare const ipThrottleKey: (ip: string) => string;
|
|
12
11
|
//# sourceMappingURL=throttle.d.ts.map
|
package/build/throttle.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../src/throttle.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../src/throttle.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAyD,MAAM,YAAY,CAAA;AAG5G,OAAO,KAAK,EAAE,KAAK,EAA0B,MAAM,kBAAkB,CAAA;AAUrE,iGAAiG;AACjG,eAAO,MAAM,yBAAyB,yBACD,KAAK,KACvC,mBAyBF,CAAA;AAED,kGAAkG;AAClG,eAAO,MAAM,wBAAwB,8CAElC,mBA4BF,CAAA;AAED,wFAAwF;AACxF,eAAO,MAAM,gBAAgB,UAAW,MAAM,KAAG,MAAmD,CAAA;AAEpG,qFAAqF;AACrF,eAAO,MAAM,aAAa,OAAQ,MAAM,KAAG,MAA6C,CAAA"}
|
package/build/throttle.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createService } from '@owlmeans/context';
|
|
3
|
-
import { appendContextual } from '@owlmeans/context';
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { createService, appendContextual } from '@owlmeans/context';
|
|
4
3
|
import { OtpUnavailable } from './errors.js';
|
|
5
4
|
import { OTP_RESOURCE, OTP_THROTTLE_SERVICE } from './consts.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export const emailThrottleKey = (email) => `email:${digestKey('email', email.trim().toLowerCase())}`;
|
|
9
|
-
/** Non-raw key for request-source throttles. Callers remain responsible for trusted-proxy parsing. */
|
|
10
|
-
export const ipThrottleKey = (ip) => `ip:${digestKey('ip', ip.trim().toLowerCase())}`;
|
|
5
|
+
import { REDIS_THROTTLE_SCRIPT } from './consts.local.js';
|
|
6
|
+
import { throttleKeyHelper } from './throttle-keys.js';
|
|
11
7
|
const assertRules = (rules) => {
|
|
12
8
|
if (rules.length === 0 || rules.some(rule => !Number.isInteger(rule.limit) || rule.limit < 1
|
|
13
9
|
|| !Number.isInteger(rule.windowSeconds) || rule.windowSeconds < 1)) {
|
|
@@ -39,32 +35,6 @@ export const makeMemoryThrottleService = (alias = OTP_THROTTLE_SERVICE, now = Da
|
|
|
39
35
|
},
|
|
40
36
|
});
|
|
41
37
|
};
|
|
42
|
-
const REDIS_THROTTLE_SCRIPT = `
|
|
43
|
-
local time = redis.call('TIME')
|
|
44
|
-
local now = tonumber(time[1]) * 1000 + math.floor(tonumber(time[2]) / 1000)
|
|
45
|
-
local retry = 0
|
|
46
|
-
for index = 1, #KEYS do
|
|
47
|
-
local offset = (index - 1) * 2
|
|
48
|
-
local limit = tonumber(ARGV[offset + 1])
|
|
49
|
-
local window = tonumber(ARGV[offset + 2])
|
|
50
|
-
redis.call('ZREMRANGEBYSCORE', KEYS[index], '-inf', now - window)
|
|
51
|
-
local count = redis.call('ZCARD', KEYS[index])
|
|
52
|
-
if count >= limit then
|
|
53
|
-
local oldest = redis.call('ZRANGE', KEYS[index], 0, 0, 'WITHSCORES')
|
|
54
|
-
if #oldest == 2 then
|
|
55
|
-
retry = math.max(retry, math.ceil((tonumber(oldest[2]) + window - now) / 1000))
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
if retry > 0 then return {0, retry} end
|
|
60
|
-
local token = ARGV[#KEYS * 2 + 1]
|
|
61
|
-
for index = 1, #KEYS do
|
|
62
|
-
local window = tonumber(ARGV[(index - 1) * 2 + 2])
|
|
63
|
-
redis.call('ZADD', KEYS[index], now, token .. ':' .. index)
|
|
64
|
-
redis.call('PEXPIRE', KEYS[index], window)
|
|
65
|
-
end
|
|
66
|
-
return {1, 0}
|
|
67
|
-
`;
|
|
68
38
|
/** Redis implementation: every configured window is checked and consumed in one Lua operation. */
|
|
69
39
|
export const makeRedisThrottleService = (alias = OTP_THROTTLE_SERVICE, resourceAlias = OTP_RESOURCE) => {
|
|
70
40
|
const service = appendContextual(alias, {
|
|
@@ -90,4 +60,8 @@ export const makeRedisThrottleService = (alias = OTP_THROTTLE_SERVICE, resourceA
|
|
|
90
60
|
});
|
|
91
61
|
return service;
|
|
92
62
|
};
|
|
63
|
+
/** @deprecated compat:factory-refactor — use `throttleKeyHelper.emailThrottleKey(…)` */
|
|
64
|
+
export const emailThrottleKey = (email) => throttleKeyHelper.emailThrottleKey(email);
|
|
65
|
+
/** @deprecated compat:factory-refactor — use `throttleKeyHelper.ipThrottleKey(…)` */
|
|
66
|
+
export const ipThrottleKey = (ip) => throttleKeyHelper.ipThrottleKey(ip);
|
|
93
67
|
//# sourceMappingURL=throttle.js.map
|
package/build/throttle.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"throttle.js","sourceRoot":"","sources":["../src/throttle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"throttle.js","sourceRoot":"","sources":["../src/throttle.ts"],"names":[],"mappings":"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"}
|