2020-12-15 16:40:14 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2021-02-20 22:35:16 +00:00
|
|
|
"context"
|
2020-12-15 16:40:14 +00:00
|
|
|
"encoding/base64"
|
2021-01-21 16:59:47 +00:00
|
|
|
"encoding/json"
|
2020-12-15 16:40:14 +00:00
|
|
|
"io"
|
|
|
|
"net/http"
|
2021-07-24 11:35:26 +00:00
|
|
|
"strings"
|
2020-12-15 16:40:14 +00:00
|
|
|
|
2021-02-28 07:57:15 +00:00
|
|
|
"github.com/pquerna/otp/totp"
|
2021-06-28 20:17:18 +00:00
|
|
|
"go.goblog.app/app/pkgs/contenttype"
|
2020-12-15 16:40:14 +00:00
|
|
|
)
|
|
|
|
|
2021-07-24 11:35:26 +00:00
|
|
|
const loggedInKey contextKey = "loggedIn"
|
|
|
|
|
|
|
|
// Check if credentials are correct
|
2021-06-06 12:39:42 +00:00
|
|
|
func (a *goBlog) checkCredentials(username, password, totpPasscode string) bool {
|
|
|
|
return username == a.cfg.User.Nick &&
|
|
|
|
password == a.cfg.User.Password &&
|
|
|
|
(a.cfg.User.TOTP == "" || totp.Validate(totpPasscode, a.cfg.User.TOTP))
|
2020-12-15 16:40:14 +00:00
|
|
|
}
|
|
|
|
|
2021-07-24 11:35:26 +00:00
|
|
|
// Check if app passwords are correct
|
2021-06-06 12:39:42 +00:00
|
|
|
func (a *goBlog) checkAppPasswords(username, password string) bool {
|
|
|
|
for _, apw := range a.cfg.User.AppPasswords {
|
2021-02-28 07:57:15 +00:00
|
|
|
if apw.Username == username && apw.Password == password {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
2021-02-06 21:53:56 +00:00
|
|
|
}
|
|
|
|
|
2021-07-24 11:35:26 +00:00
|
|
|
// Check if cookie is known and logged in
|
|
|
|
func (a *goBlog) checkLoginCookie(r *http.Request) bool {
|
|
|
|
ses, err := a.loginSessions.Get(r, "l")
|
|
|
|
if err == nil && ses != nil {
|
|
|
|
if login, ok := ses.Values["login"]; ok && login.(bool) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
2020-12-15 16:40:14 +00:00
|
|
|
}
|
|
|
|
|
2021-07-24 11:35:26 +00:00
|
|
|
// Middleware to force login
|
2021-06-06 12:39:42 +00:00
|
|
|
func (a *goBlog) authMiddleware(next http.Handler) http.Handler {
|
2020-12-15 16:40:14 +00:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2021-07-24 11:35:26 +00:00
|
|
|
// Check if already logged in
|
|
|
|
if a.isLoggedIn(r) {
|
2021-02-06 21:53:56 +00:00
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
return
|
|
|
|
}
|
2021-07-24 11:35:26 +00:00
|
|
|
// Show login form
|
2021-03-06 18:29:15 +00:00
|
|
|
w.Header().Set("Cache-Control", "no-store,max-age=0")
|
2021-11-15 11:49:18 +00:00
|
|
|
w.Header().Set("X-Robots-Tag", "noindex")
|
2021-07-24 11:35:26 +00:00
|
|
|
h, _ := json.Marshal(r.Header)
|
|
|
|
b, _ := io.ReadAll(io.LimitReader(r.Body, 20*1000*1000)) // Only allow 20 MB
|
2020-12-15 16:40:14 +00:00
|
|
|
_ = r.Body.Close()
|
|
|
|
if len(b) == 0 {
|
|
|
|
// Maybe it's a form
|
|
|
|
_ = r.ParseForm()
|
|
|
|
b = []byte(r.PostForm.Encode())
|
|
|
|
}
|
2021-06-06 12:39:42 +00:00
|
|
|
a.render(w, r, templateLogin, &renderData{
|
2021-02-28 07:57:15 +00:00
|
|
|
Data: map[string]interface{}{
|
2020-12-15 16:40:14 +00:00
|
|
|
"loginmethod": r.Method,
|
|
|
|
"loginheaders": base64.StdEncoding.EncodeToString(h),
|
|
|
|
"loginbody": base64.StdEncoding.EncodeToString(b),
|
2021-06-06 12:39:42 +00:00
|
|
|
"totp": a.cfg.User.TOTP != "",
|
2020-12-15 16:40:14 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-07-24 11:35:26 +00:00
|
|
|
// Middleware to check if the request is a login request
|
2021-06-06 12:39:42 +00:00
|
|
|
func (a *goBlog) checkIsLogin(next http.Handler) http.Handler {
|
2020-12-15 16:40:14 +00:00
|
|
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
2021-06-06 12:39:42 +00:00
|
|
|
if !a.checkLogin(rw, r) {
|
2020-12-15 16:40:14 +00:00
|
|
|
next.ServeHTTP(rw, r)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-07-24 11:35:26 +00:00
|
|
|
// Checks login and returns true if it already served an error
|
2021-06-06 12:39:42 +00:00
|
|
|
func (a *goBlog) checkLogin(w http.ResponseWriter, r *http.Request) bool {
|
2021-02-24 12:16:33 +00:00
|
|
|
if r.Method != http.MethodPost {
|
|
|
|
return false
|
|
|
|
}
|
2021-07-24 11:35:26 +00:00
|
|
|
if !strings.Contains(r.Header.Get(contentType), contenttype.WWWForm) {
|
2021-02-24 12:16:33 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
if r.FormValue("loginaction") != "login" {
|
|
|
|
return false
|
|
|
|
}
|
2021-02-28 07:57:15 +00:00
|
|
|
// Check credential
|
2021-06-06 12:39:42 +00:00
|
|
|
if !a.checkCredentials(r.FormValue("username"), r.FormValue("password"), r.FormValue("token")) {
|
|
|
|
a.serveError(w, r, "Incorrect credentials", http.StatusUnauthorized)
|
2021-02-28 07:57:15 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
// Prepare original request
|
2021-02-24 12:16:33 +00:00
|
|
|
loginbody, _ := base64.StdEncoding.DecodeString(r.FormValue("loginbody"))
|
|
|
|
req, _ := http.NewRequest(r.FormValue("loginmethod"), r.RequestURI, bytes.NewReader(loginbody))
|
|
|
|
// Copy original headers
|
|
|
|
loginheaders, _ := base64.StdEncoding.DecodeString(r.FormValue("loginheaders"))
|
|
|
|
var headers http.Header
|
|
|
|
_ = json.Unmarshal(loginheaders, &headers)
|
|
|
|
for k, v := range headers {
|
|
|
|
req.Header[k] = v
|
|
|
|
}
|
2021-02-28 07:57:15 +00:00
|
|
|
// Cookie
|
2021-06-06 12:39:42 +00:00
|
|
|
ses, err := a.loginSessions.Get(r, "l")
|
2021-02-28 07:57:15 +00:00
|
|
|
if err != nil {
|
2021-06-06 12:39:42 +00:00
|
|
|
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
2021-02-28 07:57:15 +00:00
|
|
|
return true
|
2020-12-15 16:40:14 +00:00
|
|
|
}
|
2021-05-14 16:24:02 +00:00
|
|
|
ses.Values["login"] = true
|
2021-07-24 11:35:26 +00:00
|
|
|
err = a.loginSessions.Save(r, w, ses)
|
2021-05-14 16:24:02 +00:00
|
|
|
if err != nil {
|
2021-06-06 12:39:42 +00:00
|
|
|
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
2021-05-14 16:24:02 +00:00
|
|
|
return true
|
|
|
|
}
|
2021-02-24 12:16:33 +00:00
|
|
|
// Serve original request
|
2021-07-25 08:53:12 +00:00
|
|
|
setLoggedIn(req, true)
|
2021-06-06 12:39:42 +00:00
|
|
|
a.d.ServeHTTP(w, req)
|
2021-02-24 12:16:33 +00:00
|
|
|
return true
|
2020-12-15 16:40:14 +00:00
|
|
|
}
|
|
|
|
|
2021-07-24 11:35:26 +00:00
|
|
|
func (a *goBlog) isLoggedIn(r *http.Request) bool {
|
|
|
|
// Check if context key already set
|
2021-07-25 08:53:12 +00:00
|
|
|
if loggedIn, ok := r.Context().Value(loggedInKey).(bool); ok {
|
|
|
|
return loggedIn
|
2021-07-24 11:35:26 +00:00
|
|
|
}
|
|
|
|
// Check app passwords
|
|
|
|
if username, password, ok := r.BasicAuth(); ok && a.checkAppPasswords(username, password) {
|
2021-07-25 08:53:12 +00:00
|
|
|
setLoggedIn(r, true)
|
2021-07-24 11:35:26 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
// Check session cookie
|
|
|
|
if a.checkLoginCookie(r) {
|
2021-07-25 08:53:12 +00:00
|
|
|
setLoggedIn(r, true)
|
2021-07-24 11:35:26 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
// Not logged in
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set request context value
|
2021-07-25 08:53:12 +00:00
|
|
|
func setLoggedIn(r *http.Request, loggedIn bool) {
|
|
|
|
// Overwrite the value of r (r is a pointer)
|
|
|
|
(*r) = *(r.WithContext(context.WithValue(r.Context(), loggedInKey, loggedIn)))
|
2021-07-24 11:35:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// HandlerFunc to redirect to home after login
|
2021-02-24 15:01:10 +00:00
|
|
|
// Need to set auth middleware!
|
|
|
|
func serveLogin(w http.ResponseWriter, r *http.Request) {
|
|
|
|
http.Redirect(w, r, "/", http.StatusFound)
|
|
|
|
}
|
|
|
|
|
2021-07-24 11:35:26 +00:00
|
|
|
// HandlerFunc to delete login session and cookie
|
2021-06-06 12:39:42 +00:00
|
|
|
func (a *goBlog) serveLogout(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if ses, err := a.loginSessions.Get(r, "l"); err == nil && ses != nil {
|
|
|
|
_ = a.loginSessions.Delete(r, w, ses)
|
2021-05-14 16:24:02 +00:00
|
|
|
}
|
2021-02-24 15:01:10 +00:00
|
|
|
http.Redirect(w, r, "/", http.StatusFound)
|
|
|
|
}
|