...
1 package webserver
2
3 import (
4 "crypto/rand"
5 "errors"
6 "net/http"
7 "sync"
8 "time"
9
10 jsoniter "github.com/json-iterator/go"
11
12 "github.com/golang-jwt/jwt/v5"
13 )
14
15 type loginReq struct {
16 Username string `json:"user"`
17 Password string `json:"password"`
18 }
19
20 func (s *WebUIServer) IsCorrectPassword(lr loginReq) bool {
21 return (s.WebUser+s.WebPassword == "") ||
22 (s.WebUser == lr.Username && s.WebPassword == lr.Password)
23 }
24
25 func (s *WebUIServer) handleLogin(w http.ResponseWriter, r *http.Request) {
26 var (
27 err error
28 lr loginReq
29 token string
30 )
31
32 defer func() {
33 if err != nil {
34 http.Error(w, err.Error(), http.StatusInternalServerError)
35 }
36 }()
37
38 switch r.Method {
39 case "POST":
40 if err = jsoniter.ConfigFastest.NewDecoder(r.Body).Decode(&lr); err != nil {
41 return
42 }
43 if !s.IsCorrectPassword(lr) {
44 http.Error(w, "can not authenticate this user", http.StatusUnauthorized)
45 return
46 }
47 if token, err = generateJWT(lr.Username); err != nil {
48 return
49 }
50 _, err = w.Write([]byte(token))
51
52 default:
53 w.Header().Set("Allow", "POST")
54 http.Error(w, "only POST method is allowed", http.StatusMethodNotAllowed)
55 return
56 }
57 }
58
59 type EnsureAuth struct {
60 handler http.HandlerFunc
61 }
62
63 func (ea *EnsureAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) {
64 if err := validateToken(r); err != nil {
65 http.Error(w, err.Error(), http.StatusUnauthorized)
66 return
67 }
68 ea.handler(w, r)
69 }
70
71 func NewEnsureAuth(handlerToWrap http.HandlerFunc) *EnsureAuth {
72 return &EnsureAuth{handlerToWrap}
73 }
74
75 var (
76 secretKeyOnce sync.Once
77 secretKey []byte
78 )
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94 func jwtSecretKey() []byte {
95 secretKeyOnce.Do(func() {
96 secretKey = make([]byte, 32)
97 if _, err := rand.Read(secretKey); err != nil {
98
99
100 panic("webserver: unable to generate JWT secret key: " + err.Error())
101 }
102 })
103 return secretKey
104 }
105
106 func generateJWT(username string) (string, error) {
107 token := jwt.New(jwt.SigningMethodHS256)
108 claims := token.Claims.(jwt.MapClaims)
109
110 claims["authorized"] = true
111 claims["username"] = username
112 claims["exp"] = time.Now().Add(time.Hour * 8).Unix()
113
114 return token.SignedString(jwtSecretKey())
115 }
116
117 func validateToken(r *http.Request) (err error) {
118 var t string
119 if r.Header["Token"] == nil {
120 t = r.URL.Query().Get("Token")
121 } else {
122 t = r.Header["Token"][0]
123 }
124 if t == "" {
125 return errors.New("can not find token in header")
126 }
127
128 _, err = jwt.Parse(t,
129 func(_ *jwt.Token) (any, error) {
130 return jwtSecretKey(), nil
131 },
132 jwt.WithExpirationRequired(),
133 jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}))
134 return err
135 }
136