akyuu/handlers.go

323 行
9.0 KiB
Go

// $TheSupernovaDuo: akyuu,v master 2023/4/14 21:2:6 yakumo_izuru Exp $
// See LICENSE for copyright details
package main
import (
"bufio"
"crypto/rand"
"encoding/base64"
"golang.org/x/crypto/bcrypt"
"gopkg.in/gomail.v2"
"log"
"github.com/gorilla/mux"
"net/http"
"os"
"strconv"
"strings"
"time"
)
func passwordResetRequestGetHandler(w http.ResponseWriter, r *http.Request) {
if "" == mailuser {
execTemplate(w, "nopwresetrequest.html", "")
} else {
execTemplate(w, "pwresetrequest.html", "")
}
}
func passwordResetRequestPostHandler(w http.ResponseWriter, r *http.Request) {
preparePasswordReset := func(name string) {
if "" == mailuser {
return
}
now := int(time.Now().Unix())
tokens, errWait := getFromFileEntryFor(pwResetWaitPath, name, 2)
if errWait == nil {
lastTime, err := strconv.Atoi(tokens[0])
if err != nil {
log.Fatal("Trouble parsing password reset "+
"wait times", err)
}
if lastTime+resetWaitTime >= now {
return
}
}
var target string
tokens, err := getFromFileEntryFor(loginsPath, name, 5)
if err != nil {
return
} else if "" == tokens[1] {
return
}
target = tokens[1]
b := make([]byte, 64)
if _, err := rand.Read(b); err != nil {
log.Fatal("Random string generation failed", err)
}
urlPart := base64.URLEncoding.EncodeToString(b)
strTime := strconv.Itoa(now)
appendToFile(pwResetPath, urlPart+"\t"+name+"\t"+strTime)
m := gomail.NewMessage()
m.SetHeader("From", mailuser)
m.SetHeader("To", target)
m.SetHeader("Subject", "password reset link")
msg := myself + "/passwordreset/" + urlPart
m.SetBody("text/plain", msg)
if err := dialer.DialAndSend(m); err != nil {
log.Fatal("Can't send mail", err)
}
line := name + "\t" + strTime
if nil == errWait {
replaceLineStartingWith(pwResetWaitPath, name, line)
} else {
appendToFile(pwResetWaitPath, line)
}
}
go preparePasswordReset(r.FormValue("name"))
http.Redirect(w, r, "/", 302)
}
func passwordResetLinkGetHandler(w http.ResponseWriter, r *http.Request) {
urlPart := mux.Vars(r)["secret"]
tokens, err := getFromFileEntryFor(pwResetPath, urlPart, 3)
if err != nil {
http.Redirect(w, r, "/404", 302)
return
}
createTime, err := strconv.Atoi(tokens[1])
if err != nil {
log.Fatal("Can't read time from pw reset file", err)
}
if createTime+resetLinkExp < int(time.Now().Unix()) {
http.Redirect(w, r, "/404", 302)
return
}
name := tokens[0]
tokensUser, err := getFromFileEntryFor(loginsPath, name,
5)
if err != nil {
log.Fatal("Can't read from loings file", err)
}
if "" != tokensUser[2] {
type data struct {
Secret string
Question string
}
err := templ.ExecuteTemplate(w,
"pwresetquestion.html", data{
Secret: urlPart,
Question: tokensUser[2]})
if err != nil {
log.Fatal("Trouble executing template", err)
}
return
}
execTemplate(w, "pwreset.html", urlPart)
}
func passwordResetLinkPostHandler(w http.ResponseWriter, r *http.Request) {
urlPart := mux.Vars(r)["secret"]
name := r.FormValue("name")
tokens, err := getFromFileEntryFor(pwResetPath, urlPart, 3)
if err != nil {
http.Redirect(w, r, "/404", 302)
return
}
createTime, err := strconv.Atoi(tokens[1])
if err != nil {
log.Fatal("Can't read time from pw reset file", err)
}
if createTime+resetLinkExp < int(time.Now().Unix()) {
http.Redirect(w, r, "/404", 302)
return
}
if tokens[0] != name {
execTemplate(w, "error.html", "Wrong answer(s).")
removeLineStartingWith(pwResetPath, urlPart)
return
}
tokensUser, err := getFromFileEntryFor(loginsPath, name, 5)
if err != nil {
log.Fatal("Can't get entry for user", err)
}
if "" != tokensUser[2] &&
nil != bcrypt.CompareHashAndPassword([]byte(tokensUser[3]),
[]byte(r.FormValue("secanswer"))) {
execTemplate(w, "error.html", "Wrong answer(s).")
removeLineStartingWith(pwResetPath, urlPart)
return
}
hash, err := newPassword(w, r)
if err != nil {
execTemplate(w, "error.html", err.Error())
return
}
tokensUser[0] = hash
line := name + "\t" + strings.Join(tokensUser, "\t")
replaceLineStartingWith(loginsPath, tokens[0], line)
removeLineStartingWith(pwResetPath, urlPart)
execTemplate(w, "feedset.html", "")
}
func signUpFormHandler(w http.ResponseWriter, r *http.Request) {
if !signupOpen {
execTemplate(w, "nosignup.html", "")
return
}
execTemplate(w, "signupform.html", "")
}
func signUpHandler(w http.ResponseWriter, r *http.Request) {
if !signupOpen {
execTemplate(w, "error.html",
"Account creation currently not allowed.")
return
}
name := r.FormValue("name")
if !nameIsLegal(name) {
execTemplate(w, "error.html", "Illegal name.")
return
}
if _, err := getFromFileEntryFor(loginsPath, name, 5); err == nil {
execTemplate(w, "error.html", "Username taken.")
return
}
hash, err := newPassword(w, r)
if err != nil {
execTemplate(w, "error.html", err.Error())
return
}
mail := ""
if "" != r.FormValue("mail") {
mail, err = newMailAddress(w, r)
if err != nil {
execTemplate(w, "error.html", err.Error())
return
}
}
var secquestion, secanswer string
if "" != r.FormValue("secquestion") || "" != r.FormValue("secanswer") {
secquestion, secanswer, err = newSecurityQuestion(w, r)
if err != nil {
execTemplate(w, "error.html", err.Error())
return
}
}
appendToFile(loginsPath,
name+"\t"+hash+"\t"+mail+"\t"+secquestion+"\t"+secanswer)
execTemplate(w, "feedset.html", "")
}
func accountSetPwHandler(w http.ResponseWriter, r *http.Request) {
changeLoginField(w, r, newPassword, 0)
}
func accountSetMailHandler(w http.ResponseWriter, r *http.Request) {
changeLoginField(w, r, newMailAddress, 1)
}
func accountSetQuestionHandler(w http.ResponseWriter, r *http.Request) {
name, err := login(w, r)
if err != nil {
return
}
var secquestion, secanswer string
if "" != r.FormValue("secquestion") || "" != r.FormValue("secanswer") {
secquestion, secanswer, err = newSecurityQuestion(w, r)
if err != nil {
execTemplate(w, "error.html", err.Error())
return
}
}
tokens, err := getFromFileEntryFor(loginsPath, name, 5)
if err != nil {
log.Fatal("Can't get entry for user", err)
}
tokens[2] = secquestion
tokens[3] = secanswer
replaceLineStartingWith(loginsPath, name,
name+"\t"+strings.Join(tokens, "\t"))
execTemplate(w, "feedset.html", "")
}
func listHandler(w http.ResponseWriter, r *http.Request) {
file := openFile(loginsPath)
defer file.Close()
scanner := bufio.NewScanner(bufio.NewReader(file))
var dir []string
tokens := tokensFromLine(scanner, 5)
for 0 != len(tokens) {
dir = append(dir, tokens[0])
tokens = tokensFromLine(scanner, 5)
}
type data struct{ Dir []string }
err := templ.ExecuteTemplate(w, "list.html", data{Dir: dir})
if err != nil {
log.Fatal("Trouble executing template", err)
}
}
func twtxtPostHandler(w http.ResponseWriter, r *http.Request) {
name, err := login(w, r)
if err != nil {
return
}
text := r.FormValue("twt")
twtsFile := feedsPath + "/" + name
createFileIfNotExists(twtsFile)
text = strings.Replace(text, "\n", " ", -1)
appendToFile(twtsFile, time.Now().Format(time.RFC3339)+"\t"+text)
http.Redirect(w, r, "/"+feedsDir+"/"+name, 302)
}
func twtxtHandler(w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"]
if !onlyLegalRunes(name) {
execTemplate(w, "error.html", "Bad path.")
return
}
path := feedsPath + "/" + name
if _, err := os.Stat(path); err != nil {
execTemplate(w, "error.html", "Empty twtxt for user.")
return
}
http.ServeFile(w, r, path)
}
func handleRoutes() *mux.Router {
router := mux.NewRouter()
router.HandleFunc("/", handleTemplate("index.html", ""))
router.HandleFunc("/feeds", listHandler).Methods("GET")
router.HandleFunc("/feeds/", listHandler)
router.HandleFunc("/accountsetquestion",
handleTemplate("accountsetquestion.html", "")).Methods("GET")
router.HandleFunc("/accountsetquestion", accountSetQuestionHandler).
Methods("POST")
router.HandleFunc("/accountsetmail",
handleTemplate("accountsetmail.html", "")).Methods("GET")
router.HandleFunc("/accountsetmail", accountSetMailHandler).
Methods("POST")
router.HandleFunc("/accountsetpw", handleTemplate("accountsetpw.html",
"")).Methods("GET")
router.HandleFunc("/accountsetpw", accountSetPwHandler).Methods("POST")
router.HandleFunc("/account", handleTemplate("account.html", ""))
router.HandleFunc("/signup", signUpFormHandler).Methods("GET")
router.HandleFunc("/signup", signUpHandler).Methods("POST")
router.HandleFunc("/feeds", twtxtPostHandler).Methods("POST")
router.HandleFunc("/feeds/{name}", twtxtHandler)
router.HandleFunc("/info", handleTemplate("info.html", contact))
router.HandleFunc("/passwordreset", passwordResetRequestPostHandler).
Methods("POST")
router.HandleFunc("/passwordreset", passwordResetRequestGetHandler).
Methods("GET")
router.HandleFunc("/passwordreset/{secret}",
passwordResetLinkGetHandler).Methods("GET")
router.HandleFunc("/passwordreset/{secret}",
passwordResetLinkPostHandler).Methods("POST")
router.HandleFunc("/style.css",
func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, templPath+"/style.css")
})
return router
}