Skip to content

feat(identity): add tenant-bound SCIM machine foundation - #89

Merged
kl3inIT merged 3 commits into
mainfrom
feat/scim-machine-security
Jul 27, 2026
Merged

feat(identity): add tenant-bound SCIM machine foundation#89
kl3inIT merged 3 commits into
mainfrom
feat/scim-machine-security

Conversation

@kl3inIT

@kl3inIT kl3inIT commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • add the highest-priority stateless /scim/v2/** security chain with tenant-bound bearer credentials, TLS/request/rate guards, expiry, bounded rotation overlap, immediate revocation, and last-use metadata
  • add authenticated SCIM discovery and a separate contracts/scim-openapi.json while keeping SCIM routes out of the product/browser contract
  • add the organization-admin SCIM console for disabled connections, Tenant URL, one-time token copy, rotation, revocation, and auditable credential metadata
  • let SCIM safely adopt an exact same-organization email, consume an open invitation, preserve local role/department, and bind the first verified Keycloak (issuer, subject) login
  • enforce one SCIM provisioning authority per application user with Flyway V11
  • remove the duplicated standalone Access check route/sidebar item; the inspector remains in the selected user detail where the subject context is explicit

Why

Keycloak remains the interactive IdP/broker, but OrgMemory needs to own organization tenancy, workforce lifecycle, provisioning evidence, and authorization. This foundation establishes that machine boundary without making Keycloak the account source of truth or exposing partial User/Group mutation.

Product and security impact

  • every connection is created DISABLED
  • no SCIM User or Group mutation route is reachable in this increment
  • raw tokens are displayed once and never persisted; PostgreSQL stores only a keyed verifier digest
  • SCIM credentials cannot enter /api/**, and OIDC/browser credentials cannot fall through into /scim/v2/**
  • invitation remains available for unmanaged users, while invitations cannot reclaim a SCIM-managed user
  • tenant ownership for credential administration is always derived from CurrentActor and guarded by can_manage_members

Validation

  • ./gradlew.bat --no-daemon clean test
  • focused SCIM machine-security, tenant-isolation, concurrent rotation/revocation, admin provisioning, invitation adoption, production configuration, and OpenAPI drift tests
  • corepack pnpm -C web build (lint, TypeScript, production build)
  • product/SCIM contract-boundary check, git diff --check, mechanical Java scan, and literal bearer-token scan

JetBrains semantic inspection was unavailable because this worktree was not the project open in the IDE; the full Gradle suite and mechanical checks were used as fallback.

Deferred

  • previous-binary and database restore rehearsal remain operational follow-up evidence
  • SCIM Users require the next private-beta capability gate
  • Directory Groups and authorization mapping remain later increments

Summary by CodeRabbit

  • New Features
    • Added SCIM provisioning administration, including connection creation and bearer token issue, rotation, and revocation.
    • Added SCIM discovery endpoints and secure bearer-token authentication.
    • Added request protections including TLS enforcement, size limits, rate limiting, and scoped access.
    • Improved directory user onboarding and invitation reconciliation.
  • Bug Fixes
    • Prevented SCIM credentials from accessing product APIs.
    • Added safeguards against duplicate user provisioning and cross-tenant access.
  • UI Updates
    • Replaced the static SCIM administration page with an interactive provisioning management experience.
    • Reorganized administration navigation and removed the Access check page.

@kl3inIT
kl3inIT force-pushed the feat/scim-machine-security branch from a365e27 to fffdcc8 Compare July 27, 2026 13:37
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kl3inIT, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 10 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 14e65281-9eb4-4a45-89be-280f0c7dbe08

📥 Commits

Reviewing files that changed from the base of the PR and between fffdcc8 and afff6c0.

⛔ Files ignored due to path filters (2)
  • docs/increments/active/2026-07-27-scim-provisioning-foundation/design.md is excluded by !docs/**
  • docs/roadmap.md is excluded by !docs/**
📒 Files selected for processing (21)
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimAuthenticationProvider.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimConnectionRateLimitFilter.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimErrorWriter.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimRequestGuardFilter.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimRequestRateLimiter.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimSecurityConfiguration.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimSecurityProperties.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimTokenCodec.java
  • apps/api/src/main/resources/application.yml
  • apps/api/src/test/java/com/orgmemory/api/scim/ScimErrorWriterTests.java
  • apps/api/src/test/java/com/orgmemory/api/scim/ScimMachineSecurityIntegrationTests.java
  • apps/api/src/test/java/com/orgmemory/api/scim/ScimRequestGuardFilterTests.java
  • apps/api/src/test/java/com/orgmemory/api/scim/ScimTokenCodecTests.java
  • apps/api/src/test/java/com/orgmemory/api/security/ProductionConfigurationGuardTests.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningConflictException.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningCredential.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningLedgerService.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ScimUserResourceRepository.java
  • core/src/main/java/com/orgmemory/core/organization/UserProvisioningService.java
  • core/src/test/java/com/orgmemory/core/organization/UserProvisioningServiceTests.java
  • web/src/features/admin/components/admin-scim-page.tsx
📝 Walkthrough

Walkthrough

This PR adds SCIM discovery, bearer-token security, credential lifecycle administration, directory-user reconciliation, provisioning persistence rules, OpenAPI contract separation, and a web interface for managing SCIM connections and credentials.

Changes

SCIM provisioning

Layer / File(s) Summary
Provisioning ledger and directory reconciliation
core/src/main/java/com/orgmemory/core/identityprovisioning/*, core/src/main/java/com/orgmemory/core/organization/*, core/src/main/resources/db/migration/*, core/src/test/java/com/orgmemory/core/organization/*
Provisioning services and repositories now support tenant-scoped credential lifecycle operations, SCIM user registration conflicts, invitation reconciliation, and directory-managed users.
Verified sign-in reconciliation
apps/api/src/main/java/com/orgmemory/api/security/OidcCurrentActorProvider.java, apps/api/src/test/java/com/orgmemory/api/security/OidcCurrentActorProviderTests.java
Unlinked verified OIDC identities are resolved through directory-user matching or invitation provisioning.
SCIM token security and request enforcement
apps/api/src/main/java/com/orgmemory/api/scim/*, apps/api/src/main/java/com/orgmemory/api/security/*, apps/api/src/main/resources/application*.yml, apps/api/src/test/java/com/orgmemory/api/scim/*, apps/api/src/test/java/com/orgmemory/api/security/*
SCIM tokens are issued and verified with HMAC-backed verifiers, authenticated through a dedicated security chain, constrained by scope, request size, TLS, request IDs, and rate limits, and rejected on the product API.
Admin provisioning API
apps/api/src/main/java/com/orgmemory/api/admin/AdminProvisioningController.java, apps/api/src/test/java/com/orgmemory/api/admin/*
Administrators can create disabled connections, list metadata, issue and rotate users-only credentials, revoke credentials, and perform restricted state updates.
SCIM administration interface
web/src/features/admin/*, web/src/components/app-shell/admin-sidebar.tsx, web/src/routes/admin/access.tsx, web/src/routeTree.gen.ts
The SCIM page now manages connections and bearer tokens, while the access-check page and route are removed.

OpenAPI contracts

Layer / File(s) Summary
SCIM discovery and OpenAPI contracts
apps/api/src/main/java/com/orgmemory/api/OpenApiGroupingConfiguration.java, apps/api/src/main/java/com/orgmemory/api/scim/ScimDiscoveryController.java, apps/api/src/main/java/com/orgmemory/api/scim/ScimErrorWriter.java, apps/api/src/test/java/com/orgmemory/api/OpenApiContractTests.java, contracts/scim-openapi.json
Product and SCIM paths are generated as separate OpenAPI groups, with SCIM discovery responses and a committed SCIM bearer-token contract.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • kl3inIT/OrgMemory#12: Extends the same production security guard and SCIM configuration validation.
  • kl3inIT/OrgMemory#16: Introduced the earlier admin SCIM page that this PR makes interactive.
  • kl3inIT/OrgMemory#86: Provides the provisioning ledger foundation consumed by the new SCIM and admin flows.

Sequence Diagram(s)

sequenceDiagram
  participant Admin
  participant AdminProvisioningController
  participant ScimCredentialAdministrationService
  participant ScimTokenCodec
  participant ProvisioningLedgerService
  Admin->>AdminProvisioningController: Issue or rotate credential
  AdminProvisioningController->>ScimCredentialAdministrationService: Request credential lifecycle operation
  ScimCredentialAdministrationService->>ScimTokenCodec: Generate token and verifier
  ScimCredentialAdministrationService->>ProvisioningLedgerService: Persist credential verifier
  ProvisioningLedgerService-->>AdminProvisioningController: Credential metadata and one-time token
  AdminProvisioningController-->>Admin: Return credential response
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.47% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: introducing tenant-bound SCIM machine infrastructure.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/scim-machine-security

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningLedgerService.java (1)

167-200: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Guarantee SCIM single-authority before saving scim_user_resources.

existsByOrganizationIdAndAppUserId(...) runs after provisionFromDirectory has already modified/committed the AppUser row, and ensureNewUserResource() only checks connection-scoped externalId/userName duplicates. A second concurrent registration can read the same AppUser, pass exists/app-user conflict checks, and insert a duplicate scim_user_resources row until it loses against the added unique constraint. Capture the uq_scim_user_app_user violation and rethrow as ProvisioningConflictException, or lock the target user/SCIM row before provisionFromDirectory.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningLedgerService.java`
around lines 167 - 200, Update registerUserResource to guarantee
single-authority before persisting the SCIM resource: either lock the target
AppUser/SCIM record before provisionFromDirectory, or catch the
uq_scim_user_app_user unique-constraint violation from users.save and rethrow
ProvisioningConflictException. Preserve the existing conflict message and ensure
concurrent registrations cannot leak the database constraint exception.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/api/src/main/java/com/orgmemory/api/scim/ScimAuthenticationProvider.java`:
- Around line 64-66: Restrict the IllegalArgumentException catch in
ScimAuthenticationProvider to only the SCIM credential parsing step, leaving
ledger access, authority construction, and markCredentialUsed outside that catch
so downstream faults propagate normally. Preserve ProvisioningNotFoundException
handling as appropriate, and pass the caught parsing exception as the cause when
creating BadCredentialsException.
- Around line 60-61: Throttle the credential usage write in the authentication
flow around markCredentialUsed so authenticated SCIM requests do not
synchronously update the same credential row on every request. Preserve the
first-use update and only perform subsequent updates when lastUsedAt is older
than a coarse interval such as one minute, while keeping
authenticatedDiscoveryIsTruthfulAndUsesScimMediaType’s first-request behavior
intact.

In `@apps/api/src/main/java/com/orgmemory/api/scim/ScimErrorWriter.java`:
- Around line 20-26: Update ScimErrorWriter.escape to produce valid JSON for
every control character in U+0000–U+001F, including newline, form feed, tab, and
backspace, while preserving backslash and quote escaping. Prefer the existing
JSON serializer if available; otherwise implement complete JSON string escaping
and add a regression test covering control characters in detail.

In `@apps/api/src/main/java/com/orgmemory/api/scim/ScimRequestGuardFilter.java`:
- Around line 46-52: Update ScimRequestGuardFilter to consume a rate-limit
bucket when authentication does not produce a ScimMachinePrincipal, using a
stable fallback key such as the parsed public token identifier or client
address. Preserve the existing connectionId-based bucket for authenticated
principals, and return the same 429 response when either bucket is exhausted so
malformed, revoked, and incorrect bearer-token attempts are throttled.
- Around line 56-69: Update the rate-limiting implementation around
ScimRequestGuardFilter.consume and its windows state to use a shared counter
store such as Redis or Postgres, ensuring requests across replicas share the
configured per-minute limit and counters survive individual instance restarts;
if retaining the local ConcurrentHashMap design, explicitly document that the
limit is per instance and resets on deployment.
- Around line 38-41: Update ScimRequestGuardFilter’s request-size enforcement so
chunked requests cannot bypass the configured maximumRequestSize limit: reject
unknown content lengths or enforce the cap while reading via a counting/limiting
request stream. Preserve the existing 413 response and allow requests whose
actual body remains within the configured limit.

In
`@apps/api/src/main/java/com/orgmemory/api/scim/ScimSecurityConfiguration.java`:
- Around line 32-53: Update the filter ordering in the security configuration
around ScimRequestGuardFilter and BearerTokenAuthenticationFilter so TLS and
maximumRequestSize guards execute before bearer token validation, while the
per-connection rate limit remains in the authenticated-principal path. Adjust
the guard filter or split its placement as needed without changing the existing
authorization rules.

In `@apps/api/src/main/java/com/orgmemory/api/scim/ScimTokenCodec.java`:
- Around line 59-66: Update ScimTokenCodec.matches and its key configuration to
accept the current verifier key plus a bounded set of previous key versions
during rotation, using a version-to-key map and selecting the matching key for
digest verification. Preserve rejection for unknown or expired versions, and
ensure rotation no longer invalidates all existing SCIM tokens immediately.

In
`@apps/api/src/main/java/com/orgmemory/api/security/OidcCurrentActorProvider.java`:
- Around line 89-95: Update provisionVerifiedSignIn to scope the
directory-managed email lookup to the authenticated tenant/organization before
binding the OIDC issuer and subject. Prefer the organization-aware users lookup,
such as findByOrganizationIdAndEmailIgnoreCase, and preserve the existing
invitation-adoption behavior for collisions if no scoped lookup is available.

In `@apps/api/src/main/resources/application.yml`:
- Around line 143-150: Update the scim.token-ttl default in application.yml from
365d to a materially shorter duration such as 90d, while preserving the
ORGMEMORY_SCIM_TOKEN_TTL override so operators can tune it for their
deployments.

In
`@apps/api/src/test/java/com/orgmemory/api/scim/ScimMachineSecurityIntegrationTests.java`:
- Around line 79-114: Update seedTenantAndDisabledConnection to replace the
global deletes of provisioning_credentials and provisioning_connections with
organization-scoped deletes limited to ORGANIZATION_ID and
OTHER_ORGANIZATION_ID, using the appropriate organization ownership columns and
parameter binding. Preserve the existing cleanup ordering and tenant setup.
- Around line 149-232: Add integration tests in
ScimMachineSecurityIntegrationTests for ScimRequestGuardFilter covering
rate-limit exhaustion at requestsPerMinute with HTTP 429 and Retry-After,
oversized request bodies returning HTTP 413, require-tls rejecting non-TLS
requests with HTTP 403, and invalid inbound X-Request-ID values being replaced
by a valid fallback ID. Reuse the existing MVC, configuration, token, and
assertion helpers while preserving current authentication and scope coverage.

In
`@core/src/main/java/com/orgmemory/core/organization/UserProvisioningService.java`:
- Around line 171-215: Add an explicit null/blank validation for command.email()
in provisionFromDirectory before calling UserInvitation.normalizeEmail, and
throw the service’s BusinessValidationException with the expected directory-user
validation key/message. Preserve normalization and subsequent provisioning
behavior for valid emails.

---

Outside diff comments:
In
`@core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningLedgerService.java`:
- Around line 167-200: Update registerUserResource to guarantee single-authority
before persisting the SCIM resource: either lock the target AppUser/SCIM record
before provisionFromDirectory, or catch the uq_scim_user_app_user
unique-constraint violation from users.save and rethrow
ProvisioningConflictException. Preserve the existing conflict message and ensure
concurrent registrations cannot leak the database constraint exception.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d0ad8264-a477-453c-87da-d67bb212140c

📥 Commits

Reviewing files that changed from the base of the PR and between 097510d and fffdcc8.

⛔ Files ignored due to path filters (6)
  • contracts/openapi.json is excluded by !contracts/openapi.json
  • docs/decisions/0016-native-scim-behind-keycloak-broker.md is excluded by !docs/**
  • docs/increments/active/2026-07-27-scim-provisioning-foundation/design.md is excluded by !docs/**
  • docs/increments/active/2026-07-27-scim-provisioning-foundation/plan.md is excluded by !docs/**
  • docs/increments/active/2026-07-27-scim-provisioning-foundation/protocol-profile.md is excluded by !docs/**
  • docs/roadmap.md is excluded by !docs/**
📒 Files selected for processing (44)
  • apps/api/src/main/java/com/orgmemory/api/OpenApiGroupingConfiguration.java
  • apps/api/src/main/java/com/orgmemory/api/admin/AdminProvisioningController.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimAuthenticationProvider.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimCredentialAdministrationService.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimDiscoveryController.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimErrorWriter.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimMachinePrincipal.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimRequestGuardFilter.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimSecurityConfiguration.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimSecurityProperties.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimTokenCodec.java
  • apps/api/src/main/java/com/orgmemory/api/security/OidcCurrentActorProvider.java
  • apps/api/src/main/java/com/orgmemory/api/security/ProductionConfigurationGuard.java
  • apps/api/src/main/java/com/orgmemory/api/security/ProductionSecurityConfiguration.java
  • apps/api/src/main/java/com/orgmemory/api/security/SecurityConfig.java
  • apps/api/src/main/resources/application-prod.yml
  • apps/api/src/main/resources/application.yml
  • apps/api/src/test/java/com/orgmemory/api/OpenApiContractTests.java
  • apps/api/src/test/java/com/orgmemory/api/admin/AdminProvisioningIntegrationTests.java
  • apps/api/src/test/java/com/orgmemory/api/identityprovisioning/ProvisioningUserAdoptionIntegrationTests.java
  • apps/api/src/test/java/com/orgmemory/api/scim/ScimMachineSecurityIntegrationTests.java
  • apps/api/src/test/java/com/orgmemory/api/security/OidcCurrentActorProviderTests.java
  • apps/api/src/test/java/com/orgmemory/api/security/ProductionConfigurationGuardTests.java
  • contracts/scim-openapi.json
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningConflictException.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningConnectionRepository.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningCredential.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningCredentialRepository.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningLedgerService.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningNotFoundException.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ScimUserResource.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ScimUserResourceRepository.java
  • core/src/main/java/com/orgmemory/core/organization/AppUser.java
  • core/src/main/java/com/orgmemory/core/organization/AppUserRepository.java
  • core/src/main/java/com/orgmemory/core/organization/UserInvitationRepository.java
  • core/src/main/java/com/orgmemory/core/organization/UserProvisioningService.java
  • core/src/main/resources/db/migration/V11__enforce_single_scim_authority_per_user.sql
  • core/src/test/java/com/orgmemory/core/organization/UserProvisioningServiceTests.java
  • web/src/components/app-shell/admin-sidebar.tsx
  • web/src/features/admin/admin-queries.ts
  • web/src/features/admin/components/admin-access-page.tsx
  • web/src/features/admin/components/admin-scim-page.tsx
  • web/src/routeTree.gen.ts
  • web/src/routes/admin/access.tsx
💤 Files with no reviewable changes (3)
  • web/src/features/admin/components/admin-access-page.tsx
  • web/src/routes/admin/access.tsx
  • web/src/routeTree.gen.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Backend · Java 25
🧰 Additional context used
📓 Path-based instructions (7)
**/*

📄 CodeRabbit inference engine (CLAUDE.md)

**/*: Record current behavior in architecture/specification documentation only after it exists in code; keep intended behavior in vision, roadmap, or an active increment, and do not duplicate state.
Before using unfamiliar Spring Boot 4, Spring Modulith 2, Spring AI 2, Gradle, React, Vite, Tailwind, or TypeScript APIs, consult current official documentation via Context7 and the project orgmemory-* verification skills.
Before retrieval, AI, MCP, permission, upload, graph, or export work, read docs/guidelines/agent-safety.md.
Never commit .env files, provider keys, tokens, or customer data.
Run the relevant verification gates from docs/guidelines/testing-harness.md; use a terminating clean test as the context gate, and do not treat bootRun as verification.

Files:

  • core/src/main/resources/db/migration/V11__enforce_single_scim_authority_per_user.sql
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimErrorWriter.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimMachinePrincipal.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningConflictException.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimRequestGuardFilter.java
  • core/src/main/java/com/orgmemory/core/organization/AppUserRepository.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimAuthenticationProvider.java
  • apps/api/src/main/resources/application-prod.yml
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningConnectionRepository.java
  • contracts/scim-openapi.json
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningNotFoundException.java
  • apps/api/src/main/resources/application.yml
  • apps/api/src/main/java/com/orgmemory/api/security/ProductionSecurityConfiguration.java
  • core/src/main/java/com/orgmemory/core/organization/UserInvitationRepository.java
  • core/src/main/java/com/orgmemory/core/organization/AppUser.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimSecurityConfiguration.java
  • apps/api/src/main/java/com/orgmemory/api/security/ProductionConfigurationGuard.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimDiscoveryController.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningCredential.java
  • apps/api/src/test/java/com/orgmemory/api/OpenApiContractTests.java
  • apps/api/src/test/java/com/orgmemory/api/security/OidcCurrentActorProviderTests.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimSecurityProperties.java
  • apps/api/src/main/java/com/orgmemory/api/OpenApiGroupingConfiguration.java
  • web/src/components/app-shell/admin-sidebar.tsx
  • web/src/features/admin/admin-queries.ts
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimTokenCodec.java
  • apps/api/src/test/java/com/orgmemory/api/identityprovisioning/ProvisioningUserAdoptionIntegrationTests.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ScimUserResourceRepository.java
  • core/src/test/java/com/orgmemory/core/organization/UserProvisioningServiceTests.java
  • apps/api/src/test/java/com/orgmemory/api/admin/AdminProvisioningIntegrationTests.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ScimUserResource.java
  • web/src/features/admin/components/admin-scim-page.tsx
  • apps/api/src/main/java/com/orgmemory/api/security/OidcCurrentActorProvider.java
  • apps/api/src/test/java/com/orgmemory/api/scim/ScimMachineSecurityIntegrationTests.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimCredentialAdministrationService.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningCredentialRepository.java
  • apps/api/src/test/java/com/orgmemory/api/security/ProductionConfigurationGuardTests.java
  • apps/api/src/main/java/com/orgmemory/api/security/SecurityConfig.java
  • apps/api/src/main/java/com/orgmemory/api/admin/AdminProvisioningController.java
  • core/src/main/java/com/orgmemory/core/organization/UserProvisioningService.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningLedgerService.java
core/src/main/resources/db/migration/*.sql

⚙️ CodeRabbit configuration file

core/src/main/resources/db/migration/*.sql: The repository is pre-release: V1 is the intentionally resettable clean
baseline and development data carries no migration cost. Once a release
baseline is frozen, later Flyway migrations are immutable. Check tenant
isolation, foreign keys, uniqueness, indexes, append-only evidence
semantics, safe defaults, and PostgreSQL 18 plus pgvector compatibility.

Files:

  • core/src/main/resources/db/migration/V11__enforce_single_scim_authority_per_user.sql
**/*.{java,kt}

📄 CodeRabbit inference engine (CLAUDE.md)

JetBrains IDE inspection is a verification gate for the Java backend.

Files:

  • apps/api/src/main/java/com/orgmemory/api/scim/ScimErrorWriter.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimMachinePrincipal.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningConflictException.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimRequestGuardFilter.java
  • core/src/main/java/com/orgmemory/core/organization/AppUserRepository.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimAuthenticationProvider.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningConnectionRepository.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningNotFoundException.java
  • apps/api/src/main/java/com/orgmemory/api/security/ProductionSecurityConfiguration.java
  • core/src/main/java/com/orgmemory/core/organization/UserInvitationRepository.java
  • core/src/main/java/com/orgmemory/core/organization/AppUser.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimSecurityConfiguration.java
  • apps/api/src/main/java/com/orgmemory/api/security/ProductionConfigurationGuard.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimDiscoveryController.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningCredential.java
  • apps/api/src/test/java/com/orgmemory/api/OpenApiContractTests.java
  • apps/api/src/test/java/com/orgmemory/api/security/OidcCurrentActorProviderTests.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimSecurityProperties.java
  • apps/api/src/main/java/com/orgmemory/api/OpenApiGroupingConfiguration.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimTokenCodec.java
  • apps/api/src/test/java/com/orgmemory/api/identityprovisioning/ProvisioningUserAdoptionIntegrationTests.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ScimUserResourceRepository.java
  • core/src/test/java/com/orgmemory/core/organization/UserProvisioningServiceTests.java
  • apps/api/src/test/java/com/orgmemory/api/admin/AdminProvisioningIntegrationTests.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ScimUserResource.java
  • apps/api/src/main/java/com/orgmemory/api/security/OidcCurrentActorProvider.java
  • apps/api/src/test/java/com/orgmemory/api/scim/ScimMachineSecurityIntegrationTests.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimCredentialAdministrationService.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningCredentialRepository.java
  • apps/api/src/test/java/com/orgmemory/api/security/ProductionConfigurationGuardTests.java
  • apps/api/src/main/java/com/orgmemory/api/security/SecurityConfig.java
  • apps/api/src/main/java/com/orgmemory/api/admin/AdminProvisioningController.java
  • core/src/main/java/com/orgmemory/core/organization/UserProvisioningService.java
  • core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningLedgerService.java
apps/api/src/main/java/**/*.java

⚙️ CodeRabbit configuration file

apps/api/src/main/java/**/*.java: Enforce the browser-BFF and resource-server boundaries. Authentication
must resolve an active internal actor through the explicit issuer and
subject binding. Reject identity, tenant, roles, or permissions supplied
by request payloads, JWT email, or untrusted JWT role claims.

Files:

  • apps/api/src/main/java/com/orgmemory/api/scim/ScimErrorWriter.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimMachinePrincipal.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimRequestGuardFilter.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimAuthenticationProvider.java
  • apps/api/src/main/java/com/orgmemory/api/security/ProductionSecurityConfiguration.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimSecurityConfiguration.java
  • apps/api/src/main/java/com/orgmemory/api/security/ProductionConfigurationGuard.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimDiscoveryController.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimSecurityProperties.java
  • apps/api/src/main/java/com/orgmemory/api/OpenApiGroupingConfiguration.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimTokenCodec.java
  • apps/api/src/main/java/com/orgmemory/api/security/OidcCurrentActorProvider.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimCredentialAdministrationService.java
  • apps/api/src/main/java/com/orgmemory/api/security/SecurityConfig.java
  • apps/api/src/main/java/com/orgmemory/api/admin/AdminProvisioningController.java
**/application*.{yml,yaml,properties}

📄 CodeRabbit inference engine (CLAUDE.md)

Keep JPA ddl-auto=validate; pair JPA schema or entity changes with a Flyway migration.

Files:

  • apps/api/src/main/resources/application-prod.yml
  • apps/api/src/main/resources/application.yml
**/*.{ts,tsx,js,jsx,css,scss,html}

📄 CodeRabbit inference engine (CLAUDE.md)

For frontend files, run Oxlint, TypeScript typecheck, the production build, and browser tests when the UI flow matters; do not run JetBrains IDE inspection on TypeScript, TSX, or web configuration.

Files:

  • web/src/components/app-shell/admin-sidebar.tsx
  • web/src/features/admin/admin-queries.ts
  • web/src/features/admin/components/admin-scim-page.tsx
web/src/**/*.{ts,tsx}

⚙️ CodeRabbit configuration file

web/src/**/*.{ts,tsx}: OAuth access and refresh tokens must never enter browser JavaScript or
browser storage. Use the HttpOnly BFF session, CSRF-protected mutations,
generated Hey API data clients, accessible states, and both light and
dark themes. Handwritten transport is reserved for documented protocol
flows such as navigation redirects and streaming.

Files:

  • web/src/components/app-shell/admin-sidebar.tsx
  • web/src/features/admin/admin-queries.ts
  • web/src/features/admin/components/admin-scim-page.tsx
🧠 Learnings (3)
📚 Learning: 2026-07-23T23:30:44.585Z
Learnt from: kl3inIT
Repo: kl3inIT/OrgMemory PR: 30
File: core/src/main/resources/db/migration/V32__evidence_scoped_graph_semantics.sql:0-0
Timestamp: 2026-07-23T23:30:44.585Z
Learning: For OrgMemory PostgreSQL Flyway migrations under core/src/main/resources/db/migration, do not recommend using `CREATE INDEX CONCURRENTLY` or `DROP INDEX CONCURRENTLY` inside application-owned Flyway migration SQL. Flyway’s schema-history connection may hold a transaction that can cause concurrent index operations to wait indefinitely (e.g., on a `virtualxid`), and docs/conventions.md forbids this pattern. If you need large production-table index replacement, pre-stage online index operations via the deployment pipeline (outside Flyway) rather than inside the migration; “ordinary” index replacement is acceptable for unreleased projections before production traffic.

Applied to files:

  • core/src/main/resources/db/migration/V11__enforce_single_scim_authority_per_user.sql
📚 Learning: 2026-07-26T05:46:47.443Z
Learnt from: kl3inIT
Repo: kl3inIT/OrgMemory PR: 61
File: apps/mcp/src/main/java/com/orgmemory/mcp/McpSecurityConfiguration.java:50-52
Timestamp: 2026-07-26T05:46:47.443Z
Learning: In OrgMemory, treat the `apps/mcp` and `apps/api` as independent protocol adapter modules. When adjusting OAuth/wire-level scopes, do not introduce a shared Java constant or create a code dependency from `apps/mcp` to `apps/api` solely to deduplicate scope values. Instead, keep OAuth/scope constants adapter-local (e.g., in the relevant adapter/security configuration classes) and ensure cross-adapter consistency via automated realm/OAuth/authorization tests, rather than via shared wiring-level constants or cross-module references.

Applied to files:

  • apps/api/src/main/java/com/orgmemory/api/scim/ScimErrorWriter.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimMachinePrincipal.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimRequestGuardFilter.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimAuthenticationProvider.java
  • apps/api/src/main/java/com/orgmemory/api/security/ProductionSecurityConfiguration.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimSecurityConfiguration.java
  • apps/api/src/main/java/com/orgmemory/api/security/ProductionConfigurationGuard.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimDiscoveryController.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimSecurityProperties.java
  • apps/api/src/main/java/com/orgmemory/api/OpenApiGroupingConfiguration.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimTokenCodec.java
  • apps/api/src/main/java/com/orgmemory/api/security/OidcCurrentActorProvider.java
  • apps/api/src/main/java/com/orgmemory/api/scim/ScimCredentialAdministrationService.java
  • apps/api/src/main/java/com/orgmemory/api/security/SecurityConfig.java
  • apps/api/src/main/java/com/orgmemory/api/admin/AdminProvisioningController.java
📚 Learning: 2026-07-23T03:36:09.053Z
Learnt from: kl3inIT
Repo: kl3inIT/OrgMemory PR: 16
File: web/src/features/admin/components/admin-mappings-page.tsx:190-211
Timestamp: 2026-07-23T03:36:09.053Z
Learning: In the OrgMemory admin UI, preserve the principal display ordering returned by the backend instead of re-sorting on the client. The server-owned order (e.g., unmapped views first, then deterministic ordering by source-system, connection, kind, and external-key as defined in SourcePrincipalAdminService#listPrincipals) must be used as-is to avoid mismatches with the server’s intended admin mappings/principals list.

Applied to files:

  • web/src/features/admin/components/admin-scim-page.tsx
🪛 ast-grep (0.44.1)
apps/api/src/main/java/com/orgmemory/api/scim/ScimErrorWriter.java

[warning] 19-21: Avoid writing untrusted input to the HTTP response
Context: response.getWriter().write("""
{"schemas":["%s"],"status":"%d","detail":"%s"}
""".formatted(ERROR_SCHEMA, status, escape(detail)))
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(xss-protection-java)

apps/api/src/main/java/com/orgmemory/api/scim/ScimSecurityConfiguration.java

[warning] 31-33: Do not disable CSRF
Context: http
.securityMatcher("/scim/v2/**")
.csrf(AbstractHttpConfigurer::disable)
Note: [CWE-352] Cross-Site Request Forgery (CSRF).

(spring-csrf-disable)

apps/api/src/test/java/com/orgmemory/api/scim/ScimMachineSecurityIntegrationTests.java

[warning] 165-165: Avoid using untrusted input as a setAttribute() name (trust boundary violation)
Context: session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, context)
Note: [CWE-501] Trust Boundary Violation.

(trust-boundaries-java)

🪛 Checkov (3.3.8)
contracts/scim-openapi.json

[high] 1: Ensure that the global security field has rules defined

(CKV_OPENAPI_4)

🪛 PMD (7.26.0)
apps/api/src/main/java/com/orgmemory/api/scim/ScimAuthenticationProvider.java

[Medium] 65-65: PreserveStackTrace (Best Practices): Thrown exception does not preserve the stack trace of exception 'failure' on all code paths

(PreserveStackTrace (Best Practices))

🪛 Squawk (2.59.0)
core/src/main/resources/db/migration/V11__enforce_single_scim_authority_per_user.sql

[warning] 5-6: By default new constraints require a table scan and block writes to the table while that scan occurs. Use NOT VALID with a later VALIDATE CONSTRAINT call.

(constraint-missing-not-valid)


[warning] 5-6: Adding a UNIQUE constraint requires an ACCESS EXCLUSIVE lock which blocks reads and writes to the table while the index is built. Create an index CONCURRENTLY and create the constraint using the index.

(disallowed-unique-constraint)

🔇 Additional comments (46)
apps/api/src/main/java/com/orgmemory/api/OpenApiGroupingConfiguration.java (1)

9-41: LGTM!

apps/api/src/main/java/com/orgmemory/api/scim/ScimDiscoveryController.java (1)

12-76: LGTM!

apps/api/src/test/java/com/orgmemory/api/OpenApiContractTests.java (1)

22-81: LGTM!

contracts/scim-openapi.json (1)

1-1: LGTM!

apps/api/src/main/java/com/orgmemory/api/admin/AdminProvisioningController.java (1)

1-244: LGTM!

apps/api/src/test/java/com/orgmemory/api/admin/AdminProvisioningIntegrationTests.java (1)

1-312: LGTM!

web/src/features/admin/admin-queries.ts (1)

29-32: LGTM!

Also applies to: 95-105, 189-192

web/src/features/admin/components/admin-scim-page.tsx (2)

65-65: 🎯 Functional Correctness | ⚡ Quick win

Credential badge ignores expiry/overlap, so dead tokens still show "Active".

revoked = Boolean(credential.revokedAt) is the only signal used for the "Active"/"Revoked" badge, but CredentialResponse also carries expiresAt and overlapEndsAt. Per ScimMachineSecurityIntegrationTests.expiredRevokedAndEndedOverlapCredentialsAreGenericUnauthorized, a credential whose overlap window or TTL has elapsed is rejected server-side while revokedAt stays null — so this admin page will keep showing it as "Active" indefinitely, which undermines the credential-lifecycle review this page is meant to support.

🩹 Proposed fix
-  const revoked = Boolean(credential.revokedAt)
+  const now = Date.now()
+  const revoked = Boolean(credential.revokedAt)
+  const expired =
+    !revoked &&
+    ((credential.overlapEndsAt && new Date(credential.overlapEndsAt).getTime() < now) ||
+      (credential.expiresAt && new Date(credential.expiresAt).getTime() < now))
-          <Badge variant={revoked ? "outline" : "secondary"}>{revoked ? "Revoked" : "Active"}</Badge>
+          <Badge variant={revoked || expired ? "outline" : "secondary"}>
+            {revoked ? "Revoked" : expired ? "Expired" : "Active"}
+          </Badge>

Also applies to: 104-104


142-142: 🎯 Functional Correctness

No change needed. /scim/v2 is served at the same browser-facing origin in this deployment; there is no separate reverse-proxy prefix for the SCIM resource-server.

apps/api/src/test/java/com/orgmemory/api/identityprovisioning/ProvisioningUserAdoptionIntegrationTests.java (1)

1-234: LGTM!

apps/api/src/main/java/com/orgmemory/api/scim/ScimCredentialAdministrationService.java (1)

1-125: LGTM!

core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningConflictException.java (1)

1-14: LGTM!

core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningCredential.java (1)

91-144: LGTM!

Also applies to: 168-195

core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningCredentialRepository.java (1)

3-13: LGTM!

Also applies to: 14-17, 19-32, 33-34, 36-47

core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningLedgerService.java (2)

3-7: LGTM!

Also applies to: 24-37, 57-63, 104-166


227-269: LGTM!

Also applies to: 307-369, 380-386

core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningNotFoundException.java (1)

3-12: LGTM!

core/src/main/java/com/orgmemory/core/identityprovisioning/ScimUserResource.java (1)

93-108: LGTM!

apps/api/src/main/java/com/orgmemory/api/security/OidcCurrentActorProvider.java (1)

38-44: LGTM!

Also applies to: 108-108

apps/api/src/test/java/com/orgmemory/api/security/OidcCurrentActorProviderTests.java (1)

40-41: LGTM!

Also applies to: 124-125, 134-135, 160-160

core/src/main/resources/db/migration/V11__enforce_single_scim_authority_per_user.sql (1)

1-6: Squawk's CONCURRENTLY/NOT VALID warnings don't apply pre-release here.

Static analysis flags the blocking unique-constraint rebuild, but per prior guidance for this repo, ordinary constraint/index replacement (without CONCURRENTLY/NOT VALID) is accepted for unreleased projections before production traffic, and CONCURRENTLY is explicitly discouraged inside Flyway migrations here due to schema-history connection lock risk.

Based on learnings: "For OrgMemory PostgreSQL Flyway migrations... do not recommend using CREATE INDEX CONCURRENTLY or DROP INDEX CONCURRENTLY... 'ordinary' index replacement is acceptable for unreleased projections before production traffic."

Sources: Learnings, Linters/SAST tools

core/src/main/java/com/orgmemory/core/identityprovisioning/ProvisioningConnectionRepository.java (1)

4-4: LGTM!

Also applies to: 20-21

core/src/main/java/com/orgmemory/core/identityprovisioning/ScimUserResourceRepository.java (1)

13-23: LGTM!

core/src/main/java/com/orgmemory/core/organization/AppUser.java (2)

100-109: LGTM!


155-158: LGTM!

core/src/main/java/com/orgmemory/core/organization/AppUserRepository.java (1)

12-13: LGTM!

core/src/main/java/com/orgmemory/core/organization/UserInvitationRepository.java (1)

30-41: LGTM!

core/src/main/java/com/orgmemory/core/organization/UserProvisioningService.java (4)

50-85: LGTM!


115-125: LGTM!


4-4: LGTM!

Also applies to: 16-26, 153-164, 239-262


171-215: 🗄️ Data Integrity & Integration

No change needed: registerUserResource’s SCIM conflict exception is unchecked and the method runs under @Transactional, so the later check rolls back prior directory-user mutations.

core/src/test/java/com/orgmemory/core/organization/UserProvisioningServiceTests.java (1)

15-15: LGTM!

Also applies to: 89-130, 246-327

web/src/components/app-shell/admin-sidebar.tsx (1)

32-39: LGTM!

apps/api/src/main/java/com/orgmemory/api/scim/ScimSecurityProperties.java (1)

20-48: LGTM!

apps/api/src/main/java/com/orgmemory/api/scim/ScimTokenCodec.java (1)

26-57: LGTM!

apps/api/src/main/java/com/orgmemory/api/scim/ScimMachinePrincipal.java (1)

6-12: LGTM!

apps/api/src/main/java/com/orgmemory/api/security/SecurityConfig.java (2)

65-68: LGTM!

Also applies to: 192-213


56-56: 🩺 Stability & Availability

No change needed.

The SCIM chain is already registered with @Order(1) and a securityMatcher("/scim/v2/**"), so the browser @Order(3) catch-all chain is isolated from SCIM requests.

apps/api/src/test/java/com/orgmemory/api/scim/ScimMachineSecurityIntegrationTests.java (2)

116-147: LGTM!

Also applies to: 234-294


49-51: 📐 Maintainability & Code Quality

No change needed.

org.testcontainers.postgresql.PostgreSQLContainer is the current Testcontainers PostgreSQL module import, and using the raw type matches the existing PostgreSQL container declarations in this module.

apps/api/src/main/java/com/orgmemory/api/scim/ScimRequestGuardFilter.java (1)

34-37: 🔒 Security & Privacy

No change needed for isSecure().

Production enables server.forward-headers-strategy: framework in application-prod.yml, while SCIM TLS enforcement is disabled by default and only required in prod.

apps/api/src/main/java/com/orgmemory/api/scim/ScimSecurityConfiguration.java (1)

1-30: LGTM!

apps/api/src/main/java/com/orgmemory/api/security/ProductionConfigurationGuard.java (1)

21-47: LGTM!

Also applies to: 76-83

apps/api/src/main/java/com/orgmemory/api/security/ProductionSecurityConfiguration.java (1)

3-3: LGTM!

Also applies to: 25-26, 41-42

apps/api/src/main/resources/application-prod.yml (1)

48-52: LGTM!

apps/api/src/test/java/com/orgmemory/api/security/ProductionConfigurationGuardTests.java (1)

6-15: LGTM!

Also applies to: 58-97, 125-176, 194-251

Comment thread apps/api/src/main/java/com/orgmemory/api/scim/ScimAuthenticationProvider.java Outdated
Comment thread apps/api/src/main/java/com/orgmemory/api/scim/ScimErrorWriter.java Outdated
Comment thread apps/api/src/main/java/com/orgmemory/api/scim/ScimRequestGuardFilter.java Outdated
Comment on lines +46 to +52
var authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication == null ? null : authentication.getPrincipal();
if (principal instanceof ScimMachinePrincipal machine && !consume(machine.connectionId())) {
response.setHeader(HttpHeaders.RETRY_AFTER, "60");
ScimErrorWriter.write(response, 429, "Request rate exceeded");
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Rate limiting only applies to already-authenticated callers, so credential guessing is unthrottled.

The counter keys on ScimMachinePrincipal.connectionId(), which exists only after ScimAuthenticationProvider succeeds. Requests with a missing, malformed, revoked, or simply wrong bearer token never consume a slot, so an attacker can hammer /scim/v2/** with candidate tokens at full speed — each attempt also triggers a ledger lookup. Add a fallback bucket for unauthenticated attempts (e.g., keyed by parsed public token id or client address) so failed authentication is throttled too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/main/java/com/orgmemory/api/scim/ScimRequestGuardFilter.java`
around lines 46 - 52, Update ScimRequestGuardFilter to consume a rate-limit
bucket when authentication does not produce a ScimMachinePrincipal, using a
stable fallback key such as the parsed public token identifier or client
address. Preserve the existing connectionId-based bucket for authenticated
principals, and return the same 429 response when either bucket is exhausted so
malformed, revoked, and incorrect bearer-token attempts are throttled.

Comment on lines +89 to +95
private AppUser provisionVerifiedSignIn(ExternalSubject external) {
if (!external.emailVerified() || !StringUtils.hasText(external.email())) {
throw new OrgMemoryAccessDeniedException(
"A verified OIDC email is required to accept an invitation");
"A verified OIDC email is required for first sign-in");
}
return provisioning
.provisionFromInvitation(
.provisionForVerifiedSignIn(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the full provisionForVerifiedSignIn implementation and the
# AppUserRepository.findByEmailIgnoreCase contract for tenant scoping.
ast-grep run --pattern 'Optional<AppUser> provisionForVerifiedSignIn($$$) { $$$ }' \
  --lang java core/src/main/java/com/orgmemory/core/organization/UserProvisioningService.java
rg -nP -B2 -A5 'findByEmailIgnoreCase' core/src/main/java/com/orgmemory/core/organization

Repository: kl3inIT/OrgMemory

Length of output: 2472


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Candidate files =="
git ls-files | rg '(^|/)(AppUserRepository|UserProvisioningService|OidcCurrentActorProvider)\.java$'

echo
echo "== AppUserRepository =="
cat -n core/src/main/java/com/orgmemory/core/organization/AppUserRepository.java

echo
echo "== UserProvisioningService relevant section =="
sed -n '1,140p' core/src/main/java/com/orgmemory/core/organization/UserProvisioningService.java | cat -n

echo
echo "== OidcCurrentActorProvider relevant section =="
sed -n '1,160p' apps/api/src/main/java/com/orgmemory/api/security/OidcCurrentActorProvider.java | cat -n

Repository: kl3inIT/OrgMemory

Length of output: 14218


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for issuer/organization mapping and directory-managed provisioning =="
rg -n "provisionForVerifiedSignIn|ExternalSubject|directoryManaged|DirectoryManaged|organizationId.*email|email.*organization|findByOrganizationIdAndEmailIgnoreCase|findByIssuer|issuer" apps core -g '*.java'

Repository: kl3inIT/OrgMemory

Length of output: 20920


Scope the first-sign-in directory lookup by tenant

provisionForVerifiedSignIn() uses users.findByEmailIgnoreCase(normalized), which matches directory-managed users across all organizations, then binds the OIDC issuer/subject to the first unique email. If two organizations have the same directory-managed email, a sign-in from one issuer can be bound to a user in the other tenant. Use an org-scoped email lookup, such as findByOrganizationIdAndEmailIgnoreCase, or reject multiple colliding directory-managed users the same way invitation adoption does.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/api/src/main/java/com/orgmemory/api/security/OidcCurrentActorProvider.java`
around lines 89 - 95, Update provisionVerifiedSignIn to scope the
directory-managed email lookup to the authenticated tenant/organization before
binding the OIDC issuer and subject. Prefer the organization-aware users lookup,
such as findByOrganizationIdAndEmailIgnoreCase, and preserve the existing
invitation-adoption behavior for collisions if no scoped lookup is available.

Comment thread apps/api/src/main/resources/application.yml
@kl3inIT
kl3inIT marked this pull request as ready for review July 27, 2026 14:27
@kl3inIT
kl3inIT merged commit 749ae11 into main Jul 27, 2026
12 checks passed
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