vona-module-a-captcha 5.0.8 → 5.0.10
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/.metadata/index.d.ts +10 -10
- package/dist/bean/interceptor.captchaVerify.d.ts +11 -0
- package/dist/index.js +68 -33
- package/dist/lib/captcha.d.ts +6 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/types/captchaProvider.d.ts +1 -1
- package/dist/types/captchaScene.d.ts +1 -1
- package/package.json +1 -1
- package/dist/bean/middleware.captcha.d.ts +0 -11
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import type { TypeSymbolKeyFieldsMore } from 'vona-module-a-orm';
|
|
2
2
|
import type { TypeEntityOptionsFields, TypeControllerOptionsActions } from 'vona-module-a-openapi';
|
|
3
|
-
/**
|
|
4
|
-
export * from '../bean/
|
|
5
|
-
import type {
|
|
3
|
+
/** interceptor: begin */
|
|
4
|
+
export * from '../bean/interceptor.captchaVerify.ts';
|
|
5
|
+
import type { IInterceptorOptionsCaptchaVerify } from '../bean/interceptor.captchaVerify.ts';
|
|
6
6
|
import 'vona';
|
|
7
7
|
declare module 'vona-module-a-aspect' {
|
|
8
|
-
interface
|
|
9
|
-
'a-captcha:
|
|
8
|
+
interface IInterceptorRecordLocal {
|
|
9
|
+
'a-captcha:captchaVerify': IInterceptorOptionsCaptchaVerify;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
declare module 'vona-module-a-captcha' {
|
|
13
|
-
interface
|
|
13
|
+
interface InterceptorCaptchaVerify {
|
|
14
14
|
}
|
|
15
|
-
interface
|
|
16
|
-
get $beanFullName(): 'a-captcha.
|
|
17
|
-
get $onionName(): 'a-captcha:
|
|
15
|
+
interface InterceptorCaptchaVerify {
|
|
16
|
+
get $beanFullName(): 'a-captcha.interceptor.captchaVerify';
|
|
17
|
+
get $onionName(): 'a-captcha:captchaVerify';
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
20
|
+
/** interceptor: end */
|
|
21
21
|
/** bean: begin */
|
|
22
22
|
export * from '../bean/bean.captcha.ts';
|
|
23
23
|
import 'vona';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Next } from 'vona';
|
|
2
|
+
import type { IDecoratorInterceptorOptions, IInterceptorExecute } from 'vona-module-a-aspect';
|
|
3
|
+
import type { ICaptchaSceneRecord } from '../types/captchaScene.ts';
|
|
4
|
+
import { BeanBase } from 'vona';
|
|
5
|
+
export interface IInterceptorOptionsCaptchaVerify extends IDecoratorInterceptorOptions {
|
|
6
|
+
scene?: keyof ICaptchaSceneRecord;
|
|
7
|
+
bodyField?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class InterceptorCaptchaVerify extends BeanBase implements IInterceptorExecute {
|
|
10
|
+
execute(options: IInterceptorOptionsCaptchaVerify, next: Next): Promise<any>;
|
|
11
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BeanInfo, BeanBase, uuidv4, beanFullNameFromOnionName, deepExtend, BeanScopeBase, createBeanDecorator } from 'vona';
|
|
2
|
-
import {
|
|
2
|
+
import { Interceptor, Aspect } from 'vona-module-a-aspect';
|
|
3
3
|
import { getRandomInt } from '@cabloy/utils';
|
|
4
4
|
import { Bean, Scope } from 'vona-module-a-bean';
|
|
5
5
|
import { CacheRedis, BeanCacheRedisBase } from 'vona-module-a-cache';
|
|
@@ -9,35 +9,27 @@ import { Passport } from 'vona-module-a-user';
|
|
|
9
9
|
import z from 'zod';
|
|
10
10
|
|
|
11
11
|
var _dec$6, _dec2$6, _class$6;
|
|
12
|
-
let
|
|
12
|
+
let InterceptorCaptchaVerify = (_dec$6 = Interceptor({
|
|
13
13
|
bodyField: 'captcha'
|
|
14
14
|
}), _dec2$6 = BeanInfo({
|
|
15
15
|
module: "a-captcha"
|
|
16
|
-
}), _dec$6(_class$6 = _dec2$6(_class$6 = class
|
|
16
|
+
}), _dec$6(_class$6 = _dec2$6(_class$6 = class InterceptorCaptchaVerify extends BeanBase {
|
|
17
17
|
async execute(options, next) {
|
|
18
|
+
// scene
|
|
18
19
|
const sceneName = options.scene;
|
|
19
|
-
if (!sceneName) throw new Error('
|
|
20
|
+
if (!sceneName) throw new Error('should specify the captchaVerify scene name');
|
|
20
21
|
// captcha
|
|
21
22
|
const bodyField = options.bodyField;
|
|
22
23
|
const captcha = this.ctx.request.body[bodyField];
|
|
23
24
|
if (!captcha) throw new Error('not found captcha data');
|
|
25
|
+
if (typeof captcha !== 'object') throw new Error('not found valid captcha data');
|
|
26
|
+
// verify
|
|
24
27
|
const verified = await this.bean.captcha.verify(captcha.id, captcha.token, sceneName);
|
|
25
|
-
if (!verified) throw
|
|
28
|
+
if (!verified) throw this.bean.zod.customError([bodyField], this.scope.locale.CaptchaInvalid());
|
|
26
29
|
// next
|
|
27
30
|
return next();
|
|
28
31
|
}
|
|
29
32
|
}) || _class$6) || _class$6);
|
|
30
|
-
function combineCaptchaError(bodyField, message) {
|
|
31
|
-
// error
|
|
32
|
-
const error = new Error();
|
|
33
|
-
error.code = 422;
|
|
34
|
-
error.message = [{
|
|
35
|
-
code: 'custom',
|
|
36
|
-
path: [bodyField],
|
|
37
|
-
message
|
|
38
|
-
}];
|
|
39
|
-
return error;
|
|
40
|
-
}
|
|
41
33
|
|
|
42
34
|
var _dec$5, _dec2$5, _class$5;
|
|
43
35
|
const SymbolProviders = Symbol('SymbolProviders');
|
|
@@ -78,13 +70,40 @@ let BeanCaptcha = (_dec$5 = Bean(), _dec2$5 = BeanInfo({
|
|
|
78
70
|
return result;
|
|
79
71
|
}
|
|
80
72
|
async refresh(id, sceneName) {
|
|
81
|
-
|
|
82
|
-
|
|
73
|
+
let captchaData = await this.getCaptchaData(id);
|
|
74
|
+
if (!captchaData) {
|
|
75
|
+
// create
|
|
76
|
+
return await this.create(sceneName);
|
|
77
|
+
}
|
|
78
|
+
// scene
|
|
79
|
+
if (captchaData.scene !== sceneName) this.app.throw(403);
|
|
83
80
|
// create
|
|
84
|
-
|
|
81
|
+
const beanInstance = this._getProviderInstance(captchaData.provider);
|
|
82
|
+
const providerOptions = this._getProviderOptions(captchaData.scene, captchaData.provider);
|
|
83
|
+
const captcha = await beanInstance.create(providerOptions);
|
|
84
|
+
// data
|
|
85
|
+
captchaData = {
|
|
86
|
+
scene: sceneName,
|
|
87
|
+
provider: captchaData.provider,
|
|
88
|
+
token: captcha.token
|
|
89
|
+
};
|
|
90
|
+
// cache
|
|
91
|
+
await this.scope.cacheRedis.captcha.set(captchaData, id, {
|
|
92
|
+
ttl: providerOptions.ttl ?? this.scope.config.captchaProvider.ttl
|
|
93
|
+
});
|
|
94
|
+
// result
|
|
95
|
+
const result = {
|
|
96
|
+
id,
|
|
97
|
+
provider: captchaData.provider,
|
|
98
|
+
payload: captcha.payload
|
|
99
|
+
};
|
|
100
|
+
if (this.scope.config.captcha.showToken) {
|
|
101
|
+
result.token = captcha.token;
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
85
104
|
}
|
|
86
105
|
async verify(id, token, sceneName) {
|
|
87
|
-
|
|
106
|
+
let captchaData = await this.getCaptchaData(id);
|
|
88
107
|
if (!captchaData) return false;
|
|
89
108
|
// scene
|
|
90
109
|
if (captchaData.scene !== sceneName) return false;
|
|
@@ -92,44 +111,53 @@ let BeanCaptcha = (_dec$5 = Bean(), _dec2$5 = BeanInfo({
|
|
|
92
111
|
const tokenSecondary = captchaData.token2;
|
|
93
112
|
if (tokenSecondary) {
|
|
94
113
|
// delete cache
|
|
95
|
-
this.
|
|
96
|
-
await this.scope.cacheRedis.captcha.del(id);
|
|
97
|
-
});
|
|
114
|
+
await this.scope.cacheRedis.captcha.del(id);
|
|
98
115
|
return tokenSecondary === token;
|
|
99
116
|
}
|
|
100
117
|
// provider
|
|
101
118
|
const beanInstance = this._getProviderInstance(captchaData.provider);
|
|
102
119
|
const providerOptions = this._getProviderOptions(captchaData.scene, captchaData.provider);
|
|
103
120
|
// verify
|
|
121
|
+
if (!captchaData.token) return false;
|
|
104
122
|
const verified = await beanInstance.verify(captchaData.token, token, providerOptions);
|
|
105
123
|
if (!verified) {
|
|
106
|
-
//
|
|
124
|
+
// update token. not delete cache for refresh
|
|
125
|
+
captchaData = {
|
|
126
|
+
...captchaData,
|
|
127
|
+
token: undefined
|
|
128
|
+
};
|
|
129
|
+
await this.scope.cacheRedis.captcha.set(captchaData, id, {
|
|
130
|
+
ttl: providerOptions.ttl ?? this.scope.config.captchaProvider.ttl
|
|
131
|
+
});
|
|
107
132
|
return false;
|
|
108
133
|
}
|
|
109
134
|
// delete cache
|
|
110
|
-
this.
|
|
111
|
-
await this.scope.cacheRedis.captcha.del(id);
|
|
112
|
-
});
|
|
135
|
+
await this.scope.cacheRedis.captcha.del(id);
|
|
113
136
|
// ok
|
|
114
137
|
return true;
|
|
115
138
|
}
|
|
116
139
|
async verifyImmediate(id, token) {
|
|
117
140
|
let captchaData = await this.getCaptchaData(id);
|
|
118
141
|
if (!captchaData) return false;
|
|
119
|
-
|
|
120
|
-
let tokenSecondary = captchaData.token2;
|
|
121
|
-
if (tokenSecondary) return tokenSecondary; // maybe called more times
|
|
142
|
+
if (!captchaData.token) return false;
|
|
122
143
|
// provider
|
|
123
144
|
const beanInstance = this._getProviderInstance(captchaData.provider);
|
|
124
145
|
const providerOptions = this._getProviderOptions(captchaData.scene, captchaData.provider);
|
|
125
146
|
// verify
|
|
126
147
|
const verified = await beanInstance.verify(captchaData.token, token, providerOptions);
|
|
127
148
|
if (!verified) {
|
|
128
|
-
//
|
|
149
|
+
// update token. not delete cache for refresh
|
|
150
|
+
captchaData = {
|
|
151
|
+
...captchaData,
|
|
152
|
+
token: undefined
|
|
153
|
+
};
|
|
154
|
+
await this.scope.cacheRedis.captcha.set(captchaData, id, {
|
|
155
|
+
ttl: providerOptions.ttl ?? this.scope.config.captchaProvider.ttl
|
|
156
|
+
});
|
|
129
157
|
return false;
|
|
130
158
|
}
|
|
131
159
|
// tokenSecondary
|
|
132
|
-
tokenSecondary = uuidv4();
|
|
160
|
+
const tokenSecondary = uuidv4();
|
|
133
161
|
captchaData = {
|
|
134
162
|
...captchaData,
|
|
135
163
|
token: undefined,
|
|
@@ -347,6 +375,13 @@ function $locale(key) {
|
|
|
347
375
|
}
|
|
348
376
|
/** scope: end */
|
|
349
377
|
|
|
378
|
+
function Verify(options) {
|
|
379
|
+
return Aspect.interceptor('a-captcha:captchaVerify', options);
|
|
380
|
+
}
|
|
381
|
+
const Captcha = {
|
|
382
|
+
verify: Verify
|
|
383
|
+
};
|
|
384
|
+
|
|
350
385
|
function CaptchaProvider(options) {
|
|
351
386
|
return createBeanDecorator('captchaProvider', options);
|
|
352
387
|
}
|
|
@@ -355,4 +390,4 @@ function CaptchaScene(options) {
|
|
|
355
390
|
return createBeanDecorator('captchaScene', options);
|
|
356
391
|
}
|
|
357
392
|
|
|
358
|
-
export { $locale, BeanCaptcha, CacheRedisCaptcha, CaptchaProvider, CaptchaScene, ControllerCaptcha, DtoCaptchaData, DtoCaptchaVerify,
|
|
393
|
+
export { $locale, BeanCaptcha, CacheRedisCaptcha, Captcha, CaptchaProvider, CaptchaScene, ControllerCaptcha, DtoCaptchaData, DtoCaptchaVerify, InterceptorCaptchaVerify, ScopeModuleACaptcha, config, locales };
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface IDecoratorCaptchaProviderOptions extends TypeOnionOptionsEnable
|
|
|
16
16
|
}
|
|
17
17
|
declare module 'vona-module-a-onion' {
|
|
18
18
|
interface BeanOnion {
|
|
19
|
-
captchaProvider: ServiceOnion<
|
|
19
|
+
captchaProvider: ServiceOnion<ICaptchaProviderRecord>;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
declare module 'vona' {
|
|
@@ -17,7 +17,7 @@ export interface IDecoratorCaptchaSceneOptions {
|
|
|
17
17
|
}
|
|
18
18
|
declare module 'vona-module-a-onion' {
|
|
19
19
|
interface BeanOnion {
|
|
20
|
-
captchaScene: ServiceOnion<
|
|
20
|
+
captchaScene: ServiceOnion<ICaptchaSceneRecord>;
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
declare module 'vona' {
|
package/package.json
CHANGED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { Next } from 'vona';
|
|
2
|
-
import type { IDecoratorMiddlewareOptions, IMiddlewareExecute } from 'vona-module-a-aspect';
|
|
3
|
-
import type { ICaptchaSceneRecord } from '../types/captchaScene.ts';
|
|
4
|
-
import { BeanBase } from 'vona';
|
|
5
|
-
export interface IMiddlewareOptionsCaptcha extends IDecoratorMiddlewareOptions {
|
|
6
|
-
scene?: keyof ICaptchaSceneRecord;
|
|
7
|
-
bodyField?: string;
|
|
8
|
-
}
|
|
9
|
-
export declare class MiddlewareCaptcha extends BeanBase implements IMiddlewareExecute {
|
|
10
|
-
execute(options: IMiddlewareOptionsCaptcha, next: Next): Promise<any>;
|
|
11
|
-
}
|