Merge branch 'dev' of gitea.51mars.com:Oak-Team/oak-backend-base into dev

This commit is contained in:
Xu Chang 2024-11-19 09:04:06 +08:00
commit bdd3eb6730
2 changed files with 92 additions and 8 deletions

View File

@ -1,6 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const crypto_1 = require("crypto");
const types_1 = require("oak-domain/lib/types");
const relationPath_1 = require("oak-domain/lib/utils/relationPath");
const assert_1 = tslib_1.__importDefault(require("assert"));
@ -11,6 +12,31 @@ const uuid_1 = require("oak-domain/lib/utils/uuid");
const lodash_2 = require("lodash");
const OAK_SYNC_HEADER_ENTITY = 'oak-sync-entity';
const OAK_SYNC_HEADER_ENTITY_ID = 'oak-sync-entity-id';
const OAK_SYNC_HEADER_TIMESTAMP = 'oak-sync-timestamp';
const OAK_SYNC_HEADER_NONCE = 'oak-sync-nonce';
const OAK_SYNC_HEADER_SIGN = 'oak-sync-sign';
function generateSignStr(body, ts, nonce) {
return `${body}\n${ts}\n${nonce}`;
}
async function sign(privateKey, body) {
const ts = Date.now();
const nonce = await (0, uuid_1.generateNewIdAsync)();
const sign2 = (0, crypto_1.createSign)('SHA256');
sign2.update(generateSignStr(body, `${ts}`, nonce));
sign2.end();
const signature = sign2.sign(privateKey).toString('hex');
return {
ts,
nonce,
signature,
};
}
function verify(publicKey, body, ts, nonce, signature) {
const verify2 = (0, crypto_1.createVerify)('SHA256');
verify2.update(generateSignStr(body, ts, nonce));
verify2.end();
return verify2.verify(publicKey, signature, 'hex');
}
class Synchronizer {
config;
schema;
@ -31,14 +57,19 @@ class Synchronizer {
const finalApi = (0, path_1.join)(api, selfEncryptInfo.id);
channel.queue = [];
try {
const body = JSON.stringify(opers);
const { ts, nonce, signature } = await sign(selfEncryptInfo.privateKey, body);
const res = await fetch(finalApi, {
method: 'post',
headers: {
'Content-Type': 'application/json',
[OAK_SYNC_HEADER_ENTITY]: entity,
[OAK_SYNC_HEADER_ENTITY_ID]: entityId,
[OAK_SYNC_HEADER_TIMESTAMP]: `${ts}`,
[OAK_SYNC_HEADER_NONCE]: nonce,
[OAK_SYNC_HEADER_SIGN]: signature,
},
body: JSON.stringify(opers),
body,
});
if (res.status !== 200) {
throw new Error(`sync数据时访问api「${finalApi}」的结果不是200。「${res.status}`);
@ -462,7 +493,7 @@ class Synchronizer {
fn: async (context, params, headers, req, body) => {
// body中是传过来的oper数组信息
const { entity, entityId } = params;
const { [OAK_SYNC_HEADER_ENTITY]: meEntity, [OAK_SYNC_HEADER_ENTITY_ID]: meEntityId } = headers;
const { [OAK_SYNC_HEADER_ENTITY]: meEntity, [OAK_SYNC_HEADER_ENTITY_ID]: meEntityId, [OAK_SYNC_HEADER_NONCE]: syncNonce, [OAK_SYNC_HEADER_TIMESTAMP]: syncTs, [OAK_SYNC_HEADER_SIGN]: syncSign } = headers;
if (process.env.NODE_ENV === 'development') {
console.log('接收到来自远端的sync数据', entity, JSON.stringify(body));
}
@ -493,7 +524,13 @@ class Synchronizer {
if (cxtInfo) {
await context.initialize(cxtInfo);
}
// todo 解密
const syncTimestamp = parseInt(syncTs, 10);
if (!(Date.now() - syncTimestamp < 10000)) {
throw new Error('同步时钟漂移过长');
}
if (!verify(publicKey, JSON.stringify(body), syncTs, syncNonce, syncSign)) {
throw new Error('sync验签失败');
}
const opers = body;
const ids = opers.map(ele => ele.id);
const existsIds = (await context.select('oper', {

View File

@ -1,3 +1,4 @@
import { createSign, createVerify } from 'crypto';
import {
EntityDict, StorageSchema, EndpointItem, RemotePullInfo, SelfEncryptInfo,
RemotePushInfo, PushEntityDef, PullEntityDef, SyncConfig, TriggerDataAttribute, TriggerUuidAttribute,
@ -16,6 +17,9 @@ import { merge, uniq, unset } from 'lodash';
const OAK_SYNC_HEADER_ENTITY = 'oak-sync-entity';
const OAK_SYNC_HEADER_ENTITY_ID = 'oak-sync-entity-id';
const OAK_SYNC_HEADER_TIMESTAMP = 'oak-sync-timestamp';
const OAK_SYNC_HEADER_NONCE = 'oak-sync-nonce';
const OAK_SYNC_HEADER_SIGN = 'oak-sync-sign';
// 一个channel是代表要推送的一个目标对象
type Channel<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> = {
@ -30,6 +34,32 @@ type Channel<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeC
onFailed?: SyncRemoteConfig<ED, Cxt>['onFailed'];
};
function generateSignStr(body: string, ts: string, nonce: string) {
return `${body}\n${ts}\n${nonce}`;
}
async function sign(privateKey: string, body: string) {
const ts = Date.now();
const nonce = await generateNewIdAsync();
const sign2 = createSign('SHA256');
sign2.update(generateSignStr(body, `${ts}`, nonce));
sign2.end();
const signature = sign2.sign(privateKey).toString('hex');
return {
ts,
nonce,
signature,
};
}
function verify(publicKey: string, body: string, ts: string, nonce: string, signature: string) {
const verify2 = createVerify('SHA256');
verify2.update(generateSignStr(body, ts, nonce));
verify2.end();
return verify2.verify(publicKey, signature, 'hex');
}
export default class Synchronizer<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> {
private config: SyncConfig<ED, Cxt>;
private schema: StorageSchema<ED>;
@ -71,20 +101,25 @@ export default class Synchronizer<ED extends EntityDict & BaseEntityDict, Cxt ex
channel.queue = [];
try {
const body = JSON.stringify(opers);
const { ts, nonce, signature } = await sign(selfEncryptInfo.privateKey, body);
const res = await fetch(finalApi, {
method: 'post',
headers: {
'Content-Type': 'application/json',
[OAK_SYNC_HEADER_ENTITY]: entity as string,
[OAK_SYNC_HEADER_ENTITY_ID]: entityId,
[OAK_SYNC_HEADER_TIMESTAMP]: `${ts}`,
[OAK_SYNC_HEADER_NONCE]: nonce,
[OAK_SYNC_HEADER_SIGN]: signature,
},
body: JSON.stringify(opers),
body,
});
if (res.status !== 200) {
throw new Error(`sync数据时访问api「${finalApi}」的结果不是200。「${res.status}`);
}
const json = await res.json();
if (json.exception) {
throw new Error(`sync数据时远端服务报异常「${json.exception}`);
@ -595,7 +630,11 @@ export default class Synchronizer<ED extends EntityDict & BaseEntityDict, Cxt ex
fn: async (context, params, headers, req, body): Promise<{}> => {
// body中是传过来的oper数组信息
const { entity, entityId } = params;
const { [OAK_SYNC_HEADER_ENTITY]: meEntity, [OAK_SYNC_HEADER_ENTITY_ID]: meEntityId } = headers;
const {
[OAK_SYNC_HEADER_ENTITY]: meEntity, [OAK_SYNC_HEADER_ENTITY_ID]: meEntityId,
[OAK_SYNC_HEADER_NONCE]: syncNonce, [OAK_SYNC_HEADER_TIMESTAMP]: syncTs,
[OAK_SYNC_HEADER_SIGN]: syncSign
} = headers;
if (process.env.NODE_ENV === 'development') {
console.log('接收到来自远端的sync数据', entity, JSON.stringify(body));
@ -631,7 +670,15 @@ export default class Synchronizer<ED extends EntityDict & BaseEntityDict, Cxt ex
if (cxtInfo) {
await context.initialize(cxtInfo);
}
// todo 解密
const syncTimestamp = parseInt(syncTs as string, 10);
if (!(Date.now() - syncTimestamp < 10000)) {
throw new Error('同步时钟漂移过长');
}
if (!verify(publicKey, JSON.stringify(body), syncTs as string, syncNonce as string, syncSign as string)) {
throw new Error('sync验签失败');
}
const opers = body as ED['oper']['Schema'][];