1package ext
2
3import (
4 "context"
5 "encoding/base64"
6 "errors"
7 "net/http"
8 "time"
9
10 "golang.org/x/exp/slog"
11
12 "git.sr.ht/~gabrielgio/img/pkg/database/repository"
13 "git.sr.ht/~gabrielgio/img/pkg/service"
14)
15
16func HTML(next http.HandlerFunc) http.HandlerFunc {
17 return func(w http.ResponseWriter, r *http.Request) {
18 w.Header().Set("Content-Type", "text/html")
19 next(w, r)
20 }
21}
22
23type (
24 User string
25
26 LogMiddleware struct {
27 logger *slog.Logger
28 }
29)
30
31const (
32 UserKey User = "user"
33)
34
35func NewLogMiddleare(log *slog.Logger) *LogMiddleware {
36 return &LogMiddleware{
37 logger: log,
38 }
39}
40
41func (l *LogMiddleware) HTTP(next http.HandlerFunc) http.HandlerFunc {
42 return func(w http.ResponseWriter, r *http.Request) {
43 start := time.Now()
44 next(w, r)
45 elapsed := time.Since(start)
46 l.logger.Info(
47 r.Method,
48 slog.Duration("elapsed", elapsed),
49 slog.String("path", r.URL.Path),
50 )
51 }
52}
53
54type AuthMiddleware struct {
55 key []byte
56 logger *slog.Logger
57 userRepository repository.UserRepository
58}
59
60func NewAuthMiddleware(
61 key []byte,
62 logger *slog.Logger,
63 userRepository repository.UserRepository,
64) *AuthMiddleware {
65 return &AuthMiddleware{
66 key: key,
67 logger: logger,
68 userRepository: userRepository,
69 }
70}
71
72func (a *AuthMiddleware) LoggedIn(next http.HandlerFunc) http.HandlerFunc {
73 return func(w http.ResponseWriter, r *http.Request) {
74 path := r.URL.Path
75 if path == "/login" || path == "/initial" {
76 next(w, r)
77 return
78 }
79
80 redirectLogin := "/login?redirect=" + path
81 authBase64, err := r.Cookie("auth")
82 if errors.Is(err, http.ErrNoCookie) {
83 a.logger.Info("No auth provided")
84 http.Redirect(w, r, redirectLogin, http.StatusTemporaryRedirect)
85 return
86 }
87
88 auth, err := base64.StdEncoding.DecodeString(authBase64.Value)
89 if err != nil {
90 a.logger.Error(err.Error())
91 return
92 }
93
94 token, err := service.ReadToken(auth, a.key)
95 if err != nil {
96 a.logger.Error(err.Error())
97 http.Redirect(w, r, redirectLogin, http.StatusTemporaryRedirect)
98 return
99 }
100
101 user, err := a.userRepository.Get(r.Context(), token.UserID)
102 if err != nil {
103 a.logger.Error(err.Error())
104 return
105 }
106
107 r = r.WithContext(context.WithValue(r.Context(), UserKey, user))
108 a.logger.
109 Info(
110 "user recognized",
111 slog.Uint64("userid", uint64(token.UserID)),
112 slog.String("username", token.Username),
113 )
114 next(w, r)
115 }
116}
117
118func GetUserFromCtx(r *http.Request) *repository.User {
119 tokenValue := r.Context().Value(UserKey)
120 if token, ok := tokenValue.(*repository.User); ok {
121 return token
122 }
123 return nil
124}
125
126type InitialSetupMiddleware struct {
127 userRepository repository.UserRepository
128}
129
130func NewInitialSetupMiddleware(userRepository repository.UserRepository) *InitialSetupMiddleware {
131 return &InitialSetupMiddleware{
132 userRepository: userRepository,
133 }
134}
135
136func (i *InitialSetupMiddleware) Check(next http.HandlerFunc) http.HandlerFunc {
137 return func(w http.ResponseWriter, r *http.Request) {
138 // if user has been set to context it is logged in already
139 token := GetUserFromCtx(r)
140 if token != nil {
141 next(w, r)
142 return
143 }
144
145 path := r.URL.Path
146 if path == "/initial" {
147 next(w, r)
148 return
149 }
150
151 exists, err := i.userRepository.Any(r.Context())
152 if err != nil {
153 InternalServerError(w, err)
154 return
155 }
156
157 if exists {
158 next(w, r)
159 return
160 }
161 http.Redirect(w, r, "/initial", http.StatusTemporaryRedirect)
162 }
163}