add rights and a few menus to go along with that

This commit is contained in:
MathMan05 2025-03-15 22:06:19 -05:00
parent b944be96a4
commit 11d2a8cb32
7 changed files with 339 additions and 9 deletions

View file

@ -29,6 +29,7 @@ import {Emoji} from "./emoji.js";
import {Play} from "./audio/play.js";
import {Message} from "./message.js";
import {badgeArr} from "./Dbadges.js";
import {Rights} from "./rights.js";
const wsCodesRetry = new Set([4000, 4001, 4002, 4003, 4005, 4007, 4008, 4009]);
@ -79,11 +80,13 @@ class Localuser {
this.play = _;
});
if (userinfo === -1) {
this.rights = new Rights("");
return;
}
this.token = userinfo.token;
this.userinfo = userinfo;
this.perminfo.guilds ??= {};
this.perminfo.user ??= {};
this.serverurls = this.userinfo.serverurls;
this.initialized = false;
this.info = this.serverurls;
@ -91,6 +94,8 @@ class Localuser {
"Content-type": "application/json; charset=UTF-8",
Authorization: this.userinfo.token,
};
const rights = this.perminfo.user.rights || "875069521787904";
this.rights = new Rights(rights);
}
async gottenReady(ready: readyjson): Promise<void> {
await I18n.done;
@ -406,6 +411,11 @@ class Localuser {
await promise;
}
relationshipsUpdate = () => {};
rights: Rights;
updateRights(rights: string | number) {
this.rights.update(rights);
this.perminfo.user.rights = rights;
}
async handleEvent(temp: wsjson) {
console.debug(temp);
if (temp.s) this.lastSequence = temp.s;
@ -1776,6 +1786,135 @@ class Localuser {
}
});
}
if (
this.rights.hasPermission("OPERATOR") ||
this.rights.hasPermission("CREATE_REGISTRATION_TOKENS")
) {
const manageInstance = settings.addButton(I18n.localuser.manageInstance());
if (this.rights.hasPermission("OPERATOR")) {
manageInstance.addButtonInput("", I18n.manageInstance.stop(), () => {
const menu = new Dialog("");
const options = menu.float.options;
options.addTitle(I18n.manageInstance.AreYouSureStop());
const yesno = options.addOptions("", {ltr: true});
yesno.addButtonInput("", I18n.yes(), () => {
fetch(this.info.api + "/stop", {headers: this.headers, method: "POST"});
menu.hide();
});
yesno.addButtonInput("", I18n.no(), () => {
menu.hide();
});
menu.show();
});
}
if (this.rights.hasPermission("CREATE_REGISTRATION_TOKENS")) {
manageInstance.addButtonInput("", I18n.manageInstance.createTokens(), () => {
const tokens = manageInstance.addSubOptions(I18n.manageInstance.createTokens(), {
noSubmit: true,
});
const count = tokens.addTextInput(I18n.manageInstance.count(), () => {}, {
initText: "1",
});
const length = tokens.addTextInput(I18n.manageInstance.length(), () => {}, {
initText: "32",
});
const format = tokens.addSelect(
I18n.manageInstance.format(),
() => {},
[
I18n.manageInstance.TokenFormats.JSON(),
I18n.manageInstance.TokenFormats.plain(),
I18n.manageInstance.TokenFormats.URLs(),
],
{
defaultIndex: 2,
},
);
format.watchForChange((e) => {
if (e !== 2) {
urlOption.removeAll();
} else {
makeURLMenu();
}
});
const urlOption = tokens.addOptions("");
const urlOptionsJSON = {
url: window.location.origin,
type: "Jank",
};
function makeURLMenu() {
urlOption
.addTextInput(I18n.manageInstance.clientURL(), () => {}, {
initText: urlOptionsJSON.url,
})
.watchForChange((str) => {
urlOptionsJSON.url = str;
});
urlOption
.addSelect(
I18n.manageInstance.regType(),
() => {},
["Jank", I18n.manageInstance.genericType()],
{
defaultIndex: ["Jank", "generic"].indexOf(urlOptionsJSON.type),
},
)
.watchForChange((i) => {
urlOptionsJSON.type = ["Jank", "generic"][i];
});
}
makeURLMenu();
tokens.addButtonInput("", I18n.manageInstance.create(), async () => {
const params = new URLSearchParams();
params.set("count", count.value);
params.set("length", length.value);
const json = (await (
await fetch(
this.info.api + "/auth/generate-registration-tokens?" + params.toString(),
{
headers: this.headers,
},
)
).json()) as {tokens: string[]};
if (format.index === 0) {
pre.textContent = JSON.stringify(json.tokens);
} else if (format.index === 1) {
pre.textContent = json.tokens.join("\n");
} else if (format.index === 2) {
if (urlOptionsJSON.type === "Jank") {
const options = new URLSearchParams();
options.set("instance", this.info.wellknown);
pre.textContent = json.tokens
.map((token) => {
options.set("token", token);
return `${urlOptionsJSON.url}/register?` + options.toString();
})
.join("\n");
} else {
const options = new URLSearchParams();
pre.textContent = json.tokens
.map((token) => {
options.set("token", token);
return `${urlOptionsJSON.url}/register?` + options.toString();
})
.join("\n");
}
}
});
tokens.addButtonInput("", I18n.manageInstance.copy(), async () => {
try {
if (pre.textContent) {
await navigator.clipboard.writeText(pre.textContent);
}
} catch (err) {
console.error(err);
}
});
const pre = document.createElement("pre");
tokens.addHTMLArea(pre);
});
}
}
settings.show();
}
readonly botTokens: Map<string, string> = new Map();

View file

@ -88,7 +88,10 @@ if (instancein) {
}
timeout = setTimeout(() => checkInstance((instancein as HTMLInputElement).value), 1000);
});
if (localStorage.getItem("instanceinfo")) {
if (
localStorage.getItem("instanceinfo") &&
!new URLSearchParams(window.location.search).get("instance")
) {
const json = JSON.parse(localStorage.getItem("instanceinfo")!);
if (json.value) {
(instancein as HTMLInputElement).value = json.value;

View file

@ -42,9 +42,13 @@ async function registertry(e: Event) {
const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}");
const apiurl = new URL(instanceInfo.api);
let add = "";
const token = new URLSearchParams(window.location.search).get("token");
if (token) {
add = "?" + new URLSearchParams([["token", token]]).toString();
}
try {
const response = await fetch(apiurl + "/auth/register", {
const response = await fetch(apiurl + "/auth/register" + add, {
body: JSON.stringify({
date_of_birth: dateofbirth,
email,

116
src/webpage/rights.ts Normal file
View file

@ -0,0 +1,116 @@
import {I18n} from "./i18n.js";
class Rights {
allow!: bigint;
constructor(allow: string | number) {
this.update(allow);
}
update(allow: string | number) {
try {
this.allow = BigInt(allow);
} catch {
this.allow = 875069521787904n;
console.error(
`Something really stupid happened with a permission with allow being ${allow}, execution will still happen, but something really stupid happened, please report if you know what caused this.`,
);
}
}
getPermissionbit(b: number, big: bigint): boolean {
return Boolean((big >> BigInt(b)) & 1n);
}
setPermissionbit(b: number, state: boolean, big: bigint): bigint {
const bit = 1n << BigInt(b);
return (big & ~bit) | (BigInt(state) << BigInt(b)); //thanks to geotale for this code :3
}
static *info(): Generator<{name: string; readableName: string; description: string}> {
throw new Error("Isn't implemented");
for (const thing of this.permisions) {
yield {
name: thing,
readableName: I18n.getTranslation("permissions.readableNames." + thing),
description: I18n.getTranslation("permissions.descriptions." + thing),
};
}
}
static readonly permisions = [
"OPERATOR",
"MANAGE_APPLICATIONS",
"MANAGE_GUILDS",
"MANAGE_MESSAGES",
"MANAGE_RATE_LIMITS",
"MANAGE_ROUTING",
"MANAGE_TICKETS",
"MANAGE_USERS",
"ADD_MEMBERS",
"BYPASS_RATE_LIMITS",
"CREATE_APPLICATIONS",
"CREATE_CHANNELS",
"CREATE_DMS",
"CREATE_DM_GROUPS",
"CREATE_GUILDS",
"CREATE_INVITES",
"CREATE_ROLES",
"CREATE_TEMPLATES",
"CREATE_WEBHOOKS",
"JOIN_GUILDS",
"PIN_MESSAGES",
"SELF_ADD_REACTIONS",
"SELF_DELETE_MESSAGES",
"SELF_EDIT_MESSAGES",
"SELF_EDIT_NAME",
"SEND_MESSAGES",
"USE_ACTIVITIES",
"USE_VIDEO",
"USE_VOICE",
"INVITE_USERS",
"SELF_DELETE_DISABLE",
"DEBTABLE",
"CREDITABLE",
"KICK_BAN_MEMBERS",
"SELF_LEAVE_GROUPS",
"PRESENCE",
"SELF_ADD_DISCOVERABLE",
"MANAGE_GUILD_DIRECTORY",
"POGGERS",
"USE_ACHIEVEMENTS",
"INITIATE_INTERACTIONS",
"RESPOND_TO_INTERACTIONS",
"SEND_BACKDATED_EVENTS",
"USE_MASS_INVITES",
"ACCEPT_INVITES",
"SELF_EDIT_FLAGS",
"EDIT_FLAGS",
"MANAGE_GROUPS",
"VIEW_SERVER_STATS",
"RESEND_VERIFICATION_EMAIL",
"CREATE_REGISTRATION_TOKENS",
];
getPermission(name: string): boolean {
if (undefined === Rights.permisions.indexOf(name)) {
console.error(name + " is not found in map", Rights.permisions);
}
return this.getPermissionbit(Rights.permisions.indexOf(name), this.allow);
}
hasPermission(name: string, adminOverride = true): boolean {
if (this.getPermissionbit(Rights.permisions.indexOf(name), this.allow)) return true;
if (name !== "OPERATOR" && adminOverride) return this.hasPermission("OPERATOR");
return false;
}
setPermission(name: string, setto: number): void {
const bit = Rights.permisions.indexOf(name);
if (bit === undefined) {
return console.error(
"Tried to set permission to " + setto + " for " + name + " but it doesn't exist",
);
}
if (setto === 0) {
this.allow = this.setPermissionbit(bit, false, this.allow);
} else if (setto === 1) {
this.allow = this.setPermissionbit(bit, true, this.allow);
} else {
console.error("invalid number entered:" + setto);
}
}
}
export {Rights};

View file

@ -4,6 +4,11 @@ import {Guild} from "./guild.js";
import {SnowFlake} from "./snowflake.js";
import {rolesjson} from "./jsontypes.js";
import {Search} from "./search.js";
import {OptionsElement, Buttons} from "./settings.js";
import {Contextmenu} from "./contextmenu.js";
import {Channel} from "./channel.js";
import {I18n} from "./i18n.js";
class Role extends SnowFlake {
permissions: Permissions;
owner: Guild;
@ -135,10 +140,7 @@ class PermissionToggle implements OptionsElement<number> {
}
submit() {}
}
import {OptionsElement, Buttons} from "./settings.js";
import {Contextmenu} from "./contextmenu.js";
import {Channel} from "./channel.js";
import {I18n} from "./i18n.js";
class RoleList extends Buttons {
permissions: [Role, Permissions][];
permission: Permissions;

View file

@ -10,6 +10,7 @@ import {Search} from "./search.js";
import {I18n} from "./i18n.js";
import {Direct} from "./direct.js";
import {Hover} from "./hover.js";
import {Dialog} from "./settings.js";
class User extends SnowFlake {
owner: Localuser;
@ -69,6 +70,10 @@ class User extends SnowFlake {
"totp_secret",
"webauthn_enabled",
]);
if (!this.localuser.rights.getPermission("OPERATOR")) {
//Unless the user is an operator, we really shouldn't ever see this
bad.add("rights");
}
for (const thing of bad) {
if (json.hasOwnProperty(thing)) {
console.error(thing + " should not be exposed to the client");
@ -346,6 +351,36 @@ class User extends SnowFlake {
navigator.clipboard.writeText(this.id);
},
);
this.contextmenu.addSeperator();
this.contextmenu.addButton(
() => I18n.user.instanceBan(),
function (this: User) {
const menu = new Dialog("");
const options = menu.float.options;
options.addTitle(I18n.user.confirmInstBan(this.name));
const opt = options.addOptions("", {ltr: true});
opt.addButtonInput("", I18n.yes(), () => {
fetch(this.info.api + "/users/" + this.id, {
headers: this.localuser.headers,
method: "POST",
});
menu.hide();
});
opt.addButtonInput("", I18n.no(), () => {
menu.hide();
});
menu.show();
},
{
visable: function () {
return this.localuser.rights.hasPermission("MANAGE_USERS");
},
color: "red",
},
);
console.warn("this ran");
}
static checkuser(user: User | userjson, owner: Localuser): User {
@ -461,6 +496,14 @@ class User extends SnowFlake {
}
(this as any)[key] = (json as any)[key];
}
if ("rights" in this) {
if (
this === this.localuser.user &&
(typeof this.rights == "string" || typeof this.rights == "number")
) {
this.localuser.updateRights(this.rights);
}
}
}
bind(html: HTMLElement, guild: Guild | null = null, error = true): void {

View file

@ -321,7 +321,26 @@
"areYouSureDelete": "Are you sure you want to delete your account? If so enter the phrase $1",
"sillyDeleteConfirmPhrase": "Shrek is love, Shrek is life",
"deleteAccountButton": "Delete account",
"mustTypePhrase": "To delete your account you must type the phrase"
"mustTypePhrase": "To delete your account you must type the phrase",
"manageInstance": "Manage Instance"
},
"manageInstance": {
"stop": "Stop instance",
"AreYouSureStop": "Are you sure you want to stop this instance?",
"createTokens": "Create Registration Tokens",
"count": "Count:",
"length": "Length:",
"format": "Format:",
"TokenFormats": {
"plain": "Plain",
"JSON": "JSON formatted",
"URLs": "Invite URLs"
},
"create": "Create",
"clientURL": "Client URL:",
"regType": "Register token URL type",
"genericType": "Generic",
"copy": "Copy"
},
"message": {
"reactionAdd": "Add reaction",
@ -403,7 +422,9 @@
"ban": "Ban member",
"addRole": "Add roles",
"removeRole": "Remove roles",
"editServerProfile": "Edit server profile"
"editServerProfile": "Edit server profile",
"instanceBan": "Instance ban",
"confirmInstBan": "Are you sure you want to instance ban $1?"
},
"login": {
"checking": "Checking Instance",
@ -453,6 +474,8 @@
"name:": "Name:",
"confirmDel": "Are you sure you want to delete this emoji?"
},
"incorrectURLS": "## This instance has likely sent the incorrect URLs.\n### If you're the instance owner please see [here](https://docs.spacebar.chat/setup/server/) under *Connecting from remote machines* to correct the issue.\n Would you like Jank Client to automatically try to fix this error to let you connect to the instance?",
"uploadFilesText": "Upload your files here!",
"errorReconnect": "Unable to connect to the server, retrying in **$1** seconds...",
"retrying": "Retrying...",