feat(admin): implement full admin dashboard structure
This commit is contained in:
102
lib/server/admin.go
Normal file
102
lib/server/admin.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"warpbox/lib/config"
|
||||
)
|
||||
|
||||
const adminSessionCookie = "warpbox_admin_session"
|
||||
const adminSessionMarker = "1"
|
||||
|
||||
func (app *App) adminLoginEnabled() bool {
|
||||
return app.config.AdminLoginEnabled(app.config.AdminPassword != "")
|
||||
}
|
||||
|
||||
func (app *App) adminAuthMiddleware(ctx *gin.Context) {
|
||||
if !app.adminLoginEnabled() {
|
||||
ctx.Redirect(http.StatusSeeOther, "/")
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
token, err := ctx.Cookie(adminSessionCookie)
|
||||
if err != nil || token != app.adminSessionToken() {
|
||||
ctx.Redirect(http.StatusSeeOther, "/admin/login")
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Next()
|
||||
}
|
||||
|
||||
func (app *App) adminSessionToken() string {
|
||||
// A simple deterministic token derived from the admin credentials.
|
||||
// This will improve when proper user/session storage is added.
|
||||
return app.config.AdminUsername + ":" + app.config.AdminPassword
|
||||
}
|
||||
|
||||
func (app *App) handleAdminLogin(ctx *gin.Context) {
|
||||
if !app.adminLoginEnabled() {
|
||||
ctx.Redirect(http.StatusSeeOther, "/")
|
||||
return
|
||||
}
|
||||
|
||||
// Already logged in.
|
||||
if token, err := ctx.Cookie(adminSessionCookie); err == nil && token == app.adminSessionToken() {
|
||||
ctx.Redirect(http.StatusSeeOther, "/admin/dashboard")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, "admin/login.html", gin.H{})
|
||||
}
|
||||
|
||||
func (app *App) handleAdminLoginPost(ctx *gin.Context) {
|
||||
if !app.adminLoginEnabled() {
|
||||
ctx.Redirect(http.StatusSeeOther, "/")
|
||||
return
|
||||
}
|
||||
|
||||
username := strings.TrimSpace(ctx.PostForm("username"))
|
||||
password := ctx.PostForm("password")
|
||||
|
||||
if username != app.config.AdminUsername || password != app.config.AdminPassword {
|
||||
ctx.HTML(http.StatusUnauthorized, "admin/login.html", gin.H{
|
||||
"ErrorMessage": "Invalid username or password.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
secure := app.config.AdminCookieSecure
|
||||
maxAge := int(app.config.SessionTTLSeconds)
|
||||
|
||||
ctx.SetCookie(adminSessionCookie, app.adminSessionToken(), maxAge, "/admin", "", secure, true)
|
||||
ctx.Redirect(http.StatusSeeOther, "/admin/dashboard")
|
||||
}
|
||||
|
||||
func (app *App) handleAdminLogout(ctx *gin.Context) {
|
||||
secure := app.config.AdminCookieSecure
|
||||
ctx.SetCookie(adminSessionCookie, "", -1, "/admin", "", secure, true)
|
||||
ctx.Redirect(http.StatusSeeOther, "/admin/login")
|
||||
}
|
||||
|
||||
func (app *App) handleAdminDashboard(ctx *gin.Context) {
|
||||
if !app.adminLoginEnabled() {
|
||||
ctx.Redirect(http.StatusSeeOther, "/")
|
||||
return
|
||||
}
|
||||
|
||||
dashboardEnabled := config.AdminEnabledTrue
|
||||
if cfgVal := app.config.AdminEnabled; cfgVal == config.AdminEnabledAuto || cfgVal == config.AdminEnabledTrue {
|
||||
dashboardEnabled = cfgVal
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, "admin/dashboard.html", gin.H{
|
||||
"AdminUsername": app.config.AdminUsername,
|
||||
"AdminEmail": app.config.AdminEmail,
|
||||
"DashboardEnabled": string(dashboardEnabled),
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"time"
|
||||
|
||||
"github.com/gin-contrib/gzip"
|
||||
@@ -29,7 +30,11 @@ func Run(addr string) error {
|
||||
app := &App{config: cfg}
|
||||
|
||||
router := gin.Default()
|
||||
router.LoadHTMLGlob("templates/*.html")
|
||||
htmlTemplates, err := loadHTMLTemplates()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
router.SetHTMLTemplate(htmlTemplates)
|
||||
|
||||
routing.Register(router, routing.Handlers{
|
||||
Index: app.handleIndex,
|
||||
@@ -45,6 +50,12 @@ func Run(addr string) error {
|
||||
FileStatusUpdate: app.handleFileStatusUpdate,
|
||||
DirectBoxUpload: app.handleDirectBoxUpload,
|
||||
LegacyUpload: app.handleLegacyUpload,
|
||||
|
||||
AdminLogin: app.handleAdminLogin,
|
||||
AdminLoginPost: app.handleAdminLoginPost,
|
||||
AdminLogout: app.handleAdminLogout,
|
||||
AdminDashboard: app.handleAdminDashboard,
|
||||
AdminAuth: app.adminAuthMiddleware,
|
||||
})
|
||||
|
||||
compressed := router.Group("/", gzip.Gzip(gzip.DefaultCompression))
|
||||
@@ -55,6 +66,22 @@ func Run(addr string) error {
|
||||
return router.Run(addr)
|
||||
}
|
||||
|
||||
func loadHTMLTemplates() (*template.Template, error) {
|
||||
tmpl := template.New("")
|
||||
for _, pattern := range []string{
|
||||
"templates/*.html",
|
||||
"templates/admin/*.html",
|
||||
"templates/admin/partials/*.html",
|
||||
} {
|
||||
var err error
|
||||
tmpl, err = tmpl.ParseGlob(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return tmpl, nil
|
||||
}
|
||||
|
||||
func applyBoxstoreRuntimeConfig(cfg *config.Config) {
|
||||
boxstore.SetUploadRoot(cfg.UploadsDir)
|
||||
boxstore.SetOneTimeDownloadExpiry(cfg.OneTimeDownloadExpirySeconds)
|
||||
|
||||
Reference in New Issue
Block a user