@jayfong/x-server 1.21.0 → 1.24.0

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/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [1.24.0](https://github.com/jfWorks/x-server/compare/v1.23.0...v1.24.0) (2022-05-02)
6
+
7
+
8
+ ### Features
9
+
10
+ * add PayService ([c8d5ecc](https://github.com/jfWorks/x-server/commit/c8d5eccf2393c1a1bf98415564032b09ffb10497))
11
+
12
+ ## [1.23.0](https://github.com/jfWorks/x-server/compare/v1.22.0...v1.23.0) (2022-05-01)
13
+
14
+
15
+ ### Features
16
+
17
+ * **captcha:** add generateDataUrl ([54205df](https://github.com/jfWorks/x-server/commit/54205df24d0cc397029d8c66ea17020d7cb5c5fd))
18
+
19
+ ## [1.22.0](https://github.com/jfWorks/x-server/compare/v1.21.0...v1.22.0) (2022-05-01)
20
+
21
+
22
+ ### Features
23
+
24
+ * **cors:** allowAll ([5ce4b5d](https://github.com/jfWorks/x-server/commit/5ce4b5d9183f68a32173d77de569f00c5409ccdc))
25
+
5
26
  ## [1.21.0](https://github.com/jfWorks/x-server/compare/v1.20.1...v1.21.0) (2022-04-29)
6
27
 
7
28
 
package/lib/_cjs/index.js CHANGED
@@ -178,6 +178,14 @@ Object.keys(_mail).forEach(function (key) {
178
178
  exports[key] = _mail[key];
179
179
  });
180
180
 
181
+ var _pay = require("./services/pay");
182
+
183
+ Object.keys(_pay).forEach(function (key) {
184
+ if (key === "default" || key === "__esModule") return;
185
+ if (key in exports && exports[key] === _pay[key]) return;
186
+ exports[key] = _pay[key];
187
+ });
188
+
181
189
  var _rate_limit = require("./services/rate_limit");
182
190
 
183
191
  Object.keys(_rate_limit).forEach(function (key) {
@@ -21,9 +21,10 @@ class CorsPlugin {
21
21
 
22
22
  register(fastify) {
23
23
  const allows = this.options.allow.map(item => ({
24
- type: item.startsWith('*.') ? 'endsWith' : 'equal',
24
+ type: item === '*' ? 'all' : item.startsWith('*.') ? 'endsWith' : 'equal',
25
25
  domain: item.startsWith('*.') ? item.replace('*.', '.') : item
26
26
  }));
27
+ const allowAll = allows.length === 1 && allows[0].type === 'all';
27
28
  const check = (0, _vtils.memoize)(origin => {
28
29
  let i = origin.indexOf(':');
29
30
  let j = origin.indexOf(':', i + 1);
@@ -34,7 +35,7 @@ class CorsPlugin {
34
35
  return pass;
35
36
  });
36
37
  fastify.register(_fastifyCors.default, {
37
- origin: (origin, cb) => cb(null, origin ? check(origin) : true),
38
+ origin: (origin, cb) => allowAll ? cb(null, true) : cb(null, origin ? check(origin) : true),
38
39
  maxAge: (0, _date.ms)(this.options.ttl, true)
39
40
  });
40
41
  }
@@ -7,6 +7,8 @@ exports.CaptchaService = void 0;
7
7
 
8
8
  var _svgCaptcha = _interopRequireDefault(require("svg-captcha"));
9
9
 
10
+ var _miniSvgDataUri = _interopRequireDefault(require("mini-svg-data-uri"));
11
+
10
12
  var _x = require("../x");
11
13
 
12
14
  class CaptchaService {
@@ -40,6 +42,20 @@ class CaptchaService {
40
42
  };
41
43
  }
42
44
 
45
+ async generateDataUrl() {
46
+ const {
47
+ token,
48
+ code,
49
+ svg
50
+ } = await this.generateImage();
51
+ const dataUrl = (0, _miniSvgDataUri.default)(svg);
52
+ return {
53
+ token: token,
54
+ code: code,
55
+ dataUrl: dataUrl
56
+ };
57
+ }
58
+
43
59
  async verify(options) {
44
60
  if (options.code.length !== this.options.size) {
45
61
  return false;
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
4
+
5
+ exports.__esModule = true;
6
+ exports.PayService = void 0;
7
+
8
+ var _form = _interopRequireDefault(require("alipay-sdk/lib/form"));
9
+
10
+ var _alipaySdk = _interopRequireDefault(require("alipay-sdk"));
11
+
12
+ var _crypto = _interopRequireDefault(require("crypto"));
13
+
14
+ var _got = _interopRequireDefault(require("got"));
15
+
16
+ var _x = require("../x");
17
+
18
+ class PayService {
19
+ constructor(options) {
20
+ this.options = options;
21
+ this.serviceName = 'pay';
22
+ this.alipaySdk = void 0;
23
+ }
24
+
25
+ async prepareAlipay(options) {
26
+ if (!this.alipaySdk) {
27
+ this.alipaySdk = new _alipaySdk.default({
28
+ appId: this.options.alipay.appId,
29
+ privateKey: this.options.alipay.privateKey,
30
+ alipayPublicKey: this.options.alipay.publicKey
31
+ });
32
+ }
33
+
34
+ const token = await _x.x.cache.save('x', '1h');
35
+ const formData = new _form.default();
36
+ formData.setMethod('get');
37
+ formData.addField('notifyUrl', options.notifyUrl);
38
+ formData.addField('returnUrl', options.returnUrl);
39
+ formData.addField('bizContent', {
40
+ outTradeNo: options.tradeNumber,
41
+ productCode: 'FAST_INSTANT_TRADE_PAY',
42
+ totalAmount: options.totalMoney,
43
+ subject: options.goodsName,
44
+ goodsType: '0',
45
+ passbackParams: token
46
+ });
47
+ const payUrl = await this.alipaySdk.exec('alipay.trade.page.pay', {}, {
48
+ formData: formData
49
+ });
50
+ return {
51
+ payUrl: payUrl
52
+ };
53
+ }
54
+
55
+ async prepareWepay(options) {
56
+ const token = await _x.x.cache.save('x', '1h');
57
+ const res = await this.wepayRequest('https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi', {
58
+ appid: this.options.wepay.appId,
59
+ mchid: this.options.wepay.merchantId,
60
+ description: options.goodsName,
61
+ out_trade_no: options.tradeNumber,
62
+ attach: token,
63
+ notify_url: options.notifyUrl,
64
+ amount: {
65
+ total: options.totalMoney * 100,
66
+ currency: 'CNY'
67
+ },
68
+ payer: {
69
+ openid: options.openid
70
+ }
71
+ });
72
+
73
+ if (!res || !res.prepay_id) {
74
+ const error = res && res.message || '支付错误';
75
+ throw new Error(error);
76
+ }
77
+
78
+ const params = {
79
+ appId: this.options.wepay.appId,
80
+ timestamp: String(Math.round(Date.now() / 1000)),
81
+ nonceStr: Math.random().toString().substr(2, 12),
82
+ package: `prepay_id=${res.prepay_id}`,
83
+ signType: 'RSA',
84
+ paySign: ''
85
+ };
86
+ params.paySign = this.wepaySign([params.appId, params.timestamp, params.nonceStr, params.package]);
87
+ return {
88
+ payParams: params
89
+ };
90
+ }
91
+
92
+ async verifyNotifyData(data) {
93
+ if (!data || typeof data !== 'object') return false;
94
+ const token = // 微信支付
95
+ data.resource ? JSON.parse(this.wepayDecrypt(data.resource)).attach : // 支付宝
96
+ data.passback_params;
97
+ if (!token) return false;
98
+ if ((await _x.x.cache.get(token)) == null) return false;
99
+ await _x.x.cache.remove(token);
100
+ return true;
101
+ }
102
+
103
+ wepaySign(data) {
104
+ return _crypto.default.createSign('RSA-SHA256').update(data.map(v => `${v}\n`).join('')).sign(this.options.wepay.privateKey, 'base64');
105
+ }
106
+
107
+ wepayDecrypt(payload) {
108
+ const ciphertextBuffer = Buffer.from(payload.ciphertext, 'base64');
109
+ const authTag = ciphertextBuffer.slice(ciphertextBuffer.length - 16);
110
+ const data = ciphertextBuffer.slice(0, ciphertextBuffer.length - 16);
111
+
112
+ const decipherIv = _crypto.default.createDecipheriv('aes-256-gcm', this.options.wepay.secretKey, payload.nonce);
113
+
114
+ decipherIv.setAuthTag(Buffer.from(authTag));
115
+ decipherIv.setAAD(Buffer.from(payload.associated_data));
116
+ const decryptStr = decipherIv.update(data, undefined, 'utf8');
117
+ decipherIv.final();
118
+ return decryptStr;
119
+ }
120
+
121
+ async wepayRequest(url, data) {
122
+ const path = url.replace(/^https?:\/\/[^/]+/, '');
123
+ const body = JSON.stringify(data);
124
+ const params = {
125
+ method: 'POST',
126
+ url: path,
127
+ time: String(Math.round(Date.now() / 1000)),
128
+ rand: Math.random().toString().substr(2, 12),
129
+ body: body,
130
+ signature: ''
131
+ };
132
+ params.signature = this.wepaySign([params.method, params.url, params.time, params.rand, params.body]);
133
+ const Authorization = [`WECHATPAY2-SHA256-RSA2048 `, `mchid="${this.options.wepay.merchantId}",`, `nonce_str="${params.rand}",`, `signature="${params.signature}",`, `timestamp="${params.time}",`, `serial_no="${this.options.wepay.certificateSerialNumber}"`].join('');
134
+ const res = await _got.default.post(url, {
135
+ json: data,
136
+ headers: {
137
+ Authorization
138
+ },
139
+ responseType: 'json',
140
+ resolveBodyOnly: true
141
+ });
142
+ return res;
143
+ }
144
+
145
+ }
146
+
147
+ exports.PayService = PayService;
package/lib/index.d.ts CHANGED
@@ -20,6 +20,7 @@ export * from './services/captcha';
20
20
  export * from './services/dispose';
21
21
  export * from './services/jwt';
22
22
  export * from './services/mail';
23
+ export * from './services/pay';
23
24
  export * from './services/rate_limit';
24
25
  export * from './services/redis';
25
26
  export * from './x';
package/lib/index.js CHANGED
@@ -21,6 +21,7 @@ export * from "./services/captcha";
21
21
  export * from "./services/dispose";
22
22
  export * from "./services/jwt";
23
23
  export * from "./services/mail";
24
+ export * from "./services/pay";
24
25
  export * from "./services/rate_limit";
25
26
  export * from "./services/redis";
26
27
  export * from "./x"; // @endindex
@@ -12,9 +12,10 @@ export class CorsPlugin {
12
12
 
13
13
  register(fastify) {
14
14
  const allows = this.options.allow.map(item => ({
15
- type: item.startsWith('*.') ? 'endsWith' : 'equal',
15
+ type: item === '*' ? 'all' : item.startsWith('*.') ? 'endsWith' : 'equal',
16
16
  domain: item.startsWith('*.') ? item.replace('*.', '.') : item
17
17
  }));
18
+ const allowAll = allows.length === 1 && allows[0].type === 'all';
18
19
  const check = memoize(origin => {
19
20
  let i = origin.indexOf(':');
20
21
  let j = origin.indexOf(':', i + 1);
@@ -25,7 +26,7 @@ export class CorsPlugin {
25
26
  return pass;
26
27
  });
27
28
  fastify.register(FastifyCors, {
28
- origin: (origin, cb) => cb(null, origin ? check(origin) : true),
29
+ origin: (origin, cb) => allowAll ? cb(null, true) : cb(null, origin ? check(origin) : true),
29
30
  maxAge: ms(this.options.ttl, true)
30
31
  });
31
32
  }
@@ -19,6 +19,11 @@ export interface CaptchaGenerateImageResult {
19
19
  code: string;
20
20
  svg: string;
21
21
  }
22
+ export interface CaptchaGenerateDataUrlResult {
23
+ token: string;
24
+ code: string;
25
+ dataUrl: string;
26
+ }
22
27
  export interface CaptchaVerifyOptions {
23
28
  token: string;
24
29
  code: string;
@@ -29,6 +34,7 @@ export declare class CaptchaService implements BaseService {
29
34
  constructor(options: CaptchaOptions);
30
35
  generate(): Promise<CaptchaGenerateResult>;
31
36
  generateImage(): Promise<CaptchaGenerateImageResult>;
37
+ generateDataUrl(): Promise<CaptchaGenerateDataUrlResult>;
32
38
  verify(options: CaptchaVerifyOptions): Promise<boolean>;
33
39
  }
34
40
  declare module '../x' {
@@ -1,4 +1,5 @@
1
1
  import svgCaptcha from 'svg-captcha';
2
+ import svgToDataUrl from 'mini-svg-data-uri';
2
3
  import { x } from "../x";
3
4
  export class CaptchaService {
4
5
  constructor(options) {
@@ -30,6 +31,20 @@ export class CaptchaService {
30
31
  };
31
32
  }
32
33
 
34
+ async generateDataUrl() {
35
+ const {
36
+ token,
37
+ code,
38
+ svg
39
+ } = await this.generateImage();
40
+ const dataUrl = svgToDataUrl(svg);
41
+ return {
42
+ token: token,
43
+ code: code,
44
+ dataUrl: dataUrl
45
+ };
46
+ }
47
+
33
48
  async verify(options) {
34
49
  if (options.code.length !== this.options.size) {
35
50
  return false;
@@ -0,0 +1,81 @@
1
+ import { BaseService } from './base';
2
+ export interface PayOptions {
3
+ /** 支付宝 */
4
+ alipay?: {
5
+ /** 应用 ID */
6
+ appId: string;
7
+ /** 商户应用私钥 */
8
+ privateKey: string;
9
+ /** 支付宝公钥,用于对返回结果验签 */
10
+ publicKey: string;
11
+ };
12
+ /** 微信支付 */
13
+ wepay?: {
14
+ /** 公众号 APPID */
15
+ appId: string;
16
+ /** 商户 ID */
17
+ merchantId: string;
18
+ /** 商户应用私钥 */
19
+ privateKey: string;
20
+ /** 证书序列号 */
21
+ certificateSerialNumber: string;
22
+ /** 秘钥,用于解密请求平台证书、回调数据 */
23
+ secretKey: string;
24
+ };
25
+ }
26
+ export interface PayPrepareAlipayOptions {
27
+ /** 商品名称 */
28
+ goodsName: string;
29
+ /** 交易单号 */
30
+ tradeNumber: string;
31
+ /** 总金额 */
32
+ totalMoney: number;
33
+ /** 通知地址(POST) */
34
+ notifyUrl: string;
35
+ /** 返回地址 */
36
+ returnUrl: string;
37
+ }
38
+ export interface PayPrepareAlipayResult {
39
+ /** 支付地址,需跳转过去 */
40
+ payUrl: string;
41
+ }
42
+ export interface PayPrepareWepayOptions {
43
+ /** 商品名称 */
44
+ goodsName: string;
45
+ /** 交易单号 */
46
+ tradeNumber: string;
47
+ /** 总金额 */
48
+ totalMoney: number;
49
+ /** 通知地址(POST) */
50
+ notifyUrl: string;
51
+ /** 对应公众号用户的 openid */
52
+ openid: string;
53
+ }
54
+ export interface PayPrepareWepayResult {
55
+ /** 支付参数,可直接传给 JSSDK 调用 */
56
+ payParams: {
57
+ appId: string;
58
+ timestamp: string;
59
+ nonceStr: string;
60
+ package: string;
61
+ signType: string;
62
+ paySign: string;
63
+ };
64
+ }
65
+ export declare class PayService implements BaseService {
66
+ private options;
67
+ serviceName: string;
68
+ private alipaySdk;
69
+ constructor(options: PayOptions);
70
+ prepareAlipay(options: PayPrepareAlipayOptions): Promise<PayPrepareAlipayResult>;
71
+ prepareWepay(options: PayPrepareWepayOptions): Promise<PayPrepareWepayResult>;
72
+ verifyNotifyData(data: any): Promise<boolean>;
73
+ private wepaySign;
74
+ private wepayDecrypt;
75
+ private wepayRequest;
76
+ }
77
+ declare module '../x' {
78
+ interface X {
79
+ pay: PayService;
80
+ }
81
+ }
@@ -0,0 +1,131 @@
1
+ import AlipayFormData from 'alipay-sdk/lib/form';
2
+ import AlipaySdk from 'alipay-sdk';
3
+ import crypto from 'crypto';
4
+ import got from 'got';
5
+ import { x } from "../x";
6
+ export class PayService {
7
+ constructor(options) {
8
+ this.options = options;
9
+ this.serviceName = 'pay';
10
+ this.alipaySdk = void 0;
11
+ }
12
+
13
+ async prepareAlipay(options) {
14
+ if (!this.alipaySdk) {
15
+ this.alipaySdk = new AlipaySdk({
16
+ appId: this.options.alipay.appId,
17
+ privateKey: this.options.alipay.privateKey,
18
+ alipayPublicKey: this.options.alipay.publicKey
19
+ });
20
+ }
21
+
22
+ const token = await x.cache.save('x', '1h');
23
+ const formData = new AlipayFormData();
24
+ formData.setMethod('get');
25
+ formData.addField('notifyUrl', options.notifyUrl);
26
+ formData.addField('returnUrl', options.returnUrl);
27
+ formData.addField('bizContent', {
28
+ outTradeNo: options.tradeNumber,
29
+ productCode: 'FAST_INSTANT_TRADE_PAY',
30
+ totalAmount: options.totalMoney,
31
+ subject: options.goodsName,
32
+ goodsType: '0',
33
+ passbackParams: token
34
+ });
35
+ const payUrl = await this.alipaySdk.exec('alipay.trade.page.pay', {}, {
36
+ formData: formData
37
+ });
38
+ return {
39
+ payUrl: payUrl
40
+ };
41
+ }
42
+
43
+ async prepareWepay(options) {
44
+ const token = await x.cache.save('x', '1h');
45
+ const res = await this.wepayRequest('https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi', {
46
+ appid: this.options.wepay.appId,
47
+ mchid: this.options.wepay.merchantId,
48
+ description: options.goodsName,
49
+ out_trade_no: options.tradeNumber,
50
+ attach: token,
51
+ notify_url: options.notifyUrl,
52
+ amount: {
53
+ total: options.totalMoney * 100,
54
+ currency: 'CNY'
55
+ },
56
+ payer: {
57
+ openid: options.openid
58
+ }
59
+ });
60
+
61
+ if (!res || !res.prepay_id) {
62
+ const error = res && res.message || '支付错误';
63
+ throw new Error(error);
64
+ }
65
+
66
+ const params = {
67
+ appId: this.options.wepay.appId,
68
+ timestamp: String(Math.round(Date.now() / 1000)),
69
+ nonceStr: Math.random().toString().substr(2, 12),
70
+ package: `prepay_id=${res.prepay_id}`,
71
+ signType: 'RSA',
72
+ paySign: ''
73
+ };
74
+ params.paySign = this.wepaySign([params.appId, params.timestamp, params.nonceStr, params.package]);
75
+ return {
76
+ payParams: params
77
+ };
78
+ }
79
+
80
+ async verifyNotifyData(data) {
81
+ if (!data || typeof data !== 'object') return false;
82
+ const token = // 微信支付
83
+ data.resource ? JSON.parse(this.wepayDecrypt(data.resource)).attach : // 支付宝
84
+ data.passback_params;
85
+ if (!token) return false;
86
+ if ((await x.cache.get(token)) == null) return false;
87
+ await x.cache.remove(token);
88
+ return true;
89
+ }
90
+
91
+ wepaySign(data) {
92
+ return crypto.createSign('RSA-SHA256').update(data.map(v => `${v}\n`).join('')).sign(this.options.wepay.privateKey, 'base64');
93
+ }
94
+
95
+ wepayDecrypt(payload) {
96
+ const ciphertextBuffer = Buffer.from(payload.ciphertext, 'base64');
97
+ const authTag = ciphertextBuffer.slice(ciphertextBuffer.length - 16);
98
+ const data = ciphertextBuffer.slice(0, ciphertextBuffer.length - 16);
99
+ const decipherIv = crypto.createDecipheriv('aes-256-gcm', this.options.wepay.secretKey, payload.nonce);
100
+ decipherIv.setAuthTag(Buffer.from(authTag));
101
+ decipherIv.setAAD(Buffer.from(payload.associated_data));
102
+ const decryptStr = decipherIv.update(data, undefined, 'utf8');
103
+ decipherIv.final();
104
+ return decryptStr;
105
+ }
106
+
107
+ async wepayRequest(url, data) {
108
+ const path = url.replace(/^https?:\/\/[^/]+/, '');
109
+ const body = JSON.stringify(data);
110
+ const params = {
111
+ method: 'POST',
112
+ url: path,
113
+ time: String(Math.round(Date.now() / 1000)),
114
+ rand: Math.random().toString().substr(2, 12),
115
+ body: body,
116
+ signature: ''
117
+ };
118
+ params.signature = this.wepaySign([params.method, params.url, params.time, params.rand, params.body]);
119
+ const Authorization = [`WECHATPAY2-SHA256-RSA2048 `, `mchid="${this.options.wepay.merchantId}",`, `nonce_str="${params.rand}",`, `signature="${params.signature}",`, `timestamp="${params.time}",`, `serial_no="${this.options.wepay.certificateSerialNumber}"`].join('');
120
+ const res = await got.post(url, {
121
+ json: data,
122
+ headers: {
123
+ Authorization
124
+ },
125
+ responseType: 'json',
126
+ resolveBodyOnly: true
127
+ });
128
+ return res;
129
+ }
130
+
131
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jayfong/x-server",
3
- "version": "1.21.0",
3
+ "version": "1.24.0",
4
4
  "license": "ISC",
5
5
  "sideEffects": false,
6
6
  "main": "lib/_cjs/index.js",
@@ -33,6 +33,7 @@
33
33
  "@types/jsonwebtoken": "^8.5.8",
34
34
  "@types/nodemailer": "^6.4.4",
35
35
  "@types/ws": "^8.5.3",
36
+ "alipay-sdk": "^3.2.0",
36
37
  "bufferutil": "^4.0.6",
37
38
  "bull": "^4.8.1",
38
39
  "chokidar": "^3.5.3",
@@ -50,9 +51,11 @@
50
51
  "fastify-multipart": "^5.3.1",
51
52
  "fastify-websocket": "^4.2.2",
52
53
  "fs-extra": "^10.0.1",
54
+ "got": "^11.8.2",
53
55
  "http-errors": "^1.8.1",
54
56
  "ioredis": "^4.28.5",
55
57
  "jsonwebtoken": "^8.5.1",
58
+ "mini-svg-data-uri": "^1.4.4",
56
59
  "node-ssh": "^12.0.4",
57
60
  "nodemailer": "^6.7.3",
58
61
  "pino-pretty": "^7.6.1",