refactor(center): local-only guild register endpoint without shared secret
This commit is contained in:
@@ -15,6 +15,3 @@ JWT_ACCESS_SECRET=change-me-access
|
|||||||
JWT_REFRESH_SECRET=change-me-refresh
|
JWT_REFRESH_SECRET=change-me-refresh
|
||||||
JWT_ACCESS_EXPIRES_IN=15m
|
JWT_ACCESS_EXPIRES_IN=15m
|
||||||
JWT_REFRESH_EXPIRES_IN=30d
|
JWT_REFRESH_EXPIRES_IN=30d
|
||||||
|
|
||||||
# Center <-> Guild handshake
|
|
||||||
CENTER_SHARED_SECRET=change-me-center-secret
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ function validateEnv(): void {
|
|||||||
requireEnv('DB_USER');
|
requireEnv('DB_USER');
|
||||||
requireEnv('DB_PASSWORD');
|
requireEnv('DB_PASSWORD');
|
||||||
requireEnv('DB_NAME');
|
requireEnv('DB_NAME');
|
||||||
requireEnv('CENTER_SHARED_SECRET');
|
|
||||||
requireEnv('JWT_ACCESS_SECRET');
|
requireEnv('JWT_ACCESS_SECRET');
|
||||||
requireEnv('JWT_REFRESH_SECRET');
|
requireEnv('JWT_REFRESH_SECRET');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
Patch,
|
Patch,
|
||||||
Post,
|
Post,
|
||||||
Query,
|
Query,
|
||||||
|
Req,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
@@ -22,12 +23,6 @@ import { GuildNode } from '../entities/guild-node.entity';
|
|||||||
import { AuditService } from '../audit/audit.service';
|
import { AuditService } from '../audit/audit.service';
|
||||||
import { RegisterNodeDto } from './dto.register-node.dto';
|
import { RegisterNodeDto } from './dto.register-node.dto';
|
||||||
import { UpdateNodeStatusDto } from './dto.update-node-status.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';
|
import { FABRIC_PROTOCOL_VERSION, normalizeVersion } from '../common/version';
|
||||||
|
|
||||||
@Controller('nodes')
|
@Controller('nodes')
|
||||||
@@ -40,12 +35,19 @@ export class NodesController {
|
|||||||
|
|
||||||
@Post('register')
|
@Post('register')
|
||||||
async register(
|
async register(
|
||||||
|
@Req() req: { ip?: string; socket?: { remoteAddress?: string } },
|
||||||
@Body() body: RegisterNodeDto,
|
@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,
|
@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);
|
const requestedVersion = normalizeVersion(fabricVersion);
|
||||||
if (requestedVersion !== FABRIC_PROTOCOL_VERSION) {
|
if (requestedVersion !== FABRIC_PROTOCOL_VERSION) {
|
||||||
throw new HttpException(
|
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({
|
const existedByNodeId = await this.nodeRepo.findOne({
|
||||||
where: { nodeId: body.nodeId },
|
where: { nodeId: body.nodeId },
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user