fix(bskyweb): jpeg avatars, base64 wallet creds, require token secret

This commit is contained in:
vineyardbovines
2026-06-25 18:14:31 -04:00
parent d3410fb281
commit e147fab647
4 changed files with 70 additions and 7 deletions
+20 -5
View File
@@ -7,9 +7,12 @@ import (
"errors"
"fmt"
"image"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"io"
"io/fs"
"log/slog"
"net/http"
"strings"
"time"
@@ -73,6 +76,9 @@ func (srv *Server) RegisterInvitePassRoutes() {
}
func (srv *Server) WebInvitePassURL(c echo.Context) error {
if len(srv.cfg.InvitePass.TokenSecret) == 0 {
return c.JSON(http.StatusServiceUnavailable, echo.Map{"error": "InvitePassDisabled"})
}
did, _, err := srv.authenticator.Authenticate(c)
if err != nil {
return c.JSON(http.StatusUnauthorized, echo.Map{"error": "AuthMissing"})
@@ -91,6 +97,9 @@ func (srv *Server) WebInvitePassURL(c echo.Context) error {
}
func (srv *Server) WebInvitePassPkpass(c echo.Context) error {
if len(srv.cfg.InvitePass.TokenSecret) == 0 {
return c.JSON(http.StatusServiceUnavailable, echo.Map{"error": "InvitePassDisabled"})
}
theme := CoerceTheme(c.QueryParam("theme"))
tok := c.QueryParam("t")
did, tokTheme, err := VerifyPassToken(srv.cfg.InvitePass.TokenSecret, tok, time.Now())
@@ -165,7 +174,7 @@ func (srv *Server) WebInviteWalletHero(c echo.Context) error {
if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "StripLoadFailed"})
}
avatarImg, _ := decodePNG(avatarBytes)
avatarImg, _ := decodeImage(avatarBytes)
out, err := CompositeStrip(base, avatarImg, handle, srv.cfg.InvitePass.FontFace)
if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "StripBuildFailed"})
@@ -190,13 +199,19 @@ func (srv *Server) buildPassAssets(theme, handle string, avatarBytes []byte) ([]
logo2, _ := readFS(srv.cfg.InvitePass.StripFS, "passes/logo@2x.png")
logo3, _ := readFS(srv.cfg.InvitePass.StripFS, "passes/logo@3x.png")
avatarImg, _ := decodePNG(avatarBytes)
avatarImg, _ := decodeImage(avatarBytes)
strip1Bytes, err := buildStripAtDensity(srv.cfg.InvitePass.StripFS, theme, 1, avatarImg, handle, srv.cfg.InvitePass.FontFace)
if err != nil {
return nil, fmt.Errorf("strip@1x: %w", err)
}
strip2Bytes, _ := buildStripAtDensity(srv.cfg.InvitePass.StripFS, theme, 2, avatarImg, handle, srv.cfg.InvitePass.FontFace)
strip3Bytes, _ := buildStripAtDensity(srv.cfg.InvitePass.StripFS, theme, 3, avatarImg, handle, srv.cfg.InvitePass.FontFace)
strip2Bytes, err := buildStripAtDensity(srv.cfg.InvitePass.StripFS, theme, 2, avatarImg, handle, srv.cfg.InvitePass.FontFace)
if err != nil {
slog.Debug("invite pass: strip composite failed", "scale", 2, "err", err)
}
strip3Bytes, err := buildStripAtDensity(srv.cfg.InvitePass.StripFS, theme, 3, avatarImg, handle, srv.cfg.InvitePass.FontFace)
if err != nil {
slog.Debug("invite pass: strip composite failed", "scale", 3, "err", err)
}
assets := []PassAsset{
{Name: "icon.png", Data: icon1},
@@ -241,7 +256,7 @@ func readFS(staticFS fs.FS, name string) ([]byte, error) {
return io.ReadAll(f)
}
func decodePNG(data []byte) (image.Image, error) {
func decodeImage(data []byte) (image.Image, error) {
if len(data) == 0 {
return nil, errors.New("empty")
}
+41
View File
@@ -91,3 +91,44 @@ func TestWebInvitePassPkpass_ThemeMismatchInToken(t *testing.T) {
t.Fatalf("status = %d", rec.Code)
}
}
func TestWebInvitePassURL_NoTokenSecret(t *testing.T) {
authStub := &stubAuth{did: "did:plc:abc", handle: "alice.bsky.social"}
srv := &Server{
echo: echo.New(),
cfg: &Config{
InvitePass: InvitePassConfig{
TokenSecret: nil,
},
},
authenticator: authStub,
}
srv.RegisterInvitePassRoutes()
req := httptest.NewRequest(http.MethodPost, "/invite/pass.url", bytes.NewReader([]byte(`{"theme":"dusk"}`)))
req.Header.Set("Authorization", "Bearer fake")
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
srv.echo.ServeHTTP(rec, req)
if rec.Code != http.StatusServiceUnavailable {
t.Fatalf("status = %d, want 503", rec.Code)
}
}
func TestWebInvitePassPkpass_NoTokenSecret(t *testing.T) {
srv := &Server{
echo: echo.New(),
cfg: &Config{
InvitePass: InvitePassConfig{
TokenSecret: nil,
},
},
authenticator: &stubAuth{},
}
srv.RegisterInvitePassRoutes()
req := httptest.NewRequest(http.MethodGet, "/invite/pass.pkpass?theme=dusk&t=anytoken", nil)
rec := httptest.NewRecorder()
srv.echo.ServeHTTP(rec, req)
if rec.Code != http.StatusServiceUnavailable {
t.Fatalf("status = %d, want 503", rec.Code)
}
}
+8 -1
View File
@@ -138,7 +138,11 @@ func serve(cctx *cli.Context) error {
}
}
if saJSON, issuer := os.Getenv("GOOGLE_WALLET_SA_JSON"), os.Getenv("GOOGLE_WALLET_ISSUER_ID"); saJSON != "" && issuer != "" {
wcfg, werr := LoadWalletConfig([]byte(saJSON), issuer,
saBytes := []byte(saJSON)
if decoded, err := base64.StdEncoding.DecodeString(saJSON); err == nil {
saBytes = decoded
}
wcfg, werr := LoadWalletConfig(saBytes, issuer,
os.Getenv("GOOGLE_WALLET_HERO_BASE_URL"),
os.Getenv("GOOGLE_WALLET_LOGO_URL"))
if werr != nil {
@@ -154,6 +158,9 @@ func serve(cctx *cli.Context) error {
invitePass.StripFS = stripFS
invitePass.FontFace = basicfont.Face7x13
invitePass.BaseURL = "https://bsky.app"
if invitePass.Signer != nil && len(invitePass.TokenSecret) == 0 {
slog.Warn("invite pass: Apple cert loaded but INVITE_PASS_TOKEN_SECRET is empty; pass endpoints disabled")
}
//
// server
+1 -1
View File
@@ -9,7 +9,7 @@ APPLE_PASS_KEY_PEM=
APPLE_PASS_WWDR_PEM=
# Invite passes (Google Wallet)
GOOGLE_WALLET_ISSUER_ID=
GOOGLE_WALLET_SA_JSON=
GOOGLE_WALLET_SA_JSON= # raw JSON or base64-encoded JSON; server tries base64 first, falls back to raw
GOOGLE_WALLET_HERO_BASE_URL=https://bsky.app/invite/wallet/hero
GOOGLE_WALLET_LOGO_URL=https://web-cdn.bsky.app/passes/logo.png
# HMAC key for the iOS download token