- tip gate: CreateTipPayment saved-card 2FA gate now has scaTokenizedSavedCard skip matching every other charge surface (booking, terminal, gift-card); isSCATokenizeResultShape escape added to tip SAVE gate - webhook: align UPDATE clears VAT fields before re-apply (matches sweep rescue); 503 unknown-event tracking with 24h timeout notification via square_webhook_events table - cash-tip: cashChargeBasePence no longer restores campaign or subtracts loyalty — overcharge and tip shortfall fixed; 2FA dead code remnants removed from gift-card buy flow; TwoFactorCodeInput help text deconfused; refund pre-fill unit mismatch fixed (pounds vs pence); SCA buyer names split from full_name; passwordless delete UI accepts empty password - lockout: successful current-password clears shared failed_attempts/locked_until (victim can recover from login lockout via password change); passwordless delete condition changed to require 2FA only in enforced env - erasure: stale-guest batch erasure persists Square card/customer targets to durable outbox before NULLing them (crash-safe); S3 deletion retry capped at 10 attempts with admin notification; S3_PROFILE_PICS_BUCKET startup check added - env parsing: IsExplicitDevOrMockEnv and Square HTTP client base-URL switch now normalize (ToLower+TrimSpace) for consistency - auth: change-password/delete-account get per-user rate limiters (10/min); consume param dead code suppressed with TODO - frontend: 2FA/SCA dead code removed from gift-card buy flow, TwoFactorCodeInput help text fixed, refund pre-fill unit mismatch fixed, buyer names populated from full_name Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
98 lines
2.9 KiB
Go
98 lines
2.9 KiB
Go
//go:build !dev
|
|
|
|
package s3
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
)
|
|
|
|
var Client Uploader
|
|
|
|
// FallbackToInMemory mirrors the dev-build flag so main.go can reference it
|
|
// in non-dev builds. It is always false here: the production/R2 build never
|
|
// uses the in-memory fallback client.
|
|
var FallbackToInMemory bool
|
|
|
|
// ClientIsStub reports whether the active client is the production stub that
|
|
// cannot perform real S3 operations. It is always true in the !dev build:
|
|
// every operation (Upload, Download, Delete) returns "not implemented" because
|
|
// the AWS SDK v2 is not compiled in. main.go uses it to warn at startup so the
|
|
// operator knows that profile-picture deletion will never succeed in this build.
|
|
var ClientIsStub = true
|
|
|
|
type Uploader interface {
|
|
Upload(ctx context.Context, bucket, key string, body io.Reader, contentType string) error
|
|
Download(ctx context.Context, bucket, key string, w io.Writer) error
|
|
Delete(ctx context.Context, bucket, key string) error
|
|
GetURL(ctx context.Context, bucket, key string) (string, error)
|
|
HealthCheck(ctx context.Context) error
|
|
}
|
|
|
|
type S3Client struct {
|
|
bucket string
|
|
endpoint string
|
|
region string
|
|
accessKey string
|
|
secretKey string
|
|
publicURL string
|
|
}
|
|
|
|
func Connect() error {
|
|
endpoint := os.Getenv("R2_ENDPOINT")
|
|
if endpoint == "" {
|
|
log.Println("WARNING: R2_ENDPOINT not set, S3 client not initialized")
|
|
return nil
|
|
}
|
|
|
|
Client = &S3Client{
|
|
endpoint: endpoint,
|
|
bucket: getEnv("R2_BUCKET", ""),
|
|
region: getEnv("AWS_REGION", "auto"),
|
|
accessKey: getEnv("R2_ACCESS_KEY", ""),
|
|
secretKey: getEnv("R2_SECRET_KEY", ""),
|
|
publicURL: getEnv("R2_PUBLIC_URL", ""),
|
|
}
|
|
|
|
log.Printf("Connected to R2: bucket=%s, endpoint=%s", Client.(*S3Client).bucket, endpoint)
|
|
return nil
|
|
}
|
|
|
|
func (s *S3Client) Upload(ctx context.Context, bucket, key string, body io.Reader, contentType string) error {
|
|
return fmt.Errorf("not implemented: production S3 upload requires AWS SDK v2 (use RUSTFS in dev)")
|
|
}
|
|
|
|
func (s *S3Client) Download(ctx context.Context, bucket, key string, w io.Writer) error {
|
|
return fmt.Errorf("not implemented: production S3 download requires AWS SDK v2 (use RUSTFS in dev)")
|
|
}
|
|
|
|
func (s *S3Client) Delete(ctx context.Context, bucket, key string) error {
|
|
return fmt.Errorf("not implemented: production S3 delete requires AWS SDK v2 (use RUSTFS in dev)")
|
|
}
|
|
|
|
func (s *S3Client) GetURL(ctx context.Context, bucket, key string) (string, error) {
|
|
return fmt.Sprintf("%s/%s/%s", s.publicURL, bucket, key), nil
|
|
}
|
|
|
|
func (s *S3Client) HealthCheck(_ context.Context) error {
|
|
if s.endpoint == "" {
|
|
return fmt.Errorf("S3 client not initialized: R2_ENDPOINT not set")
|
|
}
|
|
// Production stub — can't perform a real check without the SDK.
|
|
// Actual operations (Upload, Download, Delete) will return errors.
|
|
return nil
|
|
}
|
|
|
|
func getEnv(key, fallback string) string {
|
|
if val := os.Getenv(key); val != "" {
|
|
return val
|
|
}
|
|
if fallback != "" {
|
|
return fallback
|
|
}
|
|
panic("Environment variable not set: " + key)
|
|
}
|