893 lines
18 KiB
Go
893 lines
18 KiB
Go
// Copyright (C) 2021 Marius Schellenberger
|
|
|
|
package server
|
|
|
|
import (
|
|
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
|
"git.giftfish.de/ston1th/gowiki/pkg/log"
|
|
"git.giftfish.de/ston1th/gowiki/pkg/otp"
|
|
"git.giftfish.de/ston1th/gowiki/pkg/render"
|
|
"git.giftfish.de/ston1th/jwt/v3"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
type notFoundHandler struct {
|
|
s *HTTPServer
|
|
}
|
|
|
|
func (nf *notFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
newContext(w, r, nf.s).NotFound()
|
|
}
|
|
|
|
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)
|
|
ctx.Redirect(core.IndexURI, http.StatusFound)
|
|
return
|
|
}
|
|
if err = ctx.Srv.JWT.Verify(t); err != nil {
|
|
ctx.LogSetCookie("VerifyToken:", err)
|
|
ctx.Redirect(core.IndexURI, http.StatusFound)
|
|
return
|
|
}
|
|
if t.Claims.GetString(totpClaim) != "" {
|
|
path := ctx.Path()
|
|
if path == core.TotpURI || path == core.LoginURI || path == core.LogoutURI {
|
|
ctx.Token = *t
|
|
h(ctx)
|
|
return
|
|
}
|
|
ctx.Redirect(core.TotpURI, http.StatusFound)
|
|
return
|
|
|
|
}
|
|
if !ctx.Srv.DB.LockedOut(t.Claims.GetString(userClaim), t.Claims.GetString(userCreatedClaim)) {
|
|
ctx.Token = *t
|
|
h(ctx)
|
|
return
|
|
}
|
|
if err = ctx.Srv.JWT.Invalidate(t); err != nil {
|
|
log.Println("Invalidate:", err)
|
|
}
|
|
}
|
|
ctx.SetCookie(nil, 0)
|
|
h(ctx)
|
|
}
|
|
}
|
|
|
|
func totpAuthHandler(h ctxHandler) ctxHandler {
|
|
return func(ctx *Context) {
|
|
if ctx.Totp() != "" {
|
|
h(ctx)
|
|
return
|
|
}
|
|
ctx.Redirect(core.IndexURI, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func adminAuthHandler(h ctxHandler) ctxHandler {
|
|
return func(ctx *Context) {
|
|
if ctx.Admin() {
|
|
h(ctx)
|
|
return
|
|
}
|
|
ctx.Forbidden()
|
|
}
|
|
}
|
|
|
|
func userAuthHandler(h ctxHandler) ctxHandler {
|
|
return func(ctx *Context) {
|
|
if ctx.Admin() {
|
|
h(ctx)
|
|
return
|
|
}
|
|
if ctx.User() == ctx.Var("user") {
|
|
h(ctx)
|
|
return
|
|
}
|
|
ctx.Forbidden()
|
|
}
|
|
}
|
|
|
|
func authHandler(h ctxHandler) ctxHandler {
|
|
return func(ctx *Context) {
|
|
if ctx.LoggedOn() {
|
|
h(ctx)
|
|
return
|
|
}
|
|
ctx.Forbidden()
|
|
}
|
|
}
|
|
|
|
func indexHandler(ctx *Context) {
|
|
ctx.Redirect(core.IndexURI, http.StatusFound)
|
|
}
|
|
|
|
func loginHandler(ctx *Context) {
|
|
if ctx.LoggedOn() {
|
|
ctx.Redirect(core.IndexURI, http.StatusFound)
|
|
return
|
|
}
|
|
ctx.Template("loginHandler")
|
|
ctx.Data = webData{
|
|
Title: "Login",
|
|
BodyTitle: "Login",
|
|
}
|
|
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.Printf("login: %s from: %s %s", data.User, ctx.Forwarded(), err)
|
|
ctx.Error("bad username or password")
|
|
return
|
|
}
|
|
if u.Secret == "" {
|
|
log.Println("login:", 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",
|
|
}
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
pin := ctx.Form("pin")
|
|
if pin == "" {
|
|
ctx.Error("empty pin")
|
|
return
|
|
}
|
|
user := ctx.Totp()
|
|
u, err := ctx.Srv.DB.Totp(user, pin)
|
|
if err != nil {
|
|
log.Printf("totp: %s from: %s %s", user, ctx.Forwarded(), err)
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
ref := ctx.Referer()
|
|
log.Println("login:", 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",
|
|
}
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
search := ctx.Form("search")
|
|
|
|
res, err := ctx.Srv.DB.Index.Search(search)
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
user := ctx.User()
|
|
r := make([]core.Result, len(res))
|
|
i := 0
|
|
for _, v := range res {
|
|
if ctx.Srv.DB.ValidatePage(v.StoreTitle, user) {
|
|
r[i] = v
|
|
i++
|
|
}
|
|
}
|
|
|
|
ctx.Data.Data = r[:i]
|
|
ctx.Data.Search = search
|
|
ctx.Exec()
|
|
}
|
|
}
|
|
|
|
func sectionsHandler(ctx *Context) {
|
|
ctx.Template("sectionsHandler")
|
|
ctx.Data = webData{
|
|
Title: "All Sections",
|
|
BodyTitle: "All Sections",
|
|
Admin: ctx.Admin(),
|
|
}
|
|
ctx.Data.Data = ctx.Srv.DB.GetAllSections(ctx.User(), ctx.Admin())
|
|
ctx.Exec()
|
|
}
|
|
|
|
func sectionHandler(ctx *Context) {
|
|
ctx.Template("sectionHandler")
|
|
section := ctx.Var("section")
|
|
ctx.Data = webData{
|
|
Title: section,
|
|
BodyTitle: section,
|
|
Admin: ctx.Admin(),
|
|
}
|
|
var err error
|
|
ctx.Data.Data, err = ctx.Srv.DB.GetAllSectionPages(section, ctx.User(), ctx.Admin())
|
|
if err != nil {
|
|
ctx.NotFound()
|
|
return
|
|
}
|
|
ctx.Exec()
|
|
}
|
|
|
|
func sectionNewHandler(ctx *Context) {
|
|
ctx.Template("sectionNewHandler")
|
|
ctx.Data = webData{
|
|
Title: "New Section",
|
|
BodyTitle: "New Section",
|
|
}
|
|
users, err := ctx.Srv.DB.GetUsers()
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
sd := sectionData{Members: make(map[string]bool)}
|
|
for _, u := range users {
|
|
sd.Members[u.Username] = false
|
|
}
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Data.Data = sd
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
section := ctx.Form("section")
|
|
members := ctx.FormSlice("members")
|
|
for _, m := range members {
|
|
sd.Members[m] = true
|
|
}
|
|
ctx.Data.Data = sd
|
|
if section == "" {
|
|
ctx.Error("empty section")
|
|
return
|
|
}
|
|
err = ctx.Srv.DB.CreateSection(section, members, false)
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
log.Printf("create: section %s created by %s\n", section, ctx.User())
|
|
ctx.Redirect(core.SectionsURI, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func sectionEditHandler(ctx *Context) {
|
|
ctx.Template("sectionEditHandler")
|
|
ctx.Data = webData{
|
|
Title: "Edit Section",
|
|
BodyTitle: "Edit Section",
|
|
}
|
|
users, err := ctx.Srv.DB.GetUsers()
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
sd := sectionData{Members: make(map[string]bool)}
|
|
for _, u := range users {
|
|
sd.Members[u.Username] = false
|
|
}
|
|
s, err := ctx.Srv.DB.GetSection(ctx.Var("section"))
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
sd.Name = s.Name
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
for _, m := range s.Members {
|
|
sd.Members[m] = true
|
|
}
|
|
ctx.Data.Data = sd
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
members := ctx.FormSlice("members")
|
|
for _, m := range members {
|
|
sd.Members[m] = true
|
|
}
|
|
ctx.Data.Data = sd
|
|
err = ctx.Srv.DB.UpdateSection(s.Name, members, s.User)
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
log.Printf("update: section %s updated by %s\n", s.Name, ctx.User())
|
|
ctx.Redirect(core.SectionsURI, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func sectionDelHandler(ctx *Context) {
|
|
ctx.Template("sectionDelHandler")
|
|
ctx.Data = webData{
|
|
Title: "Delete Section",
|
|
BodyTitle: "Delete Section",
|
|
}
|
|
s, err := ctx.Srv.DB.GetSection(ctx.Var("section"))
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
if s.User {
|
|
ctx.Error("user section can not be deleted")
|
|
return
|
|
}
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Data.Data = s.Name
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
err := ctx.Srv.DB.DeleteSection(s.Name)
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
log.Printf("delete: section %s deleted by %s\n", s.Name, ctx.User())
|
|
ctx.Redirect(core.SectionsURI, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func pageNewHandler(ctx *Context) {
|
|
ctx.Template("pageNewHandler")
|
|
ctx.Data = webData{Title: "New Page"}
|
|
user := ctx.User()
|
|
secs, err := ctx.Srv.DB.GetUserSections(user)
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
secs[user] = true
|
|
ctx.Data.Data = pageData{core.Page{Perm: core.Public}, secs}
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
section := ctx.Form("section")
|
|
title := ctx.Form("title")
|
|
markdown := ctx.Form("markdown")
|
|
perm := core.ParsePermString(ctx.Form("perm"))
|
|
p := core.Page{
|
|
Title: title,
|
|
Markdown: markdown,
|
|
Perm: perm,
|
|
}
|
|
secs[section] = true
|
|
ctx.Data.Data = pageData{p, secs}
|
|
if title == "" {
|
|
ctx.Error("empty title")
|
|
return
|
|
}
|
|
if perm == core.Invalid {
|
|
ctx.Error("invalid permission")
|
|
return
|
|
}
|
|
|
|
page, err := ctx.Srv.DB.CreatePage(title, section, markdown, user, perm)
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
ctx.Redirect(page.StoreTitle, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func pageHandler(ctx *Context) {
|
|
var pageCreated string
|
|
ctx.Data = webData{
|
|
Admin: ctx.Admin(),
|
|
}
|
|
section := ctx.Var("section")
|
|
title := ctx.Var("title")
|
|
user := ctx.User()
|
|
key := ctx.Var("key")
|
|
if key != "" {
|
|
t, err := jwt.DecodeToken(key)
|
|
if err != nil {
|
|
log.Println("share: DecodeToken:", err)
|
|
ctx.NotFound()
|
|
return
|
|
}
|
|
if err = ctx.Srv.JWT.Verify(t); err != nil {
|
|
log.Println("share: VerifyToken:", err)
|
|
ctx.NotFound()
|
|
return
|
|
}
|
|
user = t.Claims.GetString(sharedClaim)
|
|
if ctx.Srv.DB.LockedOut(user, t.Claims.GetString(userCreatedClaim)) {
|
|
if err = ctx.Srv.JWT.Invalidate(t); err != nil {
|
|
log.Println("share: Invalidate:", err)
|
|
}
|
|
ctx.NotFound()
|
|
return
|
|
}
|
|
section = t.Claims.GetString(sectionClaim)
|
|
title = t.Claims.GetString(titleClaim)
|
|
pageCreated = t.Claims.GetString(pageCreatedClaim)
|
|
ctx.Data.Key = key
|
|
if ctx.T == nil {
|
|
ctx.Template("pageViewHandler")
|
|
}
|
|
} else {
|
|
if ctx.T == nil {
|
|
ctx.Template("pageHandler")
|
|
}
|
|
}
|
|
page, err := ctx.Srv.DB.GetPage(section, title, user)
|
|
if err != nil {
|
|
log.Println(err)
|
|
ctx.NotFound()
|
|
return
|
|
}
|
|
if key != "" && page.Created != pageCreated {
|
|
ctx.NotFound()
|
|
return
|
|
}
|
|
ctx.Data.Data = page
|
|
ctx.Data.Title = section + " | " + page.Title
|
|
ctx.Data.BodyTitle = section
|
|
ctx.Exec()
|
|
}
|
|
|
|
func pageMDHandler(f ctxHandler) ctxHandler {
|
|
return func(ctx *Context) {
|
|
ctx.Template("pageMDHandler")
|
|
f(ctx)
|
|
}
|
|
}
|
|
|
|
func pageShareHandler(ctx *Context) {
|
|
ctx.Template("pageShareHandler")
|
|
ctx.Data = webData{}
|
|
section := ctx.Var("section")
|
|
title := ctx.Var("title")
|
|
user := ctx.User()
|
|
page, err := ctx.Srv.DB.GetPage(section, title, user)
|
|
if err != nil {
|
|
ctx.NotFound()
|
|
return
|
|
}
|
|
if page.Perm != core.Private {
|
|
ctx.Error("only private pages can be shared")
|
|
return
|
|
}
|
|
ctx.Data.Data = page.StoreTitle
|
|
ctx.Data.Title = page.Title
|
|
ctx.Data.BodyTitle = page.Title
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
|
|
d, err := duration(ctx.Form("duration"), ctx.Form("mode"))
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
t := jwt.NewToken(map[string]interface{}{
|
|
sharedClaim: user,
|
|
userCreatedClaim: ctx.Token.Claims.GetString(userCreatedClaim),
|
|
pageCreatedClaim: page.Created,
|
|
sectionClaim: section,
|
|
titleClaim: title,
|
|
jwt.ExpClaim: jwt.NewExp(d),
|
|
}, nil)
|
|
|
|
if err = ctx.Srv.JWT.Sign(t); err != nil {
|
|
log.Println("share: SignToken:", err)
|
|
ctx.NotFound()
|
|
return
|
|
}
|
|
ctx.Data.Key = t.String()
|
|
ctx.Exec()
|
|
}
|
|
}
|
|
|
|
func pageBlacklistHandler(ctx *Context) {
|
|
ctx.Template("pageBlacklistHandler")
|
|
ctx.Data = webData{
|
|
Title: "Blacklist",
|
|
BodyTitle: "Blacklist",
|
|
}
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
|
|
t, err := jwt.DecodeToken(ctx.Form("share"))
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
err = ctx.Srv.JWT.Verify(t)
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
user := t.Claims.GetString(sharedClaim)
|
|
if user == "" {
|
|
ctx.Error("blacklisting denied: no share token")
|
|
return
|
|
}
|
|
if ctx.User() != user {
|
|
ctx.Error("blacklisting denied: token was issued by " + user)
|
|
return
|
|
}
|
|
err = ctx.Srv.JWT.Invalidate(t)
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
ctx.Exec()
|
|
}
|
|
}
|
|
|
|
func pageEditHandler(ctx *Context) {
|
|
ctx.Template("pageEditHandler")
|
|
ctx.Data = webData{}
|
|
section := ctx.Var("section")
|
|
title := ctx.Var("title")
|
|
page, err := ctx.Srv.DB.GetPage(section, title, ctx.User())
|
|
if err != nil {
|
|
ctx.NotFound()
|
|
return
|
|
}
|
|
ctx.Data.Title = page.Title
|
|
ctx.Data.BodyTitle = page.Title
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Data.Data = page
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
page.Markdown = ctx.Form("markdown")
|
|
page.Perm = core.ParsePermString(ctx.Form("perm"))
|
|
ctx.Data.Data = page
|
|
|
|
err = ctx.Srv.DB.UpdatePage(title, section, page.Markdown, ctx.User(), page.Perm)
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
ctx.Redirect(page.StoreTitle, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func pageDelHandler(ctx *Context) {
|
|
section := ctx.Var("section")
|
|
title := ctx.Var("title")
|
|
ctx.Template("pageDelHandler")
|
|
ctx.Data.Data = render.StoreTitle(section, title)
|
|
ctx.Data.Title = title
|
|
ctx.Data.BodyTitle = title
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
if err := ctx.Srv.DB.DeletePage(title, section, ctx.User()); err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
ctx.Redirect(core.IndexURI, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func usersHandler(ctx *Context) {
|
|
if !ctx.LoggedOn() {
|
|
ctx.NotFound()
|
|
return
|
|
}
|
|
ctx.Template("usersHandler")
|
|
ctx.Data = webData{
|
|
Title: "Users",
|
|
BodyTitle: "Users",
|
|
User: ctx.User(),
|
|
}
|
|
us, err := ctx.Srv.DB.GetUsers()
|
|
if err != nil {
|
|
ctx.Data.Msg = err.Error()
|
|
}
|
|
ctx.Data.Data = us
|
|
ctx.Exec()
|
|
}
|
|
|
|
func userNewHandler(ctx *Context) {
|
|
ctx.Template("userNewHandler")
|
|
ctx.Data = webData{
|
|
Title: "New User",
|
|
BodyTitle: "New User",
|
|
}
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
user := ctx.Form("user")
|
|
password := ctx.Form("password")
|
|
repeat := ctx.Form("repeat")
|
|
admin := ctx.Form("admin")
|
|
adm := false
|
|
if admin == "0" {
|
|
adm = true
|
|
}
|
|
|
|
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, adm); err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
log.Printf("create: user %s created by %s\n", user, ctx.User())
|
|
ctx.Redirect(core.UsersURI, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func userEditHandler(ctx *Context) {
|
|
ctx.Template("userEditHandler")
|
|
ctx.Data = webData{
|
|
Title: "Edit User",
|
|
BodyTitle: "Edit User",
|
|
}
|
|
u, err := ctx.Srv.DB.GetUserWithoutPassword(ctx.Var("user"))
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
ctx.Data.Data = u
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
user := ctx.Var("user")
|
|
password := ctx.Form("password")
|
|
repeat := ctx.Form("repeat")
|
|
admin := ctx.Form("admin")
|
|
adm := false
|
|
if admin == "0" {
|
|
adm = true
|
|
}
|
|
|
|
if password != repeat {
|
|
ctx.Error("passwords do not match")
|
|
return
|
|
}
|
|
if ctx.Admin() {
|
|
if err := ctx.Srv.DB.AdminUpdateUser(user, password, adm); err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
log.Printf("update: user %s updated by %s\n", user, ctx.User())
|
|
ctx.Redirect(core.UsersURI, http.StatusFound)
|
|
return
|
|
}
|
|
if err := ctx.Srv.DB.UpdateUserPassword(user, password); err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
log.Printf("update: %s updated by %s\n", user, ctx.User())
|
|
ctx.Redirect(core.IndexURI, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func userTotpHandler(ctx *Context) {
|
|
ctx.Template("userTotpHandler")
|
|
ctx.Data = webData{
|
|
Title: "TOTP",
|
|
BodyTitle: "TOTP",
|
|
}
|
|
req := otp.Request{Username: ctx.Var("user")}
|
|
u, err := ctx.Srv.DB.GetUser(req.Username)
|
|
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
|
|
}
|
|
valid := otp.Validate(pin, secret)
|
|
user := ctx.User()
|
|
if user != req.Username && ctx.Admin() {
|
|
valid = true
|
|
}
|
|
|
|
if !valid {
|
|
ctx.Error("validation failed")
|
|
return
|
|
}
|
|
if err := ctx.Srv.DB.UpdateUserSecret(req.Username, req.Secret); err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
if req.Secret == "" {
|
|
log.Printf("totp: disabled by %s for %s\n", user, req.Username)
|
|
} else {
|
|
log.Printf("totp: enabled by %s for %s\n", user, req.Username)
|
|
}
|
|
ctx.Redirect(core.UserURI+"/edit/"+req.Username, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func userUnlockHandler(ctx *Context) {
|
|
user := ctx.Var("user")
|
|
if err := ctx.Srv.DB.UnlockUser(user); err != nil {
|
|
log.Println("unlock:", err)
|
|
}
|
|
log.Printf("unlock: %s unlocked by %s\n", user, ctx.User())
|
|
ctx.Redirect(core.UsersURI, http.StatusFound)
|
|
}
|
|
|
|
func userDelHandler(ctx *Context) {
|
|
ctx.Template("userDelHandler")
|
|
ctx.Data = webData{
|
|
Title: "Delete User",
|
|
BodyTitle: "Delete User",
|
|
}
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
user, err := ctx.Srv.DB.GetUserWithoutPassword(ctx.Var("user"))
|
|
if err != nil {
|
|
ctx.Error(err)
|
|
return
|
|
}
|
|
ctx.Data.Data = user
|
|
ctx.Exec()
|
|
case "POST":
|
|
if !ctx.CheckXsrf() {
|
|
return
|
|
}
|
|
self := ctx.User()
|
|
user := ctx.Var("user")
|
|
if err := ctx.Srv.DB.DeleteUser(user); err != nil {
|
|
log.Println("delete:", err)
|
|
}
|
|
log.Printf("delete: user %s deleted by %s\n", user, self)
|
|
if user == self {
|
|
ctx.Redirect(core.LogoutURI, http.StatusFound)
|
|
return
|
|
}
|
|
ctx.Redirect(core.UsersURI, http.StatusFound)
|
|
}
|
|
}
|
|
|
|
func statsHandler(ctx *Context) {
|
|
ctx.Template("statsHandler")
|
|
ctx.Data = webData{
|
|
Title: "Stats",
|
|
BodyTitle: "Stats",
|
|
}
|
|
switch ctx.Method() {
|
|
case "GET":
|
|
ctx.Data.Data = getStats(ctx.Srv.DB.Index)
|
|
ctx.Exec()
|
|
}
|
|
}
|
|
|
|
func logoutHandler(ctx *Context) {
|
|
var totp string
|
|
user := ctx.User()
|
|
if user == "" {
|
|
user = ctx.Totp()
|
|
if user != "" {
|
|
totp = " (totp)"
|
|
}
|
|
}
|
|
if user != "" {
|
|
ctx.Srv.JWT.Invalidate(&ctx.Token)
|
|
} else {
|
|
user = empty
|
|
}
|
|
log.Printf("logout: %s%s", user, totp)
|
|
ctx.SetCookie(nil, 0)
|
|
ctx.Redirect(core.IndexURI, http.StatusFound)
|
|
}
|