basic room functions (untested)

This commit is contained in:
jusax23 2023-03-09 13:44:46 +01:00
parent ff94e53a58
commit 869f1503c2
Signed by: jusax23
GPG key ID: 499E2AA870C1CD41
7 changed files with 467 additions and 39 deletions

View file

@ -1,8 +1,8 @@
import { select, count, alias, insert, remove, le, update, minus, eq, and } from "dblang";
import { outbagURLfromTag } from "../../server/outbagURL.js";
import { checkSelfTag, outbagServer, outbagURLfromTag } from "../../server/outbagURL.js";
import { PERMISSIONS } from "../../server/permissions.js";
import { getRemote } from "../../server/serverCerts.js";
import { localhostTag, oConf } from "../../sys/config.js";
import { oConf } from "../../sys/config.js";
import { sha256, verify } from "../../sys/crypto.js";
import { accounts, db, signupOTA as signupOTATable } from "../../sys/db.js";
import { get64, uts } from "../../sys/tools.js";
@ -27,9 +27,14 @@ export const signup: Act = {
right: 0,
data: {
name: "name-100",
server: "string",
accountKey: "string"
},
func: async (client: Client, data: any, aws: (code: string, data: any) => void) => {
if (!checkSelfTag(data.server)) {
aws("error", "data");
return;
}
let countAlias = alias(count(accounts.accID), "countAlias") as any;
let query = await select([countAlias], accounts)
.query(db);
@ -50,7 +55,7 @@ export const signup: Act = {
client.state = STATE.client;
client.accID = accID;
client.name = data.name;
client.server = localhostTag;
client.server = new outbagServer(data.server, oConf.get("System", "URL") + "", oConf.get("System", "PATHexposed") + "", oConf.get("System", "PORTexposed") + "");
}
} else {
client.suspect();
@ -64,10 +69,15 @@ export const signupOTA = {
right: 0,
data: {
name: "string-100",
server: "string",
accountKey: "string",
OTA: "string"
},
func: async (client: Client, data: any, aws: (code: string, data: any) => void) => {
if (!checkSelfTag(data.server)) {
aws("error", "data");
return;
}
// TODO: make transaktion when posible
await remove(signupOTATable)
.where(le(signupOTATable.expires, uts()))
@ -92,7 +102,7 @@ export const signupOTA = {
client.state = STATE.client;
client.accID = accID;
client.name = data.name;
client.server = localhostTag;
client.server = new outbagServer(data.server, oConf.get("System", "URL") + "", oConf.get("System", "PATHexposed") + "", oConf.get("System", "PORTexposed") + "");
}
} else {
client.suspect();
@ -106,9 +116,14 @@ export const signin = {
right: 0,
data: {
name: "string",
server: "string",
accountKey: "string"
},
func: async (client: Client, data: any, aws: (code: string, data: any) => void) => {
if (!checkSelfTag(data.server)) {
aws("error", "data");
return;
}
let query = await select([accounts.accID, accounts.accountKey, accounts.accountKeySalt], accounts)
.where(and(
eq(accounts.name, data.name),
@ -126,7 +141,7 @@ export const signin = {
client.state = STATE.client;
client.accID = accID;
client.name = data.name;
client.server = localhostTag;
client.server = new outbagServer(data.server, oConf.get("System", "URL") + "", oConf.get("System", "PATHexposed") + "", oConf.get("System", "PORTexposed") + "");
}
}
};

372
src/api/acts/rooms.ts Normal file
View file

@ -0,0 +1,372 @@
import { alias, and, eq, exists, innerJoinOn, insert, le, minus, naturalJoin, not, or, remove, select, update } from "dblang";
import { ROOM_RIGHTS } from "../../server/permissions.js";
import { accounts, db, roomMembers, roomOTAs, rooms } from "../../sys/db.js";
import { uts } from "../../sys/tools.js";
import { isRoomFull } from "../helper.js";
import { Act, Client, STATE } from "../user.js";
export const listLocalRooms: Act = {
state: STATE.client | STATE.remote,
right: 0,
data: {},
func: async (client: Client, data: any, aws: (code: string, data: any) => void) => {
let ownerAlias = alias(
select([accounts.name], accounts)
.where(eq(accounts.accID, rooms.owner)),
"owner") as any;
let req = await select([
rooms.name,
ownerAlias,
rooms.rights,
rooms.public,
rooms.title,
rooms.description,
rooms.icon
], naturalJoin(rooms, roomMembers))
.where(and(
eq(roomMembers.name, client.name),
eq(roomMembers.server, client.state == STATE.client ? "local" : client.server.tag)
))
.query(db);
let out = req.map(d => {
let name = d[rooms.name];
let owner = d[ownerAlias];
let rights = d[rooms.rights];
let isPublic = d[rooms.public];
let title = d[rooms.title];
let description = d[rooms.description];
let icon = d[rooms.icon];
if (name != null && owner != null && rights != null && isPublic != null && title != null && description != null && icon != null) {
return { name, owner, rights, public: isPublic, title, description, icon };
}
return null;
});
aws("ok", out.filter(d => d != null));
}
};
export const getRoomMembers: Act = {
state: STATE.client | STATE.remote,
right: 0,
data: {
room: "name",
server: "string", // Unused at the Moment
},
func: async (client: Client, data: any, aws: (code: string, data: any) => void) => {
let roomID = await client.isInRoom(data.room);
if (roomID == -1) {
aws("error", "existence");
return;
}
let req = await select([
roomMembers.name,
roomMembers.server,
roomMembers.admin
], roomMembers)
.where(eq(rooms.roomID, roomID))
.query(db);
let out = req.map(d => {
let name = d[roomMembers.name];
let server = d[roomMembers.server];
let admin = d[roomMembers.admin];
if (name != null && server != null && admin != null) {
return { name, server, admin };
}
return null;
});
aws("ok", out.filter(d => d != null));
}
};
export const joinRoom: Act = {
state: STATE.client | STATE.remote,
right: 0,
data: {
room: "string",
server: "string", // Unused at the Moment
OTA: "string"
},
func: async (client: Client, data: any, aws: (code: string, data: any) => void) => {
let query = await select([rooms.roomID], rooms)
.where(eq(rooms.name, data.room))
.query(db);
let roomID = (query[0] ?? {})[rooms.roomID];
if (typeof roomID != "number" || roomID < 0) {
return void aws("error", "ota");
}
if (await isRoomFull(roomID)) return void aws("error", "limit");
// TODO: Make Transaktion when possible
await remove(roomOTAs)
.where(le(roomOTAs.expires, uts()))
.query(db);
let req = await update(roomOTAs)
.set(roomOTAs.usesLeft, minus(roomOTAs.usesLeft, 1))
.query(db);
await remove(roomOTAs)
.where(eq(roomOTAs.usesLeft, 0))
.query(db);
if (req[1].affectedRows == 0) {
return void aws("error", "ota");
}
let queryx = await insert(
roomMembers.roomID,
roomMembers.name,
roomMembers.server,
roomMembers.admin
).add(roomID, client.name, client.server.tag, 0)
.query(db);
if (queryx.affectedRows > 0) {
aws("ok", "");
} else {
aws("error", "server");
}
}
};
export const joinPublicRoom: Act = {
state: STATE.client | STATE.remote,
right: 0,
data: {
room: "string",
server: "string", // Unused at the Moment
},
func: async (client: Client, data: any, aws: (code: string, data: any) => void) => {
let query = await select([rooms.roomID, rooms.public], rooms)
.where(eq(rooms.name, data.room))
.query(db);
let roomID = (query[0] ?? {})[rooms.roomID];
let isPublic = (query[0] ?? {})[rooms.public];
if (typeof roomID != "number" || roomID < 0 || typeof isPublic != "number") {
return void aws("error", "existence");
}
if (((client.state == STATE.client) && (isPublic < 1)) || ((client.state == STATE.remote) && (isPublic < 2))) {
return void aws("error", "existence");
}
if (await isRoomFull(roomID)) return void aws("error", "limit");
let queryx = await insert(
roomMembers.roomID,
roomMembers.name,
roomMembers.server,
roomMembers.admin
).add(roomID, client.name, client.server.tag, 0)
.query(db);
if (queryx.affectedRows > 0) {
aws("ok", "");
} else {
aws("error", "server");
}
}
};
export const getRoomOTAs: Act = {
state: STATE.client | STATE.remote,
right: 0,
data: {
room: "string",
server: "string", // Unused at the Moment
},
func: async (client: Client, data: any, aws: (code: string, data: any) => void) => {
let roomID = await client.isRoomAdmin(data.room, ROOM_RIGHTS.OTA);
if (roomID == -1) return void aws("error", "roomAdmin");
let req = await select([roomOTAs.token, roomOTAs.expires, roomOTAs.usesLeft], roomOTAs)
.where(eq(roomOTAs.roomID, roomID))
.query(db);
aws("ok", req.map(d => ({
token: d[roomOTAs.token],
expires: d[roomOTAs.expires],
usesLeft: d[roomOTAs.usesLeft]
})));
}
};
export const addRoomOTA: Act = {
state: STATE.client | STATE.remote,
right: 0,
data: {
room: "string",
server: "string", // Unused at the Moment
token: "string-255",
expires: "number",
usesLeft: "number",
},
func: async (client: Client, data: any, aws: (code: string, data: any) => void) => {
let roomID = await client.isRoomAdmin(data.room, ROOM_RIGHTS.OTA);
if (roomID == -1) return void aws("error", "roomAdmin");
try {
await insert(roomOTAs.roomID, roomOTAs.token, roomOTAs.expires, roomOTAs.usesLeft)
.add(roomID, data.token, data.expires, data.usesLeft)
.query(db);
} catch (error) {
await update(roomOTAs)
.set(roomOTAs.expires, data.expires)
.set(roomOTAs.usesLeft, data.usesLeft)
.where(and(
eq(roomOTAs.token, data.token),
eq(roomOTAs.roomID, roomID)
)).query(db);
}
aws("ok", "");
}
};
export const deleteRoomOTA: Act = {
state: STATE.client | STATE.remote,
right: 0,
data: {
room: "string",
server: "string", // Unused at the Moment
token: "string"
},
func: async (client: Client, data: any, aws: (code: string, data: any) => void) => {
let roomID = await client.isRoomAdmin(data.room, ROOM_RIGHTS.OTA);
if (roomID == -1) return void aws("error", "roomAdmin");
await remove(roomOTAs)
.where(and(
eq(roomOTAs.roomID, roomID),
eq(roomOTAs.token, data.token)
)).query(db);
aws("ok", "");
}
};
export const kickMember: Act = {
state: STATE.client | STATE.remote,
right: 0,
data: {
room: "string",
roomServer: "string", // Unused at the Moment
name: "string",
server: "string",
},
func: async (client: Client, data: any, aws: (code: string, data: any) => void) => {
let roomID = await client.isRoomAdmin(data.room, ROOM_RIGHTS.MANAGE_MEMBERS);
if (roomID == -1) return void aws("error", "roomAdmin");
let req = await remove(roomMembers)
.where(and(
eq(roomMembers.roomID, roomID),
eq(roomMembers.name, data.name),
eq(roomMembers.server, data.server),
or(
not(eq(roomMembers.server, "local")),
not(exists(
select([accounts.accID], innerJoinOn(accounts, rooms, eq(accounts.accID, rooms.owner)))
.where(and(
eq(rooms.roomID, roomMembers.roomID),
eq(accounts.name, roomMembers.name)
))
))
)
)).query(db);
if (req.affectedRows > 0) {
aws("ok", "");
} else {
aws("error", "existence")
}
}
};
export const setAdminStatus: Act = {
state: STATE.client | STATE.remote,
right: 0,
data: {
room: "string",
roomServer: "string", // Unused at the Moment
name: "string",
server: "string",
admin: "boolean",
},
func: async (client: Client, data: any, aws: (code: string, data: any) => void) => {
let roomID = await client.isRoomAdmin(data.room, ROOM_RIGHTS.MANAGE_MEMBERS);
if (roomID == -1) return void aws("error", "roomAdmin");
let req = await update(roomMembers)
.set(roomMembers.admin, data.admin)
.where(and(
eq(roomMembers.roomID, roomID),
eq(roomMembers.name, data.name),
eq(roomMembers.server, data.server)
)).query(db);
if (req.affectedRows > 0) {
aws("ok", "");
} else {
aws("error", "existence");
}
}
};
export const leaveRoom: Act = {
state: STATE.client | STATE.remote,
right: 0,
data: {
room: "string",
server: "string", // Unused at the Moment
},
func: async (client: Client, data: any, aws: (code: string, data: any) => void) => {
let roomID = await client.isInRoom(data.room);
if (roomID == -1) return void aws("error", "existence");
let req = await remove(roomMembers)
.where(and(
eq(roomMembers.roomID, roomID),
eq(roomMembers.name, client.name),
eq(roomMembers.server, client.state == STATE.client ? "local" : client.server.tag),
or(
not(eq(roomMembers.server, "local")),
not(exists(
select([accounts.accID], innerJoinOn(accounts, rooms, eq(accounts.accID, rooms.owner)))
.where(and(
eq(rooms.roomID, roomMembers.roomID),
eq(accounts.name, roomMembers.name)
))
))
)
)).query(db);
if (req.affectedRows > 0) {
aws("ok", "");
} else {
aws("error", "owner");
}
}
};
export const roomSettings: Act = {
state: STATE.client | STATE.remote,
right: 0,
data: {
room: "string",
server: "string", // Unused at the Moment
rights: "number", //see permissions.ts
isPublic: "number" //0 is not, 1 only to clients, 2 or bigger everywhere
},
func: async (client, data, aws) => {
let roomID = await client.isRoomAdmin(data.room, ROOM_RIGHTS.CHANGE_SETTINGS);
if (roomID == -1) return void aws("error", "roomAdmin");
let req = await update(rooms)
.set(rooms.rights, data.rights)
.set(rooms.public, data.isPublic)
.where(eq(rooms.roomID, roomID))
.query(db);
aws("ok", "");
}
};
export const roomMeta: Act = {
state: STATE.client | STATE.remote,
right: 0,
data: {
room: "string",
server: "string",
title: "string-255",
description: "string-255",
icon: "string-255"
},
func: async (client: Client, data: any, aws: (code: string, data: any) => void) =>{
let roomID = await client.isRoomAdmin(data.room, ROOM_RIGHTS.CHANGE_META);
if (roomID == -1) return void aws("error", "roomAdmin");
let req = await update(rooms)
.set(rooms.title, data.title)
.set(rooms.description, data.description)
.set(rooms.icon, data.icon)
.where(eq(rooms.roomID, roomID))
.query(db);
aws("ok", "");
}
}

View file

@ -0,0 +1,19 @@
import { alias, eq, count, select, naturalJoin, innerJoinOn } from "dblang";
import { accounts, db, roomMembers, rooms } from "../sys/db.js";
export const isRoomFull = async (roomID: number) => {
let currentCount = alias(
select([count(roomMembers.roomMemberID)], roomMembers)
.where(eq(roomMembers.roomID, roomID)),
"currentCount"
) as any;
let maxCount = alias(
select([accounts.maxUsersPerRoom],
innerJoinOn(accounts, rooms, eq(accounts.accID, rooms.owner)))
.where(eq(rooms.roomID, roomID)),
"maxCount"
) as any;
let req = await select([currentCount, maxCount], null)
.query(db);
return req[0][currentCount] >= req[0][maxCount];
};

View file

@ -10,7 +10,8 @@ import { sha256 } from "../sys/crypto.js";
import { get64, uts } from "../sys/tools.js";
import { addShutdownTask } from "nman";
import { suspectRequest } from "../sys/bruteforce.js";
import { localhostTag } from "../sys/config.js";
import { checkSelfTag, outbagServer } from "../server/outbagURL.js";
import { oConf, selfTag } from "../sys/config.js";
let acts = importActs as { [key: string]: Act };
@ -47,10 +48,15 @@ export const addPostMethods = (server: express.Express) => {
aws("error", "token");
return;
}
} else if (auth?.params?.name != null && auth?.params?.accountKey != null && typeof auth?.params?.name == "string" && typeof auth?.params?.accountKey == "string") {
} else if (auth?.params?.name != null && auth?.params?.server != null && auth?.params?.accountKey != null && typeof auth?.params?.name == "string" && auth?.params?.server == "string" && typeof auth?.params?.accountKey == "string") {
client = new postClient(req.ip);
client.client.name = auth?.params?.name;
client.client.server = localhostTag;
let serverTag = auth?.params?.server;
if (!checkSelfTag(serverTag)) {
aws("error", "data");
return;
}
client.client.server = new outbagServer(serverTag, selfTag.host, selfTag.path, selfTag.port);;
let accountKey = auth?.params?.accountKey;
let query = await select([accounts.accID, accounts.accountKey, accounts.accountKeySalt], accounts)

View file

@ -101,7 +101,7 @@ async function complete_loaded() {
startUpdateCert();
await wait(500);
let succ = await generateTag();
if(!succ) error("Outbag", "Could not check own Server Tag. Remote-Auth will not work! Check if the Server is reachable and the config ist correct!");
if(!succ) error("Outbag", "Could not check own Server Tag. Remote-Auth may not work! Check if the Server is reachable and the config ist correct!");
activatePost();
activateWS();
log("Server", 'Listening...');

View file

@ -1,3 +1,5 @@
import { oConf } from "../sys/config.js";
const WELL_KNOWN_PATH = "/.well-known/outbag/server";
const DEFAULT_PORT = "7223";
@ -53,3 +55,7 @@ export class outbagServer {
return `wss://${this.host}:${this.port}${this.path}`;
}
};
export const checkSelfTag = (tag: string) => {
return tag == oConf.get("System", "URL") || tag == oConf.get("System", "URL") + ":" + oConf.get("System", "PORTexposed")
}

View file

@ -1,60 +1,70 @@
import juml from "juml";
import { outbagServer, outbagURLfromTag } from "../server/outbagURL.js";
import { debug } from "./log.js";
const conf_struct = {
System: {
PORT: { type: "number", default: 7223, env: "OUTBAG_PORT", comment:"The Server will listen on this Port!" },
PORTexposed: { type: "number", default: 7223, env: "OUTBAG_EXPOSED_PORT" },
PATHexposed: { type: "string", default: "/", env: "OUTBAG_EXPOSED_PATH" },
URL: { type: "string", default: "localhost", env: "OUTBAG_HOST" },
CertLiveSec: { type: "number", default: 60*60*24*30, env: "OUTBAG_CERT_LIVE_SEC" },
PORT: { type: "number", default: 7223, env: "OUTBAG_PORT", comment: "The Server will listen on this Port!" },
PORTexposed: { type: "number", default: 7223, env: "OUTBAG_EXPOSED_PORT" },
PATHexposed: { type: "string", default: "/", env: "OUTBAG_EXPOSED_PATH" },
URL: { type: "string", default: "localhost", env: "OUTBAG_HOST" },
CertLiveSec: { type: "number", default: 60 * 60 * 24 * 30, env: "OUTBAG_CERT_LIVE_SEC" },
},
ssl: {
enable: { type: "boolean", default: false, env: "OUTBAG_SSL_ENABLED" },
privkey: { type: "string", default: "privkey.pem", env: "OUTBAG_SSL_PRIVATE_KEY" },
cert: { type: "string", default: "cert.pem", env: "OUTBAG_SSL_CERT" },
chain: { type: "string", default: "chain.pem", env: "OUTBAG_SSL_CHAIN" }
enable: { type: "boolean", default: false, env: "OUTBAG_SSL_ENABLED" },
privkey: { type: "string", default: "privkey.pem", env: "OUTBAG_SSL_PRIVATE_KEY" },
cert: { type: "string", default: "cert.pem", env: "OUTBAG_SSL_CERT" },
chain: { type: "string", default: "chain.pem", env: "OUTBAG_SSL_CHAIN" }
},
Database: {
host: { type: "string", default: "localhost", env: "OUTBAG_MYSQL_HOST" },
port: { type: "number", default: 3306, env: "OUTBAG_MYSQL_PORT" },
user: { type: "string", default: "admin", env: "OUTBAG_MYSQL_USER" },
password: { type: "string", default: "", env: "OUTBAG_MYSQL_PASSWORD" },
database: { type: "string", default: "outbag", env: "OUTBAG_MYSQL_DATABASE" }
host: { type: "string", default: "localhost", env: "OUTBAG_MYSQL_HOST" },
port: { type: "number", default: 3306, env: "OUTBAG_MYSQL_PORT" },
user: { type: "string", default: "admin", env: "OUTBAG_MYSQL_USER" },
password: { type: "string", default: "", env: "OUTBAG_MYSQL_PASSWORD" },
database: { type: "string", default: "outbag", env: "OUTBAG_MYSQL_DATABASE" }
},
Settings: {
maxUsers: { type:"number", default:0, env: "OUTBAG_MAX_USERS"},//Infinity = -1
defaultMaxRooms: { type:"number", default:3, env: "OUTBAG_DEFAULT_MAX_ROOMS"},//Infinity = -1
defaultMaxRoomSize: { type:"number", default:10000, env: "OUTBAG_DEFAULT_MAX_ROOMS_SIZE"},//Infinity = -1
defaultMaxUsersPerRoom: { type:"number", default:5, env: "OUTBAG_DEFAULT_MAX_USERS_PER_ROOM"},//Infinity = -1
maxUsers: { type: "number", default: 0, env: "OUTBAG_MAX_USERS" },//Infinity = -1
defaultMaxRooms: { type: "number", default: 3, env: "OUTBAG_DEFAULT_MAX_ROOMS" },//Infinity = -1
defaultMaxRoomSize: { type: "number", default: 10000, env: "OUTBAG_DEFAULT_MAX_ROOMS_SIZE" },//Infinity = -1
defaultMaxUsersPerRoom: { type: "number", default: 5, env: "OUTBAG_DEFAULT_MAX_USERS_PER_ROOM" },//Infinity = -1
}
};
export const oConf = new juml(conf_struct);
export let localhostTag: outbagServer = new outbagServer(
export let selfTag: outbagServer = new outbagServer(
"localhost",
oConf.get("System", "URL")+"",
oConf.get("System", "PATHexposed")+"",
oConf.get("System", "PORTexposed")+"",
oConf.get("System", "URL") + "",
oConf.get("System", "PATHexposed") + "",
oConf.get("System", "PORTexposed") + "",
);
export let selfTag: outbagServer = localhostTag;
export const generateTag = async () => {
try {
selfTag = new outbagServer(
"localhost",
oConf.get("System", "URL") + "",
oConf.get("System", "PATHexposed") + "",
oConf.get("System", "PORTexposed") + "",
);
let initselfTag = selfTag;
let mainServerHost: outbagServer | null = null;
try {
mainServerHost = await outbagURLfromTag(oConf.get("System", "URL"));
} catch (error) {}
} catch (error) { }
let serverHostPort = await outbagURLfromTag(
oConf.get("System", "URL") + ":" + oConf.get("System", "PORTexposed"));
if(mainServerHost == null || mainServerHost.tag != serverHostPort.tag){
if (mainServerHost == null || mainServerHost.tag != serverHostPort.tag) {
selfTag = serverHostPort;
}else selfTag = mainServerHost;
} else selfTag = mainServerHost;
if (initselfTag.httpsURL != selfTag.httpsURL) {
console.log(initselfTag, initselfTag.httpsURL, selfTag.httpsURL);
debug("Outbag", "Not matching Server host, port, path and expected server link.");
return false;
}
return true;
} catch (error) {
return false;
}
};