|
|
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
|
|
Patch,
|
|
|
|
|
Post,
|
|
|
|
|
Query,
|
|
|
|
|
Req,
|
|
|
|
|
} from '@nestjs/common';
|
|
|
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
|
|
|
import { Repository } from 'typeorm';
|
|
|
|
|
@@ -22,12 +23,6 @@ import { GuildNode } from '../entities/guild-node.entity';
|
|
|
|
|
import { AuditService } from '../audit/audit.service';
|
|
|
|
|
import { RegisterNodeDto } from './dto.register-node.dto';
|
|
|
|
|
import { UpdateNodeStatusDto } from './dto.update-node-status.dto';
|
|
|
|
|
import {
|
|
|
|
|
buildCanonical,
|
|
|
|
|
safeEqualHex,
|
|
|
|
|
signCanonical,
|
|
|
|
|
verifyRequestTime,
|
|
|
|
|
} from '../common/hmac';
|
|
|
|
|
import { FABRIC_PROTOCOL_VERSION, normalizeVersion } from '../common/version';
|
|
|
|
|
|
|
|
|
|
@Controller('nodes')
|
|
|
|
|
@@ -40,12 +35,19 @@ export class NodesController {
|
|
|
|
|
|
|
|
|
|
@Post('register')
|
|
|
|
|
async register(
|
|
|
|
|
@Req() req: { ip?: string; socket?: { remoteAddress?: string } },
|
|
|
|
|
@Body() body: RegisterNodeDto,
|
|
|
|
|
@Headers('x-fabric-signature') signature?: string,
|
|
|
|
|
@Headers('x-fabric-timestamp') timestamp?: string,
|
|
|
|
|
@Headers('x-fabric-nonce') nonce?: string,
|
|
|
|
|
@Headers('x-fabric-version') fabricVersion?: string,
|
|
|
|
|
) {
|
|
|
|
|
const remoteAddress = (req.ip ?? req.socket?.remoteAddress ?? '').toLowerCase();
|
|
|
|
|
const isLoopback =
|
|
|
|
|
remoteAddress === '127.0.0.1' ||
|
|
|
|
|
remoteAddress === '::1' ||
|
|
|
|
|
remoteAddress === '::ffff:127.0.0.1';
|
|
|
|
|
if (!isLoopback) {
|
|
|
|
|
throw new ForbiddenException('register endpoint only allows localhost caller');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const requestedVersion = normalizeVersion(fabricVersion);
|
|
|
|
|
if (requestedVersion !== FABRIC_PROTOCOL_VERSION) {
|
|
|
|
|
throw new HttpException(
|
|
|
|
|
@@ -61,23 +63,6 @@ export class NodesController {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const secret = process.env.CENTER_SHARED_SECRET as string;
|
|
|
|
|
if (!signature || !timestamp || !nonce || !verifyRequestTime(timestamp)) {
|
|
|
|
|
throw new ForbiddenException('invalid hmac headers');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const canonical = buildCanonical({
|
|
|
|
|
method: 'POST',
|
|
|
|
|
path: '/api/nodes/register',
|
|
|
|
|
timestamp,
|
|
|
|
|
nonce,
|
|
|
|
|
body: JSON.stringify(body),
|
|
|
|
|
});
|
|
|
|
|
const expected = signCanonical(secret, canonical);
|
|
|
|
|
if (!safeEqualHex(signature, expected)) {
|
|
|
|
|
throw new ForbiddenException('invalid shared secret');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existedByNodeId = await this.nodeRepo.findOne({
|
|
|
|
|
where: { nodeId: body.nodeId },
|
|
|
|
|
});
|
|
|
|
|
|