Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
21caa93461 | ||
![]() |
ad77e4cf3d | ||
![]() |
62ccb927b2 | ||
![]() |
42df8debb0 | ||
![]() |
5e56a91bd3 | ||
![]() |
c8926d99be | ||
![]() |
4b1ad8511d | ||
![]() |
3551e5084c | ||
![]() |
6948958cf3 | ||
![]() |
f494a89f5c |
@ -13,13 +13,18 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-ses": "^3.577.0",
|
||||
"@fastify/cors": "^8.2.0",
|
||||
"@fastify/formbody": "^7.4.0",
|
||||
"@fastify/helmet": "^10.1.0",
|
||||
"@fastify/jwt": "^6.7.0",
|
||||
"@wecom/crypto": "^1.0.1",
|
||||
"dotenv": "^16.0.3",
|
||||
"fastify": "^4.14.1",
|
||||
"fastify-plugin": "^4.5.0",
|
||||
"fastify-xml-body-parser": "^2.2.0",
|
||||
"form-data": "^4.0.0",
|
||||
"mailgun.js": "^10.2.1",
|
||||
"nodemailer": "^6.9.1",
|
||||
"tracer": "^1.1.6"
|
||||
},
|
||||
|
@ -37,6 +37,7 @@ export class ApiServer {
|
||||
|
||||
private registerPlugins() {
|
||||
this.server.register(require('@fastify/formbody'))
|
||||
this.server.register(require('fastify-xml-body-parser'))
|
||||
this.server.register(zReqParserPlugin)
|
||||
this.server.register(helmet, { hidePoweredBy: false })
|
||||
this.server.register(zTokenParserPlugin)
|
||||
|
@ -12,6 +12,6 @@ class MailController extends BaseController {
|
||||
throw new ZError(10, 'params mismatch')
|
||||
}
|
||||
const result = await new MailQueue().addTaskToQueue(message)
|
||||
return { msgId: result.messageId }
|
||||
return { msgId: result.messageId, messageId: result.messageId }
|
||||
}
|
||||
}
|
||||
|
36
src/controllers/workflow.controller.ts
Normal file
36
src/controllers/workflow.controller.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import BaseController from 'common/base.controller'
|
||||
import { ZError } from 'common/ZError'
|
||||
import { role, router } from 'decorators/router'
|
||||
import { getSignature, decrypt } from '@wecom/crypto'
|
||||
|
||||
class WorkFlowController extends BaseController {
|
||||
@role('anon')
|
||||
@router('get /workflow/notify')
|
||||
async wxNotifyCheck(req, res) {
|
||||
const token = process.env.WX_TOKEN
|
||||
const aesKey = process.env.WX_AES_KEY
|
||||
let { msg_signature, timestamp, nonce, echostr } = req.params
|
||||
const signature = getSignature(token, timestamp, nonce, echostr)
|
||||
if (msg_signature !== signature) {
|
||||
throw new ZError(10, 'sign check failed')
|
||||
}
|
||||
const { message, id } = decrypt(aesKey, echostr)
|
||||
res.send(message)
|
||||
}
|
||||
|
||||
@role('anon')
|
||||
@router('post /workflow/notify')
|
||||
async flowNotify(req, res) {
|
||||
let { msg_signature, timestamp, nonce, xml } = req.params
|
||||
const token = process.env.WX_TOKEN
|
||||
const aesKey = process.env.WX_AES_KEY
|
||||
const signature = getSignature(token, timestamp, nonce, xml.Encrypt)
|
||||
if (msg_signature !== signature) {
|
||||
throw new ZError(10, 'sign check failed')
|
||||
}
|
||||
const { message, id } = decrypt(aesKey, xml.Encrypt)
|
||||
console.log(id)
|
||||
console.log(message)
|
||||
res.send('success')
|
||||
}
|
||||
}
|
@ -1,27 +1,61 @@
|
||||
import { singleton } from 'decorators/singleton'
|
||||
import { createTransport, Transporter } from 'nodemailer'
|
||||
import Mail from 'nodemailer/lib/mailer'
|
||||
import FormData from 'form-data'
|
||||
import Mailgun, { InputFormData } from 'mailgun.js'
|
||||
import { ZError } from 'common/ZError'
|
||||
import * as aws from '@aws-sdk/client-ses'
|
||||
|
||||
@singleton
|
||||
export class MailService {
|
||||
private transporter: Transporter
|
||||
private mailClient: any
|
||||
private awsClient: any
|
||||
constructor() {
|
||||
const options = {
|
||||
host: process.env.MAIL_SMTP_HOST,
|
||||
secure: true,
|
||||
auth: {
|
||||
user: process.env.MAIL_SMTP_USER,
|
||||
pass: process.env.MAIL_SMTP_PASS,
|
||||
// const options = {
|
||||
// host: process.env.MAIL_SMTP_HOST,
|
||||
// secure: true,
|
||||
// auth: {
|
||||
// user: process.env.MAIL_SMTP_USER,
|
||||
// pass: process.env.MAIL_SMTP_PASS,
|
||||
// },
|
||||
// logger: true,
|
||||
// debug: false,
|
||||
// }
|
||||
// // @ts-ignore
|
||||
// this.transporter = createTransport(options, {})
|
||||
|
||||
const mailgun = new Mailgun(FormData)
|
||||
this.mailClient = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY })
|
||||
const ses = new aws.SES({
|
||||
region: 'ap-southeast-1', // Your region will need to be updated
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY,
|
||||
secretAccessKey: process.env.AWS_SECRET_KEY,
|
||||
},
|
||||
logger: true,
|
||||
debug: false,
|
||||
}
|
||||
// @ts-ignore
|
||||
this.transporter = createTransport(options, {})
|
||||
})
|
||||
this.transporter = createTransport({
|
||||
SES: { ses, aws },
|
||||
})
|
||||
}
|
||||
|
||||
public async send(message: Mail.Options) {
|
||||
await this.transporter.verify()
|
||||
return this.transporter.sendMail(message)
|
||||
}
|
||||
|
||||
public async sendMailgun(message: Mail.Options) {
|
||||
const domain = 'counterfire.games'
|
||||
const sendResult = await this.mailClient.messages.create(domain, {
|
||||
from: message.from,
|
||||
to: message.to,
|
||||
subject: message.subject,
|
||||
html: message.html,
|
||||
text: message.text,
|
||||
})
|
||||
if (sendResult.status !== 200) {
|
||||
throw new ZError(20, sendResult.message)
|
||||
}
|
||||
return { messageId: sendResult.id }
|
||||
}
|
||||
}
|
||||
|
53
src/services/WechatWorkService.ts
Normal file
53
src/services/WechatWorkService.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { singleton } from 'decorators/singleton'
|
||||
|
||||
const WX_API_HOST = 'https://qyapi.weixin.qq.com'
|
||||
@singleton
|
||||
export class WechatWorkService {
|
||||
private accessToken: string
|
||||
private tokenExpire: number
|
||||
private wxToken: string
|
||||
private wxAesKey: string
|
||||
private wxCorpId: string
|
||||
private wxCorpSecret: string
|
||||
|
||||
constructor() {
|
||||
this.wxToken = process.env.WX_TOKEN
|
||||
this.wxAesKey = process.env.WX_AES_KEY
|
||||
this.wxCorpId = process.env.WX_CORP_ID
|
||||
this.wxCorpSecret = process.env.WX_CORP_SECRET
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信企业号的access_token
|
||||
* https://developer.work.weixin.qq.com/resource/devtool
|
||||
*/
|
||||
public async refreshAccessToken() {
|
||||
const url = `${WX_API_HOST}/cgi-bin/gettoken`
|
||||
// use axios get url
|
||||
let config = {
|
||||
method: 'get',
|
||||
maxBodyLength: Infinity,
|
||||
url,
|
||||
}
|
||||
let response = await axios.request(config).then(response => {
|
||||
return response.data
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 获取审批申请详情
|
||||
* https://developer.work.weixin.qq.com/devtool/interface/alone?id=18615
|
||||
* @param spNo 审批单号
|
||||
*/
|
||||
public async fetchApprovalDetail(spNo: string) {
|
||||
const url = `${WX_API_HOST}/cgi-bin/oa/getapprovaldetail`
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据media_id获取文件
|
||||
* https://developer.work.weixin.qq.com/devtool/interface/alone?id=18615
|
||||
* @param mediaId
|
||||
*/
|
||||
public async fetchFile(mediaId: string) {
|
||||
const url = `${WX_API_HOST}/cgi-bin/media/get`
|
||||
}
|
||||
}
|
59
src/utils/security.util.ts
Normal file
59
src/utils/security.util.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import crypto from 'crypto'
|
||||
const ENCODER = 'base64'
|
||||
const REG_KEY = /^[0-9a-fA-F]{63,64}$/
|
||||
|
||||
export function isEncrypt(msg: string) {
|
||||
return !REG_KEY.test(msg)
|
||||
}
|
||||
|
||||
export function aesEncrypt(text: string, password: string, iv: string) {
|
||||
var md5 = crypto.createHash('md5')
|
||||
const key = md5.update(password).digest('hex')
|
||||
let cipher = crypto.createCipheriv('aes-256-cbc', key, iv)
|
||||
let encrypted = cipher.update(text, 'utf8', ENCODER)
|
||||
encrypted += cipher.final(ENCODER)
|
||||
return encrypted
|
||||
}
|
||||
|
||||
export function aesDecrypt(encryptedText: string, password: string, iv: string) {
|
||||
var md5 = crypto.createHash('md5')
|
||||
const key = md5.update(password).digest('hex')
|
||||
let decipher = crypto.createDecipheriv('aes-256-cbc', key, iv)
|
||||
let decrypted = decipher.update(encryptedText, ENCODER, 'utf8')
|
||||
return decrypted + decipher.final('utf8')
|
||||
}
|
||||
|
||||
export function sha512(password: string, salt: string) {
|
||||
let hash = crypto.createHmac('sha512', salt)
|
||||
hash.update(password)
|
||||
let value = hash.digest('hex')
|
||||
return {
|
||||
salt: salt,
|
||||
passwordHash: value,
|
||||
}
|
||||
}
|
||||
|
||||
export function genRandomString(length: number) {
|
||||
return crypto
|
||||
.randomBytes(Math.ceil(length / 2))
|
||||
.toString('hex')
|
||||
.slice(0, length)
|
||||
}
|
||||
|
||||
export function uuid() {
|
||||
return crypto.randomUUID()
|
||||
}
|
||||
|
||||
export function md5(content: string) {
|
||||
var md5 = crypto.createHash('md5')
|
||||
return md5.update(content).digest('hex')
|
||||
}
|
||||
|
||||
export function sha1(content: string) {
|
||||
var md5 = crypto.createHash('sha1')
|
||||
return md5.update(content).digest('hex')
|
||||
}
|
||||
|
||||
export function base64Decode(str: string) {
|
||||
return Buffer.from(str, ENCODER).toString()
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user