Add rudimentary user management to dashboard

This commit is contained in:
Dragory 2021-09-05 16:42:35 +03:00
parent 48c4b3578d
commit 3b09d2d679
No known key found for this signature in database
GPG key ID: 5F387BA66DF8AAC1
12 changed files with 395 additions and 81 deletions

View file

@ -149,7 +149,7 @@ export function initAuth(app: express.Express) {
return res.json({ valid: false });
}
res.json({ valid: true });
res.json({ valid: true, userId });
});
app.post("/auth/logout", ...apiTokenAuthHandlers(), async (req: Request, res: Response) => {
await apiLogins.expireApiKey(req.user!.apiKey);

View file

@ -3,15 +3,21 @@ import express, { Request, Response } from "express";
import { YAMLException } from "js-yaml";
import { validateGuildConfig } from "../configValidator";
import { AllowedGuilds } from "../data/AllowedGuilds";
import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments";
import { ApiPermissionAssignments, ApiPermissionTypes } from "../data/ApiPermissionAssignments";
import { Configs } from "../data/Configs";
import { apiTokenAuthHandlers } from "./auth";
import { hasGuildPermission, requireGuildPermission } from "./permissions";
import { clientError, ok, serverError, unauthorized } from "./responses";
import { loadYamlSafely } from "../utils/loadYamlSafely";
import { ObjectAliasError } from "../utils/validateNoObjectAliases";
import { isSnowflake } from "../utils";
import moment from "moment-timezone";
import { ApiAuditLog } from "../data/ApiAuditLog";
import { AuditLogEventTypes } from "../data/apiAuditLogTypes";
import { Queue } from "../Queue";
const apiPermissionAssignments = new ApiPermissionAssignments();
const auditLog = new ApiAuditLog();
export function initGuildsAPI(app: express.Express) {
const allowedGuilds = new AllowedGuilds();
@ -25,6 +31,14 @@ export function initGuildsAPI(app: express.Express) {
res.json(guilds);
});
guildRouter.get(
"/my-permissions", // a
async (req: Request, res: Response) => {
const permissions = await apiPermissionAssignments.getByUserId(req.user!.userId);
res.json(permissions);
},
);
guildRouter.get("/:guildId", async (req: Request, res: Response) => {
if (!(await hasGuildPermission(req.user!.userId, req.params.guildId, ApiPermissions.ViewGuild))) {
return unauthorized(res);
@ -101,5 +115,65 @@ export function initGuildsAPI(app: express.Express) {
},
);
const permissionManagementQueue = new Queue();
guildRouter.post(
"/:guildId/set-target-permissions",
requireGuildPermission(ApiPermissions.ManageAccess),
async (req: Request, res: Response) => {
await permissionManagementQueue.add(async () => {
const { type, targetId, permissions, expiresAt } = req.body;
if (type !== ApiPermissionTypes.User) {
return clientError(res, "Invalid type");
}
if (!isSnowflake(targetId)) {
return clientError(res, "Invalid targetId");
}
const validPermissions = new Set(Object.values(ApiPermissions));
validPermissions.delete(ApiPermissions.Owner);
if (!Array.isArray(permissions) || permissions.some(p => !validPermissions.has(p))) {
return clientError(res, "Invalid permissions");
}
if (expiresAt != null && !moment.utc(expiresAt).isValid()) {
return clientError(res, "Invalid expiresAt");
}
const existingAssignment = await apiPermissionAssignments.getByGuildAndUserId(req.params.guildId, targetId);
if (existingAssignment && existingAssignment.permissions.includes(ApiPermissions.Owner)) {
return clientError(res, "Can't change owner permissions");
}
if (permissions.length === 0) {
await apiPermissionAssignments.removeUser(req.params.guildId, targetId);
await auditLog.addEntry(req.params.guildId, req.user!.userId, AuditLogEventTypes.REMOVE_API_PERMISSION, {
type: ApiPermissionTypes.User,
target_id: targetId,
});
} else {
const existing = await apiPermissionAssignments.getByGuildAndUserId(req.params.guildId, targetId);
if (existing) {
await apiPermissionAssignments.updateUserPermissions(req.params.guildId, targetId, permissions);
await auditLog.addEntry(req.params.guildId, req.user!.userId, AuditLogEventTypes.EDIT_API_PERMISSION, {
type: ApiPermissionTypes.User,
target_id: targetId,
permissions,
expires_at: existing.expires_at,
});
} else {
await apiPermissionAssignments.addUser(req.params.guildId, targetId, permissions, expiresAt);
await auditLog.addEntry(req.params.guildId, req.user!.userId, AuditLogEventTypes.ADD_API_PERMISSION, {
type: ApiPermissionTypes.User,
target_id: targetId,
permissions,
expires_at: expiresAt,
});
}
}
ok(res);
});
},
);
app.use("/guilds", guildRouter);
}

View file

@ -48,12 +48,13 @@ export class ApiPermissionAssignments extends BaseRepository {
});
}
addUser(guildId, userId, permissions: ApiPermissions[]) {
addUser(guildId, userId, permissions: ApiPermissions[], expiresAt: string | null = null) {
return this.apiPermissions.insert({
guild_id: guildId,
type: ApiPermissionTypes.User,
target_id: userId,
permissions,
expires_at: expiresAt,
});
}
@ -61,6 +62,19 @@ export class ApiPermissionAssignments extends BaseRepository {
return this.apiPermissions.delete({ guild_id: guildId, type: ApiPermissionTypes.User, target_id: userId });
}
async updateUserPermissions(guildId: string, userId: string, permissions: ApiPermissions[]): Promise<void> {
await this.apiPermissions.update(
{
guild_id: guildId,
type: ApiPermissionTypes.User,
target_id: userId,
},
{
permissions,
},
);
}
async clearExpiredPermissions() {
await this.apiPermissions
.createQueryBuilder()

View file

@ -8,7 +8,7 @@ export class ApiPermissionAssignment {
@PrimaryColumn()
guild_id: string;
@Column({ type: "string" })
@Column({ type: String })
@PrimaryColumn()
type: ApiPermissionTypes;
@ -19,8 +19,8 @@ export class ApiPermissionAssignment {
@Column("simple-array")
permissions: string[];
@Column()
expires_at: string;
@Column({ type: String, nullable: true })
expires_at: string | null;
@ManyToOne(
type => ApiUserInfo,

View file

@ -5,7 +5,7 @@ export class AddExpiresAtToApiPermissions1630837386329 implements MigrationInter
await queryRunner.addColumns("api_permissions", [
new TableColumn({
name: "expires_at",
type: "boolean",
type: "datetime",
isNullable: true,
default: null,
}),

View file

@ -9,9 +9,11 @@
"version": "1.0.0",
"dependencies": {
"highlight.js": "^9.15.10",
"humanize-duration": "^3.27.0",
"js-yaml": "^3.13.1",
"marked": "^0.7.0",
"modern-css-reset": "^1.0.4",
"moment": "^2.29.1",
"vue": "^2.6.10",
"vue-highlightjs": "git://github.com/Dragory/vue-highlightjs.git#pass-hljs-instance",
"vue-material-design-icons": "^4.1.0",
@ -6038,6 +6040,11 @@
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
"dev": true
},
"node_modules/humanize-duration": {
"version": "3.27.0",
"resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.0.tgz",
"integrity": "sha512-qLo/08cNc3Tb0uD7jK0jAcU5cnqCM0n568918E7R2XhMr/+7F37p4EY062W/stg7tmzvknNn9b/1+UhVRzsYrQ=="
},
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -7164,6 +7171,14 @@
"resolved": "https://registry.npmjs.org/modern-css-reset/-/modern-css-reset-1.4.0.tgz",
"integrity": "sha512-0crZmSFmrxkI7159rvQWjpDhy0u4+Awg/iOycJdlVn0RSeft/a+6BrQHR3IqvmdK25sqt0o6Z5Ap7cWgUee2rw=="
},
"node_modules/moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==",
"engines": {
"node": "*"
}
},
"node_modules/move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@ -17836,6 +17851,11 @@
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
"dev": true
},
"humanize-duration": {
"version": "3.27.0",
"resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.0.tgz",
"integrity": "sha512-qLo/08cNc3Tb0uD7jK0jAcU5cnqCM0n568918E7R2XhMr/+7F37p4EY062W/stg7tmzvknNn9b/1+UhVRzsYrQ=="
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -18685,6 +18705,11 @@
"resolved": "https://registry.npmjs.org/modern-css-reset/-/modern-css-reset-1.4.0.tgz",
"integrity": "sha512-0crZmSFmrxkI7159rvQWjpDhy0u4+Awg/iOycJdlVn0RSeft/a+6BrQHR3IqvmdK25sqt0o6Z5Ap7cWgUee2rw=="
},
"moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",

View file

@ -39,9 +39,11 @@
},
"dependencies": {
"highlight.js": "^9.15.10",
"humanize-duration": "^3.27.0",
"js-yaml": "^3.13.1",
"marked": "^0.7.0",
"modern-css-reset": "^1.0.4",
"moment": "^2.29.1",
"vue": "^2.6.10",
"vue-highlightjs": "git://github.com/Dragory/vue-highlightjs.git#pass-hljs-instance",
"vue-material-design-icons": "^4.1.0",

View file

@ -1,70 +1,182 @@
<template>
<div>
<h1>Guild Access</h1>
<h1>Dashboard access</h1>
<p>
<img class="inline-block w-16 mr-4" style="vertical-align: -20px" src="../../img/squint.png"> Or here
On this page you can manage who has access to the server's Zeppelin dashboard.
</p>
<div v-for="permAssignment in permissionAssignments">
<strong>{{ permAssignment.type }} {{ permAssignment.target_id }}</strong>
<permission-tree :tree="permAssignment._permissionTree" :granted-permissions="permAssignment.permissions" :on-change="onTreeUpdate.bind(null, permAssignment)" />
<h2 class="mt-8">Roles</h2>
<ul>
<li>
<strong>Owner:</strong> All permissions. Managed automatically by the bot.
</li>
<li>
<strong>Bot manager:</strong> Can manage dashboard users (including other bot managers) and edit server configuration
</li>
<li>
<strong>Bot operator:</strong> Can edit server configuration
</li>
</ul>
<h2 class="mt-8">Dashboard users</h2>
<div class="mt-4">
<div v-if="permanentPermissionAssignments.length === 0">
No dashboard users
</div>
<ul v-if="permanentPermissionAssignments.length">
<li v-for="perm in permanentPermissionAssignments">
<div class="flex gap-4">
<div>
<strong>{{ perm.target_id }}</strong>
</div>
<div class="flex gap-4">
<label class="block" v-if="isOwner(perm)">
<input type="checkbox" checked="checked" disabled>
Owner
</label>
<label class="block">
<input
type="checkbox"
:checked="hasPermission(perm, 'MANAGE_ACCESS')"
@change="ev => setPermissionValue(perm, 'MANAGE_ACCESS', ev.target.checked)"
:disabled="hasPermissionIndirectly(perm, 'MANAGE_ACCESS')"
>
Bot manager
</label>
<label class="block">
<input
type="checkbox"
:checked="hasPermission(perm, 'EDIT_CONFIG')"
@change="ev => setPermissionValue(perm, 'EDIT_CONFIG', ev.target.checked)"
:disabled="hasPermissionIndirectly(perm, 'EDIT_CONFIG')"
>
Bot operator
</label>
<a href="#" v-on:click="deletePermissionAssignment(perm)" v-if="!isOwner(perm)">
Delete
</a>
</div>
</div>
</li>
</ul>
<div class="mt-2">
<a href="#" v-on:click="addPermissionAssignment()">
Add new user
</a>
</div>
</div>
<h2 class="mt-8">Temporary dashboard users</h2>
<p>
You can add temporary dashboard users to e.g. request help from a person outside your organization.<br>
Temporary users always have <strong>Bot operator</strong> permissions.
</p>
<div v-if="temporaryPermissionAssignments.length === 0">
No temporary dashboard users
</div>
<ul v-if="temporaryPermissionAssignments.length">
<li v-for="perm in temporaryPermissionAssignments">
<div class="flex gap-4">
<div>
<strong>{{ perm.target_id }}</strong>
</div>
<div>
Expires in {{ formatTimeRemaining(perm) }}
</div>
<div>
<a href="#" v-on:click="deletePermissionAssignment(perm)">
Delete
</a>
</div>
</div>
</li>
</ul>
<div class="mt-2">
<a href="#" v-on:click="addTemporaryPermissionAssignment()">
Add temporary user for 1 hour
</a>
</div>
</div>
</template>
<script lang="ts">
import { ApiPermissions, permissionHierarchy } from "@shared/apiPermissions";
import PermissionTree from "./PermissionTree.vue";
import { applyStateToPermissionHierarchy } from "./permissionTreeUtils";
import { mapState } from "vuex";
import { GuildState } from "../../store/types";
import { ApiPermissions, hasPermission } from "@shared/apiPermissions";
import PermissionTree from "./PermissionTree.vue";
import { mapState } from "vuex";
import {
GuildPermissionAssignment,
GuildState,
RootState
} from "../../store/types";
import humanizeDuration from "humanize-duration";
import moment from "moment";
export default {
export default {
components: {PermissionTree},
data() {
return {
managerPermissions: new Set([ApiPermissions.ManageAccess]),
};
},
computed: {
...mapState({
canManage(state: RootState): boolean {
const guildPermissions = state.guilds.guildPermissionAssignments[this.$route.params.guildId] || [];
const myPermissions = guildPermissions.find(p => p.type === "USER" && p.target_id === state.auth.userId) || null;
return myPermissions && hasPermission(myPermissions.permissions, ApiPermissions.ManageAccess);
},
}),
...mapState<GuildState>("guilds", {
canManage(guilds) {
return guilds.myPermissions[this.$route.params.guildId]?.[ApiPermissions.ManageAccess];
permanentPermissionAssignments(guilds: GuildState): GuildPermissionAssignment[] {
return (guilds.guildPermissionAssignments[this.$route.params.guildId] || []).filter(perm => perm.expires_at == null);
},
permissionAssignments(guilds) {
return (guilds.guildPermissionAssignments[this.$route.params.guildId] || []).map(permAssignment => {
return {
...permAssignment,
_permissionTree: applyStateToPermissionHierarchy(permissionHierarchy, permAssignment.permissions, this.managerPermissions),
};
});
temporaryPermissionAssignments(guilds: GuildState): GuildPermissionAssignment[] {
return (guilds.guildPermissionAssignments[this.$route.params.guildId] || []).filter(perm => perm.expires_at != null);
},
}),
},
// beforeMount() {
// this.tree = applyStateToPermissionHierarchy(permissionHierarchy, this.grantedPermissions, this.managerPermissions);
// },
async mounted() {
await this.$store.dispatch("guilds/checkPermission", {
guildId: this.$route.params.guildId,
permission: ApiPermissions.ManageAccess,
});
await this.$store.dispatch("guilds/loadGuildPermissionAssignments", this.$route.params.guildId).catch(() => {});
if (! this.canManage) {
this.$router.push('/dashboard');
return;
}
await this.$store.dispatch("guilds/loadGuildPermissionAssignments", this.$route.params.guildId);
},
methods: {
// updateTreeState() {
// this.tree = applyStateToPermissionHierarchy(permissionHierarchy, this.grantedPermissions, this.managerPermissions);
// },
//
// onChange() {
// this.updateTreeState();
// }
isOwner(perm: GuildPermissionAssignment) {
return perm.permissions.has(ApiPermissions.Owner);
},
hasPermission(perm: GuildPermissionAssignment, permissionName: ApiPermissions) {
return hasPermission(perm.permissions, permissionName);
},
hasPermissionIndirectly(perm: GuildPermissionAssignment, permissionName: ApiPermissions) {
return hasPermission(perm.permissions, permissionName) && ! perm.permissions.has(permissionName);
},
setPermissionValue(perm: GuildPermissionAssignment, permissionName: ApiPermissions, value) {
if (value) {
perm.permissions.add(permissionName);
} else {
perm.permissions.delete(permissionName);
}
this.$store.dispatch("guilds/setTargetPermissions", {
guildId: this.$route.params.guildId,
type: perm.type,
targetId: perm.target_id,
permissions: Array.from(perm.permissions),
expiresAt: null,
});
this.$set(perm, "permissions", new Set(perm.permissions));
},
onTreeUpdate(targetPermissions) {
this.$store.dispatch("guilds/setTargetPermissions", {
@ -73,7 +185,58 @@
type: targetPermissions.type,
permissions: targetPermissions.permissions,
});
},
formatTimeRemaining(perm: GuildPermissionAssignment) {
const ms = Math.max(moment.utc(perm.expires_at).valueOf() - Date.now(), 0);
return humanizeDuration(ms, { largest: 2, round: true });
},
addPermissionAssignment() {
const userId = window.prompt("Enter user ID");
if (!userId) {
return;
}
this.$store.dispatch("guilds/setTargetPermissions", {
guildId: this.$route.params.guildId,
type: "USER",
targetId: userId,
permissions: [ApiPermissions.EditConfig],
expiresAt: null,
});
},
addTemporaryPermissionAssignment() {
const userId = window.prompt("Enter user ID");
if (!userId) {
return;
}
const expiresAt = moment.utc().add(1, "hour").format("YYYY-MM-DD HH:mm:ss");
this.$store.dispatch("guilds/setTargetPermissions", {
guildId: this.$route.params.guildId,
type: "USER",
targetId: userId,
permissions: [ApiPermissions.EditConfig],
expiresAt,
});
},
deletePermissionAssignment(perm: GuildPermissionAssignment) {
const confirm = window.confirm(`Remove ${perm.target_id} from dashboard users?`);
if (! confirm) {
return;
}
this.$store.dispatch("guilds/setTargetPermissions", {
guildId: this.$route.params.guildId,
type: perm.type,
targetId: perm.target_id,
permissions: [],
expiresAt: null,
});
},
}
}
</script>

View file

@ -17,9 +17,8 @@
<div class="text-gray-600 text-sm leading-tight">{{ guild.id }}</div>
</div>
<div class="pt-1">
<span class="inline-block bg-gray-700 rounded px-1 opacity-50 select-none">Info</span>
<router-link class="inline-block bg-gray-700 rounded px-1 hover:bg-gray-800" :to="'/dashboard/guilds/' + guild.id + '/config'">Config</router-link>
<span class="inline-block bg-gray-700 rounded px-1 opacity-50 select-none">Access</span>
<router-link v-if="canManageAccess(guild.id)" class="inline-block bg-gray-700 rounded px-1 hover:bg-gray-800" :to="'/dashboard/guilds/' + guild.id + '/access'">Access</router-link>
</div>
</div>
</div>
@ -28,12 +27,15 @@
</div>
</template>
<script>
import {mapState} from "vuex";
<script lang="ts">
import { mapState } from "vuex";
import { ApiPermissions, hasPermission } from "@shared/apiPermissions";
import { AuthState, GuildState } from "../../store/types";
export default {
async mounted() {
await this.$store.dispatch("guilds/loadAvailableGuilds");
await this.$store.dispatch("guilds/loadMyPermissionAssignments");
this.loading = false;
},
data() {
@ -41,7 +43,7 @@
},
computed: {
...mapState('guilds', {
guilds: state => {
guilds: (state: GuildState) => {
const guilds = Array.from(state.available.values());
guilds.sort((a, b) => {
if (a.name > b.name) return 1;
@ -52,7 +54,20 @@
});
return guilds;
},
guildPermissionAssignments: (state: GuildState) => state.guildPermissionAssignments,
}),
...mapState('auth', {
userId: (state: AuthState) => state.userId!,
}),
},
methods: {
canManageAccess(guildId: string) {
const guildPermissions = this.guildPermissionAssignments[guildId] || [];
const myPermissions = guildPermissions.find(p => p.type === "USER" && p.target_id === this.userId) || null;
return myPermissions && hasPermission(new Set(myPermissions.permissions), ApiPermissions.ManageAccess);
},
},
};
</script>

View file

@ -12,6 +12,7 @@ export const AuthStore: Module<AuthState, RootState> = {
apiKey: null,
loadedInitialAuth: false,
authRefreshInterval: null,
userId: null,
},
actions: {
@ -23,7 +24,7 @@ export const AuthStore: Module<AuthState, RootState> = {
try {
const result = await post("auth/validate-key", { key: storedKey });
if (result.valid) {
await dispatch("setApiKey", storedKey);
await dispatch("setApiKey", { key: storedKey, userId: result.userId });
return;
}
} catch {} // tslint:disable-line
@ -35,9 +36,9 @@ export const AuthStore: Module<AuthState, RootState> = {
commit("markInitialAuthLoaded");
},
setApiKey({ commit, state, dispatch }, newKey: string) {
localStorage.setItem("apiKey", newKey);
commit("setApiKey", newKey);
setApiKey({ commit, state, dispatch }, { key, userId }) {
localStorage.setItem("apiKey", key);
commit("setApiKey", { key, userId });
dispatch("startAuthAutoRefresh");
},
@ -64,7 +65,7 @@ export const AuthStore: Module<AuthState, RootState> = {
await dispatch("endAuthAutoRefresh");
localStorage.removeItem("apiKey");
commit("setApiKey", null);
commit("setApiKey", { key: null, userId: null });
},
async logout({ dispatch }) {
@ -79,8 +80,9 @@ export const AuthStore: Module<AuthState, RootState> = {
},
mutations: {
setApiKey(state: AuthState, key) {
setApiKey(state: AuthState, { key, userId }) {
state.apiKey = key;
state.userId = userId;
},
setAuthRefreshInterval(state: AuthState, interval: IntervalType | null) {

View file

@ -11,7 +11,6 @@ export const GuildStore: Module<GuildState, RootState> = {
availableGuildsLoadStatus: LoadStatus.None,
available: new Map(),
configs: {},
myPermissions: {},
guildPermissionAssignments: {},
},
@ -48,9 +47,14 @@ export const GuildStore: Module<GuildState, RootState> = {
await post(`guilds/${guildId}/config`, { config });
},
async checkPermission({ commit }, { guildId, permission }) {
const result = await post(`guilds/${guildId}/check-permission`, { permission });
commit("setMyPermission", { guildId, permission, value: result.result });
async loadMyPermissionAssignments({ commit }) {
const myPermissionAssignments = await get(`guilds/my-permissions`);
for (const permissionAssignment of myPermissionAssignments) {
commit("setGuildPermissionAssignments", {
guildId: permissionAssignment.guild_id,
permissionAssignments: [permissionAssignment],
});
}
},
async loadGuildPermissionAssignments({ commit }, guildId) {
@ -58,8 +62,9 @@ export const GuildStore: Module<GuildState, RootState> = {
commit("setGuildPermissionAssignments", { guildId, permissionAssignments });
},
async setTargetPermissions({ commit }, { guildId, targetId, type, permissions }) {
commit("setTargetPermissions", { guildId, targetId, type, permissions });
async setTargetPermissions({ commit }, { guildId, targetId, type, permissions, expiresAt }) {
await post(`guilds/${guildId}/set-target-permissions`, { guildId, targetId, type, permissions, expiresAt });
commit("setTargetPermissions", { guildId, targetId, type, permissions, expiresAt });
},
},
@ -77,12 +82,11 @@ export const GuildStore: Module<GuildState, RootState> = {
Vue.set(state.configs, guildId, config);
},
setMyPermission(state: GuildState, { guildId, permission, value }) {
Vue.set(state.myPermissions, guildId, state.myPermissions[guildId] || {});
Vue.set(state.myPermissions[guildId], permission, value);
},
setGuildPermissionAssignments(state: GuildState, { guildId, permissionAssignments }) {
if (!state.guildPermissionAssignments) {
Vue.set(state, "guildPermissionAssignments", {});
}
Vue.set(
state.guildPermissionAssignments,
guildId,
@ -93,12 +97,29 @@ export const GuildStore: Module<GuildState, RootState> = {
);
},
setTargetPermissions(state: GuildState, { guildId, targetId, type, permissions }) {
setTargetPermissions(state: GuildState, { guildId, targetId, type, permissions, expiresAt }) {
const guildPermissionAssignments = state.guildPermissionAssignments[guildId] || [];
if (permissions.length === 0) {
// No permissions -> remove permission assignment
guildPermissionAssignments.splice(
guildPermissionAssignments.findIndex(p => p.target_id === targetId && p.type === type),
1,
);
} else {
// Update/add permission assignment
const itemToEdit = guildPermissionAssignments.find(p => p.target_id === targetId && p.type === type);
if (!itemToEdit) return;
if (itemToEdit) {
itemToEdit.permissions = new Set(permissions);
} else {
state.guildPermissionAssignments[guildId].push({
type,
target_id: targetId,
permissions: new Set(permissions),
expires_at: expiresAt,
});
}
}
itemToEdit.permissions = permissions;
state.guildPermissionAssignments = { ...state.guildPermissionAssignments };
},
},

View file

@ -1,5 +1,4 @@
import { ApiPermissions } from "@shared/apiPermissions";
import { ApiPermissionTypes } from "../../../backend/src/data/ApiPermissionAssignments";
export enum LoadStatus {
None = 1,
@ -14,6 +13,14 @@ export interface AuthState {
apiKey: string | null;
loadedInitialAuth: boolean;
authRefreshInterval: IntervalType | null;
userId: string | null;
}
export interface GuildPermissionAssignment {
type: string;
target_id: string;
permissions: Set<ApiPermissions>;
expires_at: string | null;
}
export interface GuildState {
@ -29,17 +36,8 @@ export interface GuildState {
configs: {
[key: string]: string;
};
myPermissions: {
[guildId: string]: {
[K in ApiPermissions]?: boolean;
};
};
guildPermissionAssignments: {
[guildId: string]: Array<{
target_id: string;
type: ApiPermissionTypes;
permissions: Set<ApiPermissions>;
}>;
[guildId: string]: GuildPermissionAssignment[];
};
}