1package ext
2
3import (
4 "context"
5 "encoding/base64"
6 "errors"
7 "log/slog"
8 "net/http"
9 "time"
10
11 "git.sr.ht/~gabrielgio/img/pkg/database/repository"
12 "git.sr.ht/~gabrielgio/img/pkg/service"
13)
14
15func HTML(next http.HandlerFunc) http.HandlerFunc {
16 return func(w http.ResponseWriter, r *http.Request) {
17 w.Header().Set("Content-Type", "text/html")
18 next(w, r)
19 }
20}
21
22type (
23 User string
24
25 LogMiddleware struct {
26 logger *slog.Logger
27 }
28)
29
30const (
31 UserKey User = "user"
32)
33
34func NewLogMiddleare(log *slog.Logger) *LogMiddleware {
35 return &LogMiddleware{
36 logger: log,
37 }
38}
39
40func (l *LogMiddleware) HTTP(next http.HandlerFunc) http.HandlerFunc {
41 return func(w http.ResponseWriter, r *http.Request) {
42 start := time.Now()
43 next(w, r)
44 elapsed := time.Since(start)
45 l.logger.Info(
46 r.Method,
47 slog.Duration("elapsed", elapsed),
48 slog.String("path", r.URL.Path),
49 )
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
139 // if user has been set to context it is logged in already
140 token := GetUserFromCtx(r)
141 if token != nil {
142 next(w, r)
143 return
144 }
145
146 path := r.URL.Path
147 if path == "/initial" {
148 next(w, r)
149 return
150 }
151
152 exists, err := i.userRepository.Any(r.Context())
153 if err != nil {
154 InternalServerError(w, err)
155 return
156 }
157
158 if exists {
159 next(w, r)
160 return
161 }
162 http.Redirect(w, r, "/initial", http.StatusTemporaryRedirect)
163 }
164}