mirror of
https://github.com/Forceu/Gokapi.git
synced 2025-12-30 13:29:34 -06:00
Use API for most of UI interactions with an internal API key (#211), breaking API changes
* Changed color of API procressing status * Added internal system key, use system key for API page perm changes, BREAKING: removed session auth * API: Added /auth/delete, added option to include basic parameters in /auth/create, migrated API overview page completely to use API calls * Added toast in API menu for clipboard, removed reference to session authentication in documentation * Changed delete button in upload menu to API call, fixed modify API call in menu #210 * Set Api key in API menu for changing permissions, renamed title for Deleting Uploads permission, refactoring * Added tests, refactoring
This commit is contained in:
@@ -137,6 +137,6 @@ func fileExists(filename string) bool {
|
||||
// Auto-generated content below, do not modify
|
||||
// Version codes can be changed in updateVersionNumbers.go
|
||||
|
||||
const jsAdminVersion = 5
|
||||
const jsAdminVersion = 6
|
||||
const jsE2EVersion = 3
|
||||
const cssMainVersion = 2
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const versionJsAdmin = 5
|
||||
const versionJsAdmin = 6
|
||||
const versionJsDropzone = 4
|
||||
const versionJsE2EAdmin = 3
|
||||
const versionCssMain = 2
|
||||
|
||||
@@ -220,7 +220,7 @@ Interacting with the API
|
||||
============================
|
||||
|
||||
|
||||
All API calls will need an API key as authentication or a valid admin session cookie. An API key can be generated in the web UI in the menu "API". The API key needs to be passed as a header.
|
||||
All API calls will need an API key as authentication. An API key can be generated in the web UI in the menu "API". The API key needs to be passed as a header.
|
||||
|
||||
Example: Getting a list of all stored files with curl
|
||||
::
|
||||
|
||||
@@ -226,10 +226,7 @@ Only use this if you are running Gokapi behind a reverse proxy that is capable o
|
||||
This option disables Gokapis internal authentication completely, except for API calls. The following URLs need to be restricted by the reverse proxy:
|
||||
|
||||
- ``/admin``
|
||||
- ``/apiDelete``
|
||||
- ``/apiKeys``
|
||||
- ``/apiNew``
|
||||
- ``/delete``
|
||||
- ``/e2eInfo``
|
||||
- ``/e2eSetup``
|
||||
- ``/logs``
|
||||
|
||||
@@ -130,6 +130,11 @@ func DeleteApiKey(id string) {
|
||||
db.DeleteApiKey(id)
|
||||
}
|
||||
|
||||
// GetSystemKey returns the latest UI API key
|
||||
func GetSystemKey() (models.ApiKey, bool) {
|
||||
return db.GetSystemKey()
|
||||
}
|
||||
|
||||
// E2E Section
|
||||
|
||||
// SaveEnd2EndInfo stores the encrypted e2e info
|
||||
|
||||
@@ -43,6 +43,8 @@ type Database interface {
|
||||
UpdateTimeApiKey(apikey models.ApiKey)
|
||||
// DeleteApiKey deletes an API key with the given ID
|
||||
DeleteApiKey(id string)
|
||||
// GetSystemKey returns the latest UI API key
|
||||
GetSystemKey() (models.ApiKey, bool)
|
||||
|
||||
// SaveEnd2EndInfo stores the encrypted e2e info
|
||||
SaveEnd2EndInfo(info models.E2EInfoEncrypted)
|
||||
|
||||
@@ -41,9 +41,32 @@ func (p DatabaseProvider) GetApiKey(id string) (models.ApiKey, bool) {
|
||||
return apikey, true
|
||||
}
|
||||
|
||||
// GetSystemKey returns the latest UI API key
|
||||
func (p DatabaseProvider) GetSystemKey() (models.ApiKey, bool) {
|
||||
keys := p.GetAllApiKeys()
|
||||
foundKey := ""
|
||||
var latestExpiry int64
|
||||
for _, key := range keys {
|
||||
if !key.IsSystemKey {
|
||||
continue
|
||||
}
|
||||
if key.Expiry > latestExpiry {
|
||||
foundKey = key.Id
|
||||
latestExpiry = key.Expiry
|
||||
}
|
||||
}
|
||||
if foundKey == "" {
|
||||
return models.ApiKey{}, false
|
||||
}
|
||||
return keys[foundKey], true
|
||||
}
|
||||
|
||||
// SaveApiKey saves the API key to the database
|
||||
func (p DatabaseProvider) SaveApiKey(apikey models.ApiKey) {
|
||||
p.setHashMap(p.buildArgs(prefixApiKeys + apikey.Id).AddFlat(apikey))
|
||||
if apikey.Expiry != 0 {
|
||||
p.setExpiryAt(prefixApiKeys+apikey.Id, apikey.Expiry)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateTimeApiKey writes the content of LastUsage to the database
|
||||
|
||||
@@ -17,7 +17,7 @@ type DatabaseProvider struct {
|
||||
sqliteDb *sql.DB
|
||||
}
|
||||
|
||||
const DatabaseSchemeVersion = 3
|
||||
const DatabaseSchemeVersion = 4
|
||||
|
||||
// New returns an instance
|
||||
func New(dbConfig models.DbConnection) (DatabaseProvider, error) {
|
||||
@@ -57,6 +57,14 @@ func (p DatabaseProvider) Upgrade(currentDbVersion int) {
|
||||
ALTER TABLE "ApiKeys_New" RENAME TO "ApiKeys";`)
|
||||
helper.Check(err)
|
||||
}
|
||||
// < v1.9.0
|
||||
if currentDbVersion < 4 {
|
||||
// Add Column LastUsedString, keeping old data
|
||||
err := p.rawSqlite(`ALTER TABLE "ApiKeys" ADD COLUMN "Expiry" INTEGER;`)
|
||||
helper.Check(err)
|
||||
err = p.rawSqlite(`ALTER TABLE "ApiKeys" ADD COLUMN "IsSystemKey" INTEGER;`)
|
||||
helper.Check(err)
|
||||
}
|
||||
}
|
||||
|
||||
// GetDbVersion gets the version number of the database
|
||||
@@ -125,6 +133,7 @@ func (p DatabaseProvider) Close() {
|
||||
func (p DatabaseProvider) RunGarbageCollection() {
|
||||
p.cleanExpiredSessions()
|
||||
p.cleanUploadStatus()
|
||||
p.cleanApiKeys()
|
||||
}
|
||||
|
||||
func (p DatabaseProvider) createNewDatabase() error {
|
||||
@@ -133,6 +142,8 @@ func (p DatabaseProvider) createNewDatabase() error {
|
||||
"FriendlyName" TEXT NOT NULL,
|
||||
"LastUsed" INTEGER NOT NULL,
|
||||
"Permissions" INTEGER NOT NULL DEFAULT 0,
|
||||
"Expiry" INTEGER,
|
||||
"IsSystemKey" INTEGER,
|
||||
PRIMARY KEY("Id")
|
||||
) WITHOUT ROWID;
|
||||
CREATE TABLE "E2EConfig" (
|
||||
|
||||
@@ -12,24 +12,28 @@ type schemaApiKeys struct {
|
||||
FriendlyName string
|
||||
LastUsed int64
|
||||
Permissions int
|
||||
Expiry int64
|
||||
IsSystemKey int
|
||||
}
|
||||
|
||||
// GetAllApiKeys returns a map with all API keys
|
||||
func (p DatabaseProvider) GetAllApiKeys() map[string]models.ApiKey {
|
||||
result := make(map[string]models.ApiKey)
|
||||
|
||||
rows, err := p.sqliteDb.Query("SELECT * FROM ApiKeys")
|
||||
rows, err := p.sqliteDb.Query("SELECT * FROM ApiKeys WHERE ApiKeys.Expiry == 0 OR ApiKeys.Expiry > ?", currentTime().Unix())
|
||||
helper.Check(err)
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
rowData := schemaApiKeys{}
|
||||
err = rows.Scan(&rowData.Id, &rowData.FriendlyName, &rowData.LastUsed, &rowData.Permissions)
|
||||
err = rows.Scan(&rowData.Id, &rowData.FriendlyName, &rowData.LastUsed, &rowData.Permissions, &rowData.Expiry, &rowData.IsSystemKey)
|
||||
helper.Check(err)
|
||||
result[rowData.Id] = models.ApiKey{
|
||||
Id: rowData.Id,
|
||||
FriendlyName: rowData.FriendlyName,
|
||||
LastUsed: rowData.LastUsed,
|
||||
Permissions: uint8(rowData.Permissions),
|
||||
Expiry: rowData.Expiry,
|
||||
IsSystemKey: rowData.IsSystemKey == 1,
|
||||
}
|
||||
}
|
||||
return result
|
||||
@@ -39,7 +43,7 @@ func (p DatabaseProvider) GetAllApiKeys() map[string]models.ApiKey {
|
||||
func (p DatabaseProvider) GetApiKey(id string) (models.ApiKey, bool) {
|
||||
var rowResult schemaApiKeys
|
||||
row := p.sqliteDb.QueryRow("SELECT * FROM ApiKeys WHERE Id = ?", id)
|
||||
err := row.Scan(&rowResult.Id, &rowResult.FriendlyName, &rowResult.LastUsed, &rowResult.Permissions)
|
||||
err := row.Scan(&rowResult.Id, &rowResult.FriendlyName, &rowResult.LastUsed, &rowResult.Permissions, &rowResult.Expiry, &rowResult.IsSystemKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return models.ApiKey{}, false
|
||||
@@ -53,15 +57,45 @@ func (p DatabaseProvider) GetApiKey(id string) (models.ApiKey, bool) {
|
||||
FriendlyName: rowResult.FriendlyName,
|
||||
LastUsed: rowResult.LastUsed,
|
||||
Permissions: uint8(rowResult.Permissions),
|
||||
Expiry: rowResult.Expiry,
|
||||
IsSystemKey: rowResult.IsSystemKey == 1,
|
||||
}
|
||||
|
||||
return result, true
|
||||
}
|
||||
|
||||
// GetSystemKey returns the latest UI API key
|
||||
func (p DatabaseProvider) GetSystemKey() (models.ApiKey, bool) {
|
||||
var rowResult schemaApiKeys
|
||||
row := p.sqliteDb.QueryRow("SELECT * FROM ApiKeys WHERE IsSystemKey = 1 ORDER BY Expiry DESC LIMIT 1")
|
||||
err := row.Scan(&rowResult.Id, &rowResult.FriendlyName, &rowResult.LastUsed, &rowResult.Permissions, &rowResult.Expiry, &rowResult.IsSystemKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return models.ApiKey{}, false
|
||||
}
|
||||
helper.Check(err)
|
||||
return models.ApiKey{}, false
|
||||
}
|
||||
|
||||
result := models.ApiKey{
|
||||
Id: rowResult.Id,
|
||||
FriendlyName: rowResult.FriendlyName,
|
||||
LastUsed: rowResult.LastUsed,
|
||||
Permissions: uint8(rowResult.Permissions),
|
||||
Expiry: rowResult.Expiry,
|
||||
IsSystemKey: rowResult.IsSystemKey == 1,
|
||||
}
|
||||
return result, true
|
||||
}
|
||||
|
||||
// SaveApiKey saves the API key to the database
|
||||
func (p DatabaseProvider) SaveApiKey(apikey models.ApiKey) {
|
||||
_, err := p.sqliteDb.Exec("INSERT OR REPLACE INTO ApiKeys (Id, FriendlyName, LastUsed, Permissions) VALUES (?, ?, ?, ?)",
|
||||
apikey.Id, apikey.FriendlyName, apikey.LastUsed, apikey.Permissions)
|
||||
isSystemKey := 0
|
||||
if apikey.IsSystemKey {
|
||||
isSystemKey = 1
|
||||
}
|
||||
_, err := p.sqliteDb.Exec("INSERT OR REPLACE INTO ApiKeys (Id, FriendlyName, LastUsed, Permissions, Expiry, IsSystemKey) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
apikey.Id, apikey.FriendlyName, apikey.LastUsed, apikey.Permissions, apikey.Expiry, isSystemKey)
|
||||
helper.Check(err)
|
||||
}
|
||||
|
||||
@@ -77,3 +111,8 @@ func (p DatabaseProvider) DeleteApiKey(id string) {
|
||||
_, err := p.sqliteDb.Exec("DELETE FROM ApiKeys WHERE Id = ?", id)
|
||||
helper.Check(err)
|
||||
}
|
||||
|
||||
func (p DatabaseProvider) cleanApiKeys() {
|
||||
_, err := p.sqliteDb.Exec("DELETE FROM ApiKeys WHERE ApiKeys.Expiry > 0 AND ApiKeys.Expiry < ?", currentTime().Unix())
|
||||
helper.Check(err)
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@ package setup
|
||||
|
||||
// protectedUrls contains a list of URLs that need to be protected if authentication is disabled.
|
||||
// This list will be displayed during the setup
|
||||
var protectedUrls = []string{"/admin", "/apiDelete", "/apiKeys", "/apiNew", "/delete", "/e2eInfo", "/e2eSetup", "/logs", "/uploadChunk", "/uploadComplete", "/uploadStatus"}
|
||||
var protectedUrls = []string{"/admin", "/apiKeys", "/e2eInfo", "/e2eSetup", "/logs", "/uploadChunk", "/uploadComplete", "/uploadStatus"}
|
||||
|
||||
@@ -18,18 +18,21 @@ const (
|
||||
// ApiPermNone means no permission granted
|
||||
const ApiPermNone = 0
|
||||
|
||||
// ApiPermAllNoApiMod means all permission granted, except ApiPermApiMod
|
||||
const ApiPermAllNoApiMod = 23
|
||||
|
||||
// ApiPermAll means all permission granted
|
||||
const ApiPermAll = 31
|
||||
|
||||
// ApiPermAllNoApiMod means all permission granted, except ApiPermApiMod
|
||||
// This is the default for new API keys that are created from the UI
|
||||
const ApiPermAllNoApiMod = ApiPermAll - ApiPermApiMod
|
||||
|
||||
// ApiKey contains data of a single api key
|
||||
type ApiKey struct {
|
||||
Id string `json:"Id" redis:"Id"`
|
||||
FriendlyName string `json:"FriendlyName" redis:"FriendlyName"`
|
||||
LastUsed int64 `json:"LastUsed" redis:"LastUsed"`
|
||||
Permissions uint8 `json:"Permissions" redis:"Permissions"`
|
||||
Expiry int64 `json:"Expiry" redis:"Expiry"` // Does not expire if 0
|
||||
IsSystemKey bool `json:"IsSystemKey" redis:"IsSystemKey"`
|
||||
}
|
||||
|
||||
func (key *ApiKey) GetReadableDate() string {
|
||||
|
||||
@@ -107,17 +107,16 @@ func (f *File) ToJsonResult(serverUrl string, includeFilename bool) string {
|
||||
if err != nil {
|
||||
return errorAsJson(err)
|
||||
}
|
||||
result := Result{
|
||||
|
||||
byteOutput, err := json.Marshal(Result{
|
||||
Result: "OK",
|
||||
IncludeFilename: includeFilename,
|
||||
FileInfo: info,
|
||||
}
|
||||
|
||||
bytes, err := json.Marshal(result)
|
||||
})
|
||||
if err != nil {
|
||||
return errorAsJson(err)
|
||||
}
|
||||
return string(bytes)
|
||||
return string(byteOutput)
|
||||
}
|
||||
|
||||
// RequiresClientDecryption checks if the file needs to be decrypted by the client
|
||||
|
||||
@@ -92,11 +92,8 @@ func Start() {
|
||||
|
||||
mux.HandleFunc("/admin", requireLogin(showAdminMenu, false))
|
||||
mux.HandleFunc("/api/", processApi)
|
||||
mux.HandleFunc("/apiDelete", requireLogin(deleteApiKey, false))
|
||||
mux.HandleFunc("/apiKeys", requireLogin(showApiAdmin, false))
|
||||
mux.HandleFunc("/apiNew", requireLogin(newApiKey, false))
|
||||
mux.HandleFunc("/d", showDownload)
|
||||
mux.HandleFunc("/delete", requireLogin(deleteFile, false))
|
||||
mux.HandleFunc("/downloadFile", downloadFile)
|
||||
mux.HandleFunc("/e2eInfo", requireLogin(e2eInfo, true))
|
||||
mux.HandleFunc("/e2eSetup", requireLogin(showE2ESetup, false))
|
||||
@@ -302,21 +299,6 @@ func showApiAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
helper.CheckIgnoreTimeout(err)
|
||||
}
|
||||
|
||||
// Handling of /apiNew
|
||||
func newApiKey(w http.ResponseWriter, r *http.Request) {
|
||||
api.NewKey(true)
|
||||
redirect(w, "apiKeys")
|
||||
}
|
||||
|
||||
// Handling of /apiDelete
|
||||
func deleteApiKey(w http.ResponseWriter, r *http.Request) {
|
||||
keys, ok := r.URL.Query()["id"]
|
||||
if ok {
|
||||
api.DeleteKey(keys[0])
|
||||
}
|
||||
redirect(w, "apiKeys")
|
||||
}
|
||||
|
||||
// Handling of /api/
|
||||
func processApi(w http.ResponseWriter, r *http.Request) {
|
||||
api.Process(w, r, configuration.Get().MaxMemory)
|
||||
@@ -505,17 +487,6 @@ func getE2eInfo(w http.ResponseWriter) {
|
||||
_, _ = w.Write(bytesE2e)
|
||||
}
|
||||
|
||||
// Handling of /delete
|
||||
// User needs to be admin. Deletes the requested file
|
||||
func deleteFile(w http.ResponseWriter, r *http.Request) {
|
||||
keyId := queryUrl(w, r, "admin")
|
||||
if keyId == "" {
|
||||
return
|
||||
}
|
||||
storage.DeleteFile(keyId, true)
|
||||
redirect(w, "admin")
|
||||
}
|
||||
|
||||
// Checks if a file is associated with the GET parameter from the current URL
|
||||
// Stops for 500ms to limit brute forcing if invalid key and redirects to redirectUrl
|
||||
func queryUrl(w http.ResponseWriter, r *http.Request, redirectUrl string) string {
|
||||
@@ -593,6 +564,7 @@ type UploadView struct {
|
||||
DefaultPassword string
|
||||
Logs string
|
||||
PublicName string
|
||||
SystemKey string
|
||||
IsAdminView bool
|
||||
IsDownloadView bool
|
||||
IsApiView bool
|
||||
@@ -678,6 +650,7 @@ func (u *UploadView) convertGlobalConfig(view int) *UploadView {
|
||||
u.MaxParallelUploads = config.MaxParallelUploads
|
||||
u.ChunkSize = config.ChunkSize
|
||||
u.IncludeFilename = config.IncludeFilename
|
||||
u.SystemKey = api.GetSystemKey()
|
||||
return u
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ package webserver
|
||||
import (
|
||||
"errors"
|
||||
"github.com/forceu/gokapi/internal/configuration"
|
||||
"github.com/forceu/gokapi/internal/configuration/database"
|
||||
"github.com/forceu/gokapi/internal/test"
|
||||
"github.com/forceu/gokapi/internal/test/testconfiguration"
|
||||
"github.com/forceu/gokapi/internal/webserver/authentication"
|
||||
@@ -428,37 +427,6 @@ func TestDownloadCorrectPassword(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteFileNonAuth(t *testing.T) {
|
||||
t.Parallel()
|
||||
test.HttpPageResult(t, test.HttpTestConfig{
|
||||
Url: "http://127.0.0.1:53843/delete?id=e4TjE7CokWK0giiLNxDL",
|
||||
IsHtml: true,
|
||||
RequiredContent: []string{"URL=./login"},
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteFileInvalidKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
test.HttpPageResult(t, test.HttpTestConfig{
|
||||
Url: "http://127.0.0.1:53843/delete",
|
||||
IsHtml: true,
|
||||
RequiredContent: []string{"URL=./admin"},
|
||||
Cookies: []test.Cookie{{
|
||||
Name: "session_token",
|
||||
Value: "validsession",
|
||||
}},
|
||||
})
|
||||
test.HttpPageResult(t, test.HttpTestConfig{
|
||||
Url: "http://127.0.0.1:53843/delete?id=",
|
||||
IsHtml: true,
|
||||
RequiredContent: []string{"URL=./admin"},
|
||||
Cookies: []test.Cookie{{
|
||||
Name: "session_token",
|
||||
Value: "validsession",
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
func TestPostUploadNoAuth(t *testing.T) {
|
||||
t.Parallel()
|
||||
test.HttpPostUploadRequest(t, test.HttpTestConfig{
|
||||
@@ -522,18 +490,6 @@ func TestPostUpload(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteFile(t *testing.T) {
|
||||
test.HttpPageResult(t, test.HttpTestConfig{
|
||||
Url: "http://127.0.0.1:53843/delete?id=e4TjE7CokWK0giiLNxDL",
|
||||
IsHtml: true,
|
||||
RequiredContent: []string{"URL=./admin"},
|
||||
Cookies: []test.Cookie{{
|
||||
Name: "session_token",
|
||||
Value: "validsession",
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
func TestApiPageAuthorized(t *testing.T) {
|
||||
t.Parallel()
|
||||
test.HttpPageResult(t, test.HttpTestConfig{
|
||||
@@ -560,78 +516,6 @@ func TestApiPageNotAuthorized(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewApiKey(t *testing.T) {
|
||||
// Authorised
|
||||
amountKeys := len(database.GetAllApiKeys())
|
||||
test.HttpPageResult(t, test.HttpTestConfig{
|
||||
Url: "http://127.0.0.1:53843/apiNew",
|
||||
IsHtml: true,
|
||||
RequiredContent: []string{"URL=./apiKeys"},
|
||||
ExcludedContent: []string{"URL=./login"},
|
||||
Cookies: []test.Cookie{{
|
||||
Name: "session_token",
|
||||
Value: "validsession",
|
||||
}},
|
||||
})
|
||||
amountKeysAfter := len(database.GetAllApiKeys())
|
||||
test.IsEqualInt(t, amountKeysAfter, amountKeys+1)
|
||||
test.IsEqualInt(t, amountKeysAfter, 5)
|
||||
|
||||
// Not authorised
|
||||
amountKeys++
|
||||
test.HttpPageResult(t, test.HttpTestConfig{
|
||||
Url: "http://127.0.0.1:53843/apiNew",
|
||||
IsHtml: true,
|
||||
RequiredContent: []string{"URL=./login"},
|
||||
ExcludedContent: []string{"URL=./apiKeys"},
|
||||
Cookies: []test.Cookie{{
|
||||
Name: "session_token",
|
||||
Value: "invalid",
|
||||
}},
|
||||
})
|
||||
amountKeysAfter = len(database.GetAllApiKeys())
|
||||
test.IsEqualInt(t, amountKeysAfter, amountKeys)
|
||||
test.IsEqualInt(t, amountKeysAfter, 5)
|
||||
}
|
||||
|
||||
func TestDeleteApiKey(t *testing.T) {
|
||||
// Not authorised
|
||||
amountKeys := len(database.GetAllApiKeys())
|
||||
test.HttpPageResult(t, test.HttpTestConfig{
|
||||
Url: "http://127.0.0.1:53843/apiDelete?id=jiREglQJW0bOqJakfjdVfe8T1EM8n8",
|
||||
IsHtml: true,
|
||||
RequiredContent: []string{"URL=./login"},
|
||||
ExcludedContent: []string{"URL=./apiKeys"},
|
||||
Cookies: []test.Cookie{{
|
||||
Name: "session_token",
|
||||
Value: "invalid",
|
||||
}},
|
||||
})
|
||||
amountKeysAfter := len(database.GetAllApiKeys())
|
||||
key, ok := database.GetApiKey("jiREglQJW0bOqJakfjdVfe8T1EM8n8")
|
||||
test.IsEqualBool(t, ok, true)
|
||||
test.IsEqualString(t, key.Id, "jiREglQJW0bOqJakfjdVfe8T1EM8n8")
|
||||
test.IsEqualInt(t, amountKeysAfter, amountKeys)
|
||||
test.IsEqualInt(t, amountKeysAfter, 5)
|
||||
|
||||
// Authorised
|
||||
test.HttpPageResult(t, test.HttpTestConfig{
|
||||
Url: "http://127.0.0.1:53843/apiDelete?id=jiREglQJW0bOqJakfjdVfe8T1EM8n8",
|
||||
IsHtml: true,
|
||||
RequiredContent: []string{"URL=./apiKeys"},
|
||||
ExcludedContent: []string{"URL=./login"},
|
||||
Cookies: []test.Cookie{{
|
||||
Name: "session_token",
|
||||
Value: "validsession",
|
||||
}},
|
||||
})
|
||||
amountKeysAfter = len(database.GetAllApiKeys())
|
||||
_, ok = database.GetApiKey("jiREglQJW0bOqJakfjdVfe8T1EM8n8")
|
||||
test.IsEqualBool(t, ok, false)
|
||||
test.IsEqualInt(t, amountKeysAfter, amountKeys-1)
|
||||
test.IsEqualInt(t, amountKeysAfter, 4)
|
||||
}
|
||||
|
||||
func TestProcessApi(t *testing.T) {
|
||||
// Not authorised
|
||||
test.HttpPageResult(t, test.HttpTestConfig{
|
||||
@@ -652,11 +536,12 @@ func TestProcessApi(t *testing.T) {
|
||||
Headers: []test.Header{{"apikey", "invalid"}},
|
||||
})
|
||||
|
||||
// Authorised
|
||||
// Valid session does not grant API access
|
||||
test.HttpPageResult(t, test.HttpTestConfig{
|
||||
Url: "http://127.0.0.1:53843/api/files/list",
|
||||
RequiredContent: []string{"smallfile2"},
|
||||
ExcludedContent: []string{"Unauthorized"},
|
||||
RequiredContent: []string{"{\"Result\":\"error\",\"ErrorMessage\":\"Unauthorized\"}"},
|
||||
ExcludedContent: []string{"smallfile2"},
|
||||
ResultCode: 401,
|
||||
Cookies: []test.Cookie{{
|
||||
Name: "session_token",
|
||||
Value: "validsession",
|
||||
|
||||
@@ -8,8 +8,6 @@ import (
|
||||
"github.com/forceu/gokapi/internal/helper"
|
||||
"github.com/forceu/gokapi/internal/models"
|
||||
"github.com/forceu/gokapi/internal/storage"
|
||||
"github.com/forceu/gokapi/internal/webserver/authentication"
|
||||
"github.com/forceu/gokapi/internal/webserver/authentication/sessionmanager"
|
||||
"github.com/forceu/gokapi/internal/webserver/fileupload"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -46,6 +44,8 @@ func Process(w http.ResponseWriter, r *http.Request, maxMemory int) {
|
||||
changeFriendlyName(w, request)
|
||||
case "/auth/modify":
|
||||
modifyApiPermission(w, request)
|
||||
case "/auth/delete":
|
||||
deleteApiKey(w, request)
|
||||
default:
|
||||
sendError(w, http.StatusBadRequest, "Invalid request")
|
||||
}
|
||||
@@ -122,6 +122,8 @@ func getApiPermissionRequired(requestUrl string) (uint8, bool) {
|
||||
return models.ApiPermApiMod, true
|
||||
case "/auth/modify":
|
||||
return models.ApiPermApiMod, true
|
||||
case "/auth/delete":
|
||||
return models.ApiPermApiMod, true
|
||||
default:
|
||||
return models.ApiPermNone, false
|
||||
}
|
||||
@@ -143,6 +145,8 @@ func NewKey(defaultPermissions bool) string {
|
||||
FriendlyName: "Unnamed key",
|
||||
LastUsed: 0,
|
||||
Permissions: models.ApiPermAllNoApiMod,
|
||||
Expiry: 0,
|
||||
IsSystemKey: false,
|
||||
}
|
||||
if !defaultPermissions {
|
||||
newKey.Permissions = models.ApiPermNone
|
||||
@@ -151,6 +155,38 @@ func NewKey(defaultPermissions bool) string {
|
||||
return newKey.Id
|
||||
}
|
||||
|
||||
// newSystemKey generates a new API key that is only used internally for the GUI
|
||||
// and will be valid for 48 hours
|
||||
func newSystemKey() string {
|
||||
newKey := models.ApiKey{
|
||||
Id: helper.GenerateRandomString(30),
|
||||
FriendlyName: "Internal System Key",
|
||||
LastUsed: 0,
|
||||
Permissions: models.ApiPermAll,
|
||||
Expiry: time.Now().Add(time.Hour * 48).Unix(),
|
||||
IsSystemKey: true,
|
||||
}
|
||||
database.SaveApiKey(newKey)
|
||||
return newKey.Id
|
||||
}
|
||||
|
||||
// GetSystemKey returns the latest System API key or generates a new one, if none exists or the current one expires
|
||||
// within the next 24 hours
|
||||
func GetSystemKey() string {
|
||||
key, ok := database.GetSystemKey()
|
||||
if !ok || key.Expiry < time.Now().Add(time.Hour*24).Unix() {
|
||||
return newSystemKey()
|
||||
}
|
||||
return key.Id
|
||||
}
|
||||
|
||||
func deleteApiKey(w http.ResponseWriter, request apiRequest) {
|
||||
if !isValidKeyForEditing(w, request) {
|
||||
return
|
||||
}
|
||||
DeleteKey(request.apiInfo.apiKeyToModify)
|
||||
}
|
||||
|
||||
func modifyApiPermission(w http.ResponseWriter, request apiRequest) {
|
||||
if !isValidKeyForEditing(w, request) {
|
||||
return
|
||||
@@ -184,7 +220,7 @@ func isValidKeyForEditing(w http.ResponseWriter, request apiRequest) bool {
|
||||
}
|
||||
|
||||
func createApiKey(w http.ResponseWriter, request apiRequest) {
|
||||
key := NewKey(false)
|
||||
key := NewKey(request.apiInfo.basicPermissions)
|
||||
output := models.ApiKeyOutput{
|
||||
Result: "OK",
|
||||
Id: key,
|
||||
@@ -283,7 +319,7 @@ func upload(w http.ResponseWriter, request apiRequest, maxMemory int) {
|
||||
}
|
||||
|
||||
request.request.Body = http.MaxBytesReader(w, request.request.Body, maxUpload)
|
||||
err := fileupload.Process(w, request.request, false, maxMemory)
|
||||
err := fileupload.Process(w, request.request, maxMemory)
|
||||
if err != nil {
|
||||
sendError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
@@ -329,10 +365,7 @@ func isAuthorisedForApi(w http.ResponseWriter, request apiRequest) bool {
|
||||
sendError(w, http.StatusBadRequest, "Invalid request")
|
||||
return false
|
||||
}
|
||||
config := configuration.Get()
|
||||
isOauth := config.Authentication.Method == authentication.OAuth2
|
||||
interval := config.Authentication.OAuthRecheckInterval
|
||||
if IsValidApiKey(request.apiKey, true, perm) || sessionmanager.IsValidSession(w, request.request, isOauth, interval) {
|
||||
if IsValidApiKey(request.apiKey, true, perm) {
|
||||
return true
|
||||
}
|
||||
sendError(w, http.StatusUnauthorized, "Unauthorized")
|
||||
@@ -384,10 +417,11 @@ type fileInfo struct {
|
||||
}
|
||||
|
||||
type apiInfo struct {
|
||||
friendlyName string
|
||||
apiKeyToModify string
|
||||
permission uint8
|
||||
grantPermission bool
|
||||
friendlyName string
|
||||
apiKeyToModify string
|
||||
permission uint8
|
||||
grantPermission bool
|
||||
basicPermissions bool
|
||||
}
|
||||
type filemodInfo struct {
|
||||
id string
|
||||
@@ -424,10 +458,12 @@ func parseRequest(r *http.Request) apiRequest {
|
||||
originalPassword: r.Header.Get("originalPassword") == "true",
|
||||
},
|
||||
apiInfo: apiInfo{
|
||||
friendlyName: r.Header.Get("friendlyName"),
|
||||
apiKeyToModify: r.Header.Get("apiKeyToModify"),
|
||||
permission: uint8(permission),
|
||||
grantPermission: r.Header.Get("permissionModifier") == "GRANT"},
|
||||
friendlyName: r.Header.Get("friendlyName"),
|
||||
apiKeyToModify: r.Header.Get("apiKeyToModify"),
|
||||
permission: uint8(permission),
|
||||
grantPermission: r.Header.Get("permissionModifier") == "GRANT",
|
||||
basicPermissions: r.Header.Get("basicPermissions") == "true",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,7 +530,7 @@ func IsValidApiKey(key string, modifyTime bool, requiredPermission uint8) bool {
|
||||
return false
|
||||
}
|
||||
savedKey, ok := database.GetApiKey(key)
|
||||
if ok && savedKey.Id != "" {
|
||||
if ok && savedKey.Id != "" && (savedKey.Expiry == 0 || savedKey.Expiry > time.Now().Unix()) {
|
||||
if modifyTime {
|
||||
savedKey.LastUsed = time.Now().Unix()
|
||||
database.UpdateTimeApiKey(savedKey)
|
||||
|
||||
@@ -30,8 +30,6 @@ func TestMain(m *testing.M) {
|
||||
os.Exit(exitVal)
|
||||
}
|
||||
|
||||
// TODO test new permission system
|
||||
|
||||
const maxMemory = 20
|
||||
|
||||
var newKeyId string
|
||||
@@ -62,22 +60,212 @@ func TestDeleteKey(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsValidApiKey(t *testing.T) {
|
||||
test.IsEqualBool(t, IsValidApiKey("", false, models.ApiPermNone), false) // TODO permission
|
||||
test.IsEqualBool(t, IsValidApiKey("invalid", false, models.ApiPermNone), false) // TODO permission
|
||||
test.IsEqualBool(t, IsValidApiKey("validkey", false, models.ApiPermNone), true) // TODO permission
|
||||
test.IsEqualBool(t, IsValidApiKey("", false, models.ApiPermNone), false)
|
||||
test.IsEqualBool(t, IsValidApiKey("invalid", false, models.ApiPermNone), false)
|
||||
test.IsEqualBool(t, IsValidApiKey("validkey", false, models.ApiPermNone), true)
|
||||
key, ok := database.GetApiKey("validkey")
|
||||
test.IsEqualBool(t, ok, true)
|
||||
test.IsEqualBool(t, key.LastUsed == 0, true)
|
||||
test.IsEqualBool(t, IsValidApiKey("validkey", true, models.ApiPermNone), true) // TODO permission
|
||||
test.IsEqualBool(t, IsValidApiKey("validkey", true, models.ApiPermNone), true)
|
||||
key, ok = database.GetApiKey("validkey")
|
||||
test.IsEqualBool(t, ok, true)
|
||||
test.IsEqualBool(t, key.LastUsed == 0, false)
|
||||
|
||||
newApiKey := NewKey(false)
|
||||
test.IsEqualBool(t, IsValidApiKey(newApiKey, true, models.ApiPermNone), true)
|
||||
for _, permission := range getAvailablePermissions(t) {
|
||||
test.IsEqualBool(t, IsValidApiKey(newApiKey, true, permission), false)
|
||||
}
|
||||
for _, newPermission := range getAvailablePermissions(t) {
|
||||
setPermissionApikey(newApiKey, newPermission, t)
|
||||
for _, permission := range getAvailablePermissions(t) {
|
||||
test.IsEqualBool(t, IsValidApiKey(newApiKey, true, permission), permission == newPermission)
|
||||
}
|
||||
}
|
||||
setPermissionApikey(newApiKey, models.ApiPermEdit|models.ApiPermDelete, t)
|
||||
test.IsEqualBool(t, IsValidApiKey(newApiKey, true, models.ApiPermEdit), true)
|
||||
test.IsEqualBool(t, IsValidApiKey(newApiKey, true, models.ApiPermAll), false)
|
||||
test.IsEqualBool(t, IsValidApiKey(newApiKey, true, models.ApiPermView), false)
|
||||
}
|
||||
|
||||
func setPermissionApikey(key string, newPermission uint8, t *testing.T) {
|
||||
apiKey, ok := database.GetApiKey(key)
|
||||
test.IsEqualBool(t, ok, true)
|
||||
apiKey.Permissions = newPermission
|
||||
database.SaveApiKey(apiKey)
|
||||
}
|
||||
|
||||
func getAvailablePermissions(t *testing.T) []uint8 {
|
||||
result := []uint8{models.ApiPermView, models.ApiPermUpload, models.ApiPermDelete, models.ApiPermApiMod, models.ApiPermEdit}
|
||||
sum := 0
|
||||
for _, perm := range result {
|
||||
sum = sum + int(perm)
|
||||
}
|
||||
if sum != models.ApiPermAll {
|
||||
t.Fatal("List of permissions are incorrect")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func TestGetSystemKey(t *testing.T) {
|
||||
keys := database.GetAllApiKeys()
|
||||
for _, key := range keys {
|
||||
if key.IsSystemKey {
|
||||
t.Error("No system key expected, but found")
|
||||
}
|
||||
}
|
||||
systemKey := GetSystemKey()
|
||||
retrievedSystemKey, ok := database.GetApiKey(systemKey)
|
||||
test.IsEqualBool(t, ok, true)
|
||||
test.IsEqualBool(t, retrievedSystemKey.IsSystemKey, true)
|
||||
test.IsEqualBool(t, retrievedSystemKey.Permissions == models.ApiPermAll, true)
|
||||
test.IsEqualBool(t, retrievedSystemKey.Expiry > time.Now().Add(time.Hour*47).Unix(), true)
|
||||
newKey := GetSystemKey()
|
||||
test.IsEqualBool(t, systemKey == newKey, true)
|
||||
retrievedSystemKey.Expiry = time.Now().Add(time.Hour * 23).Unix()
|
||||
database.SaveApiKey(retrievedSystemKey)
|
||||
newKey = GetSystemKey()
|
||||
test.IsEqualBool(t, systemKey != newKey, true)
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
database.SaveApiKey(models.ApiKey{
|
||||
Id: "toDelete",
|
||||
})
|
||||
_, ok := database.GetApiKey("toDelete")
|
||||
test.IsEqualBool(t, ok, true)
|
||||
|
||||
w, r := test.GetRecorder("GET", "/auth/delete", nil, []test.Header{{
|
||||
Name: "apikey",
|
||||
Value: "invalid",
|
||||
}}, nil)
|
||||
Process(w, r, maxMemory)
|
||||
test.ResponseBodyContains(t, w, "{\"Result\":\"error\",\"ErrorMessage\":\"Unauthorized\"}")
|
||||
w, r = test.GetRecorder("GET", "/auth/delete", nil, []test.Header{{
|
||||
Name: "apiKeyToModify",
|
||||
Value: "toDelete",
|
||||
}}, nil)
|
||||
Process(w, r, maxMemory)
|
||||
test.ResponseBodyContains(t, w, "{\"Result\":\"error\",\"ErrorMessage\":\"Unauthorized\"}")
|
||||
|
||||
w, r = test.GetRecorder("GET", "/auth/delete", nil, []test.Header{{
|
||||
Name: "apiKeyToModify",
|
||||
Value: "toDelete",
|
||||
}, {
|
||||
Name: "apikey",
|
||||
Value: getNewKeyWithPermissionMissing(t, models.ApiPermApiMod).Id,
|
||||
}}, nil)
|
||||
Process(w, r, maxMemory)
|
||||
test.ResponseBodyContains(t, w, "{\"Result\":\"error\",\"ErrorMessage\":\"Unauthorized\"}")
|
||||
|
||||
w, r = test.GetRecorder("GET", "/auth/delete", nil, []test.Header{{
|
||||
Name: "apiKeyToModify",
|
||||
Value: "toDelete",
|
||||
}, {
|
||||
Name: "apikey",
|
||||
Value: getNewKeyWithAllPermissions(t).Id,
|
||||
}}, nil)
|
||||
Process(w, r, maxMemory)
|
||||
test.IsEqualInt(t, w.Code, 200)
|
||||
_, ok = database.GetApiKey("toDelete")
|
||||
test.IsEqualBool(t, ok, false)
|
||||
|
||||
w, r = test.GetRecorder("GET", "/auth/delete", nil, []test.Header{{
|
||||
Name: "apiKeyToModify",
|
||||
Value: "toDelete",
|
||||
}, {
|
||||
Name: "apikey",
|
||||
Value: "validkey",
|
||||
}}, nil)
|
||||
Process(w, r, maxMemory)
|
||||
test.ResponseBodyContains(t, w, "{\"Result\":\"error\",\"ErrorMessage\":\"Invalid api key provided.\"}")
|
||||
}
|
||||
|
||||
func getNewKeyWithAllPermissions(t *testing.T) models.ApiKey {
|
||||
validKey, ok := database.GetApiKey(NewKey(false))
|
||||
test.IsEqualBool(t, ok, true)
|
||||
validKey.SetPermission(models.ApiPermAll)
|
||||
database.SaveApiKey(validKey)
|
||||
return validKey
|
||||
}
|
||||
|
||||
func getNewKeyWithPermissionMissing(t *testing.T, removePerm uint8) models.ApiKey {
|
||||
validKey, ok := database.GetApiKey(NewKey(false))
|
||||
test.IsEqualBool(t, ok, true)
|
||||
validKey.SetPermission(models.ApiPermAll)
|
||||
validKey.RemovePermission(removePerm)
|
||||
database.SaveApiKey(validKey)
|
||||
return validKey
|
||||
}
|
||||
|
||||
func countApiKeys() int {
|
||||
return len(database.GetAllApiKeys())
|
||||
}
|
||||
|
||||
func TestNewApiKey(t *testing.T) {
|
||||
keysBefore := countApiKeys()
|
||||
w, r := test.GetRecorder("GET", "/auth/create", nil, []test.Header{{
|
||||
Name: "apikey",
|
||||
Value: "invalid",
|
||||
}}, nil)
|
||||
Process(w, r, maxMemory)
|
||||
test.ResponseBodyContains(t, w, "{\"Result\":\"error\",\"ErrorMessage\":\"Unauthorized\"}")
|
||||
w, r = test.GetRecorder("GET", "/auth/create", nil, nil, nil)
|
||||
Process(w, r, maxMemory)
|
||||
test.ResponseBodyContains(t, w, "{\"Result\":\"error\",\"ErrorMessage\":\"Unauthorized\"}")
|
||||
|
||||
w, r = test.GetRecorder("GET", "/auth/create", nil, []test.Header{{
|
||||
Name: "apikey",
|
||||
Value: "validkey",
|
||||
}, {
|
||||
Name: "friendlyName",
|
||||
Value: "New Key",
|
||||
}}, nil)
|
||||
Process(w, r, maxMemory)
|
||||
test.IsEqualInt(t, w.Code, 200)
|
||||
keysAfter := countApiKeys()
|
||||
test.IsEqualInt(t, keysAfter, keysBefore+1)
|
||||
var result models.ApiKeyOutput
|
||||
err := json.Unmarshal(w.Body.Bytes(), &result)
|
||||
test.IsNil(t, err)
|
||||
|
||||
newKey, ok := database.GetApiKey(result.Id)
|
||||
test.IsEqualBool(t, ok, true)
|
||||
test.IsEqualString(t, newKey.FriendlyName, "New Key")
|
||||
|
||||
w, r = test.GetRecorder("GET", "/auth/create", nil, []test.Header{{
|
||||
Name: "apikey",
|
||||
Value: "validkey",
|
||||
}}, nil)
|
||||
Process(w, r, maxMemory)
|
||||
test.IsEqualInt(t, w.Code, 200)
|
||||
keysAfter = countApiKeys()
|
||||
test.IsEqualInt(t, keysAfter, keysBefore+2)
|
||||
err = json.Unmarshal(w.Body.Bytes(), &result)
|
||||
test.IsNil(t, err)
|
||||
|
||||
newKey, ok = database.GetApiKey(result.Id)
|
||||
test.IsEqualBool(t, ok, true)
|
||||
test.IsEqualString(t, newKey.FriendlyName, "Unnamed key")
|
||||
|
||||
w, r = test.GetRecorder("GET", "/auth/create", nil, []test.Header{{
|
||||
Name: "apikey",
|
||||
Value: getNewKeyWithPermissionMissing(t, models.ApiPermApiMod).Id,
|
||||
}}, nil)
|
||||
Process(w, r, maxMemory)
|
||||
test.ResponseBodyContains(t, w, "{\"Result\":\"error\",\"ErrorMessage\":\"Unauthorized\"}")
|
||||
}
|
||||
|
||||
func TestProcess(t *testing.T) {
|
||||
w, r := test.GetRecorder("GET", "/api/auth/friendlyname", nil, nil, nil)
|
||||
Process(w, r, maxMemory)
|
||||
test.ResponseBodyContains(t, w, "{\"Result\":\"error\",\"ErrorMessage\":\"Unauthorized\"}")
|
||||
w, r = test.GetRecorder("GET", "/api/auth/friendlyname", []test.Cookie{{
|
||||
Name: "session_token",
|
||||
Value: "validsession",
|
||||
}}, nil, nil)
|
||||
Process(w, r, maxMemory)
|
||||
test.ResponseBodyContains(t, w, "{\"Result\":\"error\",\"ErrorMessage\":\"Unauthorized\"}")
|
||||
w, r = test.GetRecorder("GET", "/api/invalid", nil, nil, nil)
|
||||
Process(w, r, maxMemory)
|
||||
test.ResponseBodyContains(t, w, "Invalid request")
|
||||
@@ -87,12 +275,6 @@ func TestProcess(t *testing.T) {
|
||||
}}, nil)
|
||||
Process(w, r, maxMemory)
|
||||
test.ResponseBodyContains(t, w, "Invalid request")
|
||||
w, r = test.GetRecorder("GET", "/api/invalid", []test.Cookie{{
|
||||
Name: "session_token",
|
||||
Value: "validsession",
|
||||
}}, nil, nil)
|
||||
Process(w, r, maxMemory)
|
||||
test.ResponseBodyContains(t, w, "Invalid request")
|
||||
}
|
||||
|
||||
func TestAuthDisabledLogin(t *testing.T) {
|
||||
@@ -134,6 +316,13 @@ func TestChangeFriendlyName(t *testing.T) {
|
||||
w = httptest.NewRecorder()
|
||||
Process(w, r, maxMemory)
|
||||
test.IsEqualInt(t, w.Code, 200)
|
||||
|
||||
w, r = test.GetRecorder("GET", "/api/auth/friendlyname", nil, []test.Header{{
|
||||
Name: "apikey", Value: getNewKeyWithPermissionMissing(t, models.ApiPermApiMod).Id}, {
|
||||
Name: "apiKeyToModify", Value: "validkey"}, {
|
||||
Name: "friendlyName", Value: "NewName2"}}, nil)
|
||||
Process(w, r, maxMemory)
|
||||
test.ResponseBodyContains(t, w, "{\"Result\":\"error\",\"ErrorMessage\":\"Unauthorized\"}")
|
||||
}
|
||||
|
||||
func TestDeleteFile(t *testing.T) {
|
||||
|
||||
@@ -13,13 +13,13 @@ import (
|
||||
)
|
||||
|
||||
// Process processes a file upload request
|
||||
func Process(w http.ResponseWriter, r *http.Request, isWeb bool, maxMemory int) error {
|
||||
func Process(w http.ResponseWriter, r *http.Request, maxMemory int) error {
|
||||
err := r.ParseMultipartForm(int64(maxMemory) * 1024 * 1024)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.MultipartForm.RemoveAll()
|
||||
config, err := parseConfig(r.Form, isWeb)
|
||||
config, err := parseConfig(r.Form, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -80,12 +80,12 @@ func TestParseConfig(t *testing.T) {
|
||||
|
||||
func TestProcess(t *testing.T) {
|
||||
w, r := test.GetRecorder("POST", "/upload", nil, nil, strings.NewReader("invalid§$%&%§"))
|
||||
err := Process(w, r, false, 20)
|
||||
err := Process(w, r, 20)
|
||||
test.IsNotNil(t, err)
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
r = getFileUploadRecorder(false)
|
||||
err = Process(w, r, false, 20)
|
||||
err = Process(w, r, 20)
|
||||
test.IsNil(t, err)
|
||||
resp := w.Result()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
{
|
||||
"apikey": ["VIEW","UPLOAD","DELETE", "API_MANAGE"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
{
|
||||
@@ -40,9 +37,6 @@
|
||||
{
|
||||
"apikey": ["VIEW"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
@@ -80,9 +74,6 @@
|
||||
{
|
||||
"apikey": ["UPLOAD"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
@@ -126,9 +117,6 @@
|
||||
{
|
||||
"apikey": ["UPLOAD"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
@@ -172,9 +160,6 @@
|
||||
{
|
||||
"apikey": ["UPLOAD"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
@@ -218,9 +203,6 @@
|
||||
{
|
||||
"apikey": ["VIEW","UPLOAD"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
@@ -264,9 +246,6 @@
|
||||
{
|
||||
"apikey": ["API_EDIT"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
@@ -346,9 +325,6 @@
|
||||
"security": [
|
||||
{
|
||||
"apikey": ["DELETE"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
@@ -383,14 +359,11 @@
|
||||
"auth"
|
||||
],
|
||||
"summary": "Creates a new API key",
|
||||
"description": "This API call returns a new API key. The new key does not have any permissions. Requires permission API_MOD",
|
||||
"description": "This API call returns a new API key. The new key does not have any permissions, unless specified. Requires permission API_MOD",
|
||||
"operationId": "create",
|
||||
"security": [
|
||||
{
|
||||
"apikey": ["API_MANAGE"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
@@ -404,6 +377,17 @@
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "basicPermissions",
|
||||
"in": "header",
|
||||
"description": "If true, basic permissions are automatically granted",
|
||||
"required": false,
|
||||
"style": "simple",
|
||||
"explode": false,
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -434,9 +418,6 @@
|
||||
"security": [
|
||||
{
|
||||
"apikey": ["API_MANAGE"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
@@ -487,9 +468,6 @@
|
||||
"security": [
|
||||
{
|
||||
"apikey": ["API_MANAGE"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
@@ -541,6 +519,45 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/delete": {
|
||||
"delete": {
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Deletes an API key",
|
||||
"description": "This API call deletes the given API key. Requires permission API_MOD",
|
||||
"operationId": "apidelete",
|
||||
"security": [
|
||||
{
|
||||
"apikey": ["API_MANAGE"]
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "apiKeyToModify",
|
||||
"in": "header",
|
||||
"description": "The API key to delete",
|
||||
"required": true,
|
||||
"style": "simple",
|
||||
"explode": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Operation successful"
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid ID supplied"
|
||||
},
|
||||
"401": {
|
||||
"description": "Invalid API key provided or not logged in as admin"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
@@ -812,11 +829,6 @@
|
||||
"type": "apiKey",
|
||||
"name": "apikey",
|
||||
"in": "header"
|
||||
},
|
||||
"session": {
|
||||
"type": "apiKey",
|
||||
"name": "session_token",
|
||||
"in": "cookie"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ a:hover {
|
||||
color: #7e7e7e;
|
||||
}
|
||||
.apiperm-processing {
|
||||
color: #929611;
|
||||
color: #e5eb00;
|
||||
}
|
||||
|
||||
.gokapi-dialog {
|
||||
|
||||
@@ -1 +1 @@
|
||||
.btn-secondary,.btn-secondary:hover,.btn-secondary:focus{color:#333;text-shadow:none}body{background:url(../../assets/background.jpg)no-repeat 50% fixed;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;display:-ms-flexbox;display:-webkit-box;display:flex;-ms-flex-pack:center;-webkit-box-pack:center;justify-content:center}td{vertical-align:middle;position:relative}a{color:inherit}a:hover{color:inherit;filter:brightness(80%)}.dropzone{background:#2f343a!important;color:#fff;border-radius:5px}.dropzone:hover{background:#33393f!important;color:#fff;border-radius:5px}.card{margin:0 auto;float:none;margin-bottom:10px;border:2px solid #33393f}.card-body{background-color:#212529;color:#ddd}.card-title{font-weight:900}.admin-input{text-align:center}.form-control:disabled{background:#bababa}.break{flex-basis:100%;height:0}.bd-placeholder-img{font-size:1.125rem;text-anchor:middle;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(min-width:768px){.bd-placeholder-img-lg{font-size:3.5rem}.break{flex-basis:0}}.masthead{margin-bottom:2rem}.masthead-brand{margin-bottom:0}.nav-masthead .nav-link{padding:.25rem 0;font-weight:700;color:rgba(255,255,255,.5);background-color:initial;border-bottom:.25rem solid transparent}.nav-masthead .nav-link:hover,.nav-masthead .nav-link:focus{border-bottom-color:rgba(255,255,255,.25)}.nav-masthead .nav-link+.nav-link{margin-left:1rem}.nav-masthead .active{color:#fff;border-bottom-color:#fff}#qroverlay{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.3)}#qrcode{position:absolute;top:50%;left:50%;margin-top:-105px;margin-left:-105px;width:210px;height:210px;border:5px solid #fff}.toastnotification{pointer-events:none;position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background-color:#333;color:#fff;padding:15px;border-radius:5px;box-shadow:0 2px 5px rgba(0,0,0,.3);opacity:0;transition:opacity .3s ease-in-out;z-index:9999}.toastnotification.show{opacity:1;pointer-events:auto}.apiperm-granted{cursor:pointer;color:#19b90e}.apiperm-notgranted{cursor:pointer;color:#7e7e7e}.apiperm-processing{color:#929611}.gokapi-dialog{background-color:#212529;color:#ddd}.filename{font-weight:700;font-size:14px;margin-bottom:5px}.upload-progress-container{display:flex;align-items:center}.upload-progress-bar{position:relative;height:10px;background-color:#eee;flex:1;margin-right:10px;border-radius:4px}.upload-progress-bar-progress{position:absolute;top:0;left:0;height:100%;background-color:#0a0;border-radius:4px;transition:width .2s ease-in-out}.upload-progress-info{font-size:12px}.us-container{margin-top:10px;margin-bottom:20px}.uploaderror{font-weight:700;color:red;margin-bottom:5px}.uploads-container{background-color:#2f343a;border:2px solid rgba(0,0,0,.3);border-radius:5px;margin-left:0;margin-right:0;max-width:none;visibility:hidden}
|
||||
.btn-secondary,.btn-secondary:hover,.btn-secondary:focus{color:#333;text-shadow:none}body{background:url(../../assets/background.jpg)no-repeat 50% fixed;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;display:-ms-flexbox;display:-webkit-box;display:flex;-ms-flex-pack:center;-webkit-box-pack:center;justify-content:center}td{vertical-align:middle;position:relative}a{color:inherit}a:hover{color:inherit;filter:brightness(80%)}.dropzone{background:#2f343a!important;color:#fff;border-radius:5px}.dropzone:hover{background:#33393f!important;color:#fff;border-radius:5px}.card{margin:0 auto;float:none;margin-bottom:10px;border:2px solid #33393f}.card-body{background-color:#212529;color:#ddd}.card-title{font-weight:900}.admin-input{text-align:center}.form-control:disabled{background:#bababa}.break{flex-basis:100%;height:0}.bd-placeholder-img{font-size:1.125rem;text-anchor:middle;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(min-width:768px){.bd-placeholder-img-lg{font-size:3.5rem}.break{flex-basis:0}}.masthead{margin-bottom:2rem}.masthead-brand{margin-bottom:0}.nav-masthead .nav-link{padding:.25rem 0;font-weight:700;color:rgba(255,255,255,.5);background-color:initial;border-bottom:.25rem solid transparent}.nav-masthead .nav-link:hover,.nav-masthead .nav-link:focus{border-bottom-color:rgba(255,255,255,.25)}.nav-masthead .nav-link+.nav-link{margin-left:1rem}.nav-masthead .active{color:#fff;border-bottom-color:#fff}#qroverlay{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.3)}#qrcode{position:absolute;top:50%;left:50%;margin-top:-105px;margin-left:-105px;width:210px;height:210px;border:5px solid #fff}.toastnotification{pointer-events:none;position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background-color:#333;color:#fff;padding:15px;border-radius:5px;box-shadow:0 2px 5px rgba(0,0,0,.3);opacity:0;transition:opacity .3s ease-in-out;z-index:9999}.toastnotification.show{opacity:1;pointer-events:auto}.apiperm-granted{cursor:pointer;color:#19b90e}.apiperm-notgranted{cursor:pointer;color:#7e7e7e}.apiperm-processing{color:#e5eb00}.gokapi-dialog{background-color:#212529;color:#ddd}.filename{font-weight:700;font-size:14px;margin-bottom:5px}.upload-progress-container{display:flex;align-items:center}.upload-progress-bar{position:relative;height:10px;background-color:#eee;flex:1;margin-right:10px;border-radius:4px}.upload-progress-bar-progress{position:absolute;top:0;left:0;height:100%;background-color:#0a0;border-radius:4px;transition:width .2s ease-in-out}.upload-progress-info{font-size:12px}.us-container{margin-top:10px;margin-bottom:20px}.uploaderror{font-weight:700;color:red;margin-bottom:5px}.uploads-container{background-color:#2f343a;border:2px solid rgba(0,0,0,.3);border-radius:5px;margin-left:0;margin-right:0;max-width:none;visibility:hidden}
|
||||
@@ -249,6 +249,7 @@ function editFile() {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'id': button.getAttribute('data-fileid'),
|
||||
'apikey': systemKey,
|
||||
'allowedDownloads': allowedDownloads,
|
||||
'expiryTimestamp': expiryTimestamp,
|
||||
'password': password,
|
||||
@@ -393,6 +394,7 @@ function changeApiPermission(apiKey, permission, buttonId) {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'apikey': systemKey,
|
||||
'apiKeyToModify': apiKey,
|
||||
'permission': permission,
|
||||
'permissionModifier': modifier
|
||||
@@ -400,7 +402,6 @@ function changeApiPermission(apiKey, permission, buttonId) {
|
||||
},
|
||||
};
|
||||
|
||||
// Send the request
|
||||
fetch(apiUrl, requestOptions)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
@@ -427,6 +428,93 @@ function changeApiPermission(apiKey, permission, buttonId) {
|
||||
});
|
||||
}
|
||||
|
||||
function deleteApiKey(apiKey) {
|
||||
|
||||
document.getElementById("delete-"+apiKey).disabled = true;
|
||||
var apiUrl = './api/auth/delete';
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'apikey': systemKey,
|
||||
'apiKeyToModify': apiKey,
|
||||
},
|
||||
};
|
||||
|
||||
fetch(apiUrl, requestOptions)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Request failed with status: ${response.status}`);
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
document.getElementById("row-"+apiKey).remove();
|
||||
})
|
||||
.catch(error => {
|
||||
alert("Unable to delete API key: " + error);
|
||||
console.error('Error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function newApiKey() {
|
||||
|
||||
document.getElementById("button-newapi").disabled = true;
|
||||
var apiUrl = './api/auth/create';
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'apikey': systemKey,
|
||||
'basicPermissions': 'true'
|
||||
},
|
||||
};
|
||||
|
||||
fetch(apiUrl, requestOptions)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Request failed with status: ${response.status}`);
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
location.reload();
|
||||
})
|
||||
.catch(error => {
|
||||
alert("Unable to create API key: " + error);
|
||||
console.error('Error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function deleteFile(id) {
|
||||
|
||||
document.getElementById("button-delete-"+id).disabled = true;
|
||||
var apiUrl = './api/files/delete';
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'apikey': systemKey,
|
||||
'id': id
|
||||
},
|
||||
};
|
||||
|
||||
fetch(apiUrl, requestOptions)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Request failed with status: ${response.status}`);
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
location.reload();
|
||||
})
|
||||
.catch(error => {
|
||||
alert("Unable to delete file: " + error);
|
||||
console.error('Error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function checkBoxChanged(checkBox, correspondingInput) {
|
||||
let disable = !checkBox.checked;
|
||||
@@ -550,6 +638,7 @@ function addRow(jsonText) {
|
||||
let item = jsonObject.FileInfo;
|
||||
let table = document.getElementById("downloadtable");
|
||||
let row = table.insertRow(0);
|
||||
row.id = "row-"+ item.Id;
|
||||
let cellFilename = row.insertCell(0);
|
||||
let cellFileSize = row.insertCell(1);
|
||||
let cellRemainingDownloads = row.insertCell(2);
|
||||
@@ -586,7 +675,7 @@ function addRow(jsonText) {
|
||||
}
|
||||
buttons = buttons + '<button type="button" id="qrcode-'+item.Id+'" title="QR Code" class="btn btn-outline-light btn-sm" onclick="showQrCode(\'' + item.UrlDownload + '\');"><i class="bi bi-qr-code"></i></button> ';
|
||||
buttons = buttons + '<button type="button" title="Edit" class="btn btn-outline-light btn-sm" onclick="showEditModal(\'' + item.Name + '\',\'' + item.Id + '\', ' + item.DownloadsRemaining + ', ' + item.ExpireAt + ', ' + item.IsPasswordProtected + ', ' + item.UnlimitedDownloads + ', ' + item.UnlimitedTime + ');"><i class="bi bi-pencil"></i></button> ';
|
||||
buttons = buttons + '<button type="button" title="Delete" class="btn btn-outline-danger btn-sm" onclick="window.location=\'./delete?id=' + item.Id + '\'"><i class="bi bi-trash3"></i></button>';
|
||||
buttons = buttons + '<button type="button" id="button-delete-' + item.Id + '" title="Delete" class="btn btn-outline-danger btn-sm" onclick="deleteFile(\'' + item.Id + '\')"><i class="bi bi-trash3"></i></button>';
|
||||
|
||||
cellButtons.innerHTML = buttons;
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
1
internal/webserver/web/static/js/min/admin.min.6.js
Normal file
1
internal/webserver/web/static/js/min/admin.min.6.js
Normal file
File diff suppressed because one or more lines are too long
@@ -64,7 +64,7 @@
|
||||
{{ range .Items }}
|
||||
{{ if or (gt .ExpireAt $.TimeNow) (.UnlimitedTime) }}
|
||||
{{ if or (gt .DownloadsRemaining 0) (.UnlimitedDownloads) }}
|
||||
<tr>
|
||||
<tr id="row-{{ .Id }}">
|
||||
<td id="cell-name-{{ .Id }}">{{ .Name }}</td>
|
||||
<td data-order="{{ .SizeBytes }}">{{ .Size }}</td>
|
||||
{{ if .UnlimitedDownloads }}
|
||||
@@ -87,7 +87,7 @@
|
||||
{{ end }}
|
||||
<button type="button" id="qrcode-{{ .Id }}" title="QR Code" class="btn btn-outline-light btn-sm" onclick="showQrCode('{{ .UrlDownload }}');"><i class="bi bi-qr-code"></i></button>
|
||||
<button type="button" title="Edit" class="btn btn-outline-light btn-sm" onclick="showEditModal('{{.Name }}','{{.Id}}', {{.DownloadsRemaining }}, {{.ExpireAt }}, {{.IsPasswordProtected}}, {{.UnlimitedDownloads }}, {{.UnlimitedTime}});"><i class="bi bi-pencil"></i></button>
|
||||
<button type="button" title="Delete" class="btn btn-outline-danger btn-sm" onclick="window.location='./delete?id={{ .Id }}'"><i class="bi bi-trash3"></i></button></td>
|
||||
<button id="button-delete-{{ .Id }}" type="button" title="Delete" class="btn btn-outline-danger btn-sm" onclick="deleteFile('{{ .Id }}')"><i class="bi bi-trash3"></i></button></td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
@@ -176,6 +176,7 @@
|
||||
});
|
||||
});
|
||||
registerChangeHandler();
|
||||
var systemKey = "{{.SystemKey}}";
|
||||
</script>
|
||||
|
||||
{{ if .EndToEndEncryption }}
|
||||
|
||||
@@ -18,12 +18,13 @@
|
||||
<th scope="col">Last Used</th>
|
||||
<th scope="col">Permissions</th>
|
||||
<th scope="col">Actions</th>
|
||||
<th scope="col"><button type="button" class="btn btn-outline-light btn-sm" onclick="window.location='./apiNew'"><i class="bi bi-plus-circle-fill"></i> New Key</button></th>
|
||||
<th scope="col"><button id="button-newapi" type="button" class="btn btn-outline-light btn-sm" onclick="newApiKey()"><i class="bi bi-plus-circle-fill"></i> New Key</button></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range .ApiKeys }}
|
||||
<tr>
|
||||
{{ if not .IsSystemKey }}
|
||||
<tr id="row-{{ .Id }}">
|
||||
<td scope="col" id="{{ .Id }}" class="apiname">{{ .FriendlyName }}</td>
|
||||
<td scope="col">{{ .Id }}</td>
|
||||
<td scope="col">{{ .GetReadableDate }}</td>
|
||||
@@ -31,19 +32,21 @@
|
||||
<i id="perm_view_{{ .Id }}" class="bi bi-eye {{if not .HasPermissionView}}apiperm-notgranted{{else}}apiperm-granted{{end}}" title="List Uploads" onclick='changeApiPermission("{{ .Id }}","PERM_VIEW", "perm_view_{{ .Id }}");'></i>
|
||||
<i id="perm_upload_{{ .Id }}" class="bi bi-file-earmark-arrow-up {{if not .HasPermissionUpload}}apiperm-notgranted{{else}}apiperm-granted{{end}}" title="Upload" onclick='changeApiPermission("{{ .Id }}","PERM_UPLOAD", "perm_upload_{{ .Id }}");'></i>
|
||||
<i id="perm_edit_{{ .Id }}" class="bi bi-pencil {{if not .HasPermissionEdit}}apiperm-notgranted{{else}}apiperm-granted{{end}}" title="Edit Uploads" onclick='changeApiPermission("{{ .Id }}","PERM_EDIT", "perm_edit_{{ .Id }}");'></i>
|
||||
<i id="perm_delete_{{ .Id }}" class="bi bi-trash3 {{if not .HasPermissionDelete}}apiperm-notgranted{{else}}apiperm-granted{{end}}" title="Delete" onclick='changeApiPermission("{{ .Id }}","PERM_DELETE", "perm_delete_{{ .Id }}");'></i>
|
||||
<i id="perm_delete_{{ .Id }}" class="bi bi-trash3 {{if not .HasPermissionDelete}}apiperm-notgranted{{else}}apiperm-granted{{end}}" title="Delete Uploads" onclick='changeApiPermission("{{ .Id }}","PERM_DELETE", "perm_delete_{{ .Id }}");'></i>
|
||||
<i id="perm_api_{{ .Id }}" class="bi bi-sliders2 {{if not .HasPermissionApiMod}}apiperm-notgranted{{else}}apiperm-granted{{end}}" title="Manage API Keys" onclick='changeApiPermission("{{ .Id }}","PERM_API_MOD", "perm_api_{{ .Id }}");'></i>
|
||||
|
||||
</td>
|
||||
<td scope="col"><button type="button" data-clipboard-text="{{ .Id }}" title="Copy API Key" class="copyurl btn btn-outline-light btn-sm"><i class="bi bi-copy"></i></button> <button type="button" class="btn btn-outline-danger btn-sm" onclick="window.location='./apiDelete?id={{ .Id }}'" title="Delete"><i class="bi bi-trash3"></i></button></td>
|
||||
<td scope="col"><button type="button" data-clipboard-text="{{ .Id }}" onclick="showToast()" title="Copy API Key" class="copyurl btn btn-outline-light btn-sm"><i class="bi bi-copy"></i></button> <button id="delete-{{ .Id }}" type="button" class="btn btn-outline-danger btn-sm" onclick="deleteApiKey('{{ .Id }}')" title="Delete"><i class="bi bi-trash3"></i></button></td>
|
||||
<td scope="col"></td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="toastnotification" class="toastnotification">API key copied to clipboard</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="./js/min/admin.min.{{ template "js_admin_version"}}.js"></script>
|
||||
@@ -69,6 +72,7 @@
|
||||
xmlhttp.open("GET", "./api/auth/friendlyname");
|
||||
xmlhttp.setRequestHeader("apiKeyToModify", row.id);
|
||||
xmlhttp.setRequestHeader("friendlyName", val);
|
||||
xmlhttp.setRequestHeader("apikey", systemKey);
|
||||
xmlhttp.send();
|
||||
row.classList.remove("isBeingEdited");
|
||||
|
||||
@@ -88,6 +92,7 @@
|
||||
input.focus();
|
||||
}
|
||||
});
|
||||
var systemKey = "{{.SystemKey}}";
|
||||
</script>
|
||||
{{ template "footer" true }}
|
||||
{{ end }}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
// Specifies the version of JS files, so that the browser doesn't
|
||||
// use a cached version, if the file has been updated
|
||||
{{define "js_admin_version"}}5{{end}}
|
||||
{{define "js_admin_version"}}6{{end}}
|
||||
{{define "js_dropzone_version"}}4{{end}}
|
||||
{{define "js_e2eversion"}}3{{end}}
|
||||
{{define "css_main"}}2{{end}}
|
||||
90
openapi.json
90
openapi.json
@@ -15,9 +15,6 @@
|
||||
{
|
||||
"apikey": ["VIEW","UPLOAD","DELETE", "API_MANAGE"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
{
|
||||
@@ -40,9 +37,6 @@
|
||||
{
|
||||
"apikey": ["VIEW"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
@@ -80,9 +74,6 @@
|
||||
{
|
||||
"apikey": ["UPLOAD"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
@@ -126,9 +117,6 @@
|
||||
{
|
||||
"apikey": ["UPLOAD"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
@@ -172,9 +160,6 @@
|
||||
{
|
||||
"apikey": ["UPLOAD"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
@@ -218,9 +203,6 @@
|
||||
{
|
||||
"apikey": ["VIEW","UPLOAD"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
@@ -264,9 +246,6 @@
|
||||
{
|
||||
"apikey": ["API_EDIT"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
@@ -346,9 +325,6 @@
|
||||
"security": [
|
||||
{
|
||||
"apikey": ["DELETE"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
@@ -383,14 +359,11 @@
|
||||
"auth"
|
||||
],
|
||||
"summary": "Creates a new API key",
|
||||
"description": "This API call returns a new API key. The new key does not have any permissions. Requires permission API_MOD",
|
||||
"description": "This API call returns a new API key. The new key does not have any permissions, unless specified. Requires permission API_MOD",
|
||||
"operationId": "create",
|
||||
"security": [
|
||||
{
|
||||
"apikey": ["API_MANAGE"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
@@ -404,6 +377,17 @@
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "basicPermissions",
|
||||
"in": "header",
|
||||
"description": "If true, basic permissions are automatically granted",
|
||||
"required": false,
|
||||
"style": "simple",
|
||||
"explode": false,
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -434,9 +418,6 @@
|
||||
"security": [
|
||||
{
|
||||
"apikey": ["API_MANAGE"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
@@ -487,9 +468,6 @@
|
||||
"security": [
|
||||
{
|
||||
"apikey": ["API_MANAGE"]
|
||||
},
|
||||
{
|
||||
"session": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
@@ -541,6 +519,45 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/delete": {
|
||||
"delete": {
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Deletes an API key",
|
||||
"description": "This API call deletes the given API key. Requires permission API_MOD",
|
||||
"operationId": "apidelete",
|
||||
"security": [
|
||||
{
|
||||
"apikey": ["API_MANAGE"]
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "apiKeyToModify",
|
||||
"in": "header",
|
||||
"description": "The API key to delete",
|
||||
"required": true,
|
||||
"style": "simple",
|
||||
"explode": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Operation successful"
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid ID supplied"
|
||||
},
|
||||
"401": {
|
||||
"description": "Invalid API key provided or not logged in as admin"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
@@ -812,11 +829,6 @@
|
||||
"type": "apiKey",
|
||||
"name": "apikey",
|
||||
"in": "header"
|
||||
},
|
||||
"session": {
|
||||
"type": "apiKey",
|
||||
"name": "session_token",
|
||||
"in": "cookie"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user