-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathadmin_session.go
More file actions
108 lines (98 loc) · 3.12 KB
/
Copy pathadmin_session.go
File metadata and controls
108 lines (98 loc) · 3.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package main
import (
"log/slog"
"net/http"
"time"
"github.com/golang-jwt/jwt/v5"
)
const flashCookieName = "vp_flash"
// flashMessages maps opaque flash codes to the fixed strings the layout
// renders. Cookie values are attacker-writable, so free text is never
// rendered — unknown codes yield nothing (spec §4.6).
var flashMessages = map[string]string{
"vehicle_created": "Vehicle created.",
"vehicle_updated": "Vehicle updated.",
"vehicle_deactivated": "Vehicle deactivated.",
"vehicle_activated": "Vehicle reactivated.",
"user_created": "User created.",
"user_updated": "User updated.",
"user_deactivated": "User deactivated.",
"user_activated": "User reactivated.",
"vehicle_assigned": "Vehicle assigned.",
"vehicle_unassigned": "Vehicle unassigned.",
}
func setSessionCookie(w http.ResponseWriter, r *http.Request, token string, trustProxy bool) {
http.SetCookie(w, &http.Cookie{
Name: sessionCookieName,
Value: token,
Path: "/",
MaxAge: int((24 * time.Hour).Seconds()),
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
Secure: requestIsSecure(r, trustProxy),
})
}
func clearSessionCookie(w http.ResponseWriter) {
http.SetCookie(w, &http.Cookie{
Name: sessionCookieName,
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
}
// adminClaimsFromCookie validates the session cookie's JWT via the shared
// parseSessionToken path and additionally requires the admin role.
func adminClaimsFromCookie(r *http.Request, secret []byte) (jwt.MapClaims, bool) {
c, err := r.Cookie(sessionCookieName)
if err != nil || c.Value == "" {
return nil, false
}
claims, err := parseSessionToken(c.Value, secret)
if err != nil {
return nil, false
}
if role, _ := claims["role"].(string); role != roleAdmin {
return nil, false
}
return claims, true
}
// requireAdminPage guards HTML admin pages: unauthenticated or non-admin
// visitors are redirected to the login page (303) rather than given JSON.
func requireAdminPage(secret []byte) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims, ok := adminClaimsFromCookie(r, secret)
if !ok {
http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
return
}
ctx := contextWithClaims(r.Context(), claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
func setFlash(w http.ResponseWriter, code string) {
http.SetCookie(w, &http.Cookie{
Name: flashCookieName, Value: code, Path: "/", MaxAge: 60,
HttpOnly: true, SameSite: http.SameSiteLaxMode,
})
}
// takeFlash reads, clears, and resolves the flash cookie to its message.
func takeFlash(w http.ResponseWriter, r *http.Request) string {
c, err := r.Cookie(flashCookieName)
if err != nil || c.Value == "" {
return ""
}
http.SetCookie(w, &http.Cookie{
Name: flashCookieName, Value: "", Path: "/", MaxAge: -1,
HttpOnly: true, SameSite: http.SameSiteLaxMode,
})
msg, ok := flashMessages[c.Value]
if !ok {
slog.Debug("unknown flash code ignored", "code", c.Value)
return ""
}
return msg
}