appview/oauth: add to default spindle #421

merged
opened by anirudh.fi targeting master from push-lyrpkknpnrus
Changed files
+129
appview
config
oauth
handler
+3
appview/config/config.go
··· 16 AppviewHost string `env:"APPVIEW_HOST, default=https://tangled.sh"` 17 Dev bool `env:"DEV, default=false"` 18 DisallowedNicknamesFile string `env:"DISALLOWED_NICKNAMES_FILE"` 19 } 20 21 type OAuthConfig struct {
··· 16 AppviewHost string `env:"APPVIEW_HOST, default=https://tangled.sh"` 17 Dev bool `env:"DEV, default=false"` 18 DisallowedNicknamesFile string `env:"DISALLOWED_NICKNAMES_FILE"` 19 + 20 + // temporarily, to add users to default spindle 21 + AppPassword string `env:"APP_PASSWORD"` 22 } 23 24 type OAuthConfig struct {
+126
appview/oauth/handler/handler.go
··· 1 package oauth 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "log" 7 "net/http" 8 "net/url" 9 "strings" 10 11 "github.com/go-chi/chi/v5" 12 "github.com/gorilla/sessions" 13 "github.com/lestrrat-go/jwx/v2/jwk" 14 "github.com/posthog/posthog-go" 15 "tangled.sh/icyphox.sh/atproto-oauth/helpers" 16 sessioncache "tangled.sh/tangled.sh/core/appview/cache/session" 17 "tangled.sh/tangled.sh/core/appview/config" 18 "tangled.sh/tangled.sh/core/appview/db" ··· 23 "tangled.sh/tangled.sh/core/idresolver" 24 "tangled.sh/tangled.sh/core/knotclient" 25 "tangled.sh/tangled.sh/core/rbac" 26 ) 27 28 const ( ··· 294 295 log.Println("session saved successfully") 296 go o.addToDefaultKnot(oauthRequest.Did) 297 298 if !o.config.Core.Dev { 299 err = o.posthog.Enqueue(posthog.Capture{ ··· 332 return pubKey, nil 333 } 334 335 func (o *OAuthHandler) addToDefaultKnot(did string) { 336 defaultKnot := "knot1.tangled.sh" 337
··· 1 package oauth 2 3 import ( 4 + "bytes" 5 + "context" 6 "encoding/json" 7 "fmt" 8 "log" 9 "net/http" 10 "net/url" 11 "strings" 12 + "time" 13 14 "github.com/go-chi/chi/v5" 15 "github.com/gorilla/sessions" 16 "github.com/lestrrat-go/jwx/v2/jwk" 17 "github.com/posthog/posthog-go" 18 "tangled.sh/icyphox.sh/atproto-oauth/helpers" 19 + tangled "tangled.sh/tangled.sh/core/api/tangled" 20 sessioncache "tangled.sh/tangled.sh/core/appview/cache/session" 21 "tangled.sh/tangled.sh/core/appview/config" 22 "tangled.sh/tangled.sh/core/appview/db" ··· 27 "tangled.sh/tangled.sh/core/idresolver" 28 "tangled.sh/tangled.sh/core/knotclient" 29 "tangled.sh/tangled.sh/core/rbac" 30 + "tangled.sh/tangled.sh/core/tid" 31 ) 32 33 const ( ··· 299 300 log.Println("session saved successfully") 301 go o.addToDefaultKnot(oauthRequest.Did) 302 + go o.addToDefaultSpindle(oauthRequest.Did) 303 304 if !o.config.Core.Dev { 305 err = o.posthog.Enqueue(posthog.Capture{ ··· 338 return pubKey, nil 339 } 340 341 + func (o *OAuthHandler) addToDefaultSpindle(did string) { 342 + // use the tangled.sh app password to get an accessJwt 343 + // and create an sh.tangled.spindle.member record with that 344 + 345 + defaultSpindle := "spindle.tangled.sh" 346 + appPassword := o.config.Core.AppPassword 347 + 348 + // TODO: hardcoded tangled handle and did for now 349 + tangledHandle := "tangled.sh" 350 + tangledDid := "did:plc:wshs7t2adsemcrrd4snkeqli" 351 + 352 + if appPassword == "" { 353 + log.Println("no app password configured, skipping spindle member addition") 354 + return 355 + } 356 + 357 + log.Printf("adding %s to default spindle", did) 358 + 359 + resolved, err := o.idResolver.ResolveIdent(context.Background(), tangledDid) 360 + if err != nil { 361 + log.Printf("failed to resolve tangled.sh DID %s: %v", tangledDid, err) 362 + return 363 + } 364 + 365 + pdsEndpoint := resolved.PDSEndpoint() 366 + if pdsEndpoint == "" { 367 + log.Printf("no PDS endpoint found for tangled.sh DID %s", tangledDid) 368 + return 369 + } 370 + 371 + sessionPayload := map[string]string{ 372 + "identifier": tangledHandle, 373 + "password": appPassword, 374 + } 375 + sessionBytes, err := json.Marshal(sessionPayload) 376 + if err != nil { 377 + log.Printf("failed to marshal session payload: %v", err) 378 + return 379 + } 380 + 381 + sessionURL := pdsEndpoint + "/xrpc/com.atproto.server.createSession" 382 + sessionReq, err := http.NewRequestWithContext(context.Background(), "POST", sessionURL, bytes.NewBuffer(sessionBytes)) 383 + if err != nil { 384 + log.Printf("failed to create session request: %v", err) 385 + return 386 + } 387 + sessionReq.Header.Set("Content-Type", "application/json") 388 + 389 + client := &http.Client{Timeout: 30 * time.Second} 390 + sessionResp, err := client.Do(sessionReq) 391 + if err != nil { 392 + log.Printf("failed to create session: %v", err) 393 + return 394 + } 395 + defer sessionResp.Body.Close() 396 + 397 + if sessionResp.StatusCode != http.StatusOK { 398 + log.Printf("failed to create session: HTTP %d", sessionResp.StatusCode) 399 + return 400 + } 401 + 402 + var session struct { 403 + AccessJwt string `json:"accessJwt"` 404 + } 405 + if err := json.NewDecoder(sessionResp.Body).Decode(&session); err != nil { 406 + log.Printf("failed to decode session response: %v", err) 407 + return 408 + } 409 + 410 + record := tangled.SpindleMember{ 411 + LexiconTypeID: "sh.tangled.spindle.member", 412 + Subject: did, 413 + Instance: defaultSpindle, 414 + CreatedAt: time.Now().Format(time.RFC3339), 415 + } 416 + 417 + recordBytes, err := json.Marshal(record) 418 + if err != nil { 419 + log.Printf("failed to marshal spindle member record: %v", err) 420 + return 421 + } 422 + 423 + payload := map[string]interface{}{ 424 + "repo": tangledDid, 425 + "collection": tangled.SpindleMemberNSID, 426 + "rkey": tid.TID(), 427 + "record": json.RawMessage(recordBytes), 428 + } 429 + 430 + payloadBytes, err := json.Marshal(payload) 431 + if err != nil { 432 + log.Printf("failed to marshal request payload: %v", err) 433 + return 434 + } 435 + 436 + url := pdsEndpoint + "/xrpc/com.atproto.repo.putRecord" 437 + req, err := http.NewRequestWithContext(context.Background(), "POST", url, bytes.NewBuffer(payloadBytes)) 438 + if err != nil { 439 + log.Printf("failed to create HTTP request: %v", err) 440 + return 441 + } 442 + 443 + req.Header.Set("Content-Type", "application/json") 444 + req.Header.Set("Authorization", "Bearer "+session.AccessJwt) 445 + 446 + resp, err := client.Do(req) 447 + if err != nil { 448 + log.Printf("failed to add user to default spindle: %v", err) 449 + return 450 + } 451 + defer resp.Body.Close() 452 + 453 + if resp.StatusCode != http.StatusOK { 454 + log.Printf("failed to add user to default spindle: HTTP %d", resp.StatusCode) 455 + return 456 + } 457 + 458 + log.Printf("successfully added %s to default spindle", did) 459 + } 460 + 461 func (o *OAuthHandler) addToDefaultKnot(did string) { 462 defaultKnot := "knot1.tangled.sh" 463