Adds configuration options and environment variables to manage box owner policies, including settings for refresh counts and expiry.
163 lines
4.7 KiB
Go
163 lines
4.7 KiB
Go
package server
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"warpbox/lib/metastore"
|
|
)
|
|
|
|
const accountSessionCookie = "warpbox_account_session"
|
|
|
|
func (app *App) registerAccountRoutes(router *gin.Engine) {
|
|
account := router.Group("/account")
|
|
account.Use(noStoreAdminHeaders)
|
|
account.GET("/login", app.handleAccountLogin)
|
|
account.POST("/login", app.handleAccountLoginPost)
|
|
|
|
protected := account.Group("")
|
|
protected.Use(app.requireAccountSession)
|
|
protected.GET("", app.handleAccountDashboard)
|
|
protected.GET("/", app.handleAccountDashboard)
|
|
protected.POST("/logout", app.handleAccountLogout)
|
|
protected.GET("/settings", app.handleAccountSettings)
|
|
protected.POST("/settings", app.handleAccountSettingsPost)
|
|
protected.POST("/settings/reset", app.handleAccountSettingsReset)
|
|
protected.GET("/settings/export.json", app.handleAccountSettingsExport)
|
|
protected.POST("/settings/import.json", app.handleAccountSettingsImport)
|
|
}
|
|
|
|
func (app *App) handleAccountLogin(ctx *gin.Context) {
|
|
if app.isAccountSessionValid(ctx) {
|
|
ctx.Redirect(http.StatusSeeOther, "/account")
|
|
return
|
|
}
|
|
app.renderAccountLogin(ctx, "")
|
|
}
|
|
|
|
func (app *App) handleAccountLoginPost(ctx *gin.Context) {
|
|
if !app.adminLoginEnabled {
|
|
app.renderAccountLogin(ctx, "Account login is disabled.")
|
|
return
|
|
}
|
|
|
|
username := strings.TrimSpace(ctx.PostForm("username"))
|
|
password := ctx.PostForm("password")
|
|
user, ok, err := app.store.GetUserByUsername(username)
|
|
if err != nil {
|
|
ctx.String(http.StatusInternalServerError, "Could not load user")
|
|
return
|
|
}
|
|
if !ok || user.Disabled || !metastore.VerifyPassword(user.PasswordHash, password) {
|
|
app.renderAccountLogin(ctx, "The username or password was not accepted.")
|
|
return
|
|
}
|
|
|
|
if _, err := app.permissionsForUser(user); err != nil {
|
|
ctx.String(http.StatusInternalServerError, "Could not load permissions")
|
|
return
|
|
}
|
|
|
|
session, err := app.store.CreateSession(user.ID, time.Duration(app.config.SessionTTLSeconds)*time.Second)
|
|
if err != nil {
|
|
ctx.String(http.StatusInternalServerError, "Could not create session")
|
|
return
|
|
}
|
|
ctx.SetSameSite(http.SameSiteLaxMode)
|
|
ctx.SetCookie(accountSessionCookie, session.Token, int(app.config.SessionTTLSeconds), "/account", "", app.config.AdminCookieSecure, true)
|
|
ctx.Redirect(http.StatusSeeOther, "/account")
|
|
}
|
|
|
|
func (app *App) handleAccountLogout(ctx *gin.Context) {
|
|
if token, err := ctx.Cookie(accountSessionCookie); err == nil {
|
|
_ = app.store.DeleteSession(token)
|
|
}
|
|
ctx.SetSameSite(http.SameSiteLaxMode)
|
|
ctx.SetCookie(accountSessionCookie, "", -1, "/account", "", app.config.AdminCookieSecure, true)
|
|
ctx.Redirect(http.StatusSeeOther, "/account/login")
|
|
}
|
|
|
|
func (app *App) requireAccountSession(ctx *gin.Context) {
|
|
token, err := ctx.Cookie(accountSessionCookie)
|
|
if err != nil {
|
|
ctx.Redirect(http.StatusSeeOther, "/account/login")
|
|
ctx.Abort()
|
|
return
|
|
}
|
|
session, ok, err := app.store.GetSession(token)
|
|
if err != nil || !ok {
|
|
ctx.Redirect(http.StatusSeeOther, "/account/login")
|
|
ctx.Abort()
|
|
return
|
|
}
|
|
if !validAdminCSRF(ctx, session) {
|
|
ctx.String(http.StatusForbidden, "Permission denied")
|
|
ctx.Abort()
|
|
return
|
|
}
|
|
user, ok, err := app.store.GetUser(session.UserID)
|
|
if err != nil || !ok || user.Disabled {
|
|
ctx.Redirect(http.StatusSeeOther, "/account/login")
|
|
ctx.Abort()
|
|
return
|
|
}
|
|
perms, err := app.permissionsForUser(user)
|
|
if err != nil {
|
|
ctx.Redirect(http.StatusSeeOther, "/account/login")
|
|
ctx.Abort()
|
|
return
|
|
}
|
|
|
|
ctx.Set("accountUser", user)
|
|
ctx.Set("adminUser", user)
|
|
ctx.Set("accountPerms", perms)
|
|
ctx.Set("adminPerms", perms)
|
|
ctx.Set("accountSession", session)
|
|
ctx.Set("accountCSRFToken", session.CSRFToken)
|
|
ctx.Set("adminCSRFToken", session.CSRFToken)
|
|
ctx.Next()
|
|
}
|
|
|
|
func (app *App) isAccountSessionValid(ctx *gin.Context) bool {
|
|
token, err := ctx.Cookie(accountSessionCookie)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
session, ok, err := app.store.GetSession(token)
|
|
if err != nil || !ok {
|
|
return false
|
|
}
|
|
user, ok, err := app.store.GetUser(session.UserID)
|
|
if err != nil || !ok || user.Disabled {
|
|
return false
|
|
}
|
|
_, err = app.permissionsForUser(user)
|
|
return err == nil
|
|
}
|
|
|
|
func (app *App) renderAccountLogin(ctx *gin.Context, errorMessage string) {
|
|
ctx.HTML(http.StatusOK, "account_login.html", gin.H{
|
|
"PageTitle": "WarpBox Account Login",
|
|
"AdminLoginEnabled": app.adminLoginEnabled,
|
|
"AccountLoginEnabled": app.adminLoginEnabled,
|
|
"Error": errorMessage,
|
|
})
|
|
}
|
|
|
|
func currentAccountUser(ctx *gin.Context) (metastore.User, bool) {
|
|
if current, ok := ctx.Get("accountUser"); ok {
|
|
if user, ok := current.(metastore.User); ok {
|
|
return user, true
|
|
}
|
|
}
|
|
if current, ok := ctx.Get("adminUser"); ok {
|
|
if user, ok := current.(metastore.User); ok {
|
|
return user, true
|
|
}
|
|
}
|
|
return metastore.User{}, false
|
|
}
|