this repo has no description
1package state
2
3import (
4 "context"
5 "log"
6 "net/http"
7 "strings"
8 "time"
9
10 comatproto "github.com/bluesky-social/indigo/api/atproto"
11 "github.com/bluesky-social/indigo/atproto/identity"
12 "github.com/bluesky-social/indigo/xrpc"
13 "github.com/go-chi/chi/v5"
14 "github.com/sotangled/tangled/appview"
15 "github.com/sotangled/tangled/appview/auth"
16)
17
18type Middleware func(http.Handler) http.Handler
19
20func AuthMiddleware(s *State) Middleware {
21 return func(next http.Handler) http.Handler {
22 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
23 session, _ := s.auth.Store.Get(r, appview.SessionName)
24 authorized, ok := session.Values[appview.SessionAuthenticated].(bool)
25 if !ok || !authorized {
26 log.Printf("not logged in, redirecting")
27 http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
28 return
29 }
30
31 // refresh if nearing expiry
32 // TODO: dedup with /login
33 expiryStr := session.Values[appview.SessionExpiry].(string)
34 expiry, err := time.Parse(time.RFC3339, expiryStr)
35 if err != nil {
36 log.Println("invalid expiry time", err)
37 return
38 }
39 pdsUrl := session.Values[appview.SessionPds].(string)
40 did := session.Values[appview.SessionDid].(string)
41 refreshJwt := session.Values[appview.SessionRefreshJwt].(string)
42
43 if time.Now().After(expiry) {
44 log.Println("token expired, refreshing ...")
45
46 client := xrpc.Client{
47 Host: pdsUrl,
48 Auth: &xrpc.AuthInfo{
49 Did: did,
50 AccessJwt: refreshJwt,
51 RefreshJwt: refreshJwt,
52 },
53 }
54 atSession, err := comatproto.ServerRefreshSession(r.Context(), &client)
55 if err != nil {
56 log.Println(err)
57 return
58 }
59
60 sessionish := auth.RefreshSessionWrapper{atSession}
61
62 err = s.auth.StoreSession(r, w, &sessionish, pdsUrl)
63 if err != nil {
64 log.Printf("failed to store session for did: %s\n: %s", atSession.Did, err)
65 return
66 }
67
68 log.Println("successfully refreshed token")
69 }
70
71 next.ServeHTTP(w, r)
72 })
73 }
74}
75
76func RoleMiddleware(s *State, group string) Middleware {
77 return func(next http.Handler) http.Handler {
78 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
79 // requires auth also
80 actor := s.auth.GetUser(r)
81 if actor == nil {
82 // we need a logged in user
83 log.Printf("not logged in, redirecting")
84 http.Error(w, "Forbiden", http.StatusUnauthorized)
85 return
86 }
87 domain := chi.URLParam(r, "domain")
88 if domain == "" {
89 http.Error(w, "malformed url", http.StatusBadRequest)
90 return
91 }
92
93 ok, err := s.enforcer.E.HasGroupingPolicy(actor.Did, group, domain)
94 if err != nil || !ok {
95 // we need a logged in user
96 log.Printf("%s does not have perms of a %s in domain %s", actor.Did, group, domain)
97 http.Error(w, "Forbiden", http.StatusUnauthorized)
98 return
99 }
100
101 next.ServeHTTP(w, r)
102 })
103 }
104}
105
106func StripLeadingAt(next http.Handler) http.Handler {
107 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
108 path := req.URL.Path
109 if strings.HasPrefix(path, "/@") {
110 req.URL.Path = "/" + strings.TrimPrefix(path, "/@")
111 }
112 next.ServeHTTP(w, req)
113 })
114}
115
116func ResolveIdent(s *State) Middleware {
117 return func(next http.Handler) http.Handler {
118 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
119 start := time.Now()
120 didOrHandle := chi.URLParam(req, "user")
121
122 log.Println(didOrHandle)
123 id, err := s.resolver.ResolveIdent(req.Context(), didOrHandle)
124 if err != nil {
125 // invalid did or handle
126 log.Println("failed to resolve did/handle")
127 w.WriteHeader(http.StatusNotFound)
128 return
129 }
130
131 ctx := context.WithValue(req.Context(), "resolvedId", *id)
132
133 elapsed := time.Since(start)
134 log.Println("Execution time:", elapsed)
135 next.ServeHTTP(w, req.WithContext(ctx))
136 })
137 }
138}
139
140func ResolveRepoKnot(s *State) Middleware {
141 return func(next http.Handler) http.Handler {
142 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
143 repoName := chi.URLParam(req, "repo")
144 id, ok := req.Context().Value("resolvedId").(identity.Identity)
145 if !ok {
146 log.Println("malformed middleware")
147 w.WriteHeader(http.StatusInternalServerError)
148 return
149 }
150
151 repo, err := s.db.GetRepo(id.DID.String(), repoName)
152 if err != nil {
153 // invalid did or handle
154 log.Println("failed to resolve repo")
155 w.WriteHeader(http.StatusNotFound)
156 return
157 }
158
159 ctx := context.WithValue(req.Context(), "knot", repo.Knot)
160 next.ServeHTTP(w, req.WithContext(ctx))
161 })
162 }
163}