465 lines
No EOL
12 KiB
JavaScript
465 lines
No EOL
12 KiB
JavaScript
import {
|
|
object,
|
|
array,
|
|
string,
|
|
number,
|
|
enums,
|
|
boolean,
|
|
literal,
|
|
optional,
|
|
dynamic,
|
|
nullable,
|
|
defaulted,
|
|
union,
|
|
size,
|
|
create,
|
|
assert,
|
|
define,
|
|
} from "superstruct";
|
|
import { Database } from "all.db";
|
|
import crypto from "node:crypto";
|
|
import {
|
|
ipv4,
|
|
ipv6
|
|
} from "cidr-block";
|
|
import path from "node:path";
|
|
import { EventEmitter } from "node:events";
|
|
|
|
|
|
class ObjectModel {
|
|
#modelName;
|
|
#modelStruct;
|
|
#modelStore;
|
|
#dynRef = {};
|
|
#hooks = new EventEmitter();
|
|
|
|
constructor(settings) {
|
|
this.#modelName = settings.name;
|
|
this.#modelStruct = settings.model;
|
|
this.#modelStore = new Database({
|
|
dataPath: path.join(process.cwd(), 'data/database', `${settings.name}.json`)
|
|
});
|
|
|
|
if (settings.hooks) {
|
|
Object.entries(settings.hooks).forEach(([eventName, eventFunction]) => {
|
|
this.on(eventName, eventFunction);
|
|
})
|
|
}
|
|
|
|
this.#dynRef.refOne = dynamic((objectId, context) => {
|
|
let permittedObjIds = Object.values(this.getAll()).map(item => item.id);
|
|
return enums(permittedObjIds);
|
|
});
|
|
|
|
this.#dynRef.refMany = dynamic((objectId, context) => {
|
|
let permittedObjIds = Object.values(this.getAll()).map(item => item.id);
|
|
return array(
|
|
enums(permittedObjIds)
|
|
);
|
|
});
|
|
|
|
if (settings.dynRef instanceof Object) {
|
|
this.#dynRef = Object.assign(this.#dynRef, settings.dynRef)
|
|
}
|
|
}
|
|
|
|
complete(data) {
|
|
try {
|
|
let result = create(data, this.#modelStruct);
|
|
return result;
|
|
} catch (error) {
|
|
throw new Error(`error completing ${this.#modelName}: ` + error.message);
|
|
}
|
|
}
|
|
validate(data) {
|
|
try {
|
|
assert(data, this.#modelStruct);
|
|
return data;
|
|
} catch (error) {
|
|
throw new Error(`error validating ${this.#modelName}: ` + error.message);
|
|
}
|
|
}
|
|
|
|
getAll() {
|
|
return this.#modelStore.getAll();
|
|
}
|
|
getById(objectId) {
|
|
return this.#modelStore.get(objectId);
|
|
}
|
|
create(objectData) {
|
|
// try {
|
|
const newData = this.validate(
|
|
this.complete(objectData)
|
|
);
|
|
|
|
if (this.#modelStore.exists(newData.id)) {
|
|
throw new Error(`failed create ${this.#modelName} with ID ${newData.id}. already existing in database.`);
|
|
}
|
|
|
|
this.#hooks.emit('beforeCreate', null, newData);
|
|
this.#modelStore.set(newData.id, newData);
|
|
this.#hooks.emit('afterCreate', newData);
|
|
return newData;
|
|
// } catch (error) {
|
|
// throw error;
|
|
// }
|
|
}
|
|
update(objectData) {
|
|
// try {
|
|
const newData = this.validate(
|
|
this.complete(objectData)
|
|
);
|
|
const oldData = this.getById(newData.id)
|
|
|
|
if (!oldData) {
|
|
throw new Error(`failed update ${this.#modelName} with ID ${newData.id}. not existing in database.`);
|
|
}
|
|
|
|
this.#hooks.emit('beforeUpdate', oldData, newData);
|
|
this.#modelStore.set(newData.id, newData);
|
|
this.#hooks.emit('afterUpdate', oldData, newData);
|
|
return newData;
|
|
// } catch (error) {
|
|
// throw error;
|
|
// }
|
|
}
|
|
delete(objectId) {
|
|
// try {
|
|
const oldData = this.getById(objectId);
|
|
|
|
if (!oldData) {
|
|
throw new Error(`failed delete ${this.#modelName} with ID ${newData.id}. not existing in database.`);
|
|
}
|
|
this.#hooks.emit('beforeDelete', oldData, null);
|
|
this.#modelStore.delete(oldData.id);
|
|
this.#hooks.emit('afterDelete', oldData, null);
|
|
return oldData;
|
|
// } catch (error) {
|
|
// throw error;
|
|
// }
|
|
}
|
|
has(objectId) {
|
|
return this.#modelStore.has(objectId);
|
|
}
|
|
get dynRef() {
|
|
return this.#dynRef;
|
|
}
|
|
|
|
on(eventName, eventFunction) {
|
|
this.#hooks.on(eventName, eventFunction);
|
|
}
|
|
}
|
|
|
|
|
|
// - --- --- --- --- --- --- --- --- --- --- --- ---
|
|
//
|
|
// Custom Checks
|
|
//
|
|
// - --- --- --- --- --- --- --- --- --- --- --- ---
|
|
|
|
const ipv4Address = define('ipv4Address', (value) => {
|
|
return ipv4.isValidAddress(value);
|
|
});
|
|
|
|
const ipv4Cidr = define('ipv4Cidr', (value) => {
|
|
console.log(value);
|
|
console.log(ipv4.isValidCIDR(value));
|
|
|
|
return ipv4.isValidCIDR(value);
|
|
});
|
|
|
|
|
|
// - --- --- --- --- --- --- --- --- --- --- --- ---
|
|
//
|
|
// Base Models
|
|
//
|
|
// - --- --- --- --- --- --- --- --- --- --- --- ---
|
|
|
|
// Authorisation Provider Model
|
|
export const authProvider = new ObjectModel({
|
|
name: 'authProvider',
|
|
model: object({
|
|
id: defaulted(string(), () => {
|
|
return crypto.randomUUID();
|
|
}),
|
|
name: string(),
|
|
login_text: string(),
|
|
type: enums(["oauth2"]),
|
|
settings: dynamic((value, context) => {
|
|
switch (value.type) {
|
|
case "oauth2":
|
|
return object({
|
|
client_id: string(),
|
|
client_secret: string(),
|
|
authorize_url: string(),
|
|
token_url: string(),
|
|
user_info_url: string(),
|
|
redirect_url: string(),
|
|
scope: array(string())
|
|
});
|
|
}
|
|
return object({});
|
|
})
|
|
})
|
|
});
|
|
|
|
// User Model
|
|
export const user = new ObjectModel({
|
|
name: 'user',
|
|
model: object({
|
|
id: defaulted(string(), () => {
|
|
return crypto.randomUUID();
|
|
}),
|
|
external_id: string(),
|
|
provider: string(),
|
|
displayName: string(),
|
|
email: string()
|
|
})
|
|
});
|
|
|
|
// User Group Model
|
|
export const userGroup = new ObjectModel({
|
|
name: 'userGroup',
|
|
model: object({
|
|
id: defaulted(string(), () => {
|
|
return crypto.randomUUID();
|
|
}),
|
|
name: string(),
|
|
comment: defaulted(string(), ''),
|
|
members: user.dynRef.refMany
|
|
})
|
|
});
|
|
|
|
|
|
// - --- --- --- --- --- --- --- --- --- --- --- ---
|
|
//
|
|
// Wireguard Models
|
|
//
|
|
// - --- --- --- --- --- --- --- --- --- --- --- ---
|
|
|
|
// Wireguard Interface Model
|
|
export const wireguardInterface = new ObjectModel({
|
|
name: 'wireguardInterface',
|
|
model: object({
|
|
id: defaulted(string(), () => {
|
|
return crypto.randomUUID();
|
|
}),
|
|
name: size(string(), 1, 128),
|
|
comment: defaulted(size(string(), 0, 256), ''),
|
|
enabled: defaulted(boolean(), true),
|
|
privateKey: string(),
|
|
publicKey: string(),
|
|
ifName: string(),
|
|
ifAddress: ipv4Cidr,
|
|
listenPort: number(),
|
|
endpoint: string(),
|
|
dnsServer: nullable(ipv4Address)
|
|
})
|
|
});
|
|
|
|
// Wireguard Peer Model
|
|
export const wireguardPeer = new ObjectModel({
|
|
name: 'wireguardPeer',
|
|
model: object({
|
|
id: defaulted(string(), () => {
|
|
return crypto.randomUUID();
|
|
}),
|
|
name: size(string(), 1, 128),
|
|
comment: defaulted(size(string(), 0, 256), ''),
|
|
interface: wireguardInterface.dynRef.refOne,
|
|
enabled: defaulted(boolean(), true),
|
|
|
|
privateKey: string(),
|
|
publicKey: string(),
|
|
presharedKey: string(),
|
|
ifAddress: ipv4Cidr,
|
|
keepalive: size(defaulted(number(), 30), 1, 300),
|
|
})
|
|
});
|
|
|
|
// - --- --- --- --- --- --- --- --- --- --- --- ---
|
|
//
|
|
// CMDB Models
|
|
//
|
|
// - --- --- --- --- --- --- --- --- --- --- --- ---
|
|
|
|
// Address Object Model
|
|
export const addressObject = new ObjectModel({
|
|
name: 'addressObject',
|
|
model: object({
|
|
id: defaulted(string(), () => {
|
|
return crypto.randomUUID();
|
|
}),
|
|
name: size(string(), 1, 128),
|
|
comment: defaulted(size(string(), 0, 256), ''),
|
|
elements: array(union([
|
|
object({
|
|
type: literal('ipmask'),
|
|
ipmask: ipv4Address
|
|
}),
|
|
object({
|
|
type: literal('fqdn'),
|
|
fqdn: string(),
|
|
result: nullable(defaulted(string(), null))
|
|
}),
|
|
object({
|
|
type: literal('peer'),
|
|
peer: wireguardPeer.dynRef.refOne
|
|
})
|
|
])),
|
|
members: dynamic((memberList, context) => {
|
|
// allow many values from list of Peer IDs
|
|
// - exclude objects that contain members (loop prevention)
|
|
// - exclude self (loop prevention)
|
|
let permittedAdressObjects = Object.values(addressObject.getAll())
|
|
.filter(object => object.members.length == 0)
|
|
.filter(object => object.id != context.id)
|
|
.map(item => item.id);
|
|
|
|
// return array from here to reduce Database reads
|
|
return array(enums(permittedAdressObjects));
|
|
}),
|
|
})
|
|
});
|
|
|
|
// Service Object Model
|
|
export const serviceObject = new ObjectModel({
|
|
name: 'serviceObject',
|
|
model: object({
|
|
id: defaulted(string(), () => {
|
|
return crypto.randomUUID();
|
|
}),
|
|
name: size(string(), 1, 128),
|
|
comment: defaulted(size(string(), 0, 256), ''),
|
|
elements: array(union([
|
|
object({
|
|
type: literal('tcp'),
|
|
port: number()
|
|
}),
|
|
object({
|
|
type: literal('udp'),
|
|
port: number()
|
|
}),
|
|
object({
|
|
type: literal('icmp'),
|
|
icmpType: enums([
|
|
"echo-reply",
|
|
"destination-unreachable",
|
|
"source-quench",
|
|
"redirect",
|
|
"echo-request",
|
|
"time-exceeded",
|
|
"parameter-problem",
|
|
"timestamp-request",
|
|
"timestamp-reply",
|
|
"info-request",
|
|
"info-reply",
|
|
"address-mask-request",
|
|
"address-mask-reply",
|
|
"router-advertisement",
|
|
"router-solicitation"
|
|
])
|
|
}),
|
|
object({
|
|
type: literal('esp'),
|
|
port: number()
|
|
})
|
|
]))
|
|
})
|
|
});
|
|
|
|
|
|
// - --- --- --- --- --- --- --- --- --- --- --- ---
|
|
//
|
|
// Firewall Policy Models
|
|
//
|
|
// - --- --- --- --- --- --- --- --- --- --- --- ---
|
|
|
|
export const accessPolicy = new ObjectModel({
|
|
name: 'accessPolicy',
|
|
model: object({
|
|
id: defaulted(string(), () => {
|
|
return crypto.randomUUID();
|
|
}),
|
|
name: size(string(), 1, 128),
|
|
comment: defaulted(size(string(), 0, 256), ''),
|
|
enabled: defaulted(boolean(), true),
|
|
|
|
srcAddr: addressObject.dynRef.refMany,
|
|
dstAddr: addressObject.dynRef.refMany,
|
|
services: serviceObject.dynRef.refMany,
|
|
})
|
|
});
|
|
|
|
export const natPolicy = new ObjectModel({
|
|
name: 'natPolicy',
|
|
model: object({
|
|
id: defaulted(string(), () => {
|
|
return crypto.randomUUID();
|
|
}),
|
|
name: size(string(), 1, 128),
|
|
comment: defaulted(size(string(), 0, 256), ''),
|
|
enabled: defaulted(boolean(), true),
|
|
|
|
natType: enums(['snat', 'dnat', ' masquerade']),
|
|
srcAddress: string(),
|
|
dstAddress: string(),
|
|
dnatPort: optional(number()), // Ziel Port für DNAT
|
|
})
|
|
});
|
|
|
|
|
|
// - --- --- --- --- --- --- --- --- --- --- --- ---
|
|
//
|
|
// Access Control Models
|
|
//
|
|
// - --- --- --- --- --- --- --- --- --- --- --- ---
|
|
|
|
export const configToken = new ObjectModel({
|
|
name: 'configToken',
|
|
model: object({
|
|
id: defaulted(string(), () => {
|
|
return crypto.randomUUID();
|
|
}),
|
|
token: defaulted(string(), () => {
|
|
return crypto.randomBytes(32).toString('hex');
|
|
}),
|
|
peer: string(),
|
|
name: string()
|
|
})
|
|
});
|
|
|
|
export const apiToken = new ObjectModel({
|
|
name: 'apiToken',
|
|
model: object({
|
|
id: defaulted(string(), () => {
|
|
return crypto.randomUUID();
|
|
}),
|
|
token: defaulted(string(), () => {
|
|
return crypto.randomBytes(32).toString('hex');
|
|
}),
|
|
name: string()
|
|
}),
|
|
hooks: {
|
|
beforeCreate: (oldData, newData) => {
|
|
console.log("before API Token created");
|
|
},
|
|
afterCreate: (oldData, newData) => {
|
|
console.log("after API Token created");
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
export default {
|
|
wireguardInterface,
|
|
wireguardPeer,
|
|
addressObject,
|
|
authProvider,
|
|
user,
|
|
userGroup,
|
|
accessPolicy,
|
|
natPolicy,
|
|
configToken,
|
|
apiToken
|
|
} |