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.
@@ -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
- /** middleware: begin */
4
- export * from '../bean/middleware.captcha.ts';
5
- import type { IMiddlewareOptionsCaptcha } from '../bean/middleware.captcha.ts';
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 IMiddlewareRecordLocal {
9
- 'a-captcha:captcha': IMiddlewareOptionsCaptcha;
8
+ interface IInterceptorRecordLocal {
9
+ 'a-captcha:captchaVerify': IInterceptorOptionsCaptchaVerify;
10
10
  }
11
11
  }
12
12
  declare module 'vona-module-a-captcha' {
13
- interface MiddlewareCaptcha {
13
+ interface InterceptorCaptchaVerify {
14
14
  }
15
- interface MiddlewareCaptcha {
16
- get $beanFullName(): 'a-captcha.middleware.captcha';
17
- get $onionName(): 'a-captcha:captcha';
15
+ interface InterceptorCaptchaVerify {
16
+ get $beanFullName(): 'a-captcha.interceptor.captchaVerify';
17
+ get $onionName(): 'a-captcha:captchaVerify';
18
18
  }
19
19
  }
20
- /** middleware: end */
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 { Middleware } from 'vona-module-a-aspect';
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 MiddlewareCaptcha = (_dec$6 = Middleware({
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 MiddlewareCaptcha extends BeanBase {
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('please specify the captcha scene name');
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 combineCaptchaError(bodyField, this.scope.locale.CaptchaInvalid());
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
- // delete cache
82
- await this.scope.cacheRedis.captcha.del(id);
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
- return await this.create(sceneName);
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
- const captchaData = await this.getCaptchaData(id);
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.ctx.commit(async () => {
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
- // do not mutate cache
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.ctx.commit(async () => {
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
- // tokenSecondary
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
- // do not mutate cache
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, MiddlewareCaptcha, ScopeModuleACaptcha, config, locales };
393
+ export { $locale, BeanCaptcha, CacheRedisCaptcha, Captcha, CaptchaProvider, CaptchaScene, ControllerCaptcha, DtoCaptchaData, DtoCaptchaVerify, InterceptorCaptchaVerify, ScopeModuleACaptcha, config, locales };
@@ -0,0 +1,6 @@
1
+ import type { IInterceptorOptionsCaptchaVerify } from '../bean/interceptor.captchaVerify.ts';
2
+ declare function Verify(options?: Partial<IInterceptorOptionsCaptchaVerify>): MethodDecorator;
3
+ export declare const Captcha: {
4
+ verify: typeof Verify;
5
+ };
6
+ export {};
@@ -1,2 +1,3 @@
1
+ export * from './captcha.ts';
1
2
  export * from './captchaProvider.ts';
2
3
  export * from './captchaScene.ts';
@@ -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<IDecoratorCaptchaProviderOptions, keyof ICaptchaProviderRecord>;
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<IDecoratorCaptchaSceneOptions, keyof ICaptchaSceneRecord>;
20
+ captchaScene: ServiceOnion<ICaptchaSceneRecord>;
21
21
  }
22
22
  }
23
23
  declare module 'vona' {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vona-module-a-captcha",
3
3
  "type": "module",
4
- "version": "5.0.8",
4
+ "version": "5.0.10",
5
5
  "title": "a-captcha",
6
6
  "vonaModule": {
7
7
  "dependencies": {},
@@ -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
- }