1package ext
2
3import (
4 "context"
5 "encoding/base64"
6 "errors"
7 "net/http"
8 "time"
9
10 "github.com/sirupsen/logrus"
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 LogMiddleware struct {
24 entry *logrus.Entry
25}
26
27func NewLogMiddleare(log *logrus.Entry) *LogMiddleware {
28 return &LogMiddleware{
29 entry: log,
30 }
31}
32
33func (l *LogMiddleware) HTTP(next http.HandlerFunc) http.HandlerFunc {
34 return func(w http.ResponseWriter, r *http.Request) {
35 start := time.Now()
36 next(w, r)
37 elapsed := time.Since(start)
38 l.entry.
39 WithField("time", elapsed).
40 WithField("path", r.URL.Path).
41 Info(r.Method)
42 }
43}
44
45type AuthMiddleware struct {
46 key []byte
47 entry *logrus.Entry
48}
49
50func NewAuthMiddleware(key []byte, log *logrus.Entry) *AuthMiddleware {
51 return &AuthMiddleware{
52 key: key,
53 entry: log.WithField("context", "auth"),
54 }
55}
56
57func (a *AuthMiddleware) LoggedIn(next http.HandlerFunc) http.HandlerFunc {
58 return func(w http.ResponseWriter, r *http.Request) {
59 path := string(r.URL.Path)
60 if path == "/login" || path == "/initial" {
61 next(w, r)
62 return
63 }
64
65 redirectLogin := "/login?redirect=" + path
66 authBase64, err := r.Cookie("auth")
67 if errors.Is(err, http.ErrNoCookie) {
68 a.entry.Info("No auth provided")
69 http.Redirect(w, r, redirectLogin, http.StatusTemporaryRedirect)
70 return
71 }
72
73 auth, err := base64.StdEncoding.DecodeString(authBase64.Value)
74 if err != nil {
75 a.entry.Error(err)
76 return
77 }
78
79 token, err := service.ReadToken(auth, a.key)
80 if err != nil {
81 a.entry.Error(err)
82 http.Redirect(w, r, redirectLogin, http.StatusTemporaryRedirect)
83 return
84 }
85 r = r.WithContext(context.WithValue(r.Context(), "token", token))
86 a.entry.
87 WithField("userID", token.UserID).
88 WithField("username", token.Username).
89 Info("user recognized")
90 next(w, r)
91 }
92}
93
94func GetTokenFromCtx(w http.ResponseWriter, r *http.Request) *service.Token {
95 tokenValue := r.Context().Value("token")
96 if token, ok := tokenValue.(*service.Token); ok {
97 return token
98 }
99 return nil
100}
101
102type InitialSetupMiddleware struct {
103 userRepository repository.UserRepository
104}
105
106func NewInitialSetupMiddleware(userRepository repository.UserRepository) *InitialSetupMiddleware {
107 return &InitialSetupMiddleware{
108 userRepository: userRepository,
109 }
110}
111
112func (i *InitialSetupMiddleware) Check(next http.HandlerFunc) http.HandlerFunc {
113 return func(w http.ResponseWriter, r *http.Request) {
114 // if user has been set to context it is logged in already
115 token := GetTokenFromCtx(w, r)
116 if token == nil {
117 next(w, r)
118 return
119 }
120
121 path := r.URL.Path
122 if path == "/initial" {
123 next(w, r)
124 return
125 }
126
127 exists, err := i.userRepository.Any(r.Context())
128 if err != nil {
129 InternalServerError(w, err)
130 return
131 }
132
133 if exists {
134 next(w, r)
135 return
136 }
137 http.Redirect(w, r, "/initial", http.StatusTemporaryRedirect)
138 }
139}