Skip to content

Commit

Permalink
Merge pull request #14 from EsefexBot/dev-api
Browse files Browse the repository at this point in the history
Permission System & Improved Commands
  • Loading branch information
jokil123 authored Jan 26, 2024
2 parents 70919d9 + 801128f commit 0da494f
Show file tree
Hide file tree
Showing 130 changed files with 4,717 additions and 981 deletions.
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

0 comments on commit 0da494f

Please sign in to comment.