Add support for downloading a backup
This commit is contained in:
parent
ccbb119948
commit
c4474e22f6
1
http.go
1
http.go
|
@ -396,7 +396,6 @@ func (rt *Router) ReaderToBytes(r io.Reader) []byte {
|
||||||
func (rt *Router) ConfigureRouter() *httprouter.Router {
|
func (rt *Router) ConfigureRouter() *httprouter.Router {
|
||||||
router := httprouter.New()
|
router := httprouter.New()
|
||||||
|
|
||||||
router.GET("/download/backup", rt.routeDownloadBackup)
|
|
||||||
router.POST("/api/servers/:server/backup", rt.AuthenticateRequest(rt.routeServerBackup))
|
router.POST("/api/servers/:server/backup", rt.AuthenticateRequest(rt.routeServerBackup))
|
||||||
|
|
||||||
router.POST("/api/servers/:server/archive", rt.AuthenticateRequest(rt.routeRequestServerArchive))
|
router.POST("/api/servers/:server/archive", rt.AuthenticateRequest(rt.routeRequestServerArchive))
|
||||||
|
|
|
@ -7,6 +7,9 @@ func Configure() *gin.Engine {
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
router.Use(SetAccessControlHeaders)
|
router.Use(SetAccessControlHeaders)
|
||||||
|
|
||||||
|
// These routes use signed URLs to validate access to the resource being requested.
|
||||||
|
router.GET("/download/backup", getDownloadBackup)
|
||||||
|
|
||||||
// This route is special is sits above all of the other requests because we are
|
// This route is special is sits above all of the other requests because we are
|
||||||
// using a JWT to authorize access to it, therefore it needs to be publically
|
// using a JWT to authorize access to it, therefore it needs to be publically
|
||||||
// accessible.
|
// accessible.
|
||||||
|
|
|
@ -1 +1,45 @@
|
||||||
package router
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pterodactyl/wings/router/tokens"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getDownloadBackup(c *gin.Context) {
|
||||||
|
token := tokens.BackupPayload{}
|
||||||
|
if err := tokens.ParseToken([]byte(c.Query("token")), &token); err != nil {
|
||||||
|
TrackedError(err).AbortWithServerError(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s := GetServer(token.ServerUuid)
|
||||||
|
if s == nil || !token.IsUniqueRequest() {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
|
||||||
|
"error": "The requested resource was not found on this server.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p, st, err := s.LocateBackup(token.BackupUuid)
|
||||||
|
if err != nil {
|
||||||
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(p)
|
||||||
|
if err != nil {
|
||||||
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
c.Header("Content-Length", strconv.Itoa(int(st.Size())))
|
||||||
|
c.Header("Content-Disposition", "attachment; filename="+st.Name())
|
||||||
|
c.Header("Content-Type", "application/octet-stream")
|
||||||
|
|
||||||
|
bufio.NewReader(f).WriteTo(c.Writer)
|
||||||
|
}
|
||||||
|
|
25
router/tokens/backup.go
Normal file
25
router/tokens/backup.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package tokens
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gbrlsnchs/jwt/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BackupPayload struct {
|
||||||
|
jwt.Payload
|
||||||
|
ServerUuid string `json:"server_uuid"`
|
||||||
|
BackupUuid string `json:"backup_uuid"`
|
||||||
|
UniqueId string `json:"unique_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the JWT payload.
|
||||||
|
func (p *BackupPayload) GetPayload() *jwt.Payload {
|
||||||
|
return &p.Payload
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determines if this JWT is valid for the given request cycle. If the
|
||||||
|
// unique ID passed in the token has already been seen before this will
|
||||||
|
// return false. This allows us to use this JWT as a one-time token that
|
||||||
|
// validates all of the request.
|
||||||
|
func (p *BackupPayload) IsUniqueRequest() bool {
|
||||||
|
return getTokenStore().IsValidToken(p.UniqueId)
|
||||||
|
}
|
|
@ -30,4 +30,4 @@ func ParseToken(token []byte, data TokenData) error {
|
||||||
_, err := jwt.Verify(token, alg, &data, verifyOptions)
|
_, err := jwt.Verify(token, alg, &data, verifyOptions)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
41
router/tokens/token_store.go
Normal file
41
router/tokens/token_store.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package tokens
|
||||||
|
|
||||||
|
import (
|
||||||
|
cache2 "github.com/patrickmn/go-cache"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TokenStore struct {
|
||||||
|
cache *cache2.Cache
|
||||||
|
mutex *sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
var _tokens *TokenStore
|
||||||
|
|
||||||
|
// Returns the global unqiue token store cache. This is used to validate
|
||||||
|
// one time token usage by storing any received tokens in a local memory
|
||||||
|
// cache until they are ready to expire.
|
||||||
|
func getTokenStore() *TokenStore {
|
||||||
|
if _tokens == nil {
|
||||||
|
_tokens = &TokenStore{
|
||||||
|
cache: cache2.New(time.Minute*60, time.Minute*5),
|
||||||
|
mutex: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TokenStore) IsValidToken(token string) bool {
|
||||||
|
t.mutex.Lock()
|
||||||
|
defer t.mutex.Unlock()
|
||||||
|
|
||||||
|
_, exists := t.cache.Get(token)
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
t.cache.Add(token, "", time.Minute*60)
|
||||||
|
}
|
||||||
|
|
||||||
|
return !exists
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user