Commit Graph

1816 Commits

Author SHA1 Message Date
Émile Ré
a92690cdc3 Remove unused pkg/equal package
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-02 19:48:30 +04:00
Émile Ré
9dedeeaa42 Skip draft version for uncategorised-only merges
Pattern merges and adoptions that stay entirely within the
uncategorised category never change the visitor-facing snapshot.
Only trigger ensureDraftVersionForBanner when a merge group
belongs to a consent category.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-02 19:31:53 +04:00
Émile Ré
9d4af4d73e Validate cookie policy link in banner description
Translations are no longer part of the version snapshot, so the
banner_description text must be validated at write time to ensure
the {{cookie_policy_link}} placeholder is present. Without it
the cookie policy URL silently disappears from the rendered banner.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-02 19:09:16 +04:00
Émile Ré
33940d63ca Run cookie detection regardless of banner state
Decouple detection from the banner's active state so admins
can discover cookies before making the banner visible. The
client now starts the detector even when the config endpoint
returns 404, and the detector stops itself if the report
endpoint returns 404 (wrong or deleted banner ID).

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-02 19:01:13 +04:00
Émile Ré
478ccf6785 Exclude uncategorised category from consent contract
The uncategorised category is an admin-side inbox for detected
cookies and should not be part of the visitor-facing consent
contract. Filter it out of snapshots so changes to uncategorised
patterns no longer trigger version bumps.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-02 18:33:07 +04:00
Émile Ré
887453fee2 Exclude translations from cookie banner version snapshots
Translation changes are cosmetic, not consent-contract changes,
so they should not trigger a version bump. Translations are now
loaded live from the database at serve time instead of being
frozen in the snapshot.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-02 18:10:18 +04:00
Émile Ré
685ff91d74 Extract snapshot functions into snapshot.go
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-02 17:49:19 +04:00
Émile Ré
e21388fa0c Stop bumping cookie banner version on no-op updates
A central snapshot-equality guard in ensureDraftVersion now returns
the latest version unchanged when the candidate snapshot matches it,
so no-op admin saves no longer force visitors to re-consent. Per-
operation short-circuits in UpdateCookieBanner, UpdateCookieCategory,
UpdateCookiePattern, DeleteCookiePattern, MoveCookiePatternToCategory,
ReorderCookieCategory, and UpsertCookieBannerTranslation skip the row
update and version bump when nothing visitor-facing changes (excluded
patterns, identical values, identical translation JSON).

Rank is now treated as admin-only metadata: buildSnapshot sorts
categories by (Kind weight, ID byte order) instead of relying on the
implicit rank-driven slice order, and ReorderCookieCategory no longer
calls ensureDraftVersionForBanner. Default banners keep their
visitor-facing order (insertion order matches Kind+ID); banners with
admin-customised ranks see a one-time order shift to insertion order
on the next snapshot rebuild.

Reusable equality helpers (Ptr generic + JSON canonicalisation) move
to a new pkg/equal package; snapshotsEqual stays in service.go as the
documented chokepoint for visitor-identical snapshot comparison.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-02 17:42:06 +04:00
Émile Ré
dd1d0ebffb Sync excluded field to MCP and CLI
Add excluded boolean to the MCP CookiePattern schema and
UpdateCookiePatternInput. Expose it in CLI cookie-pattern
view, list, and update commands.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-01 16:48:55 +04:00
Émile Ré
3ed61c6084 Add excluded field to cookie pattern GraphQL schema
Expose excluded on the CookiePattern type and accept it as
an optional input on UpdateCookiePatternInput so the console
can toggle pattern exclusion inline.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-01 16:39:33 +04:00
Émile Ré
bfc0ded464 Exclude excluded patterns from merge and adoption
Filter excluded and non-exact patterns in SQL when loading
patterns for the pattern analysis worker. Both merge group
building and uncategorised adoption only see non-excluded
exact patterns, so excluded patterns are preserved as
punch-out overrides.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-01 16:39:33 +04:00
Émile Ré
dc3c2701ac Skip excluded patterns in detection and snapshots
In ReportDetectedCookies, silently skip cookies that match an
excluded pattern instead of recording them. Filter excluded
patterns in SQL when building version snapshots so they never
appear in the published banner config. Add Excluded field to
UpdateCookiePatternRequest.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-01 16:39:33 +04:00
Émile Ré
2192c854a0 Add excluded flag to cookie pattern model
Adds an `excluded` boolean column to the cookie_patterns table
so operators can mark patterns to be omitted from the consent
banner without deleting them. Includes the migration, struct
field, updated SQL queries, and filter support.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-01 16:39:32 +04:00
Émile Ré
e7c2118b7b Run gofmt on cookie-banner CLI commands
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-01 16:07:22 +04:00
Émile Ré
d133d5ba1d Fix PR review comments and rename versions to latest-version
- Validate __typename in all list command pagination callbacks
- Serialize PostHogConsent=false as explicit false (not nil)
- Fix maxAgeSeconds factory to only include when value is an int
- Rename cookie-banner versions to latest-version subcommand

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-01 15:38:43 +04:00
Émile Ré
08aaab1393 Add CLI commands for cookie banner, category, pattern, and consent records
Expose prb cookie-banner (10 subcommands), prb cookie-category (6),
prb cookie-pattern (6), and prb consent-record (2) with full CRUD,
lifecycle operations, pagination, and interactive prompts.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-01 15:19:21 +04:00
Émile Ré
7bc8343c18 Add MCP tools for cookie banner, category, pattern, version, and consent
Wire cookiebanner.Service into the MCP resolver and expose 24 tools
covering full CRUD, activation, versioning, translations, and consent
record queries with pagination and filtering support.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-01 15:19:21 +04:00
Cursor Agent
8c74a7bc93 Fix auditor processing activity access
Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Émile Ré <nemile.re@gmail.com>
2026-05-01 15:18:23 +04:00
Bryan Frimin
febf4a930e Treat OIDC and magic link sessions as password-equivalent when assuming an org
Users authenticated via Google/Microsoft OIDC or magic link previously
relied on a fall-through in the password-only org check. Make the rule
explicit so SSO-only users can access password-only organizations
without being bounced to the password login form they cannot satisfy.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
2026-04-30 17:24:04 +04:00
Émile Ré
edcb5ba9c7 Add CookiePatternFilter to push adoption filtering to SQL
The adoptUncategorisedPatterns method loaded all patterns for a
banner then filtered in Go. This adds a CookiePatternFilter
(match_type + cookie_category_id) and wires it into
LoadAllByCookieBannerID so the two targeted loads only fetch
the rows they need.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-30 12:28:31 +04:00
Émile Ré
33abbfbb8f Link detected cookies to existing patterns on conflict
When a cookie pattern already exists for a banner, the insert
conflict caused a continue that silently dropped the detected
cookie instead of linking it to the existing pattern. Load the
existing pattern to obtain its ID and proceed with cookie
insertion.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-30 11:46:13 +04:00
Émile Ré
426066e078 Use CookieBanner entity directly in pattern analysis worker
Replace the ad-hoc patternAnalysisTask struct with coredata.CookieBanner
as the worker type parameter, matching the pattern used by other workers
(esign, accessreview). SQL methods move back to coredata on *CookieBanner.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-30 11:46:12 +04:00
Émile Ré
e5e1119883 Remove dead code from cookiebanner and coredata packages
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-30 11:46:12 +04:00
Émile Ré
6482361352 Fix aborted transaction on duplicate cookie pattern insert
Use InsertIfNotExists instead of Insert with error check, since
a unique-violation aborts the PostgreSQL transaction even when caught.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-30 11:46:12 +04:00
Émile Ré
427bbbaf5c Move DurationInput to @probo/ui and scope pattern merge by category
Move the DurationInput component from the console app into @probo/ui
for reuse, add duration formatting helpers to @probo/helpers, and
update pattern merge to group by both category ID and prefix.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-30 11:46:12 +04:00
Émile Ré
2fe77d9ddc Fix review issues in cookie pattern handling
- Fix DurationInput fallback unit from "minutes" to "seconds" and add
  seconds as a selectable unit to prevent silent duration inflation
- Use parseFloat instead of parseInt for duration input to preserve
  fractional values
- Scope prefix merge groups by category ID to prevent cross-category
  merging
- Relink cookies and delete exact patterns even when prefix pattern
  already exists
- Prefer exact matches and longest prefix in pattern selection query
- Fix wrong error type in GetCookiePattern (ErrCookiePatternNotFound)
- Handle singular/plural in humanizeSeconds fallback branch

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-30 11:46:11 +04:00
Émile Ré
5cdaddf8b1 Store cookie durations as max_age_seconds
Replace the free-form duration TEXT column with a nullable
max_age_seconds INTEGER on both cookies and cookie_patterns
tables. The SDK detector now sends raw seconds instead of
humanized strings, eliminating locale-dependent comparisons
in the pattern merge worker. Humanization happens at display
time in the widget and console UI.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-30 11:46:11 +04:00
Émile Ré
b8ff3c66e1 Improve cookie pattern merge with longest prefix
The previous algorithm split on the first separator only, producing
overly broad prefixes (e.g. ph_ for ph_phc_abc123). Replace with a
greedy longest-shared-prefix approach that finds the longest
separator-boundary prefix shared by 3+ exact patterns, avoiding
false merges across unrelated cookie families.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-30 11:46:11 +04:00
Émile Ré
94ccc71357 Switch console cookies page to cookie patterns
The console now manages CookiePattern entities instead of raw
Cookie rows. The frontend queries cookiePatterns on each category
and uses createCookiePattern, updateCookiePattern,
deleteCookiePattern, and moveCookiePatternToCategory mutations.

The entire Cookie GraphQL surface (type, connection, mutations,
inputs, payloads, resolvers, Go types) is removed since the
backing struct already lost description and cookieCategoryID.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-30 11:46:11 +04:00
Émile Ré
22fd103795 Backfill pattern analysis flag on existing banners
Set pattern_analysis_requested_at on all existing banners so the worker
runs once per banner on release and merges any existing prefix groups.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-30 11:46:11 +04:00
Émile Ré
7caa098941 Add cookie pattern analysis worker for prefix auto-detection
Background worker polls cookie_banners with pattern_analysis_requested_at
set, groups EXACT patterns sharing a common prefix, and merges groups of
3+ into a PREFIX pattern. Detection sets the flag when new EXACT patterns
are created. The worker relinks cookies, removes orphaned patterns, and
updates the draft version via ensureDraftVersionForBanner.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-30 11:46:10 +04:00
Émile Ré
9c402058ec Add GraphQL API and service CRUD for cookie patterns
Add CookiePattern type, connection, and mutations to the GraphQL schema
with full resolver implementations. Add service methods for pattern
CRUD, category movement, listing, and counting. This enables the console
to manage cookie patterns instead of individual cookies.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-30 11:46:10 +04:00
Émile Ré
f9fec45eb1 Add cookie pattern entity to group detected cookies
Introduce a cookie_patterns table that groups cookies sharing a common
prefix (e.g. phc_*) into a single manageable row. Every cookie now
belongs to a pattern (EXACT or PREFIX match type). Category, description,
and display metadata move from cookies to patterns, making patterns the
unit of management and display in the console and published snapshots.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-30 11:46:10 +04:00
Émile Ré
48606f34c1 Filter browser-extension cookies from detection
Cookies set by browser extensions are not the website operator's
compliance responsibility. This adds stack-trace inspection to
filter out extension-originated document.cookie writes, and
annotates pre-existing cookies with a source field so operators
can triage them separately.

Introduces a CookieSource enum (SCRIPT / PRE_EXISTING) across
the full stack: PostgreSQL, coredata, service, HTTP handler, and
GraphQL schema. On conflict, source is upgraded from PRE_EXISTING
to SCRIPT when a page script is later observed setting the cookie.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-30 11:46:10 +04:00
Sacha Al Himdani
8e2227847e Fix empty text nodes in generated documents
Helpers feeding ProseMirror JSON templates under pkg/probo/templates
could return empty strings, producing { "type": "text", "text": "" }
nodes that Tiptap rejects with "Empty text nodes are not allowed" when
the document is loaded into the editor. The backend HTML/PDF export
silently tolerates them, so the issue only surfaces in the UI.

Route every enum format helper's default branch through
stringOrNotSpecified (formatClassification, formatAssetType,
formatFinding{Kind,Status,Priority}, formatObligation{Status,Type},
formatVendorCategory, formatDataSensitivity, formatBusinessImpact,
formatProcessingActivityRole, formatLawfulBasis,
formatSpecialOrCriminalData, formatTransferSafeguard, formatDPIANeeded,
formatTIANeeded, formatResidualRisk), wrap asset DataTypesStored, and
treat an empty FullName as a missing profile in every owner/DPO lookup
(lookupProfileName plus the inline profileMap reads in data, asset,
finding, obligation and processing-activity builders).

Also tweak the vendor list template: insert a horizontalRule after each
record so they paginate cleanly, rename the trailing section to
"Definitions", and drop the now-redundant "3.1 Lexicon" subheading.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-04-29 17:59:07 +02:00
Sacha Al Himdani
c026f67bd9 Add vendor publish to document system
Replace the old snapshot-based system for vendors with the publish
document system, mirroring the prior processing activity / DPIA / TIA
migration. Includes the GraphQL mutation, MCP tool, CLI command, n8n
operation, frontend publish dialog, e2e tests, and a prosemirror
register template covering vendor profile fields plus per-vendor
sections for services, contacts, risk assessments, compliance reports,
BAA and DPA agreements.

The vendor register lives as a generated DocumentTypeRegister document
on the organization, reused across publishes (the major version bumps
on every republish). Approvers can be passed in to create a draft
pending approval; otherwise the version is published immediately. The
frontend Vendors page exposes a Publish button and a Document link
button when the document exists, and pre-fills the previous default
approvers.

Remove snapshot mode entirely from vendors and their sub-entities: drop
snapshotId/sourceId from GraphQL Vendor type and VendorFilter; remove
SnapshotsTypeVendors from the snapshot registry and delete
Vendors.Snapshot, VendorSnapshotter interface and all
*.InsertVendorSnapshots methods on contacts, services, risk
assessments, compliance reports, BAA and DPA. Drop the snapshot routes
and banner from the frontend. The snapshot_id columns remain in the
database but are now filtered out with snapshot_id IS NULL.

Add Get/Upsert/Clear GeneratedDocumentID methods on Vendor backed by a
new vendors_document_id column on generated_documents, matching the
ProcessingActivity/Finding/Obligation pattern.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-04-29 16:24:29 +02:00
Bryan Frimin
79d4730806 Drop IntrospectToken doc comment
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
2026-04-29 17:59:11 +04:00
Bryan Frimin
2418079785 Introspect OAuth2 refresh tokens
RFC 7662 lets clients introspect any OAuth2 token, but the endpoint
only resolved access tokens. Look up refresh tokens too, honor the
optional token_type_hint to drive lookup order with a fallback to the
other table, and report revoked or expired refresh tokens as inactive.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
2026-04-29 17:59:09 +04:00
Bryan Frimin
62f05b3ff2 Invalidate sessions on password change and reset
Session validity was decoupled from credential rotation: changing
a password (logged-in flow) or completing a forgot-password reset
left every existing iam_sessions row valid until its idle TTL.
A user who saw their account compromised on another device had
no way to actually evict that device by rotating the password.

Inside the same DB transaction as the password update, expire the
identity's other active sessions:

- ChangePassword keeps the caller's current session and revokes
  every other session for the identity, so the user is not
  logged out of the browser they just used.
- ResetPassword has no caller session (the user is anonymous,
  authenticated only by a stateless token) and revokes all of
  the identity's active sessions.

The session middleware already rejects rows with expire_reason
set, so revoked sessions are kicked out on the next request
without any middleware change.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
2026-04-29 17:57:19 +04:00
Bryan Frimin
34d8bf10a2 Use forwarded headers for SCIM event client IP
The SCIM event handler read the client address from
r.RemoteAddr only, so when probod runs behind an HTTP
load balancer every event row recorded the load
balancer's internal IP. Switch to clientip.Extract,
which honors the Forwarded / X-Forwarded-For headers
already validated by trustedproxy middleware.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
2026-04-29 17:56:26 +04:00
Émile Ré
9920093c22 Use rightmost IP from forwarded headers
A client can prepend a spoofed entry to X-Forwarded-For before
the request reaches our load balancer. Taking the first value
would return the attacker's address. Since we sit behind a
single trusted LB that appends the real client IP as the last
entry, switch to rightmost extraction for both X-Forwarded-For
and RFC 7239 Forwarded headers.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-29 14:16:37 +04:00
Émile Ré
f0ebfe67fa Review fixes
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-29 12:48:44 +04:00
Émile Ré
dbc5827357 Fix node_modules install + optimize go install by mounting go mod folder into VM
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-29 12:48:44 +04:00
Émile Ré
c043a7f1f5 Extract config structs into pkg/probodconfig
probod-bootstrap only needs the config struct definitions for
YAML marshaling but transitively pulled in ~40 heavy runtime
dependencies via pkg/probod. Move all config types and their
methods to a new pkg/probodconfig package and re-export them
from pkg/probod via type aliases for backward compatibility.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-29 12:48:43 +04:00
Sacha Al Himdani
ba8bce2ad3 Add processing activity, DPIA and TIA publish to document system
Replace the old PDF/snapshot-based exports for processing activities,
Data Protection Impact Assessments and Transfer Impact Assessments with
the publish document system. Includes GraphQL mutations, MCP tools, CLI
commands, n8n operations, frontend publish dialogs, e2e tests, and
prosemirror register templates that mirror the previous PDF layouts.

Each register lives as a generated DocumentTypeRegister document on the
organization, reused across publishes (the major version bumps on every
republish). Approvers can be passed in to create a draft pending
approval; otherwise the version is published immediately. The frontend
ProcessingActivities page exposes a Publish dropdown per register and a
Document link button per active tab, pre-fills the previous default
approvers, and navigates to the published document on success.

Remove snapshot mode entirely from these three entities: drop snapshotId
and sourceId from GraphQL schemas, types, filters, resolvers, MCP spec,
frontend routes and pages; remove SnapshotsTypeProcessingActivities from
the snapshot registry and delete the ProcessingActivities.Snapshot,
ProcessingActivitySnapshotter interface and *.InsertProcessingActivitySnapshots
methods. The snapshot_id columns remain in the database but are now
filtered out with snapshot_id IS NULL.

Add Get/Upsert/Clear GeneratedDocumentID methods on each entity type
(ProcessingActivity, DataProtectionImpactAssessment,
TransferImpactAssessment) backed by new columns in the generated_documents
table, matching the Finding/Obligation pattern.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-04-28 18:07:46 +02:00
Émile Ré
b9835f0f25 Add cookie banner and category dataloaders
Batch-load CookieBanner and CookieCategory entities via
dataloadgen instead of making individual service calls in
GraphQL resolvers, matching the existing dataloader pattern
used for organizations, frameworks, etc.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-28 15:28:18 +04:00
Émile Ré
d9b0d5f44d Remove useless nil guards
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-28 15:28:18 +04:00
Émile Ré
23cf2caeba Add missing authorize calls in cookie banner resolvers
Resolvers for CookieCategory, Organization, CookieBanner,
Translations, and CookieBannerVersion.Categories were either
missing authorization checks or returning ID-only stubs
without querying the database. This fixes both issues by
adding proper authorize calls and fetching full entities.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-28 15:28:17 +04:00
Émile Ré
9b83e319a0 Add consent record detail page
Display record attributes and parsed consent data with
per-category consent state and cookies from the banner
version snapshot. The page lives outside the config layout
with its own breadcrumb navigation.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-28 15:28:17 +04:00
Émile Ré
84632fe795 Filter consent records by version number
Replace the opaque cookieBannerVersionId filter with an
integer version filter. The SQL filter now resolves the
version number via a subquery against cookie_banner_versions.

Also fix the CookieBannerVersion resolver on consent records
to load the full version from the database instead of
returning a stub with only the ID set (which caused the
version to always display as 0).

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-28 15:28:17 +04:00