323 行
9.0 KiB
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
|
|
}
|