Merge branch 'dev' of gitea.51mars.com:Oak-Team/oak-backend-base into dev
This commit is contained in:
commit
bdd3eb6730
|
|
@ -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', {
|
||||
|
|
|
|||
|
|
@ -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'][];
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue