mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-06 08:22:24 +01:00
feature (rest): setup for rest api
This commit is contained in:
parent
d1a2c7a3ea
commit
952f45097e
8 changed files with 145 additions and 44 deletions
12
server/common/api.go
Normal file
12
server/common/api.go
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func IsApiKeyValid(api_key string) bool {
|
||||
if api_key == os.Getenv("API_KEY") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -85,9 +85,10 @@ func NewKeyValueStore() KeyValueStore {
|
|||
}
|
||||
|
||||
func (this *KeyValueStore) Get(key string) interface{} {
|
||||
var val interface{}
|
||||
this.RLock()
|
||||
defer this.RUnlock()
|
||||
val := this.cache[key]
|
||||
val = this.cache[key]
|
||||
this.RUnlock()
|
||||
return val
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,21 +6,22 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
APP_VERSION = "v0.5"
|
||||
LOG_PATH = "data/state/log/"
|
||||
CONFIG_PATH = "data/state/config/"
|
||||
DB_PATH = "data/state/db/"
|
||||
FTS_PATH = "data/state/search/"
|
||||
CERT_PATH = "data/state/certs/"
|
||||
TMP_PATH = "data/cache/tmp/"
|
||||
COOKIE_NAME_AUTH = "auth"
|
||||
COOKIE_NAME_PROOF = "proof"
|
||||
COOKIE_NAME_ADMIN = "admin"
|
||||
COOKIE_PATH_ADMIN = "/admin/api/"
|
||||
COOKIE_PATH = "/api/"
|
||||
FILE_INDEX = "./data/public/index.html"
|
||||
FILE_ASSETS = "./data/public/"
|
||||
URL_SETUP = "/admin/setup"
|
||||
APP_VERSION = "v0.5"
|
||||
LOG_PATH = "data/state/log/"
|
||||
CONFIG_PATH = "data/state/config/"
|
||||
DB_PATH = "data/state/db/"
|
||||
FTS_PATH = "data/state/search/"
|
||||
CERT_PATH = "data/state/certs/"
|
||||
TMP_PATH = "data/cache/tmp/"
|
||||
COOKIE_NAME_AUTH = "auth"
|
||||
COOKIE_NAME_PROOF = "proof"
|
||||
COOKIE_NAME_ADMIN = "admin"
|
||||
COOKIE_PATH_ADMIN = "/admin/api/"
|
||||
COOKIE_PATH = "/api/"
|
||||
FILE_INDEX = "./data/public/index.html"
|
||||
FILE_ASSETS = "./data/public/"
|
||||
URL_SETUP = "/admin/setup"
|
||||
EXPIRATION_API_TOKEN = 3600
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
|||
|
|
@ -90,6 +90,12 @@ func SendErrorResult(res http.ResponseWriter, err error) {
|
|||
encoder.Encode(APIErrorMessage{"error", m})
|
||||
}
|
||||
|
||||
func SendRaw(res http.ResponseWriter, data interface{}) {
|
||||
encoder := json.NewEncoder(res)
|
||||
encoder.SetEscapeHTML(false)
|
||||
encoder.Encode(data)
|
||||
}
|
||||
|
||||
func Page(stuff string) string {
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
|
|
|
|||
|
|
@ -128,6 +128,58 @@ func SessionAuthenticate(ctx *App, res http.ResponseWriter, req *http.Request) {
|
|||
SendSuccessResult(res, nil)
|
||||
}
|
||||
|
||||
func SessionAuthenticateExternal(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
api_key := string(req.URL.Query().Get("key"))
|
||||
if api_key == "" {
|
||||
SendErrorResult(res, NewError(fmt.Sprintf(
|
||||
"You need to provide your API key in the request URL (e.g.: '%s?key=foobar'). See https://www.filestash.app/docs/api/#authentication for details, or we can help at support@filestash.app",
|
||||
req.URL.Path,
|
||||
), 403))
|
||||
return
|
||||
} else if IsApiKeyValid(api_key) == false {
|
||||
SendErrorResult(res, NewError(fmt.Sprintf(
|
||||
"Invalid API Key provided: %s",
|
||||
api_key,
|
||||
), 401))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Body["timestamp"] = time.Now().Format(time.RFC3339)
|
||||
ctx.Body["api_key"] = api_key
|
||||
session := model.MapStringInterfaceToMapStringString(ctx.Body)
|
||||
session["path"] = EnforceDirectory(session["path"])
|
||||
if _, err := model.NewBackend(ctx, session); err != nil {
|
||||
Log.Debug("session::auth_external 'NewBackend' %+v", err)
|
||||
SendErrorResult(res, err)
|
||||
return
|
||||
}
|
||||
s, err := json.Marshal(session)
|
||||
if err != nil {
|
||||
Log.Debug("session::auth_external 'Marshal' %+v", err)
|
||||
SendErrorResult(res, NewError(err.Error(), 500))
|
||||
return
|
||||
}
|
||||
obfuscate, err := EncryptString(SECRET_KEY_DERIVATE_FOR_USER, string(s))
|
||||
if err != nil {
|
||||
Log.Debug("session::auth_external 'Encryption' %+v", err)
|
||||
SendErrorResult(res, NewError(err.Error(), 500))
|
||||
return
|
||||
}
|
||||
|
||||
SendRaw(res, struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
Version string `json:"version"`
|
||||
License string `json:"license"`
|
||||
ApiDoc string `json:"doc"`
|
||||
}{
|
||||
obfuscate, "bearer",
|
||||
EXPIRATION_API_TOKEN, "Filestash " + APP_VERSION + "." + BUILD_DATE,
|
||||
LICENSE, "https://www.filestash.app/docs/api/",
|
||||
})
|
||||
}
|
||||
|
||||
func SessionLogout(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
go func() {
|
||||
// user typically expect the logout to feel instant but in our case we still need to make sure
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ func Init(a App) {
|
|||
middlewares = []Middleware{ApiHeaders, SecureHeaders}
|
||||
session.HandleFunc("/auth/{service}", NewMiddlewareChain(SessionOAuthBackend, middlewares, a)).Methods("GET")
|
||||
session.HandleFunc("/auth/", NewMiddlewareChain(SessionAuthMiddleware, middlewares, a)).Methods("GET", "POST")
|
||||
middlewares = []Middleware{ApiHeaders, BodyParser}
|
||||
r.HandleFunc("/api/token", NewMiddlewareChain(SessionAuthenticateExternal, middlewares, a)).Methods("POST")
|
||||
|
||||
// API for Admin Console
|
||||
middlewares = []Middleware{ApiHeaders, SecureAjax}
|
||||
|
|
@ -62,7 +64,7 @@ func Init(a App) {
|
|||
files.HandleFunc("/zip", NewMiddlewareChain(FileDownloader, middlewares, a)).Methods("GET")
|
||||
middlewares = []Middleware{ApiHeaders, SecureHeaders, SecureAjax, SessionStart, LoggedInOnly}
|
||||
files.HandleFunc("/cat", NewMiddlewareChain(FileAccess, middlewares, a)).Methods("OPTIONS")
|
||||
files.HandleFunc("/cat", NewMiddlewareChain(FileSave, middlewares, a)).Methods("POST")
|
||||
files.HandleFunc("/cat", NewMiddlewareChain(FileSave, middlewares, a)).Methods("POST", "PUT")
|
||||
files.HandleFunc("/ls", NewMiddlewareChain(FileLs, middlewares, a)).Methods("GET")
|
||||
files.HandleFunc("/mv", NewMiddlewareChain(FileMv, middlewares, a)).Methods("GET")
|
||||
files.HandleFunc("/rm", NewMiddlewareChain(FileRm, middlewares, a)).Methods("GET")
|
||||
|
|
|
|||
|
|
@ -80,11 +80,14 @@ func SecureHeaders(fn func(*App, http.ResponseWriter, *http.Request)) func(ctx *
|
|||
|
||||
func SecureAjax(fn func(*App, http.ResponseWriter, *http.Request)) func(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
return func(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
if req.Header.Get("X-Requested-With") != "XmlHttpRequest" {
|
||||
Log.Warning("Intrusion detection: %s - %s", req.RemoteAddr, req.URL.String())
|
||||
SendErrorResult(res, ErrNotAllowed)
|
||||
if req.Header.Get("Authorization") != "" {
|
||||
fn(ctx, res, req)
|
||||
return
|
||||
} else if req.Header.Get("X-Requested-With") == "XmlHttpRequest" {
|
||||
fn(ctx, res, req)
|
||||
return
|
||||
}
|
||||
fn(ctx, res, req)
|
||||
Log.Warning("Intrusion detection: %s - %s", req.RemoteAddr, req.URL.String())
|
||||
SendErrorResult(res, ErrNotAllowed)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func LoggedInOnly(fn func(*App, http.ResponseWriter, *http.Request)) func(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
|
|
@ -239,7 +240,7 @@ func _extractSession(req *http.Request, ctx *App) (map[string]string, error) {
|
|||
var err error
|
||||
var session map[string]string = make(map[string]string)
|
||||
|
||||
if ctx.Share.Id != "" {
|
||||
if ctx.Share.Id != "" { // Shared link
|
||||
str, err = DecryptString(SECRET_KEY_DERIVATE_FOR_USER, ctx.Share.Auth)
|
||||
if err != nil {
|
||||
// This typically happen when changing the secret key
|
||||
|
|
@ -262,28 +263,51 @@ func _extractSession(req *http.Request, ctx *App) (map[string]string, error) {
|
|||
session["path"] = strings.TrimSuffix(ctx.Share.Path, path) + "/"
|
||||
}
|
||||
return session, err
|
||||
} else {
|
||||
str := ""
|
||||
index := 0
|
||||
for {
|
||||
cookie, err := req.Cookie(CookieName(index))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
index++
|
||||
str += cookie.Value
|
||||
}
|
||||
if str == "" {
|
||||
return session, nil
|
||||
}
|
||||
str, err = DecryptString(SECRET_KEY_DERIVATE_FOR_USER, str)
|
||||
if err != nil {
|
||||
// This typically happen when changing the secret key
|
||||
return session, nil
|
||||
}
|
||||
err = json.Unmarshal([]byte(str), &session)
|
||||
return session, err
|
||||
}
|
||||
|
||||
authHeader := req.Header.Get("Authorization")
|
||||
if authHeader != "" && strings.HasPrefix(authHeader, "Bearer ") { // API request
|
||||
bearer := strings.TrimPrefix(req.Header.Get("Authorization"), "Bearer ")
|
||||
str, err = DecryptString(SECRET_KEY_DERIVATE_FOR_USER, bearer)
|
||||
if err != nil {
|
||||
return session, nil
|
||||
}
|
||||
if err = json.Unmarshal([]byte(str), &session); err != nil {
|
||||
return session, err
|
||||
}
|
||||
t, err := time.Parse(time.RFC3339, session["timestamp"])
|
||||
if err != nil {
|
||||
return session, err
|
||||
}
|
||||
if IsApiKeyValid(session["api_key"]) == false {
|
||||
Log.Warning("attempt to use a non valid api key %s", session["api_key"])
|
||||
return session, ErrNotValid
|
||||
} else if t.Add(EXPIRATION_API_TOKEN * time.Second).Before(time.Now()) {
|
||||
return session, NewError("Access Token has expired", 401)
|
||||
}
|
||||
return session, nil
|
||||
}
|
||||
|
||||
str = ""
|
||||
index := 0
|
||||
for {
|
||||
cookie, err := req.Cookie(CookieName(index))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
index++
|
||||
str += cookie.Value
|
||||
}
|
||||
if str == "" {
|
||||
return session, nil
|
||||
}
|
||||
str, err = DecryptString(SECRET_KEY_DERIVATE_FOR_USER, str)
|
||||
if err != nil {
|
||||
// This typically happen when changing the secret key
|
||||
return session, nil
|
||||
}
|
||||
err = json.Unmarshal([]byte(str), &session)
|
||||
return session, err
|
||||
}
|
||||
|
||||
func _extractBackend(req *http.Request, ctx *App) (IBackend, error) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue