Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
1914738
Add Synvya account and server origins to CORS config
alejandro-runner Apr 9, 2026
5de3ad1
Only pass DISABLE_EMAILS when explicitly set
alejandro-runner Apr 9, 2026
a8a74b1
Allow users to create their first team
alejandro-runner Apr 9, 2026
179a664
Merge pull request #10 from Synvya/codex/first-team-creation
alejandro-runner Apr 9, 2026
1082c6e
Fix tenant-scoped team membership test setup
alejandro-runner Apr 9, 2026
6331243
Merge pull request #11 from Synvya/codex/first-team-creation
alejandro-runner Apr 9, 2026
a5d70b6
Document Synvya team bootstrap and harden deploy workflow
alejandro-runner Apr 9, 2026
d7a5caf
Fix logout CORS for credentialed requests
alejandro-runner Apr 9, 2026
955f397
Prune Docker cache before Synvya deploys
alejandro-runner Apr 9, 2026
464dab5
Align Synvya auth UX with account app
alejandro-runner Apr 9, 2026
0096f20
Match Synvya reset password page layout
alejandro-runner Apr 9, 2026
5a38c99
Match Synvya reset password layout to account app
alejandro-runner Apr 10, 2026
1057bee
Redirect to client app after email verification
alejandro-runner Apr 13, 2026
9d34869
Merge pull request #13 from Synvya/fix/email-verify-redirect
alejandro-runner Apr 13, 2026
72a4406
Fix cargo fmt formatting for redirect_uri validation
alejandro-runner Apr 13, 2026
5e84963
Add team invite by email feature
alejandro-runner Apr 13, 2026
37b24b7
Merge pull request #14 from Synvya/feature/team-invite-by-email
alejandro-runner Apr 13, 2026
42f1306
Update team invite spec to match implementation
alejandro-runner Apr 13, 2026
a2375fc
Apply Synvya branding to register and reset-password pages
alejandro-runner Apr 14, 2026
3915f3d
Add Synvya-themed support-admin user detail view
alejandro-runner Apr 14, 2026
9be25d0
Merge pull request #15 from Synvya/feature/synvya-admin-user-detail
alejandro-runner Apr 14, 2026
659aeb0
Fix clippy type_complexity in get_user_teams admin handler
alejandro-runner Apr 14, 2026
d348b7d
Merge pull request #16 from Synvya/fix/admin-clippy-type-complexity
alejandro-runner Apr 14, 2026
d18b3fe
Apply cargo fmt to admin get_user_teams handler
alejandro-runner Apr 14, 2026
4533d11
Merge pull request #17 from Synvya/fix/admin-cargo-fmt
alejandro-runner Apr 14, 2026
a8fb3c1
feat: soft-delete for team authorizations (#18)
alejandro-runner Apr 14, 2026
896668e
Merge pull request #20 from Synvya/feature/authorization-soft-delete
alejandro-runner Apr 14, 2026
55511d8
feat(support-admin): collapse same-label authorizations (#19)
alejandro-runner Apr 14, 2026
eea4cba
Merge pull request #21 from Synvya/feature/support-admin-collapse-auths
alejandro-runner Apr 14, 2026
05206f2
feat(support-admin): add Revoke button + Show revoked toggle
alejandro-runner Apr 14, 2026
7237914
Merge pull request #22 from Synvya/feature/support-admin-revoke-button
alejandro-runner Apr 14, 2026
2db105e
fix(support-admin): hide shared header + fix dark auth-group background
alejandro-runner Apr 14, 2026
c56a0be
chore: remove accidentally-committed web/package-lock.json (repo uses…
alejandro-runner Apr 14, 2026
cb81abd
fix(verify-email): use Synvya-themed icons on Synvya deployments
alejandro-runner Apr 14, 2026
607d346
chore: gitignore web/package-lock.json (repo uses Bun)
alejandro-runner Apr 14, 2026
6b4495f
Merge pull request #23 from Synvya/feature/support-admin-logo-fix
alejandro-runner Apr 14, 2026
d530814
fix: disable keycast web UI for Synvya deployments (#24)
alejandro-runner Apr 15, 2026
8d8e7ef
Merge pull request #25 from Synvya/fix/disable-web-ui-issue-24
alejandro-runner Apr 15, 2026
05cb221
feat(invitations): return email in preview + point invite links at cl…
alejandro-runner Apr 16, 2026
35d7fc7
Merge pull request #26 from Synvya/feat/accept-invite-polish
alejandro-runner Apr 16, 2026
627a97d
feat(teams): include member email in TeamUser roster responses
alejandro-runner Apr 17, 2026
1e09cb8
Merge pull request #27 from Synvya/feat/team-user-email-in-roster
alejandro-runner Apr 17, 2026
eb7cbc2
fix(cors): allow credentialed requests on /api/invitations/preview
alejandro-runner Apr 17, 2026
a557984
Merge pull request #30 from Synvya/fix/invitations-preview-cors
alejandro-runner Apr 17, 2026
e0e70b0
feat(invitations): add team_key_pubkey and invited_by_email to preview
alejandro-runner Apr 17, 2026
8c785ef
Merge pull request #31 from Synvya/feat/invitation-preview-team-key-e…
alejandro-runner Apr 17, 2026
f35a008
fix(email): route verify-email past DISABLE_WEB_UI and split reset URL
alejandro-runner Apr 17, 2026
f56c6e3
Merge pull request #32 from Synvya/fix/verify-email-and-reset-url
alejandro-runner Apr 17, 2026
683c26a
build keycast locally
funmu Apr 21, 2026
f8ac106
use Synvya branding; coding style udpates from tooling
funmu Apr 21, 2026
cde5d80
complete brand migration
funmu Apr 21, 2026
e31cba3
update for handling localhost
funmu Apr 21, 2026
0117681
feat(invitations): card-style invite email with kind-0 team profile
alejandro-runner Apr 22, 2026
d4de85e
Merge pull request #33 from Synvya/feat/invitation-email-card
alejandro-runner Apr 22, 2026
02c637e
style: apply cargo fmt
alejandro-runner Apr 22, 2026
93426ac
Merge pull request #34 from Synvya/fix/cargo-fmt
alejandro-runner Apr 22, 2026
20646c5
feat(email): unify verify / reset / claim email layouts
alejandro-runner Apr 22, 2026
304f509
feat(email): card layout for claim email using preloaded kind-0
alejandro-runner Apr 22, 2026
01eca16
Merge pull request #35 from Synvya/feat/email-visual-consistency
alejandro-runner Apr 22, 2026
0907e5f
chore(fmt): apply cargo fmt to admin.rs claim-email call site
alejandro-runner Apr 22, 2026
3501595
Merge pull request #36 from Synvya/fix/cargo-fmt-claim-email
alejandro-runner Apr 22, 2026
bc1fe0a
enable session cookies to work in localhost
funmu Apr 22, 2026
71fa917
update port mapping
funmu Apr 22, 2026
0bae6db
Merge pull request #37 from Synvya/murali-dev
funmu Apr 22, 2026
3aa8d71
handle staging environment for secure_cookies
funmu Apr 22, 2026
a31f974
Merge pull request #38 from Synvya/synvya-staging
funmu Apr 22, 2026
6626176
merge and run tests.
funmu Apr 23, 2026
cd6f908
Merge pull request #39 from Synvya/murali-dev
funmu Apr 23, 2026
c7df077
fix ci/cd test issue
funmu Apr 23, 2026
696aa72
Merge pull request #40 from Synvya/murali-dev
funmu Apr 23, 2026
22796b0
keep order intact
funmu Apr 23, 2026
acf377c
Merge pull request #41 from Synvya/murali-dev
funmu Apr 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
DOMAIN=keycast.example.com

# PostgreSQL database password (required for docker-compose)
POSTGRES_PASSWORD=change-this-secure-password-in-production
POSTGRES_PASSWORD=change-this-secure-password-for-nonlocal

# Database connection URL
# Local dev: postgres://postgres:password@localhost/keycast
# Docker: postgres://postgres:${POSTGRES_PASSWORD}@postgres/keycast
DATABASE_URL=postgres://postgres:password@localhost/keycast
DATABASE_URL=postgres://postgres:change-this-secure-password-for-nonlocal@localhost/keycast

# Allowed public keys for admin access (comma-separated)
# Leave empty to allow any authenticated user
Expand Down Expand Up @@ -45,7 +45,7 @@ SENDGRID_API_KEY=
FROM_EMAIL=noreply@keycast.example.com

# Optional: From name for email notifications
FROM_NAME=diVine
FROM_NAME=Synvya

# Optional: Base URL for email verification links (used in email templates)
# Should match your frontend URL
Expand Down
22 changes: 20 additions & 2 deletions .github/workflows/build-test-push-synvya.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,21 @@ jobs:
key: ${{ secrets.EC2_STAGING_SSH_KEY }}
command_timeout: 30m
script: |
set -euo pipefail
cd /opt/synvya/keycast
git pull origin synvya-staging
if [ -n "$(git status --porcelain)" ]; then
echo "Refusing to deploy from a dirty worktree"
git status --short
exit 1
fi
git pull --ff-only origin synvya-staging
bash scripts/load-secrets.sh staging
if [ -n "${{ vars.DISABLE_EMAILS }}" ]; then
echo "DISABLE_EMAILS=${{ vars.DISABLE_EMAILS }}" >> /opt/synvya/.env
fi
docker system df || true
docker builder prune -af || true
docker image prune -af || true
docker compose -f docker-compose.synvya.yml --env-file /opt/synvya/.env \
build postgres redis migrate keycast
docker compose -f docker-compose.synvya.yml --env-file /opt/synvya/.env \
Expand All @@ -165,12 +174,21 @@ jobs:
key: ${{ secrets.EC2_PRODUCTION_SSH_KEY }}
command_timeout: 30m
script: |
set -euo pipefail
cd /opt/synvya/keycast
git pull origin synvya
if [ -n "$(git status --porcelain)" ]; then
echo "Refusing to deploy from a dirty worktree"
git status --short
exit 1
fi
git pull --ff-only origin synvya
bash scripts/load-secrets.sh production
if [ -n "${{ vars.DISABLE_EMAILS }}" ]; then
echo "DISABLE_EMAILS=${{ vars.DISABLE_EMAILS }}" >> /opt/synvya/.env
fi
docker system df || true
docker builder prune -af || true
docker image prune -af || true
docker compose -f docker-compose.synvya.yml --env-file /opt/synvya/.env \
build postgres redis migrate keycast
docker compose -f docker-compose.synvya.yml --env-file /opt/synvya/.env \
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-test-push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ jobs:
env:
AWS_SES_TEST_RECIPIENT: ${{ vars.AWS_SES_TEST_RECIPIENT }}
FROM_EMAIL: ${{ vars.AWS_SES_FROM_EMAIL || 'noreply@divine.video' }}
FROM_NAME: ${{ vars.AWS_SES_FROM_NAME || 'diVine' }}
FROM_NAME: ${{ vars.AWS_SES_FROM_NAME || 'Synvya' }}
BASE_URL: https://example.com
run: |
cargo test -p keycast_api --features aws --test aws_ses_test -- --test-threads=1
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ api/oauth-ui/
specs/
examples/auth-flows-demo/

# Machine-specific Kamal deployment (Pi homelab only, not for production)
# Machine-specific Kamal deployment# Environment variables
.env
.env.*
!.env.example
config/deploy.yml
.kamal/
scripts/deploy-to-pi.sh
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ Optional:
- `SQLX_POOL_SIZE`: Database connection pool size (should match Cloud Run concurrency, default: `50`)
- `VITE_ALLOWED_PUBKEYS`: Comma-separated pubkeys for whitelist access (web frontend)
- `ENABLE_EXAMPLES`: Enable `/examples` directory serving (default: `false`, set to `true` for development)
- `DISABLE_WEB_UI`: Disable the SvelteKit web frontend (default: `false`). When `true`, non-API requests return 404 or redirect to `WEB_UI_REDIRECT_URL`. Use for deployments (e.g. Synvya) where end users should not access a keycast personal UI. `/api/*`, `/.well-known/*`, `/health*`, `/verify-email` (plus the `/_app/*` static assets it needs), and the server-rendered `/api/oauth/authorize` approval page remain available.
- `WEB_UI_REDIRECT_URL`: When `DISABLE_WEB_UI=true`, redirect non-API requests to this URL (e.g. `https://synvya.com`). If unset, non-API requests return 404.
- `PASSWORD_RESET_BASE_URL`: Base URL used when constructing password reset links in emails (default: `BASE_URL`). Set when the reset page is hosted on a different domain than `BASE_URL` — e.g. Synvya sets this to `https://account.synvya.com` in production and `https://account.staging.synvya.com` in staging, so the link points at the Synvya-hosted reset form that POSTs to keycast's `/api/auth/reset-password`.

Development (`.env` in `/web`):
- `VITE_ALLOWED_PUBKEYS`: Comma-separated pubkeys for dev access
Expand Down
103 changes: 103 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# ─────────────────────────────────────────────────────────────────────────────
# Keycast Development Helper
# ─────────────────────────────────────────────────────────────────────────────

.PHONY: check-prereq install-prereq setup migrate help dev test env-local env-staging docker-build docker-up docker-down docker-logs

# Default target: show help
all: help

help: ## Show this help message
@echo "🔑 \033[1;32mSynvya Keycast\033[0m"
@echo "Unified Nostr key management and event signing service."
@echo ""
@echo "\033[1;34mUsage:\033[0m"
@echo " make <target>"
@echo ""
@echo "\033[1;34mSetup & Environment:\033[0m"
@grep -E '^[-a-zA-Z0-9_]+:.*?## (Setup|Environment).*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'
@echo ""
@echo "\033[1;34mDevelopment & Testing:\033[0m"
@grep -E '^[-a-zA-Z0-9_]+:.*?## (Development|Quality).*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'
@echo ""
@echo "\033[1;34mDocker & Deployment:\033[0m"
@grep -E '^[-a-zA-Z0-9_]+:.*?## Docker.*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'
@echo ""

# --- Setup & Environment ---

check-prereq: ## Setup: Verify Rust, Bun, and SQLX are installed
@echo "==> Checking Keycast prerequisites..."
@command -v cargo >/dev/null 2>&1 || (echo " ✗ cargo (Rust) not found"; exit 1)
@command -v bun >/dev/null 2>&1 || (echo " ✗ bun not found"; exit 1)
@command -v sqlx >/dev/null 2>&1 || (echo " ✗ sqlx-cli not found. Run 'make install-prereq'"; exit 1)
@echo " ✓ All Keycast prerequisites met!"

install-prereq: ## Setup: Install sqlx-cli tool (required for migrations)
@echo "==> Installing prerequisites..."
cargo install sqlx-cli --no-default-features --features postgres
cargo install cargo-nextest --locked

setup: ## Setup: Initialize .env and generate master key
@echo "==> Initializing environment configuration (.env.local)..."
@if [ ! -f ".env.local" ]; then bash scripts/init.sh --domain localhost --file .env.local; fi
@if grep -q "SERVER_NSEC=$$" .env.local; then \
echo "==> Generating SERVER_NSEC for .env.local..."; \
RAND_SEC=$$(openssl rand -hex 32); \
sed -i '' "s/SERVER_NSEC=.*/SERVER_NSEC=$$RAND_SEC/" .env.local || sed -i "s/SERVER_NSEC=.*/SERVER_NSEC=$$RAND_SEC/" .env.local; \
fi
@if [ ! -f "master.key" ]; then bun run key:generate; fi
@$(MAKE) env-local
@echo " ✓ Setup complete."

env-local: ## Environment: Set active environment to .env.local
@echo "==> Setting active environment to .env.local"
@ln -sf .env.local .env

env-staging: ## Environment: Set active environment to .env.staging
@echo "==> Setting active environment to .env.staging"
@if [ ! -f ".env.staging" ]; then echo " ✗ .env.staging not found. Create it from .env.example"; exit 1; fi
@ln -sf .env.staging .env

migrate: ## Environment: Run database migrations
@$(MAKE) env-check
@echo "==> Running migrations..."
bun run db:migrate

# --- Development ---

dev: ## Development: Start the local development stack (native)
@$(MAKE) env-check
bun run dev

# --- Quality ---

test: ## Quality: Run unit and integration tests
@$(MAKE) env-check
bun run test

# --- Docker ---

docker-build: ## Docker: Build the docker images
@$(MAKE) env-check
@echo "==> Building Docker images..."
docker compose build

docker-up: ## Docker: Start the services via docker-compose
@$(MAKE) env-check
@echo "==> Starting Keycast stack..."
docker compose up -d

docker-down: ## Docker: Stop the services
@$(MAKE) env-check
@echo "==> Stopping Keycast stack..."
docker compose down

docker-logs: ## Docker: Follow docker logs
@$(MAKE) env-check
docker compose logs -f

# --- Internal ---

env-check:
@if [ ! -L ".env" ] && [ ! -f ".env" ]; then echo " ✗ No .env file or symlink found. Run 'make setup'"; exit 1; fi
24 changes: 13 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,25 +160,26 @@ POST to `/api/nostr` with `Authorization: Bearer <access_token>`:
| `nip44_encrypt` / `nip44_decrypt` | NIP-44 encryption |
| `nip04_encrypt` / `nip04_decrypt` | NIP-04 encryption |

## Self-Hosting
## Development & Self-Hosting

The fastest way to get started is using the provided `Makefile`.

```bash
git clone https://github.com/ArcadeLabsInc/keycast.git
cd keycast
bun install

# Generate encryption key
bun run key:generate
# 1. Interactive setup (generates keys, .env.local, etc.)
make setup

# Configure environment
cp .env.example .env
# Edit DATABASE_URL, SERVER_NSEC, ALLOWED_ORIGINS
# 2. Run with Docker
make docker-build
make docker-up

# Run with Docker
docker compose up -d --build
# 3. Run tests
make test
```

See [DEVELOPMENT.md](./docs/DEVELOPMENT.md) for local development setup.
For detailed instructions on environment management, native development, and testing architecture, see **[build.README.md](./build.README.md)**.

### Environment Variables

Expand All @@ -196,7 +197,7 @@ See [DEVELOPMENT.md](./docs/DEVELOPMENT.md) for local development setup.
|----------|---------|-------------|
| `SENDGRID_API_KEY` | *(none)* | If set, uses SendGrid; otherwise logs emails to console |
| `FROM_EMAIL` | `noreply@keycast.app` | Sender email address |
| `FROM_NAME` | `diVine` | Sender display name |
| `FROM_NAME` | `Synvya` | Sender display name |
| `BASE_URL` | `https://login.divine.video` | Base URL for email verification links |
| `DISABLE_EMAILS` | *(none)* | If set (any value), skips sending emails |

Expand All @@ -208,6 +209,7 @@ See [DEVELOPMENT.md](./docs/DEVELOPMENT.md) for local development setup.
| `APP_URL` | `https://login.divine.video` | Fallback URL for OAuth callbacks |
| `ALLOWED_PUBKEYS` | *(none)* | Comma-separated admin pubkeys whitelist |
| `ALLOWED_ORIGINS` | *(none)* | CORS origins (comma-separated) |
| `NODE_ENV` | `development` | Set to `production` to enable mandatory HTTPS and `Secure` cookies. |

#### Multi-tenancy

Expand Down
Loading
Loading