add full data erasure + reformat the entire thing cause vsc sucks

This commit is contained in:
Lewis Crichton
2023-04-03 19:04:09 +01:00
parent 3e02889c64
commit 481b34cb3b
+219 -204
View File
@@ -19,267 +19,282 @@ import (
) )
type DiscordAccessTokenResult struct { type DiscordAccessTokenResult struct {
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
} }
type DiscordUserResult struct { type DiscordUserResult struct {
Id string `json:"id"` Id string `json:"id"`
} }
var rdb *redis.Client
func hash(s string) string { func hash(s string) string {
return fmt.Sprintf("%x", sha1.Sum([]byte(s))) return fmt.Sprintf("%x", sha1.Sum([]byte(s)))
}
func requireAuth(c *fiber.Ctx) error {
authToken := c.Get("Authorization")
if authToken == "" {
return c.Status(401).JSON(&fiber.Map{
"error": "Missing authorization",
})
}
// decode base64 token and split by :
// token[0] = username
// token[1] = password
token, err := base64.StdEncoding.DecodeString(authToken)
if err != nil {
return c.Status(401).JSON(&fiber.Map{
"error": "Invalid authorization",
})
}
tokenStr := string(token)
tokenSplit := strings.Split(tokenStr, ":")
if len(tokenSplit) != 2 {
return c.Status(401).JSON(&fiber.Map{
"error": "Invalid authorization",
})
}
userId := tokenSplit[0]
secret := tokenSplit[1]
storedSecret, err := rdb.Get(c.Context(), "secrets:"+hash(os.Getenv("PEPPER_SECRETS")+userId)).Result()
if err == redis.Nil {
return c.Status(401).JSON(&fiber.Map{
"error": "Invalid authorization",
})
} else if err != nil {
panic(err)
}
if storedSecret != secret {
return c.Status(401).JSON(&fiber.Map{
"error": "Invalid authorization",
})
}
c.Context().SetUserValue("userId", userId)
return c.Next()
} }
func main() { func main() {
// environment // environment
HOST := os.Getenv("HOST") HOST := os.Getenv("HOST")
PORT := os.Getenv("PORT") PORT := os.Getenv("PORT")
REDIS_URI := os.Getenv("REDIS_URI") REDIS_URI := os.Getenv("REDIS_URI")
ROOT_REDIRECT := os.Getenv("ROOT_REDIRECT") ROOT_REDIRECT := os.Getenv("ROOT_REDIRECT")
DISCORD_CLIENT_ID := os.Getenv("DISCORD_CLIENT_ID") DISCORD_CLIENT_ID := os.Getenv("DISCORD_CLIENT_ID")
DISCORD_CLIENT_SECRET := os.Getenv("DISCORD_CLIENT_SECRET") DISCORD_CLIENT_SECRET := os.Getenv("DISCORD_CLIENT_SECRET")
DISCORD_REDIRECT_URI := os.Getenv("DISCORD_REDIRECT_URI") DISCORD_REDIRECT_URI := os.Getenv("DISCORD_REDIRECT_URI")
PEPPER_SECRETS := os.Getenv("PEPPER_SECRETS") PEPPER_SECRETS := os.Getenv("PEPPER_SECRETS")
PEPPER_SETTINGS := os.Getenv("PEPPER_SETTINGS") 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) SIZE_LIMIT := int(slRaw)
app := fiber.New() app := fiber.New()
rdb := redis.NewClient(&redis.Options{ rdb = redis.NewClient(&redis.Options{
Addr: REDIS_URI, Addr: REDIS_URI,
}) })
req := reqHttp.C() req := reqHttp.C()
app.Use(cors.New(cors.Config{ app.Use(cors.New(cors.Config{
ExposeHeaders: "ETag", ExposeHeaders: "ETag",
})) }))
app.Use(logger.New()) app.Use(logger.New())
// #region settings // #region settings
app.All("/settings", func(c *fiber.Ctx) error { app.All("/settings", requireAuth)
authToken := c.Get("Authorization")
if authToken == "" { app.Head("/settings", func(c *fiber.Ctx) error {
return c.Status(401).JSON(&fiber.Map{ userId := c.Context().UserValue("userId").(string)
"error": "Missing authorization",
})
}
// decode base64 token and split by : written, err := rdb.HGet(c.Context(), "settings:"+hash(PEPPER_SETTINGS+userId), "written").Result()
// token[0] = username
// token[1] = password
token, err := base64.StdEncoding.DecodeString(authToken)
if err != nil { if err == redis.Nil {
return c.Status(401).JSON(&fiber.Map{ return c.Status(404).Send(nil)
"error": "Invalid authorization", } else if err != nil {
}) panic(err)
} }
tokenStr := string(token) c.Set("ETag", written)
tokenSplit := strings.Split(tokenStr, ":") return c.SendStatus(204)
})
if len(tokenSplit) != 2 { app.Get("/settings", func(c *fiber.Ctx) error {
return c.Status(401).JSON(&fiber.Map{ userId := c.Context().UserValue("userId").(string)
"error": "Invalid authorization",
})
}
userId := tokenSplit[0] settings, err := rdb.HMGet(c.Context(), "settings:"+hash(PEPPER_SETTINGS+userId), "value", "written").Result()
secret := tokenSplit[1]
storedSecret, err := rdb.Get(c.Context(), "secrets:" + hash(PEPPER_SECRETS + userId)).Result() // we shouldn't expect an error here, HMGet doesn't return one
if err != nil {
panic(err)
}
if err == redis.Nil { if settings[0] == nil {
return c.Status(401).JSON(&fiber.Map{ return c.Status(404).Send(nil)
"error": "Invalid authorization", }
})
} else if err != nil {
panic(err)
}
if storedSecret != secret { // value is compressed data, written is a timestamp
return c.Status(401).JSON(&fiber.Map{ value, written := []byte(settings[0].(string)), settings[1].(string)
"error": "Invalid authorization",
})
}
c.Context().SetUserValue("userId", userId) if ifm := c.Get("if-none-match"); ifm == written {
return c.SendStatus(304)
}
return c.Next() c.Set("Content-Type", "application/octet-stream")
}) c.Set("ETag", written)
return c.Send(value)
})
app.Head("/settings", func(c *fiber.Ctx) error { app.Put("/settings", func(c *fiber.Ctx) error {
userId := c.Context().UserValue("userId").(string) if c.Get("Content-Type") != "application/octet-stream" {
return c.Status(415).JSON(&fiber.Map{
"error": "Content type must be application/octet-stream",
})
}
written, err := rdb.HGet(c.Context(), "settings:" + hash(PEPPER_SETTINGS + userId), "written").Result() if len(c.Body()) > SIZE_LIMIT {
return c.Status(413).JSON(&fiber.Map{
"error": "Settings are too large",
})
}
if err == redis.Nil { userId := c.Context().UserValue("userId").(string)
return c.Status(404).Send(nil)
} else if err != nil {
panic(err)
}
c.Set("ETag", written) now := time.Now().UnixMilli()
return c.SendStatus(204)
})
app.Get("/settings", func(c *fiber.Ctx) error { _, err := rdb.HSet(c.Context(), "settings:"+hash(PEPPER_SETTINGS+userId), map[string]interface{}{
userId := c.Context().UserValue("userId").(string) "value": c.Body(),
"written": now,
}).Result()
settings, err := rdb.HMGet(c.Context(), "settings:" + hash(PEPPER_SETTINGS + userId), "value", "written").Result() if err != nil {
panic(err)
}
// we shouldn't expect an error here, HMGet doesn't return one return c.JSON(&fiber.Map{
if err != nil { "written": now,
panic(err) })
} })
if settings[0] == nil { app.Delete("/settings", func(c *fiber.Ctx) error {
return c.Status(404).Send(nil) userId := c.Context().UserValue("userId").(string)
}
// value is compressed data, written is a timestamp rdb.Del(c.Context(), "settings:"+hash(PEPPER_SETTINGS+userId))
value, written := []byte(settings[0].(string)), settings[1].(string)
if ifm := c.Get("if-none-match"); ifm == written { return c.SendStatus(204)
return c.SendStatus(304) })
} // #endregion
c.Set("Content-Type", "application/octet-stream") // #region discord oauth
c.Set("ETag", written) app.Get("/oauth/callback", func(c *fiber.Ctx) error {
return c.Send(value) code := c.Query("code")
})
app.Put("/settings", func(c *fiber.Ctx) error { if code == "" {
if (c.Get("Content-Type") != "application/octet-stream") { return c.Status(400).JSON(&fiber.Map{
return c.Status(415).JSON(&fiber.Map{ "error": "Missing code",
"error": "Content type must be application/octet-stream", })
}) }
}
if (len(c.Body()) > SIZE_LIMIT) { var accessTokenResult DiscordAccessTokenResult
return c.Status(413).JSON(&fiber.Map{
"error": "Settings are too large",
})
}
userId := c.Context().UserValue("userId").(string) 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")
now := time.Now().UnixMilli() if err != nil {
return c.Status(500).JSON(&fiber.Map{
"error": "Failed to request access token",
})
}
_, err := rdb.HSet(c.Context(), "settings:" + hash(PEPPER_SETTINGS + userId), map[string]interface{}{ if res.IsErrorState() {
"value": c.Body(), return c.Status(400).JSON(&fiber.Map{
"written": now, "error": "Invalid code",
}).Result() })
}
if err != nil { accessToken := accessTokenResult.AccessToken
panic(err)
}
return c.JSON(&fiber.Map{ var userResult DiscordUserResult
"written": now,
})
})
app.Delete("/settings", func(c *fiber.Ctx) error { res, err = req.R().SetHeaders(map[string]string{
userId := c.Context().UserValue("userId").(string) "Authorization": "Bearer " + accessToken,
}).SetSuccessResult(&userResult).Get("https://discord.com/api/users/@me")
rdb.Del(c.Context(), "settings:" + hash(PEPPER_SETTINGS + userId)) if err != nil {
return c.Status(500).JSON(&fiber.Map{
"error": "Failed to request user",
})
}
return c.SendStatus(204) if res.IsErrorState() {
}) return c.Status(500).JSON(&fiber.Map{
// #endregion "error": "Failed to request user",
})
}
// #region discord oauth userId := userResult.Id
app.Get("/oauth/callback", func(c *fiber.Ctx) error {
code := c.Query("code")
if code == "" { secret, err := rdb.Get(c.Context(), "secrets:"+hash(PEPPER_SECRETS+userId)).Result()
return c.Status(400).JSON(&fiber.Map{
"error": "Missing code",
})
}
var accessTokenResult DiscordAccessTokenResult if err == redis.Nil {
key := make([]byte, 48)
res, err := req.R().SetFormData(map[string]string{ _, err := rand.Read(key)
"client_id": DISCORD_CLIENT_ID, if err != nil {
"client_secret": DISCORD_CLIENT_SECRET, return c.Status(500).JSON(&fiber.Map{
"grant_type": "authorization_code", "error": "Failed to generate secret",
"code": code, })
"redirect_uri": DISCORD_REDIRECT_URI, }
"scope": "identify",
}).SetSuccessResult(&accessTokenResult).Post("https://discord.com/api/oauth2/token")
if err != nil { secret = hex.EncodeToString(key)
return c.Status(500).JSON(&fiber.Map{ rdb.Set(c.Context(), "secrets:"+hash(PEPPER_SECRETS+userId), secret, 0)
"error": "Failed to request access token", }
})
}
if res.IsErrorState() { return c.JSON(&fiber.Map{
return c.Status(400).JSON(&fiber.Map{ "secret": secret,
"error": "Invalid code", })
}) })
}
accessToken := accessTokenResult.AccessToken app.Get("/oauth/settings", func(c *fiber.Ctx) error {
return c.JSON(&fiber.Map{
"clientId": DISCORD_CLIENT_ID,
"redirectUri": DISCORD_REDIRECT_URI,
})
})
// #endregion
var userResult DiscordUserResult // #region erase all
app.Delete("/", requireAuth, func(c *fiber.Ctx) error {
userId := c.Context().UserValue("userId").(string)
res, err = req.R().SetHeaders(map[string]string{ rdb.Del(c.Context(), "settings:"+hash(PEPPER_SETTINGS+userId))
"Authorization": "Bearer " + accessToken, rdb.Del(c.Context(), "secret"+hash(PEPPER_SECRETS+userId))
}).SetSuccessResult(&userResult).Get("https://discord.com/api/users/@me")
if err != nil { return c.SendStatus(204)
return c.Status(500).JSON(&fiber.Map{ })
"error": "Failed to request user", // #endregion
})
}
if res.IsErrorState() { app.Get("/", func(c *fiber.Ctx) error {
return c.Status(500).JSON(&fiber.Map{ return c.Redirect(ROOT_REDIRECT, 307)
"error": "Failed to request user", })
})
}
userId := userResult.Id app.Listen(HOST + ":" + PORT)
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)
}
return c.JSON(&fiber.Map{
"secret": secret,
})
})
app.Get("/oauth/settings", func(c *fiber.Ctx) error {
return c.JSON(&fiber.Map{
"clientId": DISCORD_CLIENT_ID,
"redirectUri": DISCORD_REDIRECT_URI,
})
})
// #endregion
app.Get("/", func(c *fiber.Ctx) error {
return c.Redirect(ROOT_REDIRECT, 307)
})
app.Listen(HOST + ":" + PORT)
} }