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(next http.Handler) http.Handler {
117 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
118 didOrHandle := chi.URLParam(req, "user")
119
120 log.Println(didOrHandle)
121 id, err := auth.ResolveIdent(req.Context(), didOrHandle)
122 if err != nil {
123 // invalid did or handle
124 log.Println("failed to resolve did/handle")
125 w.WriteHeader(http.StatusNotFound)
126 return
127 }
128
129 ctx := context.WithValue(req.Context(), "resolvedId", *id)
130 next.ServeHTTP(w, req.WithContext(ctx))
131 })
132}
133
134func ResolveRepoKnot(s *State) Middleware {
135 return func(next http.Handler) http.Handler {
136 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
137 repoName := chi.URLParam(req, "repo")
138 id, ok := req.Context().Value("resolvedId").(identity.Identity)
139 if !ok {
140 log.Println("malformed middleware")
141 w.WriteHeader(http.StatusInternalServerError)
142 return
143 }
144
145 repo, err := s.db.GetRepo(id.DID.String(), repoName)
146 if err != nil {
147 // invalid did or handle
148 log.Println("failed to resolve repo")
149 w.WriteHeader(http.StatusNotFound)
150 return
151 }
152
153 ctx := context.WithValue(req.Context(), "knot", repo.Knot)
154 next.ServeHTTP(w, req.WithContext(ctx))
155 })
156 }
157}