style: code maid is here!
refactored the project significantly to reduce the single-file monolith
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
package globals
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
// environment variables
|
||||||
|
var (
|
||||||
|
HOST = os.Getenv("HOST")
|
||||||
|
PORT = os.Getenv("PORT")
|
||||||
|
|
||||||
|
REDIS_URI = os.Getenv("REDIS_URI")
|
||||||
|
|
||||||
|
ROOT_REDIRECT = os.Getenv("ROOT_REDIRECT")
|
||||||
|
|
||||||
|
DISCORD_CLIENT_ID = os.Getenv("DISCORD_CLIENT_ID")
|
||||||
|
DISCORD_CLIENT_SECRET = os.Getenv("DISCORD_CLIENT_SECRET")
|
||||||
|
DISCORD_REDIRECT_URI = os.Getenv("DISCORD_REDIRECT_URI")
|
||||||
|
|
||||||
|
PEPPER_SETTINGS = os.Getenv("PEPPER_SETTINGS")
|
||||||
|
PEPPER_SECRETS = os.Getenv("PEPPER_SECRETS")
|
||||||
|
|
||||||
|
SIZE_LIMIT int // initialised in main
|
||||||
|
|
||||||
|
ALLOWED_USERS map[string]bool // initialised in main
|
||||||
|
)
|
||||||
|
|
||||||
|
// other app globals, initialised in main
|
||||||
|
var (
|
||||||
|
// redis client
|
||||||
|
RDB *redis.Client
|
||||||
|
)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
module github.com/Vencord/Backend
|
module github.com/vencord/backend
|
||||||
|
|
||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
|
|||||||
@@ -18,60 +18,25 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ansrivas/fiberprometheus/v2"
|
"github.com/ansrivas/fiberprometheus/v2"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||||
reqHttp "github.com/imroc/req/v3"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
|
|
||||||
|
g "github.com/vencord/backend/globals"
|
||||||
|
"github.com/vencord/backend/routes"
|
||||||
|
"github.com/vencord/backend/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DiscordAccessTokenResult struct {
|
|
||||||
AccessToken string `json:"access_token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DiscordUserResult struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var ALLOWED_USERS map[string]bool
|
|
||||||
|
|
||||||
var rdb *redis.Client
|
var rdb *redis.Client
|
||||||
var (
|
|
||||||
accountsRegistered = promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
|
||||||
Name: "vencord_accounts_registered",
|
|
||||||
Help: "The total number of accounts registered",
|
|
||||||
}, func() float64 {
|
|
||||||
iter := rdb.Scan(context.Background(), 0, "secrets:*", 0).Iterator()
|
|
||||||
var count int64
|
|
||||||
|
|
||||||
for iter.Next(context.Background()) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := iter.Err(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return float64(count)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
func hash(s string) string {
|
|
||||||
return fmt.Sprintf("%x", sha1.Sum([]byte(s)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func requireAuth(c *fiber.Ctx) error {
|
func requireAuth(c *fiber.Ctx) error {
|
||||||
authToken := c.Get("Authorization")
|
authToken := c.Get("Authorization")
|
||||||
@@ -105,13 +70,13 @@ func requireAuth(c *fiber.Ctx) error {
|
|||||||
secret := tokenSplit[0]
|
secret := tokenSplit[0]
|
||||||
userId := tokenSplit[1]
|
userId := tokenSplit[1]
|
||||||
|
|
||||||
if ALLOWED_USERS != nil && c.Path() != "/v1" && c.Method() != "DELETE" && !ALLOWED_USERS[userId] {
|
if g.ALLOWED_USERS != nil && c.Path() != "/v1" && c.Method() != "DELETE" && !g.ALLOWED_USERS[userId] {
|
||||||
return c.Status(403).JSON(&fiber.Map{
|
return c.Status(403).JSON(&fiber.Map{
|
||||||
"error": "User is not whitelisted",
|
"error": "User is not whitelisted",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
storedSecret, err := rdb.Get(c.Context(), "secrets:"+hash(os.Getenv("PEPPER_SECRETS")+userId)).Result()
|
storedSecret, err := rdb.Get(c.Context(), "secrets:"+util.Hash(g.PEPPER_SECRETS+userId)).Result()
|
||||||
|
|
||||||
if err == redis.Nil {
|
if err == redis.Nil {
|
||||||
return c.Status(401).JSON(&fiber.Map{
|
return c.Status(401).JSON(&fiber.Map{
|
||||||
@@ -134,243 +99,75 @@ func requireAuth(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// environment
|
// environment
|
||||||
HOST := os.Getenv("HOST")
|
|
||||||
PORT := os.Getenv("PORT")
|
|
||||||
REDIS_URI := os.Getenv("REDIS_URI")
|
|
||||||
ROOT_REDIRECT := os.Getenv("ROOT_REDIRECT")
|
|
||||||
|
|
||||||
DISCORD_CLIENT_ID := os.Getenv("DISCORD_CLIENT_ID")
|
|
||||||
DISCORD_CLIENT_SECRET := os.Getenv("DISCORD_CLIENT_SECRET")
|
|
||||||
DISCORD_REDIRECT_URI := os.Getenv("DISCORD_REDIRECT_URI")
|
|
||||||
|
|
||||||
PEPPER_SECRETS := os.Getenv("PEPPER_SECRETS")
|
|
||||||
PEPPER_SETTINGS := os.Getenv("PEPPER_SETTINGS")
|
|
||||||
|
|
||||||
slRaw, _ := strconv.ParseInt(os.Getenv("SIZE_LIMIT"), 10, 0)
|
slRaw, _ := strconv.ParseInt(os.Getenv("SIZE_LIMIT"), 10, 0)
|
||||||
SIZE_LIMIT := int(slRaw)
|
g.SIZE_LIMIT = int(slRaw)
|
||||||
|
|
||||||
auRaw := os.Getenv("ALLOWED_USERS")
|
auRaw := os.Getenv("ALLOWED_USERS")
|
||||||
if auRaw != "" {
|
if auRaw != "" {
|
||||||
ALLOWED_USERS = make(map[string]bool)
|
g.ALLOWED_USERS = make(map[string]bool)
|
||||||
for _, userId := range strings.Split(auRaw, ",") {
|
for _, userId := range strings.Split(auRaw, ",") {
|
||||||
ALLOWED_USERS[userId] = true
|
g.ALLOWED_USERS[userId] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
rdb = redis.NewClient(&redis.Options{
|
rdb = redis.NewClient(&redis.Options{
|
||||||
Addr: REDIS_URI,
|
Addr: g.REDIS_URI,
|
||||||
})
|
})
|
||||||
req := reqHttp.C()
|
|
||||||
|
|
||||||
if os.Getenv("PROMETHEUS") == "true" {
|
if os.Getenv("PROMETHEUS") == "true" {
|
||||||
prometheus := fiberprometheus.New("vencord")
|
promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||||
prometheus.RegisterAt(app, "/metrics")
|
Name: "vencord_accounts_registered",
|
||||||
app.Use(prometheus.Middleware)
|
Help: "The total number of accounts registered",
|
||||||
}
|
}, func() float64 {
|
||||||
|
iter := rdb.Scan(context.Background(), 0, "secrets:*", 0).Iterator()
|
||||||
|
var count int64
|
||||||
|
|
||||||
|
for iter.Next(context.Background()) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := iter.Err(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return float64(count)
|
||||||
|
})
|
||||||
|
|
||||||
|
prometheus := fiberprometheus.New("vencord")
|
||||||
|
prometheus.RegisterAt(app, "/metrics")
|
||||||
|
app.Use(prometheus.Middleware)
|
||||||
|
}
|
||||||
|
|
||||||
app.Use(cors.New(cors.Config{
|
app.Use(cors.New(cors.Config{
|
||||||
ExposeHeaders: "ETag",
|
ExposeHeaders: "ETag",
|
||||||
AllowOrigins: "https://discord.com,https://ptb.discord.com,https://canary.discord.com",
|
AllowOrigins: "https://discord.com,https://ptb.discord.com,https://canary.discord.com",
|
||||||
}))
|
}))
|
||||||
app.Use(logger.New())
|
app.Use(logger.New())
|
||||||
|
|
||||||
// #region settings
|
// #region settings
|
||||||
app.All("/v1/settings", requireAuth)
|
app.All("/v1/settings", requireAuth)
|
||||||
|
|
||||||
app.Head("/v1/settings", func(c *fiber.Ctx) error {
|
app.Head("/v1/settings", routes.HEADSettings)
|
||||||
userId := c.Context().UserValue("userId").(string)
|
app.Get("/v1/settings", routes.GETSettings)
|
||||||
|
app.Put("/v1/settings", routes.PUTSettings)
|
||||||
written, err := rdb.HGet(c.Context(), "settings:"+hash(PEPPER_SETTINGS+userId), "written").Result()
|
app.Delete("/v1/settings", routes.DELETESettings)
|
||||||
|
|
||||||
if err == redis.Nil {
|
|
||||||
return c.Status(404).Send(nil)
|
|
||||||
} else if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Set("ETag", written)
|
|
||||||
return c.SendStatus(204)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.Get("/v1/settings", func(c *fiber.Ctx) error {
|
|
||||||
userId := c.Context().UserValue("userId").(string)
|
|
||||||
|
|
||||||
settings, err := rdb.HMGet(c.Context(), "settings:"+hash(PEPPER_SETTINGS+userId), "value", "written").Result()
|
|
||||||
|
|
||||||
// we shouldn't expect an error here, HMGet doesn't return one
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings[0] == nil {
|
|
||||||
return c.Status(404).Send(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// value is compressed data, written is a timestamp
|
|
||||||
value, written := []byte(settings[0].(string)), settings[1].(string)
|
|
||||||
|
|
||||||
if ifm := c.Get("if-none-match"); ifm == written {
|
|
||||||
return c.SendStatus(304)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Set("Content-Type", "application/octet-stream")
|
|
||||||
c.Set("ETag", written)
|
|
||||||
return c.Send(value)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.Put("/v1/settings", func(c *fiber.Ctx) error {
|
|
||||||
if c.Get("Content-Type") != "application/octet-stream" {
|
|
||||||
return c.Status(415).JSON(&fiber.Map{
|
|
||||||
"error": "Content type must be application/octet-stream",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.Body()) > SIZE_LIMIT {
|
|
||||||
return c.Status(413).JSON(&fiber.Map{
|
|
||||||
"error": "Settings are too large",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
userId := c.Context().UserValue("userId").(string)
|
|
||||||
|
|
||||||
now := time.Now().UnixMilli()
|
|
||||||
|
|
||||||
_, err := rdb.HSet(c.Context(), "settings:"+hash(PEPPER_SETTINGS+userId), map[string]interface{}{
|
|
||||||
"value": c.Body(),
|
|
||||||
"written": now,
|
|
||||||
}).Result()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(&fiber.Map{
|
|
||||||
"written": now,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
app.Delete("/v1/settings", func(c *fiber.Ctx) error {
|
|
||||||
userId := c.Context().UserValue("userId").(string)
|
|
||||||
|
|
||||||
rdb.Del(c.Context(), "settings:"+hash(PEPPER_SETTINGS+userId))
|
|
||||||
|
|
||||||
return c.SendStatus(204)
|
|
||||||
})
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region discord oauth
|
// #region discord oauth
|
||||||
app.Get("/v1/oauth/callback", func(c *fiber.Ctx) error {
|
app.Get("/v1/oauth/callback", routes.GETOAuthCallback)
|
||||||
code := c.Query("code")
|
app.Get("/v1/oauth/settings", routes.GETOAuthSettings)
|
||||||
|
|
||||||
if code == "" {
|
|
||||||
return c.Status(400).JSON(&fiber.Map{
|
|
||||||
"error": "Missing code",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var accessTokenResult DiscordAccessTokenResult
|
|
||||||
|
|
||||||
res, err := req.R().SetFormData(map[string]string{
|
|
||||||
"client_id": DISCORD_CLIENT_ID,
|
|
||||||
"client_secret": DISCORD_CLIENT_SECRET,
|
|
||||||
"grant_type": "authorization_code",
|
|
||||||
"code": code,
|
|
||||||
"redirect_uri": DISCORD_REDIRECT_URI,
|
|
||||||
"scope": "identify",
|
|
||||||
}).SetSuccessResult(&accessTokenResult).Post("https://discord.com/api/oauth2/token")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(500).JSON(&fiber.Map{
|
|
||||||
"error": "Failed to request access token",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if res.IsErrorState() {
|
|
||||||
return c.Status(400).JSON(&fiber.Map{
|
|
||||||
"error": "Invalid code",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
accessToken := accessTokenResult.AccessToken
|
|
||||||
|
|
||||||
var userResult DiscordUserResult
|
|
||||||
|
|
||||||
res, err = req.R().SetHeaders(map[string]string{
|
|
||||||
"Authorization": "Bearer " + accessToken,
|
|
||||||
}).SetSuccessResult(&userResult).Get("https://discord.com/api/users/@me")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(500).JSON(&fiber.Map{
|
|
||||||
"error": "Failed to request user",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if res.IsErrorState() {
|
|
||||||
return c.Status(500).JSON(&fiber.Map{
|
|
||||||
"error": "Failed to request user",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
userId := userResult.Id
|
|
||||||
|
|
||||||
if ALLOWED_USERS != nil && !ALLOWED_USERS[userId] {
|
|
||||||
return c.Status(403).JSON(&fiber.Map{
|
|
||||||
"error": "User is not whitelisted",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
secret, err := rdb.Get(c.Context(), "secrets:"+hash(PEPPER_SECRETS+userId)).Result()
|
|
||||||
|
|
||||||
if err == redis.Nil {
|
|
||||||
key := make([]byte, 48)
|
|
||||||
|
|
||||||
_, err := rand.Read(key)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(500).JSON(&fiber.Map{
|
|
||||||
"error": "Failed to generate secret",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
secret = hex.EncodeToString(key)
|
|
||||||
rdb.Set(c.Context(), "secrets:"+hash(PEPPER_SECRETS+userId), secret, 0)
|
|
||||||
} else if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(&fiber.Map{
|
|
||||||
"secret": secret,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
app.Get("/v1/oauth/settings", func(c *fiber.Ctx) error {
|
|
||||||
return c.JSON(&fiber.Map{
|
|
||||||
"clientId": DISCORD_CLIENT_ID,
|
|
||||||
"redirectUri": DISCORD_REDIRECT_URI,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region erase all
|
// #region erase all
|
||||||
app.Delete("/v1", requireAuth, func(c *fiber.Ctx) error {
|
app.Delete("/v1", requireAuth, routes.DELETE)
|
||||||
userId := c.Context().UserValue("userId").(string)
|
|
||||||
|
|
||||||
rdb.Del(c.Context(), "settings:"+hash(PEPPER_SETTINGS+userId))
|
|
||||||
rdb.Del(c.Context(), "secrets:"+hash(PEPPER_SECRETS+userId))
|
|
||||||
|
|
||||||
return c.SendStatus(204)
|
|
||||||
})
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
app.Get("/v1", func(c *fiber.Ctx) error {
|
app.Get("/v1", routes.GET)
|
||||||
return c.JSON(&fiber.Map{
|
|
||||||
"ping": "pong",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
app.Get("/", func(c *fiber.Ctx) error {
|
app.Get("/", func(c *fiber.Ctx) error {
|
||||||
return c.Redirect(ROOT_REDIRECT, 303)
|
return c.Redirect(g.ROOT_REDIRECT, 303)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.Listen(HOST + ":" + PORT)
|
app.Listen(g.HOST + ":" + g.PORT)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/imroc/req/v3"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
|
||||||
|
g "github.com/vencord/backend/globals"
|
||||||
|
"github.com/vencord/backend/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// /v1/oauth
|
||||||
|
|
||||||
|
type DiscordAccessTokenResult struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiscordUserResult struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// /v1/oauth/callback
|
||||||
|
func GETOAuthCallback(c *fiber.Ctx) error {
|
||||||
|
code := c.Query("code")
|
||||||
|
|
||||||
|
if code == "" {
|
||||||
|
return c.Status(400).JSON(&fiber.Map{
|
||||||
|
"error": "Missing code",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessTokenResult DiscordAccessTokenResult
|
||||||
|
|
||||||
|
res, err := req.R().SetFormData(map[string]string{
|
||||||
|
"client_id": g.DISCORD_CLIENT_ID,
|
||||||
|
"client_secret": g.DISCORD_CLIENT_SECRET,
|
||||||
|
"grant_type": "authorization_code",
|
||||||
|
"code": code,
|
||||||
|
"redirect_uri": g.DISCORD_REDIRECT_URI,
|
||||||
|
"scope": "identify",
|
||||||
|
}).SetSuccessResult(&accessTokenResult).Post("https://discord.com/api/oauth2/token")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(500).JSON(&fiber.Map{
|
||||||
|
"error": "Failed to request access token",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.IsErrorState() {
|
||||||
|
return c.Status(400).JSON(&fiber.Map{
|
||||||
|
"error": "Invalid code",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
accessToken := accessTokenResult.AccessToken
|
||||||
|
|
||||||
|
var userResult DiscordUserResult
|
||||||
|
|
||||||
|
res, err = req.R().SetHeaders(map[string]string{
|
||||||
|
"Authorization": "Bearer " + accessToken,
|
||||||
|
}).SetSuccessResult(&userResult).Get("https://discord.com/api/users/@me")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(500).JSON(&fiber.Map{
|
||||||
|
"error": "Failed to request user",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.IsErrorState() {
|
||||||
|
return c.Status(500).JSON(&fiber.Map{
|
||||||
|
"error": "Failed to request user",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
userId := userResult.Id
|
||||||
|
|
||||||
|
if g.ALLOWED_USERS != nil && !g.ALLOWED_USERS[userId] {
|
||||||
|
return c.Status(403).JSON(&fiber.Map{
|
||||||
|
"error": "User is not whitelisted",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
secret, err := g.RDB.Get(c.Context(), "secrets:"+util.Hash(g.PEPPER_SECRETS+userId)).Result()
|
||||||
|
|
||||||
|
if err == redis.Nil {
|
||||||
|
key := make([]byte, 48)
|
||||||
|
|
||||||
|
_, err := rand.Read(key)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(500).JSON(&fiber.Map{
|
||||||
|
"error": "Failed to generate secret",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
secret = hex.EncodeToString(key)
|
||||||
|
g.RDB.Set(c.Context(), "secrets:"+util.Hash(g.PEPPER_SECRETS+userId), secret, 0)
|
||||||
|
} else if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(&fiber.Map{
|
||||||
|
"secret": secret,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// /v1/oauth/settings
|
||||||
|
func GETOAuthSettings(c *fiber.Ctx) error {
|
||||||
|
return c.JSON(&fiber.Map{
|
||||||
|
"clientId": g.DISCORD_CLIENT_ID,
|
||||||
|
"redirectUri": g.DISCORD_REDIRECT_URI,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
|
g "github.com/vencord/backend/globals"
|
||||||
|
"github.com/vencord/backend/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// /v1
|
||||||
|
|
||||||
|
func DELETE(c *fiber.Ctx) error {
|
||||||
|
userId := c.Context().UserValue("userId").(string)
|
||||||
|
|
||||||
|
g.RDB.Del(c.Context(), "settings:"+util.Hash(g.PEPPER_SETTINGS+userId))
|
||||||
|
g.RDB.Del(c.Context(), "secrets:"+util.Hash(g.PEPPER_SECRETS+userId))
|
||||||
|
|
||||||
|
return c.SendStatus(204)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GET(c *fiber.Ctx) error {
|
||||||
|
return c.JSON(&fiber.Map{
|
||||||
|
"ping": "pong",
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
|
||||||
|
g "github.com/vencord/backend/globals"
|
||||||
|
"github.com/vencord/backend/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// /v1/settings
|
||||||
|
|
||||||
|
func HEADSettings(c *fiber.Ctx) error {
|
||||||
|
userId := c.Context().UserValue("userId").(string)
|
||||||
|
|
||||||
|
written, err := g.RDB.HGet(c.Context(), "settings:"+util.Hash(g.PEPPER_SETTINGS+userId), "written").Result()
|
||||||
|
|
||||||
|
if err == redis.Nil {
|
||||||
|
return c.Status(404).Send(nil)
|
||||||
|
} else if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("ETag", written)
|
||||||
|
return c.SendStatus(204)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GETSettings(c *fiber.Ctx) error {
|
||||||
|
userId := c.Context().UserValue("userId").(string)
|
||||||
|
|
||||||
|
settings, err := g.RDB.HMGet(c.Context(), "settings:"+util.Hash(g.PEPPER_SETTINGS+userId), "value", "written").Result()
|
||||||
|
|
||||||
|
// we shouldn't expect an error here, HMGet doesn't return one
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings[0] == nil {
|
||||||
|
return c.Status(404).Send(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// value is compressed data, written is a timestamp
|
||||||
|
value, written := []byte(settings[0].(string)), settings[1].(string)
|
||||||
|
|
||||||
|
if ifm := c.Get("if-none-match"); ifm == written {
|
||||||
|
return c.SendStatus(304)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("Content-Type", "application/octet-stream")
|
||||||
|
c.Set("ETag", written)
|
||||||
|
return c.Send(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PUTSettings(c *fiber.Ctx) error {
|
||||||
|
if c.Get("Content-Type") != "application/octet-stream" {
|
||||||
|
return c.Status(415).JSON(&fiber.Map{
|
||||||
|
"error": "Content type must be application/octet-stream",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.Body()) > g.SIZE_LIMIT {
|
||||||
|
return c.Status(413).JSON(&fiber.Map{
|
||||||
|
"error": "Settings are too large",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
userId := c.Context().UserValue("userId").(string)
|
||||||
|
|
||||||
|
now := time.Now().UnixMilli()
|
||||||
|
|
||||||
|
_, err := g.RDB.HSet(c.Context(), "settings:"+util.Hash(g.PEPPER_SETTINGS+userId), map[string]interface{}{
|
||||||
|
"value": c.Body(),
|
||||||
|
"written": now,
|
||||||
|
}).Result()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(&fiber.Map{
|
||||||
|
"written": now,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func DELETESettings(c *fiber.Ctx) error {
|
||||||
|
userId := c.Context().UserValue("userId").(string)
|
||||||
|
|
||||||
|
g.RDB.Del(c.Context(), "settings:"+util.Hash(g.PEPPER_SETTINGS+userId))
|
||||||
|
|
||||||
|
return c.SendStatus(204)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Hash(s string) string {
|
||||||
|
return fmt.Sprintf("%x", sha1.Sum([]byte(s)))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user