23 Commits

Author SHA1 Message Date
Head of Product & Engineering
3266714dff fix: add container log output to deploy.sh for crash diagnostics
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
When the bridge container fails to start, the deploy script previously
only showed docker compose ps which doesn't include the crash reason.
Now outputs last 30 log lines so pipeline output shows the error.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 23:39:30 +02:00
Head of Product & Engineering
c98ba0d4d6 fix: upgrade golang.org/x/crypto to v0.35.0 (CVE-2025-22869)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Trivy flagged golang.org/x/crypto v0.33.0 with HIGH severity
CVE-2025-22869 (DoS in SSH key exchange). Upgraded to v0.35.0
which contains the fix.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 23:20:05 +02:00
Head of Product & Engineering
6db500235b fix: resolve gosec findings G112 and G602
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
G112 (Slowloris): add ReadHeaderTimeout: 10s to http.Server
G602 (slice bounds): use explicit bounds-safe index for backoff slice
  (attempt is guarded but gosec can't prove it statically)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 23:03:47 +02:00
Head of Product & Engineering
5f7dd7462d fix: add USER nobody to Dockerfile to pass semgrep security check
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Running as root in a container is a security hazard. Use the existing
nobody user from alpine:3.19 to drop privileges before CMD.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 22:49:28 +02:00
Head of Product & Engineering
7714013e48 fix: suppress remaining errcheck failures in test and oauth code
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Fix two more json.NewEncoder(w).Encode() calls in oauth_test.go
(lines 53 and 119) that were missed in the previous pass.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 22:37:52 +02:00
Head of Product & Engineering
6d3c9c071f fix: suppress remaining errcheck failures in test and oauth code
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- internal/ghl/oauth.go:186: defer func(){ _ = resp.Body.Close() }()
- internal/cast/client_test.go: prefix all json.Decode/Encode calls with _ =
- internal/ghl/oauth_test.go: _ = r.ParseForm(), _, _ = w.Write(...)

golangci-lint exclusion rules in v2 are not suppressing test file errcheck
as expected, so fixes are applied directly in source.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 22:33:35 +02:00
Head of Product & Engineering
6a853f6566 fix: resolve all golangci-lint v2 failures
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Production fixes:
- cmd/server/main.go: refactor to run() helper to eliminate exitAfterDefer
  (os.Exit in main() no longer bypasses deferred s.Close)
- internal/cast/client.go: use _ = resp.Body.Close() (errcheck)
- internal/ghl/api.go: wrap both defers as func(){ _ = resp.Body.Close() }()

Test fixes:
- internal/cast/client_test.go: replace err.(*CastAPIError) type assertions
  with errors.As (errorlint)

Config:
- .golangci.yml: use explicit path regex .*_test\\.go and add errorlint
  to test-file exclusions

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 21:52:36 +02:00
Head of Product & Engineering
8f2080203d fix: update .golangci.yml to v2 config format
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
golangci-lint v2 requires version: "2" at the top level, linters.settings
(not linters-settings), and issues.exclusions.rules (not issues.exclude-rules).
Also removed gosimple and unused which are now merged into staticcheck.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 21:39:28 +02:00
Head of Product & Engineering
12c547d215 fix: address remaining golangci-lint warnings
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- internal/ghl/oauth.go: acknowledge fmt.Fprint return (errcheck)
- internal/ghl/api.go: handle io.ReadAll error instead of discarding (errcheck)
- internal/cast/client.go: replace defer-in-loop with explicit Body.Close
  after ReadAll (gocritic defer-in-loop)
- internal/phone/normalize.go: move inline regexp.MustCompile to package-level
  var e164Pattern (gocritic / performance)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 21:33:03 +02:00
Head of Product & Engineering
675f765cc0 fix: resolve golangci-lint failures and .gitignore scope issue
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- cmd/server/main.go: acknowledge w.Write return value (errcheck)
- internal/store/mongo.go: use errors.Is for ErrNoDocuments (errorlint)
- .golangci.yml: add linter config scoped to relevant linters
- .gitignore: scope /server to root only (was blocking cmd/server/ directory)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 21:22:03 +02:00
Head of Product & Engineering
2e07374681 fix: tolerate literal \n in GHL_WEBHOOK_PUBLIC_KEY env var
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
pem.Decode requires actual newlines. When a PEM key is pasted into a
.env file it is commonly stored as a single line with \n literals.
Normalise these before decoding so both formats work.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 18:50:56 +02:00
Head of Product & Engineering
83de6cb089 fix: correct MONGO_URI to use Docker service hostname
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
localhost:27017 resolves to the container itself inside Docker.
The mongo service is reachable via its compose service name 'mongo'.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 18:48:29 +02:00
Head of Product & Engineering
72345a5129 fix: bump Go image to 1.26 to match go.mod requirement
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
go.mod declares go 1.26.1; docker build was failing with
"go.mod requires go >= 1.26.1 (running go 1.22.12; GOTOOLCHAIN=local)"

- Dockerfile: golang:1.22-alpine → golang:1.26-alpine
- .woodpecker.yml: test image 1.22-alpine → 1.26-alpine;
  gosec image 1.22-bookworm → 1.26-bookworm
- CLAUDE.md: update stack note to Go 1.26+

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 18:34:21 +02:00
Head of Product & Engineering
7745d205cb feat: add production deployment artifacts for ghl.cast.ph (Vultr)
Align Woodpecker CI pipeline with team standard (cast-backend pattern):
- Replace plugins/docker with woodpeckerci/plugin-docker-buildx
- Use git.sds.dev registry; tag with CI_COMMIT_SHA short + latest
- Use team secret names: registry_user/password, deploy_ssh_key
- Add golangci-lint, semgrep, gosec, trivy-fs, trivy-secrets security gates
- Deploy on push to main (not on tag): build-and-push then deploy step
  calls bash /opt/cast-ghl-provider/deploy/deploy.sh on server
- Add Telegram notification on success/failure

docker-compose.yaml: add image: git.sds.dev/cast/cast-ghl-provider:latest
(server pulls from registry; build: kept for local dev only)

deploy/deploy.sh: simplified to docker compose pull + up
(build now happens in CI, not on the server)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 14:12:27 +02:00
Head of Product & Engineering
877895f43e fix: correct domain from ghl.cast.ph to hl.cast.ph throughout
Update all active config and documentation files to use the correct
production domain hl.cast.ph (not ghl.cast.ph).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 14:09:43 +02:00
Head of Product & Engineering
f29b39b40c feat: adapt deployment for existing Docker server with nginx-proxy + Woodpecker
- docker-compose.yaml: remove port binding; add VIRTUAL_HOST/LETSENCRYPT_HOST
  env vars for nginx-proxy auto-routing; add internal + external proxy networks
- .woodpecker.yml: consolidate build steps into single ci step; add deploy-main
  step that builds + deploys on every push to main; keep deploy-tag for
  registry-pull deploys on version tags
- deploy/deploy.sh: simplify for manual/emergency use on existing server;
  add --from-registry flag for registry pull vs local build
- Remove deploy/setup-server.sh and deploy/nginx/ (not needed on existing server)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 14:06:21 +02:00
Head of Product & Engineering
f99772d8c0 feat: add production deployment artifacts for ghl.cast.ph (Vultr)
- deploy/nginx/ghl.cast.ph.conf: Nginx reverse proxy with SSL (Let's Encrypt)
- deploy/setup-server.sh: one-shot Ubuntu VPS bootstrap (Docker, Nginx, Certbot, UFW)
- deploy/deploy.sh: pull-and-redeploy script using Docker Compose
- docker-compose.yaml: bind bridge to 127.0.0.1 only; add Mongo healthcheck;
  bridge waits for Mongo healthy before starting

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 01:46:26 +02:00
Head of Product & Engineering
c2fd0e2f98 chore: add root server binary to .gitignore
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Co-Authored-By: SideKx <sidekx.ai@sds.dev>
2026-04-05 01:43:04 +02:00
Head of Product & Engineering
a2826a3da7 fix: switch webhook signature verification from ECDSA to RSA-PKCS1v15+SHA-256
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
GHL uses RSA + SHA-256 for x-wh-signature, not ECDSA P-256 as documented
in the original task files. Also adds forward-compatible Ed25519 support
for X-GHL-Signature (GHL migration scheduled July 2026): handler checks
X-GHL-Signature first, falls back to x-wh-signature.

- webhook.go: replace ecdsa.VerifyASN1 with rsa.VerifyPKCS1v15; add
  verifyEd25519 + verifyIncomingSignature dispatch; update struct fields
- webhook_test.go: regenerate test keys as RSA-2048, sign with PKCS1v15
- CLAUDE.md: correct crypto stack and key implementation notes
- .env.example: clarify GHL_WEBHOOK_PUBLIC_KEY is a static RSA key from docs

Co-Authored-By: SideKx <sidekx.ai@sds.dev>
2026-04-05 01:15:56 +02:00
Head of Product & Engineering
dcf1e3070e test: expand test coverage — uninstall, dedup, 401, token refresh
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- cast/client_test: add TestSendSMS_Unauthorized (401 → CastAPIError)
- ghl/webhook_test: add duplicate messageId, 450-char message, HandleUninstall (valid/invalid sig)
- ghl/oauth_test: add GetValidToken auto-refresh and refresh-failure tests
- ghl/oauth: make tokenURL a struct field (default ghlTokenURL) so tests can inject a mock endpoint

Co-Authored-By: SideKx <sidekx.ai@sds.dev>
2026-04-05 00:36:45 +02:00
Head of Product & Engineering
d081875fce fix: add uninstall handler, idempotency guard, and OAuth error handling
GHL Marketplace submission blockers resolved:
- Add POST /api/ghl/v1/webhook/uninstall to delete token on app removal
- Add in-memory messageId deduplication (10-min TTL) to prevent duplicate SMS sends on webhook retries
- Handle ?error= param in OAuth callback for user-denied auth flows
- Pass store to WebhookHandler; update tests accordingly

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-04 17:52:09 +02:00
Head of Product & Engineering
a40a4aa626 feat: initial implementation of Cast GHL Provider
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Complete MVP implementation of the Cast GHL Conversation Provider bridge:
- Go module setup with chi router and mongo-driver dependencies
- Config loading with env var validation and defaults
- MongoDB token store with upsert, get, update, delete operations
- Cast.ph SMS client with 429 retry logic and typed errors
- Phone number normalization (E.164 ↔ Philippine local format)
- GHL OAuth 2.0 install/callback/refresh flow
- GHL webhook handler with ECDSA signature verification (async dispatch)
- GHL API client for message status updates and inbound message stubs
- Multi-stage Dockerfile, docker-compose with MongoDB, Woodpecker CI pipeline
- Unit tests for phone normalization, Cast client, GHL webhook, and OAuth handlers

Co-Authored-By: SideKx <sidekx.ai@sds.dev>
2026-04-04 17:27:05 +02:00
8a2fa2407c Initial commit 2026-04-04 14:57:29 +00:00