@owlmeans/server-auth-otp 0.1.18-rc.50 → 0.1.18-rc.51
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/service.d.ts.map +1 -1
- package/build/service.js +13 -1
- package/build/service.js.map +1 -1
- package/package.json +9 -8
- package/src/service.ts +16 -1
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.51
|
|
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.47
|
|
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.51",
|
|
5
|
+
"generatedAt": "2026-10-03T20:05:07.593Z",
|
|
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.51"` 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/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAwBpD,eAAO,MAAM,eAAe,eAAgB,MAAM,KAAG,MAAuD,CAAA;AAC5G,eAAO,MAAM,WAAW,UAAW,MAAM,KAAG,MAAqD,CAAA;AACjG,eAAO,MAAM,WAAW,eAAgB,MAAM,QAAQ,MAAM,KAAG,MACd,CAAA;AAEjD,eAAO,MAAM,cAAc,sBAA0B,UA0EpD,CAAA"}
|
package/build/service.js
CHANGED
|
@@ -8,6 +8,10 @@ import { OTP_CHALLENGE_STORE } from './consts.js';
|
|
|
8
8
|
import { emailThrottleKey } from './throttle.js';
|
|
9
9
|
import { OtpThrottled, OtpUnavailable } from './errors.js';
|
|
10
10
|
import { makeRedisOtpChallengeStore } from './challenge.js';
|
|
11
|
+
import { logger, logThrottle } from '@owlmeans/log';
|
|
12
|
+
const log = logger('server-auth-otp');
|
|
13
|
+
/** An e-mail as a log may carry it: its domain only. */
|
|
14
|
+
const domainOf = (email) => email.slice(email.lastIndexOf('@') + 1);
|
|
11
15
|
const digest = (scope, value) => createHash('sha256').update(`owlmeans:otp:${scope}\0${value}`).digest('hex');
|
|
12
16
|
export const otpChallengeKey = (issuanceId) => `challenge:${digest('issuance', issuanceId)}`;
|
|
13
17
|
export const otpEmailKey = (email) => digest('email', email.trim().toLowerCase());
|
|
@@ -39,8 +43,14 @@ export const makeOtpService = (alias = OTP_SERVICE) => {
|
|
|
39
43
|
if (throttleAlias != null && throttleRules != null) {
|
|
40
44
|
const decision = await ctx.service(throttleAlias)
|
|
41
45
|
.consume(emailThrottleKey(email), throttleRules, issuanceId);
|
|
42
|
-
if (!decision.allowed)
|
|
46
|
+
if (!decision.allowed) {
|
|
47
|
+
if (logThrottle(`otp-throttled:${otpEmailKey(email)}`, 60_000)) {
|
|
48
|
+
log.warn('Login code refused: throttled', {
|
|
49
|
+
reason: 'otp-throttled', emailDomain: domainOf(email), retryAfter: decision.retryAfter,
|
|
50
|
+
}, { event: 'auth.refused' });
|
|
51
|
+
}
|
|
43
52
|
throw new OtpThrottled(decision.retryAfter);
|
|
53
|
+
}
|
|
44
54
|
}
|
|
45
55
|
const issued = await challenges.issue({
|
|
46
56
|
id: otpChallengeKey(issuanceId),
|
|
@@ -61,6 +71,8 @@ export const makeOtpService = (alias = OTP_SERVICE) => {
|
|
|
61
71
|
const ctx = service.ctx;
|
|
62
72
|
const outcome = await challengeStore(ctx).verify(otpChallengeKey(issuanceId), otpEmailKey(email), otpCodeHash(issuanceId, code), OTP_MAX_FAILED_ATTEMPTS);
|
|
63
73
|
if (outcome !== OtpChallengeOutcome.Verified) {
|
|
74
|
+
// The sign-in refusal itself (`auth.refused`) is logged where the manager decides sign-in.
|
|
75
|
+
log.debug('OTP challenge refused', { outcome, emailDomain: domainOf(email) });
|
|
64
76
|
throw new AuthenFailed('otp:code');
|
|
65
77
|
}
|
|
66
78
|
},
|
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;AAE7C,OAAO,EACL,WAAW,EAAE,eAAe,EAAE,eAAe,EAAE,uBAAuB,EACtE,YAAY,GACb,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEhE,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC1D,OAAO,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/server-auth-otp",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.51",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -26,13 +26,14 @@
|
|
|
26
26
|
"@owlmeans/auth-otp": "^0.1.18-rc.32",
|
|
27
27
|
"@owlmeans/context": "^0.1.18-rc.32",
|
|
28
28
|
"@owlmeans/error": "^0.1.18-rc.36",
|
|
29
|
-
"@owlmeans/
|
|
30
|
-
"@owlmeans/
|
|
31
|
-
"@owlmeans/
|
|
32
|
-
"@owlmeans/resource": "^0.1.18-rc.
|
|
33
|
-
"@owlmeans/
|
|
34
|
-
"@owlmeans/server-auth
|
|
35
|
-
"@owlmeans/server-
|
|
29
|
+
"@owlmeans/log": "^0.1.18-rc.0",
|
|
30
|
+
"@owlmeans/mailer": "^0.1.18-rc.34",
|
|
31
|
+
"@owlmeans/oidc": "^0.1.18-rc.53",
|
|
32
|
+
"@owlmeans/redis-resource": "^0.1.18-rc.42",
|
|
33
|
+
"@owlmeans/resource": "^0.1.18-rc.38",
|
|
34
|
+
"@owlmeans/server-auth": "^0.1.18-rc.52",
|
|
35
|
+
"@owlmeans/server-auth-identity": "^0.1.18-rc.45",
|
|
36
|
+
"@owlmeans/server-context": "^0.1.18-rc.41"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
39
|
"@owlmeans/dep-config": "workspace:*",
|
package/src/service.ts
CHANGED
|
@@ -14,6 +14,12 @@ import { OTP_CHALLENGE_STORE } from './consts.js'
|
|
|
14
14
|
import { emailThrottleKey } from './throttle.js'
|
|
15
15
|
import { OtpThrottled, OtpUnavailable } from './errors.js'
|
|
16
16
|
import { makeRedisOtpChallengeStore } from './challenge.js'
|
|
17
|
+
import { logger, logThrottle } from '@owlmeans/log'
|
|
18
|
+
|
|
19
|
+
const log = logger('server-auth-otp')
|
|
20
|
+
|
|
21
|
+
/** An e-mail as a log may carry it: its domain only. */
|
|
22
|
+
const domainOf = (email: string): string => email.slice(email.lastIndexOf('@') + 1)
|
|
17
23
|
|
|
18
24
|
const digest = (scope: string, value: string): string =>
|
|
19
25
|
createHash('sha256').update(`owlmeans:otp:${scope}\0${value}`).digest('hex')
|
|
@@ -55,7 +61,14 @@ export const makeOtpService = (alias = OTP_SERVICE): OtpService => {
|
|
|
55
61
|
if (throttleAlias != null && throttleRules != null) {
|
|
56
62
|
const decision = await ctx.service<AuthThrottleService>(throttleAlias)
|
|
57
63
|
.consume(emailThrottleKey(email), throttleRules, issuanceId)
|
|
58
|
-
if (!decision.allowed)
|
|
64
|
+
if (!decision.allowed) {
|
|
65
|
+
if (logThrottle(`otp-throttled:${otpEmailKey(email)}`, 60_000)) {
|
|
66
|
+
log.warn('Login code refused: throttled', {
|
|
67
|
+
reason: 'otp-throttled', emailDomain: domainOf(email), retryAfter: decision.retryAfter,
|
|
68
|
+
}, { event: 'auth.refused' })
|
|
69
|
+
}
|
|
70
|
+
throw new OtpThrottled(decision.retryAfter)
|
|
71
|
+
}
|
|
59
72
|
}
|
|
60
73
|
|
|
61
74
|
const issued = await challenges.issue({
|
|
@@ -82,6 +95,8 @@ export const makeOtpService = (alias = OTP_SERVICE): OtpService => {
|
|
|
82
95
|
OTP_MAX_FAILED_ATTEMPTS
|
|
83
96
|
)
|
|
84
97
|
if (outcome !== OtpChallengeOutcome.Verified) {
|
|
98
|
+
// The sign-in refusal itself (`auth.refused`) is logged where the manager decides sign-in.
|
|
99
|
+
log.debug('OTP challenge refused', { outcome, emailDomain: domainOf(email) })
|
|
85
100
|
throw new AuthenFailed('otp:code')
|
|
86
101
|
}
|
|
87
102
|
},
|