feat(center-nodes): validate shared secret on node registration
This commit is contained in:
18
Fabric.Backend.Center/src/nodes/dto.register-node.dto.ts
Normal file
18
Fabric.Backend.Center/src/nodes/dto.register-node.dto.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { IsString, IsUrl, MinLength } from 'class-validator';
|
||||||
|
|
||||||
|
export class RegisterNodeDto {
|
||||||
|
@IsString()
|
||||||
|
@MinLength(3)
|
||||||
|
nodeId!: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@MinLength(2)
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@IsUrl({ require_tld: false })
|
||||||
|
endpoint!: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@MinLength(8)
|
||||||
|
sharedSecret!: string;
|
||||||
|
}
|
||||||
@@ -1,24 +1,53 @@
|
|||||||
import { Body, Controller, Get, Post } from '@nestjs/common';
|
import {
|
||||||
|
Body,
|
||||||
type NodeRegistration = {
|
Controller,
|
||||||
nodeId?: string;
|
ForbiddenException,
|
||||||
name?: string;
|
Get,
|
||||||
endpoint?: string;
|
Post,
|
||||||
handshakeProof?: string;
|
} from '@nestjs/common';
|
||||||
};
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { GuildNode } from '../entities/guild-node.entity';
|
||||||
|
import { RegisterNodeDto } from './dto.register-node.dto';
|
||||||
|
|
||||||
@Controller('nodes')
|
@Controller('nodes')
|
||||||
export class NodesController {
|
export class NodesController {
|
||||||
private readonly nodes: NodeRegistration[] = [];
|
constructor(
|
||||||
|
@InjectRepository(GuildNode)
|
||||||
|
private readonly nodeRepo: Repository<GuildNode>,
|
||||||
|
) {}
|
||||||
|
|
||||||
@Post('register')
|
@Post('register')
|
||||||
register(@Body() body: NodeRegistration) {
|
async register(@Body() body: RegisterNodeDto) {
|
||||||
this.nodes.push(body);
|
if (body.sharedSecret !== process.env.CENTER_SHARED_SECRET) {
|
||||||
return { status: 'accepted', handshake: 'todo-verify-shared-secret', node: body };
|
throw new ForbiddenException('invalid shared secret');
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = this.nodeRepo.create({
|
||||||
|
nodeId: body.nodeId,
|
||||||
|
name: body.name,
|
||||||
|
endpoint: body.endpoint,
|
||||||
|
status: 'active',
|
||||||
|
});
|
||||||
|
const saved = await this.nodeRepo.save(node);
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 'accepted',
|
||||||
|
node: {
|
||||||
|
id: saved.id,
|
||||||
|
nodeId: saved.nodeId,
|
||||||
|
name: saved.name,
|
||||||
|
endpoint: saved.endpoint,
|
||||||
|
status: saved.status,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
list() {
|
async list() {
|
||||||
return { items: this.nodes };
|
const items = await this.nodeRepo.find({
|
||||||
|
order: { createdAt: 'DESC' },
|
||||||
|
});
|
||||||
|
return { items };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { NodesController } from './nodes.controller';
|
import { NodesController } from './nodes.controller';
|
||||||
|
import { GuildNode } from '../entities/guild-node.entity';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([GuildNode])],
|
||||||
controllers: [NodesController],
|
controllers: [NodesController],
|
||||||
})
|
})
|
||||||
export class NodesModule {}
|
export class NodesModule {}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
- [x] DTO + 参数校验 + 错误码规范
|
- [x] DTO + 参数校验 + 错误码规范
|
||||||
|
|
||||||
### 1.2 Guild Node 注册与握手
|
### 1.2 Guild Node 注册与握手
|
||||||
- [ ] `POST /nodes/register` shared-secret 校验
|
- [x] `POST /nodes/register` shared-secret 校验
|
||||||
- [ ] node 唯一性校验(nodeId/endpoint)
|
- [ ] node 唯一性校验(nodeId/endpoint)
|
||||||
- [ ] node 状态模型(active/offline/revoked)
|
- [ ] node 状态模型(active/offline/revoked)
|
||||||
- [ ] `GET /nodes` 列表 + 分页
|
- [ ] `GET /nodes` 列表 + 分页
|
||||||
|
|||||||
Reference in New Issue
Block a user