Skip to content

refactor: simplify domain types and rename to constructiveInternalType*#40

Open
pyramation wants to merge 8 commits intomainfrom
feat/simplify-types
Open

refactor: simplify domain types and rename to constructiveInternalType*#40
pyramation wants to merge 8 commits intomainfrom
feat/simplify-types

Conversation

@pyramation
Copy link
Contributor

@pyramation pyramation commented Feb 5, 2026

Summary

This PR simplifies PostgreSQL domain type validation and renames smart comments from pgpmInternalType* to constructiveInternalType* for GraphQL schema generation. The philosophy shift moves strict regex validation to the application layer, keeping only minimal structural checks at the database level.

Changes:

  • Removed: single_select and multiple_select domain types (no longer needed)
  • Simplified validation:
    • email: citext with no regex (was complex email regex)
    • hostname: plain text (was hostname regex)
    • attachment: plain text (was URL regex)
    • url: lenient regex ^https?://[^\s]+$ - requires http/https prefix, no whitespace, paths allowed
    • origin: strict regex ^https?://[^/\s]+$ - protocol + host only, no paths (for CORS security)
    • image: jsonb requiring only url key (was url + mime + URL regex)
    • upload: jsonb requiring url OR id OR key (was url + mime + URL regex)
  • Version bump: 0.15.3 → 0.16.0

Updates since last revision

  • Differentiated url vs origin validation: The origin domain now uses ^https?://[^/\s]+$ which disallows paths (e.g., rejects https://example.com/path). This is important for CORS security where origins must be protocol + host only. The url domain still allows paths with ^https?://[^\s]+$.
  • Added comprehensive tests for origin domain that verify paths, query strings, and fragments are rejected
  • Updated test file to include separate validOrigins and invalidOrigins test cases

Review & Testing Checklist for Human

  • Verify origin regex for CORS security - The origin domain now rejects paths like https://example.com/path. Confirm this aligns with your CORS requirements
  • Confirm removal of single_select and multiple_select won't break downstream consumers
  • Test that existing valid data still works with the loosened constraints
  • Coordinate merge with constructive-db#469 (constructive#688 already merged)

Recommended test plan:

  1. Run the test suite to verify both url and origin validation work correctly
  2. Test origin domain rejects: https://example.com/, https://example.com/path, https://example.com?query
  3. Test origin domain accepts: https://example.com, http://localhost:3000, https://api.example.com:8080

Notes

This is part of a coordinated change across three repos (pgpm-modules, constructive-db, constructive) to align domain type definitions.

Requested by: Dan Lynch (@pyramation)
Link to Devin run: https://app.devin.ai/sessions/ba45b64c92bb4307bf9e920123213d9e

- Remove single_select and multiple_select domain types
- Simplify validation:
  - email: citext (no regex)
  - hostname: text (no regex)
  - url: text with simple http/https prefix check
  - origin: text with simple http/https prefix check
  - attachment: text (no regex)
  - image: jsonb requiring only 'url' key
  - upload: jsonb requiring url OR id OR key
- Rename all smart comments from pgpmInternalType* to constructiveInternalType*
- Version bump to 0.16.0
@devin-ai-integration
Copy link

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Per user feedback: 'For origin or URL, you don't want to do any check at all'
- url: plain text (no CHECK constraint)
- origin: plain text (no CHECK constraint)
Tests now reflect the new validation philosophy:
- url, hostname, attachment, email: accept any text (no validation)
- image: jsonb requiring only 'url' key
- upload: jsonb requiring 'url' OR 'id' OR 'key'
URL validation: ^https?://[^\s]+$
- Must start with http:// or https://
- Must have at least one character after protocol
- No whitespace allowed

This is lenient enough to never reject valid URLs while catching obvious mistakes.
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

BEGIN;
CREATE DOMAIN origin AS text CHECK (VALUE = substring(VALUE from '^(https?://[^/]*)'));
COMMENT ON DOMAIN origin IS E'@name pgpmInternalTypeOrigin';
CREATE DOMAIN origin AS text CHECK (value ~ '^https?://[^\s]+$');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Origin domain validation allows paths, breaking CORS/security semantics

The origin domain validation was changed from VALUE = substring(VALUE from '^(https?://[^/]*)') to value ~ '^https?://[^\s]+$'. The old validation ensured values were exactly an "origin" (protocol + host only, no path). The new regex allows paths like https://example.com/malicious/path to pass validation.

Root Cause and Impact

The original check VALUE = substring(VALUE from '^(https?://[^/]*)') validated that the value equals its own extracted origin - meaning only https://example.com would pass, not https://example.com/path.

The new regex ^https?://[^\s]+$ only checks that the string starts with http/https and contains no whitespace, allowing any URL with paths.

This domain is used for CORS validation and origin-based access control as seen in packages/jwt-claims/deploy/schemas/ctx/procedures/origin.sql:9:

-- Used for CORS validation and origin-based access control
CREATE FUNCTION ctx.origin()
  RETURNS origin

Impact: Security-sensitive code relying on the origin type to enforce that only protocol+host values are stored will now accept arbitrary URL paths, potentially breaking CORS policies or origin-based access control.

Suggested change
CREATE DOMAIN origin AS text CHECK (value ~ '^https?://[^\s]+$');
CREATE DOMAIN origin AS text CHECK (value ~ '^https?://[^/\s]+$' AND value !~ '/');
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

- Changed origin regex from ^https?://[^\s]+$ to ^https?://[^/\s]+$
- Origin must be protocol + host only (no paths, query strings, or fragments)
- Added tests for valid/invalid origins
- URL domain still allows paths (lenient regex unchanged)
- image: jsonb url field must start with http/https
- upload: url field (when present) must start with http/https
- attachment: same lenient regex as url domain (^https?://[^\s]+$)
- Updated tests to verify new validation rules
- Update image/upload test data to use valid http/https URLs
- Update attachment tests to verify http/https validation
- Sync test expectations with new domain constraints
- image: add jsonb_typeof object check, url string validation, optional bucket/provider/mime
- upload: add jsonb_typeof object check, string validation for id/key, optional bucket/provider/mime
- email: add @ symbol check (must contain @)
- hostname: add no-whitespace check (^[^\s]+$)
- Update tests to cover new validation rules including optional fields
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant