Skip to content

Commit

Permalink
Allow server admins access their server dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
l7ssha committed Dec 28, 2024
1 parent f0919d8 commit ac707b1
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 16 deletions.
5 changes: 2 additions & 3 deletions frontend/src/component/NavigationBar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import {discordLoginUri, getUserAvatar} from "../constants";
import {isLoggedIn, getUser, logout, getCurrentUserPermissions} from "../service/auth";
import {isLoggedIn, getUser, logout} from "../service/auth";

import {
AppBar,
Expand All @@ -23,7 +23,6 @@ export default function NavigationBar() {
const navigate = useNavigate();

const userLoggedIn = isLoggedIn();
const userPermissions = getCurrentUserPermissions();

const [anchorElUser, setAnchorElUser] = React.useState<null | HTMLElement>(null);
const toggleUserMenu = (event?: React.MouseEvent<HTMLElement>) => {
Expand Down Expand Up @@ -109,7 +108,7 @@ export default function NavigationBar() {
Running on Dart
</Typography>
<Box sx={{flexGrow: 1, display: {xs: 'none', md: 'flex'}}}>
<ProtectedRouteButton permissions={userPermissions} label="Guilds" navigateTo="/guilds" requiredPermissions={[1]} />
<ProtectedRouteButton label="Guilds" navigateTo="/guilds" />
</Box>

{userElement}
Expand Down
9 changes: 5 additions & 4 deletions frontend/src/component/ProtectedRouteButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ import {Button} from "@mui/material";
import React from "react";
import {useNavigate} from "react-router-dom";
import {containsAll} from "../util";
import {getCurrentUserPermissions} from "../service/auth";

export type ProtectedRouteButtonProps = {
permissions: number[]|null,
label: string,
navigateTo: string,
requiredPermissions: number[]
requiredPermissions?: number[]
}

export function ProtectedRouteButton({permissions, label, navigateTo, requiredPermissions}: ProtectedRouteButtonProps) {
export function ProtectedRouteButton({label, navigateTo, requiredPermissions}: ProtectedRouteButtonProps) {
const navigate = useNavigate();
const userPermissions = getCurrentUserPermissions();

if (permissions == null || !containsAll(permissions, requiredPermissions)) {
if (userPermissions == null || (requiredPermissions != null && !containsAll(userPermissions, requiredPermissions))) {
return <span></span>;
}

Expand Down
2 changes: 1 addition & 1 deletion lib/src/cli.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ bool handleCli(List<String> args) {
if (args.isNotEmpty && dev) {
final commandArg = args.first;
if (commandArg == 'generate-test-jwt') {
print(generateJwt('test', maxAge: Duration(days: 31), permissions: JwtPermission.intValues()));
print(generateJwt('1300543841996374131', maxAge: Duration(days: 31), permissions: []));
return true;
}
}
Expand Down
49 changes: 41 additions & 8 deletions lib/src/web_app/api_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,38 @@ class WebServer {
final perPage = _intOrDefault(request.requestedUri.queryParameters['perPage'], 25);
final page = _intOrDefault(request.requestedUri.queryParameters['page'], 1);

final guilds = client.guilds.cache.values.skip(perPage * (page - 1)).take(perPage);
final jwtPermissions = getJwtPermissionsFromRequest(request);

var guilds = client.guilds.cache.values;
var total = client.guilds.cache.length;
if (!jwtPermissions.contains(JwtPermission.guilds.value)) {
final userId = getLoggedInUserIdFromRequest(request);
if (userId == null) {
guilds = [];
total = 0;
} else {
final userIdSnowflake = Snowflake.parse(userId);

guilds = guilds.where((guild) {
final member = guild.members.cache[userIdSnowflake];
if (member == null) {
return false;
}

return member.permissions?.isAdministrator ?? false;
});

total = guilds.length;
}
}

guilds = guilds.skip(perPage * (page - 1)).take(perPage);

return createOkResponse(createPaginationResponse(
data: await mapGuildsToGuildReducedData(guilds).toList(),
page: page,
perPage: perPage,
total: client.guilds.cache.length,
total: total,
));
}

Expand Down Expand Up @@ -214,12 +239,15 @@ class WebServer {

return shelf_router.Router()
..get("/api/server-info", _handleServerInfo)
..get("/api/guilds", _requireJwt(_handleGuilds, [JwtPermission.guilds]))
..get("/api/guilds/<id>", _requireJwt(_handleGuildDetails, [JwtPermission.guilds]))
..get("/api/guilds/<id>/tags", _requireJwt(_handleGuildTags, [JwtPermission.guilds]))
..get("/api/guilds/<id>/reminders", _requireJwt(_handleGuildReminders, [JwtPermission.guilds]))
..get("/api/guilds/<id>/members/<member_id>", _requireJwt(_handleGuildMember, [JwtPermission.guilds]))
..get("/api/guilds/<id>/channels/<channel_id>", _requireJwt(_handleGuildChannel, [JwtPermission.guilds]))
..get("/api/guilds", _requireJwt(_handleGuilds))
..get("/api/guilds/<id>", _requireJwt(_requireAdminUserOrPerms(_handleGuildDetails, [JwtPermission.guilds])))
..get("/api/guilds/<id>/tags", _requireJwt(_requireAdminUserOrPerms(_handleGuildTags, [JwtPermission.guilds])))
..get("/api/guilds/<id>/reminders",
_requireJwt(_requireAdminUserOrPerms(_handleGuildReminders, [JwtPermission.guilds])))
..get("/api/guilds/<id>/members/<member_id>",
_requireJwt(_requireAdminUserOrPerms(_handleGuildMember, [JwtPermission.guilds])))
..get("/api/guilds/<id>/channels/<channel_id>",
_requireJwt(_requireAdminUserOrPerms(_handleGuildChannel, [JwtPermission.guilds])))
..get("/api/validate-oauth", _handleValidateCode)
..all(r"/<ignored|.+\w+\.\w+$>", staticHandler)
..all("/<ignored|.*>", _handleIndex);
Expand All @@ -228,6 +256,11 @@ class WebServer {
shelf.Handler _requireJwt(shelf.Handler inner, [List<JwtPermission> permissions = const []]) =>
shelf.Pipeline().addMiddleware(processJwt(permissions)).addHandler(inner);

shelf.Handler _requireAdminUserOrPerms(shelf.Handler inner, [List<JwtPermission> orPermissions = const []]) =>
shelf.Pipeline()
.addMiddleware(processGuildUser(orPermissions: orPermissions.map((e) => e.value).toList()))
.addHandler(inner);

Future<void> startServer() async {
if (!webServerEnabled) {
_logger.info("Web server not enabled skipping");
Expand Down
50 changes: 50 additions & 0 deletions lib/src/web_app/jwt.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import 'package:injector/injector.dart';
import 'package:jaguar_jwt/jaguar_jwt.dart';
import 'package:nyxx/nyxx.dart';
import 'package:running_on_dart/running_on_dart.dart';
import 'package:running_on_dart/src/web_app/utils.dart';
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf_router/shelf_router.dart';

enum JwtPermission {
guilds(1);
Expand Down Expand Up @@ -80,6 +83,53 @@ shelf.Middleware processJwt(List<JwtPermission> permissions) => (innerHandler) {
return createForbiddenResponse("Missing permissions");
}

final updatedRequest = request.change(context: {
'jwt_permissions': permissions,
'user_id': claim.subject.toString(),
});

return innerHandler(updatedRequest);
};
};

shelf.Middleware processGuildUser({List<int> orPermissions = const []}) => (innerHandler) {
return (request) {
if (orPermissions.isNotEmpty) {
final jwtPermissions = getJwtPermissionsFromRequest(request);

if (jwtPermissions.containsAll(orPermissions)) {
return innerHandler(request);
}
}

final guildId = request.params['id'];
if (guildId == null) {
return createForbiddenResponse();
}

final guild = Injector.appInstance.get<NyxxGateway>().guilds.cache[Snowflake.parse(guildId)];
if (guild == null) {
return createForbiddenResponse();
}

final userId = getLoggedInUserIdFromRequest(request);
if (userId == null) {
return createForbiddenResponse();
}

final member = guild.members.cache[Snowflake.parse(userId)];
if (member == null) {
return createForbiddenResponse();
}

if (!(member.permissions?.isAdministrator ?? false)) {
return createForbiddenResponse();
}

return innerHandler(request);
};
};

Set<int> getJwtPermissionsFromRequest(shelf.Request request) => (request.context['jwt_permissions'] as Set<int>?) ?? {};

String? getLoggedInUserIdFromRequest(shelf.Request request) => request.context['user_id'] as String?;

0 comments on commit ac707b1

Please sign in to comment.