Monorepo for Tangled tangled.org

appview: swap out old auth service for oauth

Also does some driveby config refactoring.

anirudh.fi 492f7060 ed9740a1

verified
+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)
··· 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)
+2
appview/oauth/oauth.go
··· 48 } 49 50 userSession.Values[appview.SessionDid] = oreq.Did 51 userSession.Values[appview.SessionAuthenticated] = true 52 err = userSession.Save(r, w) 53 if err != nil {
··· 48 } 49 50 userSession.Values[appview.SessionDid] = oreq.Did 51 + userSession.Values[appview.SessionHandle] = oreq.Handle 52 + userSession.Values[appview.SessionPds] = oreq.PdsUrl 53 userSession.Values[appview.SessionAuthenticated] = true 54 err = userSession.Save(r, w) 55 if err != nil {
+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,
+98 -85
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 } ··· 58 pgs := pages.NewPages(config) 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 } ··· 60 pgs := pages.NewPages(config) 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 }