appview: oauth: setup handlers and service #89

closed
opened by anirudh.fi targeting master from oauth
+2
.gitignore
··· 11 ./avatar/node_modules/* 12 patches 13 *.qcow2
··· 11 ./avatar/node_modules/* 12 patches 13 *.qcow2 14 + .DS_Store 15 + .env
+24
appview/oauth/client/oauth_client.go
···
··· 1 + package client 2 + 3 + import ( 4 + oauth "github.com/haileyok/atproto-oauth-golang" 5 + "github.com/haileyok/atproto-oauth-golang/helpers" 6 + ) 7 + 8 + type OAuthClient struct { 9 + *oauth.Client 10 + } 11 + 12 + func NewClient(clientId, clientJwk, redirectUri string) (*OAuthClient, error) { 13 + k, err := helpers.ParseJWKFromBytes([]byte(clientJwk)) 14 + if err != nil { 15 + return nil, err 16 + } 17 + 18 + cli, err := oauth.NewClient(oauth.ClientArgs{ 19 + ClientId: clientId, 20 + ClientJwk: k, 21 + RedirectUri: redirectUri, 22 + }) 23 + return &OAuthClient{cli}, err 24 + }
+260
appview/oauth/handler/handler.go
···
··· 1 + package oauth 2 + 3 + import ( 4 + "encoding/json" 5 + "fmt" 6 + "log" 7 + "net/http" 8 + "net/url" 9 + "strings" 10 + 11 + "github.com/go-chi/chi/v5" 12 + "github.com/gorilla/sessions" 13 + "github.com/haileyok/atproto-oauth-golang/helpers" 14 + "github.com/lestrrat-go/jwx/v2/jwk" 15 + "tangled.sh/tangled.sh/core/appview" 16 + "tangled.sh/tangled.sh/core/appview/db" 17 + "tangled.sh/tangled.sh/core/appview/oauth" 18 + "tangled.sh/tangled.sh/core/appview/oauth/client" 19 + "tangled.sh/tangled.sh/core/appview/pages" 20 + ) 21 + 22 + const ( 23 + oauthScope = "atproto transition:generic" 24 + ) 25 + 26 + type OAuthHandler struct { 27 + Config *appview.Config 28 + Pages *pages.Pages 29 + Resolver *appview.Resolver 30 + Db *db.DB 31 + Store *sessions.CookieStore 32 + OAuth *oauth.OAuth 33 + } 34 + 35 + func (o *OAuthHandler) Router() http.Handler { 36 + r := chi.NewRouter() 37 + 38 + // gets mounted on /oauth 39 + r.Get("/client-metadata.json", o.clientMetadata) 40 + r.Get("/jwks.json", o.jwks) 41 + r.Get("/login", o.login) 42 + r.Post("/login", o.login) 43 + r.Get("/callback", o.callback) 44 + return r 45 + } 46 + 47 + func (o *OAuthHandler) clientMetadata(w http.ResponseWriter, r *http.Request) { 48 + metadata := map[string]any{ 49 + "client_id": o.Config.OAuth.ServerMetadataUrl, 50 + "client_name": "Tangled", 51 + "subject_type": "public", 52 + "client_uri": o.Config.Core.AppviewHost, 53 + "redirect_uris": []string{fmt.Sprintf("%s/oauth/callback", o.Config.Core.AppviewHost)}, 54 + "grant_types": []string{"authorization_code", "refresh_token"}, 55 + "response_types": []string{"code"}, 56 + "application_type": "web", 57 + "dpop_bound_access_tokens": true, 58 + "jwks_uri": fmt.Sprintf("%s/oauth/jwks.json", o.Config.Core.AppviewHost), 59 + "scope": "atproto transition:generic", 60 + "token_endpoint_auth_method": "private_key_jwt", 61 + "token_endpoint_auth_signing_alg": "ES256", 62 + } 63 + 64 + fmt.Println("clientMetadata", metadata) 65 + 66 + w.Header().Set("Content-Type", "application/json") 67 + w.WriteHeader(http.StatusOK) 68 + json.NewEncoder(w).Encode(metadata) 69 + } 70 + 71 + func (o *OAuthHandler) jwks(w http.ResponseWriter, r *http.Request) { 72 + jwks := o.Config.OAuth.Jwks 73 + pubKey, err := pubKeyFromJwk(jwks) 74 + if err != nil { 75 + log.Printf("error parsing public key: %v", err) 76 + http.Error(w, err.Error(), http.StatusInternalServerError) 77 + return 78 + } 79 + 80 + response := helpers.CreateJwksResponseObject(pubKey) 81 + 82 + w.Header().Set("Content-Type", "application/json") 83 + w.WriteHeader(http.StatusOK) 84 + json.NewEncoder(w).Encode(response) 85 + } 86 + 87 + // temporary until we swap out the main login page 88 + func (o *OAuthHandler) login(w http.ResponseWriter, r *http.Request) { 89 + switch r.Method { 90 + case http.MethodGet: 91 + o.Pages.OAuthLogin(w, pages.LoginParams{}) 92 + case http.MethodPost: 93 + handle := strings.TrimPrefix(r.FormValue("handle"), "@") 94 + 95 + resolved, err := o.Resolver.ResolveIdent(r.Context(), handle) 96 + if err != nil { 97 + log.Println("failed to resolve handle:", err) 98 + o.Pages.Notice(w, "login-msg", fmt.Sprintf("\"%s\" is an invalid handle.", handle)) 99 + return 100 + } 101 + oauthClient, err := client.NewClient( 102 + o.Config.OAuth.ServerMetadataUrl, 103 + o.Config.OAuth.Jwks, 104 + fmt.Sprintf("%s/oauth/callback", o.Config.Core.AppviewHost)) 105 + 106 + if err != nil { 107 + log.Println("failed to create oauth client:", err) 108 + o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 109 + return 110 + } 111 + 112 + authServer, err := oauthClient.ResolvePdsAuthServer(r.Context(), resolved.PDSEndpoint()) 113 + if err != nil { 114 + log.Println("failed to resolve auth server:", err) 115 + o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 116 + return 117 + } 118 + 119 + authMeta, err := oauthClient.FetchAuthServerMetadata(r.Context(), authServer) 120 + if err != nil { 121 + log.Println("failed to fetch auth server metadata:", err) 122 + o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 123 + return 124 + } 125 + 126 + dpopKey, err := helpers.GenerateKey(nil) 127 + if err != nil { 128 + log.Println("failed to generate dpop key:", err) 129 + o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 130 + return 131 + } 132 + 133 + dpopKeyJson, err := json.Marshal(dpopKey) 134 + if err != nil { 135 + log.Println("failed to marshal dpop key:", err) 136 + o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 137 + return 138 + } 139 + 140 + parResp, err := oauthClient.SendParAuthRequest(r.Context(), authServer, authMeta, handle, oauthScope, dpopKey) 141 + if err != nil { 142 + log.Println("failed to send par auth request:", err) 143 + o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 144 + return 145 + } 146 + 147 + err = db.SaveOAuthRequest(o.Db, db.OAuthRequest{ 148 + Did: resolved.DID.String(), 149 + PdsUrl: resolved.PDSEndpoint(), 150 + Handle: handle, 151 + AuthserverIss: authMeta.Issuer, 152 + PkceVerifier: parResp.PkceVerifier, 153 + DpopAuthserverNonce: parResp.DpopAuthserverNonce, 154 + DpopPrivateJwk: string(dpopKeyJson), 155 + State: parResp.State, 156 + }) 157 + if err != nil { 158 + log.Println("failed to save oauth request:", err) 159 + o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 160 + return 161 + } 162 + 163 + u, _ := url.Parse(authMeta.AuthorizationEndpoint) 164 + u.RawQuery = fmt.Sprintf("client_id=%s&request_uri=%s", url.QueryEscape(o.Config.OAuth.ServerMetadataUrl), parResp.RequestUri) 165 + o.Pages.HxRedirect(w, u.String()) 166 + } 167 + } 168 + 169 + func (o *OAuthHandler) callback(w http.ResponseWriter, r *http.Request) { 170 + state := r.FormValue("state") 171 + 172 + oauthRequest, err := db.GetOAuthRequestByState(o.Db, state) 173 + if err != nil { 174 + log.Println("failed to get oauth request:", err) 175 + o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 176 + return 177 + } 178 + 179 + defer func() { 180 + err := db.DeleteOAuthRequestByState(o.Db, state) 181 + if err != nil { 182 + log.Println("failed to delete oauth request for state:", state, err) 183 + } 184 + }() 185 + 186 + code := r.FormValue("code") 187 + if code == "" { 188 + log.Println("missing code for state: ", state) 189 + o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 190 + return 191 + } 192 + 193 + iss := r.FormValue("iss") 194 + if iss == "" { 195 + log.Println("missing iss for state: ", state) 196 + o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 197 + return 198 + } 199 + 200 + oauthClient, err := client.NewClient( 201 + o.Config.OAuth.ServerMetadataUrl, 202 + o.Config.OAuth.Jwks, 203 + fmt.Sprintf("%s/oauth/callback", o.Config.Core.AppviewHost)) 204 + 205 + if err != nil { 206 + log.Println("failed to create oauth client:", err) 207 + o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 208 + return 209 + } 210 + 211 + jwk, err := helpers.ParseJWKFromBytes([]byte(oauthRequest.DpopPrivateJwk)) 212 + if err != nil { 213 + log.Println("failed to parse jwk:", err) 214 + o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 215 + return 216 + } 217 + 218 + tokenResp, err := oauthClient.InitialTokenRequest( 219 + r.Context(), 220 + code, 221 + oauthRequest.AuthserverIss, 222 + oauthRequest.PkceVerifier, 223 + oauthRequest.DpopAuthserverNonce, 224 + jwk, 225 + ) 226 + if err != nil { 227 + log.Println("failed to get token:", err) 228 + o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 229 + return 230 + } 231 + 232 + if tokenResp.Scope != oauthScope { 233 + log.Println("scope doesn't match:", tokenResp.Scope) 234 + o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 235 + return 236 + } 237 + 238 + err = o.OAuth.SaveSession(w, r, oauthRequest, tokenResp) 239 + if err != nil { 240 + log.Println("failed to save session:", err) 241 + o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 242 + return 243 + } 244 + 245 + log.Println("session saved successfully") 246 + 247 + http.Redirect(w, r, "/", http.StatusFound) 248 + } 249 + 250 + func pubKeyFromJwk(jwks string) (jwk.Key, error) { 251 + k, err := helpers.ParseJWKFromBytes([]byte(jwks)) 252 + if err != nil { 253 + return nil, err 254 + } 255 + pubKey, err := k.PublicKey() 256 + if err != nil { 257 + return nil, err 258 + } 259 + return pubKey, nil 260 + }
+18 -12
go.mod
··· 1 module tangled.sh/tangled.sh/core 2 3 - go 1.23.0 4 5 - toolchain go1.23.6 6 7 require ( 8 github.com/Blank-Xu/sql-adapter v1.1.1 9 github.com/alecthomas/chroma/v2 v2.15.0 10 github.com/bluekeyes/go-gitdiff v0.8.1 11 - github.com/bluesky-social/indigo v0.0.0-20250123072624-9e3b84fdbb20 12 github.com/bluesky-social/jetstream v0.0.0-20241210005130-ea96859b93d1 13 github.com/casbin/casbin/v2 v2.103.0 14 github.com/cyphar/filepath-securejoin v0.4.1 ··· 19 github.com/go-git/go-git/v5 v5.14.0 20 github.com/google/uuid v1.6.0 21 github.com/gorilla/sessions v1.4.0 22 github.com/ipfs/go-cid v0.5.0 23 github.com/mattn/go-sqlite3 v1.14.24 24 github.com/microcosm-cc/bluemonday v1.0.27 25 github.com/resend/resend-go/v2 v2.15.0 ··· 41 github.com/casbin/govaluate v1.3.0 // indirect 42 github.com/cespare/xxhash/v2 v2.3.0 // indirect 43 github.com/cloudflare/circl v1.6.0 // indirect 44 - github.com/davecgh/go-spew v1.1.1 // indirect 45 github.com/dlclark/regexp2 v1.11.5 // indirect 46 github.com/emirpasic/gods v1.18.1 // indirect 47 github.com/felixge/httpsnoop v1.0.4 // indirect 48 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 49 github.com/go-git/go-billy/v5 v5.6.2 // indirect 50 - github.com/go-logr/logr v1.4.1 // indirect 51 github.com/go-logr/stdr v1.2.2 // indirect 52 github.com/goccy/go-json v0.10.2 // indirect 53 github.com/gogo/protobuf v1.3.2 // indirect 54 github.com/gorilla/css v1.0.1 // indirect 55 github.com/gorilla/securecookie v1.1.2 // indirect 56 github.com/gorilla/websocket v1.5.1 // indirect ··· 75 github.com/kevinburke/ssh_config v1.2.0 // indirect 76 github.com/klauspost/compress v1.17.9 // indirect 77 github.com/klauspost/cpuid/v2 v2.2.7 // indirect 78 github.com/mattn/go-isatty v0.0.20 // indirect 79 github.com/minio/sha256-simd v1.0.1 // indirect 80 github.com/mr-tron/base58 v1.2.0 // indirect ··· 86 github.com/opentracing/opentracing-go v1.2.0 // indirect 87 github.com/pjbgf/sha1cd v0.3.2 // indirect 88 github.com/pkg/errors v0.9.1 // indirect 89 - github.com/pmezard/go-difflib v1.0.0 // indirect 90 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect 91 github.com/prometheus/client_golang v1.19.1 // indirect 92 github.com/prometheus/client_model v0.6.1 // indirect 93 github.com/prometheus/common v0.54.0 // indirect 94 github.com/prometheus/procfs v0.15.1 // indirect 95 github.com/sergi/go-diff v1.3.1 // indirect 96 github.com/skeema/knownhosts v1.3.1 // indirect 97 github.com/spaolacci/murmur3 v1.1.0 // indirect 98 - github.com/stretchr/testify v1.10.0 // indirect 99 github.com/xanzy/ssh-agent v0.3.3 // indirect 100 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 101 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 102 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect 103 - go.opentelemetry.io/otel v1.21.0 // indirect 104 - go.opentelemetry.io/otel/metric v1.21.0 // indirect 105 - go.opentelemetry.io/otel/trace v1.21.0 // indirect 106 go.uber.org/atomic v1.11.0 // indirect 107 go.uber.org/multierr v1.11.0 // indirect 108 go.uber.org/zap v1.26.0 // indirect 109 golang.org/x/crypto v0.37.0 // indirect 110 golang.org/x/net v0.39.0 // indirect 111 golang.org/x/sys v0.32.0 // indirect 112 - golang.org/x/time v0.5.0 // indirect 113 google.golang.org/protobuf v1.34.2 // indirect 114 gopkg.in/warnings.v0 v0.1.2 // indirect 115 - gopkg.in/yaml.v3 v3.0.1 // indirect 116 lukechampine.com/blake3 v1.2.1 // indirect 117 ) 118
··· 1 module tangled.sh/tangled.sh/core 2 3 + go 1.24.0 4 5 + toolchain go1.24.3 6 7 require ( 8 github.com/Blank-Xu/sql-adapter v1.1.1 9 github.com/alecthomas/chroma/v2 v2.15.0 10 github.com/bluekeyes/go-gitdiff v0.8.1 11 + github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188 12 github.com/bluesky-social/jetstream v0.0.0-20241210005130-ea96859b93d1 13 github.com/casbin/casbin/v2 v2.103.0 14 github.com/cyphar/filepath-securejoin v0.4.1 ··· 19 github.com/go-git/go-git/v5 v5.14.0 20 github.com/google/uuid v1.6.0 21 github.com/gorilla/sessions v1.4.0 22 + github.com/haileyok/atproto-oauth-golang v0.0.2 23 github.com/ipfs/go-cid v0.5.0 24 + github.com/lestrrat-go/jwx/v2 v2.0.12 25 github.com/mattn/go-sqlite3 v1.14.24 26 github.com/microcosm-cc/bluemonday v1.0.27 27 github.com/resend/resend-go/v2 v2.15.0 ··· 43 github.com/casbin/govaluate v1.3.0 // indirect 44 github.com/cespare/xxhash/v2 v2.3.0 // indirect 45 github.com/cloudflare/circl v1.6.0 // indirect 46 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect 47 github.com/dlclark/regexp2 v1.11.5 // indirect 48 github.com/emirpasic/gods v1.18.1 // indirect 49 github.com/felixge/httpsnoop v1.0.4 // indirect 50 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 51 github.com/go-git/go-billy/v5 v5.6.2 // indirect 52 + github.com/go-logr/logr v1.4.2 // indirect 53 github.com/go-logr/stdr v1.2.2 // indirect 54 github.com/goccy/go-json v0.10.2 // indirect 55 github.com/gogo/protobuf v1.3.2 // indirect 56 + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect 57 github.com/gorilla/css v1.0.1 // indirect 58 github.com/gorilla/securecookie v1.1.2 // indirect 59 github.com/gorilla/websocket v1.5.1 // indirect ··· 78 github.com/kevinburke/ssh_config v1.2.0 // indirect 79 github.com/klauspost/compress v1.17.9 // indirect 80 github.com/klauspost/cpuid/v2 v2.2.7 // indirect 81 + github.com/lestrrat-go/blackmagic v1.0.2 // indirect 82 + github.com/lestrrat-go/httpcc v1.0.1 // indirect 83 + github.com/lestrrat-go/httprc v1.0.4 // indirect 84 + github.com/lestrrat-go/iter v1.0.2 // indirect 85 + github.com/lestrrat-go/option v1.0.1 // indirect 86 github.com/mattn/go-isatty v0.0.20 // indirect 87 github.com/minio/sha256-simd v1.0.1 // indirect 88 github.com/mr-tron/base58 v1.2.0 // indirect ··· 94 github.com/opentracing/opentracing-go v1.2.0 // indirect 95 github.com/pjbgf/sha1cd v0.3.2 // indirect 96 github.com/pkg/errors v0.9.1 // indirect 97 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect 98 github.com/prometheus/client_golang v1.19.1 // indirect 99 github.com/prometheus/client_model v0.6.1 // indirect 100 github.com/prometheus/common v0.54.0 // indirect 101 github.com/prometheus/procfs v0.15.1 // indirect 102 + github.com/segmentio/asm v1.2.0 // indirect 103 github.com/sergi/go-diff v1.3.1 // indirect 104 github.com/skeema/knownhosts v1.3.1 // indirect 105 github.com/spaolacci/murmur3 v1.1.0 // indirect 106 github.com/xanzy/ssh-agent v0.3.3 // indirect 107 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 108 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 109 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect 110 + go.opentelemetry.io/otel v1.29.0 // indirect 111 + go.opentelemetry.io/otel/metric v1.29.0 // indirect 112 + go.opentelemetry.io/otel/trace v1.29.0 // indirect 113 go.uber.org/atomic v1.11.0 // indirect 114 go.uber.org/multierr v1.11.0 // indirect 115 go.uber.org/zap v1.26.0 // indirect 116 golang.org/x/crypto v0.37.0 // indirect 117 golang.org/x/net v0.39.0 // indirect 118 golang.org/x/sys v0.32.0 // indirect 119 + golang.org/x/time v0.8.0 // indirect 120 google.golang.org/protobuf v1.34.2 // indirect 121 gopkg.in/warnings.v0 v0.1.2 // indirect 122 lukechampine.com/blake3 v1.2.1 // indirect 123 ) 124
+61 -16
go.sum
··· 26 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 27 github.com/bluekeyes/go-gitdiff v0.8.1 h1:lL1GofKMywO17c0lgQmJYcKek5+s8X6tXVNOLxy4smI= 28 github.com/bluekeyes/go-gitdiff v0.8.1/go.mod h1:WWAk1Mc6EgWarCrPFO+xeYlujPu98VuLW3Tu+B/85AE= 29 - github.com/bluesky-social/indigo v0.0.0-20250123072624-9e3b84fdbb20 h1:yHusfYYi8odoCcsI6AurU+dRWb7itHAQNwt3/Rl9Vfs= 30 - github.com/bluesky-social/indigo v0.0.0-20250123072624-9e3b84fdbb20/go.mod h1:Qp4YqWf+AQ3TwQCxV5Ls8O2tXE55zVTGVs3zTmn7BOg= 31 github.com/bluesky-social/jetstream v0.0.0-20241210005130-ea96859b93d1 h1:CFvRtYNSnWRAi/98M3O466t9dYuwtesNbu6FVPymRrA= 32 github.com/bluesky-social/jetstream v0.0.0-20241210005130-ea96859b93d1/go.mod h1:WiYEeyJSdUwqoaZ71KJSpTblemUCpwJfh5oVXplK6T4= 33 github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= ··· 52 github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= 53 github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= 54 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 55 - github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 56 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 57 github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE= 58 github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU= 59 github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= ··· 82 github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk= 83 github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8= 84 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 85 - github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= 86 - github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 87 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 88 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 89 github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= ··· 91 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 92 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 93 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 94 github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= 95 github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 96 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= ··· 111 github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= 112 github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= 113 github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 114 github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 115 github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 116 github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= ··· 159 github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= 160 github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= 161 github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= 162 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 163 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 164 github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= ··· 177 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 178 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 179 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 180 github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= 181 github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= 182 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= ··· 212 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 213 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 214 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 215 - github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 216 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 217 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0= 218 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= 219 github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= ··· 227 github.com/resend/resend-go/v2 v2.15.0 h1:B6oMEPf8IEQwn2Ovx/9yymkESLDSeNfLFaNMw+mzHhE= 228 github.com/resend/resend-go/v2 v2.15.0/go.mod h1:3YCb8c8+pLiqhtRFXTyFwlLvfjQtluxOr9HEh2BwCkQ= 229 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 230 - github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 231 - github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 232 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 233 github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= 234 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 235 github.com/sethvargo/go-envconfig v1.1.0 h1:cWZiJxeTm7AlCvzGXrEXaSTCNgip5oJepekh/BOQuog= ··· 246 github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 247 github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 248 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 249 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 250 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 251 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 252 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 253 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 254 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 255 github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= ··· 270 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I= 271 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= 272 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= 273 - go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= 274 - go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= 275 - go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= 276 - go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= 277 - go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= 278 - go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= 279 go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 280 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 281 go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= ··· 303 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 304 golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= 305 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 306 golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= 307 golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 308 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= ··· 314 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 315 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 316 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= 317 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 318 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 319 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= ··· 327 golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= 328 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 329 golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 330 golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= 331 golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 332 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= ··· 334 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 335 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 336 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 337 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 338 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 339 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= ··· 348 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 349 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 350 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 351 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 352 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 353 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= ··· 357 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 358 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 359 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 360 golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 361 golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 362 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= ··· 364 golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 365 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 366 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 367 golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= 368 golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 369 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= ··· 372 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 373 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 374 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 375 golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 376 golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 377 - golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 378 - golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 379 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 380 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 381 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= ··· 389 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 390 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 391 golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= 392 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 393 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 394 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
··· 26 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 27 github.com/bluekeyes/go-gitdiff v0.8.1 h1:lL1GofKMywO17c0lgQmJYcKek5+s8X6tXVNOLxy4smI= 28 github.com/bluekeyes/go-gitdiff v0.8.1/go.mod h1:WWAk1Mc6EgWarCrPFO+xeYlujPu98VuLW3Tu+B/85AE= 29 + github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188 h1:1sQaG37xk08/rpmdhrmMkfQWF9kZbnfHm9Zav3bbSMk= 30 + github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188/go.mod h1:NVBwZvbBSa93kfyweAmKwOLYawdVHdwZ9s+GZtBBVLA= 31 github.com/bluesky-social/jetstream v0.0.0-20241210005130-ea96859b93d1 h1:CFvRtYNSnWRAi/98M3O466t9dYuwtesNbu6FVPymRrA= 32 github.com/bluesky-social/jetstream v0.0.0-20241210005130-ea96859b93d1/go.mod h1:WiYEeyJSdUwqoaZ71KJSpTblemUCpwJfh5oVXplK6T4= 33 github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= ··· 52 github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= 53 github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= 54 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 55 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 56 + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 57 + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 58 + github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= 59 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= 60 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 61 github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE= 62 github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU= 63 github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= ··· 86 github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk= 87 github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8= 88 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 89 + github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 90 + github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 91 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 92 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 93 github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= ··· 95 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 96 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 97 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 98 + github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= 99 + github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 100 github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= 101 github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 102 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= ··· 117 github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= 118 github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= 119 github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 120 + github.com/haileyok/atproto-oauth-golang v0.0.2 h1:61KPkLB615LQXR2f5x1v3sf6vPe6dOXqNpTYCgZ0Fz8= 121 + github.com/haileyok/atproto-oauth-golang v0.0.2/go.mod h1:jcZ4GCjo5I5RuE/RsAXg1/b6udw7R4W+2rb/cGyTDK8= 122 github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 123 github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 124 github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= ··· 167 github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= 168 github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= 169 github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= 170 + github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 171 + github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 172 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 173 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 174 github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= ··· 187 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 188 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 189 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 190 + github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= 191 + github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= 192 + github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= 193 + github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= 194 + github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= 195 + github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8= 196 + github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= 197 + github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= 198 + github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= 199 + github.com/lestrrat-go/jwx/v2 v2.0.12 h1:3d589+5w/b9b7S3DneICPW16AqTyYXB7VRjgluSDWeA= 200 + github.com/lestrrat-go/jwx/v2 v2.0.12/go.mod h1:Mq4KN1mM7bp+5z/W5HS8aCNs5RKZ911G/0y2qUjAQuQ= 201 + github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 202 + github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= 203 + github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 204 github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= 205 github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= 206 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= ··· 236 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 237 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 238 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 239 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 240 + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 241 + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 242 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0= 243 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= 244 github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= ··· 252 github.com/resend/resend-go/v2 v2.15.0 h1:B6oMEPf8IEQwn2Ovx/9yymkESLDSeNfLFaNMw+mzHhE= 253 github.com/resend/resend-go/v2 v2.15.0/go.mod h1:3YCb8c8+pLiqhtRFXTyFwlLvfjQtluxOr9HEh2BwCkQ= 254 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 255 + github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 256 + github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 257 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 258 + github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= 259 + github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= 260 github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= 261 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 262 github.com/sethvargo/go-envconfig v1.1.0 h1:cWZiJxeTm7AlCvzGXrEXaSTCNgip5oJepekh/BOQuog= ··· 273 github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 274 github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 275 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 276 + github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 277 + github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 278 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 279 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 280 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 281 + github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 282 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 283 + github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 284 + github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 285 + github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 286 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 287 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 288 github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= ··· 303 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I= 304 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= 305 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= 306 + go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= 307 + go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= 308 + go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= 309 + go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= 310 + go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= 311 + go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= 312 go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 313 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 314 go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= ··· 336 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 337 golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= 338 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 339 + golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= 340 golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= 341 golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 342 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= ··· 348 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 349 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 350 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= 351 + golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 352 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 353 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 354 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= ··· 362 golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= 363 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 364 golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 365 + golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 366 golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= 367 golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 368 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= ··· 370 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 371 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 372 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 373 + golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 374 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 375 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 376 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= ··· 385 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 386 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 387 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 388 + golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 389 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 390 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 391 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= ··· 395 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 396 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 397 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 398 + golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 399 + golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 400 golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 401 golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 402 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= ··· 404 golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 405 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 406 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 407 + golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 408 + golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= 409 golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= 410 golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 411 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= ··· 414 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 415 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 416 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 417 + golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 418 + golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 419 golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 420 golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 421 + golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= 422 + golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 423 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 424 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 425 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= ··· 433 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 434 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 435 golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= 436 + golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 437 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 438 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 439 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+80
appview/xrpcclient/xrpc.go
···
··· 1 + package xrpcclient 2 + 3 + import ( 4 + "bytes" 5 + "context" 6 + "io" 7 + 8 + "github.com/bluesky-social/indigo/api/atproto" 9 + "github.com/bluesky-social/indigo/xrpc" 10 + oauth "github.com/haileyok/atproto-oauth-golang" 11 + ) 12 + 13 + type Client struct { 14 + *oauth.XrpcClient 15 + authArgs *oauth.XrpcAuthedRequestArgs 16 + } 17 + 18 + func NewClient(client *oauth.XrpcClient, authArgs *oauth.XrpcAuthedRequestArgs) *Client { 19 + return &Client{ 20 + XrpcClient: client, 21 + authArgs: authArgs, 22 + } 23 + } 24 + 25 + func (c *Client) RepoPutRecord(ctx context.Context, input *atproto.RepoPutRecord_Input) (*atproto.RepoPutRecord_Output, error) { 26 + var out atproto.RepoPutRecord_Output 27 + if err := c.Do(ctx, c.authArgs, xrpc.Procedure, "application/json", "com.atproto.repo.putRecord", nil, input, &out); err != nil { 28 + return nil, err 29 + } 30 + 31 + return &out, nil 32 + } 33 + 34 + func (c *Client) RepoGetRecord(ctx context.Context, cid string, collection string, repo string, rkey string) (*atproto.RepoGetRecord_Output, error) { 35 + var out atproto.RepoGetRecord_Output 36 + 37 + params := map[string]interface{}{ 38 + "cid": cid, 39 + "collection": collection, 40 + "repo": repo, 41 + "rkey": rkey, 42 + } 43 + if err := c.Do(ctx, c.authArgs, xrpc.Query, "", "com.atproto.repo.getRecord", params, nil, &out); err != nil { 44 + return nil, err 45 + } 46 + 47 + return &out, nil 48 + } 49 + 50 + func (c *Client) RepoUploadBlob(ctx context.Context, input io.Reader) (*atproto.RepoUploadBlob_Output, error) { 51 + var out atproto.RepoUploadBlob_Output 52 + if err := c.Do(ctx, c.authArgs, xrpc.Procedure, "*/*", "com.atproto.repo.uploadBlob", nil, input, &out); err != nil { 53 + return nil, err 54 + } 55 + 56 + return &out, nil 57 + } 58 + 59 + func (c *Client) SyncGetBlob(ctx context.Context, cid string, did string) ([]byte, error) { 60 + buf := new(bytes.Buffer) 61 + 62 + params := map[string]interface{}{ 63 + "cid": cid, 64 + "did": did, 65 + } 66 + if err := c.Do(ctx, c.authArgs, xrpc.Query, "", "com.atproto.sync.getBlob", params, nil, buf); err != nil { 67 + return nil, err 68 + } 69 + 70 + return buf.Bytes(), nil 71 + } 72 + 73 + func (c *Client) RepoDeleteRecord(ctx context.Context, input *atproto.RepoDeleteRecord_Input) (*atproto.RepoDeleteRecord_Output, error) { 74 + var out atproto.RepoDeleteRecord_Output 75 + if err := c.Do(ctx, c.authArgs, xrpc.Procedure, "application/json", "com.atproto.repo.deleteRecord", nil, input, &out); err != nil { 76 + return nil, err 77 + } 78 + 79 + return &out, nil 80 + }
+1 -1
.air/appview.toml
··· 1 [build] 2 cmd = "tailwindcss -i input.css -o ./appview/pages/static/tw.css && go build -o .bin/app ./cmd/appview/main.go" 3 - bin = ".bin/app" 4 root = "." 5 6 exclude_regex = [".*_templ.go"]
··· 1 [build] 2 cmd = "tailwindcss -i input.css -o ./appview/pages/static/tw.css && go build -o .bin/app ./cmd/appview/main.go" 3 + bin = ";set -o allexport && source .env && set +o allexport; .bin/app" 4 root = "." 5 6 exclude_regex = [".*_templ.go"]
+37 -10
appview/config.go
··· 6 "github.com/sethvargo/go-envconfig" 7 ) 8 9 type Config struct { 10 - CookieSecret string `env:"TANGLED_COOKIE_SECRET, default=00000000000000000000000000000000"` 11 - DbPath string `env:"TANGLED_DB_PATH, default=appview.db"` 12 - ListenAddr string `env:"TANGLED_LISTEN_ADDR, default=0.0.0.0:3000"` 13 - Dev bool `env:"TANGLED_DEV, default=false"` 14 - JetstreamEndpoint string `env:"TANGLED_JETSTREAM_ENDPOINT, default=wss://jetstream1.us-east.bsky.network/subscribe"` 15 - ResendApiKey string `env:"TANGLED_RESEND_API_KEY"` 16 - CamoHost string `env:"TANGLED_CAMO_HOST, default=https://camo.tangled.sh"` 17 - CamoSharedSecret string `env:"TANGLED_CAMO_SHARED_SECRET"` 18 - AvatarSharedSecret string `env:"TANGLED_AVATAR_SHARED_SECRET"` 19 - AvatarHost string `env:"TANGLED_AVATAR_HOST, default=https://avatar.tangled.sh"` 20 } 21 22 func LoadConfig(ctx context.Context) (*Config, error) {
··· 6 "github.com/sethvargo/go-envconfig" 7 ) 8 9 + type CoreConfig struct { 10 + CookieSecret string `env:"COOKIE_SECRET, default=00000000000000000000000000000000"` 11 + DbPath string `env:"DB_PATH, default=appview.db"` 12 + ListenAddr string `env:"LISTEN_ADDR, default=0.0.0.0:3000"` 13 + AppviewHost string `env:"APPVIEW_HOST, default=https://tangled.sh"` 14 + Dev bool `env:"DEV, default=false"` 15 + } 16 + 17 + type OAuthConfig struct { 18 + Jwks string `env:"JWKS"` 19 + ServerMetadataUrl string `env:"SERVER_METADATA_URL"` 20 + } 21 + 22 + type JetstreamConfig struct { 23 + Endpoint string `env:"ENDPOINT, default=wss://jetstream1.us-east.bsky.network/subscribe"` 24 + } 25 + 26 + type ResendConfig struct { 27 + ApiKey string `env:"API_KEY"` 28 + } 29 + 30 + type CamoConfig struct { 31 + Host string `env:"HOST, default=https://camo.tangled.sh"` 32 + SharedSecret string `env:"SHARED_SECRET"` 33 + } 34 + 35 + type AvatarConfig struct { 36 + Host string `env:"HOST, default=https://avatar.tangled.sh"` 37 + SharedSecret string `env:"SHARED_SECRET"` 38 + } 39 + 40 type Config struct { 41 + Core CoreConfig `env:",prefix=TANGLED_"` 42 + Jetstream JetstreamConfig `env:",prefix=TANGLED_JETSTREAM_"` 43 + Resend ResendConfig `env:",prefix=TANGLED_RESEND_"` 44 + Camo CamoConfig `env:",prefix=TANGLED_CAMO_"` 45 + Avatar AvatarConfig `env:",prefix=TANGLED_AVATAR_"` 46 + OAuth OAuthConfig `env:",prefix=TANGLED_OAUTH_"` 47 } 48 49 func LoadConfig(ctx context.Context) (*Config, error) {
+3
appview/consts.go
··· 9 SessionRefreshJwt = "refreshJwt" 10 SessionExpiry = "expiry" 11 SessionAuthenticated = "authenticated" 12 )
··· 9 SessionRefreshJwt = "refreshJwt" 10 SessionExpiry = "expiry" 11 SessionAuthenticated = "authenticated" 12 + 13 + SessionDpopPrivateJwk = "dpopPrivateJwk" 14 + SessionDpopAuthServerNonce = "dpopAuthServerNonce" 15 )
+26
appview/db/db.go
··· 288 foreign key (at_uri) references repos(at_uri) on delete cascade 289 ); 290 291 create table if not exists migrations ( 292 id integer primary key autoincrement, 293 name text unique
··· 288 foreign key (at_uri) references repos(at_uri) on delete cascade 289 ); 290 291 + create table if not exists oauth_requests ( 292 + id integer primary key autoincrement, 293 + auth_server_iss text not null, 294 + state text not null, 295 + did text not null, 296 + handle text not null, 297 + pds_url text not null, 298 + pkce_verifier text not null, 299 + dpop_auth_server_nonce text not null, 300 + dpop_private_jwk text not null 301 + ); 302 + 303 + create table if not exists oauth_sessions ( 304 + id integer primary key autoincrement, 305 + did text not null, 306 + handle text not null, 307 + pds_url text not null, 308 + auth_server_iss text not null, 309 + access_jwt text not null, 310 + refresh_jwt text not null, 311 + dpop_pds_nonce text, 312 + dpop_auth_server_nonce text not null, 313 + dpop_private_jwk text not null, 314 + expiry text not null 315 + ); 316 + 317 create table if not exists migrations ( 318 id integer primary key autoincrement, 319 name text unique
+173
appview/db/oauth.go
···
··· 1 + package db 2 + 3 + type OAuthRequest struct { 4 + ID uint 5 + AuthserverIss string 6 + Handle string 7 + State string 8 + Did string 9 + PdsUrl string 10 + PkceVerifier string 11 + DpopAuthserverNonce string 12 + DpopPrivateJwk string 13 + } 14 + 15 + func SaveOAuthRequest(e Execer, oauthRequest OAuthRequest) error { 16 + _, err := e.Exec(` 17 + insert into oauth_requests ( 18 + auth_server_iss, 19 + state, 20 + handle, 21 + did, 22 + pds_url, 23 + pkce_verifier, 24 + dpop_auth_server_nonce, 25 + dpop_private_jwk 26 + ) values (?, ?, ?, ?, ?, ?, ?, ?)`, 27 + oauthRequest.AuthserverIss, 28 + oauthRequest.State, 29 + oauthRequest.Handle, 30 + oauthRequest.Did, 31 + oauthRequest.PdsUrl, 32 + oauthRequest.PkceVerifier, 33 + oauthRequest.DpopAuthserverNonce, 34 + oauthRequest.DpopPrivateJwk, 35 + ) 36 + return err 37 + } 38 + 39 + func GetOAuthRequestByState(e Execer, state string) (OAuthRequest, error) { 40 + var req OAuthRequest 41 + err := e.QueryRow(` 42 + select 43 + id, 44 + auth_server_iss, 45 + handle, 46 + state, 47 + did, 48 + pds_url, 49 + pkce_verifier, 50 + dpop_auth_server_nonce, 51 + dpop_private_jwk 52 + from oauth_requests 53 + where state = ?`, state).Scan( 54 + &req.ID, 55 + &req.AuthserverIss, 56 + &req.Handle, 57 + &req.State, 58 + &req.Did, 59 + &req.PdsUrl, 60 + &req.PkceVerifier, 61 + &req.DpopAuthserverNonce, 62 + &req.DpopPrivateJwk, 63 + ) 64 + return req, err 65 + } 66 + 67 + func DeleteOAuthRequestByState(e Execer, state string) error { 68 + _, err := e.Exec(` 69 + delete from oauth_requests 70 + where state = ?`, state) 71 + return err 72 + } 73 + 74 + type OAuthSession struct { 75 + ID uint 76 + Handle string 77 + Did string 78 + PdsUrl string 79 + AccessJwt string 80 + RefreshJwt string 81 + AuthServerIss string 82 + DpopPdsNonce string 83 + DpopAuthserverNonce string 84 + DpopPrivateJwk string 85 + Expiry string 86 + } 87 + 88 + func SaveOAuthSession(e Execer, session OAuthSession) error { 89 + _, err := e.Exec(` 90 + insert into oauth_sessions ( 91 + did, 92 + handle, 93 + pds_url, 94 + access_jwt, 95 + refresh_jwt, 96 + auth_server_iss, 97 + dpop_auth_server_nonce, 98 + dpop_private_jwk, 99 + expiry 100 + ) values (?, ?, ?, ?, ?, ?, ?, ?, ?)`, 101 + session.Did, 102 + session.Handle, 103 + session.PdsUrl, 104 + session.AccessJwt, 105 + session.RefreshJwt, 106 + session.AuthServerIss, 107 + session.DpopAuthserverNonce, 108 + session.DpopPrivateJwk, 109 + session.Expiry, 110 + ) 111 + return err 112 + } 113 + 114 + func RefreshOAuthSession(e Execer, did string, accessJwt, refreshJwt, expiry string) error { 115 + _, err := e.Exec(` 116 + update oauth_sessions 117 + set access_jwt = ?, refresh_jwt = ?, expiry = ? 118 + where did = ?`, 119 + accessJwt, 120 + refreshJwt, 121 + expiry, 122 + did, 123 + ) 124 + return err 125 + } 126 + 127 + func GetOAuthSessionByDid(e Execer, did string) (*OAuthSession, error) { 128 + var session OAuthSession 129 + err := e.QueryRow(` 130 + select 131 + id, 132 + did, 133 + handle, 134 + pds_url, 135 + access_jwt, 136 + refresh_jwt, 137 + auth_server_iss, 138 + dpop_auth_server_nonce, 139 + dpop_private_jwk, 140 + expiry 141 + from oauth_sessions 142 + where did = ?`, did).Scan( 143 + &session.ID, 144 + &session.Did, 145 + &session.Handle, 146 + &session.PdsUrl, 147 + &session.AccessJwt, 148 + &session.RefreshJwt, 149 + &session.AuthServerIss, 150 + &session.DpopAuthserverNonce, 151 + &session.DpopPrivateJwk, 152 + &session.Expiry, 153 + ) 154 + return &session, err 155 + } 156 + 157 + func DeleteOAuthSessionByDid(e Execer, did string) error { 158 + _, err := e.Exec(` 159 + delete from oauth_sessions 160 + where did = ?`, did) 161 + return err 162 + } 163 + 164 + func UpdateDpopPdsNonce(e Execer, did string, dpopPdsNonce string) error { 165 + _, err := e.Exec(` 166 + update oauth_sessions 167 + set dpop_pds_nonce = ? 168 + where did = ?`, 169 + dpopPdsNonce, 170 + did, 171 + ) 172 + return err 173 + }
+5 -58
appview/middleware/middleware.go
··· 5 "log" 6 "net/http" 7 "strconv" 8 - "time" 9 10 - comatproto "github.com/bluesky-social/indigo/api/atproto" 11 - "github.com/bluesky-social/indigo/xrpc" 12 - "tangled.sh/tangled.sh/core/appview" 13 - "tangled.sh/tangled.sh/core/appview/auth" 14 "tangled.sh/tangled.sh/core/appview/pagination" 15 ) 16 17 type Middleware func(http.Handler) http.Handler 18 19 - func AuthMiddleware(a *auth.Auth) Middleware { 20 return func(next http.Handler) http.Handler { 21 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 22 redirectFunc := func(w http.ResponseWriter, r *http.Request) { ··· 29 } 30 } 31 32 - session, err := a.GetSession(r) 33 - if session.IsNew || err != nil { 34 log.Printf("not logged in, redirecting") 35 redirectFunc(w, r) 36 return 37 } 38 39 - authorized, ok := session.Values[appview.SessionAuthenticated].(bool) 40 - if !ok || !authorized { 41 log.Printf("not logged in, redirecting") 42 redirectFunc(w, r) 43 return 44 } 45 46 - // refresh if nearing expiry 47 - // TODO: dedup with /login 48 - expiryStr := session.Values[appview.SessionExpiry].(string) 49 - expiry, err := time.Parse(time.RFC3339, expiryStr) 50 - if err != nil { 51 - log.Println("invalid expiry time", err) 52 - redirectFunc(w, r) 53 - return 54 - } 55 - pdsUrl, ok1 := session.Values[appview.SessionPds].(string) 56 - did, ok2 := session.Values[appview.SessionDid].(string) 57 - refreshJwt, ok3 := session.Values[appview.SessionRefreshJwt].(string) 58 - 59 - if !ok1 || !ok2 || !ok3 { 60 - log.Println("invalid expiry time", err) 61 - redirectFunc(w, r) 62 - return 63 - } 64 - 65 - if time.Now().After(expiry) { 66 - log.Println("token expired, refreshing ...") 67 - 68 - client := xrpc.Client{ 69 - Host: pdsUrl, 70 - Auth: &xrpc.AuthInfo{ 71 - Did: did, 72 - AccessJwt: refreshJwt, 73 - RefreshJwt: refreshJwt, 74 - }, 75 - } 76 - atSession, err := comatproto.ServerRefreshSession(r.Context(), &client) 77 - if err != nil { 78 - log.Println("failed to refresh session", err) 79 - redirectFunc(w, r) 80 - return 81 - } 82 - 83 - sessionish := auth.RefreshSessionWrapper{atSession} 84 - 85 - err = a.StoreSession(r, w, &sessionish, pdsUrl) 86 - if err != nil { 87 - log.Printf("failed to store session for did: %s\n: %s", atSession.Did, err) 88 - return 89 - } 90 - 91 - log.Println("successfully refreshed token") 92 - } 93 - 94 next.ServeHTTP(w, r) 95 }) 96 }
··· 5 "log" 6 "net/http" 7 "strconv" 8 9 + "tangled.sh/tangled.sh/core/appview/oauth" 10 "tangled.sh/tangled.sh/core/appview/pagination" 11 ) 12 13 type Middleware func(http.Handler) http.Handler 14 15 + func AuthMiddleware(a *oauth.OAuth) Middleware { 16 return func(next http.Handler) http.Handler { 17 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 18 redirectFunc := func(w http.ResponseWriter, r *http.Request) { ··· 25 } 26 } 27 28 + _, auth, err := a.GetSession(r) 29 + if err != nil { 30 log.Printf("not logged in, redirecting") 31 redirectFunc(w, r) 32 return 33 } 34 35 + if !auth { 36 log.Printf("not logged in, redirecting") 37 redirectFunc(w, r) 38 return 39 } 40 41 next.ServeHTTP(w, r) 42 }) 43 }
+41 -37
appview/pages/pages.go
··· 16 "strings" 17 18 "tangled.sh/tangled.sh/core/appview" 19 - "tangled.sh/tangled.sh/core/appview/auth" 20 "tangled.sh/tangled.sh/core/appview/db" 21 "tangled.sh/tangled.sh/core/appview/pages/markup" 22 "tangled.sh/tangled.sh/core/appview/pages/repoinfo" 23 "tangled.sh/tangled.sh/core/appview/pagination" ··· 48 func NewPages(config *appview.Config) *Pages { 49 // initialized with safe defaults, can be overriden per use 50 rctx := &markup.RenderContext{ 51 - IsDev: config.Dev, 52 - CamoUrl: config.CamoHost, 53 - CamoSecret: config.CamoSharedSecret, 54 } 55 56 p := &Pages{ 57 t: make(map[string]*template.Template), 58 - dev: config.Dev, 59 embedFS: Files, 60 rctx: rctx, 61 templateDir: "appview/pages", ··· 249 return p.executePlain("user/login", w, params) 250 } 251 252 type TimelineParams struct { 253 - LoggedInUser *auth.User 254 Timeline []db.TimelineEvent 255 DidHandleMap map[string]string 256 } ··· 260 } 261 262 type SettingsParams struct { 263 - LoggedInUser *auth.User 264 PubKeys []db.PublicKey 265 Emails []db.Email 266 } ··· 270 } 271 272 type KnotsParams struct { 273 - LoggedInUser *auth.User 274 Registrations []db.Registration 275 } 276 ··· 279 } 280 281 type KnotParams struct { 282 - LoggedInUser *auth.User 283 DidHandleMap map[string]string 284 Registration *db.Registration 285 Members []string ··· 291 } 292 293 type NewRepoParams struct { 294 - LoggedInUser *auth.User 295 Knots []string 296 } 297 ··· 300 } 301 302 type ForkRepoParams struct { 303 - LoggedInUser *auth.User 304 Knots []string 305 RepoInfo repoinfo.RepoInfo 306 } ··· 310 } 311 312 type ProfilePageParams struct { 313 - LoggedInUser *auth.User 314 Repos []db.Repo 315 CollaboratingRepos []db.Repo 316 ProfileTimeline *db.ProfileTimeline ··· 335 } 336 337 type ReposPageParams struct { 338 - LoggedInUser *auth.User 339 Repos []db.Repo 340 Card ProfileCard 341 ··· 356 } 357 358 type EditBioParams struct { 359 - LoggedInUser *auth.User 360 Profile *db.Profile 361 } 362 ··· 365 } 366 367 type EditPinsParams struct { 368 - LoggedInUser *auth.User 369 Profile *db.Profile 370 AllRepos []PinnedRepo 371 DidHandleMap map[string]string ··· 403 } 404 405 type RepoIndexParams struct { 406 - LoggedInUser *auth.User 407 RepoInfo repoinfo.RepoInfo 408 Active string 409 TagMap map[string][]string ··· 444 } 445 446 type RepoLogParams struct { 447 - LoggedInUser *auth.User 448 RepoInfo repoinfo.RepoInfo 449 TagMap map[string][]string 450 types.RepoLogResponse ··· 458 } 459 460 type RepoCommitParams struct { 461 - LoggedInUser *auth.User 462 RepoInfo repoinfo.RepoInfo 463 Active string 464 EmailToDidOrHandle map[string]string ··· 472 } 473 474 type RepoTreeParams struct { 475 - LoggedInUser *auth.User 476 RepoInfo repoinfo.RepoInfo 477 Active string 478 BreadCrumbs [][]string ··· 508 } 509 510 type RepoBranchesParams struct { 511 - LoggedInUser *auth.User 512 RepoInfo repoinfo.RepoInfo 513 Active string 514 types.RepoBranchesResponse ··· 520 } 521 522 type RepoTagsParams struct { 523 - LoggedInUser *auth.User 524 RepoInfo repoinfo.RepoInfo 525 Active string 526 types.RepoTagsResponse ··· 534 } 535 536 type RepoArtifactParams struct { 537 - LoggedInUser *auth.User 538 RepoInfo repoinfo.RepoInfo 539 Artifact db.Artifact 540 } ··· 544 } 545 546 type RepoBlobParams struct { 547 - LoggedInUser *auth.User 548 RepoInfo repoinfo.RepoInfo 549 Active string 550 BreadCrumbs [][]string ··· 606 } 607 608 type RepoSettingsParams struct { 609 - LoggedInUser *auth.User 610 RepoInfo repoinfo.RepoInfo 611 Collaborators []Collaborator 612 Active string ··· 622 } 623 624 type RepoIssuesParams struct { 625 - LoggedInUser *auth.User 626 RepoInfo repoinfo.RepoInfo 627 Active string 628 Issues []db.Issue ··· 637 } 638 639 type RepoSingleIssueParams struct { 640 - LoggedInUser *auth.User 641 RepoInfo repoinfo.RepoInfo 642 Active string 643 Issue db.Issue ··· 659 } 660 661 type RepoNewIssueParams struct { 662 - LoggedInUser *auth.User 663 RepoInfo repoinfo.RepoInfo 664 Active string 665 } ··· 670 } 671 672 type EditIssueCommentParams struct { 673 - LoggedInUser *auth.User 674 RepoInfo repoinfo.RepoInfo 675 Issue *db.Issue 676 Comment *db.Comment ··· 681 } 682 683 type SingleIssueCommentParams struct { 684 - LoggedInUser *auth.User 685 DidHandleMap map[string]string 686 RepoInfo repoinfo.RepoInfo 687 Issue *db.Issue ··· 693 } 694 695 type RepoNewPullParams struct { 696 - LoggedInUser *auth.User 697 RepoInfo repoinfo.RepoInfo 698 Branches []types.Branch 699 Active string ··· 705 } 706 707 type RepoPullsParams struct { 708 - LoggedInUser *auth.User 709 RepoInfo repoinfo.RepoInfo 710 Pulls []*db.Pull 711 Active string ··· 737 } 738 739 type RepoSinglePullParams struct { 740 - LoggedInUser *auth.User 741 RepoInfo repoinfo.RepoInfo 742 Active string 743 DidHandleMap map[string]string ··· 752 } 753 754 type RepoPullPatchParams struct { 755 - LoggedInUser *auth.User 756 DidHandleMap map[string]string 757 RepoInfo repoinfo.RepoInfo 758 Pull *db.Pull ··· 767 } 768 769 type RepoPullInterdiffParams struct { 770 - LoggedInUser *auth.User 771 DidHandleMap map[string]string 772 RepoInfo repoinfo.RepoInfo 773 Pull *db.Pull ··· 817 } 818 819 type PullResubmitParams struct { 820 - LoggedInUser *auth.User 821 RepoInfo repoinfo.RepoInfo 822 Pull *db.Pull 823 SubmissionId int ··· 828 } 829 830 type PullActionsParams struct { 831 - LoggedInUser *auth.User 832 RepoInfo repoinfo.RepoInfo 833 Pull *db.Pull 834 RoundNumber int ··· 841 } 842 843 type PullNewCommentParams struct { 844 - LoggedInUser *auth.User 845 RepoInfo repoinfo.RepoInfo 846 Pull *db.Pull 847 RoundNumber int
··· 16 "strings" 17 18 "tangled.sh/tangled.sh/core/appview" 19 "tangled.sh/tangled.sh/core/appview/db" 20 + "tangled.sh/tangled.sh/core/appview/oauth" 21 "tangled.sh/tangled.sh/core/appview/pages/markup" 22 "tangled.sh/tangled.sh/core/appview/pages/repoinfo" 23 "tangled.sh/tangled.sh/core/appview/pagination" ··· 48 func NewPages(config *appview.Config) *Pages { 49 // initialized with safe defaults, can be overriden per use 50 rctx := &markup.RenderContext{ 51 + IsDev: config.Core.Dev, 52 + CamoUrl: config.Camo.Host, 53 + CamoSecret: config.Camo.SharedSecret, 54 } 55 56 p := &Pages{ 57 t: make(map[string]*template.Template), 58 + dev: config.Core.Dev, 59 embedFS: Files, 60 rctx: rctx, 61 templateDir: "appview/pages", ··· 249 return p.executePlain("user/login", w, params) 250 } 251 252 + func (p *Pages) OAuthLogin(w io.Writer, params LoginParams) error { 253 + return p.executePlain("user/oauthlogin", w, params) 254 + } 255 + 256 type TimelineParams struct { 257 + LoggedInUser *oauth.User 258 Timeline []db.TimelineEvent 259 DidHandleMap map[string]string 260 } ··· 264 } 265 266 type SettingsParams struct { 267 + LoggedInUser *oauth.User 268 PubKeys []db.PublicKey 269 Emails []db.Email 270 } ··· 274 } 275 276 type KnotsParams struct { 277 + LoggedInUser *oauth.User 278 Registrations []db.Registration 279 } 280 ··· 283 } 284 285 type KnotParams struct { 286 + LoggedInUser *oauth.User 287 DidHandleMap map[string]string 288 Registration *db.Registration 289 Members []string ··· 295 } 296 297 type NewRepoParams struct { 298 + LoggedInUser *oauth.User 299 Knots []string 300 } 301 ··· 304 } 305 306 type ForkRepoParams struct { 307 + LoggedInUser *oauth.User 308 Knots []string 309 RepoInfo repoinfo.RepoInfo 310 } ··· 314 } 315 316 type ProfilePageParams struct { 317 + LoggedInUser *oauth.User 318 Repos []db.Repo 319 CollaboratingRepos []db.Repo 320 ProfileTimeline *db.ProfileTimeline ··· 339 } 340 341 type ReposPageParams struct { 342 + LoggedInUser *oauth.User 343 Repos []db.Repo 344 Card ProfileCard 345 ··· 360 } 361 362 type EditBioParams struct { 363 + LoggedInUser *oauth.User 364 Profile *db.Profile 365 } 366 ··· 369 } 370 371 type EditPinsParams struct { 372 + LoggedInUser *oauth.User 373 Profile *db.Profile 374 AllRepos []PinnedRepo 375 DidHandleMap map[string]string ··· 407 } 408 409 type RepoIndexParams struct { 410 + LoggedInUser *oauth.User 411 RepoInfo repoinfo.RepoInfo 412 Active string 413 TagMap map[string][]string ··· 448 } 449 450 type RepoLogParams struct { 451 + LoggedInUser *oauth.User 452 RepoInfo repoinfo.RepoInfo 453 TagMap map[string][]string 454 types.RepoLogResponse ··· 462 } 463 464 type RepoCommitParams struct { 465 + LoggedInUser *oauth.User 466 RepoInfo repoinfo.RepoInfo 467 Active string 468 EmailToDidOrHandle map[string]string ··· 476 } 477 478 type RepoTreeParams struct { 479 + LoggedInUser *oauth.User 480 RepoInfo repoinfo.RepoInfo 481 Active string 482 BreadCrumbs [][]string ··· 512 } 513 514 type RepoBranchesParams struct { 515 + LoggedInUser *oauth.User 516 RepoInfo repoinfo.RepoInfo 517 Active string 518 types.RepoBranchesResponse ··· 524 } 525 526 type RepoTagsParams struct { 527 + LoggedInUser *oauth.User 528 RepoInfo repoinfo.RepoInfo 529 Active string 530 types.RepoTagsResponse ··· 538 } 539 540 type RepoArtifactParams struct { 541 + LoggedInUser *oauth.User 542 RepoInfo repoinfo.RepoInfo 543 Artifact db.Artifact 544 } ··· 548 } 549 550 type RepoBlobParams struct { 551 + LoggedInUser *oauth.User 552 RepoInfo repoinfo.RepoInfo 553 Active string 554 BreadCrumbs [][]string ··· 610 } 611 612 type RepoSettingsParams struct { 613 + LoggedInUser *oauth.User 614 RepoInfo repoinfo.RepoInfo 615 Collaborators []Collaborator 616 Active string ··· 626 } 627 628 type RepoIssuesParams struct { 629 + LoggedInUser *oauth.User 630 RepoInfo repoinfo.RepoInfo 631 Active string 632 Issues []db.Issue ··· 641 } 642 643 type RepoSingleIssueParams struct { 644 + LoggedInUser *oauth.User 645 RepoInfo repoinfo.RepoInfo 646 Active string 647 Issue db.Issue ··· 663 } 664 665 type RepoNewIssueParams struct { 666 + LoggedInUser *oauth.User 667 RepoInfo repoinfo.RepoInfo 668 Active string 669 } ··· 674 } 675 676 type EditIssueCommentParams struct { 677 + LoggedInUser *oauth.User 678 RepoInfo repoinfo.RepoInfo 679 Issue *db.Issue 680 Comment *db.Comment ··· 685 } 686 687 type SingleIssueCommentParams struct { 688 + LoggedInUser *oauth.User 689 DidHandleMap map[string]string 690 RepoInfo repoinfo.RepoInfo 691 Issue *db.Issue ··· 697 } 698 699 type RepoNewPullParams struct { 700 + LoggedInUser *oauth.User 701 RepoInfo repoinfo.RepoInfo 702 Branches []types.Branch 703 Active string ··· 709 } 710 711 type RepoPullsParams struct { 712 + LoggedInUser *oauth.User 713 RepoInfo repoinfo.RepoInfo 714 Pulls []*db.Pull 715 Active string ··· 741 } 742 743 type RepoSinglePullParams struct { 744 + LoggedInUser *oauth.User 745 RepoInfo repoinfo.RepoInfo 746 Active string 747 DidHandleMap map[string]string ··· 756 } 757 758 type RepoPullPatchParams struct { 759 + LoggedInUser *oauth.User 760 DidHandleMap map[string]string 761 RepoInfo repoinfo.RepoInfo 762 Pull *db.Pull ··· 771 } 772 773 type RepoPullInterdiffParams struct { 774 + LoggedInUser *oauth.User 775 DidHandleMap map[string]string 776 RepoInfo repoinfo.RepoInfo 777 Pull *db.Pull ··· 821 } 822 823 type PullResubmitParams struct { 824 + LoggedInUser *oauth.User 825 RepoInfo repoinfo.RepoInfo 826 Pull *db.Pull 827 SubmissionId int ··· 832 } 833 834 type PullActionsParams struct { 835 + LoggedInUser *oauth.User 836 RepoInfo repoinfo.RepoInfo 837 Pull *db.Pull 838 RoundNumber int ··· 845 } 846 847 type PullNewCommentParams struct { 848 + LoggedInUser *oauth.User 849 RepoInfo repoinfo.RepoInfo 850 Pull *db.Pull 851 RoundNumber int
+71
appview/pages/templates/user/oauthlogin.html
···
··· 1 + {{ define "user/oauthlogin" }} 2 + <!doctype html> 3 + <html lang="en" class="dark:bg-gray-900"> 4 + <head> 5 + <meta charset="UTF-8" /> 6 + <meta 7 + name="viewport" 8 + content="width=device-width, initial-scale=1.0" 9 + /> 10 + <script src="/static/htmx.min.js"></script> 11 + <link 12 + rel="stylesheet" 13 + href="/static/tw.css?{{ cssContentHash }}" 14 + type="text/css" 15 + /> 16 + <title>login</title> 17 + </head> 18 + <body class="flex items-center justify-center min-h-screen"> 19 + <main class="max-w-7xl px-6 -mt-4"> 20 + <h1 21 + class="text-center text-2xl font-semibold italic dark:text-white" 22 + > 23 + tangled 24 + </h1> 25 + <h2 class="text-center text-xl italic dark:text-white"> 26 + tightly-knit social coding. 27 + </h2> 28 + <form 29 + class="w-full mt-4" 30 + hx-post="/oauth/login" 31 + hx-swap="none" 32 + hx-disabled-elt="this" 33 + > 34 + <div class="flex flex-col"> 35 + <label for="handle">handle</label> 36 + <input 37 + type="text" 38 + id="handle" 39 + name="handle" 40 + tabindex="1" 41 + required 42 + /> 43 + <span class="text-xs text-gray-500 mt-1"> 44 + Use your 45 + <a href="https://bsky.app">Bluesky</a> handle to log 46 + in. You will then be redirected to your PDS to 47 + complete authentication. 48 + </span> 49 + </div> 50 + 51 + <button 52 + class="btn w-full my-2 mt-6" 53 + type="submit" 54 + id="login-button" 55 + tabindex="3" 56 + > 57 + <span>login</span> 58 + </button> 59 + </form> 60 + <p class="text-sm text-gray-500"> 61 + Join our <a href="https://chat.tangled.sh">Discord</a> or 62 + IRC channel: 63 + <a href="https://web.libera.chat/#tangled" 64 + ><code>#tangled</code> on Libera Chat</a 65 + >. 66 + </p> 67 + <p id="login-msg" class="error w-full"></p> 68 + </main> 69 + </body> 70 + </html> 71 + {{ end }}
+27 -18
appview/settings/settings.go
··· 13 "github.com/go-chi/chi/v5" 14 "tangled.sh/tangled.sh/core/api/tangled" 15 "tangled.sh/tangled.sh/core/appview" 16 - "tangled.sh/tangled.sh/core/appview/auth" 17 "tangled.sh/tangled.sh/core/appview/db" 18 "tangled.sh/tangled.sh/core/appview/email" 19 "tangled.sh/tangled.sh/core/appview/middleware" 20 "tangled.sh/tangled.sh/core/appview/pages" 21 22 comatproto "github.com/bluesky-social/indigo/api/atproto" ··· 27 28 type Settings struct { 29 Db *db.DB 30 - Auth *auth.Auth 31 Pages *pages.Pages 32 Config *appview.Config 33 } ··· 35 func (s *Settings) Router() http.Handler { 36 r := chi.NewRouter() 37 38 - r.Use(middleware.AuthMiddleware(s.Auth)) 39 40 r.Get("/", s.settings) 41 ··· 56 } 57 58 func (s *Settings) settings(w http.ResponseWriter, r *http.Request) { 59 - user := s.Auth.GetUser(r) 60 pubKeys, err := db.GetPublicKeys(s.Db, user.Did) 61 if err != nil { 62 log.Println(err) ··· 79 verifyURL := s.verifyUrl(did, emailAddr, code) 80 81 return email.Email{ 82 - APIKey: s.Config.ResendApiKey, 83 From: "noreply@notifs.tangled.sh", 84 To: emailAddr, 85 Subject: "Verify your Tangled email", ··· 111 log.Println("unimplemented") 112 return 113 case http.MethodPut: 114 - did := s.Auth.GetDid(r) 115 emAddr := r.FormValue("email") 116 emAddr = strings.TrimSpace(emAddr) 117 ··· 174 s.Pages.Notice(w, "settings-emails-success", "Click the link in the email we sent you to verify your email address.") 175 return 176 case http.MethodDelete: 177 - did := s.Auth.GetDid(r) 178 emailAddr := r.FormValue("email") 179 emailAddr = strings.TrimSpace(emailAddr) 180 ··· 207 208 func (s *Settings) verifyUrl(did string, email string, code string) string { 209 var appUrl string 210 - if s.Config.Dev { 211 - appUrl = "http://" + s.Config.ListenAddr 212 } else { 213 appUrl = "https://tangled.sh" 214 } ··· 252 return 253 } 254 255 - did := s.Auth.GetDid(r) 256 emAddr := r.FormValue("email") 257 emAddr = strings.TrimSpace(emAddr) 258 ··· 323 } 324 325 func (s *Settings) emailsPrimary(w http.ResponseWriter, r *http.Request) { 326 - did := s.Auth.GetDid(r) 327 emailAddr := r.FormValue("email") 328 emailAddr = strings.TrimSpace(emailAddr) 329 ··· 348 log.Println("unimplemented") 349 return 350 case http.MethodPut: 351 - did := s.Auth.GetDid(r) 352 key := r.FormValue("key") 353 key = strings.TrimSpace(key) 354 name := r.FormValue("name") 355 - client, _ := s.Auth.AuthorizedClient(r) 356 357 - _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key)) 358 if err != nil { 359 log.Printf("parsing public key: %s", err) 360 s.Pages.Notice(w, "settings-keys", "That doesn't look like a valid public key. Make sure it's a <strong>public</strong> key.") ··· 378 } 379 380 // store in pds too 381 - resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 382 Collection: tangled.PublicKeyNSID, 383 Repo: did, 384 Rkey: rkey, ··· 409 return 410 411 case http.MethodDelete: 412 - did := s.Auth.GetDid(r) 413 q := r.URL.Query() 414 415 name := q.Get("name") ··· 420 log.Println(rkey) 421 log.Println(key) 422 423 - client, _ := s.Auth.AuthorizedClient(r) 424 425 if err := db.DeletePublicKey(s.Db, did, name, key); err != nil { 426 log.Printf("removing public key: %s", err) ··· 430 431 if rkey != "" { 432 // remove from pds too 433 - _, err := comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{ 434 Collection: tangled.PublicKeyNSID, 435 Repo: did, 436 Rkey: rkey,
··· 13 "github.com/go-chi/chi/v5" 14 "tangled.sh/tangled.sh/core/api/tangled" 15 "tangled.sh/tangled.sh/core/appview" 16 "tangled.sh/tangled.sh/core/appview/db" 17 "tangled.sh/tangled.sh/core/appview/email" 18 "tangled.sh/tangled.sh/core/appview/middleware" 19 + "tangled.sh/tangled.sh/core/appview/oauth" 20 "tangled.sh/tangled.sh/core/appview/pages" 21 22 comatproto "github.com/bluesky-social/indigo/api/atproto" ··· 27 28 type Settings struct { 29 Db *db.DB 30 + OAuth *oauth.OAuth 31 Pages *pages.Pages 32 Config *appview.Config 33 } ··· 35 func (s *Settings) Router() http.Handler { 36 r := chi.NewRouter() 37 38 + r.Use(middleware.AuthMiddleware(s.OAuth)) 39 40 r.Get("/", s.settings) 41 ··· 56 } 57 58 func (s *Settings) settings(w http.ResponseWriter, r *http.Request) { 59 + user := s.OAuth.GetUser(r) 60 pubKeys, err := db.GetPublicKeys(s.Db, user.Did) 61 if err != nil { 62 log.Println(err) ··· 79 verifyURL := s.verifyUrl(did, emailAddr, code) 80 81 return email.Email{ 82 + APIKey: s.Config.Resend.ApiKey, 83 From: "noreply@notifs.tangled.sh", 84 To: emailAddr, 85 Subject: "Verify your Tangled email", ··· 111 log.Println("unimplemented") 112 return 113 case http.MethodPut: 114 + did := s.OAuth.GetDid(r) 115 emAddr := r.FormValue("email") 116 emAddr = strings.TrimSpace(emAddr) 117 ··· 174 s.Pages.Notice(w, "settings-emails-success", "Click the link in the email we sent you to verify your email address.") 175 return 176 case http.MethodDelete: 177 + did := s.OAuth.GetDid(r) 178 emailAddr := r.FormValue("email") 179 emailAddr = strings.TrimSpace(emailAddr) 180 ··· 207 208 func (s *Settings) verifyUrl(did string, email string, code string) string { 209 var appUrl string 210 + if s.Config.Core.Dev { 211 + appUrl = "http://" + s.Config.Core.ListenAddr 212 } else { 213 appUrl = "https://tangled.sh" 214 } ··· 252 return 253 } 254 255 + did := s.OAuth.GetDid(r) 256 emAddr := r.FormValue("email") 257 emAddr = strings.TrimSpace(emAddr) 258 ··· 323 } 324 325 func (s *Settings) emailsPrimary(w http.ResponseWriter, r *http.Request) { 326 + did := s.OAuth.GetDid(r) 327 emailAddr := r.FormValue("email") 328 emailAddr = strings.TrimSpace(emailAddr) 329 ··· 348 log.Println("unimplemented") 349 return 350 case http.MethodPut: 351 + did := s.OAuth.GetDid(r) 352 key := r.FormValue("key") 353 key = strings.TrimSpace(key) 354 name := r.FormValue("name") 355 + client, err := s.OAuth.AuthorizedClient(r) 356 + if err != nil { 357 + s.Pages.Notice(w, "settings-keys", "Failed to authorize. Try again later.") 358 + return 359 + } 360 361 + _, _, _, _, err = ssh.ParseAuthorizedKey([]byte(key)) 362 if err != nil { 363 log.Printf("parsing public key: %s", err) 364 s.Pages.Notice(w, "settings-keys", "That doesn't look like a valid public key. Make sure it's a <strong>public</strong> key.") ··· 382 } 383 384 // store in pds too 385 + resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 386 Collection: tangled.PublicKeyNSID, 387 Repo: did, 388 Rkey: rkey, ··· 413 return 414 415 case http.MethodDelete: 416 + did := s.OAuth.GetDid(r) 417 q := r.URL.Query() 418 419 name := q.Get("name") ··· 424 log.Println(rkey) 425 log.Println(key) 426 427 + client, err := s.OAuth.AuthorizedClient(r) 428 + if err != nil { 429 + log.Printf("failed to authorize client: %s", err) 430 + s.Pages.Notice(w, "settings-keys", "Failed to authorize client.") 431 + return 432 + } 433 434 if err := db.DeletePublicKey(s.Db, did, name, key); err != nil { 435 log.Printf("removing public key: %s", err) ··· 439 440 if rkey != "" { 441 // remove from pds too 442 + _, err := client.RepoDeleteRecord(r.Context(), &comatproto.RepoDeleteRecord_Input{ 443 Collection: tangled.PublicKeyNSID, 444 Repo: did, 445 Rkey: rkey,
+19 -10
appview/state/artifact.go
··· 22 23 // TODO: proper statuses here on early exit 24 func (s *State) AttachArtifact(w http.ResponseWriter, r *http.Request) { 25 - user := s.auth.GetUser(r) 26 tagParam := chi.URLParam(r, "tag") 27 f, err := s.fullyResolvedRepo(r) 28 if err != nil { ··· 46 } 47 defer file.Close() 48 49 - client, _ := s.auth.AuthorizedClient(r) 50 51 - uploadBlobResp, err := comatproto.RepoUploadBlob(r.Context(), client, file) 52 if err != nil { 53 log.Println("failed to upload blob", err) 54 s.pages.Notice(w, "upload", "Failed to upload blob to your PDS. Try again later.") ··· 60 rkey := appview.TID() 61 createdAt := time.Now() 62 63 - putRecordResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 64 Collection: tangled.RepoArtifactNSID, 65 Repo: user.Did, 66 Rkey: rkey, ··· 140 return 141 } 142 143 - client, _ := s.auth.AuthorizedClient(r) 144 145 artifacts, err := db.GetArtifact( 146 s.db, ··· 159 160 artifact := artifacts[0] 161 162 - getBlobResp, err := comatproto.SyncGetBlob(r.Context(), client, artifact.BlobCid.String(), artifact.Did) 163 if err != nil { 164 log.Println("failed to get blob from pds", err) 165 return ··· 171 172 // TODO: proper statuses here on early exit 173 func (s *State) DeleteArtifact(w http.ResponseWriter, r *http.Request) { 174 - user := s.auth.GetUser(r) 175 tagParam := chi.URLParam(r, "tag") 176 filename := chi.URLParam(r, "file") 177 f, err := s.fullyResolvedRepo(r) ··· 180 return 181 } 182 183 - client, _ := s.auth.AuthorizedClient(r) 184 185 tag := plumbing.NewHash(tagParam) 186 ··· 208 return 209 } 210 211 - _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{ 212 Collection: tangled.RepoArtifactNSID, 213 Repo: user.Did, 214 Rkey: artifact.Rkey, ··· 254 return nil, err 255 } 256 257 - us, err := NewUnsignedClient(f.Knot, s.config.Dev) 258 if err != nil { 259 return nil, err 260 }
··· 22 23 // TODO: proper statuses here on early exit 24 func (s *State) AttachArtifact(w http.ResponseWriter, r *http.Request) { 25 + user := s.oauth.GetUser(r) 26 tagParam := chi.URLParam(r, "tag") 27 f, err := s.fullyResolvedRepo(r) 28 if err != nil { ··· 46 } 47 defer file.Close() 48 49 + client, err := s.oauth.AuthorizedClient(r) 50 + if err != nil { 51 + log.Println("failed to get authorized client", err) 52 + s.pages.Notice(w, "upload", "failed to get authorized client") 53 + return 54 + } 55 56 + uploadBlobResp, err := client.RepoUploadBlob(r.Context(), file) 57 if err != nil { 58 log.Println("failed to upload blob", err) 59 s.pages.Notice(w, "upload", "Failed to upload blob to your PDS. Try again later.") ··· 65 rkey := appview.TID() 66 createdAt := time.Now() 67 68 + putRecordResp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 69 Collection: tangled.RepoArtifactNSID, 70 Repo: user.Did, 71 Rkey: rkey, ··· 145 return 146 } 147 148 + client, err := s.oauth.AuthorizedClient(r) 149 + if err != nil { 150 + log.Println("failed to get authorized client", err) 151 + return 152 + } 153 154 artifacts, err := db.GetArtifact( 155 s.db, ··· 168 169 artifact := artifacts[0] 170 171 + getBlobResp, err := client.SyncGetBlob(r.Context(), artifact.BlobCid.String(), artifact.Did) 172 if err != nil { 173 log.Println("failed to get blob from pds", err) 174 return ··· 180 181 // TODO: proper statuses here on early exit 182 func (s *State) DeleteArtifact(w http.ResponseWriter, r *http.Request) { 183 + user := s.oauth.GetUser(r) 184 tagParam := chi.URLParam(r, "tag") 185 filename := chi.URLParam(r, "file") 186 f, err := s.fullyResolvedRepo(r) ··· 189 return 190 } 191 192 + client, _ := s.oauth.AuthorizedClient(r) 193 194 tag := plumbing.NewHash(tagParam) 195 ··· 217 return 218 } 219 220 + _, err = client.RepoDeleteRecord(r.Context(), &comatproto.RepoDeleteRecord_Input{ 221 Collection: tangled.RepoArtifactNSID, 222 Repo: user.Did, 223 Rkey: artifact.Rkey, ··· 263 return nil, err 264 } 265 266 + us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev) 267 if err != nil { 268 return nil, err 269 }
+8 -4
appview/state/follow.go
··· 14 ) 15 16 func (s *State) Follow(w http.ResponseWriter, r *http.Request) { 17 - currentUser := s.auth.GetUser(r) 18 19 subject := r.URL.Query().Get("subject") 20 if subject == "" { ··· 32 return 33 } 34 35 - client, _ := s.auth.AuthorizedClient(r) 36 37 switch r.Method { 38 case http.MethodPost: 39 createdAt := time.Now().Format(time.RFC3339) 40 rkey := appview.TID() 41 - resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 42 Collection: tangled.GraphFollowNSID, 43 Repo: currentUser.Did, 44 Rkey: rkey, ··· 75 return 76 } 77 78 - _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{ 79 Collection: tangled.GraphFollowNSID, 80 Repo: currentUser.Did, 81 Rkey: follow.Rkey,
··· 14 ) 15 16 func (s *State) Follow(w http.ResponseWriter, r *http.Request) { 17 + currentUser := s.oauth.GetUser(r) 18 19 subject := r.URL.Query().Get("subject") 20 if subject == "" { ··· 32 return 33 } 34 35 + client, err := s.oauth.AuthorizedClient(r) 36 + if err != nil { 37 + log.Println("failed to authorize client") 38 + return 39 + } 40 41 switch r.Method { 42 case http.MethodPost: 43 createdAt := time.Now().Format(time.RFC3339) 44 rkey := appview.TID() 45 + resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 46 Collection: tangled.GraphFollowNSID, 47 Repo: currentUser.Did, 48 Rkey: rkey, ··· 79 return 80 } 81 82 + _, err = client.RepoDeleteRecord(r.Context(), &comatproto.RepoDeleteRecord_Input{ 83 Collection: tangled.GraphFollowNSID, 84 Repo: currentUser.Did, 85 Rkey: follow.Rkey,
+2 -2
appview/state/git_http.go
··· 15 repo := chi.URLParam(r, "repo") 16 17 scheme := "https" 18 - if s.config.Dev { 19 scheme = "http" 20 } 21 targetURL := fmt.Sprintf("%s://%s/%s/%s/info/refs?%s", scheme, knot, user.DID, repo, r.URL.RawQuery) ··· 52 repo := chi.URLParam(r, "repo") 53 54 scheme := "https" 55 - if s.config.Dev { 56 scheme = "http" 57 } 58 targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-pack?%s", scheme, knot, user.DID, repo, r.URL.RawQuery)
··· 15 repo := chi.URLParam(r, "repo") 16 17 scheme := "https" 18 + if s.config.Core.Dev { 19 scheme = "http" 20 } 21 targetURL := fmt.Sprintf("%s://%s/%s/%s/info/refs?%s", scheme, knot, user.DID, repo, r.URL.RawQuery) ··· 52 repo := chi.URLParam(r, "repo") 53 54 scheme := "https" 55 + if s.config.Core.Dev { 56 scheme = "http" 57 } 58 targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-pack?%s", scheme, knot, user.DID, repo, r.URL.RawQuery)
+2 -2
appview/state/middleware.go
··· 20 return func(next http.Handler) http.Handler { 21 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 22 // requires auth also 23 - actor := s.auth.GetUser(r) 24 if actor == nil { 25 // we need a logged in user 26 log.Printf("not logged in, redirecting") ··· 54 return func(next http.Handler) http.Handler { 55 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 56 // requires auth also 57 - actor := s.auth.GetUser(r) 58 if actor == nil { 59 // we need a logged in user 60 log.Printf("not logged in, redirecting")
··· 20 return func(next http.Handler) http.Handler { 21 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 22 // requires auth also 23 + actor := s.oauth.GetUser(r) 24 if actor == nil { 25 // we need a logged in user 26 log.Printf("not logged in, redirecting") ··· 54 return func(next http.Handler) http.Handler { 55 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 56 // requires auth also 57 + actor := s.oauth.GetUser(r) 58 if actor == nil { 59 // we need a logged in user 60 log.Printf("not logged in, redirecting")
+17 -12
appview/state/profile.go
··· 119 log.Printf("getting follow stats repos for %s: %s", ident.DID.String(), err) 120 } 121 122 - loggedInUser := s.auth.GetUser(r) 123 followStatus := db.IsNotFollowing 124 if loggedInUser != nil { 125 followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, ident.DID.String()) ··· 161 log.Printf("getting repos for %s: %s", ident.DID.String(), err) 162 } 163 164 - loggedInUser := s.auth.GetUser(r) 165 followStatus := db.IsNotFollowing 166 if loggedInUser != nil { 167 followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, ident.DID.String()) ··· 190 } 191 192 func (s *State) GetAvatarUri(handle string) string { 193 - secret := s.config.AvatarSharedSecret 194 h := hmac.New(sha256.New, []byte(secret)) 195 h.Write([]byte(handle)) 196 signature := hex.EncodeToString(h.Sum(nil)) 197 - return fmt.Sprintf("%s/%s/%s", s.config.AvatarHost, signature, handle) 198 } 199 200 func (s *State) UpdateProfileBio(w http.ResponseWriter, r *http.Request) { 201 - user := s.auth.GetUser(r) 202 203 err := r.ParseForm() 204 if err != nil { ··· 246 } 247 248 func (s *State) UpdateProfilePins(w http.ResponseWriter, r *http.Request) { 249 - user := s.auth.GetUser(r) 250 251 err := r.ParseForm() 252 if err != nil { ··· 286 } 287 288 func (s *State) updateProfile(profile *db.Profile, w http.ResponseWriter, r *http.Request) { 289 - user := s.auth.GetUser(r) 290 tx, err := s.db.BeginTx(r.Context(), nil) 291 if err != nil { 292 log.Println("failed to start transaction", err) ··· 294 return 295 } 296 297 - client, _ := s.auth.AuthorizedClient(r) 298 299 // yeah... lexgen dose not support syntax.ATURI in the record for some reason, 300 // nor does it support exact size arrays ··· 308 vanityStats = append(vanityStats, string(v.Kind)) 309 } 310 311 - ex, _ := comatproto.RepoGetRecord(r.Context(), client, "", tangled.ActorProfileNSID, user.Did, "self") 312 var cid *string 313 if ex != nil { 314 cid = ex.Cid 315 } 316 317 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 318 Collection: tangled.ActorProfileNSID, 319 Repo: user.Did, 320 Rkey: "self", ··· 347 } 348 349 func (s *State) EditBioFragment(w http.ResponseWriter, r *http.Request) { 350 - user := s.auth.GetUser(r) 351 352 profile, err := db.GetProfile(s.db, user.Did) 353 if err != nil { ··· 361 } 362 363 func (s *State) EditPinsFragment(w http.ResponseWriter, r *http.Request) { 364 - user := s.auth.GetUser(r) 365 366 profile, err := db.GetProfile(s.db, user.Did) 367 if err != nil {
··· 119 log.Printf("getting follow stats repos for %s: %s", ident.DID.String(), err) 120 } 121 122 + loggedInUser := s.oauth.GetUser(r) 123 followStatus := db.IsNotFollowing 124 if loggedInUser != nil { 125 followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, ident.DID.String()) ··· 161 log.Printf("getting repos for %s: %s", ident.DID.String(), err) 162 } 163 164 + loggedInUser := s.oauth.GetUser(r) 165 followStatus := db.IsNotFollowing 166 if loggedInUser != nil { 167 followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, ident.DID.String()) ··· 190 } 191 192 func (s *State) GetAvatarUri(handle string) string { 193 + secret := s.config.Avatar.SharedSecret 194 h := hmac.New(sha256.New, []byte(secret)) 195 h.Write([]byte(handle)) 196 signature := hex.EncodeToString(h.Sum(nil)) 197 + return fmt.Sprintf("%s/%s/%s", s.config.Avatar.Host, signature, handle) 198 } 199 200 func (s *State) UpdateProfileBio(w http.ResponseWriter, r *http.Request) { 201 + user := s.oauth.GetUser(r) 202 203 err := r.ParseForm() 204 if err != nil { ··· 246 } 247 248 func (s *State) UpdateProfilePins(w http.ResponseWriter, r *http.Request) { 249 + user := s.oauth.GetUser(r) 250 251 err := r.ParseForm() 252 if err != nil { ··· 286 } 287 288 func (s *State) updateProfile(profile *db.Profile, w http.ResponseWriter, r *http.Request) { 289 + user := s.oauth.GetUser(r) 290 tx, err := s.db.BeginTx(r.Context(), nil) 291 if err != nil { 292 log.Println("failed to start transaction", err) ··· 294 return 295 } 296 297 + client, err := s.oauth.AuthorizedClient(r) 298 + if err != nil { 299 + log.Println("failed to get authorized client", err) 300 + s.pages.Notice(w, "update-profile", "Failed to update profile, try again later.") 301 + return 302 + } 303 304 // yeah... lexgen dose not support syntax.ATURI in the record for some reason, 305 // nor does it support exact size arrays ··· 313 vanityStats = append(vanityStats, string(v.Kind)) 314 } 315 316 + ex, _ := client.RepoGetRecord(r.Context(), "", tangled.ActorProfileNSID, user.Did, "self") 317 var cid *string 318 if ex != nil { 319 cid = ex.Cid 320 } 321 322 + _, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 323 Collection: tangled.ActorProfileNSID, 324 Repo: user.Did, 325 Rkey: "self", ··· 352 } 353 354 func (s *State) EditBioFragment(w http.ResponseWriter, r *http.Request) { 355 + user := s.oauth.GetUser(r) 356 357 profile, err := db.GetProfile(s.db, user.Did) 358 if err != nil { ··· 366 } 367 368 func (s *State) EditPinsFragment(w http.ResponseWriter, r *http.Request) { 369 + user := s.oauth.GetUser(r) 370 371 profile, err := db.GetProfile(s.db, user.Did) 372 if err != nil {
+76 -51
appview/state/pull.go
··· 13 14 "tangled.sh/tangled.sh/core/api/tangled" 15 "tangled.sh/tangled.sh/core/appview" 16 - "tangled.sh/tangled.sh/core/appview/auth" 17 "tangled.sh/tangled.sh/core/appview/db" 18 "tangled.sh/tangled.sh/core/appview/pages" 19 "tangled.sh/tangled.sh/core/patchutil" 20 "tangled.sh/tangled.sh/core/types" ··· 29 func (s *State) PullActions(w http.ResponseWriter, r *http.Request) { 30 switch r.Method { 31 case http.MethodGet: 32 - user := s.auth.GetUser(r) 33 f, err := s.fullyResolvedRepo(r) 34 if err != nil { 35 log.Println("failed to get repo and knot", err) ··· 73 } 74 75 func (s *State) RepoSinglePull(w http.ResponseWriter, r *http.Request) { 76 - user := s.auth.GetUser(r) 77 f, err := s.fullyResolvedRepo(r) 78 if err != nil { 79 log.Println("failed to get repo and knot", err) ··· 143 } 144 } 145 146 - ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev) 147 if err != nil { 148 log.Printf("failed to setup signed client for %s; ignoring: %v", f.Knot, err) 149 return types.MergeCheckResponse{ ··· 215 repoName = f.RepoName 216 } 217 218 - us, err := NewUnsignedClient(knot, s.config.Dev) 219 if err != nil { 220 log.Printf("failed to setup client for %s; ignoring: %v", knot, err) 221 return pages.Unknown ··· 250 } 251 252 func (s *State) RepoPullPatch(w http.ResponseWriter, r *http.Request) { 253 - user := s.auth.GetUser(r) 254 f, err := s.fullyResolvedRepo(r) 255 if err != nil { 256 log.Println("failed to get repo and knot", err) ··· 298 } 299 300 func (s *State) RepoPullInterdiff(w http.ResponseWriter, r *http.Request) { 301 - user := s.auth.GetUser(r) 302 303 f, err := s.fullyResolvedRepo(r) 304 if err != nil { ··· 355 interdiff := patchutil.Interdiff(previousPatch, currentPatch) 356 357 s.pages.RepoPullInterdiffPage(w, pages.RepoPullInterdiffParams{ 358 - LoggedInUser: s.auth.GetUser(r), 359 RepoInfo: f.RepoInfo(s, user), 360 Pull: pull, 361 Round: roundIdInt, ··· 397 } 398 399 func (s *State) RepoPulls(w http.ResponseWriter, r *http.Request) { 400 - user := s.auth.GetUser(r) 401 params := r.URL.Query() 402 403 state := db.PullOpen ··· 451 } 452 453 s.pages.RepoPulls(w, pages.RepoPullsParams{ 454 - LoggedInUser: s.auth.GetUser(r), 455 RepoInfo: f.RepoInfo(s, user), 456 Pulls: pulls, 457 DidHandleMap: didHandleMap, ··· 461 } 462 463 func (s *State) PullComment(w http.ResponseWriter, r *http.Request) { 464 - user := s.auth.GetUser(r) 465 f, err := s.fullyResolvedRepo(r) 466 if err != nil { 467 log.Println("failed to get repo and knot", err) ··· 519 } 520 521 atUri := f.RepoAt.String() 522 - client, _ := s.auth.AuthorizedClient(r) 523 - atResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 524 Collection: tangled.RepoPullCommentNSID, 525 Repo: user.Did, 526 Rkey: appview.TID(), ··· 568 } 569 570 func (s *State) NewPull(w http.ResponseWriter, r *http.Request) { 571 - user := s.auth.GetUser(r) 572 f, err := s.fullyResolvedRepo(r) 573 if err != nil { 574 log.Println("failed to get repo and knot", err) ··· 577 578 switch r.Method { 579 case http.MethodGet: 580 - us, err := NewUnsignedClient(f.Knot, s.config.Dev) 581 if err != nil { 582 log.Printf("failed to create unsigned client for %s", f.Knot) 583 s.pages.Error503(w) ··· 646 return 647 } 648 649 - us, err := NewUnsignedClient(f.Knot, s.config.Dev) 650 if err != nil { 651 log.Printf("failed to create unsigned client to %s: %v", f.Knot, err) 652 s.pages.Notice(w, "pull", "Failed to create a pull request. Try again later.") ··· 689 } 690 } 691 692 - func (s *State) handleBranchBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, title, body, targetBranch, sourceBranch string) { 693 pullSource := &db.PullSource{ 694 Branch: sourceBranch, 695 } ··· 698 } 699 700 // Generate a patch using /compare 701 - ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev) 702 if err != nil { 703 log.Printf("failed to create signed client for %s: %s", f.Knot, err) 704 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") ··· 723 s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, sourceRev, pullSource, recordPullSource) 724 } 725 726 - func (s *State) handlePatchBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, title, body, targetBranch, patch string) { 727 if !patchutil.IsPatchValid(patch) { 728 s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.") 729 return ··· 732 s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, "", nil, nil) 733 } 734 735 - func (s *State) handleForkBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, forkRepo string, title, body, targetBranch, sourceBranch string) { 736 fork, err := db.GetForkByDid(s.db, user.Did, forkRepo) 737 if errors.Is(err, sql.ErrNoRows) { 738 s.pages.Notice(w, "pull", "No such fork.") ··· 750 return 751 } 752 753 - sc, err := NewSignedClient(fork.Knot, secret, s.config.Dev) 754 if err != nil { 755 log.Println("failed to create signed client:", err) 756 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 757 return 758 } 759 760 - us, err := NewUnsignedClient(fork.Knot, s.config.Dev) 761 if err != nil { 762 log.Println("failed to create unsigned client:", err) 763 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") ··· 816 w http.ResponseWriter, 817 r *http.Request, 818 f *FullyResolvedRepo, 819 - user *auth.User, 820 title, body, targetBranch string, 821 patch string, 822 sourceRev string, ··· 870 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 871 return 872 } 873 - client, _ := s.auth.AuthorizedClient(r) 874 pullId, err := db.NextPullId(s.db, f.RepoAt) 875 if err != nil { 876 log.Println("failed to get pull id", err) ··· 878 return 879 } 880 881 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 882 Collection: tangled.RepoPullNSID, 883 Repo: user.Did, 884 Rkey: rkey, ··· 929 } 930 931 func (s *State) PatchUploadFragment(w http.ResponseWriter, r *http.Request) { 932 - user := s.auth.GetUser(r) 933 f, err := s.fullyResolvedRepo(r) 934 if err != nil { 935 log.Println("failed to get repo and knot", err) ··· 942 } 943 944 func (s *State) CompareBranchesFragment(w http.ResponseWriter, r *http.Request) { 945 - user := s.auth.GetUser(r) 946 f, err := s.fullyResolvedRepo(r) 947 if err != nil { 948 log.Println("failed to get repo and knot", err) 949 return 950 } 951 952 - us, err := NewUnsignedClient(f.Knot, s.config.Dev) 953 if err != nil { 954 log.Printf("failed to create unsigned client for %s", f.Knot) 955 s.pages.Error503(w) ··· 982 } 983 984 func (s *State) CompareForksFragment(w http.ResponseWriter, r *http.Request) { 985 - user := s.auth.GetUser(r) 986 f, err := s.fullyResolvedRepo(r) 987 if err != nil { 988 log.Println("failed to get repo and knot", err) ··· 1002 } 1003 1004 func (s *State) CompareForksBranchesFragment(w http.ResponseWriter, r *http.Request) { 1005 - user := s.auth.GetUser(r) 1006 1007 f, err := s.fullyResolvedRepo(r) 1008 if err != nil { ··· 1019 return 1020 } 1021 1022 - sourceBranchesClient, err := NewUnsignedClient(repo.Knot, s.config.Dev) 1023 if err != nil { 1024 log.Printf("failed to create unsigned client for %s", repo.Knot) 1025 s.pages.Error503(w) ··· 1046 return 1047 } 1048 1049 - targetBranchesClient, err := NewUnsignedClient(f.Knot, s.config.Dev) 1050 if err != nil { 1051 log.Printf("failed to create unsigned client for target knot %s", f.Knot) 1052 s.pages.Error503(w) ··· 1081 } 1082 1083 func (s *State) ResubmitPull(w http.ResponseWriter, r *http.Request) { 1084 - user := s.auth.GetUser(r) 1085 f, err := s.fullyResolvedRepo(r) 1086 if err != nil { 1087 log.Println("failed to get repo and knot", err) ··· 1117 } 1118 1119 func (s *State) resubmitPatch(w http.ResponseWriter, r *http.Request) { 1120 - user := s.auth.GetUser(r) 1121 1122 pull, ok := r.Context().Value("pull").(*db.Pull) 1123 if !ok { ··· 1159 s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull request. Try again later.") 1160 return 1161 } 1162 - client, _ := s.auth.AuthorizedClient(r) 1163 1164 - ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Did, pull.Rkey) 1165 if err != nil { 1166 // failed to get record 1167 s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.") 1168 return 1169 } 1170 1171 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1172 Collection: tangled.RepoPullNSID, 1173 Repo: user.Did, 1174 Rkey: pull.Rkey, ··· 1200 } 1201 1202 func (s *State) resubmitBranch(w http.ResponseWriter, r *http.Request) { 1203 - user := s.auth.GetUser(r) 1204 1205 pull, ok := r.Context().Value("pull").(*db.Pull) 1206 if !ok { ··· 1227 return 1228 } 1229 1230 - ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev) 1231 if err != nil { 1232 log.Printf("failed to create client for %s: %s", f.Knot, err) 1233 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") ··· 1268 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1269 return 1270 } 1271 - client, _ := s.auth.AuthorizedClient(r) 1272 1273 - ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Did, pull.Rkey) 1274 if err != nil { 1275 // failed to get record 1276 s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.") ··· 1280 recordPullSource := &tangled.RepoPull_Source{ 1281 Branch: pull.PullSource.Branch, 1282 } 1283 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1284 Collection: tangled.RepoPullNSID, 1285 Repo: user.Did, 1286 Rkey: pull.Rkey, ··· 1313 } 1314 1315 func (s *State) resubmitFork(w http.ResponseWriter, r *http.Request) { 1316 - user := s.auth.GetUser(r) 1317 1318 pull, ok := r.Context().Value("pull").(*db.Pull) 1319 if !ok { ··· 1342 } 1343 1344 // extract patch by performing compare 1345 - ksClient, err := NewUnsignedClient(forkRepo.Knot, s.config.Dev) 1346 if err != nil { 1347 log.Printf("failed to create client for %s: %s", forkRepo.Knot, err) 1348 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") ··· 1357 } 1358 1359 // update the hidden tracking branch to latest 1360 - signedClient, err := NewSignedClient(forkRepo.Knot, secret, s.config.Dev) 1361 if err != nil { 1362 log.Printf("failed to create signed client for %s: %s", forkRepo.Knot, err) 1363 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") ··· 1406 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1407 return 1408 } 1409 - client, _ := s.auth.AuthorizedClient(r) 1410 1411 - ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Did, pull.Rkey) 1412 if err != nil { 1413 // failed to get record 1414 s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.") ··· 1420 Branch: pull.PullSource.Branch, 1421 Repo: &repoAt, 1422 } 1423 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1424 Collection: tangled.RepoPullNSID, 1425 Repo: user.Did, 1426 Rkey: pull.Rkey, ··· 1503 log.Printf("failed to get primary email: %s", err) 1504 } 1505 1506 - ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev) 1507 if err != nil { 1508 log.Printf("failed to create signed client for %s: %s", f.Knot, err) 1509 s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.") ··· 1533 } 1534 1535 func (s *State) ClosePull(w http.ResponseWriter, r *http.Request) { 1536 - user := s.auth.GetUser(r) 1537 1538 f, err := s.fullyResolvedRepo(r) 1539 if err != nil { ··· 1587 } 1588 1589 func (s *State) ReopenPull(w http.ResponseWriter, r *http.Request) { 1590 - user := s.auth.GetUser(r) 1591 1592 f, err := s.fullyResolvedRepo(r) 1593 if err != nil {
··· 13 14 "tangled.sh/tangled.sh/core/api/tangled" 15 "tangled.sh/tangled.sh/core/appview" 16 "tangled.sh/tangled.sh/core/appview/db" 17 + "tangled.sh/tangled.sh/core/appview/oauth" 18 "tangled.sh/tangled.sh/core/appview/pages" 19 "tangled.sh/tangled.sh/core/patchutil" 20 "tangled.sh/tangled.sh/core/types" ··· 29 func (s *State) PullActions(w http.ResponseWriter, r *http.Request) { 30 switch r.Method { 31 case http.MethodGet: 32 + user := s.oauth.GetUser(r) 33 f, err := s.fullyResolvedRepo(r) 34 if err != nil { 35 log.Println("failed to get repo and knot", err) ··· 73 } 74 75 func (s *State) RepoSinglePull(w http.ResponseWriter, r *http.Request) { 76 + user := s.oauth.GetUser(r) 77 f, err := s.fullyResolvedRepo(r) 78 if err != nil { 79 log.Println("failed to get repo and knot", err) ··· 143 } 144 } 145 146 + ksClient, err := NewSignedClient(f.Knot, secret, s.config.Core.Dev) 147 if err != nil { 148 log.Printf("failed to setup signed client for %s; ignoring: %v", f.Knot, err) 149 return types.MergeCheckResponse{ ··· 215 repoName = f.RepoName 216 } 217 218 + us, err := NewUnsignedClient(knot, s.config.Core.Dev) 219 if err != nil { 220 log.Printf("failed to setup client for %s; ignoring: %v", knot, err) 221 return pages.Unknown ··· 250 } 251 252 func (s *State) RepoPullPatch(w http.ResponseWriter, r *http.Request) { 253 + user := s.oauth.GetUser(r) 254 f, err := s.fullyResolvedRepo(r) 255 if err != nil { 256 log.Println("failed to get repo and knot", err) ··· 298 } 299 300 func (s *State) RepoPullInterdiff(w http.ResponseWriter, r *http.Request) { 301 + user := s.oauth.GetUser(r) 302 303 f, err := s.fullyResolvedRepo(r) 304 if err != nil { ··· 355 interdiff := patchutil.Interdiff(previousPatch, currentPatch) 356 357 s.pages.RepoPullInterdiffPage(w, pages.RepoPullInterdiffParams{ 358 + LoggedInUser: s.oauth.GetUser(r), 359 RepoInfo: f.RepoInfo(s, user), 360 Pull: pull, 361 Round: roundIdInt, ··· 397 } 398 399 func (s *State) RepoPulls(w http.ResponseWriter, r *http.Request) { 400 + user := s.oauth.GetUser(r) 401 params := r.URL.Query() 402 403 state := db.PullOpen ··· 451 } 452 453 s.pages.RepoPulls(w, pages.RepoPullsParams{ 454 + LoggedInUser: s.oauth.GetUser(r), 455 RepoInfo: f.RepoInfo(s, user), 456 Pulls: pulls, 457 DidHandleMap: didHandleMap, ··· 461 } 462 463 func (s *State) PullComment(w http.ResponseWriter, r *http.Request) { 464 + user := s.oauth.GetUser(r) 465 f, err := s.fullyResolvedRepo(r) 466 if err != nil { 467 log.Println("failed to get repo and knot", err) ··· 519 } 520 521 atUri := f.RepoAt.String() 522 + client, err := s.oauth.AuthorizedClient(r) 523 + if err != nil { 524 + log.Println("failed to get authorized client", err) 525 + s.pages.Notice(w, "pull-comment", "Failed to create comment.") 526 + return 527 + } 528 + atResp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 529 Collection: tangled.RepoPullCommentNSID, 530 Repo: user.Did, 531 Rkey: appview.TID(), ··· 573 } 574 575 func (s *State) NewPull(w http.ResponseWriter, r *http.Request) { 576 + user := s.oauth.GetUser(r) 577 f, err := s.fullyResolvedRepo(r) 578 if err != nil { 579 log.Println("failed to get repo and knot", err) ··· 582 583 switch r.Method { 584 case http.MethodGet: 585 + us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev) 586 if err != nil { 587 log.Printf("failed to create unsigned client for %s", f.Knot) 588 s.pages.Error503(w) ··· 651 return 652 } 653 654 + us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev) 655 if err != nil { 656 log.Printf("failed to create unsigned client to %s: %v", f.Knot, err) 657 s.pages.Notice(w, "pull", "Failed to create a pull request. Try again later.") ··· 694 } 695 } 696 697 + func (s *State) handleBranchBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *oauth.User, title, body, targetBranch, sourceBranch string) { 698 pullSource := &db.PullSource{ 699 Branch: sourceBranch, 700 } ··· 703 } 704 705 // Generate a patch using /compare 706 + ksClient, err := NewUnsignedClient(f.Knot, s.config.Core.Dev) 707 if err != nil { 708 log.Printf("failed to create signed client for %s: %s", f.Knot, err) 709 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") ··· 728 s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, sourceRev, pullSource, recordPullSource) 729 } 730 731 + func (s *State) handlePatchBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *oauth.User, title, body, targetBranch, patch string) { 732 if !patchutil.IsPatchValid(patch) { 733 s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.") 734 return ··· 737 s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, "", nil, nil) 738 } 739 740 + func (s *State) handleForkBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *oauth.User, forkRepo string, title, body, targetBranch, sourceBranch string) { 741 fork, err := db.GetForkByDid(s.db, user.Did, forkRepo) 742 if errors.Is(err, sql.ErrNoRows) { 743 s.pages.Notice(w, "pull", "No such fork.") ··· 755 return 756 } 757 758 + sc, err := NewSignedClient(fork.Knot, secret, s.config.Core.Dev) 759 if err != nil { 760 log.Println("failed to create signed client:", err) 761 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 762 return 763 } 764 765 + us, err := NewUnsignedClient(fork.Knot, s.config.Core.Dev) 766 if err != nil { 767 log.Println("failed to create unsigned client:", err) 768 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") ··· 821 w http.ResponseWriter, 822 r *http.Request, 823 f *FullyResolvedRepo, 824 + user *oauth.User, 825 title, body, targetBranch string, 826 patch string, 827 sourceRev string, ··· 875 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 876 return 877 } 878 + client, err := s.oauth.AuthorizedClient(r) 879 + if err != nil { 880 + log.Println("failed to get authorized client", err) 881 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 882 + return 883 + } 884 pullId, err := db.NextPullId(s.db, f.RepoAt) 885 if err != nil { 886 log.Println("failed to get pull id", err) ··· 888 return 889 } 890 891 + _, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 892 Collection: tangled.RepoPullNSID, 893 Repo: user.Did, 894 Rkey: rkey, ··· 939 } 940 941 func (s *State) PatchUploadFragment(w http.ResponseWriter, r *http.Request) { 942 + user := s.oauth.GetUser(r) 943 f, err := s.fullyResolvedRepo(r) 944 if err != nil { 945 log.Println("failed to get repo and knot", err) ··· 952 } 953 954 func (s *State) CompareBranchesFragment(w http.ResponseWriter, r *http.Request) { 955 + user := s.oauth.GetUser(r) 956 f, err := s.fullyResolvedRepo(r) 957 if err != nil { 958 log.Println("failed to get repo and knot", err) 959 return 960 } 961 962 + us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev) 963 if err != nil { 964 log.Printf("failed to create unsigned client for %s", f.Knot) 965 s.pages.Error503(w) ··· 992 } 993 994 func (s *State) CompareForksFragment(w http.ResponseWriter, r *http.Request) { 995 + user := s.oauth.GetUser(r) 996 f, err := s.fullyResolvedRepo(r) 997 if err != nil { 998 log.Println("failed to get repo and knot", err) ··· 1012 } 1013 1014 func (s *State) CompareForksBranchesFragment(w http.ResponseWriter, r *http.Request) { 1015 + user := s.oauth.GetUser(r) 1016 1017 f, err := s.fullyResolvedRepo(r) 1018 if err != nil { ··· 1029 return 1030 } 1031 1032 + sourceBranchesClient, err := NewUnsignedClient(repo.Knot, s.config.Core.Dev) 1033 if err != nil { 1034 log.Printf("failed to create unsigned client for %s", repo.Knot) 1035 s.pages.Error503(w) ··· 1056 return 1057 } 1058 1059 + targetBranchesClient, err := NewUnsignedClient(f.Knot, s.config.Core.Dev) 1060 if err != nil { 1061 log.Printf("failed to create unsigned client for target knot %s", f.Knot) 1062 s.pages.Error503(w) ··· 1091 } 1092 1093 func (s *State) ResubmitPull(w http.ResponseWriter, r *http.Request) { 1094 + user := s.oauth.GetUser(r) 1095 f, err := s.fullyResolvedRepo(r) 1096 if err != nil { 1097 log.Println("failed to get repo and knot", err) ··· 1127 } 1128 1129 func (s *State) resubmitPatch(w http.ResponseWriter, r *http.Request) { 1130 + user := s.oauth.GetUser(r) 1131 1132 pull, ok := r.Context().Value("pull").(*db.Pull) 1133 if !ok { ··· 1169 s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull request. Try again later.") 1170 return 1171 } 1172 + client, err := s.oauth.AuthorizedClient(r) 1173 + if err != nil { 1174 + log.Println("failed to get authorized client", err) 1175 + s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1176 + return 1177 + } 1178 1179 + ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoPullNSID, user.Did, pull.Rkey) 1180 if err != nil { 1181 // failed to get record 1182 s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.") 1183 return 1184 } 1185 1186 + _, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 1187 Collection: tangled.RepoPullNSID, 1188 Repo: user.Did, 1189 Rkey: pull.Rkey, ··· 1215 } 1216 1217 func (s *State) resubmitBranch(w http.ResponseWriter, r *http.Request) { 1218 + user := s.oauth.GetUser(r) 1219 1220 pull, ok := r.Context().Value("pull").(*db.Pull) 1221 if !ok { ··· 1242 return 1243 } 1244 1245 + ksClient, err := NewUnsignedClient(f.Knot, s.config.Core.Dev) 1246 if err != nil { 1247 log.Printf("failed to create client for %s: %s", f.Knot, err) 1248 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") ··· 1283 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1284 return 1285 } 1286 + client, err := s.oauth.AuthorizedClient(r) 1287 + if err != nil { 1288 + log.Println("failed to authorize client") 1289 + s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1290 + return 1291 + } 1292 1293 + ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoPullNSID, user.Did, pull.Rkey) 1294 if err != nil { 1295 // failed to get record 1296 s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.") ··· 1300 recordPullSource := &tangled.RepoPull_Source{ 1301 Branch: pull.PullSource.Branch, 1302 } 1303 + _, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 1304 Collection: tangled.RepoPullNSID, 1305 Repo: user.Did, 1306 Rkey: pull.Rkey, ··· 1333 } 1334 1335 func (s *State) resubmitFork(w http.ResponseWriter, r *http.Request) { 1336 + user := s.oauth.GetUser(r) 1337 1338 pull, ok := r.Context().Value("pull").(*db.Pull) 1339 if !ok { ··· 1362 } 1363 1364 // extract patch by performing compare 1365 + ksClient, err := NewUnsignedClient(forkRepo.Knot, s.config.Core.Dev) 1366 if err != nil { 1367 log.Printf("failed to create client for %s: %s", forkRepo.Knot, err) 1368 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") ··· 1377 } 1378 1379 // update the hidden tracking branch to latest 1380 + signedClient, err := NewSignedClient(forkRepo.Knot, secret, s.config.Core.Dev) 1381 if err != nil { 1382 log.Printf("failed to create signed client for %s: %s", forkRepo.Knot, err) 1383 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") ··· 1426 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1427 return 1428 } 1429 + client, err := s.oauth.AuthorizedClient(r) 1430 + if err != nil { 1431 + log.Println("failed to get client") 1432 + s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1433 + return 1434 + } 1435 1436 + ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoPullNSID, user.Did, pull.Rkey) 1437 if err != nil { 1438 // failed to get record 1439 s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.") ··· 1445 Branch: pull.PullSource.Branch, 1446 Repo: &repoAt, 1447 } 1448 + _, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 1449 Collection: tangled.RepoPullNSID, 1450 Repo: user.Did, 1451 Rkey: pull.Rkey, ··· 1528 log.Printf("failed to get primary email: %s", err) 1529 } 1530 1531 + ksClient, err := NewSignedClient(f.Knot, secret, s.config.Core.Dev) 1532 if err != nil { 1533 log.Printf("failed to create signed client for %s: %s", f.Knot, err) 1534 s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.") ··· 1558 } 1559 1560 func (s *State) ClosePull(w http.ResponseWriter, r *http.Request) { 1561 + user := s.oauth.GetUser(r) 1562 1563 f, err := s.fullyResolvedRepo(r) 1564 if err != nil { ··· 1612 } 1613 1614 func (s *State) ReopenPull(w http.ResponseWriter, r *http.Request) { 1615 + user := s.oauth.GetUser(r) 1616 1617 f, err := s.fullyResolvedRepo(r) 1618 if err != nil {
+98 -60
appview/state/repo.go
··· 18 19 "tangled.sh/tangled.sh/core/api/tangled" 20 "tangled.sh/tangled.sh/core/appview" 21 - "tangled.sh/tangled.sh/core/appview/auth" 22 "tangled.sh/tangled.sh/core/appview/db" 23 "tangled.sh/tangled.sh/core/appview/pages" 24 "tangled.sh/tangled.sh/core/appview/pages/markup" 25 "tangled.sh/tangled.sh/core/appview/pages/repoinfo" ··· 45 return 46 } 47 48 - us, err := NewUnsignedClient(f.Knot, s.config.Dev) 49 if err != nil { 50 log.Printf("failed to create unsigned client for %s", f.Knot) 51 s.pages.Error503(w) ··· 119 120 emails := uniqueEmails(commitsTrunc) 121 122 - user := s.auth.GetUser(r) 123 s.pages.RepoIndexPage(w, pages.RepoIndexParams{ 124 LoggedInUser: user, 125 RepoInfo: f.RepoInfo(s, user), ··· 150 151 ref := chi.URLParam(r, "ref") 152 153 - us, err := NewUnsignedClient(f.Knot, s.config.Dev) 154 if err != nil { 155 log.Println("failed to create unsigned client", err) 156 return ··· 190 tagMap[hash] = append(tagMap[hash], tag.Name) 191 } 192 193 - user := s.auth.GetUser(r) 194 s.pages.RepoLog(w, pages.RepoLogParams{ 195 LoggedInUser: user, 196 TagMap: tagMap, ··· 209 return 210 } 211 212 - user := s.auth.GetUser(r) 213 s.pages.EditRepoDescriptionFragment(w, pages.RepoDescriptionParams{ 214 RepoInfo: f.RepoInfo(s, user), 215 }) ··· 232 return 233 } 234 235 - user := s.auth.GetUser(r) 236 237 switch r.Method { 238 case http.MethodGet: ··· 241 }) 242 return 243 case http.MethodPut: 244 - user := s.auth.GetUser(r) 245 newDescription := r.FormValue("description") 246 - client, _ := s.auth.AuthorizedClient(r) 247 248 // optimistic update 249 err = db.UpdateDescription(s.db, string(repoAt), newDescription) ··· 256 // this is a bit of a pain because the golang atproto impl does not allow nil SwapRecord field 257 // 258 // SwapRecord is optional and should happen automagically, but given that it does not, we have to perform two requests 259 - ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoNSID, user.Did, rkey) 260 if err != nil { 261 // failed to get record 262 s.pages.Notice(w, "repo-notice", "Failed to update description, no record found on PDS.") 263 return 264 } 265 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 266 Collection: tangled.RepoNSID, 267 Repo: user.Did, 268 Rkey: rkey, ··· 303 } 304 ref := chi.URLParam(r, "ref") 305 protocol := "http" 306 - if !s.config.Dev { 307 protocol = "https" 308 } 309 ··· 331 return 332 } 333 334 - user := s.auth.GetUser(r) 335 s.pages.RepoCommit(w, pages.RepoCommitParams{ 336 LoggedInUser: user, 337 RepoInfo: f.RepoInfo(s, user), ··· 351 ref := chi.URLParam(r, "ref") 352 treePath := chi.URLParam(r, "*") 353 protocol := "http" 354 - if !s.config.Dev { 355 protocol = "https" 356 } 357 resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/tree/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, treePath)) ··· 380 return 381 } 382 383 - user := s.auth.GetUser(r) 384 385 var breadcrumbs [][]string 386 breadcrumbs = append(breadcrumbs, []string{f.RepoName, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), ref)}) ··· 411 return 412 } 413 414 - us, err := NewUnsignedClient(f.Knot, s.config.Dev) 415 if err != nil { 416 log.Println("failed to create unsigned client", err) 417 return ··· 451 } 452 } 453 454 - user := s.auth.GetUser(r) 455 s.pages.RepoTags(w, pages.RepoTagsParams{ 456 LoggedInUser: user, 457 RepoInfo: f.RepoInfo(s, user), ··· 469 return 470 } 471 472 - us, err := NewUnsignedClient(f.Knot, s.config.Dev) 473 if err != nil { 474 log.Println("failed to create unsigned client", err) 475 return ··· 511 return strings.Compare(a.Name, b.Name) * -1 512 }) 513 514 - user := s.auth.GetUser(r) 515 s.pages.RepoBranches(w, pages.RepoBranchesParams{ 516 LoggedInUser: user, 517 RepoInfo: f.RepoInfo(s, user), ··· 530 ref := chi.URLParam(r, "ref") 531 filePath := chi.URLParam(r, "*") 532 protocol := "http" 533 - if !s.config.Dev { 534 protocol = "https" 535 } 536 resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath)) ··· 568 showRendered = r.URL.Query().Get("code") != "true" 569 } 570 571 - user := s.auth.GetUser(r) 572 s.pages.RepoBlob(w, pages.RepoBlobParams{ 573 LoggedInUser: user, 574 RepoInfo: f.RepoInfo(s, user), ··· 591 filePath := chi.URLParam(r, "*") 592 593 protocol := "http" 594 - if !s.config.Dev { 595 protocol = "https" 596 } 597 resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath)) ··· 652 return 653 } 654 655 - ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev) 656 if err != nil { 657 log.Println("failed to create client to ", f.Knot) 658 return ··· 714 } 715 716 func (s *State) DeleteRepo(w http.ResponseWriter, r *http.Request) { 717 - user := s.auth.GetUser(r) 718 719 f, err := s.fullyResolvedRepo(r) 720 if err != nil { ··· 723 } 724 725 // remove record from pds 726 - xrpcClient, _ := s.auth.AuthorizedClient(r) 727 repoRkey := f.RepoAt.RecordKey().String() 728 - _, err = comatproto.RepoDeleteRecord(r.Context(), xrpcClient, &comatproto.RepoDeleteRecord_Input{ 729 Collection: tangled.RepoNSID, 730 Repo: user.Did, 731 Rkey: repoRkey, ··· 743 return 744 } 745 746 - ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev) 747 if err != nil { 748 log.Println("failed to create client to ", f.Knot) 749 return ··· 838 return 839 } 840 841 - ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev) 842 if err != nil { 843 log.Println("failed to create client to ", f.Knot) 844 return ··· 868 switch r.Method { 869 case http.MethodGet: 870 // for now, this is just pubkeys 871 - user := s.auth.GetUser(r) 872 repoCollaborators, err := f.Collaborators(r.Context(), s) 873 if err != nil { 874 log.Println("failed to get collaborators", err) ··· 884 885 var branchNames []string 886 var defaultBranch string 887 - us, err := NewUnsignedClient(f.Knot, s.config.Dev) 888 if err != nil { 889 log.Println("failed to create unsigned client", err) 890 } else { ··· 1008 return collaborators, nil 1009 } 1010 1011 - func (f *FullyResolvedRepo) RepoInfo(s *State, u *auth.User) repoinfo.RepoInfo { 1012 isStarred := false 1013 if u != nil { 1014 isStarred = db.GetStarStatus(s.db, u.Did, syntax.ATURI(f.RepoAt)) ··· 1051 1052 knot := f.Knot 1053 var disableFork bool 1054 - us, err := NewUnsignedClient(knot, s.config.Dev) 1055 if err != nil { 1056 log.Printf("failed to create unsigned client for %s: %v", knot, err) 1057 } else { ··· 1105 } 1106 1107 func (s *State) RepoSingleIssue(w http.ResponseWriter, r *http.Request) { 1108 - user := s.auth.GetUser(r) 1109 f, err := s.fullyResolvedRepo(r) 1110 if err != nil { 1111 log.Println("failed to get repo and knot", err) ··· 1159 } 1160 1161 func (s *State) CloseIssue(w http.ResponseWriter, r *http.Request) { 1162 - user := s.auth.GetUser(r) 1163 f, err := s.fullyResolvedRepo(r) 1164 if err != nil { 1165 log.Println("failed to get repo and knot", err) ··· 1195 1196 closed := tangled.RepoIssueStateClosed 1197 1198 - client, _ := s.auth.AuthorizedClient(r) 1199 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1200 Collection: tangled.RepoIssueStateNSID, 1201 Repo: user.Did, 1202 Rkey: appview.TID(), ··· 1214 return 1215 } 1216 1217 - err := db.CloseIssue(s.db, f.RepoAt, issueIdInt) 1218 if err != nil { 1219 log.Println("failed to close issue", err) 1220 s.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.") ··· 1231 } 1232 1233 func (s *State) ReopenIssue(w http.ResponseWriter, r *http.Request) { 1234 - user := s.auth.GetUser(r) 1235 f, err := s.fullyResolvedRepo(r) 1236 if err != nil { 1237 log.Println("failed to get repo and knot", err) ··· 1279 } 1280 1281 func (s *State) NewIssueComment(w http.ResponseWriter, r *http.Request) { 1282 - user := s.auth.GetUser(r) 1283 f, err := s.fullyResolvedRepo(r) 1284 if err != nil { 1285 log.Println("failed to get repo and knot", err) ··· 1330 } 1331 1332 atUri := f.RepoAt.String() 1333 - client, _ := s.auth.AuthorizedClient(r) 1334 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1335 Collection: tangled.RepoIssueCommentNSID, 1336 Repo: user.Did, 1337 Rkey: rkey, ··· 1358 } 1359 1360 func (s *State) IssueComment(w http.ResponseWriter, r *http.Request) { 1361 - user := s.auth.GetUser(r) 1362 f, err := s.fullyResolvedRepo(r) 1363 if err != nil { 1364 log.Println("failed to get repo and knot", err) ··· 1417 } 1418 1419 func (s *State) EditIssueComment(w http.ResponseWriter, r *http.Request) { 1420 - user := s.auth.GetUser(r) 1421 f, err := s.fullyResolvedRepo(r) 1422 if err != nil { 1423 log.Println("failed to get repo and knot", err) ··· 1469 case http.MethodPost: 1470 // extract form value 1471 newBody := r.FormValue("body") 1472 - client, _ := s.auth.AuthorizedClient(r) 1473 rkey := comment.Rkey 1474 1475 // optimistic update ··· 1484 // rkey is optional, it was introduced later 1485 if comment.Rkey != "" { 1486 // update the record on pds 1487 - ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueCommentNSID, user.Did, rkey) 1488 if err != nil { 1489 // failed to get record 1490 log.Println(err, rkey) ··· 1499 createdAt := record["createdAt"].(string) 1500 commentIdInt64 := int64(commentIdInt) 1501 1502 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1503 Collection: tangled.RepoIssueCommentNSID, 1504 Repo: user.Did, 1505 Rkey: rkey, ··· 1542 } 1543 1544 func (s *State) DeleteIssueComment(w http.ResponseWriter, r *http.Request) { 1545 - user := s.auth.GetUser(r) 1546 f, err := s.fullyResolvedRepo(r) 1547 if err != nil { 1548 log.Println("failed to get repo and knot", err) ··· 1599 1600 // delete from pds 1601 if comment.Rkey != "" { 1602 - client, _ := s.auth.AuthorizedClient(r) 1603 - _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{ 1604 Collection: tangled.GraphFollowNSID, 1605 Repo: user.Did, 1606 Rkey: comment.Rkey, ··· 1647 page = pagination.FirstPage() 1648 } 1649 1650 - user := s.auth.GetUser(r) 1651 f, err := s.fullyResolvedRepo(r) 1652 if err != nil { 1653 log.Println("failed to get repo and knot", err) ··· 1676 } 1677 1678 s.pages.RepoIssues(w, pages.RepoIssuesParams{ 1679 - LoggedInUser: s.auth.GetUser(r), 1680 RepoInfo: f.RepoInfo(s, user), 1681 Issues: issues, 1682 DidHandleMap: didHandleMap, ··· 1687 } 1688 1689 func (s *State) NewIssue(w http.ResponseWriter, r *http.Request) { 1690 - user := s.auth.GetUser(r) 1691 1692 f, err := s.fullyResolvedRepo(r) 1693 if err != nil { ··· 1735 return 1736 } 1737 1738 - client, _ := s.auth.AuthorizedClient(r) 1739 atUri := f.RepoAt.String() 1740 - resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1741 Collection: tangled.RepoIssueNSID, 1742 Repo: user.Did, 1743 Rkey: appview.TID(), ··· 1770 } 1771 1772 func (s *State) ForkRepo(w http.ResponseWriter, r *http.Request) { 1773 - user := s.auth.GetUser(r) 1774 f, err := s.fullyResolvedRepo(r) 1775 if err != nil { 1776 log.Printf("failed to resolve source repo: %v", err) ··· 1779 1780 switch r.Method { 1781 case http.MethodGet: 1782 - user := s.auth.GetUser(r) 1783 knots, err := s.enforcer.GetDomainsForUser(user.Did) 1784 if err != nil { 1785 s.pages.Notice(w, "repo", "Invalid user account.") ··· 1829 return 1830 } 1831 1832 - client, err := NewSignedClient(knot, secret, s.config.Dev) 1833 if err != nil { 1834 s.pages.Notice(w, "repo", "Failed to reach knot server.") 1835 return 1836 } 1837 1838 var uri string 1839 - if s.config.Dev { 1840 uri = "http" 1841 } else { 1842 uri = "https" ··· 1883 // continue 1884 } 1885 1886 - xrpcClient, _ := s.auth.AuthorizedClient(r) 1887 1888 createdAt := time.Now().Format(time.RFC3339) 1889 - atresp, err := comatproto.RepoPutRecord(r.Context(), xrpcClient, &comatproto.RepoPutRecord_Input{ 1890 Collection: tangled.RepoNSID, 1891 Repo: user.Did, 1892 Rkey: rkey,
··· 18 19 "tangled.sh/tangled.sh/core/api/tangled" 20 "tangled.sh/tangled.sh/core/appview" 21 "tangled.sh/tangled.sh/core/appview/db" 22 + "tangled.sh/tangled.sh/core/appview/oauth" 23 "tangled.sh/tangled.sh/core/appview/pages" 24 "tangled.sh/tangled.sh/core/appview/pages/markup" 25 "tangled.sh/tangled.sh/core/appview/pages/repoinfo" ··· 45 return 46 } 47 48 + us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev) 49 if err != nil { 50 log.Printf("failed to create unsigned client for %s", f.Knot) 51 s.pages.Error503(w) ··· 119 120 emails := uniqueEmails(commitsTrunc) 121 122 + user := s.oauth.GetUser(r) 123 s.pages.RepoIndexPage(w, pages.RepoIndexParams{ 124 LoggedInUser: user, 125 RepoInfo: f.RepoInfo(s, user), ··· 150 151 ref := chi.URLParam(r, "ref") 152 153 + us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev) 154 if err != nil { 155 log.Println("failed to create unsigned client", err) 156 return ··· 190 tagMap[hash] = append(tagMap[hash], tag.Name) 191 } 192 193 + user := s.oauth.GetUser(r) 194 s.pages.RepoLog(w, pages.RepoLogParams{ 195 LoggedInUser: user, 196 TagMap: tagMap, ··· 209 return 210 } 211 212 + user := s.oauth.GetUser(r) 213 s.pages.EditRepoDescriptionFragment(w, pages.RepoDescriptionParams{ 214 RepoInfo: f.RepoInfo(s, user), 215 }) ··· 232 return 233 } 234 235 + user := s.oauth.GetUser(r) 236 237 switch r.Method { 238 case http.MethodGet: ··· 241 }) 242 return 243 case http.MethodPut: 244 + user := s.oauth.GetUser(r) 245 newDescription := r.FormValue("description") 246 + client, err := s.oauth.AuthorizedClient(r) 247 + if err != nil { 248 + log.Println("failed to get client") 249 + s.pages.Notice(w, "repo-notice", "Failed to update description, try again later.") 250 + return 251 + } 252 253 // optimistic update 254 err = db.UpdateDescription(s.db, string(repoAt), newDescription) ··· 261 // this is a bit of a pain because the golang atproto impl does not allow nil SwapRecord field 262 // 263 // SwapRecord is optional and should happen automagically, but given that it does not, we have to perform two requests 264 + ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoNSID, user.Did, rkey) 265 if err != nil { 266 // failed to get record 267 s.pages.Notice(w, "repo-notice", "Failed to update description, no record found on PDS.") 268 return 269 } 270 + _, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 271 Collection: tangled.RepoNSID, 272 Repo: user.Did, 273 Rkey: rkey, ··· 308 } 309 ref := chi.URLParam(r, "ref") 310 protocol := "http" 311 + if !s.config.Core.Dev { 312 protocol = "https" 313 } 314 ··· 336 return 337 } 338 339 + user := s.oauth.GetUser(r) 340 s.pages.RepoCommit(w, pages.RepoCommitParams{ 341 LoggedInUser: user, 342 RepoInfo: f.RepoInfo(s, user), ··· 356 ref := chi.URLParam(r, "ref") 357 treePath := chi.URLParam(r, "*") 358 protocol := "http" 359 + if !s.config.Core.Dev { 360 protocol = "https" 361 } 362 resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/tree/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, treePath)) ··· 385 return 386 } 387 388 + user := s.oauth.GetUser(r) 389 390 var breadcrumbs [][]string 391 breadcrumbs = append(breadcrumbs, []string{f.RepoName, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), ref)}) ··· 416 return 417 } 418 419 + us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev) 420 if err != nil { 421 log.Println("failed to create unsigned client", err) 422 return ··· 456 } 457 } 458 459 + user := s.oauth.GetUser(r) 460 s.pages.RepoTags(w, pages.RepoTagsParams{ 461 LoggedInUser: user, 462 RepoInfo: f.RepoInfo(s, user), ··· 474 return 475 } 476 477 + us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev) 478 if err != nil { 479 log.Println("failed to create unsigned client", err) 480 return ··· 516 return strings.Compare(a.Name, b.Name) * -1 517 }) 518 519 + user := s.oauth.GetUser(r) 520 s.pages.RepoBranches(w, pages.RepoBranchesParams{ 521 LoggedInUser: user, 522 RepoInfo: f.RepoInfo(s, user), ··· 535 ref := chi.URLParam(r, "ref") 536 filePath := chi.URLParam(r, "*") 537 protocol := "http" 538 + if !s.config.Core.Dev { 539 protocol = "https" 540 } 541 resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath)) ··· 573 showRendered = r.URL.Query().Get("code") != "true" 574 } 575 576 + user := s.oauth.GetUser(r) 577 s.pages.RepoBlob(w, pages.RepoBlobParams{ 578 LoggedInUser: user, 579 RepoInfo: f.RepoInfo(s, user), ··· 596 filePath := chi.URLParam(r, "*") 597 598 protocol := "http" 599 + if !s.config.Core.Dev { 600 protocol = "https" 601 } 602 resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath)) ··· 657 return 658 } 659 660 + ksClient, err := NewSignedClient(f.Knot, secret, s.config.Core.Dev) 661 if err != nil { 662 log.Println("failed to create client to ", f.Knot) 663 return ··· 719 } 720 721 func (s *State) DeleteRepo(w http.ResponseWriter, r *http.Request) { 722 + user := s.oauth.GetUser(r) 723 724 f, err := s.fullyResolvedRepo(r) 725 if err != nil { ··· 728 } 729 730 // remove record from pds 731 + xrpcClient, err := s.oauth.AuthorizedClient(r) 732 + if err != nil { 733 + log.Println("failed to get authorized client", err) 734 + return 735 + } 736 repoRkey := f.RepoAt.RecordKey().String() 737 + _, err = xrpcClient.RepoDeleteRecord(r.Context(), &comatproto.RepoDeleteRecord_Input{ 738 Collection: tangled.RepoNSID, 739 Repo: user.Did, 740 Rkey: repoRkey, ··· 752 return 753 } 754 755 + ksClient, err := NewSignedClient(f.Knot, secret, s.config.Core.Dev) 756 if err != nil { 757 log.Println("failed to create client to ", f.Knot) 758 return ··· 847 return 848 } 849 850 + ksClient, err := NewSignedClient(f.Knot, secret, s.config.Core.Dev) 851 if err != nil { 852 log.Println("failed to create client to ", f.Knot) 853 return ··· 877 switch r.Method { 878 case http.MethodGet: 879 // for now, this is just pubkeys 880 + user := s.oauth.GetUser(r) 881 repoCollaborators, err := f.Collaborators(r.Context(), s) 882 if err != nil { 883 log.Println("failed to get collaborators", err) ··· 893 894 var branchNames []string 895 var defaultBranch string 896 + us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev) 897 if err != nil { 898 log.Println("failed to create unsigned client", err) 899 } else { ··· 1017 return collaborators, nil 1018 } 1019 1020 + func (f *FullyResolvedRepo) RepoInfo(s *State, u *oauth.User) repoinfo.RepoInfo { 1021 isStarred := false 1022 if u != nil { 1023 isStarred = db.GetStarStatus(s.db, u.Did, syntax.ATURI(f.RepoAt)) ··· 1060 1061 knot := f.Knot 1062 var disableFork bool 1063 + us, err := NewUnsignedClient(knot, s.config.Core.Dev) 1064 if err != nil { 1065 log.Printf("failed to create unsigned client for %s: %v", knot, err) 1066 } else { ··· 1114 } 1115 1116 func (s *State) RepoSingleIssue(w http.ResponseWriter, r *http.Request) { 1117 + user := s.oauth.GetUser(r) 1118 f, err := s.fullyResolvedRepo(r) 1119 if err != nil { 1120 log.Println("failed to get repo and knot", err) ··· 1168 } 1169 1170 func (s *State) CloseIssue(w http.ResponseWriter, r *http.Request) { 1171 + user := s.oauth.GetUser(r) 1172 f, err := s.fullyResolvedRepo(r) 1173 if err != nil { 1174 log.Println("failed to get repo and knot", err) ··· 1204 1205 closed := tangled.RepoIssueStateClosed 1206 1207 + client, err := s.oauth.AuthorizedClient(r) 1208 + if err != nil { 1209 + log.Println("failed to get authorized client", err) 1210 + return 1211 + } 1212 + _, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 1213 Collection: tangled.RepoIssueStateNSID, 1214 Repo: user.Did, 1215 Rkey: appview.TID(), ··· 1227 return 1228 } 1229 1230 + err = db.CloseIssue(s.db, f.RepoAt, issueIdInt) 1231 if err != nil { 1232 log.Println("failed to close issue", err) 1233 s.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.") ··· 1244 } 1245 1246 func (s *State) ReopenIssue(w http.ResponseWriter, r *http.Request) { 1247 + user := s.oauth.GetUser(r) 1248 f, err := s.fullyResolvedRepo(r) 1249 if err != nil { 1250 log.Println("failed to get repo and knot", err) ··· 1292 } 1293 1294 func (s *State) NewIssueComment(w http.ResponseWriter, r *http.Request) { 1295 + user := s.oauth.GetUser(r) 1296 f, err := s.fullyResolvedRepo(r) 1297 if err != nil { 1298 log.Println("failed to get repo and knot", err) ··· 1343 } 1344 1345 atUri := f.RepoAt.String() 1346 + client, err := s.oauth.AuthorizedClient(r) 1347 + if err != nil { 1348 + log.Println("failed to get authorized client", err) 1349 + s.pages.Notice(w, "issue-comment", "Failed to create comment.") 1350 + return 1351 + } 1352 + _, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 1353 Collection: tangled.RepoIssueCommentNSID, 1354 Repo: user.Did, 1355 Rkey: rkey, ··· 1376 } 1377 1378 func (s *State) IssueComment(w http.ResponseWriter, r *http.Request) { 1379 + user := s.oauth.GetUser(r) 1380 f, err := s.fullyResolvedRepo(r) 1381 if err != nil { 1382 log.Println("failed to get repo and knot", err) ··· 1435 } 1436 1437 func (s *State) EditIssueComment(w http.ResponseWriter, r *http.Request) { 1438 + user := s.oauth.GetUser(r) 1439 f, err := s.fullyResolvedRepo(r) 1440 if err != nil { 1441 log.Println("failed to get repo and knot", err) ··· 1487 case http.MethodPost: 1488 // extract form value 1489 newBody := r.FormValue("body") 1490 + client, err := s.oauth.AuthorizedClient(r) 1491 + if err != nil { 1492 + log.Println("failed to get authorized client", err) 1493 + s.pages.Notice(w, "issue-comment", "Failed to create comment.") 1494 + return 1495 + } 1496 rkey := comment.Rkey 1497 1498 // optimistic update ··· 1507 // rkey is optional, it was introduced later 1508 if comment.Rkey != "" { 1509 // update the record on pds 1510 + ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoIssueCommentNSID, user.Did, rkey) 1511 if err != nil { 1512 // failed to get record 1513 log.Println(err, rkey) ··· 1522 createdAt := record["createdAt"].(string) 1523 commentIdInt64 := int64(commentIdInt) 1524 1525 + _, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 1526 Collection: tangled.RepoIssueCommentNSID, 1527 Repo: user.Did, 1528 Rkey: rkey, ··· 1565 } 1566 1567 func (s *State) DeleteIssueComment(w http.ResponseWriter, r *http.Request) { 1568 + user := s.oauth.GetUser(r) 1569 f, err := s.fullyResolvedRepo(r) 1570 if err != nil { 1571 log.Println("failed to get repo and knot", err) ··· 1622 1623 // delete from pds 1624 if comment.Rkey != "" { 1625 + client, err := s.oauth.AuthorizedClient(r) 1626 + if err != nil { 1627 + log.Println("failed to get authorized client", err) 1628 + s.pages.Notice(w, "issue-comment", "Failed to delete comment.") 1629 + return 1630 + } 1631 + _, err = client.RepoDeleteRecord(r.Context(), &comatproto.RepoDeleteRecord_Input{ 1632 Collection: tangled.GraphFollowNSID, 1633 Repo: user.Did, 1634 Rkey: comment.Rkey, ··· 1675 page = pagination.FirstPage() 1676 } 1677 1678 + user := s.oauth.GetUser(r) 1679 f, err := s.fullyResolvedRepo(r) 1680 if err != nil { 1681 log.Println("failed to get repo and knot", err) ··· 1704 } 1705 1706 s.pages.RepoIssues(w, pages.RepoIssuesParams{ 1707 + LoggedInUser: s.oauth.GetUser(r), 1708 RepoInfo: f.RepoInfo(s, user), 1709 Issues: issues, 1710 DidHandleMap: didHandleMap, ··· 1715 } 1716 1717 func (s *State) NewIssue(w http.ResponseWriter, r *http.Request) { 1718 + user := s.oauth.GetUser(r) 1719 1720 f, err := s.fullyResolvedRepo(r) 1721 if err != nil { ··· 1763 return 1764 } 1765 1766 + client, err := s.oauth.AuthorizedClient(r) 1767 + if err != nil { 1768 + log.Println("failed to get authorized client", err) 1769 + s.pages.Notice(w, "issues", "Failed to create issue.") 1770 + return 1771 + } 1772 atUri := f.RepoAt.String() 1773 + resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 1774 Collection: tangled.RepoIssueNSID, 1775 Repo: user.Did, 1776 Rkey: appview.TID(), ··· 1803 } 1804 1805 func (s *State) ForkRepo(w http.ResponseWriter, r *http.Request) { 1806 + user := s.oauth.GetUser(r) 1807 f, err := s.fullyResolvedRepo(r) 1808 if err != nil { 1809 log.Printf("failed to resolve source repo: %v", err) ··· 1812 1813 switch r.Method { 1814 case http.MethodGet: 1815 + user := s.oauth.GetUser(r) 1816 knots, err := s.enforcer.GetDomainsForUser(user.Did) 1817 if err != nil { 1818 s.pages.Notice(w, "repo", "Invalid user account.") ··· 1862 return 1863 } 1864 1865 + client, err := NewSignedClient(knot, secret, s.config.Core.Dev) 1866 if err != nil { 1867 s.pages.Notice(w, "repo", "Failed to reach knot server.") 1868 return 1869 } 1870 1871 var uri string 1872 + if s.config.Core.Dev { 1873 uri = "http" 1874 } else { 1875 uri = "https" ··· 1916 // continue 1917 } 1918 1919 + xrpcClient, err := s.oauth.AuthorizedClient(r) 1920 + if err != nil { 1921 + log.Println("failed to get authorized client", err) 1922 + s.pages.Notice(w, "repo", "Failed to create repository.") 1923 + return 1924 + } 1925 1926 createdAt := time.Now().Format(time.RFC3339) 1927 + atresp, err := xrpcClient.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 1928 Collection: tangled.RepoNSID, 1929 Repo: user.Did, 1930 Rkey: rkey,
+3 -3
appview/state/repo_util.go
··· 12 "github.com/bluesky-social/indigo/atproto/syntax" 13 "github.com/go-chi/chi/v5" 14 "github.com/go-git/go-git/v5/plumbing/object" 15 - "tangled.sh/tangled.sh/core/appview/auth" 16 "tangled.sh/tangled.sh/core/appview/db" 17 "tangled.sh/tangled.sh/core/appview/pages/repoinfo" 18 ) 19 ··· 45 ref := chi.URLParam(r, "ref") 46 47 if ref == "" { 48 - us, err := NewUnsignedClient(knot, s.config.Dev) 49 if err != nil { 50 return nil, err 51 } ··· 73 }, nil 74 } 75 76 - func RolesInRepo(s *State, u *auth.User, f *FullyResolvedRepo) repoinfo.RolesInRepo { 77 if u != nil { 78 r := s.enforcer.GetPermissionsInRepo(u.Did, f.Knot, f.DidSlashRepo()) 79 return repoinfo.RolesInRepo{r}
··· 12 "github.com/bluesky-social/indigo/atproto/syntax" 13 "github.com/go-chi/chi/v5" 14 "github.com/go-git/go-git/v5/plumbing/object" 15 "tangled.sh/tangled.sh/core/appview/db" 16 + "tangled.sh/tangled.sh/core/appview/oauth" 17 "tangled.sh/tangled.sh/core/appview/pages/repoinfo" 18 ) 19 ··· 45 ref := chi.URLParam(r, "ref") 46 47 if ref == "" { 48 + us, err := NewUnsignedClient(knot, s.config.Core.Dev) 49 if err != nil { 50 return nil, err 51 } ··· 73 }, nil 74 } 75 76 + func RolesInRepo(s *State, u *oauth.User, f *FullyResolvedRepo) repoinfo.RolesInRepo { 77 if u != nil { 78 r := s.enforcer.GetPermissionsInRepo(u.Did, f.Knot, f.DidSlashRepo()) 79 return repoinfo.RolesInRepo{r}
+34 -19
appview/state/router.go
··· 5 "strings" 6 7 "github.com/go-chi/chi/v5" 8 "tangled.sh/tangled.sh/core/appview/middleware" 9 "tangled.sh/tangled.sh/core/appview/settings" 10 "tangled.sh/tangled.sh/core/appview/state/userutil" 11 ) ··· 67 r.Route("/tags", func(r chi.Router) { 68 r.Get("/", s.RepoTags) 69 r.Route("/{tag}", func(r chi.Router) { 70 - r.Use(middleware.AuthMiddleware(s.auth)) 71 // require auth to download for now 72 r.Get("/download/{file}", s.DownloadArtifact) 73 ··· 90 r.Get("/{issue}", s.RepoSingleIssue) 91 92 r.Group(func(r chi.Router) { 93 - r.Use(middleware.AuthMiddleware(s.auth)) 94 r.Get("/new", s.NewIssue) 95 r.Post("/new", s.NewIssue) 96 r.Post("/{issue}/comment", s.NewIssueComment) ··· 106 }) 107 108 r.Route("/fork", func(r chi.Router) { 109 - r.Use(middleware.AuthMiddleware(s.auth)) 110 r.Get("/", s.ForkRepo) 111 r.Post("/", s.ForkRepo) 112 }) 113 114 r.Route("/pulls", func(r chi.Router) { 115 r.Get("/", s.RepoPulls) 116 - r.With(middleware.AuthMiddleware(s.auth)).Route("/new", func(r chi.Router) { 117 r.Get("/", s.NewPull) 118 r.Get("/patch-upload", s.PatchUploadFragment) 119 r.Post("/validate-patch", s.ValidatePatch) ··· 131 r.Get("/", s.RepoPullPatch) 132 r.Get("/interdiff", s.RepoPullInterdiff) 133 r.Get("/actions", s.PullActions) 134 - r.With(middleware.AuthMiddleware(s.auth)).Route("/comment", func(r chi.Router) { 135 r.Get("/", s.PullComment) 136 r.Post("/", s.PullComment) 137 }) ··· 142 }) 143 144 r.Group(func(r chi.Router) { 145 - r.Use(middleware.AuthMiddleware(s.auth)) 146 r.Route("/resubmit", func(r chi.Router) { 147 r.Get("/", s.ResubmitPull) 148 r.Post("/", s.ResubmitPull) ··· 165 166 // settings routes, needs auth 167 r.Group(func(r chi.Router) { 168 - r.Use(middleware.AuthMiddleware(s.auth)) 169 // repo description can only be edited by owner 170 r.With(RepoPermissionMiddleware(s, "repo:owner")).Route("/description", func(r chi.Router) { 171 r.Put("/", s.RepoDescription) ··· 196 197 r.Get("/", s.Timeline) 198 199 - r.With(middleware.AuthMiddleware(s.auth)).Post("/logout", s.Logout) 200 201 - r.Route("/login", func(r chi.Router) { 202 - r.Get("/", s.Login) 203 - r.Post("/", s.Login) 204 - }) 205 206 r.Route("/knots", func(r chi.Router) { 207 - r.Use(middleware.AuthMiddleware(s.auth)) 208 r.Get("/", s.Knots) 209 r.Post("/key", s.RegistrationKey) 210 ··· 222 223 r.Route("/repo", func(r chi.Router) { 224 r.Route("/new", func(r chi.Router) { 225 - r.Use(middleware.AuthMiddleware(s.auth)) 226 r.Get("/", s.NewRepo) 227 r.Post("/", s.NewRepo) 228 }) 229 // r.Post("/import", s.ImportRepo) 230 }) 231 232 - r.With(middleware.AuthMiddleware(s.auth)).Route("/follow", func(r chi.Router) { 233 r.Post("/", s.Follow) 234 r.Delete("/", s.Follow) 235 }) 236 237 - r.With(middleware.AuthMiddleware(s.auth)).Route("/star", func(r chi.Router) { 238 r.Post("/", s.Star) 239 r.Delete("/", s.Star) 240 }) 241 242 r.Route("/profile", func(r chi.Router) { 243 - r.Use(middleware.AuthMiddleware(s.auth)) 244 r.Get("/edit-bio", s.EditBioFragment) 245 r.Get("/edit-pins", s.EditPinsFragment) 246 r.Post("/bio", s.UpdateProfileBio) ··· 248 }) 249 250 r.Mount("/settings", s.SettingsRouter()) 251 - 252 r.Get("/keys/{user}", s.Keys) 253 254 r.NotFound(func(w http.ResponseWriter, r *http.Request) { ··· 257 return r 258 } 259 260 func (s *State) SettingsRouter() http.Handler { 261 settings := &settings.Settings{ 262 Db: s.db, 263 - Auth: s.auth, 264 Pages: s.pages, 265 Config: s.config, 266 }
··· 5 "strings" 6 7 "github.com/go-chi/chi/v5" 8 + "github.com/gorilla/sessions" 9 "tangled.sh/tangled.sh/core/appview/middleware" 10 + oauthhandler "tangled.sh/tangled.sh/core/appview/oauth/handler" 11 "tangled.sh/tangled.sh/core/appview/settings" 12 "tangled.sh/tangled.sh/core/appview/state/userutil" 13 ) ··· 69 r.Route("/tags", func(r chi.Router) { 70 r.Get("/", s.RepoTags) 71 r.Route("/{tag}", func(r chi.Router) { 72 + r.Use(middleware.AuthMiddleware(s.oauth)) 73 // require auth to download for now 74 r.Get("/download/{file}", s.DownloadArtifact) 75 ··· 92 r.Get("/{issue}", s.RepoSingleIssue) 93 94 r.Group(func(r chi.Router) { 95 + r.Use(middleware.AuthMiddleware(s.oauth)) 96 r.Get("/new", s.NewIssue) 97 r.Post("/new", s.NewIssue) 98 r.Post("/{issue}/comment", s.NewIssueComment) ··· 108 }) 109 110 r.Route("/fork", func(r chi.Router) { 111 + r.Use(middleware.AuthMiddleware(s.oauth)) 112 r.Get("/", s.ForkRepo) 113 r.Post("/", s.ForkRepo) 114 }) 115 116 r.Route("/pulls", func(r chi.Router) { 117 r.Get("/", s.RepoPulls) 118 + r.With(middleware.AuthMiddleware(s.oauth)).Route("/new", func(r chi.Router) { 119 r.Get("/", s.NewPull) 120 r.Get("/patch-upload", s.PatchUploadFragment) 121 r.Post("/validate-patch", s.ValidatePatch) ··· 133 r.Get("/", s.RepoPullPatch) 134 r.Get("/interdiff", s.RepoPullInterdiff) 135 r.Get("/actions", s.PullActions) 136 + r.With(middleware.AuthMiddleware(s.oauth)).Route("/comment", func(r chi.Router) { 137 r.Get("/", s.PullComment) 138 r.Post("/", s.PullComment) 139 }) ··· 144 }) 145 146 r.Group(func(r chi.Router) { 147 + r.Use(middleware.AuthMiddleware(s.oauth)) 148 r.Route("/resubmit", func(r chi.Router) { 149 r.Get("/", s.ResubmitPull) 150 r.Post("/", s.ResubmitPull) ··· 167 168 // settings routes, needs auth 169 r.Group(func(r chi.Router) { 170 + r.Use(middleware.AuthMiddleware(s.oauth)) 171 // repo description can only be edited by owner 172 r.With(RepoPermissionMiddleware(s, "repo:owner")).Route("/description", func(r chi.Router) { 173 r.Put("/", s.RepoDescription) ··· 198 199 r.Get("/", s.Timeline) 200 201 + r.With(middleware.AuthMiddleware(s.oauth)).Post("/logout", s.Logout) 202 203 + // r.Route("/login", func(r chi.Router) { 204 + // r.Get("/", s.Login) 205 + // r.Post("/", s.Login) 206 + // }) 207 208 r.Route("/knots", func(r chi.Router) { 209 + r.Use(middleware.AuthMiddleware(s.oauth)) 210 r.Get("/", s.Knots) 211 r.Post("/key", s.RegistrationKey) 212 ··· 224 225 r.Route("/repo", func(r chi.Router) { 226 r.Route("/new", func(r chi.Router) { 227 + r.Use(middleware.AuthMiddleware(s.oauth)) 228 r.Get("/", s.NewRepo) 229 r.Post("/", s.NewRepo) 230 }) 231 // r.Post("/import", s.ImportRepo) 232 }) 233 234 + r.With(middleware.AuthMiddleware(s.oauth)).Route("/follow", func(r chi.Router) { 235 r.Post("/", s.Follow) 236 r.Delete("/", s.Follow) 237 }) 238 239 + r.With(middleware.AuthMiddleware(s.oauth)).Route("/star", func(r chi.Router) { 240 r.Post("/", s.Star) 241 r.Delete("/", s.Star) 242 }) 243 244 r.Route("/profile", func(r chi.Router) { 245 + r.Use(middleware.AuthMiddleware(s.oauth)) 246 r.Get("/edit-bio", s.EditBioFragment) 247 r.Get("/edit-pins", s.EditPinsFragment) 248 r.Post("/bio", s.UpdateProfileBio) ··· 250 }) 251 252 r.Mount("/settings", s.SettingsRouter()) 253 + r.Mount("/oauth", s.OAuthRouter()) 254 r.Get("/keys/{user}", s.Keys) 255 256 r.NotFound(func(w http.ResponseWriter, r *http.Request) { ··· 259 return r 260 } 261 262 + func (s *State) OAuthRouter() http.Handler { 263 + oauth := &oauthhandler.OAuthHandler{ 264 + Config: s.config, 265 + Pages: s.pages, 266 + Resolver: s.resolver, 267 + Db: s.db, 268 + Store: sessions.NewCookieStore([]byte(s.config.Core.CookieSecret)), 269 + OAuth: s.oauth, 270 + } 271 + 272 + return oauth.Router() 273 + } 274 + 275 func (s *State) SettingsRouter() http.Handler { 276 settings := &settings.Settings{ 277 Db: s.db, 278 + OAuth: s.oauth, 279 Pages: s.pages, 280 Config: s.config, 281 }
+8 -4
appview/state/star.go
··· 15 ) 16 17 func (s *State) Star(w http.ResponseWriter, r *http.Request) { 18 - currentUser := s.auth.GetUser(r) 19 20 subject := r.URL.Query().Get("subject") 21 if subject == "" { ··· 29 return 30 } 31 32 - client, _ := s.auth.AuthorizedClient(r) 33 34 switch r.Method { 35 case http.MethodPost: 36 createdAt := time.Now().Format(time.RFC3339) 37 rkey := appview.TID() 38 - resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 39 Collection: tangled.FeedStarNSID, 40 Repo: currentUser.Did, 41 Rkey: rkey, ··· 80 return 81 } 82 83 - _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{ 84 Collection: tangled.FeedStarNSID, 85 Repo: currentUser.Did, 86 Rkey: star.Rkey,
··· 15 ) 16 17 func (s *State) Star(w http.ResponseWriter, r *http.Request) { 18 + currentUser := s.oauth.GetUser(r) 19 20 subject := r.URL.Query().Get("subject") 21 if subject == "" { ··· 29 return 30 } 31 32 + client, err := s.oauth.AuthorizedClient(r) 33 + if err != nil { 34 + log.Println("failed to authorize client", err) 35 + return 36 + } 37 38 switch r.Method { 39 case http.MethodPost: 40 createdAt := time.Now().Format(time.RFC3339) 41 rkey := appview.TID() 42 + resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 43 Collection: tangled.FeedStarNSID, 44 Repo: currentUser.Did, 45 Rkey: rkey, ··· 84 return 85 } 86 87 + _, err = client.RepoDeleteRecord(r.Context(), &comatproto.RepoDeleteRecord_Input{ 88 Collection: tangled.FeedStarNSID, 89 Repo: currentUser.Did, 90 Rkey: star.Rkey,
+109 -96
appview/state/state.go
··· 21 "tangled.sh/tangled.sh/core/appview" 22 "tangled.sh/tangled.sh/core/appview/auth" 23 "tangled.sh/tangled.sh/core/appview/db" 24 "tangled.sh/tangled.sh/core/appview/pages" 25 "tangled.sh/tangled.sh/core/jetstream" 26 "tangled.sh/tangled.sh/core/rbac" ··· 29 type State struct { 30 db *db.DB 31 auth *auth.Auth 32 enforcer *rbac.Enforcer 33 - tidClock *syntax.TIDClock 34 pages *pages.Pages 35 resolver *appview.Resolver 36 jc *jetstream.JetstreamClient ··· 38 } 39 40 func Make(config *appview.Config) (*State, error) { 41 - d, err := db.Make(config.DbPath) 42 if err != nil { 43 return nil, err 44 } 45 46 - auth, err := auth.Make(config.CookieSecret) 47 if err != nil { 48 return nil, err 49 } 50 51 - enforcer, err := rbac.NewEnforcer(config.DbPath) 52 if err != nil { 53 return nil, err 54 } ··· 59 60 resolver := appview.NewResolver() 61 62 wrapper := db.DbWrapper{d} 63 jc, err := jetstream.NewJetstreamClient( 64 - config.JetstreamEndpoint, 65 "appview", 66 []string{ 67 tangled.GraphFollowNSID, ··· 86 state := &State{ 87 d, 88 auth, 89 enforcer, 90 clock, 91 pgs, ··· 101 return c.Next().String() 102 } 103 104 - func (s *State) Login(w http.ResponseWriter, r *http.Request) { 105 - ctx := r.Context() 106 - 107 - switch r.Method { 108 - case http.MethodGet: 109 - err := s.pages.Login(w, pages.LoginParams{}) 110 - if err != nil { 111 - log.Printf("rendering login page: %s", err) 112 - } 113 - 114 - return 115 - case http.MethodPost: 116 - handle := strings.TrimPrefix(r.FormValue("handle"), "@") 117 - appPassword := r.FormValue("app_password") 118 - 119 - resolved, err := s.resolver.ResolveIdent(ctx, handle) 120 - if err != nil { 121 - log.Println("failed to resolve handle:", err) 122 - s.pages.Notice(w, "login-msg", fmt.Sprintf("\"%s\" is an invalid handle.", handle)) 123 - return 124 - } 125 - 126 - atSession, err := s.auth.CreateInitialSession(ctx, resolved, appPassword) 127 - if err != nil { 128 - s.pages.Notice(w, "login-msg", "Invalid handle or password.") 129 - return 130 - } 131 - sessionish := auth.CreateSessionWrapper{ServerCreateSession_Output: atSession} 132 - 133 - err = s.auth.StoreSession(r, w, &sessionish, resolved.PDSEndpoint()) 134 - if err != nil { 135 - s.pages.Notice(w, "login-msg", "Failed to login, try again later.") 136 - return 137 - } 138 - 139 - log.Printf("successfully saved session for %s (%s)", atSession.Handle, atSession.Did) 140 - 141 - did := resolved.DID.String() 142 - defaultKnot := "knot1.tangled.sh" 143 - 144 - go func() { 145 - log.Printf("adding %s to default knot", did) 146 - err = s.enforcer.AddMember(defaultKnot, did) 147 - if err != nil { 148 - log.Println("failed to add user to knot1.tangled.sh: ", err) 149 - return 150 - } 151 - err = s.enforcer.E.SavePolicy() 152 - if err != nil { 153 - log.Println("failed to add user to knot1.tangled.sh: ", err) 154 - return 155 - } 156 - 157 - secret, err := db.GetRegistrationKey(s.db, defaultKnot) 158 - if err != nil { 159 - log.Println("failed to get registration key for knot1.tangled.sh") 160 - return 161 - } 162 - signedClient, err := NewSignedClient(defaultKnot, secret, s.config.Dev) 163 - resp, err := signedClient.AddMember(did) 164 - if err != nil { 165 - log.Println("failed to add user to knot1.tangled.sh: ", err) 166 - return 167 - } 168 - 169 - if resp.StatusCode != http.StatusNoContent { 170 - log.Println("failed to add user to knot1.tangled.sh: ", resp.StatusCode) 171 - return 172 - } 173 - }() 174 - 175 - s.pages.HxRedirect(w, "/") 176 - return 177 - } 178 - } 179 180 func (s *State) Logout(w http.ResponseWriter, r *http.Request) { 181 - s.auth.ClearSession(r, w) 182 w.Header().Set("HX-Redirect", "/login") 183 w.WriteHeader(http.StatusSeeOther) 184 } 185 186 func (s *State) Timeline(w http.ResponseWriter, r *http.Request) { 187 - user := s.auth.GetUser(r) 188 189 timeline, err := db.MakeTimeline(s.db) 190 if err != nil { ··· 235 236 return 237 case http.MethodPost: 238 - session, err := s.auth.Store.Get(r, appview.SessionName) 239 if err != nil || session.IsNew { 240 log.Println("unauthorized attempt to generate registration key") 241 http.Error(w, "Forbidden", http.StatusUnauthorized) ··· 297 298 // create a signed request and check if a node responds to that 299 func (s *State) InitKnotServer(w http.ResponseWriter, r *http.Request) { 300 - user := s.auth.GetUser(r) 301 302 domain := chi.URLParam(r, "domain") 303 if domain == "" { ··· 312 return 313 } 314 315 - client, err := NewSignedClient(domain, secret, s.config.Dev) 316 if err != nil { 317 log.Println("failed to create client to ", domain) 318 } ··· 421 return 422 } 423 424 - user := s.auth.GetUser(r) 425 reg, err := db.RegistrationByDomain(s.db, domain) 426 if err != nil { 427 w.Write([]byte("failed to pull up registration info")) ··· 469 // get knots registered by this user 470 func (s *State) Knots(w http.ResponseWriter, r *http.Request) { 471 // for now, this is just pubkeys 472 - user := s.auth.GetUser(r) 473 registrations, err := db.RegistrationsByDid(s.db, user.Did) 474 if err != nil { 475 log.Println(err) ··· 522 log.Printf("adding %s to %s\n", subjectIdentity.Handle.String(), domain) 523 524 // announce this relation into the firehose, store into owners' pds 525 - client, _ := s.auth.AuthorizedClient(r) 526 - currentUser := s.auth.GetUser(r) 527 createdAt := time.Now().Format(time.RFC3339) 528 - resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 529 Collection: tangled.KnotMemberNSID, 530 Repo: currentUser.Did, 531 Rkey: appview.TID(), ··· 550 return 551 } 552 553 - ksClient, err := NewSignedClient(domain, secret, s.config.Dev) 554 if err != nil { 555 log.Println("failed to create client to ", domain) 556 return ··· 614 func (s *State) NewRepo(w http.ResponseWriter, r *http.Request) { 615 switch r.Method { 616 case http.MethodGet: 617 - user := s.auth.GetUser(r) 618 knots, err := s.enforcer.GetDomainsForUser(user.Did) 619 if err != nil { 620 s.pages.Notice(w, "repo", "Invalid user account.") ··· 627 }) 628 629 case http.MethodPost: 630 - user := s.auth.GetUser(r) 631 632 domain := r.FormValue("domain") 633 if domain == "" { ··· 671 return 672 } 673 674 - client, err := NewSignedClient(domain, secret, s.config.Dev) 675 if err != nil { 676 s.pages.Notice(w, "repo", "Failed to connect to knot server.") 677 return ··· 686 Description: description, 687 } 688 689 - xrpcClient, _ := s.auth.AuthorizedClient(r) 690 691 createdAt := time.Now().Format(time.RFC3339) 692 - atresp, err := comatproto.RepoPutRecord(r.Context(), xrpcClient, &comatproto.RepoPutRecord_Input{ 693 Collection: tangled.RepoNSID, 694 Repo: user.Did, 695 Rkey: rkey,
··· 21 "tangled.sh/tangled.sh/core/appview" 22 "tangled.sh/tangled.sh/core/appview/auth" 23 "tangled.sh/tangled.sh/core/appview/db" 24 + "tangled.sh/tangled.sh/core/appview/oauth" 25 "tangled.sh/tangled.sh/core/appview/pages" 26 "tangled.sh/tangled.sh/core/jetstream" 27 "tangled.sh/tangled.sh/core/rbac" ··· 30 type State struct { 31 db *db.DB 32 auth *auth.Auth 33 + oauth *oauth.OAuth 34 enforcer *rbac.Enforcer 35 + tidClock syntax.TIDClock 36 pages *pages.Pages 37 resolver *appview.Resolver 38 jc *jetstream.JetstreamClient ··· 40 } 41 42 func Make(config *appview.Config) (*State, error) { 43 + d, err := db.Make(config.Core.DbPath) 44 if err != nil { 45 return nil, err 46 } 47 48 + auth, err := auth.Make(config.Core.CookieSecret) 49 if err != nil { 50 return nil, err 51 } 52 53 + enforcer, err := rbac.NewEnforcer(config.Core.DbPath) 54 if err != nil { 55 return nil, err 56 } ··· 61 62 resolver := appview.NewResolver() 63 64 + oauth := oauth.NewOAuth(d, config) 65 + 66 wrapper := db.DbWrapper{d} 67 jc, err := jetstream.NewJetstreamClient( 68 + config.Jetstream.Endpoint, 69 "appview", 70 []string{ 71 tangled.GraphFollowNSID, ··· 90 state := &State{ 91 d, 92 auth, 93 + oauth, 94 enforcer, 95 clock, 96 pgs, ··· 106 return c.Next().String() 107 } 108 109 + // func (s *State) Login(w http.ResponseWriter, r *http.Request) { 110 + // ctx := r.Context() 111 + 112 + // switch r.Method { 113 + // case http.MethodGet: 114 + // err := s.pages.Login(w, pages.LoginParams{}) 115 + // if err != nil { 116 + // log.Printf("rendering login page: %s", err) 117 + // } 118 + 119 + // return 120 + // case http.MethodPost: 121 + // handle := strings.TrimPrefix(r.FormValue("handle"), "@") 122 + // appPassword := r.FormValue("app_password") 123 + 124 + // resolved, err := s.resolver.ResolveIdent(ctx, handle) 125 + // if err != nil { 126 + // log.Println("failed to resolve handle:", err) 127 + // s.pages.Notice(w, "login-msg", fmt.Sprintf("\"%s\" is an invalid handle.", handle)) 128 + // return 129 + // } 130 + 131 + // atSession, err := s.oauth.CreateInitialSession(ctx, resolved, appPassword) 132 + // if err != nil { 133 + // s.pages.Notice(w, "login-msg", "Invalid handle or password.") 134 + // return 135 + // } 136 + // sessionish := auth.CreateSessionWrapper{ServerCreateSession_Output: atSession} 137 + 138 + // err = s.oauth.StoreSession(r, w, &sessionish, resolved.PDSEndpoint()) 139 + // if err != nil { 140 + // s.pages.Notice(w, "login-msg", "Failed to login, try again later.") 141 + // return 142 + // } 143 + 144 + // log.Printf("successfully saved session for %s (%s)", atSession.Handle, atSession.Did) 145 + 146 + // did := resolved.DID.String() 147 + // defaultKnot := "knot1.tangled.sh" 148 + 149 + // go func() { 150 + // log.Printf("adding %s to default knot", did) 151 + // err = s.enforcer.AddMember(defaultKnot, did) 152 + // if err != nil { 153 + // log.Println("failed to add user to knot1.tangled.sh: ", err) 154 + // return 155 + // } 156 + // err = s.enforcer.E.SavePolicy() 157 + // if err != nil { 158 + // log.Println("failed to add user to knot1.tangled.sh: ", err) 159 + // return 160 + // } 161 + 162 + // secret, err := db.GetRegistrationKey(s.db, defaultKnot) 163 + // if err != nil { 164 + // log.Println("failed to get registration key for knot1.tangled.sh") 165 + // return 166 + // } 167 + // signedClient, err := NewSignedClient(defaultKnot, secret, s.config.Core.Dev) 168 + // resp, err := signedClient.AddMember(did) 169 + // if err != nil { 170 + // log.Println("failed to add user to knot1.tangled.sh: ", err) 171 + // return 172 + // } 173 + 174 + // if resp.StatusCode != http.StatusNoContent { 175 + // log.Println("failed to add user to knot1.tangled.sh: ", resp.StatusCode) 176 + // return 177 + // } 178 + // }() 179 + 180 + // s.pages.HxRedirect(w, "/") 181 + // return 182 + // } 183 + // } 184 185 func (s *State) Logout(w http.ResponseWriter, r *http.Request) { 186 + s.oauth.ClearSession(r, w) 187 w.Header().Set("HX-Redirect", "/login") 188 w.WriteHeader(http.StatusSeeOther) 189 } 190 191 func (s *State) Timeline(w http.ResponseWriter, r *http.Request) { 192 + user := s.oauth.GetUser(r) 193 194 timeline, err := db.MakeTimeline(s.db) 195 if err != nil { ··· 240 241 return 242 case http.MethodPost: 243 + session, err := s.oauth.Store.Get(r, appview.SessionName) 244 if err != nil || session.IsNew { 245 log.Println("unauthorized attempt to generate registration key") 246 http.Error(w, "Forbidden", http.StatusUnauthorized) ··· 302 303 // create a signed request and check if a node responds to that 304 func (s *State) InitKnotServer(w http.ResponseWriter, r *http.Request) { 305 + user := s.oauth.GetUser(r) 306 307 domain := chi.URLParam(r, "domain") 308 if domain == "" { ··· 317 return 318 } 319 320 + client, err := NewSignedClient(domain, secret, s.config.Core.Dev) 321 if err != nil { 322 log.Println("failed to create client to ", domain) 323 } ··· 426 return 427 } 428 429 + user := s.oauth.GetUser(r) 430 reg, err := db.RegistrationByDomain(s.db, domain) 431 if err != nil { 432 w.Write([]byte("failed to pull up registration info")) ··· 474 // get knots registered by this user 475 func (s *State) Knots(w http.ResponseWriter, r *http.Request) { 476 // for now, this is just pubkeys 477 + user := s.oauth.GetUser(r) 478 registrations, err := db.RegistrationsByDid(s.db, user.Did) 479 if err != nil { 480 log.Println(err) ··· 527 log.Printf("adding %s to %s\n", subjectIdentity.Handle.String(), domain) 528 529 // announce this relation into the firehose, store into owners' pds 530 + client, err := s.oauth.AuthorizedClient(r) 531 + if err != nil { 532 + http.Error(w, "failed to authorize client", http.StatusInternalServerError) 533 + return 534 + } 535 + currentUser := s.oauth.GetUser(r) 536 createdAt := time.Now().Format(time.RFC3339) 537 + resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 538 Collection: tangled.KnotMemberNSID, 539 Repo: currentUser.Did, 540 Rkey: appview.TID(), ··· 559 return 560 } 561 562 + ksClient, err := NewSignedClient(domain, secret, s.config.Core.Dev) 563 if err != nil { 564 log.Println("failed to create client to ", domain) 565 return ··· 623 func (s *State) NewRepo(w http.ResponseWriter, r *http.Request) { 624 switch r.Method { 625 case http.MethodGet: 626 + user := s.oauth.GetUser(r) 627 knots, err := s.enforcer.GetDomainsForUser(user.Did) 628 if err != nil { 629 s.pages.Notice(w, "repo", "Invalid user account.") ··· 636 }) 637 638 case http.MethodPost: 639 + user := s.oauth.GetUser(r) 640 641 domain := r.FormValue("domain") 642 if domain == "" { ··· 680 return 681 } 682 683 + client, err := NewSignedClient(domain, secret, s.config.Core.Dev) 684 if err != nil { 685 s.pages.Notice(w, "repo", "Failed to connect to knot server.") 686 return ··· 695 Description: description, 696 } 697 698 + xrpcClient, err := s.oauth.AuthorizedClient(r) 699 + if err != nil { 700 + s.pages.Notice(w, "repo", "Failed to write record to PDS.") 701 + return 702 + } 703 704 createdAt := time.Now().Format(time.RFC3339) 705 + atresp, err := xrpcClient.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 706 Collection: tangled.RepoNSID, 707 Repo: user.Did, 708 Rkey: rkey,
+1 -1
appview/tid.go
··· 4 "github.com/bluesky-social/indigo/atproto/syntax" 5 ) 6 7 - var c *syntax.TIDClock = syntax.NewTIDClock(0) 8 9 func TID() string { 10 return c.Next().String()
··· 4 "github.com/bluesky-social/indigo/atproto/syntax" 5 ) 6 7 + var c syntax.TIDClock = syntax.NewTIDClock(0) 8 9 func TID() string { 10 return c.Next().String()
+2 -2
cmd/appview/main.go
··· 26 log.Fatal(err) 27 } 28 29 - log.Println("starting server on", c.ListenAddr) 30 - log.Println(http.ListenAndServe(c.ListenAddr, state.Router())) 31 }
··· 26 log.Fatal(err) 27 } 28 29 + log.Println("starting server on", c.Core.ListenAddr) 30 + log.Println(http.ListenAndServe(c.Core.ListenAddr, state.Router())) 31 }