removed all "paid" features - no more stars, gifts, or grams
This commit is contained in:
parent
d4451d753c
commit
21d8e91756
165 changed files with 318 additions and 40948 deletions
|
|
@ -33,8 +33,6 @@ type Config struct {
|
|||
Channels PublicChannelResolver
|
||||
Privacy AnonymousPrivacyResolver
|
||||
Photos ProfilePhotoResolver
|
||||
UniqueGifts UniqueStarGiftResolver
|
||||
GiftWithdrawals StarGiftWithdrawalResolver
|
||||
ModerationAppeals ModerationAppealResolver
|
||||
// TelegramLogin is the optional OIDC/Login HTTP adapter. Public Web owns
|
||||
// the listener so discovery/auth/token and public links share the exact
|
||||
|
|
@ -69,15 +67,6 @@ type ProfilePhotoResolver interface {
|
|||
GetFile(ctx context.Context, req domain.FileDownloadRequest) (domain.FileChunk, bool, error)
|
||||
}
|
||||
|
||||
type UniqueStarGiftResolver interface {
|
||||
UniqueBySlug(ctx context.Context, slug string) (domain.UniqueStarGift, bool, error)
|
||||
}
|
||||
|
||||
type StarGiftWithdrawalResolver interface {
|
||||
ResolveWithdrawal(ctx context.Context, providerRequestID string) (domain.StarGiftWithdrawal, bool, error)
|
||||
CompleteWithdrawal(ctx context.Context, providerRequestID string, date int) (domain.StarGiftWithdrawal, error)
|
||||
}
|
||||
|
||||
type ModerationAppealResolver interface {
|
||||
ResolveAppealLink(ctx context.Context, token string, now time.Time) (domain.ModerationAppealLink, bool, error)
|
||||
Appeal(ctx context.Context, appealID int64) (domain.ModerationAppeal, bool, error)
|
||||
|
|
@ -186,37 +175,30 @@ func newHandler(cfg Config, logger *zap.Logger) (http.Handler, error) {
|
|||
publicHost = u.Host
|
||||
}
|
||||
h := &handler{
|
||||
stickerSets: cfg.StickerSets,
|
||||
users: cfg.Users,
|
||||
channels: cfg.Channels,
|
||||
privacy: cfg.Privacy,
|
||||
photos: cfg.Photos,
|
||||
uniqueGifts: cfg.UniqueGifts,
|
||||
giftWithdrawals: cfg.GiftWithdrawals,
|
||||
appeals: cfg.ModerationAppeals,
|
||||
publicBaseURL: cfg.PublicBaseURL,
|
||||
publicHost: publicHost,
|
||||
appScheme: cfg.AppScheme,
|
||||
appLinks: appLinks,
|
||||
webBaseURL: cfg.WebBaseURL,
|
||||
appName: cfg.AppName,
|
||||
downloadURL: cfg.DownloadURL,
|
||||
logger: logger,
|
||||
stickerSets: cfg.StickerSets,
|
||||
users: cfg.Users,
|
||||
channels: cfg.Channels,
|
||||
privacy: cfg.Privacy,
|
||||
photos: cfg.Photos,
|
||||
appeals: cfg.ModerationAppeals,
|
||||
publicBaseURL: cfg.PublicBaseURL,
|
||||
publicHost: publicHost,
|
||||
appScheme: cfg.AppScheme,
|
||||
appLinks: appLinks,
|
||||
webBaseURL: cfg.WebBaseURL,
|
||||
appName: cfg.AppName,
|
||||
downloadURL: cfg.DownloadURL,
|
||||
logger: logger,
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /healthz", h.healthz)
|
||||
mux.HandleFunc("GET /_public/assets/logo.png", h.brandLogo)
|
||||
mux.HandleFunc("GET /_public/assets/fonts/{file}", h.brandFont)
|
||||
mux.HandleFunc("GET /payments/dev-stars", h.devStarsCheckout)
|
||||
mux.HandleFunc("GET /_public/avatar/{username}/{photoID}", h.publicAvatar)
|
||||
mux.HandleFunc("GET /_public/invite-avatar/{hash}/{photoID}", h.publicInviteAvatar)
|
||||
mux.HandleFunc("GET /addstickers/{shortName}", h.addStickers)
|
||||
mux.HandleFunc("GET /addemoji/{shortName}", h.addEmoji)
|
||||
mux.HandleFunc("GET /addlist/{slug}", h.addList)
|
||||
mux.HandleFunc("GET /nft/{slug}", h.uniqueGift)
|
||||
mux.HandleFunc("GET /nft/{slug}/{$}", h.uniqueGift)
|
||||
mux.HandleFunc("GET /gift-withdrawal/{requestID}", h.starGiftWithdrawal)
|
||||
mux.HandleFunc("POST /gift-withdrawal/{requestID}", h.completeStarGiftWithdrawal)
|
||||
if cfg.ModerationAppeals != nil {
|
||||
mux.HandleFunc("GET /appeal/{token}", h.moderationAppeal)
|
||||
mux.HandleFunc("POST /appeal/{token}", h.moderationAppeal)
|
||||
|
|
@ -238,22 +220,20 @@ func newHandler(cfg Config, logger *zap.Logger) (http.Handler, error) {
|
|||
}
|
||||
|
||||
type handler struct {
|
||||
stickerSets StickerSetResolver
|
||||
users UsernameResolver
|
||||
channels PublicChannelResolver
|
||||
privacy AnonymousPrivacyResolver
|
||||
photos ProfilePhotoResolver
|
||||
uniqueGifts UniqueStarGiftResolver
|
||||
giftWithdrawals StarGiftWithdrawalResolver
|
||||
appeals ModerationAppealResolver
|
||||
publicBaseURL string
|
||||
publicHost string
|
||||
appScheme string
|
||||
appLinks links.AppLinkBuilder
|
||||
webBaseURL string
|
||||
appName string
|
||||
downloadURL string
|
||||
logger *zap.Logger
|
||||
stickerSets StickerSetResolver
|
||||
users UsernameResolver
|
||||
channels PublicChannelResolver
|
||||
privacy AnonymousPrivacyResolver
|
||||
photos ProfilePhotoResolver
|
||||
appeals ModerationAppealResolver
|
||||
publicBaseURL string
|
||||
publicHost string
|
||||
appScheme string
|
||||
appLinks links.AppLinkBuilder
|
||||
webBaseURL string
|
||||
appName string
|
||||
downloadURL string
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
type moderationAppealPage struct {
|
||||
|
|
@ -268,43 +248,6 @@ type moderationAppealPage struct {
|
|||
CanSubmit bool
|
||||
}
|
||||
|
||||
type devStarsCheckoutPage struct {
|
||||
AppName string
|
||||
FormID string
|
||||
}
|
||||
|
||||
var devStarsCheckoutTemplate = template.Must(template.New("dev-stars-checkout").Parse(`<!doctype html>
|
||||
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta name="referrer" content="no-referrer"><meta name="robots" content="noindex,nofollow">
|
||||
<title>Dev Stars checkout · {{.AppName}}</title><style>
|
||||
body{font:16px/1.5 system-ui,sans-serif;background:#f4f6f8;color:#17212b;margin:0;padding:24px}.card{max-width:520px;margin:9vh auto;background:#fff;border-radius:16px;padding:28px;box-shadow:0 8px 32px #0002}h1{margin-top:0}.note{color:#53606d}.status{min-height:24px;color:#b42318}button{width:100%;border:0;border-radius:10px;padding:13px 18px;background:#2481cc;color:#fff;font:inherit;font-weight:650;cursor:pointer}button:disabled{opacity:.55;cursor:default}
|
||||
</style></head><body><main class="card"><h1>Complete dev purchase</h1>
|
||||
<p>This is a local telesrv test checkout. No card, Google Play, App Store, or external payment provider will be charged.</p>
|
||||
<p class="note">The package and fiat amount shown by the client are bound to form {{.FormID}}.</p>
|
||||
<button id="complete" type="button">Complete test purchase</button><p id="status" class="status" role="status"></p>
|
||||
</main><script>
|
||||
(() => { const button=document.getElementById('complete'), status=document.getElementById('status');
|
||||
button.addEventListener('click', () => { const proxy=window.TelegramWebviewProxy;
|
||||
if(!proxy || typeof proxy.postEvent !== 'function'){status.textContent='Open this checkout inside Telegram.';return;}
|
||||
button.disabled=true;status.textContent='Submitting…';
|
||||
proxy.postEvent('payment_form_submit', JSON.stringify({title:'telesrv dev payment',credentials:{type:'telesrv_dev',form_id:'{{.FormID}}'}}));
|
||||
}); })();
|
||||
</script></body></html>`))
|
||||
|
||||
func (h *handler) devStarsCheckout(w http.ResponseWriter, r *http.Request) {
|
||||
raw := strings.TrimSpace(r.URL.Query().Get("form_id"))
|
||||
formID, err := strconv.ParseInt(raw, 10, 64)
|
||||
if err != nil || formID == 0 || raw != strconv.FormatInt(formID, 10) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if err := devStarsCheckoutTemplate.Execute(w, devStarsCheckoutPage{AppName: h.appName, FormID: raw}); err != nil {
|
||||
h.logger.Warn("render dev Stars checkout failed", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
var moderationAppealTemplate = template.Must(template.New("moderation-appeal").Parse(`<!doctype html>
|
||||
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta name="referrer" content="no-referrer"><title>Moderation appeal · {{.AppName}}</title><style>
|
||||
|
|
@ -389,65 +332,6 @@ func (h *handler) moderationAppeal(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
type starGiftWithdrawalPage struct {
|
||||
AppName string
|
||||
Title string
|
||||
Slug string
|
||||
Status string
|
||||
OwnerAddress string
|
||||
GiftAddress string
|
||||
ExpiresAt string
|
||||
CanComplete bool
|
||||
}
|
||||
|
||||
var starGiftWithdrawalTemplate = template.Must(template.New("star-gift-withdrawal").Parse(`<!doctype html>
|
||||
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>{{.Title}} · {{.AppName}}</title><style>
|
||||
body{font:16px/1.5 system-ui,sans-serif;background:#f4f6f8;color:#17212b;margin:0;padding:32px}.card{max-width:560px;margin:8vh auto;background:#fff;border-radius:16px;padding:28px;box-shadow:0 8px 32px #0002}h1{margin-top:0}.meta{overflow-wrap:anywhere;color:#53606d}button{border:0;border-radius:10px;padding:12px 18px;background:#2481cc;color:#fff;font-weight:600;cursor:pointer}.done{color:#18864b;font-weight:600}
|
||||
</style></head><body><main class="card"><h1>{{.Title}}</h1><p class="meta">Collectible: {{.Slug}}</p>
|
||||
{{if .CanComplete}}<p>This export is handled only by {{.AppName}}'s internal ledger. No external blockchain or wallet is contacted.</p><form method="post"><button type="submit">Complete local export</button></form><p class="meta">Expires: {{.ExpiresAt}}</p>{{else}}<p class="done">Status: {{.Status}}</p>{{if .OwnerAddress}}<p class="meta">Owner address: {{.OwnerAddress}}</p><p class="meta">Gift address: {{.GiftAddress}}</p>{{end}}{{end}}
|
||||
</main></body></html>`))
|
||||
|
||||
func (h *handler) starGiftWithdrawal(w http.ResponseWriter, r *http.Request) {
|
||||
h.renderStarGiftWithdrawal(w, r, false)
|
||||
}
|
||||
|
||||
func (h *handler) completeStarGiftWithdrawal(w http.ResponseWriter, r *http.Request) {
|
||||
h.renderStarGiftWithdrawal(w, r, true)
|
||||
}
|
||||
|
||||
func (h *handler) renderStarGiftWithdrawal(w http.ResponseWriter, r *http.Request, complete bool) {
|
||||
requestID := strings.TrimSpace(r.PathValue("requestID"))
|
||||
if h.giftWithdrawals == nil || requestID == "" || len(requestID) > 256 {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
var withdrawal domain.StarGiftWithdrawal
|
||||
var found bool
|
||||
var err error
|
||||
if complete {
|
||||
withdrawal, err = h.giftWithdrawals.CompleteWithdrawal(r.Context(), requestID, int(time.Now().Unix()))
|
||||
found = err == nil
|
||||
} else {
|
||||
withdrawal, found, err = h.giftWithdrawals.ResolveWithdrawal(r.Context(), requestID)
|
||||
}
|
||||
if err != nil || !found {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
page := starGiftWithdrawalPage{AppName: h.appName, Title: withdrawal.Gift.Title, Slug: withdrawal.Gift.Slug,
|
||||
Status: withdrawal.Status, OwnerAddress: withdrawal.Gift.OwnerAddress, GiftAddress: withdrawal.Gift.GiftAddress,
|
||||
ExpiresAt: time.Unix(int64(withdrawal.ExpiresAt), 0).UTC().Format(time.RFC3339),
|
||||
CanComplete: withdrawal.Status == "pending" && withdrawal.ExpiresAt > int(time.Now().Unix())}
|
||||
if page.Title == "" {
|
||||
page.Title = "Collectible gift export"
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if err := starGiftWithdrawalTemplate.Execute(w, page); err != nil {
|
||||
h.logger.Warn("render star gift withdrawal", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) healthz(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
_, _ = w.Write([]byte("ok\n"))
|
||||
|
|
@ -485,63 +369,6 @@ func (h *handler) addList(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *handler) uniqueGift(w http.ResponseWriter, r *http.Request) {
|
||||
slug := r.PathValue("slug")
|
||||
if h.uniqueGifts == nil || !validStarGiftSlugPath(slug) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
unique, found, err := h.uniqueGifts.UniqueBySlug(r.Context(), slug)
|
||||
if err != nil {
|
||||
h.logger.Error("Public unique star gift lookup failed", zap.String("slug", slug), zap.Error(err))
|
||||
http.Error(w, "collectible gift lookup failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if !found {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
canonicalSlug := unique.Slug
|
||||
if unique.ID <= 0 || unique.GiftID <= 0 || unique.Num <= 0 ||
|
||||
!validStarGiftSlugPath(canonicalSlug) || !strings.EqualFold(slug, canonicalSlug) ||
|
||||
!utf8.ValidString(unique.Title) || utf8.RuneCountInString(unique.Title) > domain.MaxStarGiftTitleRunes {
|
||||
h.logger.Error("Public unique star gift resolver returned invalid aggregate",
|
||||
zap.String("requested_slug", slug), zap.String("resolved_slug", canonicalSlug),
|
||||
zap.Int64("unique_id", unique.ID), zap.Int64("gift_id", unique.GiftID), zap.Int("num", unique.Num))
|
||||
http.Error(w, "collectible gift lookup failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if slug != canonicalSlug || strings.HasSuffix(r.URL.Path, "/") {
|
||||
http.Redirect(w, r, h.publicURL("nft", canonicalSlug), http.StatusPermanentRedirect)
|
||||
return
|
||||
}
|
||||
title := strings.TrimSpace(unique.Title)
|
||||
if title == "" {
|
||||
title = "Collectible gift"
|
||||
}
|
||||
subtitle := fmt.Sprintf("Collectible #%d", unique.Num)
|
||||
if unique.AvailabilityIssued > 0 && unique.AvailabilityTotal >= unique.AvailabilityIssued {
|
||||
subtitle += fmt.Sprintf(" · %s/%s issued", groupedDecimal(unique.AvailabilityIssued), groupedDecimal(unique.AvailabilityTotal))
|
||||
}
|
||||
app := h.appURL("nft", canonicalSlug)
|
||||
data := pageData{
|
||||
AppName: h.appName,
|
||||
Title: title,
|
||||
KindLabel: "collectible gift",
|
||||
Subtitle: subtitle,
|
||||
Description: "This collectible was created from a gift on " + h.appName + ". Open it in the app to view its current details.",
|
||||
CanonicalURL: h.publicURL("nft", canonicalSlug),
|
||||
AppURL: template.URL(app),
|
||||
LegacyTgURL: template.URL(legacyTgURL("nft", "slug", canonicalSlug)),
|
||||
}
|
||||
data.AppURLJS = template.JS(strconv.Quote(app))
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Header().Set("Cache-Control", "public, max-age=60, must-revalidate")
|
||||
if err := landingTemplate.Execute(w, data); err != nil {
|
||||
h.logger.Error("Render public unique star gift page failed", zap.String("slug", canonicalSlug), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) usernameLink(w http.ResponseWriter, r *http.Request) {
|
||||
raw := strings.TrimSpace(r.PathValue("username"))
|
||||
if strings.HasPrefix(raw, "+") {
|
||||
|
|
@ -1274,23 +1101,6 @@ func validSlugPath(slug string) bool {
|
|||
return links.ValidChatlistSlug(slug)
|
||||
}
|
||||
|
||||
func validStarGiftSlugPath(slug string) bool {
|
||||
if slug == "" || len(slug) > domain.MaxStarGiftSlugBytes {
|
||||
return false
|
||||
}
|
||||
for _, r := range slug {
|
||||
switch {
|
||||
case r >= 'a' && r <= 'z':
|
||||
case r >= 'A' && r <= 'Z':
|
||||
case r >= '0' && r <= '9':
|
||||
case r == '.' || r == '_' || r == '-':
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func validUsernamePath(username string) bool {
|
||||
return domain.ValidCollectibleUsername(domain.NormalizeUsername(username))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,44 +45,6 @@ func newTestHandlerWithPublicPeers(
|
|||
return h
|
||||
}
|
||||
|
||||
func TestDevStarsCheckoutEmitsFormBoundTelegramCredentials(t *testing.T) {
|
||||
h := newTestHandler(t, fakeResolver{}, "https://links.example.test")
|
||||
for _, target := range []string{
|
||||
"/payments/dev-stars",
|
||||
"/payments/dev-stars?form_id=0",
|
||||
"/payments/dev-stars?form_id=01",
|
||||
"/payments/dev-stars?form_id=not-a-number",
|
||||
} {
|
||||
rr := httptest.NewRecorder()
|
||||
h.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, target, nil))
|
||||
if rr.Code != http.StatusNotFound {
|
||||
t.Fatalf("GET %s status = %d, want 404", target, rr.Code)
|
||||
}
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
h.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/payments/dev-stars?form_id=-70001", nil))
|
||||
body := rr.Body.String()
|
||||
if rr.Code != http.StatusOK || rr.Header().Get("Cache-Control") != "no-store" ||
|
||||
!strings.Contains(body, "payment_form_submit") ||
|
||||
!strings.Contains(body, "type:'telesrv_dev',form_id:'-70001'") ||
|
||||
!strings.Contains(body, "No card, Google Play, App Store, or external payment provider will be charged") {
|
||||
t.Fatalf("dev checkout status=%d headers=%v body=%q", rr.Code, rr.Header(), body)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeGiftWithdrawals struct {
|
||||
value domain.StarGiftWithdrawal
|
||||
found bool
|
||||
completeCalls int
|
||||
}
|
||||
|
||||
type fakeUniqueGifts struct {
|
||||
bySlug map[string]domain.UniqueStarGift
|
||||
err error
|
||||
calls int
|
||||
}
|
||||
|
||||
type fakeModerationAppeals struct {
|
||||
link domain.ModerationAppealLink
|
||||
found bool
|
||||
|
|
@ -224,146 +186,6 @@ func TestHandlerModerationAppealFailsClosed(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func (f *fakeUniqueGifts) UniqueBySlug(_ context.Context, slug string) (domain.UniqueStarGift, bool, error) {
|
||||
f.calls++
|
||||
if f.err != nil {
|
||||
return domain.UniqueStarGift{}, false, f.err
|
||||
}
|
||||
value, ok := f.bySlug[strings.ToLower(slug)]
|
||||
return value, ok, nil
|
||||
}
|
||||
|
||||
func TestHandlerServesUniqueGiftLandingPage(t *testing.T) {
|
||||
const slug = "official-5895603153683874485-7"
|
||||
resolver := &fakeUniqueGifts{bySlug: map[string]domain.UniqueStarGift{
|
||||
slug: {
|
||||
ID: 7001, GiftID: 5895603153683874485, Title: "Official Gift", Slug: slug, Num: 7,
|
||||
AvailabilityIssued: 7, AvailabilityTotal: 1000,
|
||||
},
|
||||
}}
|
||||
handler, err := NewHandler(Config{
|
||||
StickerSets: fakeResolver{}, UniqueGifts: resolver, PublicBaseURL: "http://127.0.0.1:2401",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("NewHandler: %v", err)
|
||||
}
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/nft/"+slug, nil))
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200; body=%s", rr.Code, rr.Body.String())
|
||||
}
|
||||
for _, want := range []string{
|
||||
"Official Gift", "Collectible #7", "7/1 000 issued",
|
||||
"http://127.0.0.1:2401/nft/" + slug,
|
||||
"telesrv://127.0.0.1:2401/nft/" + slug,
|
||||
"tg://nft?slug=" + slug,
|
||||
"Open it in the app to view its current details.",
|
||||
} {
|
||||
if !strings.Contains(rr.Body.String(), want) {
|
||||
t.Fatalf("body missing %q:\n%s", want, rr.Body.String())
|
||||
}
|
||||
}
|
||||
if strings.Contains(rr.Body.String(), `window.location.href = "tg://`) {
|
||||
t.Fatalf("landing page must not auto-open tg:// and steal official Telegram:\n%s", rr.Body.String())
|
||||
}
|
||||
if got := rr.Header().Get("Cache-Control"); got != "public, max-age=60, must-revalidate" {
|
||||
t.Fatalf("Cache-Control = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerCanonicalizesUniqueGiftSlug(t *testing.T) {
|
||||
const canonical = "Official-Gift-7"
|
||||
resolver := &fakeUniqueGifts{bySlug: map[string]domain.UniqueStarGift{
|
||||
strings.ToLower(canonical): {ID: 7, GiftID: 70, Slug: canonical, Num: 7},
|
||||
}}
|
||||
handler, err := NewHandler(Config{
|
||||
StickerSets: fakeResolver{}, UniqueGifts: resolver, PublicBaseURL: "https://telesrv.net",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("NewHandler: %v", err)
|
||||
}
|
||||
for _, path := range []string{"/nft/official-gift-7", "/nft/" + canonical + "/"} {
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, path, nil))
|
||||
if rr.Code != http.StatusPermanentRedirect || rr.Header().Get("Location") != "https://telesrv.net/nft/"+canonical {
|
||||
t.Fatalf("%s status=%d location=%q", path, rr.Code, rr.Header().Get("Location"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerRejectsInvalidMissingAndBrokenUniqueGift(t *testing.T) {
|
||||
resolver := &fakeUniqueGifts{bySlug: map[string]domain.UniqueStarGift{
|
||||
"broken-1": {ID: 1, GiftID: 2, Slug: "other-1", Num: 1},
|
||||
}}
|
||||
handler, err := NewHandler(Config{
|
||||
StickerSets: fakeResolver{}, UniqueGifts: resolver, PublicBaseURL: "https://telesrv.net",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("NewHandler: %v", err)
|
||||
}
|
||||
for _, path := range []string{
|
||||
"/nft/missing-1", "/nft/bad!slug", "/nft/%E4%B8%AD%E6%96%87", "/nft/" + strings.Repeat("x", domain.MaxStarGiftSlugBytes+1),
|
||||
} {
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, path, nil))
|
||||
if rr.Code != http.StatusNotFound {
|
||||
t.Fatalf("%s status=%d, want 404", path, rr.Code)
|
||||
}
|
||||
}
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/nft/broken-1", nil))
|
||||
if rr.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("broken aggregate status=%d, want 500", rr.Code)
|
||||
}
|
||||
|
||||
resolver.err = errors.New("lookup failed")
|
||||
rr = httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/nft/error-1", nil))
|
||||
if rr.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("lookup error status=%d, want 500", rr.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fakeGiftWithdrawals) ResolveWithdrawal(context.Context, string) (domain.StarGiftWithdrawal, bool, error) {
|
||||
return f.value, f.found, nil
|
||||
}
|
||||
|
||||
func (f *fakeGiftWithdrawals) CompleteWithdrawal(_ context.Context, _ string, _ int) (domain.StarGiftWithdrawal, error) {
|
||||
f.completeCalls++
|
||||
f.value.Status = "completed"
|
||||
f.value.Gift.OwnerAddress = "telesrv-owner:test"
|
||||
f.value.Gift.GiftAddress = "telesrv-gift:test"
|
||||
return f.value, nil
|
||||
}
|
||||
|
||||
func TestHandlerCompletesLocalStarGiftWithdrawal(t *testing.T) {
|
||||
resolver := &fakeGiftWithdrawals{found: true, value: domain.StarGiftWithdrawal{
|
||||
ProviderRequestID: "safe-token", Status: "pending", ExpiresAt: int(time.Now().Add(time.Minute).Unix()),
|
||||
Gift: domain.UniqueStarGift{Title: `<script>alert("x")</script>`, Slug: "gift-1"},
|
||||
}}
|
||||
handler, err := NewHandler(Config{StickerSets: fakeResolver{}, GiftWithdrawals: resolver,
|
||||
PublicBaseURL: "https://telesrv.net", AppName: "telesrv"})
|
||||
if err != nil {
|
||||
t.Fatalf("NewHandler: %v", err)
|
||||
}
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/gift-withdrawal/safe-token", nil))
|
||||
if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), "Complete local export") ||
|
||||
strings.Contains(rr.Body.String(), `<script>alert("x")</script>`) {
|
||||
t.Fatalf("withdrawal GET status=%d body=%s", rr.Code, rr.Body.String())
|
||||
}
|
||||
if csp := rr.Header().Get("Content-Security-Policy"); !strings.Contains(csp, "form-action 'self'") {
|
||||
t.Fatalf("withdrawal CSP does not allow its same-origin POST form: %q", csp)
|
||||
}
|
||||
|
||||
rr = httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodPost, "/gift-withdrawal/safe-token", strings.NewReader("")))
|
||||
if rr.Code != http.StatusOK || resolver.completeCalls != 1 || !strings.Contains(rr.Body.String(), "Status: completed") ||
|
||||
!strings.Contains(rr.Body.String(), "telesrv-owner:test") || !strings.Contains(rr.Body.String(), "telesrv-gift:test") {
|
||||
t.Fatalf("withdrawal POST calls=%d status=%d body=%s", resolver.completeCalls, rr.Code, rr.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerServesStickerSetLandingPage(t *testing.T) {
|
||||
resolver := fakeResolver{
|
||||
"fresh_pack": {
|
||||
|
|
@ -501,10 +323,7 @@ func TestHandlerUsesConfiguredClientLinksAndBrand(t *testing.T) {
|
|||
"stickers_pack": {ShortName: "stickers_pack", Title: "Stickers", Kind: domain.StickerSetKindStickers},
|
||||
"emoji_pack": {ShortName: "emoji_pack", Title: "Emoji", Kind: domain.StickerSetKindEmoji, Emojis: true},
|
||||
},
|
||||
Users: fakeUsers{"alice": {ID: 2001, Username: "Alice", FirstName: "Alice"}},
|
||||
UniqueGifts: &fakeUniqueGifts{bySlug: map[string]domain.UniqueStarGift{
|
||||
"gift-1": {ID: 1, GiftID: 10, Slug: "gift-1", Num: 1},
|
||||
}},
|
||||
Users: fakeUsers{"alice": {ID: 2001, Username: "Alice", FirstName: "Alice"}},
|
||||
PublicBaseURL: "https://links.example.test",
|
||||
AppScheme: "example-chat",
|
||||
AppLinkBase: "owpg://tenant.example.test",
|
||||
|
|
@ -540,7 +359,6 @@ func TestHandlerUsesConfiguredClientLinksAndBrand(t *testing.T) {
|
|||
{path: "/addstickers/stickers_pack", want: "example-chat://links.example.test/addstickers/stickers_pack"},
|
||||
{path: "/addemoji/emoji_pack", want: "example-chat://links.example.test/addemoji/emoji_pack"},
|
||||
{path: "/addlist/shared-folder", want: "example-chat://links.example.test/addlist/shared-folder"},
|
||||
{path: "/nft/gift-1", want: "example-chat://links.example.test/nft/gift-1"},
|
||||
} {
|
||||
rr := httptest.NewRecorder()
|
||||
h.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, tc.path, nil))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue