Monorepo for Tangled
0

Configure Feed

Select the types of activity you want to include in your feed.

fix(appview/oauth): derive dev client URI from configured appview host

Niels Mokkenstorm (Jul 24, 2026, 12:08 AM +0200) d1215bfa 9693256d

+35 -6
+10
appview/config/config.go
··· 3 3 import ( 4 4 "context" 5 5 "fmt" 6 + "net" 6 7 "net/url" 7 8 "strings" 8 9 "time" ··· 38 39 return "https://" + c.AppviewHost 39 40 } 40 41 return "http://" + c.AppviewHost 42 + } 43 + 44 + // Hostname returns AppviewHost with any port stripped, for consumers that need 45 + // a bare host (e.g. an ssh destination) rather than a URL authority. 46 + func (c *CoreConfig) Hostname() string { 47 + if host, _, err := net.SplitHostPort(c.AppviewHost); err == nil { 48 + return host 49 + } 50 + return c.AppviewHost 41 51 } 42 52 43 53 type OAuthConfig struct {
+17
appview/config/config_test.go
··· 30 30 t.Fatalf("TANGLED_KNOT_DEFAULT override = %q, want kt.tngl.oyster.cafe", cfg.Knot.Default) 31 31 } 32 32 } 33 + 34 + func TestHostname_StripsPort(t *testing.T) { 35 + cases := map[string]string{ 36 + "127.0.0.1:3000": "127.0.0.1", 37 + "localhost:3000": "localhost", 38 + "tangled.org": "tangled.org", 39 + "[::1]:3000": "::1", 40 + } 41 + for host, want := range cases { 42 + t.Run(host, func(t *testing.T) { 43 + c := &CoreConfig{AppviewHost: host} 44 + if got := c.Hostname(); got != want { 45 + t.Fatalf("Hostname() with AppviewHost=%q = %q, want %q", host, got, want) 46 + } 47 + }) 48 + } 49 + }
+5 -5
appview/oauth/oauth.go
··· 102 102 103 103 func New(config *config.Config, ph posthog.Client, db *db.DB, enforcer *rbac.Enforcer, acl KnotMembership, res *idresolver.Resolver, logger *slog.Logger) (*OAuth, error) { 104 104 var oauthConfig oauth.ClientConfig 105 - var clientUri string 105 + clientUri := config.Core.BaseUrl() 106 + callbackUri := clientUri + "/oauth/callback" 106 107 if config.Core.Dev { 107 - clientUri = "http://127.0.0.1:3000" 108 - callbackUri := clientUri + "/oauth/callback" 108 + if config.Core.Hostname() == "localhost" { 109 + logger.Warn("dev OAuth requires a loopback IP host; use 127.0.0.1 instead of 'localhost'", "host", config.Core.AppviewHost) 110 + } 109 111 oauthConfig = oauth.NewLocalhostConfig(callbackUri, TangledScopes) 110 112 } else { 111 - clientUri = "https://" + config.Core.AppviewHost 112 113 clientId := fmt.Sprintf("%s/oauth/client-metadata.json", clientUri) 113 - callbackUri := clientUri + "/oauth/callback" 114 114 oauthConfig = oauth.NewPublicConfig(clientId, callbackUri, TangledScopes) 115 115 } 116 116
+1 -1
appview/pipelines/pipelines.go
··· 241 241 if err != nil || port == "" { 242 242 return "" 243 243 } 244 - return fmt.Sprintf("ssh -t -p %s %s %s %s", port, p.config.Core.AppviewHost, repoDid, sha) 244 + return fmt.Sprintf("ssh -t -p %s %s %s %s", port, p.config.Core.Hostname(), repoDid, sha) 245 245 } 246 246 247 247 var upgrader = websocket.Upgrader{
+2
localinfra/readme.md
··· 51 51 This writes the image directory under `out/localinfra-spindle-images`. 52 52 5. `docker compose up` 53 53 6. AppView will be running on `127.0.0.1:3000` with two test users: `alice.pds.tngl.boltless.dev` and `bob.pds.tngl.boltless.dev`. Both with password `password`. 54 + 55 + `TANGLED_APPVIEW_HOST` must be a loopback IP with the mapped port (`127.0.0.1:3000`), not `localhost`: atproto's dev OAuth client requires a loopback IP for the redirect URI. If you remap the published appview port, update `TANGLED_APPVIEW_HOST` in `docker-compose.yml` to match.