+1
-1
README.md
+1
-1
README.md
···
25
25
- [x] com.atproto.repo.deleteRecord
26
26
- [x] com.atproto.repo.describeRepo
27
27
- [x] com.atproto.repo.getRecord
28
-
- [ ] com.atproto.repo.importRepo
28
+
- [x] com.atproto.repo.importRepo (Works "okay". You still have to handle PLC operations on your own when migrating. Use with extreme caution.)
29
29
- [x] com.atproto.repo.listRecords
30
30
- [ ] com.atproto.repo.listMissingBlobs
31
31
+269
server/middleware.go
+269
server/middleware.go
···
1
+
package server
2
+
3
+
import (
4
+
"crypto/sha256"
5
+
"encoding/base64"
6
+
"fmt"
7
+
"strings"
8
+
"time"
9
+
10
+
"github.com/Azure/go-autorest/autorest/to"
11
+
"github.com/golang-jwt/jwt/v4"
12
+
"github.com/haileyok/cocoon/internal/helpers"
13
+
"github.com/haileyok/cocoon/models"
14
+
"github.com/haileyok/cocoon/oauth/provider"
15
+
"github.com/labstack/echo/v4"
16
+
"gitlab.com/yawning/secp256k1-voi"
17
+
secp256k1secec "gitlab.com/yawning/secp256k1-voi/secec"
18
+
"gorm.io/gorm"
19
+
)
20
+
21
+
func (s *Server) handleAdminMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
22
+
return func(e echo.Context) error {
23
+
username, password, ok := e.Request().BasicAuth()
24
+
if !ok || username != "admin" || password != s.config.AdminPassword {
25
+
return helpers.InputError(e, to.StringPtr("Unauthorized"))
26
+
}
27
+
28
+
if err := next(e); err != nil {
29
+
e.Error(err)
30
+
}
31
+
32
+
return nil
33
+
}
34
+
}
35
+
36
+
func (s *Server) handleLegacySessionMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
37
+
return func(e echo.Context) error {
38
+
authheader := e.Request().Header.Get("authorization")
39
+
if authheader == "" {
40
+
return e.JSON(401, map[string]string{"error": "Unauthorized"})
41
+
}
42
+
43
+
pts := strings.Split(authheader, " ")
44
+
if len(pts) != 2 {
45
+
return helpers.ServerError(e, nil)
46
+
}
47
+
48
+
// move on to oauth session middleware if this is a dpop token
49
+
if pts[0] == "DPoP" {
50
+
return next(e)
51
+
}
52
+
53
+
tokenstr := pts[1]
54
+
token, _, err := new(jwt.Parser).ParseUnverified(tokenstr, jwt.MapClaims{})
55
+
claims, ok := token.Claims.(jwt.MapClaims)
56
+
if !ok {
57
+
return helpers.InputError(e, to.StringPtr("InvalidToken"))
58
+
}
59
+
60
+
var did string
61
+
var repo *models.RepoActor
62
+
63
+
// service auth tokens
64
+
lxm, hasLxm := claims["lxm"]
65
+
if hasLxm {
66
+
pts := strings.Split(e.Request().URL.String(), "/")
67
+
if lxm != pts[len(pts)-1] {
68
+
s.logger.Error("service auth lxm incorrect", "lxm", lxm, "expected", pts[len(pts)-1], "error", err)
69
+
return helpers.InputError(e, nil)
70
+
}
71
+
72
+
maybeDid, ok := claims["iss"].(string)
73
+
if !ok {
74
+
s.logger.Error("no iss in service auth token", "error", err)
75
+
return helpers.InputError(e, nil)
76
+
}
77
+
did = maybeDid
78
+
79
+
maybeRepo, err := s.getRepoActorByDid(did)
80
+
if err != nil {
81
+
s.logger.Error("error fetching repo", "error", err)
82
+
return helpers.ServerError(e, nil)
83
+
}
84
+
repo = maybeRepo
85
+
}
86
+
87
+
if token.Header["alg"] != "ES256K" {
88
+
token, err = new(jwt.Parser).Parse(tokenstr, func(t *jwt.Token) (any, error) {
89
+
if _, ok := t.Method.(*jwt.SigningMethodECDSA); !ok {
90
+
return nil, fmt.Errorf("unsupported signing method: %v", t.Header["alg"])
91
+
}
92
+
return s.privateKey.Public(), nil
93
+
})
94
+
if err != nil {
95
+
s.logger.Error("error parsing jwt", "error", err)
96
+
// NOTE: https://github.com/bluesky-social/atproto/discussions/3319
97
+
return e.JSON(400, map[string]string{"error": "ExpiredToken", "message": "token has expired"})
98
+
}
99
+
100
+
if !token.Valid {
101
+
return helpers.InputError(e, to.StringPtr("InvalidToken"))
102
+
}
103
+
} else {
104
+
kpts := strings.Split(tokenstr, ".")
105
+
signingInput := kpts[0] + "." + kpts[1]
106
+
hash := sha256.Sum256([]byte(signingInput))
107
+
sigBytes, err := base64.RawURLEncoding.DecodeString(kpts[2])
108
+
if err != nil {
109
+
s.logger.Error("error decoding signature bytes", "error", err)
110
+
return helpers.ServerError(e, nil)
111
+
}
112
+
113
+
if len(sigBytes) != 64 {
114
+
s.logger.Error("incorrect sigbytes length", "length", len(sigBytes))
115
+
return helpers.ServerError(e, nil)
116
+
}
117
+
118
+
rBytes := sigBytes[:32]
119
+
sBytes := sigBytes[32:]
120
+
rr, _ := secp256k1.NewScalarFromBytes((*[32]byte)(rBytes))
121
+
ss, _ := secp256k1.NewScalarFromBytes((*[32]byte)(sBytes))
122
+
123
+
sk, err := secp256k1secec.NewPrivateKey(repo.SigningKey)
124
+
if err != nil {
125
+
s.logger.Error("can't load private key", "error", err)
126
+
return err
127
+
}
128
+
129
+
pubKey, ok := sk.Public().(*secp256k1secec.PublicKey)
130
+
if !ok {
131
+
s.logger.Error("error getting public key from sk")
132
+
return helpers.ServerError(e, nil)
133
+
}
134
+
135
+
verified := pubKey.VerifyRaw(hash[:], rr, ss)
136
+
if !verified {
137
+
s.logger.Error("error verifying", "error", err)
138
+
return helpers.ServerError(e, nil)
139
+
}
140
+
}
141
+
142
+
isRefresh := e.Request().URL.Path == "/xrpc/com.atproto.server.refreshSession"
143
+
scope, _ := claims["scope"].(string)
144
+
145
+
if isRefresh && scope != "com.atproto.refresh" {
146
+
return helpers.InputError(e, to.StringPtr("InvalidToken"))
147
+
} else if !hasLxm && !isRefresh && scope != "com.atproto.access" {
148
+
return helpers.InputError(e, to.StringPtr("InvalidToken"))
149
+
}
150
+
151
+
table := "tokens"
152
+
if isRefresh {
153
+
table = "refresh_tokens"
154
+
}
155
+
156
+
if isRefresh {
157
+
type Result struct {
158
+
Found bool
159
+
}
160
+
var result Result
161
+
if err := s.db.Raw("SELECT EXISTS(SELECT 1 FROM "+table+" WHERE token = ?) AS found", nil, tokenstr).Scan(&result).Error; err != nil {
162
+
if err == gorm.ErrRecordNotFound {
163
+
return helpers.InputError(e, to.StringPtr("InvalidToken"))
164
+
}
165
+
166
+
s.logger.Error("error getting token from db", "error", err)
167
+
return helpers.ServerError(e, nil)
168
+
}
169
+
170
+
if !result.Found {
171
+
return helpers.InputError(e, to.StringPtr("InvalidToken"))
172
+
}
173
+
}
174
+
175
+
exp, ok := claims["exp"].(float64)
176
+
if !ok {
177
+
s.logger.Error("error getting iat from token")
178
+
return helpers.ServerError(e, nil)
179
+
}
180
+
181
+
if exp < float64(time.Now().UTC().Unix()) {
182
+
return helpers.InputError(e, to.StringPtr("ExpiredToken"))
183
+
}
184
+
185
+
if repo == nil {
186
+
maybeRepo, err := s.getRepoActorByDid(claims["sub"].(string))
187
+
if err != nil {
188
+
s.logger.Error("error fetching repo", "error", err)
189
+
return helpers.ServerError(e, nil)
190
+
}
191
+
repo = maybeRepo
192
+
did = repo.Repo.Did
193
+
}
194
+
195
+
e.Set("repo", repo)
196
+
e.Set("did", did)
197
+
e.Set("token", tokenstr)
198
+
199
+
if err := next(e); err != nil {
200
+
e.Error(err)
201
+
}
202
+
203
+
return nil
204
+
}
205
+
}
206
+
207
+
func (s *Server) handleOauthSessionMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
208
+
return func(e echo.Context) error {
209
+
authheader := e.Request().Header.Get("authorization")
210
+
if authheader == "" {
211
+
return e.JSON(401, map[string]string{"error": "Unauthorized"})
212
+
}
213
+
214
+
pts := strings.Split(authheader, " ")
215
+
if len(pts) != 2 {
216
+
return helpers.ServerError(e, nil)
217
+
}
218
+
219
+
if pts[0] != "DPoP" {
220
+
return next(e)
221
+
}
222
+
223
+
accessToken := pts[1]
224
+
225
+
nonce := s.oauthProvider.NextNonce()
226
+
if nonce != "" {
227
+
e.Response().Header().Set("DPoP-Nonce", nonce)
228
+
e.Response().Header().Add("access-control-expose-headers", "DPoP-Nonce")
229
+
}
230
+
231
+
proof, err := s.oauthProvider.DpopManager.CheckProof(e.Request().Method, "https://"+s.config.Hostname+e.Request().URL.String(), e.Request().Header, to.StringPtr(accessToken))
232
+
if err != nil {
233
+
s.logger.Error("invalid dpop proof", "error", err)
234
+
return helpers.InputError(e, to.StringPtr(err.Error()))
235
+
}
236
+
237
+
var oauthToken provider.OauthToken
238
+
if err := s.db.Raw("SELECT * FROM oauth_tokens WHERE token = ?", nil, accessToken).Scan(&oauthToken).Error; err != nil {
239
+
s.logger.Error("error finding access token in db", "error", err)
240
+
return helpers.InputError(e, nil)
241
+
}
242
+
243
+
if oauthToken.Token == "" {
244
+
return helpers.InputError(e, to.StringPtr("InvalidToken"))
245
+
}
246
+
247
+
if *oauthToken.Parameters.DpopJkt != proof.JKT {
248
+
s.logger.Error("jkt mismatch", "token", oauthToken.Parameters.DpopJkt, "proof", proof.JKT)
249
+
return helpers.InputError(e, to.StringPtr("dpop jkt mismatch"))
250
+
}
251
+
252
+
if time.Now().After(oauthToken.ExpiresAt) {
253
+
return e.JSON(400, map[string]string{"error": "ExpiredToken", "message": "token has expired"})
254
+
}
255
+
256
+
repo, err := s.getRepoActorByDid(oauthToken.Sub)
257
+
if err != nil {
258
+
s.logger.Error("could not find actor in db", "error", err)
259
+
return helpers.ServerError(e, nil)
260
+
}
261
+
262
+
e.Set("repo", repo)
263
+
e.Set("did", repo.Repo.Did)
264
+
e.Set("token", accessToken)
265
+
e.Set("scopes", strings.Split(oauthToken.Parameters.Scope, " "))
266
+
267
+
return next(e)
268
+
}
269
+
}
+4
-261
server/server.go
+4
-261
server/server.go
···
4
4
"bytes"
5
5
"context"
6
6
"crypto/ecdsa"
7
-
"crypto/sha256"
8
7
"embed"
9
-
"encoding/base64"
10
8
"errors"
11
9
"fmt"
12
10
"io"
···
15
13
"net/smtp"
16
14
"os"
17
15
"path/filepath"
18
-
"strings"
19
16
"sync"
20
17
"text/template"
21
18
"time"
22
19
23
-
"github.com/Azure/go-autorest/autorest/to"
24
20
"github.com/aws/aws-sdk-go/aws"
25
21
"github.com/aws/aws-sdk-go/aws/credentials"
26
22
"github.com/aws/aws-sdk-go/aws/session"
···
32
28
"github.com/bluesky-social/indigo/xrpc"
33
29
"github.com/domodwyer/mailyak/v3"
34
30
"github.com/go-playground/validator"
35
-
"github.com/golang-jwt/jwt/v4"
36
31
"github.com/gorilla/sessions"
37
32
"github.com/haileyok/cocoon/identity"
38
33
"github.com/haileyok/cocoon/internal/db"
···
47
42
"github.com/labstack/echo/v4"
48
43
"github.com/labstack/echo/v4/middleware"
49
44
slogecho "github.com/samber/slog-echo"
50
-
"gitlab.com/yawning/secp256k1-voi"
51
-
secp256k1secec "gitlab.com/yawning/secp256k1-voi/secec"
52
45
"gorm.io/driver/sqlite"
53
46
"gorm.io/gorm"
54
47
)
···
197
190
return t.templates.ExecuteTemplate(w, name, data)
198
191
}
199
192
200
-
func (s *Server) handleAdminMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
201
-
return func(e echo.Context) error {
202
-
username, password, ok := e.Request().BasicAuth()
203
-
if !ok || username != "admin" || password != s.config.AdminPassword {
204
-
return helpers.InputError(e, to.StringPtr("Unauthorized"))
205
-
}
206
-
207
-
if err := next(e); err != nil {
208
-
e.Error(err)
209
-
}
210
-
211
-
return nil
212
-
}
213
-
}
214
-
215
-
func (s *Server) handleLegacySessionMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
216
-
return func(e echo.Context) error {
217
-
authheader := e.Request().Header.Get("authorization")
218
-
if authheader == "" {
219
-
return e.JSON(401, map[string]string{"error": "Unauthorized"})
220
-
}
221
-
222
-
pts := strings.Split(authheader, " ")
223
-
if len(pts) != 2 {
224
-
return helpers.ServerError(e, nil)
225
-
}
226
-
227
-
// move on to oauth session middleware if this is a dpop token
228
-
if pts[0] == "DPoP" {
229
-
return next(e)
230
-
}
231
-
232
-
tokenstr := pts[1]
233
-
token, _, err := new(jwt.Parser).ParseUnverified(tokenstr, jwt.MapClaims{})
234
-
claims, ok := token.Claims.(jwt.MapClaims)
235
-
if !ok {
236
-
return helpers.InputError(e, to.StringPtr("InvalidToken"))
237
-
}
238
-
239
-
var did string
240
-
var repo *models.RepoActor
241
-
242
-
// service auth tokens
243
-
lxm, hasLxm := claims["lxm"]
244
-
if hasLxm {
245
-
pts := strings.Split(e.Request().URL.String(), "/")
246
-
if lxm != pts[len(pts)-1] {
247
-
s.logger.Error("service auth lxm incorrect", "lxm", lxm, "expected", pts[len(pts)-1], "error", err)
248
-
return helpers.InputError(e, nil)
249
-
}
250
-
251
-
maybeDid, ok := claims["iss"].(string)
252
-
if !ok {
253
-
s.logger.Error("no iss in service auth token", "error", err)
254
-
return helpers.InputError(e, nil)
255
-
}
256
-
did = maybeDid
257
-
258
-
maybeRepo, err := s.getRepoActorByDid(did)
259
-
if err != nil {
260
-
s.logger.Error("error fetching repo", "error", err)
261
-
return helpers.ServerError(e, nil)
262
-
}
263
-
repo = maybeRepo
264
-
}
265
-
266
-
if token.Header["alg"] != "ES256K" {
267
-
token, err = new(jwt.Parser).Parse(tokenstr, func(t *jwt.Token) (any, error) {
268
-
if _, ok := t.Method.(*jwt.SigningMethodECDSA); !ok {
269
-
return nil, fmt.Errorf("unsupported signing method: %v", t.Header["alg"])
270
-
}
271
-
return s.privateKey.Public(), nil
272
-
})
273
-
if err != nil {
274
-
s.logger.Error("error parsing jwt", "error", err)
275
-
// NOTE: https://github.com/bluesky-social/atproto/discussions/3319
276
-
return e.JSON(400, map[string]string{"error": "ExpiredToken", "message": "token has expired"})
277
-
}
278
-
279
-
if !token.Valid {
280
-
return helpers.InputError(e, to.StringPtr("InvalidToken"))
281
-
}
282
-
} else {
283
-
kpts := strings.Split(tokenstr, ".")
284
-
signingInput := kpts[0] + "." + kpts[1]
285
-
hash := sha256.Sum256([]byte(signingInput))
286
-
sigBytes, err := base64.RawURLEncoding.DecodeString(kpts[2])
287
-
if err != nil {
288
-
s.logger.Error("error decoding signature bytes", "error", err)
289
-
return helpers.ServerError(e, nil)
290
-
}
291
-
292
-
if len(sigBytes) != 64 {
293
-
s.logger.Error("incorrect sigbytes length", "length", len(sigBytes))
294
-
return helpers.ServerError(e, nil)
295
-
}
296
-
297
-
rBytes := sigBytes[:32]
298
-
sBytes := sigBytes[32:]
299
-
rr, _ := secp256k1.NewScalarFromBytes((*[32]byte)(rBytes))
300
-
ss, _ := secp256k1.NewScalarFromBytes((*[32]byte)(sBytes))
301
-
302
-
sk, err := secp256k1secec.NewPrivateKey(repo.SigningKey)
303
-
if err != nil {
304
-
s.logger.Error("can't load private key", "error", err)
305
-
return err
306
-
}
307
-
308
-
pubKey, ok := sk.Public().(*secp256k1secec.PublicKey)
309
-
if !ok {
310
-
s.logger.Error("error getting public key from sk")
311
-
return helpers.ServerError(e, nil)
312
-
}
313
-
314
-
verified := pubKey.VerifyRaw(hash[:], rr, ss)
315
-
if !verified {
316
-
s.logger.Error("error verifying", "error", err)
317
-
return helpers.ServerError(e, nil)
318
-
}
319
-
}
320
-
321
-
isRefresh := e.Request().URL.Path == "/xrpc/com.atproto.server.refreshSession"
322
-
scope, _ := claims["scope"].(string)
323
-
324
-
if isRefresh && scope != "com.atproto.refresh" {
325
-
return helpers.InputError(e, to.StringPtr("InvalidToken"))
326
-
} else if !hasLxm && !isRefresh && scope != "com.atproto.access" {
327
-
return helpers.InputError(e, to.StringPtr("InvalidToken"))
328
-
}
329
-
330
-
table := "tokens"
331
-
if isRefresh {
332
-
table = "refresh_tokens"
333
-
}
334
-
335
-
if isRefresh {
336
-
type Result struct {
337
-
Found bool
338
-
}
339
-
var result Result
340
-
if err := s.db.Raw("SELECT EXISTS(SELECT 1 FROM "+table+" WHERE token = ?) AS found", nil, tokenstr).Scan(&result).Error; err != nil {
341
-
if err == gorm.ErrRecordNotFound {
342
-
return helpers.InputError(e, to.StringPtr("InvalidToken"))
343
-
}
344
-
345
-
s.logger.Error("error getting token from db", "error", err)
346
-
return helpers.ServerError(e, nil)
347
-
}
348
-
349
-
if !result.Found {
350
-
return helpers.InputError(e, to.StringPtr("InvalidToken"))
351
-
}
352
-
}
353
-
354
-
exp, ok := claims["exp"].(float64)
355
-
if !ok {
356
-
s.logger.Error("error getting iat from token")
357
-
return helpers.ServerError(e, nil)
358
-
}
359
-
360
-
if exp < float64(time.Now().UTC().Unix()) {
361
-
return helpers.InputError(e, to.StringPtr("ExpiredToken"))
362
-
}
363
-
364
-
if repo == nil {
365
-
maybeRepo, err := s.getRepoActorByDid(claims["sub"].(string))
366
-
if err != nil {
367
-
s.logger.Error("error fetching repo", "error", err)
368
-
return helpers.ServerError(e, nil)
369
-
}
370
-
repo = maybeRepo
371
-
did = repo.Repo.Did
372
-
}
373
-
374
-
e.Set("repo", repo)
375
-
e.Set("did", did)
376
-
e.Set("token", tokenstr)
377
-
378
-
if err := next(e); err != nil {
379
-
e.Error(err)
380
-
}
381
-
382
-
return nil
383
-
}
384
-
}
385
-
386
-
func (s *Server) handleOauthSessionMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
387
-
return func(e echo.Context) error {
388
-
authheader := e.Request().Header.Get("authorization")
389
-
if authheader == "" {
390
-
return e.JSON(401, map[string]string{"error": "Unauthorized"})
391
-
}
392
-
393
-
pts := strings.Split(authheader, " ")
394
-
if len(pts) != 2 {
395
-
return helpers.ServerError(e, nil)
396
-
}
397
-
398
-
if pts[0] != "DPoP" {
399
-
return next(e)
400
-
}
401
-
402
-
accessToken := pts[1]
403
-
404
-
nonce := s.oauthProvider.NextNonce()
405
-
if nonce != "" {
406
-
e.Response().Header().Set("DPoP-Nonce", nonce)
407
-
e.Response().Header().Add("access-control-expose-headers", "DPoP-Nonce")
408
-
}
409
-
410
-
proof, err := s.oauthProvider.DpopManager.CheckProof(e.Request().Method, "https://"+s.config.Hostname+e.Request().URL.String(), e.Request().Header, to.StringPtr(accessToken))
411
-
if err != nil {
412
-
s.logger.Error("invalid dpop proof", "error", err)
413
-
return helpers.InputError(e, to.StringPtr(err.Error()))
414
-
}
415
-
416
-
var oauthToken provider.OauthToken
417
-
if err := s.db.Raw("SELECT * FROM oauth_tokens WHERE token = ?", nil, accessToken).Scan(&oauthToken).Error; err != nil {
418
-
s.logger.Error("error finding access token in db", "error", err)
419
-
return helpers.InputError(e, nil)
420
-
}
421
-
422
-
if oauthToken.Token == "" {
423
-
return helpers.InputError(e, to.StringPtr("InvalidToken"))
424
-
}
425
-
426
-
if *oauthToken.Parameters.DpopJkt != proof.JKT {
427
-
s.logger.Error("jkt mismatch", "token", oauthToken.Parameters.DpopJkt, "proof", proof.JKT)
428
-
return helpers.InputError(e, to.StringPtr("dpop jkt mismatch"))
429
-
}
430
-
431
-
if time.Now().After(oauthToken.ExpiresAt) {
432
-
return e.JSON(400, map[string]string{"error": "ExpiredToken", "message": "token has expired"})
433
-
}
434
-
435
-
repo, err := s.getRepoActorByDid(oauthToken.Sub)
436
-
if err != nil {
437
-
s.logger.Error("could not find actor in db", "error", err)
438
-
return helpers.ServerError(e, nil)
439
-
}
440
-
441
-
e.Set("repo", repo)
442
-
e.Set("did", repo.Repo.Did)
443
-
e.Set("token", accessToken)
444
-
e.Set("scopes", strings.Split(oauthToken.Parameters.Scope, " "))
445
-
446
-
return next(e)
447
-
}
448
-
}
449
-
450
193
func New(args *Args) (*Server, error) {
451
194
if args.Addr == "" {
452
195
return nil, fmt.Errorf("addr must be set")
···
726
469
s.echo.GET("/xrpc/app.bsky.actor.getPreferences", s.handleActorGetPreferences, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
727
470
s.echo.POST("/xrpc/app.bsky.actor.putPreferences", s.handleActorPutPreferences, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
728
471
729
-
// are there any routes that we should be allowing without auth? i dont think so but idk
730
-
s.echo.GET("/xrpc/*", s.handleProxy, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
731
-
s.echo.POST("/xrpc/*", s.handleProxy, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
732
-
733
472
// admin routes
734
473
s.echo.POST("/xrpc/com.atproto.server.createInviteCode", s.handleCreateInviteCode, s.handleAdminMiddleware)
735
474
s.echo.POST("/xrpc/com.atproto.server.createInviteCodes", s.handleCreateInviteCodes, s.handleAdminMiddleware)
475
+
476
+
// are there any routes that we should be allowing without auth? i dont think so but idk
477
+
s.echo.GET("/xrpc/*", s.handleProxy, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
478
+
s.echo.POST("/xrpc/*", s.handleProxy, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
736
479
}
737
480
738
481
func (s *Server) Serve(ctx context.Context) error {