diff --git a/src/api/acts.ts b/src/api/acts.ts index decfc45..476e29a 100644 --- a/src/api/acts.ts +++ b/src/api/acts.ts @@ -1 +1,3 @@ -export * from "./acts/login.js" \ No newline at end of file +export * from "./acts/login.js"; +export * from "./acts/client.js" +export * from "./acts/admin.js" \ No newline at end of file diff --git a/src/api/acts/admin.ts b/src/api/acts/admin.ts new file mode 100644 index 0000000..0ce41af --- /dev/null +++ b/src/api/acts/admin.ts @@ -0,0 +1,171 @@ +import { eq, insert, le, or, remove, select, update } from "dblang"; +import { PERMISSIONS } from "../../server/permissions.js"; +import { sha256 } from "../../sys/crypto.js"; +import { accounts, db, signupOTA } from "../../sys/db.js"; +import { get64, uts } from "../../sys/tools.js"; +import { Client, STATE } from "../user.js"; + +export const getAccounts = { + state: STATE.client, + right: PERMISSIONS.CAN_USE_API | PERMISSIONS.SHOW_USERS_AND_LISTS, + data: {}, + func: async (client: Client, data: any, aws: (code: string, data: any) => void) => { + let query = await select([ + accounts.accID, + accounts.rights, + accounts.name, + accounts.viewable, + accounts.deleted, + accounts.maxRooms, + accounts.maxRoomSize, + accounts.maxUsersPerRoom + ], accounts) + .query(db); + var out = query.map(d => { + let accID = d[accounts.accID]; + let rights = d[accounts.rights]; + let name = d[accounts.name]; + let viewable = d[accounts.viewable]; + let deleted = d[accounts.deleted]; + let maxRooms = d[accounts.maxRooms]; + let maxRoomSize = d[accounts.maxRoomSize]; + let maxUsersPerRoom = d[accounts.maxUsersPerRoom]; + if (accID != null && rights != null && name != null && viewable != null && deleted != null && maxRooms != null && maxRoomSize != null && maxUsersPerRoom != null) { + return { accID, rights, name, viewable, deleted, maxRooms, maxRoomSize, maxUsersPerRoom }; + } + return null; + }); + aws("ok", out.filter(d => d != null)); + } +}; + +export const setPermissions = { + state: STATE.client, + right: PERMISSIONS.CAN_USE_API | PERMISSIONS.EDIT_RIGHTS, + data: { + accID: "number", + rights: "number" + }, + func: async (client: Client, data: any, aws: (code: string, data: any) => void) => { + let query = await update(accounts) + .set(accounts.rights, data.rights) + .where(eq(accounts.accID, data.accID)) + .query(db); + if (query.affectedRows > 0) { + aws("ok", ""); + } else { + client.suspect(); + aws("error", "existence"); + } + } +}; + +export const resetPassword = { + state: STATE.client, + right: PERMISSIONS.CAN_USE_API | PERMISSIONS.EDIT_USERS, + data: { + accID: "number", + accountKey: "string" + }, + func: async (client: Client, data: any, aws: (code: string, data: any) => void) => { + var salt = get64(16); + let req = await update(accounts) + .set(accounts.accountKey, sha256(salt + data.accountKey)) + .set(accounts.accountKeySalt, salt) + .where(eq(accounts.accID, data.accID)) + .query(db); + if (req.affectedRows > 0) { + aws("ok", ""); + } else { + client.suspect(); + aws("error", "existence"); + } + } +}; + +export const setMaxValues = { + state: STATE.client, + right: PERMISSIONS.CAN_USE_API | PERMISSIONS.EDIT_USERS, + data: { + accID: "number", + maxRooms: "number", + maxRoomSize: "number", + maxUsersPerRoom: "number", + }, + func: async (client: Client, data: any, aws: (code: string, data: any) => void) => { + let req = await update(accounts) + .set(accounts.maxRooms, data.maxRooms) + .set(accounts.maxRoomSize, data.maxRoomSize) + .set(accounts.maxUsersPerRoom, data.maxUsersPerRoom) + .where(eq(accounts.accID, data.accID)) + .query(db); + if (req.affectedRows > 0) { + aws("ok", ""); + } else { + client.suspect(); + aws("error", "existence"); + } + } +}; + +export const getOTAs = { + state: STATE.client, + right: PERMISSIONS.CAN_USE_API | PERMISSIONS.MANAGE_OTA_TOKENS, + data: {}, + func: async (client: Client, data: any, aws: (code: string, data: any) => void) => { + await remove(signupOTA) + .where(or( + eq(signupOTA.usesLeft, 0), + le(signupOTA.expires, uts()) + )) + .query(db); + let req = await select([ + signupOTA.token, + signupOTA.expires, + signupOTA.usesLeft, + ], signupOTA) + .query(db); + aws("ok", req.map(d => ({ + token: d[signupOTA.token], + expires: d[signupOTA.expires], + usesLeft: d[signupOTA.usesLeft] + }))); + } +} + +export const addOTA = { + state: STATE.client, + right: PERMISSIONS.CAN_USE_API | PERMISSIONS.MANAGE_OTA_TOKENS, + data: { + token: "string-255", + expires: "number", //uts in sec. + usesLeft: "number" + }, + func: async (client: Client, data: any, aws: (code: string, data: any) => void) => { + try { + await insert(signupOTA.token, signupOTA.expires, signupOTA.usesLeft) + .add(data.token, data.expires, data. usesLeft) + .query(db); + } catch (error) { + await update(signupOTA) + .set(signupOTA.expires, data.expires) + .set(signupOTA.usesLeft, data.usesLeft) + .where(eq(signupOTA.token, data.token)) + .query(db); + } + aws("ok", ""); + } +} +export const deleteOTA = { + state: STATE.client, + right: PERMISSIONS.CAN_USE_API | PERMISSIONS.MANAGE_OTA_TOKENS, + data: { + token: "string" + }, + func: async (client: Client, data: any, aws: (code: string, data: any) => void) => { + await remove(signupOTA) + .where(eq(signupOTA.token, data.token)) + .query(db); + aws("ok", ""); + } +} \ No newline at end of file diff --git a/src/api/acts/client.ts b/src/api/acts/client.ts new file mode 100644 index 0000000..5d3ef5f --- /dev/null +++ b/src/api/acts/client.ts @@ -0,0 +1,92 @@ +import { eq, select, update } from "dblang"; +import { outbagURLshort } from "../../server/outbagURL.js"; +import { PERMISSIONS } from "../../server/permissions.js"; +import { oConf } from "../../sys/config.js"; +import { sha256, sign } from "../../sys/crypto.js"; +import { accounts, db } from "../../sys/db.js"; +import { getSettings, SETTINGS } from "../../sys/settings.js"; +import { get64 } from "../../sys/tools.js"; +import { Client, STATE } from "../user.js"; + + +export const deleteAccount = { + state: STATE.client, + right: 0, + data: {}, + func: async (client: Client, data: any, aws: (code: string, data: any) => void) => { + client.state = STATE.no; + await update(accounts) + .set(accounts.deleted, 1) + .where(eq(accounts.accID, client.accID)) + .query(db); + aws("ok", ""); + } +}; + +export const createSignature = { + state: STATE.client, + right: PERMISSIONS.CAN_USE_API | PERMISSIONS.PROVIDE_CERT, + data: { + publicKey: "string" + }, + func: async (client: Client, data: any, aws: (code: string, data: any) => void) => { + const server = await outbagURLshort(oConf.get("System", "URL") + ":" + oConf.get("System", "PORTexposed")); + const tag = client.name + "@" + server + "-" + data.publicKey; + var signature = await sign(tag, await getSettings(SETTINGS.privateKey)); + aws("ok", signature); + } +}; + +export const getMyAccount = { + state: STATE.client, + right: 0, + data: {}, + func: async (client: Client, data: any, aws: (code: string, data: any) => void) => { + let query = await select([ + accounts.rights, + accounts.name, + accounts.viewable, + accounts.maxRooms, + accounts.maxRoomSize, + accounts.maxUsersPerRoom, + ], accounts) + .where(eq(accounts.accID, client.accID)) + .query(db); + if (query.length > 0) { + let rights = query[0][accounts.rights]; + let name = query[0][accounts.name]; + let viewable = query[0][accounts.viewable]; + let maxRooms = query[0][accounts.maxRooms]; + let maxRoomSize = query[0][accounts.maxRoomSize]; + let maxUsersPerRoom = query[0][accounts.maxUsersPerRoom]; + if (rights != null && name != null && viewable != null && maxRooms != null && maxRoomSize != null && maxUsersPerRoom != null) { + aws("ok", { rights, name, viewable, maxRooms, maxRoomSize, maxUsersPerRoom }); + return; + } + } + client.suspect(); + aws("error", "existence"); + } +}; + +export const changePassword = { + state: STATE.client, + right: PERMISSIONS.CAN_USE_API, + data: { + accountKey: "string" + }, + func: async (client: Client, data: any, aws: (code: string, data: any) => void) => { + var salt = get64(16); + let req = await update(accounts) + .set(accounts.accountKey, sha256(salt + data.accountKey)) + .set(accounts.accountKeySalt, salt) + .where(eq(accounts.accID, client.accID)) + .query(db); + if (req.affectedRows > 0) { + aws("ok", ""); + } else { + client.suspect(); + aws("error", "existence"); + } + } +}; \ No newline at end of file diff --git a/src/api/acts/login.ts b/src/api/acts/login.ts index 8585f06..61b1767 100644 --- a/src/api/acts/login.ts +++ b/src/api/acts/login.ts @@ -27,10 +27,10 @@ export const signup: Act = { } let salt = get64(16); let req = await insert(accounts.name, accounts.rights, accounts.accountKey, accounts.accountKeySalt) - .add(data.name, userNum == 0 ? PERMISSIONS.ALL : PERMISSIONS.DEFAULT, sha256(salt + data.accountKey)) + .add(data.name, userNum == 0 ? PERMISSIONS.ALL : PERMISSIONS.DEFAULT, sha256(salt + data.accountKey), salt) .query(db); if (req.affectedRows > 0) { - let accID = req.insertId; + let accID = Number(req.insertId); if (!isNaN(accID)) { aws("ok", ""); client.state = STATE.client; @@ -69,10 +69,10 @@ export const signupOTA = { } let salt = get64(16); let req = await insert(accounts.name, accounts.rights, accounts.accountKey, accounts.accountKeySalt) - .add(data.name, PERMISSIONS.DEFAULT, sha256(salt + data.accountKey)) + .add(data.name, PERMISSIONS.DEFAULT, sha256(salt + data.accountKey), salt) .query(db); if (req.affectedRows > 0) { - let accID = req.insertId; + let accID = Number(req.insertId); if (!isNaN(accID)) { aws("ok", ""); client.state = STATE.client; @@ -101,12 +101,12 @@ export const signin = { eq(accounts.deleted, 0) )) .query(db); - if (query.length == 0 || query[0].accountKey != sha256((query[0].accountKeySalt ?? '') + data.accountKey)) { + if (query.length == 0 || query[0][accounts.accountKey] != sha256((query[0][accounts.accountKeySalt] ?? '') + data.accountKey)) { client.suspect(); aws("error", "auth"); return; } - var accID = query[0].ID; + var accID = query[0][accounts.accID]; if (!isNaN(accID)) { aws("ok", ""); client.state = STATE.client; diff --git a/src/server/outbagURL.ts b/src/server/outbagURL.ts index cd28c41..b75a9ac 100644 --- a/src/server/outbagURL.ts +++ b/src/server/outbagURL.ts @@ -20,7 +20,7 @@ export const outbagURL = (url: string, prefix = "https") => new Promise((res, re }) .catch(_ => { if (uri.port == '') { - uri.port = DEFAULT_PORT+""; + uri.port = DEFAULT_PORT + ""; outbagURL(uri.toString(), prefix) .then(url => res(url)) .catch(_ => rej()); @@ -29,6 +29,7 @@ export const outbagURL = (url: string, prefix = "https") => new Promise((res, re } }); }); + export const outbagURLshort = (url: string) => new Promise((res, rej) => { let uri: URL; try { @@ -47,7 +48,7 @@ export const outbagURLshort = (url: string) => new Promise((res, rej) => { }) .catch(_ => { if (uri.port == '') { - uri.port = DEFAULT_PORT+""; + uri.port = DEFAULT_PORT + ""; outbagURLshort(uri.toString()) .then(url => res(url)) .catch(_ => rej()); diff --git a/src/server/permissions.ts b/src/server/permissions.ts index 53574a0..4d232ce 100644 --- a/src/server/permissions.ts +++ b/src/server/permissions.ts @@ -1,6 +1,3 @@ -import { eq, select, update } from "dblang"; -import { accounts, db } from "../sys/db.js"; - export const PERMISSIONS = { NONE: 0b0000000000000000, // equal to no account or blocked account @@ -17,3 +14,13 @@ export const PERMISSIONS = { EDIT_USERS: 0b1000000000000000, ALL: 0b1111111111111111, }; + +export const ROOM_RIGHTS = { //when changing, look in main (db defaults) + ADD_ARTICLES: 0b0000001, //change or add articles + REMOVE_ARTICLES: 0b0000010, + LIST_GROUPS_ITEMS: 0b0000100, //edit room intern listGroups and listItems + CHANGE_META: 0b0001000, + OTA: 0b0010000, //edit otas + CHANGE_SETTINGS: 0b0100000, + MANAGE_MEMBERS: 0b1000000, +}; diff --git a/src/sys/db.ts b/src/sys/db.ts index d607bfd..fbdf3e0 100644 --- a/src/sys/db.ts +++ b/src/sys/db.ts @@ -45,7 +45,7 @@ nMan.addShutdownTask(db.close, 3000, 10); export const accounts = db.newTable("accounts"); accounts.addAttributes({ accID: { type: INT, primaryKey: true, autoIncrement: true }, - name: { type: VARCHAR(255), default: PERMISSIONS.DEFAULT }, + name: { type: VARCHAR(100), default: PERMISSIONS.DEFAULT }, rights: { type: BIGINT, default: PERMISSIONS.DEFAULT }, diff --git a/src/sys/log.ts b/src/sys/log.ts index 4228ddf..5442028 100644 --- a/src/sys/log.ts +++ b/src/sys/log.ts @@ -1,7 +1,8 @@ export const debug = (name: string, ...args: any[]) => { - if(!global.debug)return; + if (!global.debug) return; + consorArgs(args); console.log( - "\x1b[33m%s\x1b[0m"+ + "\x1b[33m%s\x1b[0m" + "\x1b[1m\x1b[32m%s\x1b[0m", (new Date()).toLocaleString(), ` [${name}]:`, @@ -10,8 +11,9 @@ export const debug = (name: string, ...args: any[]) => { }; export const log = (name: string, ...args: string[]) => { + consorArgs(args); console.log( - "\x1b[33m%s\x1b[0m"+ + "\x1b[33m%s\x1b[0m" + "\x1b[1m\x1b[36m%s\x1b[0m", (new Date()).toLocaleString(), ` [${name}]:`, @@ -20,22 +22,38 @@ export const log = (name: string, ...args: string[]) => { }; export const warn = (name: string, ...args: any[]) => { + consorArgs(args); console.warn( - "\x1b[33m%s\x1b[0m"+ + "\x1b[33m%s\x1b[0m" + "\x1b[1m\x1b[36m%s\x1b[0m", (new Date()).toLocaleString(), ` [${name}]:`, - ...args + ...args ); }; export const error = (name: string, ...args: any[]) => { + consorArgs(args); console.error( - "\x1b[33m%s\x1b[0m"+ + "\x1b[33m%s\x1b[0m" + "\x1b[1m\x1b[41m%s\x1b[0m\x1b[41m", - (new Date()).toLocaleString()+" ", + (new Date()).toLocaleString() + " ", `[${name}]:`, ...args, - "\x1b[0m" + "\x1b[0m" ); -}; \ No newline at end of file +}; + +const consorArgs = (args: any[]) => { + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + censorLogArg(arg); + } +} +const censorLogArg = (arg: any) => { + if (typeof arg != "object") return; + for (let key in arg) { + if (key == "accountKey") arg[key] = new Array(arg[key].length).fill("*").join(""); + censorLogArg(arg[key]); + } +} \ No newline at end of file