Skip to content

Commit 25a3b87

Browse files
committed
Added option for presigned URLs and add download button in main menu
1 parent 54c771b commit 25a3b87

File tree

20 files changed

+329
-16
lines changed

20 files changed

+329
-16
lines changed

docs/setup.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ This option disables Gokapis internal authentication completely, except for API
285285
- ``/apiKeys``
286286
- ``/auth/token``
287287
- ``/changePassword``
288+
- ``/downloadPresigned``
288289
- ``/e2eSetup``
289290
- ``/filerequests``
290291
- ``/logs``

internal/configuration/database/Database.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,3 +342,20 @@ func SaveFileRequest(request models.FileRequest) int {
342342
func DeleteFileRequest(request models.FileRequest) {
343343
db.DeleteFileRequest(request)
344344
}
345+
346+
// Presigned URLs
347+
348+
// GetPresignedUrl returns the presigned url with the given ID or false if not a valid ID
349+
func GetPresignedUrl(id string) (models.Presign, bool) {
350+
return db.GetPresignedUrl(id)
351+
}
352+
353+
// DeletePresignedUrl deletes the presigned url with the given ID
354+
func DeletePresignedUrl(id string) {
355+
db.DeletePresignedUrl(id)
356+
}
357+
358+
// SavePresignedUrl saves the presigned url
359+
func SavePresignedUrl(presign models.Presign) {
360+
db.SavePresignedUrl(presign)
361+
}

internal/configuration/database/dbabstraction/DbAbstraction.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,13 @@ type Database interface {
107107
SaveFileRequest(request models.FileRequest) int
108108
// DeleteFileRequest deletes a file request with the given ID
109109
DeleteFileRequest(request models.FileRequest)
110+
111+
// GetPresignedUrl returns the presigned url with the given ID or false if not a valid ID
112+
GetPresignedUrl(id string) (models.Presign, bool)
113+
// DeletePresignedUrl deletes the presigned url with the given ID
114+
DeletePresignedUrl(id string)
115+
// SavePresignedUrl saves the presigned url
116+
SavePresignedUrl(presign models.Presign)
110117
}
111118

112119
// GetNew connects to the given database and initialises it
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package redis
2+
3+
import (
4+
"github.com/forceu/gokapi/internal/helper"
5+
"github.com/forceu/gokapi/internal/models"
6+
redigo "github.com/gomodule/redigo/redis"
7+
)
8+
9+
const (
10+
prefixPresign = "ps:"
11+
)
12+
13+
// GetPresignedUrl returns the presigned url with the given ID or false if not a valid ID
14+
func (p DatabaseProvider) GetPresignedUrl(id string) (models.Presign, bool) {
15+
hashmapEntry, ok := p.getHashMap(prefixPresign + id)
16+
if !ok {
17+
return models.Presign{}, false
18+
}
19+
var result models.Presign
20+
err := redigo.ScanStruct(hashmapEntry, &result)
21+
helper.Check(err)
22+
return result, true
23+
}
24+
25+
// SavePresignedUrl saves the presigned url
26+
func (p DatabaseProvider) SavePresignedUrl(presign models.Presign) {
27+
p.setHashMap(p.buildArgs(prefixPresign + presign.Id).AddFlat(presign))
28+
p.setExpiryAt(prefixPresign+presign.Id, presign.Expiry)
29+
}
30+
31+
// DeletePresignedUrl deletes the presigned url with the given ID
32+
func (p DatabaseProvider) DeletePresignedUrl(id string) {
33+
p.deleteKey(prefixPresign + id)
34+
}

internal/configuration/database/provider/sqlite/Sqlite.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,13 @@ func (p DatabaseProvider) Upgrade(currentDbVersion int) {
6060
"maxSize" INTEGER NOT NULL,
6161
"creation" INTEGER NOT NULL,
6262
PRIMARY KEY("id" AUTOINCREMENT)
63-
);`)
63+
);
64+
CREATE TABLE "Presign" (
65+
"id" TEXT NOT NULL UNIQUE,
66+
"fileId" TEXT NOT NULL,
67+
"expiry" INTEGER NOT NULL,
68+
PRIMARY KEY("id")
69+
);`)
6470
helper.Check(err)
6571
if environment.New().PermRequestGrantedByDefault {
6672
for _, user := range p.GetAllUsers() {
@@ -144,6 +150,7 @@ func (p DatabaseProvider) Close() {
144150
func (p DatabaseProvider) RunGarbageCollection() {
145151
p.cleanExpiredSessions()
146152
p.cleanApiKeys()
153+
p.cleanPresignedUrls()
147154
}
148155

149156
func (p DatabaseProvider) createNewDatabase() error {
@@ -219,6 +226,12 @@ func (p DatabaseProvider) createNewDatabase() error {
219226
"creation" INTEGER NOT NULL,
220227
PRIMARY KEY("id" AUTOINCREMENT)
221228
);
229+
CREATE TABLE "Presign" (
230+
"id" TEXT NOT NULL UNIQUE,
231+
"fileId" TEXT NOT NULL,
232+
"expiry" INTEGER NOT NULL,
233+
PRIMARY KEY("id")
234+
);
222235
`
223236
err := p.rawSqlite(sqlStmt)
224237
if err != nil {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package sqlite
2+
3+
import (
4+
"database/sql"
5+
"errors"
6+
7+
"github.com/forceu/gokapi/internal/helper"
8+
"github.com/forceu/gokapi/internal/models"
9+
)
10+
11+
type schemaPresign struct {
12+
Id string
13+
FileId string
14+
Expiry int64
15+
}
16+
17+
// GetPresignedUrl returns the presigned url with the given ID or false if not a valid ID
18+
func (p DatabaseProvider) GetPresignedUrl(id string) (models.Presign, bool) {
19+
var rowResult schemaPresign
20+
row := p.sqliteDb.QueryRow("SELECT * FROM Presign WHERE Id = ?", id)
21+
err := row.Scan(&rowResult.Id, &rowResult.FileId, &rowResult.Expiry)
22+
if err != nil {
23+
if errors.Is(err, sql.ErrNoRows) {
24+
return models.Presign{}, false
25+
}
26+
helper.Check(err)
27+
return models.Presign{}, false
28+
}
29+
result := models.Presign{
30+
Id: rowResult.Id,
31+
FileId: rowResult.FileId,
32+
Expiry: rowResult.Expiry,
33+
}
34+
return result, true
35+
}
36+
37+
// SavePresignedUrl saves the presigned url
38+
func (p DatabaseProvider) SavePresignedUrl(presign models.Presign) {
39+
_, err := p.sqliteDb.Exec("INSERT OR REPLACE INTO Presign (Id, FileId,Expiry) VALUES (?, ?, ?)",
40+
presign.Id, presign.FileId, presign.Expiry)
41+
helper.Check(err)
42+
}
43+
44+
// DeletePresignedUrl deletes the presigned url with the given ID
45+
func (p DatabaseProvider) DeletePresignedUrl(id string) {
46+
_, err := p.sqliteDb.Exec("DELETE FROM Presign WHERE id = ?", id)
47+
helper.Check(err)
48+
}
49+
50+
func (p DatabaseProvider) cleanPresignedUrls() {
51+
_, err := p.sqliteDb.Exec("DELETE FROM Presign WHERE expiry < ?", currentTime().Unix())
52+
helper.Check(err)
53+
}

internal/configuration/setup/ProtectedUrls.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/models/Presign.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package models
2+
3+
type Presign struct {
4+
Id string
5+
FileId string
6+
Expiry int64
7+
}

internal/storage/FileServing.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ var ErrorReplaceE2EFile = errors.New("end-to-end encrypted files cannot be repla
4646
// ErrorFileNotFound is raised when an invalid ID is passed or the file has expired
4747
var ErrorFileNotFound = errors.New("file not found")
4848

49+
// ErrorInvalidPresign is raised when an invalid presign key has been passed or it has expired
50+
var ErrorInvalidPresign = errors.New("invalid presign")
51+
4952
// NewFile creates a new file in the system. Called after an upload from the API has been completed. If a file with the same sha1 hash
5053
// already exists, it is deduplicated. This function gathers information about the file, creates an ID and saves
5154
// it into the global configuration. It is now only used by the API, the web UI uses NewFileFromChunk

internal/webserver/Webserver.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ func Start() {
100100
mux.HandleFunc("/changePassword", requireLogin(changePassword, true, true))
101101
mux.HandleFunc("/d", showDownload)
102102
mux.HandleFunc("/downloadFile", downloadFile)
103+
mux.HandleFunc("/downloadPresigned", requireLogin(downloadPresigned, false, false))
103104
mux.HandleFunc("/e2eSetup", requireLogin(showE2ESetup, true, false))
104105
mux.HandleFunc("/error", showError)
105106
mux.HandleFunc("/error-auth", showErrorAuth)
@@ -909,6 +910,33 @@ func downloadFile(w http.ResponseWriter, r *http.Request) {
909910
serveFile(id, true, w, r)
910911
}
911912

913+
// Handling of /downloadPresigned
914+
// Outputs the file to the user and reduces the download remaining count for the file, if requested
915+
func downloadPresigned(w http.ResponseWriter, r *http.Request) {
916+
id, ok := r.URL.Query()["id"]
917+
if !ok || len(id[0]) < configuration.Get().LengthId {
918+
responseError(w, storage.ErrorFileNotFound)
919+
return
920+
}
921+
presignKey, ok := r.URL.Query()["key"]
922+
if !ok {
923+
responseError(w, storage.ErrorInvalidPresign)
924+
return
925+
}
926+
presign, ok := database.GetPresignedUrl(presignKey[0])
927+
if !ok || presign.Expiry < time.Now().Unix() || presign.FileId != id[0] {
928+
responseError(w, storage.ErrorInvalidPresign)
929+
return
930+
}
931+
savedFile, ok := storage.GetFile(presign.FileId)
932+
if !ok {
933+
responseError(w, storage.ErrorFileNotFound)
934+
return
935+
}
936+
database.DeletePresignedUrl(presign.Id)
937+
storage.ServeFile(savedFile, w, r, true, false)
938+
}
939+
912940
func serveFile(id string, isRootUrl bool, w http.ResponseWriter, r *http.Request) {
913941
addNoCacheHeader(w)
914942
savedFile, ok := storage.GetFile(id)

0 commit comments

Comments
 (0)