+19
-156
auth_handlers.go
+19
-156
auth_handlers.go
···
1
1
package main
2
2
3
3
import (
4
-
"bytes"
5
-
"encoding/json"
6
4
"fmt"
7
-
"io"
8
5
"log/slog"
9
6
"net/http"
10
-
"strconv"
11
-
"time"
12
7
13
-
"github.com/golang-jwt/jwt/v5"
14
8
"github.com/willdot/bskyfeedgen/frontend"
15
9
)
16
10
17
11
const (
18
-
bskyBaseURL = "https://bsky.social/xrpc"
19
-
jwtCookieName = "JWT"
20
-
didCookieName = "DID"
12
+
bskyBaseURL = "https://bsky.social/xrpc"
21
13
)
22
14
23
15
type loginRequest struct {
24
-
Handle string `json:"handle"`
25
-
AppPassword string `json:"appPassword"`
26
-
}
27
-
28
-
type BskyAuth struct {
29
-
AccessJwt string `json:"accessJwt"`
30
-
Did string `json:"did"`
31
-
}
32
-
33
-
func (s *Server) HandleLogin(w http.ResponseWriter, r *http.Request) {
34
-
b, err := io.ReadAll(r.Body)
35
-
if err != nil {
36
-
slog.Error("failed to read body", "error", err)
37
-
_ = frontend.LoginForm("", "bad request").Render(r.Context(), w)
38
-
return
39
-
}
40
-
41
-
var loginReq loginRequest
42
-
err = json.Unmarshal(b, &loginReq)
43
-
if err != nil {
44
-
slog.Error("failed to unmarshal body", "error", err)
45
-
_ = frontend.LoginForm("", "bad request").Render(r.Context(), w)
46
-
return
47
-
}
48
-
url := fmt.Sprintf("%s/com.atproto.server.createsession", bskyBaseURL)
49
-
50
-
requestData := map[string]interface{}{
51
-
"identifier": loginReq.Handle,
52
-
"password": loginReq.AppPassword,
53
-
}
54
-
55
-
data, err := json.Marshal(requestData)
56
-
if err != nil {
57
-
slog.Error("failed marshal POST request to sign into Bsky", "error", err)
58
-
_ = frontend.LoginForm(loginReq.Handle, "internal error").Render(r.Context(), w)
59
-
return
60
-
}
61
-
62
-
reader := bytes.NewReader(data)
63
-
64
-
req, err := http.NewRequest("POST", url, reader)
65
-
if err != nil {
66
-
slog.Error("failed to create POST request to sign into Bsky", "error", err)
67
-
_ = frontend.LoginForm(loginReq.Handle, "internal error").Render(r.Context(), w)
68
-
return
69
-
}
70
-
71
-
req.Header.Add("Content-Type", "application/json")
72
-
73
-
// TODO: create a client somewhere
74
-
res, err := http.DefaultClient.Do(req)
75
-
if err != nil {
76
-
slog.Error("failed to make POST request to sign into Bsky", "error", err)
77
-
_ = frontend.LoginForm(loginReq.Handle, "internal error").Render(r.Context(), w)
78
-
return
79
-
}
80
-
81
-
defer res.Body.Close()
82
-
83
-
slog.Info("bsky resp", "code", res.StatusCode)
84
-
85
-
if res.StatusCode != 200 {
86
-
slog.Error("failed to log into bluesky", "status code", res.StatusCode)
87
-
_ = frontend.LoginForm(loginReq.Handle, "not authorized").Render(r.Context(), w)
88
-
return
89
-
}
90
-
91
-
resBody, err := io.ReadAll(res.Body)
92
-
if err != nil {
93
-
slog.Error("failed read response from Bsky login", "error", err)
94
-
_ = frontend.LoginForm(loginReq.Handle, "internal error").Render(r.Context(), w)
95
-
return
96
-
}
97
-
98
-
var loginResp BskyAuth
99
-
err = json.Unmarshal(resBody, &loginResp)
100
-
if err != nil {
101
-
slog.Error("failed unmarshal response from Bsky login", "error", err)
102
-
_ = frontend.LoginForm(loginReq.Handle, "internal error").Render(r.Context(), w)
103
-
return
104
-
}
105
-
106
-
http.SetCookie(w, &http.Cookie{
107
-
Name: jwtCookieName,
108
-
Value: loginResp.AccessJwt,
109
-
})
110
-
111
-
http.SetCookie(w, &http.Cookie{
112
-
Name: didCookieName,
113
-
Value: loginResp.Did,
114
-
})
115
-
116
-
http.Redirect(w, r, "/", http.StatusOK)
117
-
}
118
-
119
-
func (s *Server) HandleSignOut(w http.ResponseWriter, r *http.Request) {
120
-
http.SetCookie(w, &http.Cookie{
121
-
Name: jwtCookieName,
122
-
Value: "",
123
-
})
124
-
125
-
http.SetCookie(w, &http.Cookie{
126
-
Name: didCookieName,
127
-
Value: "",
128
-
})
129
-
130
-
_ = frontend.Login("", "").Render(r.Context(), w)
16
+
Handle string `json:"handle"`
131
17
}
132
18
133
19
func (s *Server) authMiddleware(next func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
134
20
return func(w http.ResponseWriter, r *http.Request) {
135
-
jwtCookie, err := r.Cookie(jwtCookieName)
136
-
if err != nil {
137
-
slog.Error("read JWT cookie", "error", err)
138
-
_ = frontend.Login("", "").Render(r.Context(), w)
139
-
return
140
-
}
141
-
if jwtCookie == nil {
142
-
slog.Error("missing JWT cookie")
21
+
_, ok := s.getDidFromSession(r)
22
+
if !ok {
23
+
slog.Warn("did not found in session")
143
24
_ = frontend.Login("", "").Render(r.Context(), w)
144
25
return
145
26
}
146
27
147
-
didCookie, err := r.Cookie(didCookieName)
148
-
if err != nil {
149
-
slog.Error("read DID cookie", "error", err)
150
-
_ = frontend.Login("", "").Render(r.Context(), w)
151
-
return
152
-
}
153
-
if didCookie == nil {
154
-
slog.Error("missing DID cookie")
155
-
_ = frontend.Login("", "").Render(r.Context(), w)
156
-
return
157
-
}
28
+
next(w, r)
29
+
}
30
+
}
158
31
159
-
claims := jwt.MapClaims{}
160
-
_, _, err = jwt.NewParser().ParseUnverified(jwtCookie.Value, &claims)
161
-
if err != nil {
162
-
slog.Error("parsing JWT", "error", err)
163
-
_ = frontend.Login("", "").Render(r.Context(), w)
164
-
return
165
-
}
32
+
func (s *Server) getDidFromSession(r *http.Request) (string, bool) {
33
+
session, err := s.sessionStore.Get(r, "oauth-session")
34
+
if err != nil {
35
+
slog.Error("getting session", "error", err)
36
+
return "", false
37
+
}
166
38
167
-
if expiry, ok := claims["exp"].(string); ok {
168
-
expiryInt, err := strconv.Atoi(expiry)
169
-
if err != nil {
170
-
slog.Error("invalid claims from token", "error", err)
171
-
_ = frontend.Login("", "").Render(r.Context(), w)
172
-
return
173
-
}
39
+
did, ok := session.Values["did"]
40
+
if !ok {
41
+
return "", false
42
+
}
174
43
175
-
if time.Now().Unix() > int64(expiryInt) {
176
-
_ = frontend.Login("", "").Render(r.Context(), w)
177
-
return
178
-
}
179
-
}
180
-
next(w, r)
181
-
}
44
+
return fmt.Sprintf("%s", did), true
182
45
}
+9
-9
bookmark_handler.go
+9
-9
bookmark_handler.go
···
17
17
)
18
18
19
19
func (s *Server) HandleAddBookmark(w http.ResponseWriter, r *http.Request) {
20
-
usersDid, err := getUsersDidFromRequestCookie(r)
21
-
if err != nil {
22
-
slog.Error("getting users did from request", "error", err)
20
+
usersDid, ok := s.getDidFromSession(r)
21
+
if !ok {
22
+
slog.Warn("did not found in session")
23
23
_ = frontend.Login("", "").Render(r.Context(), w)
24
24
return
25
25
}
···
115
115
func (s *Server) HandleDeleteBookmark(w http.ResponseWriter, r *http.Request) {
116
116
rKey := r.PathValue("rkey")
117
117
118
-
usersDid, err := getUsersDidFromRequestCookie(r)
119
-
if err != nil {
120
-
slog.Error("getting users did from request", "error", err)
118
+
usersDid, ok := s.getDidFromSession(r)
119
+
if !ok {
120
+
slog.Warn("did not found in session")
121
121
_ = frontend.Login("", "").Render(r.Context(), w)
122
122
return
123
123
}
···
149
149
}
150
150
151
151
func (s *Server) HandleGetBookmarks(w http.ResponseWriter, r *http.Request) {
152
-
usersDid, err := getUsersDidFromRequestCookie(r)
153
-
if err != nil {
154
-
slog.Error("getting users did from request", "error", err)
152
+
usersDid, ok := s.getDidFromSession(r)
153
+
if !ok {
154
+
slog.Warn("did not found in session")
155
155
_ = frontend.Login("", "").Render(r.Context(), w)
156
156
return
157
157
}
+4
-4
frontend/base_templ.go
+4
-4
frontend/base_templ.go
···
1
1
// Code generated by templ - DO NOT EDIT.
2
2
3
-
// templ: version: v0.2.793
3
+
// templ: version: v0.3.833
4
4
package frontend
5
5
6
6
//lint:file-ignore SA4006 This context is only used if a nested component is present.
···
29
29
templ_7745c5c3_Var1 = templ.NopComponent
30
30
}
31
31
ctx = templ.ClearChildren(ctx)
32
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
32
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><title>BSFeeder</title><link rel=\"icon\" type=\"image/x-icon\" href=\"/public/favicon.ico\"><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><link href=\"/public/styles.css\" rel=\"stylesheet\"><script defer src=\"https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js\"></script><script src=\"https://unpkg.com/htmx.org\"></script><script src=\"https://unpkg.com/htmx.org@1.9.11\" defer></script><script src=\"https://unpkg.com/htmx.org@1.9.12/dist/ext/json-enc.js\"></script><script src=\"https://unpkg.com/htmx.org@1.9.11/dist/ext/response-targets.js\"></script></head><body hx-ext=\"response-targets\" class=\"antialiased\">")
33
33
if templ_7745c5c3_Err != nil {
34
34
return templ_7745c5c3_Err
35
35
}
···
41
41
if templ_7745c5c3_Err != nil {
42
42
return templ_7745c5c3_Err
43
43
}
44
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
44
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</body></html>")
45
45
if templ_7745c5c3_Err != nil {
46
46
return templ_7745c5c3_Err
47
47
}
48
-
return templ_7745c5c3_Err
48
+
return nil
49
49
})
50
50
}
51
51
-2
frontend/base_templ.txt
-2
frontend/base_templ.txt
···
1
-
<!doctype html><html lang=\"en\"><head><title>BSFeeder</title><link rel=\"icon\" type=\"image/x-icon\" href=\"/public/favicon.ico\"><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><link href=\"/public/styles.css\" rel=\"stylesheet\"><script defer src=\"https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js\"></script><script src=\"https://unpkg.com/htmx.org\"></script><script src=\"https://unpkg.com/htmx.org@1.9.11\" defer></script><script src=\"https://unpkg.com/htmx.org@1.9.12/dist/ext/json-enc.js\"></script><script src=\"https://unpkg.com/htmx.org@1.9.11/dist/ext/response-targets.js\"></script></head><body hx-ext=\"response-targets\" class=\"antialiased\">
2
-
</body></html>
+15
-15
frontend/bookmarks_templ.go
+15
-15
frontend/bookmarks_templ.go
···
1
1
// Code generated by templ - DO NOT EDIT.
2
2
3
-
// templ: version: v0.2.793
3
+
// templ: version: v0.3.833
4
4
package frontend
5
5
6
6
//lint:file-ignore SA4006 This context is only used if a nested component is present.
···
38
38
if templ_7745c5c3_Err != nil {
39
39
return templ_7745c5c3_Err
40
40
}
41
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
41
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div hx-ext=\"response-targets\" class=\"flex justify-center items-center pt-6\"><form hx-post=\"/bookmarks\" hx-trigger=\"submit\" hx-target=\"#result\" hx-swap=\"innerHTML\" hx-target-error=\"#result\" class=\"w-96\" hx-on::after-request=\"this.reset()\"><input name=\"uri\" class=\"rounded-lg w-full mb-2 p-4\" placeholder=\"Add Post URI here\"> <button class=\"py-1 px-4 w-full h-10 rounded-lg text-white bg-zinc-800\">Add Bookmark</button><div id=\"result\" class=\"text-red-500 font-bold items-center pt-6\"></div></form></div><div hx-ext=\"response-targets\" class=\"flex justify-center items-center pt-6\"><table class=\"min-w-half divide-y-2 divide-gray-200 bg-white text-sm\"><tbody class=\"divide-y divide-gray-200\" id=\"bookmarks-table\">")
42
42
if templ_7745c5c3_Err != nil {
43
43
return templ_7745c5c3_Err
44
44
}
···
48
48
return templ_7745c5c3_Err
49
49
}
50
50
}
51
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
51
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</tbody></table></div>")
52
52
if templ_7745c5c3_Err != nil {
53
53
return templ_7745c5c3_Err
54
54
}
55
-
return templ_7745c5c3_Err
55
+
return nil
56
56
})
57
57
}
58
58
···
77
77
templ_7745c5c3_Var2 = templ.NopComponent
78
78
}
79
79
ctx = templ.ClearChildren(ctx)
80
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
80
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<tr id=\"")
81
81
if templ_7745c5c3_Err != nil {
82
82
return templ_7745c5c3_Err
83
83
}
···
90
90
if templ_7745c5c3_Err != nil {
91
91
return templ_7745c5c3_Err
92
92
}
93
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4)
93
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\"><td class=\"px-4 py-2 font-medium text-gray-900\"><p class=\"font-medium text-sm text-blue-300\">Author: ")
94
94
if templ_7745c5c3_Err != nil {
95
95
return templ_7745c5c3_Err
96
96
}
···
103
103
if templ_7745c5c3_Err != nil {
104
104
return templ_7745c5c3_Err
105
105
}
106
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5)
106
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</p><a class=\"font-medium text-sm\" target=\"_blank\" href=\"")
107
107
if templ_7745c5c3_Err != nil {
108
108
return templ_7745c5c3_Err
109
109
}
···
112
112
if templ_7745c5c3_Err != nil {
113
113
return templ_7745c5c3_Err
114
114
}
115
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 6)
115
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\">")
116
116
if templ_7745c5c3_Err != nil {
117
117
return templ_7745c5c3_Err
118
118
}
···
125
125
if templ_7745c5c3_Err != nil {
126
126
return templ_7745c5c3_Err
127
127
}
128
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 7)
128
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</a></td><td class=\"whitespace-nowrap px-4 py-2 text-gray-700\"><button hx-delete=\"")
129
129
if templ_7745c5c3_Err != nil {
130
130
return templ_7745c5c3_Err
131
131
}
···
138
138
if templ_7745c5c3_Err != nil {
139
139
return templ_7745c5c3_Err
140
140
}
141
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 8)
141
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" hx-swap=\"delete\" hx-target=\"")
142
142
if templ_7745c5c3_Err != nil {
143
143
return templ_7745c5c3_Err
144
144
}
···
151
151
if templ_7745c5c3_Err != nil {
152
152
return templ_7745c5c3_Err
153
153
}
154
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 9)
154
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" class=\"flex items-center border py-1 px-2 rounded-lg hover:bg-red-300\"><p class=\"text-sm\">Delete</p></button></td></tr>")
155
155
if templ_7745c5c3_Err != nil {
156
156
return templ_7745c5c3_Err
157
157
}
158
-
return templ_7745c5c3_Err
158
+
return nil
159
159
})
160
160
}
161
161
···
180
180
templ_7745c5c3_Var9 = templ.NopComponent
181
181
}
182
182
ctx = templ.ClearChildren(ctx)
183
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 10)
183
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<tbody hx-swap-oob=\"beforeend:#bookmarks-table\">")
184
184
if templ_7745c5c3_Err != nil {
185
185
return templ_7745c5c3_Err
186
186
}
···
188
188
if templ_7745c5c3_Err != nil {
189
189
return templ_7745c5c3_Err
190
190
}
191
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 11)
191
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</tbody>")
192
192
if templ_7745c5c3_Err != nil {
193
193
return templ_7745c5c3_Err
194
194
}
195
-
return templ_7745c5c3_Err
195
+
return nil
196
196
})
197
197
}
198
198
-11
frontend/bookmarks_templ.txt
-11
frontend/bookmarks_templ.txt
···
1
-
<div hx-ext=\"response-targets\" class=\"flex justify-center items-center pt-6\"><form hx-post=\"/bookmarks\" hx-trigger=\"submit\" hx-target=\"#result\" hx-swap=\"innerHTML\" hx-target-error=\"#result\" class=\"w-96\" hx-on::after-request=\"this.reset()\"><input name=\"uri\" class=\"rounded-lg w-full mb-2 p-4\" placeholder=\"Add Post URI here\"> <button class=\"py-1 px-4 w-full h-10 rounded-lg text-white bg-zinc-800\">Add Bookmark</button><div id=\"result\" class=\"text-red-500 font-bold items-center pt-6\"></div></form></div><div hx-ext=\"response-targets\" class=\"flex justify-center items-center pt-6\"><table class=\"min-w-half divide-y-2 divide-gray-200 bg-white text-sm\"><tbody class=\"divide-y divide-gray-200\" id=\"bookmarks-table\">
2
-
</tbody></table></div>
3
-
<tr id=\"
4
-
\"><td class=\"px-4 py-2 font-medium text-gray-900\"><p class=\"font-medium text-sm text-blue-300\">Author:
5
-
</p><a class=\"font-medium text-sm\" target=\"_blank\" href=\"
6
-
\">
7
-
</a></td><td class=\"whitespace-nowrap px-4 py-2 text-gray-700\"><button hx-delete=\"
8
-
\" hx-swap=\"delete\" hx-target=\"
9
-
\" class=\"flex items-center border py-1 px-2 rounded-lg hover:bg-red-300\"><p class=\"text-sm\">Delete</p></button></td></tr>
10
-
<tbody hx-swap-oob=\"beforeend:#bookmarks-table\">
11
-
</tbody>
+11
-11
frontend/home_templ.go
+11
-11
frontend/home_templ.go
···
1
1
// Code generated by templ - DO NOT EDIT.
2
2
3
-
// templ: version: v0.2.793
3
+
// templ: version: v0.3.833
4
4
package frontend
5
5
6
6
//lint:file-ignore SA4006 This context is only used if a nested component is present.
···
33
33
if templ_7745c5c3_Err != nil {
34
34
return templ_7745c5c3_Err
35
35
}
36
-
return templ_7745c5c3_Err
36
+
return nil
37
37
})
38
38
}
39
39
···
62
62
if templ_7745c5c3_Err != nil {
63
63
return templ_7745c5c3_Err
64
64
}
65
-
return templ_7745c5c3_Err
65
+
return nil
66
66
})
67
67
}
68
68
···
91
91
if templ_7745c5c3_Err != nil {
92
92
return templ_7745c5c3_Err
93
93
}
94
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
94
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"relative flex justify-center overflow-hidden bg-gray-50 py-6 sm:py-12\"><div class=\"flex-1 mx-auto w-full max-w-md bg-white px-6 pt-6 pb-6 shadow-xl ring-1 ring-gray-900/5 sm:rounded-xl sm:px-8\"><div class=\"w-full\"><div class=\"text-center\"><h1 class=\"text-3xl font-semibold text-gray-900\">Your username is</h1><p class=\"mt-2 text-gray-500\">")
95
95
if templ_7745c5c3_Err != nil {
96
96
return templ_7745c5c3_Err
97
97
}
···
104
104
if templ_7745c5c3_Err != nil {
105
105
return templ_7745c5c3_Err
106
106
}
107
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
107
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</p></div><div class=\"text-center\"><h1 class=\"text-3xl font-semibold text-gray-900\">Your email is</h1><p class=\"mt-2 text-gray-500\">")
108
108
if templ_7745c5c3_Err != nil {
109
109
return templ_7745c5c3_Err
110
110
}
···
117
117
if templ_7745c5c3_Err != nil {
118
118
return templ_7745c5c3_Err
119
119
}
120
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
120
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</p></div></div></div><div class=\"flex-1 mx-auto w-full max-w-md bg-white px-6 pt-6 pb-6 shadow-xl ring-1 ring-gray-900/5 sm:rounded-xl sm:px-8\"><div class=\"w-full\"><div class=\"text-center\"><h1 class=\"text-3xl font-semibold text-gray-900\">Your username is</h1><p class=\"mt-2 text-gray-500\">")
121
121
if templ_7745c5c3_Err != nil {
122
122
return templ_7745c5c3_Err
123
123
}
···
130
130
if templ_7745c5c3_Err != nil {
131
131
return templ_7745c5c3_Err
132
132
}
133
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4)
133
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</p></div><div class=\"text-center\"><h1 class=\"text-3xl font-semibold text-gray-900\">Your email is</h1><p class=\"mt-2 text-gray-500\">")
134
134
if templ_7745c5c3_Err != nil {
135
135
return templ_7745c5c3_Err
136
136
}
···
143
143
if templ_7745c5c3_Err != nil {
144
144
return templ_7745c5c3_Err
145
145
}
146
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5)
146
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</p></div></div></div><div class=\"flex-1 mx-auto w-full max-w-md bg-white px-6 pt-6 pb-6 shadow-xl ring-1 ring-gray-900/5 sm:rounded-xl sm:px-8\"><div class=\"w-full\"><div class=\"text-center\"><h1 class=\"text-3xl font-semibold text-gray-900\">Your username is</h1><p class=\"mt-2 text-gray-500\">")
147
147
if templ_7745c5c3_Err != nil {
148
148
return templ_7745c5c3_Err
149
149
}
···
156
156
if templ_7745c5c3_Err != nil {
157
157
return templ_7745c5c3_Err
158
158
}
159
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 6)
159
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</p></div><div class=\"text-center\"><h1 class=\"text-3xl font-semibold text-gray-900\">Your email is</h1><p class=\"mt-2 text-gray-500\">")
160
160
if templ_7745c5c3_Err != nil {
161
161
return templ_7745c5c3_Err
162
162
}
···
169
169
if templ_7745c5c3_Err != nil {
170
170
return templ_7745c5c3_Err
171
171
}
172
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 7)
172
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</p></div></div></div></div>")
173
173
if templ_7745c5c3_Err != nil {
174
174
return templ_7745c5c3_Err
175
175
}
176
-
return templ_7745c5c3_Err
176
+
return nil
177
177
})
178
178
}
179
179
-7
frontend/home_templ.txt
-7
frontend/home_templ.txt
···
1
-
<div class=\"relative flex justify-center overflow-hidden bg-gray-50 py-6 sm:py-12\"><div class=\"flex-1 mx-auto w-full max-w-md bg-white px-6 pt-6 pb-6 shadow-xl ring-1 ring-gray-900/5 sm:rounded-xl sm:px-8\"><div class=\"w-full\"><div class=\"text-center\"><h1 class=\"text-3xl font-semibold text-gray-900\">Your username is</h1><p class=\"mt-2 text-gray-500\">
2
-
</p></div><div class=\"text-center\"><h1 class=\"text-3xl font-semibold text-gray-900\">Your email is</h1><p class=\"mt-2 text-gray-500\">
3
-
</p></div></div></div><div class=\"flex-1 mx-auto w-full max-w-md bg-white px-6 pt-6 pb-6 shadow-xl ring-1 ring-gray-900/5 sm:rounded-xl sm:px-8\"><div class=\"w-full\"><div class=\"text-center\"><h1 class=\"text-3xl font-semibold text-gray-900\">Your username is</h1><p class=\"mt-2 text-gray-500\">
4
-
</p></div><div class=\"text-center\"><h1 class=\"text-3xl font-semibold text-gray-900\">Your email is</h1><p class=\"mt-2 text-gray-500\">
5
-
</p></div></div></div><div class=\"flex-1 mx-auto w-full max-w-md bg-white px-6 pt-6 pb-6 shadow-xl ring-1 ring-gray-900/5 sm:rounded-xl sm:px-8\"><div class=\"w-full\"><div class=\"text-center\"><h1 class=\"text-3xl font-semibold text-gray-900\">Your username is</h1><p class=\"mt-2 text-gray-500\">
6
-
</p></div><div class=\"text-center\"><h1 class=\"text-3xl font-semibold text-gray-900\">Your email is</h1><p class=\"mt-2 text-gray-500\">
7
-
</p></div></div></div></div>
-10
frontend/login.templ
-10
frontend/login.templ
···
18
18
<input class="bg-gray-200 appearance-none border-2 border-gray-200 rounded w-full py-2 px-4 text-gray-700 leading-tight focus:outline-none focus:bg-white focus:border-blue-500" id="handle" name="handle" type="text" value={ handle }/>
19
19
</div>
20
20
</div>
21
-
<div class="md:flex md:items-center mb-6">
22
-
<div class="md:w-1/3">
23
-
<label class="block text-gray-500 font-bold md:text-right mb-1 md:mb-0 pr-4" for="appPassword">
24
-
App Password
25
-
</label>
26
-
</div>
27
-
<div class="md:w-2/3">
28
-
<input class="bg-gray-200 appearance-none border-2 border-gray-200 rounded w-full py-2 px-4 text-gray-700 leading-tight focus:outline-none focus:bg-white focus:border-blue-500" id="appPassword" name="appPassword" type="password"/>
29
-
</div>
30
-
</div>
31
21
<div class="md:flex md:items-center">
32
22
<div class="md:w-1/3"></div>
33
23
<div class="md:w-1/3">
+9
-9
frontend/login_templ.go
+9
-9
frontend/login_templ.go
···
1
1
// Code generated by templ - DO NOT EDIT.
2
2
3
-
// templ: version: v0.2.793
3
+
// templ: version: v0.3.833
4
4
package frontend
5
5
6
6
//lint:file-ignore SA4006 This context is only used if a nested component is present.
···
37
37
if templ_7745c5c3_Err != nil {
38
38
return templ_7745c5c3_Err
39
39
}
40
-
return templ_7745c5c3_Err
40
+
return nil
41
41
})
42
42
}
43
43
···
62
62
templ_7745c5c3_Var2 = templ.NopComponent
63
63
}
64
64
ctx = templ.ClearChildren(ctx)
65
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
65
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<form class=\"h-screen flex items-center justify-center\" id=\"login-form\" hx-swap=\"outerHTML\" hx-post=\"/login\" hx-ext=\"json-enc\"><div class=\"w-full max-w-sm\"><div class=\"md:flex md:items-center mb-6\"><div class=\"md:w-1/3\"><label class=\"block text-gray-500 font-bold md:text-right mb-1 md:mb-0 pr-4\" for=\"handle\">Bsky Handle</label></div><div class=\"md:w-2/3\"><input class=\"bg-gray-200 appearance-none border-2 border-gray-200 rounded w-full py-2 px-4 text-gray-700 leading-tight focus:outline-none focus:bg-white focus:border-blue-500\" id=\"handle\" name=\"handle\" type=\"text\" value=\"")
66
66
if templ_7745c5c3_Err != nil {
67
67
return templ_7745c5c3_Err
68
68
}
···
75
75
if templ_7745c5c3_Err != nil {
76
76
return templ_7745c5c3_Err
77
77
}
78
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
78
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"></div></div><div class=\"md:flex md:items-center\"><div class=\"md:w-1/3\"></div><div class=\"md:w-1/3\"><button class=\"shadow bg-blue-500 hover:bg-blue-400 focus:shadow-outline focus:outline-none text-white font-bold py-2 px-4 rounded\" type=\"submit\" form=\"login-form\">Login</button></div>")
79
79
if templ_7745c5c3_Err != nil {
80
80
return templ_7745c5c3_Err
81
81
}
82
82
if errorMsg != "" {
83
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
83
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"md:w-1/3\" id=\"error-message\"><label class=\"text-red-500 font-bold\">")
84
84
if templ_7745c5c3_Err != nil {
85
85
return templ_7745c5c3_Err
86
86
}
87
87
var templ_7745c5c3_Var4 string
88
88
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(errorMsg)
89
89
if templ_7745c5c3_Err != nil {
90
-
return templ.Error{Err: templ_7745c5c3_Err, FileName: `frontend/login.templ`, Line: 41, Col: 17}
90
+
return templ.Error{Err: templ_7745c5c3_Err, FileName: `frontend/login.templ`, Line: 31, Col: 17}
91
91
}
92
92
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
93
93
if templ_7745c5c3_Err != nil {
94
94
return templ_7745c5c3_Err
95
95
}
96
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4)
96
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</label></div>")
97
97
if templ_7745c5c3_Err != nil {
98
98
return templ_7745c5c3_Err
99
99
}
100
100
}
101
-
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5)
101
+
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div></div></form>")
102
102
if templ_7745c5c3_Err != nil {
103
103
return templ_7745c5c3_Err
104
104
}
105
-
return templ_7745c5c3_Err
105
+
return nil
106
106
})
107
107
}
108
108
-5
frontend/login_templ.txt
-5
frontend/login_templ.txt
···
1
-
<form class=\"h-screen flex items-center justify-center\" id=\"login-form\" hx-swap=\"outerHTML\" hx-post=\"/login\" hx-ext=\"json-enc\"><div class=\"w-full max-w-sm\"><div class=\"md:flex md:items-center mb-6\"><div class=\"md:w-1/3\"><label class=\"block text-gray-500 font-bold md:text-right mb-1 md:mb-0 pr-4\" for=\"handle\">Bsky Handle</label></div><div class=\"md:w-2/3\"><input class=\"bg-gray-200 appearance-none border-2 border-gray-200 rounded w-full py-2 px-4 text-gray-700 leading-tight focus:outline-none focus:bg-white focus:border-blue-500\" id=\"handle\" name=\"handle\" type=\"text\" value=\"
2
-
\"></div></div><div class=\"md:flex md:items-center mb-6\"><div class=\"md:w-1/3\"><label class=\"block text-gray-500 font-bold md:text-right mb-1 md:mb-0 pr-4\" for=\"appPassword\">App Password</label></div><div class=\"md:w-2/3\"><input class=\"bg-gray-200 appearance-none border-2 border-gray-200 rounded w-full py-2 px-4 text-gray-700 leading-tight focus:outline-none focus:bg-white focus:border-blue-500\" id=\"appPassword\" name=\"appPassword\" type=\"password\"></div></div><div class=\"md:flex md:items-center\"><div class=\"md:w-1/3\"></div><div class=\"md:w-1/3\"><button class=\"shadow bg-blue-500 hover:bg-blue-400 focus:shadow-outline focus:outline-none text-white font-bold py-2 px-4 rounded\" type=\"submit\" form=\"login-form\">Login</button></div>
3
-
<div class=\"md:w-1/3\" id=\"error-message\"><label class=\"text-red-500 font-bold\">
4
-
</label></div>
5
-
</div></div></form>
+35
-27
go.mod
+35
-27
go.mod
···
1
1
module github.com/willdot/bskyfeedgen
2
2
3
-
go 1.23.3
3
+
go 1.24.0
4
4
5
-
toolchain go1.23.4
5
+
toolchain go1.24.1
6
6
7
7
require (
8
-
github.com/a-h/templ v0.2.793
8
+
github.com/a-h/templ v0.3.833
9
9
github.com/avast/retry-go/v4 v4.6.0
10
-
github.com/bluesky-social/indigo v0.0.0-20241031232035-1a73c3fb6841
10
+
github.com/bluesky-social/indigo v0.0.0-20250305203105-a2e0aaff387e
11
11
github.com/bluesky-social/jetstream v0.0.0-20241031234625-0ab10bd041fe
12
12
github.com/bugsnag/bugsnag-go/v2 v2.5.1
13
13
github.com/glebarez/go-sqlite v1.22.0
14
14
github.com/golang-jwt/jwt/v5 v5.2.1
15
+
github.com/gorilla/sessions v1.4.0
16
+
github.com/haileyok/atproto-oauth-golang v0.0.2
15
17
github.com/joho/godotenv v1.5.1
16
-
github.com/stretchr/testify v1.9.0
18
+
github.com/lestrrat-go/jwx/v2 v2.1.4
17
19
)
18
20
19
21
require (
···
21
23
github.com/bugsnag/panicwrap v1.3.4 // indirect
22
24
github.com/carlmjohnson/versioninfo v0.22.5 // indirect
23
25
github.com/cespare/xxhash/v2 v2.3.0 // indirect
24
-
github.com/davecgh/go-spew v1.1.1 // indirect
26
+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
25
27
github.com/dustin/go-humanize v1.0.1 // indirect
26
28
github.com/felixge/httpsnoop v1.0.4 // indirect
27
-
github.com/go-logr/logr v1.4.1 // indirect
29
+
github.com/go-logr/logr v1.4.2 // indirect
28
30
github.com/go-logr/stdr v1.2.2 // indirect
29
-
github.com/goccy/go-json v0.10.2 // indirect
31
+
github.com/goccy/go-json v0.10.5 // indirect
30
32
github.com/gogo/protobuf v1.3.2 // indirect
31
33
github.com/google/uuid v1.6.0 // indirect
34
+
github.com/gorilla/securecookie v1.1.2 // indirect
32
35
github.com/gorilla/websocket v1.5.1 // indirect
33
36
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
34
-
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
37
+
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
35
38
github.com/hashicorp/golang-lru v1.0.2 // indirect
36
39
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
37
40
github.com/ipfs/bbloom v0.0.4 // indirect
38
41
github.com/ipfs/go-block-format v0.2.0 // indirect
39
-
github.com/ipfs/go-cid v0.4.1 // indirect
40
-
github.com/ipfs/go-datastore v0.6.0 // indirect
42
+
github.com/ipfs/go-cid v0.5.0 // indirect
43
+
github.com/ipfs/go-datastore v0.8.2 // indirect
41
44
github.com/ipfs/go-ipfs-blockstore v1.3.1 // indirect
42
45
github.com/ipfs/go-ipfs-ds-help v1.1.1 // indirect
43
46
github.com/ipfs/go-ipfs-util v0.0.3 // indirect
44
-
github.com/ipfs/go-ipld-cbor v0.1.0 // indirect
47
+
github.com/ipfs/go-ipld-cbor v0.2.0 // indirect
45
48
github.com/ipfs/go-ipld-format v0.6.0 // indirect
46
49
github.com/ipfs/go-log v1.0.5 // indirect
47
50
github.com/ipfs/go-log/v2 v2.5.1 // indirect
48
-
github.com/ipfs/go-metrics-interface v0.0.1 // indirect
51
+
github.com/ipfs/go-metrics-interface v0.3.0 // indirect
49
52
github.com/jbenet/goprocess v0.1.4 // indirect
50
53
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
51
54
github.com/klauspost/compress v1.17.9 // indirect
52
-
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
55
+
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
56
+
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
57
+
github.com/lestrrat-go/httpcc v1.0.1 // indirect
58
+
github.com/lestrrat-go/httprc v1.0.6 // indirect
59
+
github.com/lestrrat-go/iter v1.0.2 // indirect
60
+
github.com/lestrrat-go/option v1.0.1 // indirect
53
61
github.com/mattn/go-isatty v0.0.20 // indirect
54
62
github.com/minio/sha256-simd v1.0.1 // indirect
55
63
github.com/mr-tron/base58 v1.2.0 // indirect
···
60
68
github.com/multiformats/go-varint v0.0.7 // indirect
61
69
github.com/opentracing/opentracing-go v1.2.0 // indirect
62
70
github.com/pkg/errors v0.9.1 // indirect
63
-
github.com/pmezard/go-difflib v1.0.0 // indirect
64
71
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect
65
72
github.com/prometheus/client_golang v1.19.1 // indirect
66
73
github.com/prometheus/client_model v0.6.1 // indirect
67
74
github.com/prometheus/common v0.54.0 // indirect
68
75
github.com/prometheus/procfs v0.15.1 // indirect
69
76
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
77
+
github.com/segmentio/asm v1.2.0 // indirect
70
78
github.com/spaolacci/murmur3 v1.1.0 // indirect
71
-
github.com/whyrusleeping/cbor-gen v0.1.3-0.20240904181319-8dc02b38228c // indirect
79
+
github.com/whyrusleeping/cbor-gen v0.3.1 // indirect
72
80
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
73
81
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
74
-
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
75
-
go.opentelemetry.io/otel v1.21.0 // indirect
76
-
go.opentelemetry.io/otel/metric v1.21.0 // indirect
77
-
go.opentelemetry.io/otel/trace v1.21.0 // indirect
82
+
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
83
+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
84
+
go.opentelemetry.io/otel v1.35.0 // indirect
85
+
go.opentelemetry.io/otel/metric v1.35.0 // indirect
86
+
go.opentelemetry.io/otel/trace v1.35.0 // indirect
78
87
go.uber.org/atomic v1.11.0 // indirect
79
88
go.uber.org/multierr v1.11.0 // indirect
80
89
go.uber.org/zap v1.27.0 // indirect
81
-
golang.org/x/crypto v0.26.0 // indirect
82
-
golang.org/x/net v0.28.0 // indirect
83
-
golang.org/x/sys v0.23.0 // indirect
84
-
golang.org/x/time v0.5.0 // indirect
85
-
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
90
+
golang.org/x/crypto v0.36.0 // indirect
91
+
golang.org/x/net v0.33.0 // indirect
92
+
golang.org/x/sys v0.31.0 // indirect
93
+
golang.org/x/time v0.8.0 // indirect
94
+
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
86
95
google.golang.org/protobuf v1.34.2 // indirect
87
-
gopkg.in/yaml.v3 v3.0.1 // indirect
88
-
lukechampine.com/blake3 v1.2.1 // indirect
96
+
lukechampine.com/blake3 v1.4.0 // indirect
89
97
modernc.org/libc v1.37.6 // indirect
90
98
modernc.org/mathutil v1.6.0 // indirect
91
99
modernc.org/memory v1.7.2 // indirect
+135
-26
go.sum
+135
-26
go.sum
···
1
1
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2
2
github.com/a-h/templ v0.2.793 h1:Io+/ocnfGWYO4VHdR0zBbf39PQlnzVCVVD+wEEs6/qY=
3
3
github.com/a-h/templ v0.2.793/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w=
4
+
github.com/a-h/templ v0.3.833 h1:L/KOk/0VvVTBegtE0fp2RJQiBm7/52Zxv5fqlEHiQUU=
5
+
github.com/a-h/templ v0.3.833/go.mod h1:cAu4AiZhtJfBjMY0HASlyzvkrtjnHWPeEsyGK2YYmfk=
4
6
github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA=
5
7
github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE=
6
8
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
···
8
10
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
9
11
github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
10
12
github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q=
11
-
github.com/bluesky-social/indigo v0.0.0-20241031232035-1a73c3fb6841 h1:HCj4iBoAV59Fn1vsEhbeEMOExR4vOrUgz+sxUoV+F6s=
12
-
github.com/bluesky-social/indigo v0.0.0-20241031232035-1a73c3fb6841/go.mod h1:Zx9nSWgd/FxMenkJW07VKnzspxpHBdPrPmS+Fspl2I0=
13
+
github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188 h1:1sQaG37xk08/rpmdhrmMkfQWF9kZbnfHm9Zav3bbSMk=
14
+
github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188/go.mod h1:NVBwZvbBSa93kfyweAmKwOLYawdVHdwZ9s+GZtBBVLA=
15
+
github.com/bluesky-social/indigo v0.0.0-20250305203105-a2e0aaff387e h1:cLBQmPW9D6F7F3FRoFeu5yyHzWo77nSun3plvHLM7yM=
16
+
github.com/bluesky-social/indigo v0.0.0-20250305203105-a2e0aaff387e/go.mod h1:NVBwZvbBSa93kfyweAmKwOLYawdVHdwZ9s+GZtBBVLA=
13
17
github.com/bluesky-social/jetstream v0.0.0-20241031234625-0ab10bd041fe h1:jduuyDfsiwWrPiN7psqDehepl68uxQ4UYCIgoqb1D4o=
14
18
github.com/bluesky-social/jetstream v0.0.0-20241031234625-0ab10bd041fe/go.mod h1:WiYEeyJSdUwqoaZ71KJSpTblemUCpwJfh5oVXplK6T4=
15
19
github.com/bugsnag/bugsnag-go/v2 v2.5.1 h1:cGsEJHcis1zfQ4KoFaBPIT4N1TYqVNRALKr2wMRZ4hs=
···
22
26
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
23
27
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
24
28
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
25
-
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
26
29
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
30
+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
31
+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
32
+
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
33
+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
34
+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
35
+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
36
+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
27
37
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
28
38
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
29
39
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
···
31
41
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
32
42
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
33
43
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
34
-
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
35
-
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
44
+
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
45
+
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
36
46
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
37
47
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
38
48
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
39
49
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
40
50
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
51
+
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
52
+
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
41
53
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
42
54
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
55
+
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
43
56
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
44
57
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
45
58
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
46
59
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
60
+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
61
+
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
62
+
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
47
63
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
48
64
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
49
65
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
···
51
67
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
52
68
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
53
69
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
70
+
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
71
+
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
72
+
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
73
+
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
54
74
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
55
75
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
76
+
github.com/haileyok/atproto-oauth-golang v0.0.2 h1:61KPkLB615LQXR2f5x1v3sf6vPe6dOXqNpTYCgZ0Fz8=
77
+
github.com/haileyok/atproto-oauth-golang v0.0.2/go.mod h1:jcZ4GCjo5I5RuE/RsAXg1/b6udw7R4W+2rb/cGyTDK8=
56
78
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
57
79
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
58
80
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
59
81
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
82
+
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
60
83
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
61
84
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
85
+
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
86
+
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
62
87
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
63
88
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
64
89
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
···
69
94
github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM=
70
95
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
71
96
github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
97
+
github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg=
98
+
github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk=
72
99
github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk=
73
100
github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8=
101
+
github.com/ipfs/go-datastore v0.8.2 h1:Jy3wjqQR6sg/LhyY0NIePZC3Vux19nLtg7dx0TVqr6U=
102
+
github.com/ipfs/go-datastore v0.8.2/go.mod h1:W+pI1NsUsz3tcsAACMtfC+IZdnQTnC/7VfPoJBQuts0=
74
103
github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk=
75
104
github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps=
76
105
github.com/ipfs/go-ipfs-blockstore v1.3.1 h1:cEI9ci7V0sRNivqaOr0elDsamxXFxJMMMy7PTTDQNsQ=
···
81
110
github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs=
82
111
github.com/ipfs/go-ipld-cbor v0.1.0 h1:dx0nS0kILVivGhfWuB6dUpMa/LAwElHPw1yOGYopoYs=
83
112
github.com/ipfs/go-ipld-cbor v0.1.0/go.mod h1:U2aYlmVrJr2wsUBU67K4KgepApSZddGRDWBYR0H4sCk=
113
+
github.com/ipfs/go-ipld-cbor v0.2.0 h1:VHIW3HVIjcMd8m4ZLZbrYpwjzqlVUfjLM7oK4T5/YF0=
114
+
github.com/ipfs/go-ipld-cbor v0.2.0/go.mod h1:Cp8T7w1NKcu4AQJLqK0tWpd1nkgTxEVB5C6kVpLW6/0=
84
115
github.com/ipfs/go-ipld-format v0.6.0 h1:VEJlA2kQ3LqFSIm5Vu6eIlSxD/Ze90xtc4Meten1F5U=
85
116
github.com/ipfs/go-ipld-format v0.6.0/go.mod h1:g4QVMTn3marU3qXchwjpKPKgJv+zF+OlaKMyhJ4LHPg=
86
117
github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8=
···
90
121
github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=
91
122
github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg=
92
123
github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY=
124
+
github.com/ipfs/go-metrics-interface v0.3.0 h1:YwG7/Cy4R94mYDUuwsBfeziJCVm9pBMJ6q/JR9V40TU=
125
+
github.com/ipfs/go-metrics-interface v0.3.0/go.mod h1:OxxQjZDGocXVdyTPocns6cOLwHieqej/jos7H4POwoY=
93
126
github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA=
94
127
github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o=
95
128
github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4=
···
105
138
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
106
139
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
107
140
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
141
+
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
142
+
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
108
143
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
109
144
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
110
145
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
···
112
147
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
113
148
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
114
149
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
150
+
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
151
+
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
152
+
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
153
+
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
154
+
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
155
+
github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8=
156
+
github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
157
+
github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k=
158
+
github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
159
+
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
160
+
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
161
+
github.com/lestrrat-go/jwx/v2 v2.0.12 h1:3d589+5w/b9b7S3DneICPW16AqTyYXB7VRjgluSDWeA=
162
+
github.com/lestrrat-go/jwx/v2 v2.0.12/go.mod h1:Mq4KN1mM7bp+5z/W5HS8aCNs5RKZ911G/0y2qUjAQuQ=
163
+
github.com/lestrrat-go/jwx/v2 v2.1.4 h1:uBCMmJX8oRZStmKuMMOFb0Yh9xmEMgNJLgjuKKt4/qc=
164
+
github.com/lestrrat-go/jwx/v2 v2.1.4/go.mod h1:nWRbDFR1ALG2Z6GJbBXzfQaYyvn751KuuyySN2yR6is=
165
+
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
166
+
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
167
+
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
115
168
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
116
169
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
117
170
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
···
134
187
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
135
188
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
136
189
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
137
-
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
138
190
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
191
+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
192
+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
139
193
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0=
140
194
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw=
141
195
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
···
149
203
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
150
204
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
151
205
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
152
-
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
153
-
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
206
+
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
207
+
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
154
208
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
209
+
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
210
+
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
155
211
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
156
212
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
157
213
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
···
160
216
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
161
217
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
162
218
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
219
+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
220
+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
163
221
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
164
222
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
165
223
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
224
+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
166
225
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
167
-
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
168
-
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
226
+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
227
+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
228
+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
229
+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
230
+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
169
231
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
170
232
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=
171
233
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
172
-
github.com/whyrusleeping/cbor-gen v0.1.3-0.20240904181319-8dc02b38228c h1:UsxJNcLPfyLyVaA4iusIrsLAqJn/xh36Qgb8emqtXzk=
173
-
github.com/whyrusleeping/cbor-gen v0.1.3-0.20240904181319-8dc02b38228c/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so=
234
+
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e h1:28X54ciEwwUxyHn9yrZfl5ojgF4CBNLWX7LR0rvBkf4=
235
+
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so=
236
+
github.com/whyrusleeping/cbor-gen v0.3.1 h1:82ioxmhEYut7LBVGhGq8xoRkXPLElVuh5mV67AFfdv0=
237
+
github.com/whyrusleeping/cbor-gen v0.3.1/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so=
174
238
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
175
239
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
176
240
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
241
+
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
177
242
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA=
178
243
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8=
179
244
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q=
180
245
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I=
246
+
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
247
+
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
181
248
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
182
249
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
183
-
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
184
-
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
185
-
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
186
-
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
187
-
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
188
-
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
250
+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
251
+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
252
+
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
253
+
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
254
+
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
255
+
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
256
+
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
257
+
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
258
+
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
259
+
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
260
+
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
261
+
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
262
+
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
263
+
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
189
264
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
190
265
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
191
266
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
···
206
281
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
207
282
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
208
283
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
209
-
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
210
-
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
284
+
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
285
+
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
286
+
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
287
+
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
288
+
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
289
+
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
211
290
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
212
291
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
213
292
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
214
293
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
215
294
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
295
+
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
296
+
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
216
297
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
217
298
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
218
299
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
219
300
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
220
301
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
302
+
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
221
303
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
222
-
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
223
-
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
304
+
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
305
+
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
306
+
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
307
+
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
308
+
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
224
309
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
225
310
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
226
311
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
227
312
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
313
+
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
314
+
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
228
315
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
229
316
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
230
317
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
231
318
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
232
319
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
233
320
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
321
+
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
234
322
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
323
+
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
324
+
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
325
+
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
235
326
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
236
327
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
237
-
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
238
-
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
328
+
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
329
+
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
330
+
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
331
+
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
332
+
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
333
+
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
239
334
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
335
+
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
336
+
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
337
+
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
338
+
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
240
339
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
241
340
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
242
-
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
243
-
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
341
+
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
342
+
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
343
+
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
344
+
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
345
+
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
346
+
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
244
347
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
245
348
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
246
349
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
···
251
354
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
252
355
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
253
356
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
357
+
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
358
+
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
254
359
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
255
360
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
256
361
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
257
362
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
258
363
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
259
364
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
365
+
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
366
+
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
260
367
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
261
368
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
262
369
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
···
273
380
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
274
381
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
275
382
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
383
+
lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w=
384
+
lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0=
276
385
modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw=
277
386
modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
278
387
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
+6
-1
main.go
+6
-1
main.go
···
83
83
go consumeLoop(ctx, store)
84
84
}
85
85
86
-
server := NewServer(443, feeder, feedHost, feedDidBase, store)
86
+
server, err := NewServer(443, feeder, feedHost, feedDidBase, store)
87
+
if err != nil {
88
+
slog.Error("create new server", "error", err)
89
+
_ = bugsnag.Notify(err)
90
+
return
91
+
}
87
92
go func() {
88
93
<-signals
89
94
cancel()
+323
oauth_handlers.go
+323
oauth_handlers.go
···
1
+
package main
2
+
3
+
import (
4
+
"context"
5
+
"encoding/json"
6
+
"fmt"
7
+
"io"
8
+
"log/slog"
9
+
"net/http"
10
+
"net/url"
11
+
"strings"
12
+
13
+
"github.com/gorilla/sessions"
14
+
oauth "github.com/haileyok/atproto-oauth-golang"
15
+
oauthhelpers "github.com/haileyok/atproto-oauth-golang/helpers"
16
+
"github.com/lestrrat-go/jwx/v2/jwk"
17
+
"github.com/willdot/bskyfeedgen/frontend"
18
+
"github.com/willdot/bskyfeedgen/store"
19
+
)
20
+
21
+
const (
22
+
scope = "atproto transition:generic"
23
+
)
24
+
25
+
func (s *Server) serverJwks(w http.ResponseWriter, r *http.Request) {
26
+
w.Header().Set("Content-Type", "application/json")
27
+
_, _ = w.Write(s.jwks.public)
28
+
}
29
+
30
+
func (s *Server) serveClientMetadata(w http.ResponseWriter, r *http.Request) {
31
+
metadata := map[string]any{
32
+
"client_id": fmt.Sprintf("https://%s/client-metadata.json", s.feedHost),
33
+
"client_name": "BS Feeder",
34
+
"client_uri": fmt.Sprintf("https://%s", s.feedHost),
35
+
"redirect_uris": []string{fmt.Sprintf("https://%s/oauth-callback", s.feedHost)},
36
+
"grant_types": []string{"authorization_code", "refresh_token"},
37
+
"response_types": []string{"code"},
38
+
"application_type": "web",
39
+
"dpop_bound_access_tokens": true,
40
+
"jwks_uri": fmt.Sprintf("https://%s/jwks.json", s.feedHost),
41
+
"scope": "atproto transition:generic",
42
+
"token_endpoint_auth_method": "private_key_jwt",
43
+
"token_endpoint_auth_signing_alg": "ES256",
44
+
}
45
+
46
+
b, err := json.Marshal(metadata)
47
+
if err != nil {
48
+
slog.Error("failed to marshal client metadata", "error", err)
49
+
http.Error(w, "marshal response", http.StatusInternalServerError)
50
+
return
51
+
}
52
+
w.Header().Set("Content-Type", "application/json")
53
+
_, _ = w.Write(b)
54
+
}
55
+
56
+
func (s *Server) HandleLogin(w http.ResponseWriter, r *http.Request) {
57
+
b, err := io.ReadAll(r.Body)
58
+
if err != nil {
59
+
slog.Error("failed to read body", "error", err)
60
+
_ = frontend.Login("", "bad request").Render(r.Context(), w)
61
+
return
62
+
}
63
+
64
+
var loginReq loginRequest
65
+
err = json.Unmarshal(b, &loginReq)
66
+
if err != nil {
67
+
slog.Error("failed to unmarshal body", "error", err)
68
+
_ = frontend.Login("", "bad request").Render(r.Context(), w)
69
+
return
70
+
}
71
+
72
+
usersDID, err := resolveHandle(loginReq.Handle)
73
+
if err != nil {
74
+
slog.Error("resolve users handle", "error", err)
75
+
_ = frontend.Login("", "bad request").Render(r.Context(), w)
76
+
return
77
+
}
78
+
79
+
dpopPrivateKey, err := oauthhelpers.GenerateKey(nil)
80
+
if err != nil {
81
+
slog.Error("generate key", "error", err)
82
+
_ = frontend.Login("", "internal server errror").Render(r.Context(), w)
83
+
return
84
+
}
85
+
86
+
parResp, meta, err := s.parseLoginRequest(r.Context(), usersDID, loginReq.Handle, dpopPrivateKey)
87
+
if err != nil {
88
+
slog.Error("handle login request", "error", err)
89
+
_ = frontend.Login("", "internal server errror").Render(r.Context(), w)
90
+
return
91
+
}
92
+
93
+
dpopPrivateKeyJson, err := json.Marshal(dpopPrivateKey)
94
+
if err != nil {
95
+
slog.Error("marshal key", "error", err)
96
+
_ = frontend.Login("", "internal server errror").Render(r.Context(), w)
97
+
return
98
+
}
99
+
100
+
oauthRequst := store.OauthRequest{
101
+
AuthserverIss: meta.Issuer,
102
+
State: parResp.State,
103
+
Did: usersDID,
104
+
PkceVerifier: parResp.PkceVerifier,
105
+
DpopAuthserverNonce: parResp.DpopAuthserverNonce,
106
+
DpopPrivateJwk: string(dpopPrivateKeyJson),
107
+
}
108
+
err = s.oauthRequestStore.CreateOauthRequest(oauthRequst)
109
+
if err != nil {
110
+
// TODO: catch already exists
111
+
slog.Error("create oauth request in store", "error", err)
112
+
_ = frontend.Login("", "internal server errror").Render(r.Context(), w)
113
+
return
114
+
}
115
+
116
+
u, _ := url.Parse(meta.AuthorizationEndpoint)
117
+
u.RawQuery = fmt.Sprintf("client_id=%s&request_uri=%s", url.QueryEscape(fmt.Sprintf("https://%s/client-metadata.json", s.feedHost)), parResp.RequestUri)
118
+
119
+
// ignore error here as it only returns an error for decoding an existing session but it will always return a session anyway which
120
+
// is what we want
121
+
session, _ := s.sessionStore.Get(r, "oauth-session")
122
+
session.Values = map[interface{}]interface{}{}
123
+
124
+
session.Options = &sessions.Options{
125
+
Path: "/",
126
+
MaxAge: 300, // save for five minutes
127
+
HttpOnly: true,
128
+
}
129
+
130
+
session.Values["oauth_state"] = parResp.State
131
+
session.Values["oauth_did"] = usersDID
132
+
133
+
err = session.Save(r, w)
134
+
if err != nil {
135
+
slog.Error("save session", "error", err)
136
+
_ = frontend.Login("", "internal server errror").Render(r.Context(), w)
137
+
return
138
+
}
139
+
140
+
w.Header().Add("HX-Redirect", u.String())
141
+
http.Redirect(w, r, u.String(), http.StatusOK)
142
+
}
143
+
144
+
func (s *Server) parseLoginRequest(ctx context.Context, did, handle string, dpopPrivateKey jwk.Key) (*oauth.SendParAuthResponse, *oauth.OauthAuthorizationMetadata, error) {
145
+
service, err := resolveService(ctx, did)
146
+
if err != nil {
147
+
return nil, nil, err
148
+
}
149
+
150
+
authserver, err := s.oauthClient.ResolvePdsAuthServer(ctx, service)
151
+
if err != nil {
152
+
return nil, nil, err
153
+
}
154
+
155
+
meta, err := s.oauthClient.FetchAuthServerMetadata(ctx, authserver)
156
+
if err != nil {
157
+
return nil, nil, err
158
+
}
159
+
160
+
resp, err := s.oauthClient.SendParAuthRequest(ctx, authserver, meta, handle, scope, dpopPrivateKey)
161
+
if err != nil {
162
+
return nil, nil, err
163
+
}
164
+
return resp, meta, nil
165
+
}
166
+
167
+
func (s *Server) handleOauthCallback(w http.ResponseWriter, r *http.Request) {
168
+
resState := r.FormValue("state")
169
+
resIss := r.FormValue("iss")
170
+
resCode := r.FormValue("code")
171
+
172
+
session, err := s.sessionStore.Get(r, "oauth-session")
173
+
if err != nil {
174
+
slog.Error("getting session", "error", err)
175
+
_ = frontend.Login("", "internal server error").Render(r.Context(), w)
176
+
return
177
+
}
178
+
179
+
sessionState := session.Values["oauth_state"]
180
+
181
+
if resState == "" || resIss == "" || resCode == "" {
182
+
slog.Error("request missing needed parameters")
183
+
_ = frontend.Login("", "internal server error").Render(r.Context(), w)
184
+
return
185
+
}
186
+
187
+
if resState != sessionState {
188
+
slog.Error("session state does not match response state")
189
+
_ = frontend.Login("", "internal server error").Render(r.Context(), w)
190
+
return
191
+
}
192
+
193
+
oauthRequest, err := s.oauthRequestStore.GetOauthRequest(fmt.Sprintf("%s", sessionState))
194
+
if err != nil {
195
+
slog.Error("get oauth request from store", "error", err)
196
+
_ = frontend.Login("", "internal server errror").Render(r.Context(), w)
197
+
return
198
+
}
199
+
200
+
err = s.oauthRequestStore.DeleteOauthRequest(fmt.Sprintf("%s", sessionState))
201
+
if err != nil {
202
+
slog.Error("delete oauth request from store", "error", err)
203
+
_ = frontend.Login("", "internal server errror").Render(r.Context(), w)
204
+
return
205
+
}
206
+
207
+
jwk, err := oauthhelpers.ParseJWKFromBytes([]byte(oauthRequest.DpopPrivateJwk))
208
+
if err != nil {
209
+
slog.Error("parse JWK", "error", err)
210
+
_ = frontend.Login("", "internal server errror").Render(r.Context(), w)
211
+
return
212
+
}
213
+
214
+
initialTokenResp, err := s.oauthClient.InitialTokenRequest(r.Context(), resCode, resIss, oauthRequest.PkceVerifier, oauthRequest.DpopAuthserverNonce, jwk)
215
+
if err != nil {
216
+
slog.Error("getting token from request", "error", err)
217
+
_ = frontend.Login("", "internal server error").Render(r.Context(), w)
218
+
return
219
+
}
220
+
221
+
if initialTokenResp.Scope != scope {
222
+
slog.Error("did not receive correct scopes from token request")
223
+
_ = frontend.Login("", "internal server errror").Render(r.Context(), w)
224
+
return
225
+
}
226
+
227
+
session.Options = &sessions.Options{
228
+
Path: "/",
229
+
MaxAge: 86400 * 7,
230
+
HttpOnly: true,
231
+
}
232
+
233
+
// make sure the session is empty
234
+
session.Values = map[interface{}]interface{}{}
235
+
session.Values["did"] = oauthRequest.Did
236
+
237
+
err = session.Save(r, w)
238
+
if err != nil {
239
+
slog.Error("save session", "error", err)
240
+
_ = frontend.Login("", "internal server errror").Render(r.Context(), w)
241
+
return
242
+
}
243
+
244
+
// TODO: work out how the hell HTMX redirects really work
245
+
s.HandleGetBookmarks(w, r)
246
+
}
247
+
248
+
func (s *Server) HandleSignOut(w http.ResponseWriter, r *http.Request) {
249
+
session, err := s.sessionStore.Get(r, "oauth-session")
250
+
if err != nil {
251
+
slog.Error("getting session", "error", err)
252
+
_ = frontend.Login("", "internal server error").Render(r.Context(), w)
253
+
return
254
+
}
255
+
session.Values = map[interface{}]interface{}{}
256
+
session.Options = &sessions.Options{
257
+
Path: "/",
258
+
MaxAge: -1,
259
+
HttpOnly: true,
260
+
}
261
+
262
+
err = session.Save(r, w)
263
+
if err != nil {
264
+
slog.Error("save session", "error", err)
265
+
_ = frontend.Login("", "internal server errror").Render(r.Context(), w)
266
+
return
267
+
}
268
+
269
+
_ = frontend.Login("", "").Render(r.Context(), w)
270
+
}
271
+
272
+
func resolveService(ctx context.Context, did string) (string, error) {
273
+
type Identity struct {
274
+
Service []struct {
275
+
ID string `json:"id"`
276
+
Type string `json:"type"`
277
+
ServiceEndpoint string `json:"serviceEndpoint"`
278
+
} `json:"service"`
279
+
}
280
+
281
+
var ustr string
282
+
if strings.HasPrefix(did, "did:plc:") {
283
+
ustr = fmt.Sprintf("https://plc.directory/%s", did)
284
+
} else if strings.HasPrefix(did, "did:web:") {
285
+
ustr = fmt.Sprintf("https://%s/.well-known/did.json", strings.TrimPrefix(did, "did:web:"))
286
+
} else {
287
+
return "", fmt.Errorf("did was not a supported did type")
288
+
}
289
+
290
+
req, err := http.NewRequestWithContext(ctx, "GET", ustr, nil)
291
+
if err != nil {
292
+
return "", err
293
+
}
294
+
295
+
resp, err := http.DefaultClient.Do(req)
296
+
if err != nil {
297
+
return "", err
298
+
}
299
+
defer resp.Body.Close()
300
+
301
+
if resp.StatusCode != 200 {
302
+
io.Copy(io.Discard, resp.Body)
303
+
return "", fmt.Errorf("could not find identity in plc registry")
304
+
}
305
+
306
+
var identity Identity
307
+
if err := json.NewDecoder(resp.Body).Decode(&identity); err != nil {
308
+
return "", err
309
+
}
310
+
311
+
var service string
312
+
for _, svc := range identity.Service {
313
+
if svc.ID == "#atproto_pds" {
314
+
service = svc.ServiceEndpoint
315
+
}
316
+
}
317
+
318
+
if service == "" {
319
+
return "", fmt.Errorf("could not find atproto_pds service in identity services")
320
+
}
321
+
322
+
return service, nil
323
+
}
-49
public/styles.css
-49
public/styles.css
···
611
611
height: 100vh;
612
612
}
613
613
614
-
.h-3 {
615
-
height: 0.75rem;
616
-
}
617
-
618
614
.w-3\/12 {
619
615
width: 25%;
620
616
}
···
627
623
width: 100%;
628
624
}
629
625
630
-
.w-1\/3 {
631
-
width: 33.333333%;
632
-
}
633
-
634
626
.max-w-md {
635
627
max-width: 28rem;
636
628
}
···
647
639
-webkit-appearance: none;
648
640
-moz-appearance: none;
649
641
appearance: none;
650
-
}
651
-
652
-
.flex-col {
653
-
flex-direction: column;
654
642
}
655
643
656
644
.items-center {
···
690
678
overflow: hidden;
691
679
}
692
680
693
-
.overflow-x-auto {
694
-
overflow-x: auto;
695
-
}
696
-
697
681
.whitespace-nowrap {
698
682
white-space: nowrap;
699
683
}
···
706
690
border-radius: 0.5rem;
707
691
}
708
692
709
-
.rounded-b {
710
-
border-bottom-right-radius: 0.25rem;
711
-
border-bottom-left-radius: 0.25rem;
712
-
}
713
-
714
693
.border {
715
694
border-width: 1px;
716
695
}
717
696
718
697
.border-2 {
719
698
border-width: 2px;
720
-
}
721
-
722
-
.border-t-0 {
723
-
border-top-width: 0px;
724
699
}
725
700
726
701
.border-gray-200 {
···
728
703
border-color: rgb(229 231 235 / var(--tw-border-opacity, 1));
729
704
}
730
705
731
-
.border-red-400 {
732
-
--tw-border-opacity: 1;
733
-
border-color: rgb(248 113 113 / var(--tw-border-opacity, 1));
734
-
}
735
-
736
706
.bg-blue-500 {
737
707
--tw-bg-opacity: 1;
738
708
background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1));
···
746
716
.bg-gray-50 {
747
717
--tw-bg-opacity: 1;
748
718
background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1));
749
-
}
750
-
751
-
.bg-red-100 {
752
-
--tw-bg-opacity: 1;
753
-
background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1));
754
719
}
755
720
756
721
.bg-white {
···
797
762
padding-bottom: 0.5rem;
798
763
}
799
764
800
-
.py-3 {
801
-
padding-top: 0.75rem;
802
-
padding-bottom: 0.75rem;
803
-
}
804
-
805
765
.py-6 {
806
766
padding-top: 1.5rem;
807
767
padding-bottom: 1.5rem;
···
817
777
818
778
.pt-6 {
819
779
padding-top: 1.5rem;
820
-
}
821
-
822
-
.pt-5 {
823
-
padding-top: 1.25rem;
824
780
}
825
781
826
782
.text-center {
···
890
846
.text-red-500 {
891
847
--tw-text-opacity: 1;
892
848
color: rgb(239 68 68 / var(--tw-text-opacity, 1));
893
-
}
894
-
895
-
.text-red-700 {
896
-
--tw-text-opacity: 1;
897
-
color: rgb(185 28 28 / var(--tw-text-opacity, 1));
898
849
}
899
850
900
851
.text-white {
+93
-18
server.go
+93
-18
server.go
···
3
3
import (
4
4
"context"
5
5
_ "embed"
6
+
"encoding/base64"
7
+
"encoding/json"
6
8
"fmt"
7
9
"log/slog"
8
10
"net/http"
11
+
"os"
9
12
10
13
"github.com/bluesky-social/indigo/xrpc"
14
+
"github.com/gorilla/sessions"
15
+
oauth "github.com/haileyok/atproto-oauth-golang"
16
+
oauthhelpers "github.com/haileyok/atproto-oauth-golang/helpers"
17
+
"github.com/lestrrat-go/jwx/v2/jwk"
11
18
"github.com/willdot/bskyfeedgen/store"
12
19
)
13
20
···
15
22
GetFeed(ctx context.Context, userDID, feed, cursor string, limit int) (FeedReponse, error)
16
23
}
17
24
25
+
type Store interface {
26
+
BookmarkStore
27
+
OauthRequestStore
28
+
}
29
+
18
30
type BookmarkStore interface {
19
31
CreateBookmark(postRKey, postURI, postATURI, authorDID, authorHandle, userDID, content string) error
20
32
GetBookmarksForUser(userDID string) ([]store.Bookmark, error)
···
23
35
DeleteFeedPostsForBookmarkedPostURIandUserDID(subscribedPostURI, userDID string) error
24
36
}
25
37
38
+
type OauthRequestStore interface {
39
+
CreateOauthRequest(request store.OauthRequest) error
40
+
GetOauthRequest(state string) (store.OauthRequest, error)
41
+
DeleteOauthRequest(state string) error
42
+
}
43
+
26
44
type Server struct {
27
-
httpsrv *http.Server
28
-
feeder Feeder
29
-
feedHost string
30
-
feedDidBase string
31
-
bookmarkStore BookmarkStore
32
-
xrpcClient *xrpc.Client
45
+
httpsrv *http.Server
46
+
feeder Feeder
47
+
feedHost string
48
+
feedDidBase string
49
+
bookmarkStore BookmarkStore
50
+
oauthRequestStore OauthRequestStore
51
+
xrpcClient *xrpc.Client
52
+
jwks *JWKS
53
+
oauthClient *oauth.Client
54
+
sessionStore *sessions.CookieStore
33
55
}
34
56
35
-
func NewServer(port int, feeder Feeder, feedHost, feedDidBase string, bookmarkStore BookmarkStore) *Server {
57
+
type JWKS struct {
58
+
public []byte
59
+
private jwk.Key
60
+
}
61
+
62
+
func NewServer(port int, feeder Feeder, feedHost, feedDidBase string, store Store) (*Server, error) {
63
+
jwks, err := getJWKS()
64
+
if err != nil {
65
+
return nil, fmt.Errorf("create public JWKS: %w", err)
66
+
}
67
+
68
+
oauthClient, err := createOauthClient(jwks, fmt.Sprintf("https://%s", feedHost))
69
+
if err != nil {
70
+
return nil, fmt.Errorf("create oauth client: %w", err)
71
+
}
72
+
73
+
sessionStore := sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY")))
74
+
36
75
srv := &Server{
37
-
feeder: feeder,
38
-
feedHost: feedHost,
39
-
feedDidBase: feedDidBase,
40
-
bookmarkStore: bookmarkStore,
76
+
feeder: feeder,
77
+
feedHost: feedHost,
78
+
feedDidBase: feedDidBase,
79
+
bookmarkStore: store,
80
+
oauthRequestStore: store,
81
+
jwks: jwks,
82
+
oauthClient: oauthClient,
83
+
sessionStore: sessionStore,
41
84
}
42
85
43
86
mux := http.NewServeMux()
···
45
88
mux.HandleFunc("/xrpc/app.bsky.feed.getFeedSkeleton", srv.HandleGetFeedSkeleton)
46
89
mux.HandleFunc("/xrpc/app.bsky.feed.describeFeedGenerator", srv.HandleDescribeFeedGenerator)
47
90
mux.HandleFunc("/.well-known/did.json", srv.HandleWellKnown)
91
+
mux.HandleFunc("/client-metadata.json", srv.serveClientMetadata)
92
+
mux.HandleFunc("/jwks.json", srv.serverJwks)
93
+
mux.HandleFunc("/oauth-callback", srv.handleOauthCallback)
48
94
49
95
mux.HandleFunc("/", srv.authMiddleware(srv.HandleGetBookmarks))
50
96
mux.HandleFunc("/login", srv.HandleLogin)
···
65
111
Host: "https://public.api.bsky.app",
66
112
}
67
113
68
-
return srv
114
+
return srv, nil
69
115
}
70
116
71
117
func (s *Server) Run() {
···
87
133
_, _ = w.Write(cssFile)
88
134
}
89
135
90
-
func getUsersDidFromRequestCookie(r *http.Request) (string, error) {
91
-
didCookie, err := r.Cookie(didCookieName)
136
+
func getJWKS() (*JWKS, error) {
137
+
jwksB64 := os.Getenv("PRIVATEJWKS")
138
+
if jwksB64 == "" {
139
+
return nil, fmt.Errorf("PRIVATEJWKS env not set")
140
+
}
141
+
142
+
jwksB, err := base64.StdEncoding.DecodeString(jwksB64)
143
+
if err != nil {
144
+
return nil, fmt.Errorf("decode jwks env: %w", err)
145
+
}
146
+
147
+
k, err := oauthhelpers.ParseJWKFromBytes([]byte(jwksB))
148
+
if err != nil {
149
+
return nil, fmt.Errorf("parse JWK from bytes: %w", err)
150
+
}
151
+
152
+
pubkey, err := k.PublicKey()
92
153
if err != nil {
93
-
return "", err
154
+
return nil, fmt.Errorf("get public key from JWKS: %w", err)
94
155
}
95
-
if didCookie == nil {
96
-
return "", fmt.Errorf("missing did cookie")
156
+
157
+
resp := oauthhelpers.CreateJwksResponseObject(pubkey)
158
+
b, err := json.Marshal(resp)
159
+
if err != nil {
160
+
return nil, fmt.Errorf("marshal public JWKS: %w", err)
97
161
}
98
162
99
-
return didCookie.Value, nil
163
+
return &JWKS{
164
+
public: b,
165
+
private: k,
166
+
}, nil
167
+
}
168
+
169
+
func createOauthClient(jwks *JWKS, serverBase string) (*oauth.Client, error) {
170
+
return oauth.NewClient(oauth.ClientArgs{
171
+
ClientJwk: jwks.private,
172
+
ClientId: fmt.Sprintf("%s/client-metadata.json", serverBase),
173
+
RedirectUri: fmt.Sprintf("%s/oauth-callback", serverBase),
174
+
})
100
175
}
+5
store/database.go
+5
store/database.go
+87
store/oauth.go
+87
store/oauth.go
···
1
+
package store
2
+
3
+
import (
4
+
"database/sql"
5
+
"errors"
6
+
"fmt"
7
+
"log/slog"
8
+
)
9
+
10
+
var ErrOauthRequestAlreadyExists = errors.New("oauth request already exists")
11
+
12
+
func createOauthRequestsTable(db *sql.DB) error {
13
+
createOauthRequestsTableSQL := `CREATE TABLE IF NOT EXISTS oauthrequests (
14
+
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
15
+
"authserverIss" TEXT,
16
+
"state" TEXT,
17
+
"did" TEXT,
18
+
"pkceVerifier" TEXT,
19
+
"dpopAuthserverNonce" TEXT,
20
+
"dpopPrivateJwk" TEXT,
21
+
UNIQUE(did,state)
22
+
);`
23
+
24
+
slog.Info("Create oauthrequests table...")
25
+
statement, err := db.Prepare(createOauthRequestsTableSQL)
26
+
if err != nil {
27
+
return fmt.Errorf("prepare DB statement to create oauthrequests table: %w", err)
28
+
}
29
+
_, err = statement.Exec()
30
+
if err != nil {
31
+
return fmt.Errorf("exec sql statement to create oauthrequests table: %w", err)
32
+
}
33
+
slog.Info("oauthrequests table created")
34
+
35
+
return nil
36
+
}
37
+
38
+
type OauthRequest struct {
39
+
ID uint
40
+
AuthserverIss string
41
+
State string
42
+
Did string
43
+
PkceVerifier string
44
+
DpopAuthserverNonce string
45
+
DpopPrivateJwk string
46
+
}
47
+
48
+
func (s *Store) CreateOauthRequest(request OauthRequest) error {
49
+
sql := `INSERT INTO oauthrequests (authserverIss, state, did, pkceVerifier, dpopAuthServerNonce, dpopPrivateJwk) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(did,state) DO NOTHING;`
50
+
res, err := s.db.Exec(sql, request.AuthserverIss, request.State, request.Did, request.PkceVerifier, request.DpopAuthserverNonce, request.DpopPrivateJwk)
51
+
if err != nil {
52
+
return fmt.Errorf("exec insert oauth request: %w", err)
53
+
}
54
+
55
+
if x, _ := res.RowsAffected(); x == 0 {
56
+
return ErrOauthRequestAlreadyExists
57
+
}
58
+
return nil
59
+
}
60
+
61
+
func (s *Store) GetOauthRequest(state string) (OauthRequest, error) {
62
+
var oauthRequest OauthRequest
63
+
sql := "SELECT authserverIss, state, did, pkceVerifier, dpopAuthServerNonce, dpopPrivateJwk FROM oauthrequests WHERE state = ?;"
64
+
rows, err := s.db.Query(sql, state)
65
+
if err != nil {
66
+
return oauthRequest, fmt.Errorf("run query to get oauth request: %w", err)
67
+
}
68
+
defer rows.Close()
69
+
70
+
for rows.Next() {
71
+
if err := rows.Scan(&oauthRequest.AuthserverIss, &oauthRequest.State, &oauthRequest.Did, &oauthRequest.PkceVerifier, &oauthRequest.DpopAuthserverNonce, &oauthRequest.DpopPrivateJwk); err != nil {
72
+
return oauthRequest, fmt.Errorf("scan row: %w", err)
73
+
}
74
+
75
+
return oauthRequest, nil
76
+
}
77
+
return oauthRequest, fmt.Errorf("not found")
78
+
}
79
+
80
+
func (s *Store) DeleteOauthRequest(state string) error {
81
+
sql := "DELETE FROM oauthrequests WHERE state = ?;"
82
+
_, err := s.db.Exec(sql, state)
83
+
if err != nil {
84
+
return fmt.Errorf("exec delete oauth request: %w", err)
85
+
}
86
+
return nil
87
+
}