GoBlog/authentication.go

160 lines
4.0 KiB
Go
Raw Normal View History

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"
"time"
"github.com/dgrijalva/jwt-go"
)
func checkCredentials(username, password string) bool {
return username == appConfig.User.Nick && password == appConfig.User.Password
}
func checkUsername(username string) bool {
return username == appConfig.User.Nick
}
2020-12-15 16:40:14 +00:00
func jwtKey() []byte {
return []byte(appConfig.Server.JWTSecret)
}
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2021-02-20 22:35:16 +00:00
// Check if already logged in
if loggedIn, ok := r.Context().Value(loggedInKey).(bool); ok && loggedIn {
next.ServeHTTP(w, r)
return
}
// 1. Check BasicAuth
if username, password, ok := r.BasicAuth(); ok && checkCredentials(username, password) {
next.ServeHTTP(w, r)
return
}
// 2. Check JWT
2021-02-20 22:35:16 +00:00
if checkAuthToken(r) {
next.ServeHTTP(w, r)
return
2020-12-15 16:40:14 +00:00
}
// 3. Show login form
2020-12-15 16:40:14 +00:00
w.WriteHeader(http.StatusUnauthorized)
h, _ := json.Marshal(r.Header.Clone())
2021-02-17 07:23:03 +00:00
b, _ := io.ReadAll(io.LimitReader(r.Body, 2000000)) // Only allow 20 Megabyte
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-02-20 22:35:16 +00:00
render(w, r, templateLogin, &renderData{
2020-12-15 16:40:14 +00:00
Data: map[string]string{
"loginmethod": r.Method,
"loginheaders": base64.StdEncoding.EncodeToString(h),
"loginbody": base64.StdEncoding.EncodeToString(b),
},
})
})
}
2021-02-20 22:35:16 +00:00
func checkAuthToken(r *http.Request) bool {
if tokenCookie, err := r.Cookie("token"); err == nil {
claims := &authClaims{}
if tkn, err := jwt.ParseWithClaims(tokenCookie.Value, claims, func(t *jwt.Token) (interface{}, error) {
return jwtKey(), nil
}); err == nil && tkn.Valid &&
claims.TokenType == "login" &&
checkUsername(claims.Username) {
return true
}
}
return false
}
const loggedInKey requestContextKey = "loggedIn"
func checkLoggedIn(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if checkAuthToken(r) {
next.ServeHTTP(rw, r.WithContext(context.WithValue(r.Context(), loggedInKey, true)))
return
}
next.ServeHTTP(rw, r)
})
}
2020-12-15 16:40:14 +00:00
func checkIsLogin(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if !checkLogin(rw, r) {
next.ServeHTTP(rw, r)
}
})
}
func checkLogin(w http.ResponseWriter, r *http.Request) bool {
2021-02-24 12:16:33 +00:00
if r.Method != http.MethodPost {
return false
}
if r.Header.Get(contentType) != contentTypeWWWForm {
return false
}
if r.FormValue("loginaction") != "login" {
return false
}
// Do original request
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
}
// Check credential
if checkCredentials(r.FormValue("username"), r.FormValue("password")) {
tokenCookie, err := createTokenCookie(r.FormValue("username"))
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
return true
2021-01-23 16:24:47 +00:00
}
2021-02-24 12:16:33 +00:00
// Add cookie to original request
req.AddCookie(tokenCookie)
// Send cookie
http.SetCookie(w, tokenCookie)
2020-12-15 16:40:14 +00:00
}
2021-02-24 12:16:33 +00:00
// Serve original request
d.ServeHTTP(w, req)
return true
2020-12-15 16:40:14 +00:00
}
type authClaims struct {
*jwt.StandardClaims
TokenType string
Username string
}
func createTokenCookie(username string) (*http.Cookie, error) {
2020-12-15 16:40:14 +00:00
expiration := time.Now().Add(7 * 24 * time.Hour)
tokenString, err := jwt.NewWithClaims(jwt.SigningMethodHS256, &authClaims{
&jwt.StandardClaims{ExpiresAt: expiration.Unix()},
"login",
username,
}).SignedString(jwtKey())
2020-12-15 16:40:14 +00:00
if err != nil {
2021-01-23 16:24:47 +00:00
return nil, err
2020-12-15 16:40:14 +00:00
}
2021-01-23 16:24:47 +00:00
return &http.Cookie{
2020-12-15 16:40:14 +00:00
Name: "token",
Value: tokenString,
Expires: expiration,
2021-02-20 21:45:38 +00:00
Secure: httpsConfigured(),
2020-12-15 16:40:14 +00:00
HttpOnly: true,
2021-02-20 21:45:38 +00:00
SameSite: http.SameSiteLaxMode,
2021-01-23 16:24:47 +00:00
}, nil
2020-12-15 16:40:14 +00:00
}