mirror of
https://github.com/idanoo/GoScrobble
synced 2025-07-01 13:42:20 +00:00
0.0.8
- Added Admin/Site config page in frontend for admin users - Added API POST/GET /config endpoint
This commit is contained in:
parent
8be4a190a6
commit
af02bd99cc
17 changed files with 826 additions and 24 deletions
57
internal/goscrobble/config.go
Normal file
57
internal/goscrobble/config.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package goscrobble
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Setting map[string]string `json:"configs"`
|
||||
}
|
||||
|
||||
func getAllConfigs() (Config, error) {
|
||||
config := Config{}
|
||||
configs := make(map[string]string)
|
||||
|
||||
rows, err := db.Query("SELECT `key`, `value` FROM `config`")
|
||||
if err != nil {
|
||||
log.Printf("Failed to fetch config: %+v", err)
|
||||
return config, errors.New("Failed to fetch configs")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var key string
|
||||
var value string
|
||||
err := rows.Scan(&key, &value)
|
||||
if err != nil {
|
||||
log.Printf("Failed to fetch config: %+v", err)
|
||||
return config, errors.New("Failed to fetch configs")
|
||||
}
|
||||
|
||||
// Append
|
||||
configs[key] = value
|
||||
}
|
||||
|
||||
// Assign the data to the parent
|
||||
config.Setting = configs
|
||||
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
log.Printf("Failed to fetch config: %+v", err)
|
||||
return config, errors.New("Failed to fetch configs")
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func updateConfigValue(key string, value string) error {
|
||||
_, err := db.Exec("UPDATE `config` SET `value` = ? WHERE `key` = ?", value, key)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to update config: %+v", err)
|
||||
return errors.New("Failed to update config value.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -15,6 +15,7 @@ var JwtExpiry time.Duration
|
|||
type CustomClaims struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Admin bool `json:"admin"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
|
@ -23,6 +24,7 @@ func generateJWTToken(user User) (string, error) {
|
|||
atClaims["sub"] = user.UUID
|
||||
atClaims["username"] = user.Username
|
||||
atClaims["email"] = user.Email
|
||||
atClaims["admin"] = user.Admin
|
||||
atClaims["iat"] = time.Now().Unix()
|
||||
atClaims["exp"] = time.Now().Add(JwtExpiry).Unix()
|
||||
at := jwt.NewWithClaims(jwt.SigningMethodHS512, atClaims)
|
||||
|
|
|
@ -51,6 +51,10 @@ func HandleRequests(port string) {
|
|||
// JWT Auth
|
||||
v1.HandleFunc("/user/{id}/scrobbles", jwtMiddleware(fetchScrobbleResponse)).Methods("GET")
|
||||
|
||||
// Config auth
|
||||
v1.HandleFunc("/config", adminMiddleware(fetchConfig)).Methods("GET")
|
||||
v1.HandleFunc("/config", adminMiddleware(postConfig)).Methods("POST")
|
||||
|
||||
// No Auth
|
||||
v1.HandleFunc("/register", limitMiddleware(handleRegister, heavyLimiter)).Methods("POST")
|
||||
v1.HandleFunc("/login", limitMiddleware(handleLogin, standardLimiter)).Methods("POST")
|
||||
|
@ -117,6 +121,16 @@ func throwOkMessage(w http.ResponseWriter, m string) {
|
|||
w.Write(js)
|
||||
}
|
||||
|
||||
// throwOkMessage - Throws a happy 200
|
||||
func throwInvalidJson(w http.ResponseWriter) {
|
||||
jr := jsonResponse{
|
||||
Err: "Invalid JSON",
|
||||
}
|
||||
js, _ := json.Marshal(&jr)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write(js)
|
||||
}
|
||||
|
||||
// generateJsonMessage - Generates a message:str response
|
||||
func generateJsonMessage(m string) []byte {
|
||||
jr := jsonResponse{
|
||||
|
@ -142,9 +156,10 @@ func tokenMiddleware(next func(http.ResponseWriter, *http.Request, string)) http
|
|||
authToken := strings.Replace(fullToken, "Bearer ", "", 1)
|
||||
if authToken == "" {
|
||||
throwUnauthorized(w, "A token is required")
|
||||
return
|
||||
}
|
||||
|
||||
userUuid, err := getUserForToken(authToken)
|
||||
userUuid, err := getUserUuidForToken(authToken)
|
||||
if err != nil {
|
||||
throwUnauthorized(w, err.Error())
|
||||
return
|
||||
|
@ -180,6 +195,32 @@ func jwtMiddleware(next func(http.ResponseWriter, *http.Request, string, string)
|
|||
}
|
||||
}
|
||||
|
||||
// adminMiddleware - Validates user is admin
|
||||
func adminMiddleware(next func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
fullToken := r.Header.Get("Authorization")
|
||||
authToken := strings.Replace(fullToken, "Bearer ", "", 1)
|
||||
claims, err := verifyJWTToken(authToken)
|
||||
if err != nil {
|
||||
throwUnauthorized(w, "Invalid JWT Token")
|
||||
return
|
||||
}
|
||||
|
||||
user, err := getUser(claims.Subject)
|
||||
if err != nil {
|
||||
throwUnauthorized(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !user.Admin {
|
||||
throwUnauthorized(w, "User is not admin")
|
||||
return
|
||||
}
|
||||
|
||||
next(w, r, claims.Subject)
|
||||
}
|
||||
}
|
||||
|
||||
// limitMiddleware - Rate limits important stuff
|
||||
func limitMiddleware(next http.HandlerFunc, limiter *IPRateLimiter) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -255,8 +296,7 @@ func handleStats(w http.ResponseWriter, r *http.Request) {
|
|||
func handleIngress(w http.ResponseWriter, r *http.Request, userUuid string) {
|
||||
bodyJson, err := decodeJson(r.Body)
|
||||
if err != nil {
|
||||
// If we can't decode. Lets tell them nicely.
|
||||
http.Error(w, "{\"error\":\"Invalid JSON\"}", http.StatusBadRequest)
|
||||
throwInvalidJson(w)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -302,6 +342,38 @@ func fetchScrobbleResponse(w http.ResponseWriter, r *http.Request, jwtUser strin
|
|||
w.Write(json)
|
||||
}
|
||||
|
||||
// fetchScrobbles - Return an array of scrobbles
|
||||
func fetchConfig(w http.ResponseWriter, r *http.Request, jwtUser string) {
|
||||
config, err := getAllConfigs()
|
||||
if err != nil {
|
||||
throwOkError(w, "Failed to fetch scrobbles")
|
||||
return
|
||||
}
|
||||
|
||||
json, _ := json.Marshal(&config)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
// fetchScrobbles - Return an array of scrobbles
|
||||
func postConfig(w http.ResponseWriter, r *http.Request, jwtUser string) {
|
||||
bodyJson, err := decodeJson(r.Body)
|
||||
if err != nil {
|
||||
throwInvalidJson(w)
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range bodyJson {
|
||||
err = updateConfigValue(k, fmt.Sprintf("%s", v))
|
||||
if err != nil {
|
||||
throwOkError(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
throwOkMessage(w, "Config updated successfully")
|
||||
}
|
||||
|
||||
// FRONTEND HANDLING
|
||||
|
||||
// ServerHTTP - Frontend server
|
||||
|
|
|
@ -15,7 +15,7 @@ func generateToken(n int) string {
|
|||
return string(b)
|
||||
}
|
||||
|
||||
func getUserForToken(token string) (string, error) {
|
||||
func getUserUuidForToken(token string) (string, error) {
|
||||
var uuid string
|
||||
cachedKey := getRedisVal("user_token:" + token)
|
||||
if cachedKey == "" {
|
||||
|
|
|
@ -98,16 +98,16 @@ func loginUser(logReq *LoginRequest, ip net.IP) ([]byte, error) {
|
|||
}
|
||||
|
||||
if strings.Contains(logReq.Username, "@") {
|
||||
err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `username`, `email`, `password` FROM `users` WHERE `email` = ? AND `active` = 1",
|
||||
logReq.Username).Scan(&user.UUID, &user.Username, &user.Email, &user.Password)
|
||||
err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `username`, `email`, `password`, `admin` FROM `users` WHERE `email` = ? AND `active` = 1",
|
||||
logReq.Username).Scan(&user.UUID, &user.Username, &user.Email, &user.Password, &user.Admin)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return resp, errors.New("Invalid Username or Password")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `username`, `email`, `password` FROM `users` WHERE `username` = ? AND `active` = 1",
|
||||
logReq.Username).Scan(&user.UUID, &user.Username, &user.Email, &user.Password)
|
||||
err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `username`, `email`, `password`, `admin` FROM `users` WHERE `username` = ? AND `active` = 1",
|
||||
logReq.Username).Scan(&user.UUID, &user.Username, &user.Email, &user.Password, &user.Admin)
|
||||
if err == sql.ErrNoRows {
|
||||
return resp, errors.New("Invalid Username or Password")
|
||||
}
|
||||
|
@ -193,3 +193,15 @@ func userAlreadyExists(req *RegisterRequest) bool {
|
|||
|
||||
return count > 0
|
||||
}
|
||||
|
||||
func getUser(uuid string) (User, error) {
|
||||
var user User
|
||||
err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `created_at`, `created_ip`, `modified_at`, `modified_ip`, `username`, `email`, `password`, `verified`, `admin` FROM `users` WHERE `uuid` = UUID_TO_BIN(?, true) AND `active` = 1",
|
||||
uuid).Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return user, errors.New("Invalid JWT Token")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue