Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permission System & Improved Commands #14

Merged
merged 21 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions EsefexApi/.golangci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
linters:
# enable-all: true
disable:
- forbidigo
- golint
- depguard
- varnamelen
- ifshort
- nosnakecase
- deadcode
- varcheck
- exhaustivestruct
- maligned
- interfacer
- scopelint
- structcheck

run:
skip-files:
- ".+_testing.go$" # skip files for quick testing of fuctionality
skip-dirs:
- "cmd/testing"
15 changes: 15 additions & 0 deletions EsefexApi/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch file",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "main.go",
}
]
}
31 changes: 31 additions & 0 deletions EsefexApi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Discord Commands

```go
/help -> shows general Help

/sound -> shows sound help
/sound upload -> upload sound
/sound list -> list sounds
/sound delete -> delete sounds
/sound modify -> modify sounds

/permission -> shows permissions help
/permission set role <roleid> <permissionID> <value> -> sets permissions for user
/permission set user <userID> <permissionID> <value>
/permission set channel <channelID> <permissionID> <value>

/permission get role/user/channel -> gets permission value for role/user/channel
/permission list role/user/channel -> list all permissions for role/user/channel
/permission clear role/user/channel -> clears permissions for role/user/channel

/bot -> shows bot help
/bot join -> makes bot join
/bot leave -> makes bot leave
/bot stats -> displays bot stats

/config -> change bot config

/user link -> link user
/user unlink -> unlink user
/user stats -> show user stats
```
23 changes: 12 additions & 11 deletions EsefexApi/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type HttpApi struct {
func NewHttpApi(dbs *db.Databases, plr audioplayer.IAudioPlayer, ds *discordgo.Session, apiPort int, cProto string) *HttpApi {
return &HttpApi{
handlers: routes.NewRouteHandlers(dbs, plr, ds, cProto),
mw: middleware.NewMiddleware(dbs),
mw: middleware.NewMiddleware(dbs, ds),
a: plr,
apiPort: apiPort,
cProto: cProto,
Expand All @@ -50,25 +50,26 @@ func (api *HttpApi) run() {
cors := api.mw.Cors
h := api.handlers

router.HandleFunc("/api/sounds/{server_id}", cors(h.GetSounds)).Methods("GET")
router.Handle("/api/sounds/{guild_id}", cors(h.GetSounds())).Methods("GET")

router.HandleFunc("/api/server", cors(auth(h.GetServer))).Methods("GET").Headers("Cookie", "")
router.HandleFunc("/api/servers", cors(auth(h.GetServers))).Methods("GET").Headers("Cookie", "")
router.Handle("/api/guild", cors(auth(h.GetGuild()))).Methods("GET").Headers("Cookie", "")
router.Handle("/api/guilds", cors(auth(h.GetGuilds()))).Methods("GET").Headers("Cookie", "")

router.HandleFunc("/api/playsound/{user_id}/{server_id}/{sound_id}", cors(h.PostPlaySoundInsecure)).Methods("POST")
router.HandleFunc("/api/playsound/{sound_id}", cors(auth(h.PostPlaySound))).Methods("POST").Headers("Cookie", "")
router.Handle("/api/playsound/{user_id}/{guild_id}/{sound_id}", cors(h.PostPlaySoundInsecure())).Methods("POST")
router.Handle("/api/playsound/{sound_id}", cors(auth(h.PostPlaySound()))).Methods("POST").Headers("Cookie", "")

router.HandleFunc("/joinsession/{server_id}", cors(h.GetJoinSession)).Methods("GET")
router.HandleFunc("/link", cors(h.GetLinkDefer)).Methods("GET").Queries("t", "{t}")
router.HandleFunc("/api/link", cors(h.GetLinkRedirect)).Methods("GET").Queries("t", "{t}")
router.Handle("/joinsession/{guild_id}", cors(h.GetJoinSession())).Methods("GET")
router.Handle("/link", cors(h.GetLinkDefer())).Methods("GET").Queries("t", "{t}")
router.Handle("/api/link", cors(h.GetLinkRedirect())).Methods("GET").Queries("t", "{t}")

router.HandleFunc("/dump", cors(h.GetDump))
router.HandleFunc("/", cors(h.GetIndex)).Methods("GET")
router.Handle("/dump", cors(h.GetDump()))
router.Handle("/", cors(h.GetIndex())).Methods("GET")

router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./api/public/"))))

log.Printf("Webserver started on port %d (http://localhost:%d)\n", api.apiPort, api.apiPort)

// nolint:errcheck
go http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", api.apiPort), router)

close(api.ready)
Expand Down
7 changes: 5 additions & 2 deletions EsefexApi/api/middleware/auth.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package middleware

import (
"context"
"esefexapi/userdb"
"fmt"
"log"
"net/http"
)

// Auth middleware checks if the user is authenticated and injects the user into the request context
func (m *Middleware) Auth(next func(w http.ResponseWriter, r *http.Request, userID string)) func(w http.ResponseWriter, r *http.Request) {
func (m *Middleware) Auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user_token, err := r.Cookie("User-Token")
if err != nil {
Expand All @@ -28,6 +29,8 @@ func (m *Middleware) Auth(next func(w http.ResponseWriter, r *http.Request, user
return
}

next(w, r, Ouser.Unwrap().ID)
// Inject the user into the request context
ctx := context.WithValue(r.Context(), "user", Ouser)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
4 changes: 2 additions & 2 deletions EsefexApi/api/middleware/cors.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package middleware

import "net/http"

func (m *Middleware) Cors(next func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
func (m *Middleware) Cors(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")

next(w, r)
next.ServeHTTP(w, r)
})
}
10 changes: 8 additions & 2 deletions EsefexApi/api/middleware/middleware.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package middleware

import "esefexapi/db"
import (
"esefexapi/db"

"github.com/bwmarrin/discordgo"
)

type Middleware struct {
dbs *db.Databases
ds *discordgo.Session
}

func NewMiddleware(dbs *db.Databases) *Middleware {
func NewMiddleware(dbs *db.Databases, ds *discordgo.Session) *Middleware {
return &Middleware{
dbs: dbs,
ds: ds,
}
}
54 changes: 54 additions & 0 deletions EsefexApi/api/middleware/permission.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package middleware

import (
"esefexapi/permissions"
"esefexapi/types"
"esefexapi/userdb"
"esefexapi/util/dcgoutil"
"esefexapi/util/refl"
"net/http"
)

func (m *Middleware) Permission(next http.Handler, perms ...string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Get the user from the request context
user := r.Context().Value("user").(userdb.User)
userChan, err := dcgoutil.UserVCAny(m.ds, user.ID)
if err != nil {
errorMsg := "Error getting user channel: " + err.Error()
http.Error(w, errorMsg, http.StatusInternalServerError)
return
}

if userChan.IsNone() {
errorMsg := "User is not in a voice channel"
http.Error(w, errorMsg, http.StatusUnauthorized)
return
}

p, err := m.dbs.PermissionDB.Query(types.GuildID(userChan.Unwrap().ChannelID), user.ID)
if err != nil {
errorMsg := "Error querying permissions: " + err.Error()
http.Error(w, errorMsg, http.StatusInternalServerError)
return
}

// Check if the user has any of the required permissions
for _, perm := range perms {
ps, err := refl.GetNestedFieldValue(p, perm)
if err != nil {
errorMsg := "Error getting nested field value: " + err.Error()
http.Error(w, errorMsg, http.StatusInternalServerError)
return
}

if !ps.(permissions.PermissionState).Allowed() {
errorMsg := "User does not have the required permissions"
http.Error(w, errorMsg, http.StatusUnauthorized)
return
}
}

next.ServeHTTP(w, r)
})
}
45 changes: 44 additions & 1 deletion EsefexApi/api/public/simpleui/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,47 @@ body {
font-size: 14px;
line-height: 1.42857143;
color: #333;
}
}

button {
margin-top: 10px;
height: 2rem;
/* width: 10rem; */
font-size: 1rem;
color: #333;
/* background-color: #f5f5f5; */
border: 1px solid #929292;
border-radius: 3px;
margin: 0.25rem;
transition: 0.2s ease-in-out;
}

button:hover {
background-color: rgb(192, 192, 192);
}

button:active {
background-color: rgb(94, 94, 94);
}

.sfxButton {
display: flex;
flex-direction: row;
align-items: center;
}

#sounds {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}

.icon {
height: 1.5rem;
width: 1.5rem;
}

.label {
margin-left: 0.5rem;
}

4 changes: 3 additions & 1 deletion EsefexApi/api/public/simpleui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
<body>
<h1>Esefex Simple UI</h1>
<p>Simple UI for Esefex for Dev Purposes</p>
<button onclick="location.reload()">Reload</button>
<button onclick="location.reload()">
Reload
</button>
<h2>Sounds</h2>
<div id="sounds"></div>
</body>
Expand Down
23 changes: 17 additions & 6 deletions EsefexApi/api/public/simpleui/index.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,39 @@
async function init() {
const soundsDiv = document.getElementById('sounds');
const userTokenInput = document.getElementById('userTokenInput');

let serverRequest = await fetch('/api/server', {
let guildRequest = await fetch('/api/guild', {
method: 'GET',
credentials: 'same-origin',
});

if (serverRequest.status != 200) {
if (guildRequest.status != 200) {
let errorP = document.createElement('p');
errorP.innerText = 'Error: ' + serverRequest.status + ' ' + serverRequest.statusText + '\n' + await serverRequest.text();
errorP.innerText = 'Error: ' + guildRequest.status + ' ' + guildRequest.statusText + '\n' + await guildRequest.text();
soundsDiv.appendChild(errorP);
return;
}

let soundsRequest = await fetch(`/api/sounds/${await serverRequest.text()}`, {
let soundsRequest = await fetch(`/api/sounds/${await guildRequest.text()}`, {
method: 'GET',
credentials: 'same-origin',
});
let sounds = await soundsRequest.json();

sounds.forEach(sound => {
let soundButton = document.createElement('button');
soundButton.innerText = sound.name;
soundButton.classList.add('sfxButton');

let buttonImage = document.createElement('img');
buttonImage.src = sound.icon.url;
buttonImage.alt = sound.icon.name;
buttonImage.classList.add('icon');
soundButton.appendChild(buttonImage);

let buttonLabel = document.createElement('p');
buttonLabel.innerText = sound.name;
buttonLabel.classList.add('label');
soundButton.appendChild(buttonLabel);

soundButton.addEventListener('click', async () => {
await fetch(`/api/playsound/${sound.id}`, {
method: 'POST',
Expand Down
8 changes: 5 additions & 3 deletions EsefexApi/api/routes/getdump.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
)

// /dump
func (h *RouteHandlers) GetDump(w http.ResponseWriter, r *http.Request) {
spew.Fdump(w, r)
spew.Dump(r)
func (h *RouteHandlers) GetDump() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
spew.Fdump(w, r)
spew.Dump(r)
})
}
33 changes: 33 additions & 0 deletions EsefexApi/api/routes/getguild.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package routes

import (
"esefexapi/types"
"esefexapi/util/dcgoutil"
"log"
"net/http"
)

// api/guild
// returns the guild a user is connected to
func (h *RouteHandlers) GetGuild() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userID := r.Context().Value("user").(types.UserID)
Ovs, err := dcgoutil.UserVCAny(h.ds, userID)
if err != nil {
errorMsg := "Error getting user Voice State"
http.Error(w, errorMsg, http.StatusInternalServerError)
return
}
if Ovs.IsNone() {
http.Error(w, "User not connected to guild channel", http.StatusForbidden)
return
}

guildID := Ovs.Unwrap().GuildID
_, err = w.Write([]byte(guildID))
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
}
Loading
Loading