vona-module-a-captcha 5.0.0 → 5.0.7
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/LICENSE +21 -0
- package/dist/.metadata/index.d.ts +161 -0
- package/dist/.metadata/this.d.ts +2 -0
- package/dist/bean/bean.captcha.d.ts +20 -0
- package/dist/bean/cacheRedis.captcha.d.ts +6 -0
- package/dist/bean/middleware.captcha.d.ts +11 -0
- package/dist/config/config.d.ts +10 -0
- package/dist/config/locale/en-us.d.ts +4 -0
- package/dist/config/locale/zh-cn.d.ts +4 -0
- package/dist/controller/captcha.d.ts +9 -0
- package/dist/dto/captchaData.d.ts +9 -0
- package/dist/dto/captchaVerify.d.ts +7 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +358 -0
- package/dist/lib/captchaProvider.d.ts +2 -0
- package/dist/lib/captchaScene.d.ts +2 -0
- package/dist/lib/index.d.ts +2 -0
- package/dist/types/captcha.d.ts +14 -0
- package/dist/types/captchaProvider.d.ts +29 -0
- package/dist/types/captchaScene.d.ts +30 -0
- package/dist/types/index.d.ts +3 -0
- package/package.json +27 -20
- package/README.md +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2016-present Vona
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { TypeSymbolKeyFieldsMore } from 'vona-module-a-orm';
|
|
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';
|
|
6
|
+
import 'vona';
|
|
7
|
+
declare module 'vona-module-a-aspect' {
|
|
8
|
+
interface IMiddlewareRecordLocal {
|
|
9
|
+
'a-captcha:captcha': IMiddlewareOptionsCaptcha;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
declare module 'vona-module-a-captcha' {
|
|
13
|
+
interface MiddlewareCaptcha {
|
|
14
|
+
}
|
|
15
|
+
interface MiddlewareCaptcha {
|
|
16
|
+
get $beanFullName(): 'a-captcha.middleware.captcha';
|
|
17
|
+
get $onionName(): 'a-captcha:captcha';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** middleware: end */
|
|
21
|
+
/** bean: begin */
|
|
22
|
+
export * from '../bean/bean.captcha.ts';
|
|
23
|
+
import 'vona';
|
|
24
|
+
declare module 'vona' {
|
|
25
|
+
}
|
|
26
|
+
declare module 'vona-module-a-captcha' {
|
|
27
|
+
interface BeanCaptcha {
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** bean: end */
|
|
31
|
+
/** bean: begin */
|
|
32
|
+
import type { BeanCaptcha } from '../bean/bean.captcha.ts';
|
|
33
|
+
import 'vona';
|
|
34
|
+
declare module 'vona' {
|
|
35
|
+
interface IBeanRecordGlobal {
|
|
36
|
+
'captcha': BeanCaptcha;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** bean: end */
|
|
40
|
+
/** cacheRedis: begin */
|
|
41
|
+
export * from '../bean/cacheRedis.captcha.ts';
|
|
42
|
+
import { type IDecoratorCacheRedisOptions } from 'vona-module-a-cache';
|
|
43
|
+
declare module 'vona-module-a-cache' {
|
|
44
|
+
interface ICacheRedisRecord {
|
|
45
|
+
'a-captcha:captcha': IDecoratorCacheRedisOptions;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
declare module 'vona-module-a-captcha' {
|
|
49
|
+
interface CacheRedisCaptcha {
|
|
50
|
+
}
|
|
51
|
+
interface CacheRedisCaptcha {
|
|
52
|
+
get $beanFullName(): 'a-captcha.cacheRedis.captcha';
|
|
53
|
+
get $onionName(): 'a-captcha:captcha';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** cacheRedis: end */
|
|
57
|
+
/** cacheRedis: begin */
|
|
58
|
+
import type { CacheRedisCaptcha } from '../bean/cacheRedis.captcha.ts';
|
|
59
|
+
export interface IModuleCacheRedis {
|
|
60
|
+
'captcha': CacheRedisCaptcha;
|
|
61
|
+
}
|
|
62
|
+
/** cacheRedis: end */
|
|
63
|
+
/** dto: begin */
|
|
64
|
+
export * from '../dto/captchaData.ts';
|
|
65
|
+
export * from '../dto/captchaVerify.ts';
|
|
66
|
+
import type { IDtoOptionsCaptchaData } from '../dto/captchaData.ts';
|
|
67
|
+
import type { IDtoOptionsCaptchaVerify } from '../dto/captchaVerify.ts';
|
|
68
|
+
import 'vona';
|
|
69
|
+
declare module 'vona-module-a-web' {
|
|
70
|
+
interface IDtoRecord {
|
|
71
|
+
'a-captcha:captchaData': IDtoOptionsCaptchaData;
|
|
72
|
+
'a-captcha:captchaVerify': IDtoOptionsCaptchaVerify;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
declare module 'vona-module-a-captcha' {
|
|
76
|
+
}
|
|
77
|
+
/** dto: end */
|
|
78
|
+
/** dto: begin */
|
|
79
|
+
import type { DtoCaptchaData } from '../dto/captchaData.ts';
|
|
80
|
+
import type { DtoCaptchaVerify } from '../dto/captchaVerify.ts';
|
|
81
|
+
declare module 'vona-module-a-captcha' {
|
|
82
|
+
interface IDtoOptionsCaptchaData {
|
|
83
|
+
fields?: TypeEntityOptionsFields<DtoCaptchaData, IDtoOptionsCaptchaData[TypeSymbolKeyFieldsMore]>;
|
|
84
|
+
}
|
|
85
|
+
interface IDtoOptionsCaptchaVerify {
|
|
86
|
+
fields?: TypeEntityOptionsFields<DtoCaptchaVerify, IDtoOptionsCaptchaVerify[TypeSymbolKeyFieldsMore]>;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** dto: end */
|
|
90
|
+
/** controller: begin */
|
|
91
|
+
export * from '../controller/captcha.ts';
|
|
92
|
+
import type { IControllerOptionsCaptcha } from '../controller/captcha.ts';
|
|
93
|
+
import 'vona';
|
|
94
|
+
declare module 'vona-module-a-web' {
|
|
95
|
+
interface IControllerRecord {
|
|
96
|
+
'a-captcha:captcha': IControllerOptionsCaptcha;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
declare module 'vona-module-a-captcha' {
|
|
100
|
+
interface ControllerCaptcha {
|
|
101
|
+
}
|
|
102
|
+
interface ControllerCaptcha {
|
|
103
|
+
get $beanFullName(): 'a-captcha.controller.captcha';
|
|
104
|
+
get $onionName(): 'a-captcha:captcha';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** controller: end */
|
|
108
|
+
/** controller: begin */
|
|
109
|
+
import '../controller/captcha.ts';
|
|
110
|
+
declare module 'vona-module-a-captcha' {
|
|
111
|
+
interface IControllerOptionsCaptcha {
|
|
112
|
+
actions?: TypeControllerOptionsActions<ControllerCaptcha>;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
declare module 'vona-module-a-web' {
|
|
116
|
+
interface IApiPathPostRecord {
|
|
117
|
+
'/captcha/create': undefined;
|
|
118
|
+
'/captcha/refresh': undefined;
|
|
119
|
+
'/captcha/verifyImmediate': undefined;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** controller: end */
|
|
123
|
+
/** config: begin */
|
|
124
|
+
export * from '../config/config.ts';
|
|
125
|
+
import type { config } from '../config/config.ts';
|
|
126
|
+
export declare const locales: {
|
|
127
|
+
'en-us': {
|
|
128
|
+
CaptchaInvalid: string;
|
|
129
|
+
};
|
|
130
|
+
'zh-cn': {
|
|
131
|
+
CaptchaInvalid: string;
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
/** locale: end */
|
|
135
|
+
/** scope: begin */
|
|
136
|
+
import { BeanScopeBase, type BeanScopeUtil, type TypeModuleConfig, type TypeModuleLocales, type TypeLocaleBase } from 'vona';
|
|
137
|
+
export declare class ScopeModuleACaptcha extends BeanScopeBase {
|
|
138
|
+
}
|
|
139
|
+
export interface ScopeModuleACaptcha {
|
|
140
|
+
util: BeanScopeUtil;
|
|
141
|
+
config: TypeModuleConfig<typeof config>;
|
|
142
|
+
locale: TypeModuleLocales<(typeof locales)[TypeLocaleBase]>;
|
|
143
|
+
cacheRedis: IModuleCacheRedis;
|
|
144
|
+
}
|
|
145
|
+
import 'vona';
|
|
146
|
+
declare module 'vona' {
|
|
147
|
+
interface IBeanScopeRecord {
|
|
148
|
+
'a-captcha': ScopeModuleACaptcha;
|
|
149
|
+
}
|
|
150
|
+
interface IBeanScopeContainer {
|
|
151
|
+
captcha: ScopeModuleACaptcha;
|
|
152
|
+
}
|
|
153
|
+
interface IBeanScopeConfig {
|
|
154
|
+
'a-captcha': ReturnType<typeof config>;
|
|
155
|
+
}
|
|
156
|
+
interface IBeanScopeLocale {
|
|
157
|
+
'a-captcha': (typeof locales)[TypeLocaleBase];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export declare function $locale<K extends keyof (typeof locales)[TypeLocaleBase]>(key: K): `a-captcha::${K}`;
|
|
161
|
+
/** scope: end */
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ICaptchaData, ICaptchaDataCache } from '../types/captcha.ts';
|
|
2
|
+
import type { ICaptchaProviderRecord } from '../types/captchaProvider.ts';
|
|
3
|
+
import type { ICaptchaSceneRecord } from '../types/captchaScene.ts';
|
|
4
|
+
import { BeanBase } from 'vona';
|
|
5
|
+
declare const SymbolProviders: unique symbol;
|
|
6
|
+
export declare class BeanCaptcha extends BeanBase {
|
|
7
|
+
protected [SymbolProviders]: Record<keyof ICaptchaSceneRecord, ICaptchaProviderRecord>;
|
|
8
|
+
create(sceneName: keyof ICaptchaSceneRecord): Promise<ICaptchaData>;
|
|
9
|
+
refresh(id: string, sceneName: keyof ICaptchaSceneRecord): Promise<ICaptchaData>;
|
|
10
|
+
verify(id: string, token: unknown, sceneName: keyof ICaptchaSceneRecord): Promise<boolean>;
|
|
11
|
+
verifyImmediate(id: string, token: unknown): Promise<false | string>;
|
|
12
|
+
getCaptchaData(id: string): Promise<ICaptchaDataCache | null | undefined>;
|
|
13
|
+
updateCaptchaToken(id: string, token: unknown): Promise<undefined>;
|
|
14
|
+
private _getProviderInstance;
|
|
15
|
+
private _getProviderOptions;
|
|
16
|
+
private _resolveProvider;
|
|
17
|
+
private _getProviders;
|
|
18
|
+
private _prepareProviders;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ICaptchaDataCache } from '../types/captcha.ts';
|
|
2
|
+
import { BeanCacheRedisBase } from 'vona-module-a-cache';
|
|
3
|
+
export type TCacheRedisCaptchaKey = string;
|
|
4
|
+
export type TCacheRedisCaptchaData = ICaptchaDataCache;
|
|
5
|
+
export declare class CacheRedisCaptcha extends BeanCacheRedisBase<TCacheRedisCaptchaKey, TCacheRedisCaptchaData> {
|
|
6
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IDecoratorControllerOptions } from 'vona-module-a-web';
|
|
2
|
+
import { BeanBase } from 'vona';
|
|
3
|
+
export interface IControllerOptionsCaptcha extends IDecoratorControllerOptions {
|
|
4
|
+
}
|
|
5
|
+
export declare class ControllerCaptcha extends BeanBase {
|
|
6
|
+
create(scene: string): Promise<import("vona-module-a-captcha").ICaptchaData>;
|
|
7
|
+
refresh(id: string, scene: string): Promise<import("vona-module-a-captcha").ICaptchaData>;
|
|
8
|
+
verifyImmediate(id: string, token: unknown): Promise<string>;
|
|
9
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { BeanInfo, BeanBase, uuidv4, beanFullNameFromOnionName, deepExtend, BeanScopeBase, createBeanDecorator } from 'vona';
|
|
2
|
+
import { Middleware } from 'vona-module-a-aspect';
|
|
3
|
+
import { getRandomInt } from '@cabloy/utils';
|
|
4
|
+
import { Bean, Scope } from 'vona-module-a-bean';
|
|
5
|
+
import { CacheRedis, BeanCacheRedisBase } from 'vona-module-a-cache';
|
|
6
|
+
import { Api, Arg } from 'vona-module-a-openapi';
|
|
7
|
+
import { Dto, Controller, Web } from 'vona-module-a-web';
|
|
8
|
+
import { Passport } from 'vona-module-a-user';
|
|
9
|
+
import z from 'zod';
|
|
10
|
+
|
|
11
|
+
var _dec$6, _dec2$6, _class$6;
|
|
12
|
+
let MiddlewareCaptcha = (_dec$6 = Middleware({
|
|
13
|
+
bodyField: 'captcha'
|
|
14
|
+
}), _dec2$6 = BeanInfo({
|
|
15
|
+
module: "a-captcha"
|
|
16
|
+
}), _dec$6(_class$6 = _dec2$6(_class$6 = class MiddlewareCaptcha extends BeanBase {
|
|
17
|
+
async execute(options, next) {
|
|
18
|
+
const sceneName = options.scene;
|
|
19
|
+
if (!sceneName) throw new Error('please specify the captcha scene name');
|
|
20
|
+
// captcha
|
|
21
|
+
const bodyField = options.bodyField;
|
|
22
|
+
const captcha = this.ctx.request.body[bodyField];
|
|
23
|
+
if (!captcha) throw new Error('not found captcha data');
|
|
24
|
+
const verified = await this.bean.captcha.verify(captcha.id, captcha.token, sceneName);
|
|
25
|
+
if (!verified) throw combineCaptchaError(bodyField, this.scope.locale.CaptchaInvalid());
|
|
26
|
+
// next
|
|
27
|
+
return next();
|
|
28
|
+
}
|
|
29
|
+
}) || _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
|
+
|
|
42
|
+
var _dec$5, _dec2$5, _class$5;
|
|
43
|
+
const SymbolProviders = Symbol('SymbolProviders');
|
|
44
|
+
let BeanCaptcha = (_dec$5 = Bean(), _dec2$5 = BeanInfo({
|
|
45
|
+
module: "a-captcha"
|
|
46
|
+
}), _dec$5(_class$5 = _dec2$5(_class$5 = class BeanCaptcha extends BeanBase {
|
|
47
|
+
constructor(...args) {
|
|
48
|
+
super(...args);
|
|
49
|
+
this[SymbolProviders] = {};
|
|
50
|
+
}
|
|
51
|
+
async create(sceneName) {
|
|
52
|
+
// resolve provider
|
|
53
|
+
const provider = await this._resolveProvider(sceneName);
|
|
54
|
+
if (!provider) throw new Error(`not found captcha provider for scene: ${sceneName}`);
|
|
55
|
+
// create
|
|
56
|
+
const beanInstance = this._getProviderInstance(provider.name);
|
|
57
|
+
const captcha = await beanInstance.create(provider.options);
|
|
58
|
+
// data
|
|
59
|
+
const id = uuidv4();
|
|
60
|
+
const captchaData = {
|
|
61
|
+
scene: sceneName,
|
|
62
|
+
provider: provider.name,
|
|
63
|
+
token: captcha.token
|
|
64
|
+
};
|
|
65
|
+
// cache
|
|
66
|
+
await this.scope.cacheRedis.captcha.set(captchaData, id, {
|
|
67
|
+
ttl: provider.options.ttl ?? this.scope.config.captchaProvider.ttl
|
|
68
|
+
});
|
|
69
|
+
// result
|
|
70
|
+
const result = {
|
|
71
|
+
id,
|
|
72
|
+
provider: provider.name,
|
|
73
|
+
payload: captcha.payload
|
|
74
|
+
};
|
|
75
|
+
if (this.scope.config.captcha.showToken) {
|
|
76
|
+
result.token = captcha.token;
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
async refresh(id, sceneName) {
|
|
81
|
+
// delete cache
|
|
82
|
+
await this.scope.cacheRedis.captcha.del(id);
|
|
83
|
+
// create
|
|
84
|
+
return await this.create(sceneName);
|
|
85
|
+
}
|
|
86
|
+
async verify(id, token, sceneName) {
|
|
87
|
+
const captchaData = await this.getCaptchaData(id);
|
|
88
|
+
if (!captchaData) return false;
|
|
89
|
+
// scene
|
|
90
|
+
if (captchaData.scene !== sceneName) return false;
|
|
91
|
+
// tokenSecondary
|
|
92
|
+
const tokenSecondary = captchaData.token2;
|
|
93
|
+
if (tokenSecondary) {
|
|
94
|
+
// delete cache
|
|
95
|
+
this.ctx.commit(async () => {
|
|
96
|
+
await this.scope.cacheRedis.captcha.del(id);
|
|
97
|
+
});
|
|
98
|
+
return tokenSecondary === token;
|
|
99
|
+
}
|
|
100
|
+
// provider
|
|
101
|
+
const beanInstance = this._getProviderInstance(captchaData.provider);
|
|
102
|
+
const providerOptions = this._getProviderOptions(captchaData.scene, captchaData.provider);
|
|
103
|
+
// verify
|
|
104
|
+
const verified = await beanInstance.verify(captchaData.token, token, providerOptions);
|
|
105
|
+
if (!verified) {
|
|
106
|
+
// do not mutate cache
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
// delete cache
|
|
110
|
+
this.ctx.commit(async () => {
|
|
111
|
+
await this.scope.cacheRedis.captcha.del(id);
|
|
112
|
+
});
|
|
113
|
+
// ok
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
async verifyImmediate(id, token) {
|
|
117
|
+
let captchaData = await this.getCaptchaData(id);
|
|
118
|
+
if (!captchaData) return false;
|
|
119
|
+
// tokenSecondary
|
|
120
|
+
let tokenSecondary = captchaData.token2;
|
|
121
|
+
if (tokenSecondary) return tokenSecondary; // maybe called more times
|
|
122
|
+
// provider
|
|
123
|
+
const beanInstance = this._getProviderInstance(captchaData.provider);
|
|
124
|
+
const providerOptions = this._getProviderOptions(captchaData.scene, captchaData.provider);
|
|
125
|
+
// verify
|
|
126
|
+
const verified = await beanInstance.verify(captchaData.token, token, providerOptions);
|
|
127
|
+
if (!verified) {
|
|
128
|
+
// do not mutate cache
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
// tokenSecondary
|
|
132
|
+
tokenSecondary = uuidv4();
|
|
133
|
+
captchaData = {
|
|
134
|
+
...captchaData,
|
|
135
|
+
token: undefined,
|
|
136
|
+
token2: tokenSecondary
|
|
137
|
+
};
|
|
138
|
+
// update cache
|
|
139
|
+
await this.scope.cacheRedis.captcha.set(captchaData, id, {
|
|
140
|
+
ttl: providerOptions.ttlSecondary ?? this.scope.config.captchaProvider.ttlSecondary
|
|
141
|
+
});
|
|
142
|
+
// ok
|
|
143
|
+
return tokenSecondary;
|
|
144
|
+
}
|
|
145
|
+
async getCaptchaData(id) {
|
|
146
|
+
return await this.scope.cacheRedis.captcha.get(id);
|
|
147
|
+
}
|
|
148
|
+
async updateCaptchaToken(id, token) {
|
|
149
|
+
let captchaData = await this.getCaptchaData(id);
|
|
150
|
+
if (!captchaData) return this.app.throw(403);
|
|
151
|
+
// provider
|
|
152
|
+
const providerOptions = this._getProviderOptions(captchaData.scene, captchaData.provider);
|
|
153
|
+
// update cache
|
|
154
|
+
captchaData = {
|
|
155
|
+
...captchaData,
|
|
156
|
+
token
|
|
157
|
+
};
|
|
158
|
+
await this.scope.cacheRedis.captcha.set(captchaData, id, {
|
|
159
|
+
ttl: providerOptions.ttl ?? this.scope.config.captchaProvider.ttl
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
_getProviderInstance(providerName) {
|
|
163
|
+
const beanFullName = beanFullNameFromOnionName(providerName, 'captchaProvider');
|
|
164
|
+
return this.bean._getBean(beanFullName);
|
|
165
|
+
}
|
|
166
|
+
_getProviderOptions(sceneName, providerName) {
|
|
167
|
+
// providers
|
|
168
|
+
const providers = this._getProviders(sceneName);
|
|
169
|
+
return providers[providerName];
|
|
170
|
+
}
|
|
171
|
+
async _resolveProvider(sceneName) {
|
|
172
|
+
// providers
|
|
173
|
+
const providers = this._getProviders(sceneName);
|
|
174
|
+
if (Object.keys(providers).length === 0) return;
|
|
175
|
+
// resolver
|
|
176
|
+
const onionSlice = this.bean.onion.captchaScene.getOnionSlice(sceneName);
|
|
177
|
+
const onionOptions = onionSlice.beanOptions.options;
|
|
178
|
+
const resolver = onionOptions?.resolver ?? resolverDefault;
|
|
179
|
+
const providerName = await resolver(this.ctx, providers);
|
|
180
|
+
if (!providerName) return;
|
|
181
|
+
return {
|
|
182
|
+
name: providerName,
|
|
183
|
+
options: providers[providerName]
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
_getProviders(sceneName) {
|
|
187
|
+
if (!this[SymbolProviders][sceneName]) {
|
|
188
|
+
const onionSlice = this.bean.onion.captchaScene.getOnionSlice(sceneName);
|
|
189
|
+
if (!onionSlice) throw new Error(`not found captcha scene: ${sceneName}`);
|
|
190
|
+
const onionOptions = onionSlice.beanOptions.options;
|
|
191
|
+
this[SymbolProviders][sceneName] = this._prepareProviders(onionOptions?.providers);
|
|
192
|
+
}
|
|
193
|
+
return this[SymbolProviders][sceneName];
|
|
194
|
+
}
|
|
195
|
+
_prepareProviders(providers) {
|
|
196
|
+
if (!providers) return {};
|
|
197
|
+
const providersNew = {};
|
|
198
|
+
for (const _key in providers) {
|
|
199
|
+
const key = _key;
|
|
200
|
+
const providerOptions = providers[key];
|
|
201
|
+
if (providerOptions === false) continue;
|
|
202
|
+
const onionSlice = this.bean.onion.captchaProvider.getOnionSliceEnabled(true, key);
|
|
203
|
+
if (!onionSlice) continue;
|
|
204
|
+
providersNew[key] = deepExtend({}, onionSlice.beanOptions.options, providerOptions);
|
|
205
|
+
}
|
|
206
|
+
return providersNew;
|
|
207
|
+
}
|
|
208
|
+
}) || _class$5) || _class$5);
|
|
209
|
+
async function resolverDefault(_ctx, providers) {
|
|
210
|
+
const keys = Object.keys(providers);
|
|
211
|
+
const index = getRandomInt(keys.length, 0);
|
|
212
|
+
return keys[index];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
var _dec$4, _dec2$4, _class$4;
|
|
216
|
+
let CacheRedisCaptcha = (_dec$4 = CacheRedis(), _dec2$4 = BeanInfo({
|
|
217
|
+
module: "a-captcha"
|
|
218
|
+
}), _dec$4(_class$4 = _dec2$4(_class$4 = class CacheRedisCaptcha extends BeanCacheRedisBase {}) || _class$4) || _class$4);
|
|
219
|
+
|
|
220
|
+
function _applyDecoratedDescriptor(i, e, r, n, l) {
|
|
221
|
+
var a = {};
|
|
222
|
+
return Object.keys(n).forEach(function (i) {
|
|
223
|
+
a[i] = n[i];
|
|
224
|
+
}), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, ("value" in a || a.initializer) && (a.writable = true), a = r.slice().reverse().reduce(function (r, n) {
|
|
225
|
+
return n(i, e, r) || r;
|
|
226
|
+
}, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a;
|
|
227
|
+
}
|
|
228
|
+
function _initializerDefineProperty(e, i, r, l) {
|
|
229
|
+
r && Object.defineProperty(e, i, {
|
|
230
|
+
enumerable: r.enumerable,
|
|
231
|
+
configurable: r.configurable,
|
|
232
|
+
writable: r.writable,
|
|
233
|
+
value: r.initializer ? r.initializer.call(l) : void 0
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
var _dec$3, _dec2$3, _dec3$2, _dec4$2, _dec5$2, _dec6$2, _dec7$1, _dec8$1, _dec9$1, _dec0$1, _class$3, _class2$2, _descriptor$1, _descriptor2$1, _descriptor3, _descriptor4;
|
|
238
|
+
let DtoCaptchaData = (_dec$3 = Dto(), _dec2$3 = BeanInfo({
|
|
239
|
+
module: "a-captcha"
|
|
240
|
+
}), _dec3$2 = Api.field(), _dec4$2 = Reflect.metadata("design:type", String), _dec5$2 = Api.field(), _dec6$2 = Reflect.metadata("design:type", String), _dec7$1 = Api.field(), _dec8$1 = Reflect.metadata("design:type", Object), _dec9$1 = Api.field(), _dec0$1 = Reflect.metadata("design:type", Object), _dec$3(_class$3 = _dec2$3(_class$3 = (_class2$2 = class DtoCaptchaData {
|
|
241
|
+
constructor() {
|
|
242
|
+
_initializerDefineProperty(this, "id", _descriptor$1, this);
|
|
243
|
+
_initializerDefineProperty(this, "provider", _descriptor2$1, this);
|
|
244
|
+
_initializerDefineProperty(this, "token", _descriptor3, this);
|
|
245
|
+
_initializerDefineProperty(this, "payload", _descriptor4, this);
|
|
246
|
+
}
|
|
247
|
+
}, _descriptor$1 = _applyDecoratedDescriptor(_class2$2.prototype, "id", [_dec3$2, _dec4$2], {
|
|
248
|
+
configurable: true,
|
|
249
|
+
enumerable: true,
|
|
250
|
+
writable: true,
|
|
251
|
+
initializer: null
|
|
252
|
+
}), _descriptor2$1 = _applyDecoratedDescriptor(_class2$2.prototype, "provider", [_dec5$2, _dec6$2], {
|
|
253
|
+
configurable: true,
|
|
254
|
+
enumerable: true,
|
|
255
|
+
writable: true,
|
|
256
|
+
initializer: null
|
|
257
|
+
}), _descriptor3 = _applyDecoratedDescriptor(_class2$2.prototype, "token", [_dec7$1, _dec8$1], {
|
|
258
|
+
configurable: true,
|
|
259
|
+
enumerable: true,
|
|
260
|
+
writable: true,
|
|
261
|
+
initializer: null
|
|
262
|
+
}), _descriptor4 = _applyDecoratedDescriptor(_class2$2.prototype, "payload", [_dec9$1, _dec0$1], {
|
|
263
|
+
configurable: true,
|
|
264
|
+
enumerable: true,
|
|
265
|
+
writable: true,
|
|
266
|
+
initializer: null
|
|
267
|
+
}), _class2$2)) || _class$3) || _class$3);
|
|
268
|
+
|
|
269
|
+
var _dec$2, _dec2$2, _dec3$1, _dec4$1, _dec5$1, _dec6$1, _class$2, _class2$1, _descriptor, _descriptor2;
|
|
270
|
+
let DtoCaptchaVerify = (_dec$2 = Dto(), _dec2$2 = BeanInfo({
|
|
271
|
+
module: "a-captcha"
|
|
272
|
+
}), _dec3$1 = Api.field(), _dec4$1 = Reflect.metadata("design:type", String), _dec5$1 = Api.field(), _dec6$1 = Reflect.metadata("design:type", Object), _dec$2(_class$2 = _dec2$2(_class$2 = (_class2$1 = class DtoCaptchaVerify {
|
|
273
|
+
constructor() {
|
|
274
|
+
_initializerDefineProperty(this, "id", _descriptor, this);
|
|
275
|
+
_initializerDefineProperty(this, "token", _descriptor2, this);
|
|
276
|
+
}
|
|
277
|
+
}, _descriptor = _applyDecoratedDescriptor(_class2$1.prototype, "id", [_dec3$1, _dec4$1], {
|
|
278
|
+
configurable: true,
|
|
279
|
+
enumerable: true,
|
|
280
|
+
writable: true,
|
|
281
|
+
initializer: null
|
|
282
|
+
}), _descriptor2 = _applyDecoratedDescriptor(_class2$1.prototype, "token", [_dec5$1, _dec6$1], {
|
|
283
|
+
configurable: true,
|
|
284
|
+
enumerable: true,
|
|
285
|
+
writable: true,
|
|
286
|
+
initializer: null
|
|
287
|
+
}), _class2$1)) || _class$2) || _class$2);
|
|
288
|
+
|
|
289
|
+
var _dec$1, _dec2$1, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec0, _dec1, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _class$1, _class2;
|
|
290
|
+
let ControllerCaptcha = (_dec$1 = Controller('captcha'), _dec2$1 = BeanInfo({
|
|
291
|
+
module: "a-captcha"
|
|
292
|
+
}), _dec3 = Web.post('create'), _dec4 = Api.body(DtoCaptchaData), _dec5 = Passport.public(), _dec6 = function (target, key) {
|
|
293
|
+
return Arg.body('scene')(target, key, 0);
|
|
294
|
+
}, _dec7 = Reflect.metadata("design:type", Function), _dec8 = Reflect.metadata("design:paramtypes", [String]), _dec9 = Web.post('refresh'), _dec0 = Api.body(DtoCaptchaData), _dec1 = Passport.public(), _dec10 = function (target, key) {
|
|
295
|
+
return Arg.body('id')(target, key, 0);
|
|
296
|
+
}, _dec11 = function (target, key) {
|
|
297
|
+
return Arg.body('scene')(target, key, 1);
|
|
298
|
+
}, _dec12 = Reflect.metadata("design:type", Function), _dec13 = Reflect.metadata("design:paramtypes", [String, String]), _dec14 = Web.post('verifyImmediate'), _dec15 = Api.body(z.string()), _dec16 = Passport.public(), _dec17 = function (target, key) {
|
|
299
|
+
return Arg.body('id')(target, key, 0);
|
|
300
|
+
}, _dec18 = function (target, key) {
|
|
301
|
+
return Arg.body('token')(target, key, 1);
|
|
302
|
+
}, _dec19 = Reflect.metadata("design:type", Function), _dec20 = Reflect.metadata("design:paramtypes", [String, Object]), _dec$1(_class$1 = _dec2$1(_class$1 = (_class2 = class ControllerCaptcha extends BeanBase {
|
|
303
|
+
async create(scene) {
|
|
304
|
+
return await this.bean.captcha.create(scene);
|
|
305
|
+
}
|
|
306
|
+
async refresh(id, scene) {
|
|
307
|
+
return await this.bean.captcha.refresh(id, scene);
|
|
308
|
+
}
|
|
309
|
+
async verifyImmediate(id, token) {
|
|
310
|
+
const verified = await this.bean.captcha.verifyImmediate(id, token);
|
|
311
|
+
if (!verified) this.app.throw(403);
|
|
312
|
+
return verified;
|
|
313
|
+
}
|
|
314
|
+
}, _applyDecoratedDescriptor(_class2.prototype, "create", [_dec3, _dec4, _dec5, _dec6, _dec7, _dec8], Object.getOwnPropertyDescriptor(_class2.prototype, "create"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, "refresh", [_dec9, _dec0, _dec1, _dec10, _dec11, _dec12, _dec13], Object.getOwnPropertyDescriptor(_class2.prototype, "refresh"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, "verifyImmediate", [_dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20], Object.getOwnPropertyDescriptor(_class2.prototype, "verifyImmediate"), _class2.prototype), _class2)) || _class$1) || _class$1);
|
|
315
|
+
|
|
316
|
+
function config(app) {
|
|
317
|
+
const showToken = !app.meta.isProd;
|
|
318
|
+
return {
|
|
319
|
+
captchaProvider: {
|
|
320
|
+
ttl: 20 * 60 * 1000,
|
|
321
|
+
ttlSecondary: 20 * 60 * 1000
|
|
322
|
+
},
|
|
323
|
+
captcha: {
|
|
324
|
+
showToken
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
var locale_en_us = {
|
|
330
|
+
CaptchaInvalid: 'Captcha Invalid'
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
var locale_zh_cn = {
|
|
334
|
+
CaptchaInvalid: '验证码不正确'
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
var _dec, _dec2, _class;
|
|
338
|
+
const locales = {
|
|
339
|
+
'en-us': locale_en_us,
|
|
340
|
+
'zh-cn': locale_zh_cn
|
|
341
|
+
};
|
|
342
|
+
let ScopeModuleACaptcha = (_dec = Scope(), _dec2 = BeanInfo({
|
|
343
|
+
module: "a-captcha"
|
|
344
|
+
}), _dec(_class = _dec2(_class = class ScopeModuleACaptcha extends BeanScopeBase {}) || _class) || _class);
|
|
345
|
+
function $locale(key) {
|
|
346
|
+
return `a-captcha::${key}`;
|
|
347
|
+
}
|
|
348
|
+
/** scope: end */
|
|
349
|
+
|
|
350
|
+
function CaptchaProvider(options) {
|
|
351
|
+
return createBeanDecorator('captchaProvider', options);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function CaptchaScene(options) {
|
|
355
|
+
return createBeanDecorator('captchaScene', options);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export { $locale, BeanCaptcha, CacheRedisCaptcha, CaptchaProvider, CaptchaScene, ControllerCaptcha, DtoCaptchaData, DtoCaptchaVerify, MiddlewareCaptcha, ScopeModuleACaptcha, config, locales };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ICaptchaProviderRecord } from './captchaProvider.ts';
|
|
2
|
+
import type { ICaptchaSceneRecord } from './captchaScene.ts';
|
|
3
|
+
export interface ICaptchaDataCache {
|
|
4
|
+
scene: keyof ICaptchaSceneRecord;
|
|
5
|
+
provider: keyof ICaptchaProviderRecord;
|
|
6
|
+
token?: unknown;
|
|
7
|
+
token2?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ICaptchaData {
|
|
10
|
+
id: string;
|
|
11
|
+
provider: keyof ICaptchaProviderRecord;
|
|
12
|
+
token?: unknown;
|
|
13
|
+
payload: unknown;
|
|
14
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { OmitNever } from 'vona';
|
|
2
|
+
import type { ServiceOnion, TypeOnionOptionsEnableSimple } from 'vona-module-a-onion';
|
|
3
|
+
export interface ICaptchaProviderRecord {
|
|
4
|
+
}
|
|
5
|
+
export interface ICaptchaProviderExecute<TOKEN = unknown, PAYLOAD = unknown> {
|
|
6
|
+
create(options: IDecoratorCaptchaProviderOptions): Promise<ICaptchaProviderData<TOKEN, PAYLOAD>>;
|
|
7
|
+
verify(token: TOKEN, tokenInput: TOKEN, options: IDecoratorCaptchaProviderOptions): Promise<boolean>;
|
|
8
|
+
}
|
|
9
|
+
export interface ICaptchaProviderData<TOKEN = unknown, PAYLOAD = unknown> {
|
|
10
|
+
token: TOKEN;
|
|
11
|
+
payload: PAYLOAD;
|
|
12
|
+
}
|
|
13
|
+
export interface IDecoratorCaptchaProviderOptions extends TypeOnionOptionsEnableSimple {
|
|
14
|
+
ttl?: number;
|
|
15
|
+
ttlSecondary?: number;
|
|
16
|
+
}
|
|
17
|
+
declare module 'vona-module-a-onion' {
|
|
18
|
+
interface BeanOnion {
|
|
19
|
+
captchaProvider: ServiceOnion<IDecoratorCaptchaProviderOptions, keyof ICaptchaProviderRecord>;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
declare module 'vona' {
|
|
23
|
+
interface ConfigOnions {
|
|
24
|
+
captchaProvider: OmitNever<ICaptchaProviderRecord>;
|
|
25
|
+
}
|
|
26
|
+
interface IBeanSceneRecord {
|
|
27
|
+
captchaProvider: never;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { OmitNever, PowerPartial, VonaContext } from 'vona';
|
|
2
|
+
import type { ServiceOnion, TypeUseOnionOmitOptionsEnable } from 'vona-module-a-onion';
|
|
3
|
+
import type { ICaptchaProviderRecord } from './captchaProvider.ts';
|
|
4
|
+
export interface ICaptchaSceneRecord {
|
|
5
|
+
}
|
|
6
|
+
export type ICaptchaSceneOptionsProviders = {
|
|
7
|
+
[K in keyof ICaptchaProviderRecord]?: PowerPartial<TypeUseOnionOmitOptionsEnable<ICaptchaProviderRecord[K]>> | boolean;
|
|
8
|
+
};
|
|
9
|
+
export interface ICaptchaSceneOptionsResolverResult<T extends keyof ICaptchaProviderRecord = keyof ICaptchaProviderRecord> {
|
|
10
|
+
name: T;
|
|
11
|
+
options: ICaptchaProviderRecord[T];
|
|
12
|
+
}
|
|
13
|
+
export type TypeCaptchaSceneOptionsResolver = (ctx: VonaContext, providers: ICaptchaProviderRecord) => Promise<keyof ICaptchaProviderRecord>;
|
|
14
|
+
export interface IDecoratorCaptchaSceneOptions {
|
|
15
|
+
resolver?: TypeCaptchaSceneOptionsResolver;
|
|
16
|
+
providers: ICaptchaSceneOptionsProviders;
|
|
17
|
+
}
|
|
18
|
+
declare module 'vona-module-a-onion' {
|
|
19
|
+
interface BeanOnion {
|
|
20
|
+
captchaScene: ServiceOnion<IDecoratorCaptchaSceneOptions, keyof ICaptchaSceneRecord>;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
declare module 'vona' {
|
|
24
|
+
interface ConfigOnions {
|
|
25
|
+
captchaScene: OmitNever<ICaptchaSceneRecord>;
|
|
26
|
+
}
|
|
27
|
+
interface IBeanSceneRecord {
|
|
28
|
+
captchaScene: never;
|
|
29
|
+
}
|
|
30
|
+
}
|
package/package.json
CHANGED
|
@@ -1,37 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona-module-a-captcha",
|
|
3
|
-
"version": "5.0.0",
|
|
4
3
|
"type": "module",
|
|
5
|
-
"
|
|
6
|
-
|
|
4
|
+
"version": "5.0.7",
|
|
5
|
+
"title": "a-captcha",
|
|
6
|
+
"vonaModule": {
|
|
7
|
+
"dependencies": {},
|
|
8
|
+
"onions": {
|
|
9
|
+
"captchaProvider": {
|
|
10
|
+
"optionsGlobalInterfaceName": "IDecoratorCaptchaProviderOptions",
|
|
11
|
+
"optionsGlobalInterfaceFrom": "vona-module-a-captcha",
|
|
12
|
+
"boilerplate": "cli/captchaProvider/boilerplate"
|
|
13
|
+
},
|
|
14
|
+
"captchaScene": {
|
|
15
|
+
"optionsGlobalInterfaceName": "IDecoratorCaptchaSceneOptions",
|
|
16
|
+
"optionsGlobalInterfaceFrom": "vona-module-a-captcha",
|
|
17
|
+
"boilerplate": "cli/captchaScene/boilerplate"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
7
20
|
},
|
|
21
|
+
"description": "",
|
|
22
|
+
"author": "",
|
|
23
|
+
"keywords": [
|
|
24
|
+
"Vona Module"
|
|
25
|
+
],
|
|
8
26
|
"exports": {
|
|
9
27
|
".": {
|
|
10
28
|
"types": [
|
|
11
29
|
"./src/index.ts",
|
|
12
30
|
"./dist/index.d.ts"
|
|
13
31
|
],
|
|
14
|
-
"
|
|
15
|
-
"import": "./dist/index.js",
|
|
16
|
-
"default": "./src/index.ts"
|
|
32
|
+
"default": "./dist/index.js"
|
|
17
33
|
},
|
|
18
34
|
"./package.json": "./package.json"
|
|
19
35
|
},
|
|
20
|
-
"description": "",
|
|
21
36
|
"files": [
|
|
22
37
|
"dist",
|
|
23
|
-
"static"
|
|
24
|
-
"typings"
|
|
38
|
+
"static"
|
|
25
39
|
],
|
|
26
40
|
"scripts": {
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
},
|
|
32
|
-
"keywords": [
|
|
33
|
-
"Cabloy Module"
|
|
34
|
-
],
|
|
35
|
-
"author": "zhennann",
|
|
36
|
-
"dependencies": {}
|
|
37
|
-
}
|
|
41
|
+
"clean": "rimraf dist tsconfig.build.tsbuildinfo",
|
|
42
|
+
"tsc:publish": "npm run clean && vona :bin:buildModule && tsc -p tsconfig.build.json"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/README.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# @cabloy/set
|