Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
c7a8205
POC PostgREST implementation
Sep 16, 2025
ab56f4c
claude cmd_code_review
Sep 16, 2025
6efdaf3
move postgrest config to file and add file documentation
Sep 17, 2025
9c9a2dc
clean up PostgREST config and update documentation
Sep 17, 2025
6e23da3
Merge branch 'main' into postgrest
commoddity Sep 17, 2025
45d0747
adding legacy transforms for Grove Portal
fredteumer Sep 17, 2025
174d5b9
Merge remote-tracking branch 'origin/main' into postgrest
Sep 18, 2025
568d917
enhancing hydration scripts, fixing schema, enhancing transform
fredteumer Sep 18, 2025
1768561
fixing bug with hydrate-applications
fredteumer Sep 18, 2025
7a7645d
fixing syntax error in schema after adjustments
fredteumer Sep 18, 2025
3dccba2
updating schema for legacy compatibility. fixes to transforms
fredteumer Sep 18, 2025
0cc5fe3
adding auth to schema. enhancing transform to bring over legacy auth …
fredteumer Sep 18, 2025
e0e0149
minor file reorganizations
Sep 19, 2025
b44e1b7
Merge branch 'portal-db-elt' of github.com:buildwithgrove/path into p…
Sep 19, 2025
bd53fbe
add transaction example
Sep 19, 2025
ea7a0b3
fix go sdk generation
Sep 19, 2025
546354d
fix: implement review comments
Sep 19, 2025
d5e6928
add Typescript SDK
Sep 19, 2025
81edda5
use better ts open api generation
Sep 19, 2025
8990b2c
Merge branch 'main' into portal-db-elt
Olshansk Sep 25, 2025
d5ffa0f
Merge branch 'portal-db-elt' of github.com:buildwithgrove/path into p…
Sep 26, 2025
bdf7f82
Merge branch 'main' into portal-db-elt
commoddity Sep 26, 2025
e40262d
Merge branch 'portal-db-elt' of github.com:buildwithgrove/path into p…
Sep 26, 2025
8e2a00c
update sdks after pulling main
Sep 26, 2025
af9ead1
add production pg dump
Sep 26, 2025
1f3e0c5
check port docker container
Sep 26, 2025
b9af073
Minor nits before leaving comments
Olshansk Sep 29, 2025
07533e0
chore: merge conflicts
Sep 29, 2025
ff3b373
fix hydrate-prod script
Sep 29, 2025
14e030a
Merge with main
Olshansk Oct 1, 2025
7a10118
WIP review
Olshansk Oct 1, 2025
42b6364
Update docasurus
Olshansk Oct 2, 2025
5336763
WIP
Olshansk Oct 2, 2025
4e9a3a7
Remove all the .ts files
Olshansk Oct 2, 2025
afb89a1
Merge branch 'postgrest' into postgrest-sdk
Olshansk Oct 2, 2025
20895f3
Revert "Remove all the .ts files"
Olshansk Oct 2, 2025
e2e1615
Delete all sdk gen code
Olshansk Oct 2, 2025
49d90dd
Merge branch 'postgrest' into postgrest-sdk
Olshansk Oct 2, 2025
2cace06
Revert "Delete all sdk gen code"
Olshansk Oct 2, 2025
f5953c0
chore: merge conflicts
Oct 6, 2025
7fd85f0
fix sdk gen script and add auth password sql file
Oct 6, 2025
af56c90
feat: add authenticator password script
Oct 7, 2025
9d50b3c
remove TS SDK
Oct 7, 2025
82e808b
add typescript SDK
Oct 7, 2025
6d693b6
fix ts readme
Oct 7, 2025
8d29335
add SQL for aux services
Oct 7, 2025
eba8d31
remove duplicate scripts
Oct 8, 2025
e781b45
generate latest sdks
Oct 8, 2025
57aaca3
fix legacy owner filter
Oct 8, 2025
59f4ba1
fix sdk generation
Oct 8, 2025
d629788
USe openapi-typescript for TS codegen
Oct 9, 2025
0e2004c
rename grove backend sql file
Oct 9, 2025
5cfbfa8
add openapi-react-query to example
Oct 9, 2025
77b4d66
use NPM package name in README.md
Oct 9, 2025
51fb8dc
add Auth0 JWT portal DB access config for portal UI access
Oct 10, 2025
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: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,9 @@ pg_dump.sql

# Benchmark Results
bench_results

# Node Modules (Portal DBTypeScript SDK)
node_modules

# JWKS file
jwks.json
Empty file added deleteme.md
Empty file.
78 changes: 47 additions & 31 deletions portal-db/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,7 @@ GREEN := \033[0;32m

JWT_ROLE ?= portal_db_admin
JWT_EMAIL ?= john@doe.com

SECOND_GOAL := $(word 2,$(MAKECMDGOALS))

ifeq ($(SECOND_GOAL),admin)
JWT_ROLE := portal_db_admin
endif

ifeq ($(SECOND_GOAL),reader)
JWT_ROLE := portal_db_reader
endif

ifdef ROLE
JWT_ROLE := $(ROLE)
endif

ifdef EMAIL
JWT_EMAIL := $(EMAIL)
endif
JWT_EXPIRES ?= 1h

# ============================================================================
# HELP
Expand All @@ -58,7 +41,7 @@ help: ## Show all available targets
@grep -h -E '^(postgrest-hydrate-testdata|hydrate-services|hydrate-applications|hydrate-gateways|hydrate-prod|dehydrate-reset-db):.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "$(CYAN)%-30s$(RESET) %s\n", $$1, $$2}'
@echo ""
@echo "$(BOLD)=== 🔐 Authentication & Testing ===$(RESET)"
@grep -h -E '^(test-postgrest-auth|test-postgrest-portal-app-creation|postgrest-gen-jwt):.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "$(CYAN)%-30s$(RESET) %s\n", $$1, $$2}'
@grep -h -E '^(test-postgrest-auth|test-postgrest-portal-app-creation|postgrest-gen-jwt|grove-download-jwks|check-jwks):.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "$(CYAN)%-30s$(RESET) %s\n", $$1, $$2}'
@echo ""
@echo "$(BOLD)=== 📝 API Generation ===$(RESET)"
@grep -h -E '^(postgrest-generate-openapi|postgrest-swagger-ui|postgrest-generate-sdks|postgrest-generate-all):.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "$(CYAN)%-30s$(RESET) %s\n", $$1, $$2}'
Expand All @@ -81,8 +64,10 @@ quickstart: ## Quick start guide for Portal DB (shows commands to run)
@echo "$(CYAN) make postgrest-hydrate-testdata$(RESET)"
@echo ""
@echo "$(BOLD)Step 3: Generate JWT tokens$(RESET)"
@echo "$(CYAN) make postgrest-gen-jwt admin$(RESET)"
@echo " # Optionally, generate reader token: $(CYAN)make postgrest-gen-jwt reader$(RESET)"
@echo "$(CYAN) make postgrest-gen-jwt$(RESET) # Defaults to admin role"
@echo " # Optionally, generate reader token: $(CYAN)JWT_ROLE=reader make postgrest-gen-jwt$(RESET)"
@echo " # With custom email: $(CYAN)JWT_EMAIL=user@example.com make postgrest-gen-jwt$(RESET)"
@echo " # With custom expiry: $(CYAN)JWT_EXPIRES=24h make postgrest-gen-jwt$(RESET)"
@echo ""
@echo "$(BOLD)Step 4: Export JWT token$(RESET)"
@echo "$(YELLOW) # Copy the export commands from the previous output$(RESET)"
Expand Down Expand Up @@ -128,7 +113,7 @@ quickstart: ## Quick start guide for Portal DB (shows commands to run)
# ============================================================================

.PHONY: portal-db-up
portal-db-up: ## Start all Portal DB services
portal-db-up: check-jwks ## Start all Portal DB services
@echo "🚀 Starting all Portal DB services..."
docker compose up -d
@echo "✅ Services started!"
Expand All @@ -137,6 +122,14 @@ portal-db-up: ## Start all Portal DB services
@echo ""
@echo "🔧 You can connect to the database using:"
@echo " $(CYAN)psql 'postgresql://postgres:portal_password@localhost:5435/portal_db'$(RESET)"
@echo ""
@echo "🔐 To test the API with authentication:"
@echo " 1. Generate a JWT token:"
@echo " $(CYAN)make postgrest-gen-jwt$(RESET)"
@echo " 2. Export the token (copy from output above):"
@echo " $(CYAN)export POSTGREST_JWT_TOKEN=\"eyJ...\"$(RESET)"
@echo " 3. Make an authenticated request:"
@echo " $(CYAN)curl -H \"Authorization: Bearer \$$POSTGREST_JWT_TOKEN\" \"http://localhost:3000/networks\" | jq '.'$(RESET)"

.PHONY: portal-db-down
portal-db-down: ## Stop all Portal DB services
Expand Down Expand Up @@ -166,6 +159,11 @@ postgrest-swagger-ui: postgrest-generate-openapi ## Start Swagger UI to view Ope
-v $(PWD)/api/openapi/openapi.json:/openapi.json:ro \
swaggerapi/swagger-ui

.PHONY: postgrest-generate-sdks
postgrest-generate-sdks: postgrest-generate-openapi ## Generate Go and TypeScript SDKs from OpenAPI specification
@echo "🔧 Generating Go and TypeScript SDKs from OpenAPI specification..."
cd api/codegen && ./generate-sdks.sh

# ============================================================================
# AUTHENTICATION & TESTING
# ============================================================================
Expand Down Expand Up @@ -198,16 +196,34 @@ test-postgrest-portal-app-creation: _require-postgrest-api ## Test portal applic
cd api/scripts && ./test-postgrest-portal-app-creation.sh

.PHONY: postgrest-gen-jwt
postgrest-gen-jwt: _require-postgrest-api ## Generate JWT token
postgrest-gen-jwt: _require-postgrest-api ## Generate JWT token (use JWT_ROLE=admin JWT_EMAIL=user@example.com JWT_EXPIRES=24h to customize)
@echo "🔑 Generating JWT token..."
cd api/scripts && ./postgrest-gen-jwt.sh $(JWT_ROLE) $(JWT_EMAIL)

.PHONY: admin reader
admin:
@:

reader:
@:
cd api/scripts && ./postgrest-gen-jwt.sh --role $(JWT_ROLE) --email $(JWT_EMAIL) --expires $(JWT_EXPIRES)

.PHONY: grove-download-jwks
grove-download-jwks: ## 🌿 GROVE EMPLOYEES ONLY - Download JWKS from 1Password to api/jwks.json
@echo "🌿 $(BOLD)GROVE EMPLOYEES ONLY$(RESET)"
@echo "🔐 Downloading JWKS from 1Password..."
@if ! command -v op >/dev/null 2>&1; then \
echo "❌ 1Password CLI (op) is not installed. Please install it first:"; \
echo " brew install 1password-cli"; \
exit 1; \
fi
@op item get 6hh3pjg7m2qdpkg26kf6gzmjs4 --format json | jq -r '.fields[] | select(.id == "notesPlain") | .value' | jq '.' > api/jwks.json
@echo "✅ JWKS downloaded to api/jwks.json"

.PHONY: check-jwks
check-jwks: ## Check if api/jwks.json exists, show instructions if missing
@if [ ! -f api/jwks.json ]; then \
echo "❌ $(YELLOW)api/jwks.json not found!$(RESET)"; \
echo ""; \
echo "$(BOLD)🌿 GROVE EMPLOYEES: Download the JWKS file from 1Password:$(RESET)"; \
echo " $(CYAN)make grove-download-jwks$(RESET)"; \
echo ""; \
echo "$(YELLOW)Note: Requires 1Password CLI (op) to be installed and authenticated.$(RESET)"; \
exit 1; \
fi
@echo "✅ api/jwks.json found"

# ============================================================================
# DATA HYDRATION
Expand Down
78 changes: 63 additions & 15 deletions portal-db/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ PostgREST automatically generates a REST API from the PostgreSQL database schema
- [Walkthrough](#walkthrough)
- [Authentication](#authentication)
- [Auth Summary](#auth-summary)
- [Database Roles Roles](#database-roles-roles)
- [JWKS Configuration](#jwks-configuration)
- [Database Roles](#database-roles)
- [Row-Level Security (RLS)](#row-level-security-rls)
- [Testing auth locally](#testing-auth-locally)
- [JWT Generation](#jwt-generation)
- [JWT Generation (Backend/Internal Use)](#jwt-generation-backendinternal-use)
- [How it Works](#how-it-works)
- [📚 Resources](#-resources)

Expand Down Expand Up @@ -65,22 +67,57 @@ make postgrest-swagger-ui

## Authentication

The PostgREST API is authenticated via the SQL migration in [002_postgrest_init.sql](../schema/002_postgrest_init.sql).
The PostgREST API uses **dual JWT authentication** to support both internal services and frontend users.

### Auth Summary

1. SSL Certs to connect to the DB
2. JWT to authenticate into the DB as a `portal_db_*` user
3. Top level roles authenticated into the DB subject to RLS (e.g. `portal_db_admin` or `portal_db_reader`)
4. Portal Application roles defined within the tables (see `rbac` of each table)
PostgREST authenticates requests using JWTs (JSON Web Tokens) with two different signing methods:

### Database Roles Roles
1. **Backend/Internal JWTs (HS256)** - For backend services and engineers
- Algorithm: HMAC-SHA256 (symmetric key)
- Use case: Internal tools, backend services, admin operations
- Roles: `portal_db_admin`, `portal_db_reader`
- Generated locally via `make postgrest-gen-jwt`

2. **Auth0 User JWTs (RS256)** - For frontend users
- Algorithm: RSA-SHA256 (asymmetric key)
- Use case: Frontend application users authenticated via Auth0
- Role: `authenticated_user` (with user-scoped RLS policies)
- Generated by Auth0 at https://auth.grove.city/

Both JWT types are verified using a **JWKS (JSON Web Key Set)** file that contains:
- HS256 symmetric key for backend JWTs (no `kid` in header)
- RS256 public key from Auth0 (with `kid="1a2b3c4d5e6f7g8h9i0j"` in header)

### JWKS Configuration

The JWKS file (`api/jwks.json`) is **gitignored** and contains sensitive key material.

**🌿 GROVE EMPLOYEES ONLY:**
```bash
# Download JWKS from 1Password (requires 1Password CLI)
make grove-download-jwks
```

This command fetches the production JWKS file containing both the internal HS256 secret and the Auth0 RS256 public key.

### Database Roles

- `authenticator` - "Chameleon" role used exclusively by PostgREST for JWT authentication (no direct API access)
- `portal_db_admin` - JWT-backed role with read/write access (subject to RLS)
- `portal_db_reader` - JWT-backed role with read-only access (subject to RLS)
- `portal_db_admin` - JWT-backed role with full read/write access (backend services)
- `portal_db_reader` - JWT-backed role with read-only access (backend services)
- `authenticated_user` - JWT-backed role for Auth0 users with user-scoped RLS policies (see [004_grove_portal_access.sql](../schema/004_grove_portal_access.sql))
- `anon` - Default unauthenticated role with no privileges

### Row-Level Security (RLS)

Frontend users authenticated via Auth0 are subject to **Row-Level Security** policies:
- Users can only access `portal_accounts` and `portal_applications` they have explicit RBAC permissions for
- Permission levels: `legacy_read` (SELECT) and `legacy_write` (INSERT/UPDATE/DELETE)
- Unauthorized access returns an empty array `[]` (by design, to prevent information leakage)

See [004_grove_portal_access.sql](../schema/004_grove_portal_access.sql) for RLS policy details.

### Testing auth locally

Run `make` from the `portal-db` directory shows the following scripts which can be used to test things locally:
Expand All @@ -92,16 +129,27 @@ test-postgrest-portal-app-creation Test portal application creation and retrieva
postgrest-gen-jwt Generate JWT token
```

### JWT Generation
### JWT Generation (Backend/Internal Use)

The `postgrest-gen-jwt` script generates HS256 JWTs for backend services and internal development:

```bash
# Admin JWT
make postgrest-gen-jwt admin
# Generate admin JWT (full read/write access)
make postgrest-gen-jwt

# Reader JWT
make postgrest-gen-jwt reader
# Generate reader JWT (read-only access)
JWT_ROLE=portal_db_reader make postgrest-gen-jwt

# Customize email, expiration
JWT_EMAIL=user@example.com JWT_EXPIRES=24h make postgrest-gen-jwt

# Token-only output (for scripting)
export POSTGREST_JWT_TOKEN=$(cd api/scripts && ./postgrest-gen-jwt.sh --token-only)
curl -H "Authorization: Bearer $POSTGREST_JWT_TOKEN" http://localhost:3000/networks
```

See `./api/scripts/postgrest-gen-jwt.sh --help` for full documentation.

## How it Works

**PostgREST** introspects PostgreSQL schema and auto-generates REST endpoints:
Expand Down
10 changes: 10 additions & 0 deletions portal-db/api/codegen/codegen-client.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json
package: portaldb
output: ../../sdk/go/client.go
generate:
client: true
models: false
embedded-spec: true
output-options:
skip-fmt: false
skip-prune: false
9 changes: 9 additions & 0 deletions portal-db/api/codegen/codegen-models.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json
package: portaldb
output: ../../sdk/go/models.go
generate:
models: true
embedded-spec: false
output-options:
skip-fmt: false
skip-prune: false
39 changes: 36 additions & 3 deletions portal-db/api/codegen/generate-openapi.sh
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,43 @@ while [ $attempt -le $max_attempts ]; do
((attempt++))
done

# Fetch OpenAPI specification
# Fetch OpenAPI specification (Swagger 2.0 format from PostgREST)
echo -e "${BLUE}📥 Fetching OpenAPI specification...${RESET}"
if curl -s -f -H "Accept: application/openapi+json" ${AUTH_HEADER:+-H "$AUTH_HEADER"} "$POSTGREST_URL/" -o "$OUTPUT_FILE"; then
echo -e "${GREEN}✅ OpenAPI specification saved to: ${CYAN}$OUTPUT_FILE${RESET}"
SWAGGER_FILE="${OUTPUT_FILE%.json}-swagger.json"

if curl -s -f -H "Accept: application/openapi+json" ${AUTH_HEADER:+-H "$AUTH_HEADER"} "$POSTGREST_URL/" -o "$SWAGGER_FILE"; then
echo -e "${GREEN}✅ Swagger 2.0 specification fetched${RESET}"

# Convert Swagger 2.0 to OpenAPI 3.x
echo -e "${BLUE}🔄 Converting Swagger 2.0 to OpenAPI 3.x...${RESET}"

# Check if swagger2openapi is available
if ! command -v swagger2openapi >/dev/null 2>&1; then
echo -e "${BLUE}📦 Installing swagger2openapi converter...${RESET}"
if command -v npm >/dev/null 2>&1; then
npm install -g swagger2openapi
else
echo -e "${RED}❌ npm not found. Please install Node.js and npm first.${RESET}"
echo " - Mac: brew install node"
echo " - Or download from: https://nodejs.org/"
exit 1
fi
fi

if ! swagger2openapi "$SWAGGER_FILE" -o "$OUTPUT_FILE"; then
echo -e "${RED}❌ Failed to convert Swagger 2.0 to OpenAPI 3.x${RESET}"
exit 1
fi

# Fix boolean format issues in the converted spec
echo -e "${BLUE}🔧 Fixing boolean format issues...${RESET}"
sed -i.bak 's/"format": "boolean",//g' "$OUTPUT_FILE"
rm -f "${OUTPUT_FILE}.bak"

# Clean up temporary Swagger file
rm -f "$SWAGGER_FILE"

echo -e "${GREEN}✅ OpenAPI 3.x specification saved to: ${CYAN}$OUTPUT_FILE${RESET}"

# Pretty print the JSON
if command -v jq >/dev/null 2>&1; then
Expand Down
Loading