forked from hailey.at/cocoon
An atproto PDS written in Go

move middleware to their own file

Changed files
+274 -262
server
+1 -1
README.md
··· 25 - [x] com.atproto.repo.deleteRecord 26 - [x] com.atproto.repo.describeRepo 27 - [x] com.atproto.repo.getRecord 28 - - [ ] com.atproto.repo.importRepo 29 - [x] com.atproto.repo.listRecords 30 - [ ] com.atproto.repo.listMissingBlobs 31
··· 25 - [x] com.atproto.repo.deleteRecord 26 - [x] com.atproto.repo.describeRepo 27 - [x] com.atproto.repo.getRecord 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 - [x] com.atproto.repo.listRecords 30 - [ ] com.atproto.repo.listMissingBlobs 31
+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 "bytes" 5 "context" 6 "crypto/ecdsa" 7 - "crypto/sha256" 8 "embed" 9 - "encoding/base64" 10 "errors" 11 "fmt" 12 "io" ··· 15 "net/smtp" 16 "os" 17 "path/filepath" 18 - "strings" 19 "sync" 20 "text/template" 21 "time" 22 23 - "github.com/Azure/go-autorest/autorest/to" 24 "github.com/aws/aws-sdk-go/aws" 25 "github.com/aws/aws-sdk-go/aws/credentials" 26 "github.com/aws/aws-sdk-go/aws/session" ··· 32 "github.com/bluesky-social/indigo/xrpc" 33 "github.com/domodwyer/mailyak/v3" 34 "github.com/go-playground/validator" 35 - "github.com/golang-jwt/jwt/v4" 36 "github.com/gorilla/sessions" 37 "github.com/haileyok/cocoon/identity" 38 "github.com/haileyok/cocoon/internal/db" ··· 47 "github.com/labstack/echo/v4" 48 "github.com/labstack/echo/v4/middleware" 49 slogecho "github.com/samber/slog-echo" 50 - "gitlab.com/yawning/secp256k1-voi" 51 - secp256k1secec "gitlab.com/yawning/secp256k1-voi/secec" 52 "gorm.io/driver/sqlite" 53 "gorm.io/gorm" 54 ) ··· 197 return t.templates.ExecuteTemplate(w, name, data) 198 } 199 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 func New(args *Args) (*Server, error) { 451 if args.Addr == "" { 452 return nil, fmt.Errorf("addr must be set") ··· 726 s.echo.GET("/xrpc/app.bsky.actor.getPreferences", s.handleActorGetPreferences, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware) 727 s.echo.POST("/xrpc/app.bsky.actor.putPreferences", s.handleActorPutPreferences, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware) 728 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 // admin routes 734 s.echo.POST("/xrpc/com.atproto.server.createInviteCode", s.handleCreateInviteCode, s.handleAdminMiddleware) 735 s.echo.POST("/xrpc/com.atproto.server.createInviteCodes", s.handleCreateInviteCodes, s.handleAdminMiddleware) 736 } 737 738 func (s *Server) Serve(ctx context.Context) error {
··· 4 "bytes" 5 "context" 6 "crypto/ecdsa" 7 "embed" 8 "errors" 9 "fmt" 10 "io" ··· 13 "net/smtp" 14 "os" 15 "path/filepath" 16 "sync" 17 "text/template" 18 "time" 19 20 "github.com/aws/aws-sdk-go/aws" 21 "github.com/aws/aws-sdk-go/aws/credentials" 22 "github.com/aws/aws-sdk-go/aws/session" ··· 28 "github.com/bluesky-social/indigo/xrpc" 29 "github.com/domodwyer/mailyak/v3" 30 "github.com/go-playground/validator" 31 "github.com/gorilla/sessions" 32 "github.com/haileyok/cocoon/identity" 33 "github.com/haileyok/cocoon/internal/db" ··· 42 "github.com/labstack/echo/v4" 43 "github.com/labstack/echo/v4/middleware" 44 slogecho "github.com/samber/slog-echo" 45 "gorm.io/driver/sqlite" 46 "gorm.io/gorm" 47 ) ··· 190 return t.templates.ExecuteTemplate(w, name, data) 191 } 192 193 func New(args *Args) (*Server, error) { 194 if args.Addr == "" { 195 return nil, fmt.Errorf("addr must be set") ··· 469 s.echo.GET("/xrpc/app.bsky.actor.getPreferences", s.handleActorGetPreferences, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware) 470 s.echo.POST("/xrpc/app.bsky.actor.putPreferences", s.handleActorPutPreferences, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware) 471 472 // admin routes 473 s.echo.POST("/xrpc/com.atproto.server.createInviteCode", s.handleCreateInviteCode, s.handleAdminMiddleware) 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) 479 } 480 481 func (s *Server) Serve(ctx context.Context) error {