icynet management
This commit is contained in:
parent
fa93a47603
commit
26401c130a
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,6 +2,8 @@
|
|||||||
/dist
|
/dist
|
||||||
/node_modules
|
/node_modules
|
||||||
*.zone
|
*.zone
|
||||||
|
*.env
|
||||||
|
*.db
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
|
@ -6,6 +6,7 @@ import { AppService } from './app.service';
|
|||||||
import { ObjectsModule } from './modules/objects/objects.module';
|
import { ObjectsModule } from './modules/objects/objects.module';
|
||||||
import { ZoneModule } from './modules/zone/zone.module';
|
import { ZoneModule } from './modules/zone/zone.module';
|
||||||
import configuration from './config/configuration';
|
import configuration from './config/configuration';
|
||||||
|
import { IcynetModule } from './modules/icynet/icynet.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -17,6 +18,7 @@ import configuration from './config/configuration';
|
|||||||
}),
|
}),
|
||||||
ObjectsModule,
|
ObjectsModule,
|
||||||
ZoneModule,
|
ZoneModule,
|
||||||
|
IcynetModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
|
@ -11,6 +11,7 @@ export default () => ({
|
|||||||
},
|
},
|
||||||
cacheTTL: parseInt(process.env.ZONE_CACHE_TTL, 10) || 1600,
|
cacheTTL: parseInt(process.env.ZONE_CACHE_TTL, 10) || 1600,
|
||||||
zoneDir: '.',
|
zoneDir: '.',
|
||||||
|
icynetKey: process.env.ICYNET_KEY || 'ch4ng3 m3!',
|
||||||
rndc: {
|
rndc: {
|
||||||
host: process.env.RNDC_SERVER || '127.0.0.1',
|
host: process.env.RNDC_SERVER || '127.0.0.1',
|
||||||
port: parseInt(process.env.RNDC_PORT, 10) || 953,
|
port: parseInt(process.env.RNDC_PORT, 10) || 953,
|
||||||
|
23
src/guards/icynet.guard.ts
Normal file
23
src/guards/icynet.guard.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { Request } from 'express';
|
||||||
|
import { timingSafeEqual } from 'crypto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class IcynetGuard implements CanActivate {
|
||||||
|
constructor(private config: ConfigService) {}
|
||||||
|
|
||||||
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
|
const request = context.switchToHttp().getRequest<Request>();
|
||||||
|
const authHeader = request.headers.authorization;
|
||||||
|
|
||||||
|
if (!authHeader) return false;
|
||||||
|
|
||||||
|
const [base, token] = authHeader.split(' ');
|
||||||
|
if (!base || base.toLowerCase() !== 'bearer' || !token) return false;
|
||||||
|
|
||||||
|
const configured = this.config.get<string>('icynetKey');
|
||||||
|
|
||||||
|
return timingSafeEqual(Buffer.from(token), Buffer.from(configured));
|
||||||
|
}
|
||||||
|
}
|
82
src/modules/icynet/icynet.controller.ts
Normal file
82
src/modules/icynet/icynet.controller.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
HttpException,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Put,
|
||||||
|
Query,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiExcludeController } from '@nestjs/swagger';
|
||||||
|
import { IcynetGuard } from 'src/guards/icynet.guard';
|
||||||
|
import { ManagerService } from '../objects/manager/manager.service';
|
||||||
|
import { ZoneEntity } from '../objects/manager/zone.entity';
|
||||||
|
|
||||||
|
@ApiExcludeController()
|
||||||
|
@UseGuards(IcynetGuard)
|
||||||
|
@Controller({
|
||||||
|
path: 'api/v1/icynet',
|
||||||
|
})
|
||||||
|
export class IcynetController {
|
||||||
|
constructor(private service: ManagerService) {}
|
||||||
|
|
||||||
|
@Get('zones')
|
||||||
|
async getZoneList(@Query('uuid') uuid?: string) {
|
||||||
|
let list: ZoneEntity[] = [];
|
||||||
|
|
||||||
|
if (uuid) {
|
||||||
|
list = await this.service.getZonesByIcynetUUID(uuid);
|
||||||
|
} else {
|
||||||
|
list = await this.service.getAllZones();
|
||||||
|
}
|
||||||
|
|
||||||
|
return list.map(({ zone }) => zone);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('access')
|
||||||
|
async getAccessList() {
|
||||||
|
return this.service.getZonesWithIcynet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('zone/:domain/:uuid')
|
||||||
|
async addZoneAccess(
|
||||||
|
@Param('uuid') uuid: string,
|
||||||
|
@Param('domain') domain: string,
|
||||||
|
) {
|
||||||
|
const success = await this.service.authorizeIcynetUser(uuid, domain);
|
||||||
|
return { success };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('zone/:domain/:uuid')
|
||||||
|
async removeZoneAccess(
|
||||||
|
@Param('uuid') uuid: string,
|
||||||
|
@Param('domain') domain: string,
|
||||||
|
) {
|
||||||
|
const success = await this.service.revokeIcynetUser(uuid, domain);
|
||||||
|
return { success };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('zone/:domain/:uuid')
|
||||||
|
async getAccess(
|
||||||
|
@Param('uuid') uuid: string,
|
||||||
|
@Param('domain') domain: string,
|
||||||
|
) {
|
||||||
|
const added = await this.service.createIcynetAccessKey(uuid, domain);
|
||||||
|
if (!added) throw new HttpException('Invalid request', 400);
|
||||||
|
return { token: added.key };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('zone/:domain')
|
||||||
|
async addZone(@Param('domain') domain: string) {
|
||||||
|
const added = await this.service.addZone(domain);
|
||||||
|
return { success: !!added };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('zone/:domain')
|
||||||
|
async removeZone(@Param('domain') domain: string) {
|
||||||
|
const success = await this.service.removeZone(domain);
|
||||||
|
return { success };
|
||||||
|
}
|
||||||
|
}
|
9
src/modules/icynet/icynet.module.ts
Normal file
9
src/modules/icynet/icynet.module.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ObjectsModule } from '../objects/objects.module';
|
||||||
|
import { IcynetController } from './icynet.controller';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [ObjectsModule],
|
||||||
|
controllers: [IcynetController],
|
||||||
|
})
|
||||||
|
export class IcynetModule {}
|
@ -25,6 +25,6 @@ export class AccessEntity {
|
|||||||
@UpdateDateColumn()
|
@UpdateDateColumn()
|
||||||
public updated_at: Date;
|
public updated_at: Date;
|
||||||
|
|
||||||
@Column({ type: 'varchar', nullable: true })
|
@Column({ type: 'datetime', nullable: true })
|
||||||
public expires_at?: Date;
|
public expires_at?: Date;
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,10 @@ export class ManagerService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getAllZones(): Promise<ZoneEntity[]> {
|
||||||
|
return this.zone.find();
|
||||||
|
}
|
||||||
|
|
||||||
public async getAllKeys(zone: string): Promise<AccessEntity[]> {
|
public async getAllKeys(zone: string): Promise<AccessEntity[]> {
|
||||||
const obj = await this.getZone(zone);
|
const obj = await this.getZone(zone);
|
||||||
if (!obj) return [];
|
if (!obj) return [];
|
||||||
@ -54,6 +58,7 @@ export class ManagerService {
|
|||||||
public async getZonesByIcynetUUID(uuid: string): Promise<ZoneEntity[]> {
|
public async getZonesByIcynetUUID(uuid: string): Promise<ZoneEntity[]> {
|
||||||
const actor = await this.actors.findOne({
|
const actor = await this.actors.findOne({
|
||||||
where: { icynetUUID: uuid },
|
where: { icynetUUID: uuid },
|
||||||
|
relations: ['zones'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!actor) return [];
|
if (!actor) return [];
|
||||||
@ -61,6 +66,20 @@ export class ManagerService {
|
|||||||
return actor.zones;
|
return actor.zones;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getZonesWithIcynet(): Promise<Record<string, string[]>> {
|
||||||
|
const actors = await this.actors.find({
|
||||||
|
relations: ['zones'],
|
||||||
|
});
|
||||||
|
|
||||||
|
return actors.reduce<Record<string, string[]>>((obj, current) => {
|
||||||
|
for (const item of current.zones) {
|
||||||
|
if (!obj[item.zone]) obj[item.zone] = [];
|
||||||
|
obj[item.zone].push(current.icynetUUID);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new temporary API key for Icy Network user for their domain.
|
* Create a new temporary API key for Icy Network user for their domain.
|
||||||
* @param icynetUUID Icy Network user UUID
|
* @param icynetUUID Icy Network user UUID
|
||||||
@ -80,7 +99,7 @@ export class ManagerService {
|
|||||||
const newObject = {
|
const newObject = {
|
||||||
key: getToken(),
|
key: getToken(),
|
||||||
zone: entry,
|
zone: entry,
|
||||||
expires_at: Date.now() + 24 * 60 * 60 * 1000,
|
expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000),
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.access.save(newObject);
|
return this.access.save(newObject);
|
||||||
@ -103,10 +122,14 @@ export class ManagerService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const actor = await this.actors.findOne({
|
let actor = await this.actors.findOne({
|
||||||
where: { icynetUUID },
|
where: { icynetUUID },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!actor) {
|
||||||
|
actor = await this.actors.save({ icynetUUID });
|
||||||
|
}
|
||||||
|
|
||||||
actor.zones = [...zones, zone];
|
actor.zones = [...zones, zone];
|
||||||
await this.actors.save(actor);
|
await this.actors.save(actor);
|
||||||
return true;
|
return true;
|
||||||
@ -125,12 +148,13 @@ export class ManagerService {
|
|||||||
if (!zone) return false;
|
if (!zone) return false;
|
||||||
|
|
||||||
const zones = await this.getZonesByIcynetUUID(icynetUUID);
|
const zones = await this.getZonesByIcynetUUID(icynetUUID);
|
||||||
if (zones.length || !zones.some((item) => item.zone === zone.zone)) {
|
if (!zones.length || !zones.some((item) => item.zone === zone.zone)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const actor = await this.actors.findOne({
|
const actor = await this.actors.findOne({
|
||||||
where: { icynetUUID },
|
where: { icynetUUID },
|
||||||
|
relations: ['zones'],
|
||||||
});
|
});
|
||||||
|
|
||||||
actor.zones = actor.zones.filter((item) => item.zone !== zone.zone);
|
actor.zones = actor.zones.filter((item) => item.zone !== zone.zone);
|
||||||
@ -168,4 +192,17 @@ export class ManagerService {
|
|||||||
zone: domain,
|
zone: domain,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a managed zone.
|
||||||
|
* @param domain Zone to remove
|
||||||
|
* @returns boolean
|
||||||
|
*/
|
||||||
|
public async removeZone(domain: string): Promise<boolean> {
|
||||||
|
const zone = await this.getZone(domain);
|
||||||
|
if (!zone) return false;
|
||||||
|
|
||||||
|
await this.zone.remove(zone);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import crypto from 'crypto';
|
import { randomBytes } from 'crypto';
|
||||||
|
|
||||||
export function getToken() {
|
export function getToken() {
|
||||||
return crypto.randomBytes(256 / 8).toString('hex');
|
return randomBytes(256 / 8).toString('hex');
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user