feat: add basic workings
This commit is contained in:
@@ -12,7 +12,7 @@ const (
|
|||||||
CreationMes string = "Object successfully created"
|
CreationMes string = "Object successfully created"
|
||||||
DeletionMes string = "Object successfully deleted"
|
DeletionMes string = "Object successfully deleted"
|
||||||
NotFoundMes string = "Requested object not found"
|
NotFoundMes string = "Requested object not found"
|
||||||
BadRequestMes string = "Request did not satisfy requirements"
|
BadRequestMes string = "Request did not satisfy requirements (bad request)"
|
||||||
ConflictMes string = "Duplicate object"
|
ConflictMes string = "Duplicate object"
|
||||||
IntErrMes string = "An internal error occured, contact your administrator"
|
IntErrMes string = "An internal error occured, contact your administrator"
|
||||||
)
|
)
|
||||||
@@ -24,10 +24,10 @@ type ResponseObject struct {
|
|||||||
|
|
||||||
// we swap out the hash for the keycontent
|
// we swap out the hash for the keycontent
|
||||||
type KeyResponse struct {
|
type KeyResponse struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"ID"`
|
||||||
MetaName string `json:"metaName"`
|
MetaName string `json:"metaName"`
|
||||||
KeyName string `json:"keyName"`
|
KeyID string `json:"keyID"`
|
||||||
KeyContent string `json:"keyContent"`
|
KeySecret string `json:"keySecret"`
|
||||||
Revoked bool `json:"revoked"`
|
Revoked bool `json:"revoked"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
@@ -35,9 +35,9 @@ type KeyResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FileResponse struct {
|
type FileResponse struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"ID"`
|
||||||
MetaName string `json:"metaName"`
|
MetaName string `json:"metaName"`
|
||||||
FileName string `json:"fileName"`
|
FileID string `json:"fileID"`
|
||||||
FilePath string `json:"filePath"`
|
FilePath string `json:"filePath"`
|
||||||
Checksum string `json:"checksum"`
|
Checksum string `json:"checksum"`
|
||||||
MediaType string `json:"mediaType"`
|
MediaType string `json:"mediaType"`
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"orbits-server/internal/server/api/assets"
|
"orbits-server/internal/server/api/assets"
|
||||||
"orbits-server/internal/server/service"
|
"orbits-server/internal/server/service"
|
||||||
"orbits-server/internal/shared/security"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -13,6 +12,8 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const authPrefix = "Bearer"
|
||||||
|
|
||||||
func SlogMiddleware(logger *slog.Logger) gin.HandlerFunc {
|
func SlogMiddleware(logger *slog.Logger) gin.HandlerFunc {
|
||||||
// Make a slog-looking logger, inspired by the gin docs themself
|
// Make a slog-looking logger, inspired by the gin docs themself
|
||||||
// JSON logger: https://gin-gonic.com/en/docs/logging/structured-logging/
|
// JSON logger: https://gin-gonic.com/en/docs/logging/structured-logging/
|
||||||
@@ -45,40 +46,30 @@ func AuthMiddleware(db *gorm.DB) gin.HandlerFunc {
|
|||||||
keyService := service.NewKeyService(db)
|
keyService := service.NewKeyService(db)
|
||||||
|
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
authorizationHeader := c.GetHeader("Authorization")
|
header := c.GetHeader("Authorization")
|
||||||
if len(authorizationHeader) == 0 {
|
if len(header) == 0 {
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, assets.ResponseObject{
|
c.AbortWithStatusJSON(http.StatusUnauthorized, assets.ResponseObject{
|
||||||
Msg: "Authorization header is required",
|
Msg: "Authorization header is required",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
headerParts := strings.Split(authorizationHeader, " ")
|
if !strings.HasPrefix(header, authPrefix) {
|
||||||
// The header must be a specific format, 0 being the bearer text and 1 being the token itself, making it 2 pieces total
|
|
||||||
// In the following if statement we verify both parts if the part after Bearer is empty its only 1 part for example
|
|
||||||
if len(headerParts) != 2 || headerParts[0] != "Bearer" {
|
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, assets.ResponseObject{
|
c.AbortWithStatusJSON(http.StatusUnauthorized, assets.ResponseObject{
|
||||||
Msg: "Authorization header is invalid",
|
Msg: "Invalid authorization header",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
candidateKey := headerParts[1]
|
token := strings.TrimSpace(header[len(authPrefix):])
|
||||||
storedKeys, err := keyService.ListValidKeyHashes()
|
ok := keyService.Validate(token)
|
||||||
if err != nil {
|
if !ok {
|
||||||
slog.Error("failed to retrieve key hashes", "error", err)
|
c.AbortWithStatusJSON(http.StatusUnauthorized, assets.ResponseObject{
|
||||||
assets.InternalErrorResponse(c)
|
Msg: "Invalid key",
|
||||||
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, key := range storedKeys {
|
|
||||||
if match := security.CompareKey(key, candidateKey); match {
|
|
||||||
c.Next()
|
c.Next()
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, assets.ResponseObject{
|
|
||||||
Msg: "invalid key",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ func RegisterFileRoutes(file *gin.RouterGroup, env bootstrap.Environment, db *go
|
|||||||
// for example: /file/<file-name>
|
// for example: /file/<file-name>
|
||||||
|
|
||||||
// file download route / display contents
|
// file download route / display contents
|
||||||
file.GET("/:fileName", func(c *gin.Context) {
|
file.GET("/:fileID", func(c *gin.Context) {
|
||||||
fileParam := c.Param("fileName")
|
fileParam := c.Param("fileID")
|
||||||
fp := filepath.Join(env.ContentDirectory, fileParam)
|
fp := filepath.Join(env.ContentDirectory, fileParam)
|
||||||
|
|
||||||
assets.FileDownloadResponse(c, fp)
|
assets.FileDownloadResponse(c, fp)
|
||||||
@@ -72,8 +72,8 @@ func RegisterFileRoutes(file *gin.RouterGroup, env bootstrap.Environment, db *go
|
|||||||
})
|
})
|
||||||
|
|
||||||
// delete route
|
// delete route
|
||||||
file.DELETE("/:filename", func(c *gin.Context) {
|
file.DELETE("/:fileID", func(c *gin.Context) {
|
||||||
fileParam := c.Param("filename")
|
fileParam := c.Param("fileID")
|
||||||
|
|
||||||
if err := fileService.DeleteByName(fileParam); err != nil {
|
if err := fileService.DeleteByName(fileParam); err != nil {
|
||||||
slog.Error("file not found", "error", err)
|
slog.Error("file not found", "error", err)
|
||||||
@@ -81,7 +81,7 @@ func RegisterFileRoutes(file *gin.RouterGroup, env bootstrap.Environment, db *go
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("received a delete request for a file", "fileName", fileParam)
|
slog.Info("received a delete request for a file", "fileID", fileParam)
|
||||||
assets.DeletionResponse(c)
|
assets.DeletionResponse(c)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -28,13 +28,12 @@ func RegisterKeyRoutes(api *gin.RouterGroup, db *gorm.DB) {
|
|||||||
|
|
||||||
keyResponse, err := keyService.Create(body.Name, body.ExpiresAt)
|
keyResponse, err := keyService.Create(body.Name, body.ExpiresAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("failed to create key", "error", err)
|
slog.Error("failed to build key record", "error", err)
|
||||||
assets.InternalErrorResponse(c)
|
assets.BadRequestResponse(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("saved key to database")
|
slog.Info("saved key to database")
|
||||||
|
|
||||||
assets.CreationResponse(c, keyResponse)
|
assets.CreationResponse(c, keyResponse)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -44,6 +43,7 @@ func RegisterKeyRoutes(api *gin.RouterGroup, db *gorm.DB) {
|
|||||||
if err := keyService.DeleteByName(keyParam); err != nil {
|
if err := keyService.DeleteByName(keyParam); err != nil {
|
||||||
slog.Error("key not found", "error", err)
|
slog.Error("key not found", "error", err)
|
||||||
assets.NotFoundResponse(c)
|
assets.NotFoundResponse(c)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("received a delete request for a key", "keyName", keyParam)
|
slog.Info("received a delete request for a key", "keyName", keyParam)
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ func ListKeys(db *gorm.DB) ([]AccessKey, error) {
|
|||||||
return keys, err
|
return keys, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func FindKeyByName(db *gorm.DB, name string) (AccessKey, error) {
|
func FindKeyByKeyID(db *gorm.DB, name string) (AccessKey, error) {
|
||||||
var key AccessKey
|
var key AccessKey
|
||||||
err := db.Where("key_name = ?", name).First(&key).Error
|
err := db.Where("key_id = ?", name).First(&key).Error
|
||||||
return key, err
|
return key, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,9 +70,9 @@ func ListFiles(db *gorm.DB) ([]File, error) {
|
|||||||
return files, err
|
return files, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func FindFileByName(db *gorm.DB, name string) (File, error) {
|
func FindFileByFileID(db *gorm.DB, name string) (File, error) {
|
||||||
var file File
|
var file File
|
||||||
err := db.Where("file_name = ?", name).First(&file).Error
|
err := db.Where("file_id = ?", name).First(&file).Error
|
||||||
return file, err
|
return file, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ type AccessKey struct {
|
|||||||
ID int `gorm:"primaryKey;not null;"`
|
ID int `gorm:"primaryKey;not null;"`
|
||||||
MetaName string
|
MetaName string
|
||||||
// UUID for safe storage
|
// UUID for safe storage
|
||||||
KeyName string `gorm:"not null;"`
|
KeyID string `gorm:"not null;"`
|
||||||
// We don't store the key itself, we hash the key
|
// We don't store the key itself, we hash the key
|
||||||
KeyHash string `gorm:"uniqueIndex;not null;"`
|
KeyHash string `gorm:"uniqueIndex;not null;"`
|
||||||
// revoked status
|
// revoked status
|
||||||
@@ -83,7 +83,7 @@ type File struct {
|
|||||||
MediaType utility.MediaType `gorm:"type:varchar(20);not null;"`
|
MediaType utility.MediaType `gorm:"type:varchar(20);not null;"`
|
||||||
// the name given by the user
|
// the name given by the user
|
||||||
MetaName string
|
MetaName string
|
||||||
FileName string `gorm:"not null;"`
|
FileID string `gorm:"not null;"`
|
||||||
FilePath string `gorm:"not null;"`
|
FilePath string `gorm:"not null;"`
|
||||||
// hex encoded sha512 checksum
|
// hex encoded sha512 checksum
|
||||||
Checksum string `gorm:"uniqueIndex;not null;"`
|
Checksum string `gorm:"uniqueIndex;not null;"`
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ func BuildFileRecord(r io.Reader, metaName string, contentDirectory string) (Fil
|
|||||||
return File{}, err
|
return File{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
safeName := security.GenerateSafeCategoryName(category, ext)
|
safeName := security.GenerateSafeName() + ext
|
||||||
destPath := filepath.Join(contentDirectory, safeName)
|
destPath := filepath.Join(contentDirectory, safeName)
|
||||||
|
|
||||||
f := File{
|
f := File{
|
||||||
MediaType: category,
|
MediaType: category,
|
||||||
MetaName: metaName,
|
MetaName: metaName,
|
||||||
FileName: safeName,
|
FileID: safeName,
|
||||||
FilePath: destPath,
|
FilePath: destPath,
|
||||||
Checksum: checksum,
|
Checksum: checksum,
|
||||||
}
|
}
|
||||||
@@ -39,16 +39,21 @@ func BuildFileRecord(r io.Reader, metaName string, contentDirectory string) (Fil
|
|||||||
return f, nil
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildKeyRecord(keyHash string, metaName string, expiresAt time.Time) AccessKey {
|
func BuildKeyRecord(keyHash string, metaName string, expiresAt time.Time) (AccessKey, error) {
|
||||||
safeName := security.GenerateSafeName()
|
now := time.Now()
|
||||||
|
if expiresAt.Before(now) {
|
||||||
|
return AccessKey{}, fmt.Errorf("key is already expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
safeName := "orbits_" + security.GenerateSafeName()
|
||||||
|
|
||||||
k := AccessKey{
|
k := AccessKey{
|
||||||
MetaName: metaName,
|
MetaName: metaName,
|
||||||
KeyName: safeName,
|
KeyID: safeName,
|
||||||
KeyHash: keyHash,
|
KeyHash: keyHash,
|
||||||
Revoked: false,
|
Revoked: false,
|
||||||
ExpiresAt: expiresAt,
|
ExpiresAt: expiresAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
return k
|
return k, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"log/slog"
|
||||||
"orbits-server/internal/server/api/assets"
|
"orbits-server/internal/server/api/assets"
|
||||||
"orbits-server/internal/server/bootstrap"
|
"orbits-server/internal/server/bootstrap"
|
||||||
"orbits-server/internal/server/database"
|
"orbits-server/internal/server/database"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -19,6 +21,35 @@ func NewFileService(db *gorm.DB, env bootstrap.Environment) *FileService {
|
|||||||
return &FileService{db: db, env: env}
|
return &FileService{db: db, env: env}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *FileService) SyncFile(fp string) {
|
||||||
|
f, err := os.Open(fp)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to open the file", "file", fp, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
fileData, err := database.BuildFileRecord(f, filepath.Base(fp), s.env.ContentDirectory)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to build file record", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := database.CreateFile(s.db, &fileData); err != nil {
|
||||||
|
slog.Error("failed to create file record", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(fp, fileData.FilePath); err != nil {
|
||||||
|
// if this fails across mounts then try copy + delete
|
||||||
|
slog.Error("failed to move file", "error", err)
|
||||||
|
|
||||||
|
database.DeleteFileByID(s.db, fileData.ID)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *FileService) ListFiles() ([]assets.FileResponse, error) {
|
func (s *FileService) ListFiles() ([]assets.FileResponse, error) {
|
||||||
fileRecords, err := database.ListFiles(s.db)
|
fileRecords, err := database.ListFiles(s.db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -32,7 +63,7 @@ func (s *FileService) ListFiles() ([]assets.FileResponse, error) {
|
|||||||
ID: f.ID,
|
ID: f.ID,
|
||||||
MetaName: f.MetaName,
|
MetaName: f.MetaName,
|
||||||
MediaType: string(f.MediaType),
|
MediaType: string(f.MediaType),
|
||||||
FileName: f.FileName,
|
FileID: f.FileID,
|
||||||
FilePath: f.FilePath,
|
FilePath: f.FilePath,
|
||||||
Checksum: f.Checksum,
|
Checksum: f.Checksum,
|
||||||
CreatedAt: f.CreatedAt,
|
CreatedAt: f.CreatedAt,
|
||||||
@@ -57,7 +88,7 @@ func (s *FileService) Create(r io.Reader, filename string) (assets.FileResponse,
|
|||||||
ID: f.ID,
|
ID: f.ID,
|
||||||
MetaName: f.MetaName,
|
MetaName: f.MetaName,
|
||||||
MediaType: string(f.MediaType),
|
MediaType: string(f.MediaType),
|
||||||
FileName: f.FileName,
|
FileID: f.FileID,
|
||||||
FilePath: f.FilePath,
|
FilePath: f.FilePath,
|
||||||
Checksum: f.Checksum,
|
Checksum: f.Checksum,
|
||||||
CreatedAt: f.CreatedAt,
|
CreatedAt: f.CreatedAt,
|
||||||
@@ -68,7 +99,7 @@ func (s *FileService) Create(r io.Reader, filename string) (assets.FileResponse,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileService) DeleteByName(filename string) error {
|
func (s *FileService) DeleteByName(filename string) error {
|
||||||
fileRecord, err := database.FindFileByName(s.db, filename)
|
fileRecord, err := database.FindFileByFileID(s.db, filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"orbits-server/internal/server/api/assets"
|
"orbits-server/internal/server/api/assets"
|
||||||
"orbits-server/internal/server/database"
|
"orbits-server/internal/server/database"
|
||||||
"orbits-server/internal/shared/security"
|
"orbits-server/internal/shared/security"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -23,20 +24,6 @@ func NewKeyService(db *gorm.DB) *KeyService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *KeyService) ListValidKeyHashes() ([]string, error) {
|
|
||||||
keyRecords, err := database.ListKeys(s.db)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hashList := make([]string, 0, len(keyRecords))
|
|
||||||
for _, k := range keyRecords {
|
|
||||||
hashList = append(hashList, k.KeyHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
return hashList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *KeyService) Create(name string, expiresAt time.Time) (assets.KeyResponse, error) {
|
func (s *KeyService) Create(name string, expiresAt time.Time) (assets.KeyResponse, error) {
|
||||||
keyContent := security.GenerateChars(accessKeyLen)
|
keyContent := security.GenerateChars(accessKeyLen)
|
||||||
|
|
||||||
@@ -45,7 +32,10 @@ func (s *KeyService) Create(name string, expiresAt time.Time) (assets.KeyRespons
|
|||||||
return assets.KeyResponse{}, err
|
return assets.KeyResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
keyRecord := database.BuildKeyRecord(hash, name, expiresAt)
|
keyRecord, err := database.BuildKeyRecord(hash, name, expiresAt)
|
||||||
|
if err != nil {
|
||||||
|
return assets.KeyResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
if err := database.CreateKey(s.db, &keyRecord); err != nil {
|
if err := database.CreateKey(s.db, &keyRecord); err != nil {
|
||||||
return assets.KeyResponse{}, err
|
return assets.KeyResponse{}, err
|
||||||
@@ -54,8 +44,8 @@ func (s *KeyService) Create(name string, expiresAt time.Time) (assets.KeyRespons
|
|||||||
keyResponse := assets.KeyResponse{
|
keyResponse := assets.KeyResponse{
|
||||||
ID: keyRecord.ID,
|
ID: keyRecord.ID,
|
||||||
MetaName: keyRecord.MetaName,
|
MetaName: keyRecord.MetaName,
|
||||||
KeyName: keyRecord.KeyName,
|
KeyID: keyRecord.KeyID,
|
||||||
KeyContent: keyContent,
|
KeySecret: keyContent,
|
||||||
CreatedAt: keyRecord.CreatedAt,
|
CreatedAt: keyRecord.CreatedAt,
|
||||||
UpdatedAt: keyRecord.UpdatedAt,
|
UpdatedAt: keyRecord.UpdatedAt,
|
||||||
ExpiresAt: keyRecord.ExpiresAt,
|
ExpiresAt: keyRecord.ExpiresAt,
|
||||||
@@ -64,8 +54,33 @@ func (s *KeyService) Create(name string, expiresAt time.Time) (assets.KeyRespons
|
|||||||
return keyResponse, nil
|
return keyResponse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *KeyService) Validate(token string) bool {
|
||||||
|
parts := strings.SplitN(token, ".", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
keyID := parts[0]
|
||||||
|
secret := parts[1]
|
||||||
|
|
||||||
|
key, err := database.FindKeyByKeyID(s.db, keyID)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if key.Revoked || time.Now().After(key.ExpiresAt) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !security.CompareKey(key.KeyHash, secret) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (s *KeyService) DeleteByName(name string) error {
|
func (s *KeyService) DeleteByName(name string) error {
|
||||||
keyRecord, err := database.FindKeyByName(s.db, name)
|
keyRecord, err := database.FindKeyByKeyID(s.db, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,41 +4,37 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"orbits-server/internal/server/bootstrap"
|
"orbits-server/internal/server/bootstrap"
|
||||||
"orbits-server/internal/server/database"
|
"orbits-server/internal/server/database"
|
||||||
|
"orbits-server/internal/server/service"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func revokeExpired(db *gorm.DB) {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
if err := db.Model(&database.AccessKey{}).
|
||||||
|
Where("expires_at < ? AND revoked = ?", now, false).
|
||||||
|
Update("revoked", true).Error; err != nil {
|
||||||
|
|
||||||
|
slog.Error("failed to revoke expired keys", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func applyFS(env bootstrap.Environment, db *gorm.DB, fsOrphans []string) {
|
func applyFS(env bootstrap.Environment, db *gorm.DB, fsOrphans []string) {
|
||||||
|
fileService := service.NewFileService(db, env)
|
||||||
for _, fp := range fsOrphans {
|
for _, fp := range fsOrphans {
|
||||||
|
|
||||||
switch env.WatchdogSyncMode {
|
switch env.WatchdogSyncMode {
|
||||||
|
|
||||||
case "sync":
|
case "sync":
|
||||||
f, err := os.Open(fp)
|
fileService.SyncFile(fp)
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
func() {
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
fileData, err := database.BuildFileRecord(
|
|
||||||
f,
|
|
||||||
filepath.Base(fp),
|
|
||||||
env.ContentDirectory,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = database.CreateFile(db, &fileData)
|
|
||||||
_ = os.Rename(fp, fileData.FilePath)
|
|
||||||
}()
|
|
||||||
|
|
||||||
case "strict":
|
case "strict":
|
||||||
_ = os.Remove(fp)
|
if err := os.Remove(fp); err != nil {
|
||||||
|
slog.Error("failed to remove file", "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
case "dry":
|
case "dry":
|
||||||
slog.Debug("dry mode", "file", fp)
|
slog.Debug("dry mode", "file", fp)
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ func scanFS(env bootstrap.Environment) (map[string]struct{}, error) {
|
|||||||
|
|
||||||
resp := make(map[string]struct{})
|
resp := make(map[string]struct{})
|
||||||
for _, f := range fsFiles {
|
for _, f := range fsFiles {
|
||||||
|
if f.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
full := filepath.Join(env.ContentDirectory, f.Name())
|
full := filepath.Join(env.ContentDirectory, f.Name())
|
||||||
resp[full] = struct{}{}
|
resp[full] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,22 +28,40 @@ func Kickoff(env bootstrap.Environment, db *gorm.DB) {
|
|||||||
|
|
||||||
run(env, db)
|
run(env, db)
|
||||||
|
|
||||||
|
running := false
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
|
if running {
|
||||||
|
slog.Warn("watchdog is still running, skipping tick")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
running = true
|
||||||
|
|
||||||
|
func() {
|
||||||
|
defer func() { running = false }()
|
||||||
run(env, db)
|
run(env, db)
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the watchdog has 2 tasks
|
||||||
|
// 1 revoke expires keys
|
||||||
|
// 2 remove / sync orphaned files/db records
|
||||||
func run(env bootstrap.Environment, db *gorm.DB) {
|
func run(env bootstrap.Environment, db *gorm.DB) {
|
||||||
slog.Debug("watchdog cycle start")
|
slog.Debug("watchdog cycle start")
|
||||||
|
|
||||||
|
revokeExpired(db)
|
||||||
|
|
||||||
fsState, err := scanFS(env)
|
fsState, err := scanFS(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
slog.Error("scanFS failed", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dbState, err := scanDB(db)
|
dbState, err := scanDB(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
slog.Error("scanDB failed", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,10 @@ package security
|
|||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"orbits-server/internal/shared/utility"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateSafeCategoryName(category utility.MediaType, ext string) string {
|
|
||||||
return uuid.New().String() + "_" + string(category) + ext
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenerateSafeName() string {
|
func GenerateSafeName() string {
|
||||||
return uuid.New().String()
|
return uuid.New().String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
argonTime = 3
|
argonTime = 1
|
||||||
argonMemory = 64 * 1024
|
argonMemory = 32 * 1024
|
||||||
argonThreads = 2
|
argonThreads = 2
|
||||||
argonSaltLen = 16
|
argonSaltLen = 16
|
||||||
argonKeyLen = 64
|
argonKeyLen = 32
|
||||||
)
|
)
|
||||||
|
|
||||||
func HashFileReader(r io.Reader) (string, error) {
|
func HashFileReader(r io.Reader) (string, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user