vona-module-captcha-simple 5.1.14 → 5.1.15
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.
|
@@ -4,6 +4,12 @@ import { BeanBase } from 'vona';
|
|
|
4
4
|
export type TypeCaptchaProviderImageTextToken = string;
|
|
5
5
|
export type TypeCaptchaProviderImageTextPayload = string;
|
|
6
6
|
export type TypeCaptchaProviderImageTextData = ICaptchaProviderData<TypeCaptchaProviderImageTextToken, TypeCaptchaProviderImageTextPayload>;
|
|
7
|
+
export interface ICaptchaImageTextState {
|
|
8
|
+
token?: string;
|
|
9
|
+
token2?: string;
|
|
10
|
+
revision: string;
|
|
11
|
+
ttlSecondary: number;
|
|
12
|
+
}
|
|
7
13
|
export type TypeCaptchaProviderImageTextType = 'char' | 'math';
|
|
8
14
|
export interface ICaptchaProviderOptionsImageText extends IDecoratorCaptchaProviderOptions {
|
|
9
15
|
type?: TypeCaptchaProviderImageTextType;
|
|
@@ -11,8 +17,12 @@ export interface ICaptchaProviderOptionsImageText extends IDecoratorCaptchaProvi
|
|
|
11
17
|
opts: ConfigObject;
|
|
12
18
|
}
|
|
13
19
|
export declare class CaptchaProviderImageText extends BeanBase implements ICaptchaProviderExecute<TypeCaptchaProviderImageTextToken, TypeCaptchaProviderImageTextPayload> {
|
|
14
|
-
create(options: ICaptchaProviderOptionsImageText): Promise<TypeCaptchaProviderImageTextData>;
|
|
15
|
-
verify(token:
|
|
20
|
+
create(options: ICaptchaProviderOptionsImageText, _payload?: unknown, previousToken?: string): Promise<TypeCaptchaProviderImageTextData>;
|
|
21
|
+
verify(token: string, tokenInput: unknown, _options: ICaptchaProviderOptionsImageText): Promise<boolean>;
|
|
22
|
+
verifyImmediate(token: string, tokenInput: unknown, _options: ICaptchaProviderOptionsImageText): Promise<false | string>;
|
|
23
|
+
updateToken(token: string, tokenInput: unknown, options: ICaptchaProviderOptionsImageText): Promise<TypeCaptchaProviderImageTextData>;
|
|
24
|
+
private _matches;
|
|
25
|
+
private _policy;
|
|
16
26
|
private _confirmFont;
|
|
17
27
|
}
|
|
18
28
|
//# sourceMappingURL=captchaProvider.imageText.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"captchaProvider.imageText.d.ts","sourceRoot":"","sources":["../../src/bean/captchaProvider.imageText.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,uBAAuB,EACvB,gCAAgC,EACjC,MAAM,uBAAuB,CAAC;AAG/B,OAAmB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAIjE,OAAO,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"captchaProvider.imageText.d.ts","sourceRoot":"","sources":["../../src/bean/captchaProvider.imageText.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,uBAAuB,EACvB,gCAAgC,EACjC,MAAM,uBAAuB,CAAC;AAG/B,OAAmB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAIjE,OAAO,EAAE,QAAQ,EAAgB,MAAM,MAAM,CAAC;AAG9C,MAAM,MAAM,iCAAiC,GAAG,MAAM,CAAC;AACvD,MAAM,MAAM,mCAAmC,GAAG,MAAM,CAAC;AACzD,MAAM,MAAM,gCAAgC,GAAG,oBAAoB,CACjE,iCAAiC,EACjC,mCAAmC,CACpC,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,gCAAgC,GAAG,MAAM,GAAG,MAAM,CAAC;AAE/D,MAAM,WAAW,gCAAiC,SAAQ,gCAAgC;IACxF,IAAI,CAAC,EAAE,gCAAgC,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,qBAMa,wBACX,SAAQ,QACR,YACE,uBAAuB,CAAC,iCAAiC,EAAE,mCAAmC,CAAC;IAE3F,MAAM,CACV,OAAO,EAAE,gCAAgC,EACzC,QAAQ,CAAC,EAAE,OAAO,EAClB,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,gCAAgC,CAAC;IAkCtC,MAAM,CACV,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,OAAO,EACnB,QAAQ,EAAE,gCAAgC,GACzC,OAAO,CAAC,OAAO,CAAC;IAmBb,eAAe,CACnB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,OAAO,EACnB,QAAQ,EAAE,gCAAgC,GACzC,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC;IAmBpB,WAAW,CACf,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,OAAO,EACnB,OAAO,EAAE,gCAAgC,GACxC,OAAO,CAAC,gCAAgC,CAAC;IAwB5C,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,OAAO;YAgBD,YAAY;CAQ3B"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BeanInfo, BeanBase, cast, BeanScopeBase } from 'vona';
|
|
1
|
+
import { BeanInfo, BeanBase, uuidv4, cast, BeanScopeBase } from 'vona';
|
|
2
2
|
import { __decorate } from 'tslib';
|
|
3
3
|
import { getRandomInt } from '@cabloy/utils';
|
|
4
4
|
import svgCaptcha from '@zhennann/svg-captcha';
|
|
@@ -12,20 +12,117 @@ import { Scope } from 'vona-module-a-bean';
|
|
|
12
12
|
|
|
13
13
|
const CaptchaProviderImageTextTypes = ['char', 'math'];
|
|
14
14
|
let CaptchaProviderImageText = class CaptchaProviderImageText extends BeanBase {
|
|
15
|
-
async create(options) {
|
|
15
|
+
async create(options, _payload, previousToken) {
|
|
16
|
+
const cache = this.$scope.captcha.cacheRedis.captcha;
|
|
17
|
+
const snapshot = previousToken ? await cache.snapshot(previousToken) : undefined;
|
|
18
|
+
if (previousToken !== undefined && !previousToken.startsWith('imageText:')) {
|
|
19
|
+
return this.app.throw(403);
|
|
20
|
+
}
|
|
21
|
+
const {
|
|
22
|
+
ttl,
|
|
23
|
+
ttlSecondary
|
|
24
|
+
} = this._policy(options);
|
|
16
25
|
await this._confirmFont(options);
|
|
17
26
|
let type = options.type;
|
|
18
27
|
if (!type) {
|
|
19
28
|
type = CaptchaProviderImageTextTypes[getRandomInt(2, 0)];
|
|
20
29
|
}
|
|
21
30
|
const captcha = type === 'char' ? svgCaptcha.create(options.opts) : svgCaptcha.createMathExpr(options.opts);
|
|
22
|
-
|
|
31
|
+
const token = snapshot ? previousToken : `imageText:${uuidv4()}`;
|
|
32
|
+
const written = await cache.writeProof(token, {
|
|
23
33
|
token: captcha.text,
|
|
24
|
-
|
|
34
|
+
revision: uuidv4(),
|
|
35
|
+
ttlSecondary
|
|
36
|
+
}, ttl, snapshot);
|
|
37
|
+
if (!written.changed) return this.app.throw(403);
|
|
38
|
+
return {
|
|
39
|
+
token,
|
|
40
|
+
tokenPublic: captcha.text,
|
|
41
|
+
payload: svg64(captcha.data),
|
|
42
|
+
serverNow: written.serverNow,
|
|
43
|
+
expiresAt: written.expiresAt,
|
|
44
|
+
routingExpiresAt: written.expiresAt + ttlSecondary
|
|
25
45
|
};
|
|
26
46
|
}
|
|
27
47
|
async verify(token, tokenInput, _options) {
|
|
28
|
-
|
|
48
|
+
const cache = this.$scope.captcha.cacheRedis.captcha;
|
|
49
|
+
const snapshot = await cache.snapshot(token);
|
|
50
|
+
if (!snapshot || !token.startsWith('imageText:')) return false;
|
|
51
|
+
const data = snapshot.data;
|
|
52
|
+
if (data.token2) {
|
|
53
|
+
const consumed = await cache.compareAndSet(token, snapshot);
|
|
54
|
+
return consumed && data.token2 === tokenInput;
|
|
55
|
+
}
|
|
56
|
+
if (!data.token) return false;
|
|
57
|
+
const verified = this._matches(data.token, tokenInput);
|
|
58
|
+
const changed = await cache.compareAndSet(token, snapshot, verified ? undefined : {
|
|
59
|
+
...data,
|
|
60
|
+
token: undefined,
|
|
61
|
+
revision: uuidv4()
|
|
62
|
+
});
|
|
63
|
+
return verified && changed;
|
|
64
|
+
}
|
|
65
|
+
async verifyImmediate(token, tokenInput, _options) {
|
|
66
|
+
const cache = this.$scope.captcha.cacheRedis.captcha;
|
|
67
|
+
const snapshot = await cache.snapshot(token);
|
|
68
|
+
if (!snapshot || !token.startsWith('imageText:') || !snapshot.data.token) return false;
|
|
69
|
+
const data = snapshot.data;
|
|
70
|
+
if (!this._matches(data.token, tokenInput)) {
|
|
71
|
+
await cache.compareAndSet(token, snapshot, {
|
|
72
|
+
...data,
|
|
73
|
+
token: undefined,
|
|
74
|
+
revision: uuidv4()
|
|
75
|
+
});
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
const tokenSecondary = uuidv4();
|
|
79
|
+
const changed = await cache.compareAndSet(token, snapshot, {
|
|
80
|
+
...data,
|
|
81
|
+
token: undefined,
|
|
82
|
+
token2: tokenSecondary,
|
|
83
|
+
revision: uuidv4()
|
|
84
|
+
}, data.ttlSecondary);
|
|
85
|
+
return changed ? tokenSecondary : false;
|
|
86
|
+
}
|
|
87
|
+
async updateToken(token, tokenInput, options) {
|
|
88
|
+
const cache = this.$scope.captcha.cacheRedis.captcha;
|
|
89
|
+
const snapshot = await cache.snapshot(token);
|
|
90
|
+
if (!snapshot || !token.startsWith('imageText:') || typeof tokenInput !== 'string') {
|
|
91
|
+
return this.app.throw(403);
|
|
92
|
+
}
|
|
93
|
+
const {
|
|
94
|
+
ttl,
|
|
95
|
+
ttlSecondary
|
|
96
|
+
} = this._policy(options);
|
|
97
|
+
const written = await cache.writeProof(token, {
|
|
98
|
+
token: tokenInput,
|
|
99
|
+
revision: uuidv4(),
|
|
100
|
+
ttlSecondary
|
|
101
|
+
}, ttl, snapshot);
|
|
102
|
+
if (!written.changed) return this.app.throw(403);
|
|
103
|
+
return {
|
|
104
|
+
token,
|
|
105
|
+
tokenPublic: tokenInput,
|
|
106
|
+
payload: '',
|
|
107
|
+
serverNow: written.serverNow,
|
|
108
|
+
expiresAt: written.expiresAt,
|
|
109
|
+
routingExpiresAt: written.expiresAt + ttlSecondary
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
_matches(token, input) {
|
|
113
|
+
return typeof input === 'string' && !!input && input.toLowerCase() === token.toLowerCase();
|
|
114
|
+
}
|
|
115
|
+
_policy(options) {
|
|
116
|
+
const defaults = this.$scope.captcha.config.captchaProvider;
|
|
117
|
+
const ttl = options.ttl ?? defaults.ttl;
|
|
118
|
+
const ttlSecondary = options.ttlSecondary ?? defaults.ttlSecondary;
|
|
119
|
+
if (!Number.isSafeInteger(ttl) || ttl <= 0 || !Number.isSafeInteger(ttlSecondary) || ttlSecondary <= 0 || !Number.isSafeInteger(Date.now() + ttl + ttlSecondary)) {
|
|
120
|
+
throw new Error('invalid captcha ttl');
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
ttl,
|
|
124
|
+
ttlSecondary
|
|
125
|
+
};
|
|
29
126
|
}
|
|
30
127
|
async _confirmFont(options) {
|
|
31
128
|
if (cast(svgCaptcha.options).font) return;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/bean/captchaProvider.imageText.ts","../src/bean/captchaScene.simple.ts","../src/bean/meta.asset.ts","../src/.metadata/index.ts"],"sourcesContent":[null,null,null,null],"names":["CaptchaProviderImageTextTypes","CaptchaProviderImageText","BeanBase","create","options","
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/bean/captchaProvider.imageText.ts","../src/bean/captchaScene.simple.ts","../src/bean/meta.asset.ts","../src/.metadata/index.ts"],"sourcesContent":[null,null,null,null],"names":["CaptchaProviderImageTextTypes","CaptchaProviderImageText","BeanBase","create","options","_payload","previousToken","cache","$scope","captcha","cacheRedis","snapshot","undefined","startsWith","app","throw","ttl","ttlSecondary","_policy","_confirmFont","type","getRandomInt","svgCaptcha","opts","createMathExpr","token","uuidv4","written","writeProof","text","revision","changed","tokenPublic","payload","svg64","data","serverNow","expiresAt","routingExpiresAt","verify","tokenInput","_options","token2","consumed","compareAndSet","verified","_matches","verifyImmediate","tokenSecondary","updateToken","input","toLowerCase","defaults","config","captchaProvider","Number","isSafeInteger","Date","now","Error","cast","font","fontFile","fontPath","scope","asset","get","_loadFont","ascender","descender","__decorate","CaptchaProvider","size","color","__z_BeanInfo","module","buffer","fse","readFile","opentypeParse","_nodeBufferToArrayBuffer","ab","ArrayBuffer","length","view","Uint8Array","i","CaptchaSceneSimple","CaptchaScene","resolver","_ctx","_providers","providers","MetaAsset","BeanAssetBase","Meta","ScopeModuleCaptchaSimple","BeanScopeBase","Scope"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@ import svgCaptcha, { ConfigObject } from '@zhennann/svg-captcha';
|
|
|
9
9
|
import fse from 'fs-extra';
|
|
10
10
|
import { parse as opentypeParse } from 'opentype.js/dist/opentype.mjs';
|
|
11
11
|
import svg64 from 'svg64';
|
|
12
|
-
import { BeanBase, cast } from 'vona';
|
|
12
|
+
import { BeanBase, cast, uuidv4 } from 'vona';
|
|
13
13
|
import { CaptchaProvider } from 'vona-module-a-captcha';
|
|
14
14
|
|
|
15
15
|
export type TypeCaptchaProviderImageTextToken = string;
|
|
@@ -19,6 +19,13 @@ export type TypeCaptchaProviderImageTextData = ICaptchaProviderData<
|
|
|
19
19
|
TypeCaptchaProviderImageTextPayload
|
|
20
20
|
>;
|
|
21
21
|
|
|
22
|
+
export interface ICaptchaImageTextState {
|
|
23
|
+
token?: string;
|
|
24
|
+
token2?: string;
|
|
25
|
+
revision: string;
|
|
26
|
+
ttlSecondary: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
export type TypeCaptchaProviderImageTextType = 'char' | 'math';
|
|
23
30
|
const CaptchaProviderImageTextTypes = ['char', 'math'] as const;
|
|
24
31
|
export interface ICaptchaProviderOptionsImageText extends IDecoratorCaptchaProviderOptions {
|
|
@@ -40,7 +47,17 @@ export class CaptchaProviderImageText
|
|
|
40
47
|
{
|
|
41
48
|
async create(
|
|
42
49
|
options: ICaptchaProviderOptionsImageText,
|
|
50
|
+
_payload?: unknown,
|
|
51
|
+
previousToken?: string,
|
|
43
52
|
): Promise<TypeCaptchaProviderImageTextData> {
|
|
53
|
+
const cache = this.$scope.captcha.cacheRedis.captcha;
|
|
54
|
+
const snapshot = previousToken
|
|
55
|
+
? await cache.snapshot<ICaptchaImageTextState>(previousToken)
|
|
56
|
+
: undefined;
|
|
57
|
+
if (previousToken !== undefined && !previousToken.startsWith('imageText:')) {
|
|
58
|
+
return this.app.throw(403);
|
|
59
|
+
}
|
|
60
|
+
const { ttl, ttlSecondary } = this._policy(options);
|
|
44
61
|
await this._confirmFont(options);
|
|
45
62
|
let type = options.type;
|
|
46
63
|
if (!type) {
|
|
@@ -48,15 +65,116 @@ export class CaptchaProviderImageText
|
|
|
48
65
|
}
|
|
49
66
|
const captcha =
|
|
50
67
|
type === 'char' ? svgCaptcha.create(options.opts) : svgCaptcha.createMathExpr(options.opts);
|
|
51
|
-
|
|
68
|
+
const token = snapshot ? previousToken! : `imageText:${uuidv4()}`;
|
|
69
|
+
const written = await cache.writeProof(
|
|
70
|
+
token,
|
|
71
|
+
{ token: captcha.text, revision: uuidv4(), ttlSecondary },
|
|
72
|
+
ttl,
|
|
73
|
+
snapshot,
|
|
74
|
+
);
|
|
75
|
+
if (!written.changed) return this.app.throw(403);
|
|
76
|
+
return {
|
|
77
|
+
token,
|
|
78
|
+
tokenPublic: captcha.text,
|
|
79
|
+
payload: svg64(captcha.data),
|
|
80
|
+
serverNow: written.serverNow,
|
|
81
|
+
expiresAt: written.expiresAt,
|
|
82
|
+
routingExpiresAt: written.expiresAt + ttlSecondary,
|
|
83
|
+
};
|
|
52
84
|
}
|
|
53
85
|
|
|
54
86
|
async verify(
|
|
55
|
-
token:
|
|
56
|
-
tokenInput:
|
|
87
|
+
token: string,
|
|
88
|
+
tokenInput: unknown,
|
|
57
89
|
_options: ICaptchaProviderOptionsImageText,
|
|
58
90
|
): Promise<boolean> {
|
|
59
|
-
|
|
91
|
+
const cache = this.$scope.captcha.cacheRedis.captcha;
|
|
92
|
+
const snapshot = await cache.snapshot<ICaptchaImageTextState>(token);
|
|
93
|
+
if (!snapshot || !token.startsWith('imageText:')) return false;
|
|
94
|
+
const data = snapshot.data;
|
|
95
|
+
if (data.token2) {
|
|
96
|
+
const consumed = await cache.compareAndSet(token, snapshot);
|
|
97
|
+
return consumed && data.token2 === tokenInput;
|
|
98
|
+
}
|
|
99
|
+
if (!data.token) return false;
|
|
100
|
+
const verified = this._matches(data.token, tokenInput);
|
|
101
|
+
const changed = await cache.compareAndSet(
|
|
102
|
+
token,
|
|
103
|
+
snapshot,
|
|
104
|
+
verified ? undefined : { ...data, token: undefined, revision: uuidv4() },
|
|
105
|
+
);
|
|
106
|
+
return verified && changed;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async verifyImmediate(
|
|
110
|
+
token: string,
|
|
111
|
+
tokenInput: unknown,
|
|
112
|
+
_options: ICaptchaProviderOptionsImageText,
|
|
113
|
+
): Promise<false | string> {
|
|
114
|
+
const cache = this.$scope.captcha.cacheRedis.captcha;
|
|
115
|
+
const snapshot = await cache.snapshot<ICaptchaImageTextState>(token);
|
|
116
|
+
if (!snapshot || !token.startsWith('imageText:') || !snapshot.data.token) return false;
|
|
117
|
+
const data = snapshot.data;
|
|
118
|
+
if (!this._matches(data.token!, tokenInput)) {
|
|
119
|
+
await cache.compareAndSet(token, snapshot, { ...data, token: undefined, revision: uuidv4() });
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
const tokenSecondary = uuidv4();
|
|
123
|
+
const changed = await cache.compareAndSet(
|
|
124
|
+
token,
|
|
125
|
+
snapshot,
|
|
126
|
+
{ ...data, token: undefined, token2: tokenSecondary, revision: uuidv4() },
|
|
127
|
+
data.ttlSecondary,
|
|
128
|
+
);
|
|
129
|
+
return changed ? tokenSecondary : false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async updateToken(
|
|
133
|
+
token: string,
|
|
134
|
+
tokenInput: unknown,
|
|
135
|
+
options: ICaptchaProviderOptionsImageText,
|
|
136
|
+
): Promise<TypeCaptchaProviderImageTextData> {
|
|
137
|
+
const cache = this.$scope.captcha.cacheRedis.captcha;
|
|
138
|
+
const snapshot = await cache.snapshot<ICaptchaImageTextState>(token);
|
|
139
|
+
if (!snapshot || !token.startsWith('imageText:') || typeof tokenInput !== 'string') {
|
|
140
|
+
return this.app.throw(403);
|
|
141
|
+
}
|
|
142
|
+
const { ttl, ttlSecondary } = this._policy(options);
|
|
143
|
+
const written = await cache.writeProof(
|
|
144
|
+
token,
|
|
145
|
+
{ token: tokenInput, revision: uuidv4(), ttlSecondary },
|
|
146
|
+
ttl,
|
|
147
|
+
snapshot,
|
|
148
|
+
);
|
|
149
|
+
if (!written.changed) return this.app.throw(403);
|
|
150
|
+
return {
|
|
151
|
+
token,
|
|
152
|
+
tokenPublic: tokenInput,
|
|
153
|
+
payload: '',
|
|
154
|
+
serverNow: written.serverNow,
|
|
155
|
+
expiresAt: written.expiresAt,
|
|
156
|
+
routingExpiresAt: written.expiresAt + ttlSecondary,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private _matches(token: string, input: unknown) {
|
|
161
|
+
return typeof input === 'string' && !!input && input.toLowerCase() === token.toLowerCase();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private _policy(options: ICaptchaProviderOptionsImageText) {
|
|
165
|
+
const defaults = this.$scope.captcha.config.captchaProvider;
|
|
166
|
+
const ttl = options.ttl ?? defaults.ttl;
|
|
167
|
+
const ttlSecondary = options.ttlSecondary ?? defaults.ttlSecondary;
|
|
168
|
+
if (
|
|
169
|
+
!Number.isSafeInteger(ttl) ||
|
|
170
|
+
ttl <= 0 ||
|
|
171
|
+
!Number.isSafeInteger(ttlSecondary) ||
|
|
172
|
+
ttlSecondary <= 0 ||
|
|
173
|
+
!Number.isSafeInteger(Date.now() + ttl + ttlSecondary)
|
|
174
|
+
) {
|
|
175
|
+
throw new Error('invalid captcha ttl');
|
|
176
|
+
}
|
|
177
|
+
return { ttl, ttlSecondary };
|
|
60
178
|
}
|
|
61
179
|
|
|
62
180
|
private async _confirmFont(options: ICaptchaProviderOptionsImageText) {
|