864 lines
18 KiB
Go
864 lines
18 KiB
Go
// Copyright (C) 2021 Marius Schellenberger
|
|
|
|
package server
|
|
|
|
import (
|
|
"html/template"
|
|
"net/http"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.giftfish.de/ston1th/docstore/pkg/core"
|
|
"git.giftfish.de/ston1th/docstore/pkg/fs"
|
|
"git.giftfish.de/ston1th/docstore/pkg/otp"
|
|
"git.giftfish.de/ston1th/jwt/v3"
|
|
)
|
|
|
|
const noSuchFile = "no such file or directory"
|
|
|
|
type notFoundHandler struct {
|
|
s *HTTPServer
|
|
}
|
|
|
|
func (nf *notFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
newContext(w, r, nf.s).NotFound()
|
|
}
|
|
|
|
func totpAuthHandler(h ctxHandler) ctxHandler {
|
|
return func(ctx *Context) {
|
|
if ctx.Totp() != "" {
|
|
h(ctx)
|
|
return
|
|
}
|
|
ctx.Redirect(core.IndexURI, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func jwtRegisterHandler(h ctxHandler) ctxHandler {
|
|
return func(ctx *Context) {
|
|
if c, err := ctx.Request.Cookie(cookieName); err == nil {
|
|
t, err := jwt.DecodeToken(c.Value)
|
|
if err != nil {
|
|
ctx.LogSetCookie("DecodeToken:", err)
|
|
return
|
|
}
|
|
if err = ctx.Srv.JWT.Verify(t); err != nil {
|
|
ctx.LogSetCookie("VerifyToken:", err)
|
|
return
|
|
}
|
|
ctx.Token = *t
|
|
h(ctx)
|
|
return
|
|
}
|
|
ctx.SetCookie(nil, 0)
|
|
h(ctx)
|
|
}
|
|
}
|
|
|
|
func jwtHandler(h ctxHandler) ctxHandler {
|
|
return func(ctx *Context) {
|
|
if c, err := ctx.Request.Cookie(cookieName); err == nil {
|
|
t, err := jwt.DecodeToken(c.Value)
|
|
if err != nil {
|
|
ctx.LogSetCookie("DecodeToken:", err)
|
|
return
|
|
}
|
|
if err = ctx.Srv.JWT.Verify(t); err != nil {
|
|
ctx.LogSetCookie("VerifyToken:", err)
|
|
return
|
|
}
|
|
path := ctx.Path()
|
|
if t.Claims.GetString(totpClaim) != "" {
|
|
if path == core.TotpURI || path == core.LoginURI || path == core.LogoutURI {
|
|
ctx.Token = *t
|
|
h(ctx)
|
|
return
|
|
}
|
|
ctx.Redirect(core.TotpURI, http.StatusFound)
|
|
return
|
|
}
|
|
ctx.Token = *t
|
|
h(ctx)
|
|
return
|
|
}
|
|
ctx.SetCookie(nil, 0)
|
|
h(ctx)
|
|
}
|
|
}
|
|
|
|
func authHandler(h ctxHandler) ctxHandler {
|
|
return func(ctx *Context) {
|
|
if ctx.LoggedOn() {
|
|
h(ctx)
|
|
return
|
|
}
|
|
ctx.Redirect(core.LoginURI, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
type registerNotFoundHandler struct {
|
|
s *HTTPServer
|
|
}
|
|
|
|
func (nf *registerNotFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
newContext(w, r, nf.s).Redirect(core.IndexURI, http.StatusFound)
|
|
}
|
|
|
|
func registerHandler(ctx *Context) {
|
|
ctx.Template("registerHandler")
|
|
ctx.Data = webData{
|
|
Title: "Register User",
|
|
BodyTitle: "Register User",
|
|
}
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Exec()
|
|
case "POST":
|
|
user := ctx.Form("user")
|
|
password := ctx.Form("password")
|
|
repeat := ctx.Form("repeat")
|
|
ctx.Data.Data = user
|
|
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
|
|
if user == "" || password == "" {
|
|
ctx.Error("empty user or password")
|
|
return
|
|
}
|
|
if password != repeat {
|
|
ctx.Error("passwords do not match")
|
|
return
|
|
}
|
|
if err := ctx.Srv.DB.CreateUser(user, password); err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
ctx.SwitchHandler()
|
|
}
|
|
}
|
|
|
|
func rawHandler(ctx *Context) {
|
|
defer ctx.Log()
|
|
path := strings.TrimPrefix(ctx.Path(), core.RawPrefix)
|
|
f, fi, err := ctx.Srv.FS.GetFile(path)
|
|
if err != nil {
|
|
ctx.Status(http.StatusNotFound)
|
|
ctx.Err = ctx.Write([]byte(noSuchFile))
|
|
return
|
|
}
|
|
ctx.Response.Header().Del("Content-Security-Policy")
|
|
http.ServeContent(ctx.Response, ctx.Request, fi.Name(), fi.ModTime(), f)
|
|
}
|
|
|
|
func indexHandler(ctx *Context) {
|
|
log := ctx.Srv.Log
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
var (
|
|
err error
|
|
ok bool
|
|
fpath string
|
|
)
|
|
path := ctx.Path()
|
|
if fpath, ok = hasTrimPrefix(path, core.IndexPrefix); ok {
|
|
log = log.WithValues("file", fpath)
|
|
log.V(2).Info("started indexing file")
|
|
err = ctx.Srv.SQ.Add(fpath, nil)
|
|
if err != nil {
|
|
log.Error(err, "error indexing file")
|
|
}
|
|
} else if fpath, ok = hasTrimPrefix(path, core.RetagPrefix); ok {
|
|
if fpath == "" {
|
|
log.V(2).Info("started retagging all files")
|
|
go func() {
|
|
err := ctx.Srv.DB.RetagAll()
|
|
if err != nil {
|
|
log.Error(err, "error retagging all files")
|
|
}
|
|
}()
|
|
} else {
|
|
log = log.WithValues("file", fpath)
|
|
log.V(2).Info("started retagging file")
|
|
err = ctx.Srv.DB.Retag(fpath, nil)
|
|
if err != nil {
|
|
log.Error(err, "error retagging file")
|
|
}
|
|
}
|
|
}
|
|
p, _ := filepath.Split(fpath)
|
|
p = filepath.Dir(p)
|
|
if err != nil {
|
|
ctx.Template("iseHandler")
|
|
ctx.Data = webData{
|
|
Data: template.HTML(strings.ReplaceAll(err.Error(), "\n", "<br>")),
|
|
Path: p,
|
|
}
|
|
ctx.Status(http.StatusInternalServerError)
|
|
ctx.Exec()
|
|
return
|
|
}
|
|
ctx.Redirect(p, http.StatusFound)
|
|
case "POST":
|
|
if ctx.Path() != core.IndexPrefix+"/all" || !ctx.CheckXsrf() {
|
|
ctx.Redirect(core.IndexURI, http.StatusFound)
|
|
return
|
|
}
|
|
if m, c, ok := ctx.Srv.DB.IndexMutex.Lock(); ok {
|
|
log.V(2).Info("started indexing all files")
|
|
go func() {
|
|
defer ctx.Srv.DB.IndexMutex.Unlock()
|
|
files := ctx.Srv.FS.RecursiveFiles("/")
|
|
ctx.Srv.DB.IndexMutex.Set(uint64(len(files)))
|
|
for _, f := range files {
|
|
if !ctx.Srv.DB.IsIndexed(f) {
|
|
err := ctx.Srv.SQ.Add(f, ctx.Srv.DB.IndexMutex.Inc)
|
|
if err != nil {
|
|
log.Error(err, "error indexing file", "file", f)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
} else {
|
|
log.Info("indexing of all files is already in progress", "indexed", c, "remaning", m)
|
|
}
|
|
ctx.Redirect(core.IndexURI, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func uploadHandler(ctx *Context) {
|
|
path := strings.TrimPrefix(ctx.Path(), core.UploadPrefix)
|
|
ctx.Data = webData{
|
|
Title: "Upload",
|
|
BodyTitle: "Upload",
|
|
}
|
|
ctx.Data.Path = path
|
|
ctx.Template("uploadHandler")
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
f, orig, err := ctx.File("file")
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
newfile, err := ctx.Srv.FS.NewFile(f, path, orig, ctx.Form("name"))
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
ctx.Redirect(newfile, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func newDirHandler(ctx *Context) {
|
|
path := strings.TrimPrefix(ctx.Path(), core.NewPrefix)
|
|
ctx.Data = webData{
|
|
Title: "Create Directory",
|
|
BodyTitle: "Create Directory",
|
|
}
|
|
ctx.Data.Path = path
|
|
ctx.Template("newDirHandler")
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
err := ctx.Srv.FS.Mkdir(path, ctx.Form("name"))
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
ctx.Redirect(path, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func moveHandler(ctx *Context) {
|
|
log := ctx.Srv.Log
|
|
path := strings.TrimPrefix(ctx.Path(), core.MovePrefix)
|
|
mode, _, err := ctx.Srv.FS.Mode(path)
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
switch mode {
|
|
case fs.IsFile:
|
|
ctx.Data = webData{
|
|
Title: "Move File",
|
|
BodyTitle: "Move File",
|
|
}
|
|
case fs.IsDir:
|
|
ctx.Data = webData{
|
|
Title: "Move Directory",
|
|
BodyTitle: "Move Directory",
|
|
}
|
|
default:
|
|
ctx.Data = webData{
|
|
Title: "Move",
|
|
BodyTitle: "Move",
|
|
}
|
|
ctx.Error(noSuchFile)
|
|
return
|
|
}
|
|
ctx.Data.Path = path
|
|
ctx.Data.Paths = ctx.Srv.FS.Recursive(path)
|
|
_, ctx.Data.Data = filepath.Split(path)
|
|
ctx.Template("moveHandler")
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
name := ctx.Form("name")
|
|
dest := ctx.Form("destination")
|
|
moved, newfile, err := ctx.Srv.FS.Move(path, dest, name)
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
for _, m := range moved {
|
|
log.V(2).Info("moving file", "old", m.Old, "new", m.New)
|
|
err = ctx.Srv.DB.MoveFile(m.Old, m.New)
|
|
if err != nil {
|
|
log.Error(err, "moving file failed", "old", m.Old, "new", m.New)
|
|
continue
|
|
}
|
|
}
|
|
ctx.Redirect(newfile, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func deleteHandler(ctx *Context) {
|
|
log := ctx.Srv.Log
|
|
path := strings.TrimPrefix(ctx.Path(), core.DeletePrefix)
|
|
mode, enoent, err := ctx.Srv.FS.Mode(path)
|
|
if !enoent && err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
switch mode {
|
|
case fs.IsFile:
|
|
ctx.Data = webData{
|
|
Title: "Delete File",
|
|
BodyTitle: "Delete File",
|
|
}
|
|
case fs.IsDir:
|
|
ctx.Data = webData{
|
|
Title: "Delete Directory",
|
|
BodyTitle: "Delete Directory",
|
|
}
|
|
default:
|
|
if ctx.Method() == "GET" {
|
|
ctx.Data = webData{
|
|
Title: "Delete",
|
|
BodyTitle: "Delete",
|
|
}
|
|
ctx.Error(noSuchFile)
|
|
return
|
|
}
|
|
}
|
|
ctx.Data.Path = path
|
|
ctx.Data.Data = mode
|
|
ctx.Template("deleteHandler")
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
p, _ := filepath.Split(path)
|
|
p = filepath.Dir(p)
|
|
clean := ctx.Form("clean")
|
|
if clean == "true" {
|
|
f := fs.Clean(path)
|
|
log = log.WithValues("file", f)
|
|
log.V(2).Info("deleting file from DB")
|
|
id, _, err := ctx.Srv.DB.DeleteFile(f)
|
|
if err != nil {
|
|
log.Error(err, "delete from DB failed")
|
|
}
|
|
if id == "" {
|
|
ctx.Redirect(p, http.StatusFound)
|
|
return
|
|
}
|
|
log = log.WithValues("id", id)
|
|
log.V(2).Info("deleting file from index")
|
|
err = ctx.Srv.DB.Index.Delete(id)
|
|
if err != nil {
|
|
log.Error(err, "delete from index failed")
|
|
}
|
|
ctx.Redirect(p, http.StatusFound)
|
|
return
|
|
}
|
|
files, err := ctx.Srv.FS.Delete(path)
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
for _, f := range files {
|
|
log.V(2).Info("deleting file from DB", "file", f)
|
|
id, _, err := ctx.Srv.DB.DeleteFile(f)
|
|
if err != nil {
|
|
log.Error(err, "delete from DB failed", "file", f)
|
|
}
|
|
if id == "" {
|
|
continue
|
|
}
|
|
log.V(2).Info("deleting file from index", "file", f, "id", id)
|
|
err = ctx.Srv.DB.Index.Delete(id)
|
|
if err != nil {
|
|
log.Error(err, "delete from index failed", "file", f, "id", id)
|
|
}
|
|
}
|
|
ctx.Redirect(p, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func dirHandler(ctx *Context) {
|
|
ctx.Template("dirHandler")
|
|
path := ctx.Path()
|
|
if ctx.Method() == "GET" {
|
|
mode, enoent, err := ctx.Srv.FS.Mode(path)
|
|
if !enoent && err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
switch mode {
|
|
case fs.IsDir:
|
|
dir := ctx.Srv.FS.ReadDir(path)
|
|
ctx.Data = webData{
|
|
Title: "Directory Viewer | " + path,
|
|
}
|
|
ctx.Srv.DB.GetIndexed(dir.Files)
|
|
ctx.Data.Path = path
|
|
ctx.Data.Data = dir
|
|
ctx.Data.Paths = fs.Paths(path)
|
|
ctx.Exec()
|
|
case fs.IsFile:
|
|
ctx.Data = webData{
|
|
Title: "Document Viewer | " + path,
|
|
}
|
|
ctx.Template("fileHandler")
|
|
// f := ctx.Srv.DB.IsIndexed(path)
|
|
id, err := ctx.Srv.DB.GetID(path)
|
|
f := err == nil
|
|
if id != "" {
|
|
d, err := ctx.Srv.DB.Index.Get(id)
|
|
if err == nil {
|
|
ctx.Data.Created = core.CreatedTime(d.Created)
|
|
}
|
|
}
|
|
ctx.Data.Data = core.Path{
|
|
Name: path,
|
|
Abs: core.RawPrefix + path,
|
|
Flag: f,
|
|
Scan: ctx.Srv.FS.CheckScan(path),
|
|
}
|
|
ctx.Data.Paths = fs.Paths(path)
|
|
if f {
|
|
ctx.Data.Tags, err = ctx.Srv.DB.GetTag(path)
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
}
|
|
ctx.Exec()
|
|
default:
|
|
p := fs.Clean(path)
|
|
if enoent && ctx.Srv.DB.IsIndexed(p) {
|
|
ctx.Template("enoentHandler")
|
|
ctx.Data = webData{
|
|
Title: "Stalled File",
|
|
}
|
|
ctx.Data.Path = p
|
|
ctx.Exec()
|
|
return
|
|
}
|
|
ctx.Error(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func loginHandler(ctx *Context) {
|
|
if ctx.LoggedOn() {
|
|
ctx.Redirect(core.IndexURI, http.StatusFound)
|
|
return
|
|
}
|
|
ctx.Template("loginHandler")
|
|
ctx.Data = webData{
|
|
Title: "Login",
|
|
BodyTitle: "Login",
|
|
}
|
|
log := ctx.Srv.Log
|
|
var data loginData
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
data.Referer = ctx.RefererURI()
|
|
ctx.Data.Data = data
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
data.User = ctx.Form("user")
|
|
data.Referer = ctx.Form("referer")
|
|
ctx.Data.Data = data
|
|
if data.Referer == "" {
|
|
data.Referer = core.IndexURI
|
|
}
|
|
password := ctx.Form("password")
|
|
if data.User == "" || password == "" {
|
|
ctx.Error("empty username or password")
|
|
return
|
|
}
|
|
u, err := ctx.Srv.DB.Login(data.User, password)
|
|
if err != nil {
|
|
log.Error(err, "login failed", "user", data.User)
|
|
ctx.Error("bad username or password")
|
|
return
|
|
}
|
|
if u.Secret == "" {
|
|
log.Info("login successful", "user", u.Username)
|
|
ctx.Login(u, ctx.Form("remember"))
|
|
ctx.Redirect(data.Referer, http.StatusFound)
|
|
return
|
|
}
|
|
ctx.SetCookie(jwt.Claims{
|
|
totpClaim: u.Username,
|
|
rememberClaim: ctx.Form("remember"),
|
|
refererClaim: data.Referer,
|
|
}, time.Minute)
|
|
ctx.Redirect(core.TotpURI, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func loginTotpHandler(ctx *Context) {
|
|
if ctx.LoggedOn() {
|
|
ctx.Redirect(core.IndexURI, http.StatusFound)
|
|
return
|
|
}
|
|
ctx.Template("loginTotpHandler")
|
|
ctx.Data = webData{
|
|
Title: "TOTP Verification",
|
|
BodyTitle: "TOTP Veriftcation",
|
|
}
|
|
log := ctx.Srv.Log
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
pin := ctx.Form("pin")
|
|
if pin == "" {
|
|
ctx.Error("empty pin")
|
|
return
|
|
}
|
|
u, err := ctx.Srv.DB.Totp(pin)
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
ref := ctx.Referer()
|
|
log.Info("login successful", "user", u.Username)
|
|
ctx.Login(u, ctx.Remember())
|
|
ctx.Redirect(ref, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func searchHandler(ctx *Context) {
|
|
ctx.Template("searchHandler")
|
|
ctx.Data = webData{
|
|
Title: "Search",
|
|
BodyTitle: "Search",
|
|
}
|
|
m := ctx.Method()
|
|
switch m {
|
|
case "GET", "POST":
|
|
if m == "POST" && !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
q := ctx.GetForm("query")
|
|
ctx.Data.Query = q
|
|
if q == "" {
|
|
q = `""`
|
|
}
|
|
res, err := ctx.Srv.DB.Index.Search(q)
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
ctx.Srv.DB.GetPaths(res)
|
|
ctx.Data.Data = res
|
|
ctx.Exec()
|
|
}
|
|
}
|
|
|
|
func userEditHandler(ctx *Context) {
|
|
ctx.Template("userEditHandler")
|
|
ctx.Data = webData{
|
|
Title: "Edit User",
|
|
BodyTitle: "Edit User",
|
|
}
|
|
log := ctx.Srv.Log
|
|
u, err := ctx.Srv.DB.GetUserWithoutPassword()
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
ctx.Data.Data = u
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
password := ctx.Form("password")
|
|
repeat := ctx.Form("repeat")
|
|
|
|
if password != repeat {
|
|
ctx.Error("passwords do not match")
|
|
return
|
|
}
|
|
if err := ctx.Srv.DB.UpdateUserPassword(password); err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
log.Info("user updated", "user", ctx.User())
|
|
ctx.Redirect(core.IndexURI, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func userTotpHandler(ctx *Context) {
|
|
ctx.Template("userTotpHandler")
|
|
ctx.Data = webData{
|
|
Title: "TOTP",
|
|
BodyTitle: "TOTP",
|
|
}
|
|
log := ctx.Srv.Log
|
|
req := otp.Request{Username: ctx.User()}
|
|
u, err := ctx.Srv.DB.GetUser()
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
if u.Secret == "" {
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
req, err = otp.New(req.Username)
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
case "POST":
|
|
req.URL = ctx.Form("url")
|
|
req.Image, req.Secret, err = otp.ImageSecretFromURL(req.URL)
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Data.Data = req
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
pin := ctx.Form("pin")
|
|
if pin == "" {
|
|
ctx.Error("empty pin")
|
|
return
|
|
}
|
|
ctx.Data.Data = req
|
|
secret := req.Secret
|
|
if u.Secret != "" {
|
|
secret = u.Secret
|
|
}
|
|
user := ctx.User()
|
|
|
|
if !otp.Validate(pin, secret) {
|
|
ctx.Error("validation failed")
|
|
return
|
|
}
|
|
|
|
if err := ctx.Srv.DB.UpdateUserSecret(req.Secret); err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
if req.Secret == "" {
|
|
log.Info("totp disabled", "user", user)
|
|
} else {
|
|
log.Info("totp enabled", "user", user)
|
|
}
|
|
ctx.Redirect("/user/edit", http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func logsHandler(ctx *Context) {
|
|
ctx.Template("logsHandler")
|
|
ctx.Data = webData{
|
|
Title: "Logs",
|
|
BodyTitle: "Logs",
|
|
}
|
|
if ctx.Method() == "GET" {
|
|
// TODO remove
|
|
//ctx.Data.Data = ctx.Srv.Log.Logs()
|
|
ctx.Exec()
|
|
}
|
|
}
|
|
|
|
func tagsHandler(ctx *Context) {
|
|
ctx.Template("tagsHandler")
|
|
ctx.Data = webData{
|
|
Title: "Tags",
|
|
BodyTitle: "Tags",
|
|
}
|
|
if ctx.Method() == "GET" {
|
|
t, err := ctx.Srv.DB.GetAllTags()
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
ctx.Data.Data = t
|
|
ctx.Exec()
|
|
}
|
|
}
|
|
|
|
func tagsNewHandler(ctx *Context) {
|
|
ctx.Template("tagsNewHandler")
|
|
ctx.Data = webData{
|
|
Title: "New Tag",
|
|
BodyTitle: "New Tag",
|
|
}
|
|
log := ctx.Srv.Log
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Data.Data = core.Tag{}
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
var t core.Tag
|
|
t.Name = ctx.Form("name")
|
|
t.Regex = ctx.Form("regex")
|
|
ctx.Data.Data = t
|
|
if t.Name == "" {
|
|
ctx.Error("name cannot be empty")
|
|
return
|
|
}
|
|
if t.Regex == "" {
|
|
ctx.Error("regex cannot be empty")
|
|
return
|
|
}
|
|
if err := ctx.Srv.DB.NewTag(t.Name, t.Regex); err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
log.Info("new tag created", "user", ctx.User(), "tag", t.Name)
|
|
ctx.Redirect(core.TagsURI, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func tagsEditHandler(ctx *Context) {
|
|
ctx.Template("tagsEditHandler")
|
|
ctx.Data = webData{
|
|
Title: "Edit Tag",
|
|
BodyTitle: "Edit Tag",
|
|
}
|
|
log := ctx.Srv.Log
|
|
name := ctx.Var("tag")
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
t, err := ctx.Srv.DB.GetTags(name)
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
ctx.Data.Data = t
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
var t core.Tag
|
|
t.Name = name
|
|
t.Regex = ctx.Form("regex")
|
|
ctx.Data.Data = t
|
|
if t.Regex == "" {
|
|
ctx.Error("regex cannot be empty")
|
|
return
|
|
}
|
|
if err := ctx.Srv.DB.NewTag(t.Name, t.Regex); err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
log.Info("tag updated", "user", ctx.User(), "tag", t.Name)
|
|
ctx.Redirect(core.TagsURI, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func tagsDelHandler(ctx *Context) {
|
|
ctx.Template("tagsDelHandler")
|
|
ctx.Data = webData{
|
|
Title: "Delete Tag",
|
|
BodyTitle: "Delete Tag",
|
|
}
|
|
log := ctx.Srv.Log
|
|
name := ctx.Var("tag")
|
|
ctx.Data.Data = name
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
if err := ctx.Srv.DB.DeleteTag(name); err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
log.Info("tag deleted", "user", ctx.User(), "tag", name)
|
|
ctx.Redirect(core.TagsURI, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func statsHandler(ctx *Context) {
|
|
ctx.Template("statsHandler")
|
|
ctx.Data = webData{
|
|
Title: "Stats",
|
|
BodyTitle: "Stats",
|
|
}
|
|
if ctx.Method() == "GET" {
|
|
ctx.Data.Data = getStats(ctx.Srv.DB.Index)
|
|
ctx.Exec()
|
|
}
|
|
}
|
|
|
|
func logoutHandler(ctx *Context) {
|
|
log := ctx.Srv.Log
|
|
user := ctx.User()
|
|
if user == "" {
|
|
user = ctx.Totp() + " (totp)"
|
|
}
|
|
log.Info("user logged out", "user", user)
|
|
ctx.Srv.JWT.Invalidate(&ctx.Token)
|
|
ctx.SetCookie(nil, 0)
|
|
ctx.Redirect(core.LoginURI, http.StatusFound)
|
|
}
|