Repository navigation
Merge develop into main: auth hardening and audit trails - #696
Conversation
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| Compatibility | 11 medium 11 high |
| BestPractice | 9 medium 3 minor 1 high |
| Documentation | 5 minor |
| ErrorProne | 1 medium |
| Performance | 2 medium |
| Comprehensibility | 4 minor |
| Security | 7 medium |
| CodeStyle | 46 minor |
🟢 Metrics 12988 complexity · 190 duplication
Metric Results Complexity 12988 Duplication 190
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Anonymous client authentication, CSV injection protection, and scheduled-export recovery have unresolved defects.
Review effort: Balanced
Findings: 1
Open (3)
What changed in this PR
Promotes develop to main, delivering authentication hardening, expanded compliance auditing, generated-client updates, and operational tooling.
Changes:
- Hardens sign-in, MFA, refresh-token lineage, revocation, and session handling.
- Adds compliance evidence packaging, retention simulation, exports, and audit observability.
- Updates generated clients, CI/security gates, branding, and local-development workflows.
| File | Description |
|---|---|
scripts/compliance/requirements.txt |
Pins secure XML dependency. |
scripts/ci/verify-repository-policy.sh |
Adds audit-validator tests. |
scripts/ci/install-and-audit-pnpm.sh |
Delegates audit validation. |
pnpm-lock.yaml |
Applies patched braces dependency. |
patches/README.md |
Documents security patch. |
packages/infrastructure/client/tests/validation/mfa-recovery-contract.test.ts |
Tests MFA client compatibility. |
packages/infrastructure/client/src/runtime/transport/types.ts |
Adds blob response option. |
packages/infrastructure/client/src/runtime/transport/fetch.ts |
Handles binary responses. |
packages/infrastructure/client/src/generated/modules/learning-assessments-peer-reviews.gen.ts |
Updates peer-review contracts. |
packages/infrastructure/client/src/generated/modules/auth.gen.ts |
Adds polymorphic sign-in client. |
packages/infrastructure/client/src/generated/index.ts |
Exports audit modules. |
packages/infrastructure/client/src/generated/.metadata.json |
Updates generation metadata. |
packages/infrastructure/client/scripts/codegen/modules.ts |
Generates binary module responses. |
packages/infrastructure/client/scripts/codegen/endpoints.ts |
Generates binary endpoint types. |
packages/infrastructure/client/scripts/codegen/binary-response.ts |
Detects binary OpenAPI responses. |
package.json |
Adds development cleanup workflows and patch. |
docs/README.md |
Links development documentation. |
docs/plans/quiz-grading-end-to-end/README.md |
Updates grading-plan status. |
docs/modules/compliance-audit-module.md |
Clarifies compliance scope. |
docs/brand/social-post-example.svg |
Adds brand example. |
docs/architecture/openapi-options.md |
Documents version reconciliation. |
docs/architecture/api-versioning.md |
Documents OpenAPI naming. |
docs/api/rate-limiting-configuration.md |
Clarifies trusted proxies. |
docs/api/authorization-cache-deployment.md |
Documents cache observability. |
deploy/monitoring/prometheus/authorization-cache.yml |
Configures Prometheus. |
deploy/monitoring/otel-collector-authorization-cache.yaml |
Configures telemetry collector. |
deploy/monitoring/alertmanager/authorization-cache.yml |
Configures local alerts. |
compose.yaml |
Exposes telemetry configuration. |
compose.coolify.yaml |
Adds deployment telemetry settings. |
compose.authorization-observability.yaml |
Adds observability stack. |
apps/web/tsconfig.learning.json |
Defines selective Learning build. |
apps/web/src/components/brand/game-guild-logo.tsx |
Uses new brand mark. |
apps/web/src/components/brand/game-guild-logo.test.tsx |
Tests brand component. |
apps/web/public/favicon.svg |
Updates favicon. |
apps/web/public/assets/brand/gameguild-mark.svg |
Adds brand mark. |
apps/web/public/assets/brand/gameguild-logo-stacked.svg |
Adds stacked logo. |
apps/web/public/assets/brand/gameguild-logo-reversed.svg |
Adds reversed logo. |
apps/web/public/assets/brand/gameguild-logo-monochrome.svg |
Adds monochrome logo. |
apps/web/public/assets/brand/gameguild-logo-horizontal.svg |
Adds horizontal logo. |
apps/web/package.json |
Adds Learning build scripts. |
apps/web/next.config.ts |
Configures isolated selective builds. |
apps/web/.gitignore |
Ignores Learning build output. |
apps/api/tests/GameGuild.SharedKernel.UnitTests/Events/CommandOutcomeTests.cs |
Tests commit-on-failure behavior. |
apps/api/tests/GameGuild.SharedKernel.UnitTests/Configuration/ConfigurationOptionsTests.cs |
Updates MFA defaults. |
apps/api/tests/GameGuild.SharedKernel.UnitTests/Configuration/ConfigurationCoreOptionsTests.cs |
Updates MFA defaults. |
apps/api/tests/GameGuild.Learning.Assessments.UnitTests/TasksAggregationTests.cs |
Tests fail-closed peer review. |
apps/api/tests/GameGuild.Identity.Users.UnitTests/Entities/UserCoverageBoostTests.cs |
Tests generated handles. |
apps/api/tests/GameGuild.Identity.Authorization.UnitTests/Services/DatabaseTenantSecurityVersionStoreTests.cs |
Tests batched version reads. |
apps/api/tests/GameGuild.Identity.Authorization.UnitTests/DiExtensionsAndCachingTests.cs |
Tests cache thresholds. |
apps/api/tests/GameGuild.Identity.Authorization.PerformanceTests/Program.cs |
Adds bulk benchmark. |
apps/api/tests/GameGuild.Identity.Authorization.PerformanceTests/PermissionCacheEfDatabaseLookupBenchmarks.cs |
Enables benchmark pooling. |
apps/api/tests/GameGuild.Identity.Authorization.PerformanceTests/PermissionCacheDatabaseLookupBenchmarks.cs |
Enables benchmark pooling. |
apps/api/tests/GameGuild.Identity.Authorization.PerformanceTests/GameGuild.Identity.Authorization.PerformanceTests.csproj |
Adds authentication reference. |
apps/api/tests/GameGuild.Identity.Authentication.UnitTests/Validators/LocalSignUpCommandValidatorTests.cs |
Tests canonical usernames. |
apps/api/tests/GameGuild.Identity.Authentication.UnitTests/Services/OAuthAuthServiceTests.cs |
Tests OAuth attempt auditing. |
apps/api/tests/GameGuild.Identity.Authentication.UnitTests/Models/AuthenticationBranchModelTests.cs |
Updates expiry projection test. |
apps/api/tests/GameGuild.Identity.Authentication.UnitTests/MaxCoverageTests5.cs |
Tests trusted IP handling. |
apps/api/tests/GameGuild.Identity.Authentication.UnitTests/MaxCoverageTests4.cs |
Updates polymorphic mocks. |
apps/api/tests/GameGuild.Identity.Authentication.UnitTests/MaxCoverageTests2.cs |
Updates session command dependencies. |
apps/api/tests/GameGuild.Identity.Authentication.UnitTests/MaxCoverageTests.cs |
Updates revocation middleware tests. |
apps/api/tests/GameGuild.Identity.Authentication.UnitTests/Controllers/AuthControllerTests.cs |
Tests generic refresh denial. |
apps/api/tests/GameGuild.Compliance.Audit.UnitTests/AuditTests.cs |
Tests tenant-storage destination. |
apps/api/tests/GameGuild.Audit.UnitTests/Services/ScheduledAuditExportBackgroundServiceTests.cs |
Tests scheduled worker. |
apps/api/tests/GameGuild.Audit.UnitTests/Services/AuditServiceTests.cs |
Tests activity buckets. |
apps/api/tests/GameGuild.Audit.UnitTests/Services/AuditExportTransportContractTests.cs |
Tests routes and CSV safety. |
apps/api/tests/GameGuild.Audit.UnitTests/GameGuild.Audit.UnitTests.csproj |
Embeds legacy fixture. |
apps/api/tests/GameGuild.Audit.IntegrationTests/AuditIntegrationTests.cs |
Tests audit CQRS registration. |
apps/api/tests/GameGuild.API.UnitTests/Core/UsernameModelContractTests.cs |
Tests username persistence contract. |
apps/api/tests/GameGuild.API.UnitTests/Core/Extensions/SecurityServiceCollectionExtensionsTests.cs |
Tests custom API-key resolver. |
apps/api/tests/GameGuild.API.IntegrationTests/GameGuild.API.IntegrationTests.csproj |
Adds mocks and linked fixtures. |
apps/api/tests/GameGuild.API.IntegrationTests/DataMaskingPostgreSqlIntegrationTests.cs |
Isolates masking fixtures. |
apps/api/tests/GameGuild.API.IntegrationTests/CommonOpenApiIntegrationTests.cs |
Verifies revocation responses. |
apps/api/Source/Modules/GameGuild.SharedKernel/Events/UseCaseOperationContext.cs |
Adds commit-on-failure outcome. |
apps/api/Source/Modules/GameGuild.SharedKernel/Configuration/README.md |
Documents BCrypt cost. |
apps/api/Source/Modules/GameGuild.SharedKernel/Configuration/PresentationLayer/RateLimiting/RateLimitingOptions.cs |
Documents forwarded schemes. |
apps/api/Source/Modules/GameGuild.SharedKernel/Configuration/PresentationLayer/Authorization/AuthorizationOptions.cs |
Adds claim transformations. |
apps/api/Source/Modules/GameGuild.SharedKernel/Configuration/PresentationLayer/Authorization/AuthorizationClaimTransformationOptions.cs |
Defines claim mappings. |
apps/api/Source/Modules/GameGuild.SharedKernel/Configuration/PresentationLayer/Authorization/AuthorizationCacheOptions.cs |
Adds cache warning thresholds. |
apps/api/Source/Modules/GameGuild.SharedKernel/Configuration/PresentationLayer/Authentication/AuthenticationPasswordPolicySettings.cs |
Configures BCrypt cost. |
apps/api/Source/Modules/GameGuild.SharedKernel/Configuration/PresentationLayer/Authentication/AuthenticationOptions.cs |
Adds API-key resolver. |
apps/api/Source/Modules/GameGuild.SharedKernel/Configuration/ApplicationLayer/MfaOptions.cs |
Lengthens recovery codes. |
apps/api/Source/Modules/GameGuild.Learning.Assessments/Services/IPeerReviewAssignmentService.cs |
Revises peer-review boundary. |
apps/api/Source/Modules/GameGuild.Learning.Assessments/Controllers/PeerReviewsController.cs |
Adds participant access check. |
apps/api/Source/Modules/GameGuild.Identity.Users/ValueObjects/UsernameSlug.cs |
Implements canonical handles. |
apps/api/Source/Modules/GameGuild.Identity.Users/ValueObjects/SignInIdentifierType.cs |
Defines sign-in identifier kinds. |
apps/api/Source/Modules/GameGuild.Identity.Users/Abstractions/IUserRepository.cs |
Adds candidate lookup API. |
apps/api/Source/Modules/GameGuild.Identity.Authorization/Services/DatabaseTenantSecurityVersionStore.cs |
Batches security-version reads. |
apps/api/Source/Modules/GameGuild.Identity.Authorization/Caching/ICacheMetricsService.cs |
Makes metrics configurable. |
apps/api/Source/Modules/GameGuild.Identity.Authorization/Caching/CachingServiceExtensions.cs |
Registers metrics monitor. |
apps/api/Source/Modules/GameGuild.Identity.Authorization/Abstractions/ITenantSecurityVersionStore.cs |
Adds bulk version contract. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Validators/LocalSignUpCommandValidator.cs |
Validates canonical handles. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Services/Web3AuthService.cs |
Audits Web3 attempts. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Services/PasswordHistoryPolicy.cs |
Supports legacy long hashes. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Services/MfaService.cs |
Preserves cancellation. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Services/LongPasswordHash.cs |
Adds long-password hashing. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Services/BackupCodeSet.cs |
Adds recovery metadata. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Repositories/UserSessionRepository.cs |
Retains referenced sessions. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Repositories/UserMfaConfigurationRepository.cs |
Reloads concurrency conflicts. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Handlers/RevokeTokenHandler.cs |
Enforces token ownership. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Handlers/RefreshTokenHandler.cs |
Preserves containment denial. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Extensions/DependencyInjection.cs |
Registers polymorphic handler. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Entities/UserMfaConfiguration.cs |
Adds enrollment deadline. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Entities/RefreshToken.cs |
Adds token lineage. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/DTOs/RefreshTokenContainmentDenial.cs |
Defines committed denial. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/DTOs/PolymorphicSignInRequest.cs |
Defines polymorphic request. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/DTOs/MfaConfigurationResponse.cs |
Exposes issued-code count. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/DTOs/LocalSignInRequest.cs |
Adds internal account resolution. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Data/Extensions/DependencyInjection.cs |
Registers lineage services. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Data/Configurations/UserMfaConfigurationConfiguration.cs |
Adds MFA concurrency tokens. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Data/Configurations/RefreshTokenConfiguration.cs |
Configures lineage foreign keys. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Controllers/SessionController.cs |
Revokes all user tokens. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Controllers/MfaController.cs |
Reports recovery history accurately. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Controllers/AuthController.cs |
Adds polymorphic sign-in and denials. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Commands/RevokeAllUserTokensCommand.cs |
Implements logout-everywhere. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Commands/PolymorphicSignInCommand.cs |
Makes sign-in transactional command. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Architecture/AuthenticationUseCaseEventContracts.cs |
Adds auth event contracts. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Abstractions/IVersionedUserTokenRevocationService.cs |
Defines versioned revocation. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Abstractions/IRefreshTokenLineageRepository.cs |
Defines lineage persistence. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Abstractions/IPasswordHasher.cs |
Documents hashing behavior. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Abstractions/IAuthenticationAuditEventSink.cs |
Defines auth audit events. |
apps/api/Source/Modules/GameGuild.Identity.Authentication/Abstractions/IAuthAttemptService.cs |
Adds provider-aware attempts. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Services/ScheduledAuditExportBackgroundService.cs |
Runs scheduled exports. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Services/CompliancePackagingEncoding.cs |
Adds canonical JSON handling. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Services/CentralAuthenticationAuditEventSink.cs |
Bridges authentication audits. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Services/AuditRetentionSimulationRepository.cs |
Persists retention simulations. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Services/AuditLogQueryService.cs |
Records audit-data reads. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Services/AuditDataAccessRecorder.cs |
Persists read observations. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Services/AuditCsvExporter.cs |
Prevents spreadsheet injection. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Services/AuditChainEvidenceVerifier.cs |
Verifies signed chains. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Services/AuditActionTypeExportFormatter.cs |
Formats action-type exports. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Resources/FedRamp2026/fedramp-vulnerability-detail-report-schema-2026-06-24.json |
Adds FedRAMP schema. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Resources/FedRamp2026/fedramp-significant-change-notifications-schema-2026-06-24.json |
Adds FedRAMP schema. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Resources/FedRamp2026/fedramp-historical-ver-activity-schema-2026-06-24.json |
Adds FedRAMP schema. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Resources/FedRamp2026/fedramp-accepted-vulnerability-info-schema-2026-06-24.json |
Adds FedRAMP schema. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Queries/AuditActionTypeQueries.cs |
Adds action-type queries. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Models/AuditActivityBucketSize.cs |
Defines activity buckets. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/GameGuild.Compliance.Audit.csproj |
Adds schemas and validator. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Extensions/AuditModule.cs |
Registers audit services. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Entities/TamperEvidentAuditLog.cs |
Normalizes timestamp precision. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Entities/CompliancePackagingEntities.cs |
Adds evidence entities. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Entities/AuditRetentionEntities.cs |
Adds retention entities. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/DTOs/AuditExportRequest.cs |
Adds export webhooks. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/DTOs/AuditDateRangeSearchResponse.cs |
Adds date-search response. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/DTOs/AuditActionTypeSearchResponse.cs |
Adds action-search responses. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Constants/AuditActionTypes.cs |
Adds permission-change action. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Commands/ScheduledAuditExportCommands.cs |
Adds scheduled-export commands. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Commands/CompliancePackagingCommands.cs |
Adds packaging commands. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Commands/AuditRetentionSimulationCommands.cs |
Adds simulation commands. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Commands/AuditExportCommands.cs |
Limits exported audit metadata. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Architecture/ScheduledAuditExportEventContracts.cs |
Adds export event contracts. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Architecture/CompliancePackagingEventContracts.cs |
Adds packaging event contracts. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Architecture/AuditRetentionSimulationEventContracts.cs |
Adds simulation event contracts. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Abstractions/IAuditService.cs |
Adds activity aggregation. |
apps/api/Source/Modules/GameGuild.Compliance.Audit/Abstractions/IAuditActionTypeSearchService.cs |
Defines search service. |
apps/api/Source/GameGuild.API/Security/AnonymousEndpointRegistry.cs |
Allowlists polymorphic sign-in. |
apps/api/Source/GameGuild.API/Database/PermissionAuditOptions.cs |
Defines permission audit hooks. |
apps/api/Source/GameGuild.API/Database/Migrations/20261005013448_MfaRecoveryConcurrency.cs |
Adds enrollment expiry column. |
apps/api/Source/GameGuild.API/Database/Migrations/20261003002747_AddAuditActionTypeSearchIndexes.cs |
Adds correlation index. |
apps/api/Source/GameGuild.API/Core/Setup/PipelineExtensions.cs |
Adds bearer revocation middleware. |
apps/api/Source/GameGuild.API/Core/Setup/InfrastructureLayerExtensions.cs |
Registers compliance adapters. |
apps/api/Source/GameGuild.API/Core/Security/ConfiguredAuthorizationClaimsTransformation.cs |
Applies claim mappings. |
apps/api/Source/GameGuild.API/Core/OpenApi/DeclaredResponseContentTypesOperationFilter.cs |
Preserves response media types. |
apps/api/Source/GameGuild.API/Core/Extensions/RateLimitingServiceCollectionExtensions.cs |
Restricts forwarded headers. |
apps/api/Source/GameGuild.API/Core/Extensions/OpenTelemetryExtensions.cs |
Validates telemetry exporters. |
apps/api/Source/GameGuild.API/Core/Extensions/OpenApiExtensions.cs |
Reconciles versioned documents. |
apps/api/Source/GameGuild.API/Core/Eventing/UseCaseOperationBehavior.cs |
Commits containment and audits. |
apps/api/Source/GameGuild.API/Core/Compliance/AuditScheduledExportStorageAdapter.cs |
Stores tenant audit exports. |
apps/api/Source/GameGuild.API/appsettings.json |
Adds audit and MFA defaults. |
apps/api/docs/audit-date-range-search.md |
Documents date-range search. |
apps/api/docs/audit-action-type-search.md |
Documents action-type search. |
apps/api/docs/api/authorization-options-configuration.md |
Documents authorization options. |
apps/api/Dockerfile |
Removes permissive forwarded headers. |
apps/api/Directory.Packages.props |
Pins JSON Schema package. |
.trivyignore |
Records patched CVE exception. |
.github/workflows/pr-verify.yml |
Adds OpenAPI HTTP verification. |
.gitattributes |
Normalizes patch files. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
… lifecycle
- AdaptiveAnomalyDetectionService: online statistical learning (EWMA baselines
per subject over hour-of-day circular stats, IP information content, log
inter-arrival cadence) with z-score deviation, configurable sensitivity,
cold-start abstention, persisted per-subject state and drift handling
(exponential decay + stale baseline reset). Wired alongside the fixed-weight
heuristic scorer in LoginAttemptAnalysisService; fails open.
- Security alert lifecycle now reaches resolution: POST
/api/audit/security-events/alerts/{id}:resolve with actor-derived resolvedBy,
resolvedAt and note, transition validation (409 on double resolve / ack after
resolve), and an audited SecurityAlertResolved security event persisted in
the same save.
- Removed the dead AuditAnomaly/IAnomalyDetectionService surface (never wired,
never mapped).
- Migration: adaptivebehaviorbaseline table + SecurityAlerts resolution columns.
- Tests: adaptive service (learned deviation, cold start, drift, sensitivity,
persistence, fail-open), login-analysis wiring, resolve lifecycle unit +
PostgreSQL HTTP tests incl. auth denials.
Refs #200
…1008 feat(authz): graph-based permission visualization and impact analysis (issue #334)
…359) User-context logging criterion for #359: evaluation records now carry the acting user's role attribution alongside user and tenant. - PermissionEvaluationRecord gains Roles (IReadOnlyCollection<string>): resolver role attribution per the #330 contract - direct roles by name, hierarchy-inherited roles marked ' (inherited)'; never null; empty for anonymous actors; capped at 32 distinct entries with a '+N more roles' overflow marker (PermissionEvaluationRoles helper). - REST [Authorize] decorator resolves attribution once per check via IRbacPermissionResolver and writes Roles into the audit metadata. - GraphQL projectAuthorize directive resolves attribution (null-safe, cached per request) and records it on every Allow/Deny/Error record. - Durable audit sink serializes normalized Roles into metadata; the log service funnel normalizes roles before any sink or monitoring event, and the monitoring event now includes roles=[...]. - PermissionEvaluationLogs table gains a required text[] Roles column (migration AddPermissionEvaluationRoles359; schema.dbml regenerated). Tests: roles on allow and deny (REST + GraphQL), inherited-role marking, 32-entry cap with overflow marker, empty-for-anonymous, entity round-trip.
…th2 client-credentials, veto-only layer) Adds the externalized authorization-decision seam for issue #146: - IExternalAuthorizationDecisionProvider abstraction (Allow/Deny/NotApplicable + reasons) in Identity.Authorization Abstractions, platform domain-free. - ExternalAuthorizationOptions under Authorization:ExternalDecision: disabled by default (zero-overhead no-op), HTTPS-only endpoints, OAuth2 client-credentials token endpoint + client id/secret, timeout, decision cache TTL, FailMode Observe|Enforce (Enforce default: unavailable external decisions deny, fail-closed). Enabled config is validated on host startup. - HttpExternalAuthorizationDecisionProvider: token acquisition with single-flight caching, JSON decision requests, tolerant response mapping, never-throw contract, short-TTL decision cache. - EffectivePermissionResolverService consults the provider for every locally-allowed permission after all local layers and evaluation extensions, immediately before DENY-WINS: external deny overrides a local allow; external allow/not-applicable/missing never grants nor overrides a local deny (veto-only, grants stay local); static wildcard non-deniable; throwing provider fails the permission closed. - docs: effective-permission-resolution.md layer 9 + precedence table + config/fail modes + deferred SAML authorization-assertion integration; authorization-options-configuration.md cross-reference; disabled section in appsettings.json. - tests: provider wire/fail-mode/cache units, resolver-chain precedence integration, options validation and DI wiring (HttpMessageHandler double, no real outbound calls). Fixes #146
…esolver tests Resource-scoped resolution exercises layer 8 (IResourcePermissionService); without a setup Moq returns null and the resolver foreach throws NRE. Returns an empty grant list like the base resolver test suite does.
…ser/tenant Guid logging CodeQL taints the user id from the SystemAccountId comparison into the established audit logging of user/tenant Guids (same-rule alerts 917-919 are already open on develop for this file). The six PR-flagged sinks are log statements; suppress with inline codeql comments rather than dropping audit context.
Five eslint-disable entries no longer match any reported violation after merging origin/develop (capability-based course checks replaced the braced legacy guards they had suppressed). Re-ran with --prune-suppressions; lint and the suppression budget ratchet pass.
…s-wide configuration
…ipe/stripe-js-10.0.0 build(deps): bump @stripe/stripe-js from 9.16.0 to 10.0.0
Bumps [@stripe/react-stripe-js](https://github.com/stripe/react-stripe-js) from 6.10.0 to 7.0.0. - [Release notes](https://github.com/stripe/react-stripe-js/releases) - [Changelog](https://github.com/stripe/react-stripe-js/blob/master/CHANGELOG.md) - [Commits](stripe/react-stripe-js@v6.10.0...v7.0.0) --- updated-dependencies: - dependency-name: "@stripe/react-stripe-js" dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
…ipe/react-stripe-js-7.0.0 build(deps): bump @stripe/react-stripe-js from 6.10.0 to 7.0.0
# Conflicts: # apps/web/package.json
… the flagged sink CodeQL inline suppression requires a standalone // codeql[rule-id] comment on the line immediately before the alert location; trailing comments are not honored.
…ailover-20261011 feat(payments): fail-closed gateway selection with PayPal adapter and failover
…an-history-20261010 Configure verified-email sign-in policy and qualify durable security owner notification access. Matching-head hosted acceptance: 8921 distinct cases passed, 0 failed/skipped; 10 policy cases, 18 account/email HTTP cases, 8 owner access cases and 27 security event pipeline/handler/publisher cases included. 15 OpenAPI cases rerun and verified as a subset. All required security/quality/CLA gates passed. Refs #240, #270.
fix(security): resolve PR #696 GHAS findings (CodeQL, Copilot, Codacy)
…ry lifecycle (#806) * feat(payments): automated retry queue with attempt history and recovery lifecycle Implements the issue #415 automation on top of the #782 config layer: - PaymentRetryQueueBackgroundService (opt-in via Payments:RetryQueue:Enabled, hourly by default, MaxBatchSize 100) polling the existing PaymentRepository.GetDueForRetryAsync and dispatching through the existing RetryPaymentCommand path with per-item error isolation; queue dispatches run as a well-known system actor; OTel metrics per the billing webhook pattern. - PaymentRetryPolicyResolver: gateway failure-code classification into hard-decline (non-retriable immediately), insufficient_funds 3d/7d/14d, do_not_honor 1d/7d, processing-error 0/1/2/3d ladders, and the configurable Payments:Retry exponential backoff with jitter for everything else; injected at the handler layer, entity stays policy-free. - PauseForMethodChangeAsync/ResumeAfterMethodChangeAsync around the subscription-checkout method swap: queued retries pause while the instrument changes and resume re-armed against the confirmed method. - Recovery lifecycle: retry exhaustion suspends the subscription through the standard lifecycle with payment_retry_exhausted reason + final-notice dunning event; a successful recovery retry reactivates it, guarded by the recorded suspension reason. - PaymentRetryAttempt history entity + migration + indexes, written by the retry path, exposed via GET payments/{paymentId}:retry-attempts. - Default retry budget raised to 4 per the issue; refund endpoint now coalesces a null amount to the remaining refundable balance instead of 0. Refs #415 * fix(payments): merge-develop reconciliation — snapshot union, fraud-gate ctor wiring, merged-tree OpenAPI client regen - Model snapshot merged as the union of both sides (retry-queue additions onto the fraud/payment-methods develop state); validated with a throwaway ef migration that generated empty Up/Down (model == snapshot). - Fraud-gate test fixtures wired for the retry-policy and dunning ctor params. - Typed client regenerated from the merged tree swagger (retry-attempts endpoint + develop's fraud/payment-method surfaces). Refs #415 * chore(openapi): regenerate client on merged tree (pricing engine + retry queue) Refs #415 * test(payments): deserialize retry-attempt outcome enum as string in integration test The API serializes enums as strings (JsonStringEnumConverter); the test-side ReadFromJsonAsync needed the same converter to read the attempt outcome. Refs #415 * fix(tests): restore Microsoft.AspNetCore.TestHost using accidentally renamed to TestServer Refs #415 * test(payments): adapt retry-budget expectations to the issue-415 default of 4 - Exhaustion loops now iterate the payment's actual MaxRetries instead of the legacy hardcoded 3 (order-payment replacement tests, entity transition test). - The insufficient-funds ladder expectation uses rung 2 (7d) for a first-retry failure, matching ladder indexing by retry count. - The no-subscription lifecycle test builds a genuine subscription-less payment. - The system-actor dispatch test uses a mutable accessor fake so the Set/restore of the actor context is actually observable. Refs #415 * chore: retrigger PR verify workflow (synchronize event did not fire for 5e31ccb) Refs #415 --------- Co-authored-by: closeout-agent <closeout-agent@gameguild.local>
… 4217 + features schema validation, compare handler, IsActive leak fix, list caching, delete guard, ViewAll gating (issue #416) - Slug auto-generation: Slug optional on create/full-update; SubscriptionPlanSlugGenerator (diacritic-stripping, hyphen-collapsing, 50-char truncation, -2/-3 collision suffixes) - Currency: create validator now enforces the payment-supported ISO 4217 allowlist (CurrencyCodes moved to the shared commerce contracts module) - Validation holes closed: name 3-100, prices >= 0 on pricing PATCH + full update, limits >= 0, features JSON schema (flat object, scalar leaves, typed well-known keys), trial period 0-90 (TrialPeriodDays now settable via full update), full-update validator (previously none) - CompareSubscriptionPlansQueryHandler: feature matrix over features JSON + limit columns; POST :compare no longer 500s with 'handler not registered' - GetActiveAsync/GetFeaturedAsync now filter IsActive (public list leak) - 5-minute sized-entry plan-list cache on active/featured queries, invalidated by every plan mutation command - Delete guard: explicit active-subscription check with archive-instead error (FK backstop) - ViewAll gating: seeing inactive plans now requires monetization:view-analytics - Snapshot regression tests: plan repricing never touches existing subscription amounts, renewal quotes, or payment matching; archived plans keep subscriptions billable
|
All required CI gates now green (PR Required Gate ✅ — API verify, Web verify, OpenAPI, Economy, Migration, Testing Lab all passing on head). CodeQL/Codacy report-only failures tracked separately: CodeQL deltas triaged (7 false positives dismissed with justifications, 1 real fix landed), Codacy issues being worked in #811/#813–#816. @migmoroni @mathrmartins — this develop→main release PR is ready for your review. 2 approvals needed (branch protection: 2 approvers, last-push approval required — my last push was the CLA bot-allowlist + CodeQL triage commits). |
…ft validation (#809) * feat(billing): durable pricing-decision ledger entity, writer and read surface (issue #400) - PricingDecisionLedgerEntry append-only table with full breakdown JSON (jsonb), correlation (order/line/payment/subscription/cycle/product), actor + tenant context, engine version stamp and unique idempotency key; migration + indexes - IPricingDecisionLedgerWriter (GameGuild.Commerce contracts) + fail-closed transactional writer with Compliance.Audit publishing (PricingDecision.Recorded) - Paginated filterable read API /api/v{v}/billing/pricing-discounts-ledger (SystemAdmin, matching sibling billing events surface) - Effectiveness analytics: redemptions, discount vs baseline revenue, top promo codes and pricing rules by impact, stacking outcomes, drift health - Drift validation command: recomputes sampled entries' final from breakdown, stamps Verified/Drifted * test(billing): ledger writer, drift validation, repository, wiring and controller tests - Writer: persist+replay+fail-closed+audit publishing (SQLite relational harness) - Drift: tampered final flagged, consistent verified, unparseable skipped+drifted - Repository: exact-code filter (no substring), tenant/order/subscription/window filters, effectiveness aggregation, sampling prefers never-validated - Wiring: AddProductToOrder ledger entry + rejection fails closed; renewal quote records; ApplyPromoCodes records when correlation key exists, skips previews - Controller: SystemAdmin policy, issue #400 route, query passthrough, defaults * chore(client): regenerate OpenAPI client for the pricing-decision ledger surface (issue #400) * fix(billing): count unparseable breakdowns as drifted; align ledger tests with Moq callback contracts - Drift validation: unparseable breakdown entries increment DriftedCount (they were stamped Drifted but reported as neither verified nor drifted) - Controller tests: Callback delegates typed as IRequest<TResponse> to match ISender.Send's signature (strict Moq validation rejected concrete types) - Writer test: zero-quantity breakdown must bypass Empty()'s normalization - Repository window test: deterministic timestamps for every seeded entry * fix(subscriptions): suppress InMemory TransactionIgnoredWarning in renewal integration hosts — renewal quotes now append a pricing-decision ledger entry (issue #400) inside a DB transaction, and the in-memory provider rejects transactions by default, turning renewal POSTs into 500s (root cause of the 3 Economy-gate Subscriptions.IntegrationTests failures)
…k reconciliation (#810) * feat(payments): refund entity lifecycle, approval workflow, dispute surface, webhook reconciliation and statistics - Refund entity with Pending/Approved/Rejected/Processing/Completed/Failed lifecycle, retry-safe idempotency key unique per payment, and migration (also materializes payment_disputes + dispute_evidence tables and the GatewayDisputeId/EvidenceDueBy reconciliation columns) - RefundProcessingService: one shared approval-aware core for the immediate, workflow and accept-dispute paths (revenue event + balancing ledger entry + audit trail emitted exactly once) - RequestRefundCommand/ApproveRefundCommand/RejectRefundCommand with the payments:refunds:approve permission, configurable manual-approval threshold (Payments:Refunds) and approver email notifications - DisputesController HTTP surface for the existing dispute CQRS stack plus accept/challenge endpoints - StripeDisputeEventConsumer reconciles charge.dispute.* webhooks into local disputes; accepting a dispute records the refund without double-charging the gateway; IPaymentGateway gains dispute accept/challenge (Stripe impl) - PartiallyRefunded payment status transition; refund/dispute statistics endpoints over the refund and dispute ledgers References #419 * fix(payments): implement dispute members on the economy Stripe stub * chore(client): regenerate API client with refunds and disputes surface References #419 * fix(payments): reject accept-dispute when the amount exceeds the remaining refundable Fails before closing the dispute at the gateway so an over-refund can never be acknowledged remotely and then fail to record locally. References #419 * fix(payments): repair six Payments unit-test failures — restore Stripe ctor null-guard contract (develop test demands ArgumentNullException on null intent factory), align refund approval tests with fail-closed tenant guards (seed actor tenant, queue before unstubbed gateway), expect PartiallyRefunded on partial refunds (issue #419), correct dispute resolution-hours expectation to creation-to-resolution average * fix(migrations): union the model snapshot for retry attempts over the refund branch The develop merge resolved ApplicationDbContextModelSnapshot with develop's version, dropping the refund/dispute model; restoring the branch snapshot dropped the retry-attempt model from #806. Hand-unify: IsRetriable + Status,NextRetryAt index on Payment, the PaymentRetryAttempt entity and its relationship. --------- Co-authored-by: Matheus Martins <matheusmartins@users.noreply.github.com>
…plan-crud-20261011
…rning-clean after develop merge
…eep uppercase currency format rule (issue #416)
…son query (issue #416)
…-20261011 feat(subscriptions): plan management hardening
…nnot outlive expiring grants (#817) * fix(authz): bulk permission cache expiry envelope (fail-closed) Cached bulk permission-check decisions previously stored raw booleans with the configured cache TTL and no grant-expiry boundary, so a cached allow could keep serving after its grant's ExpiresAt passed (no mutation, no security-version bump, no invalidation) until TTL eviction. - Cached bulk values are now CachedPermissionDecision records carrying EffectiveUntilUtc: the earliest ExpiresAt among grants that conveyed or denied the requested permission for that decision (min across participating grants). - Writes clamp each entry's TTL to the floored remaining grant lifetime via a new per-entry-TTL SetManyValuesAsync overload; the cache layers cap explicit TTLs at the configured TTLs so they only ever shorten. - Reads treat an entry whose envelope boundary has passed as a miss (fail closed) and re-evaluate from the authoritative grants; a decision whose floored remaining lifetime is already zero is returned but never cached. - HybridPermissionCache.SetAsyncCore now enforces the documented never-extends contract at L1 as well (previously only L2). - Bulk cache key bumped bulk:v1 -> bulk:v2 so pre-envelope boolean entries are never read again. - Docs: authorization-cache-configuration.md gains the bulk cache expiry envelope semantics and why load evidence stays in the BenchmarkDotNet performance projects. References #309 * wip: salvage at quota wall 2026-10-12
…tes (#812) * chore(cla): add Contributor License Agreement and cla-bot configuration Adds CLA.md adapted from the Openinary CLA v1.0 template and a .clabot config seeding the approved contributor list with all verified GitHub accounts from full-branch git history (8 users). cla-bot GitHub App still needs manual installation on gameguild-gg/gameguild. * chore(client): regenerate OpenAPI client for permission-graph endpoints (issue #334) * fix(test): parse DeletedAt JSON value instead of substring in soft-delete audit assertion * fix(auth): collision-safe ACL cache keys and time-bound stale-permission guards - Fingerprint the free-form resourceType/resourceId segments of ACL cache keys with SHA-256 (AclCacheKeys), mirroring the bulk permission key builder, so delimiter-bearing identifiers can no longer collide two distinct resources onto one cached access decision; invalidation patterns match the fingerprinted segments (#353). - Clamp cached ACL decisions to the earliest effective grant expiration: the underlying evaluator reports ExpiresAt via the new ITimeBoundAccessControlListEvaluation capability, cached copies carry the boundary (CachedAclDecision), L1 and L2 writes clamp their TTL to it, and every serve re-checks it, which also covers L2-to-L1 promotions and entries written before the boundary was tracked (#354). - Add the post-query security-version recheck to the legacy GetAccessLevelAsync path, mirroring EvaluateAccessAsync: decisions evaluated across a concurrent mutation are retried instead of cached or returned (#354). - Unit tests: delimiter non-collision for user/subject keys plus end-to-end cache separation and fingerprint-aware resource invalidation; stale-allow security tests with a controlled clock on both evaluation paths, TTL-clamp verification, lapsed L2 promotion, legacy in-flight version race, and never-caching expired decisions. * fix(tests): drop ConfigureAwait(false) from ApiKeyLifecycle test methods (xUnit1030) The Economy Release Gate builds the full solution with TreatWarningsAsErrors=true; xUnit1030 flags ConfigureAwait(false) in test methods because it can bypass xUnit parallelization limits. Omit it on the two affected awaits; the private helper is untouched. * docs(auth): document Redis authorization-cache backup and disaster recovery - New section in authorization-cache-configuration.md: RDB-vs-AOF tradeoffs, versioned-key restore safety, BGSAVE backup procedure, database-authoritative restore steps, prefix-scoped flush (SCAN+UNLINK, never FLUSHALL), and failover monitoring guidance wired to the existing Prometheus/Alertmanager cache alert rules (#353). - compose.yaml: keep AOF and add a periodic RDB snapshot policy (--save 900 1) with pointers to the documented procedures. - Document the fingerprinted ACL key segments and the time-bound TTL clamp/serve-time recheck behavior. * feat(audit): adaptive anomaly detection and security alert resolution lifecycle - AdaptiveAnomalyDetectionService: online statistical learning (EWMA baselines per subject over hour-of-day circular stats, IP information content, log inter-arrival cadence) with z-score deviation, configurable sensitivity, cold-start abstention, persisted per-subject state and drift handling (exponential decay + stale baseline reset). Wired alongside the fixed-weight heuristic scorer in LoginAttemptAnalysisService; fails open. - Security alert lifecycle now reaches resolution: POST /api/audit/security-events/alerts/{id}:resolve with actor-derived resolvedBy, resolvedAt and note, transition validation (409 on double resolve / ack after resolve), and an audited SecurityAlertResolved security event persisted in the same save. - Removed the dead AuditAnomaly/IAnomalyDetectionService surface (never wired, never mapped). - Migration: adaptivebehaviorbaseline table + SecurityAlerts resolution columns. - Tests: adaptive service (learned deviation, cold start, drift, sensitivity, persistence, fail-open), login-analysis wiring, resolve lifecycle unit + PostgreSQL HTTP tests incl. auth denials. Refs #200 * fix(auth): capture role attribution in permission evaluation records (#359) User-context logging criterion for #359: evaluation records now carry the acting user's role attribution alongside user and tenant. - PermissionEvaluationRecord gains Roles (IReadOnlyCollection<string>): resolver role attribution per the #330 contract - direct roles by name, hierarchy-inherited roles marked ' (inherited)'; never null; empty for anonymous actors; capped at 32 distinct entries with a '+N more roles' overflow marker (PermissionEvaluationRoles helper). - REST [Authorize] decorator resolves attribution once per check via IRbacPermissionResolver and writes Roles into the audit metadata. - GraphQL projectAuthorize directive resolves attribution (null-safe, cached per request) and records it on every Allow/Deny/Error record. - Durable audit sink serializes normalized Roles into metadata; the log service funnel normalizes roles before any sink or monitoring event, and the monitoring event now includes roles=[...]. - PermissionEvaluationLogs table gains a required text[] Roles column (migration AddPermissionEvaluationRoles359; schema.dbml regenerated). Tests: roles on allow and deny (REST + GraphQL), inherited-role marking, 32-entry cap with overflow marker, empty-for-anonymous, entity round-trip. * fix(build): clear analyzer warnings in permission expiration and api key tests * feat(authorization): external authorization-decision integration (OAuth2 client-credentials, veto-only layer) Adds the externalized authorization-decision seam for issue #146: - IExternalAuthorizationDecisionProvider abstraction (Allow/Deny/NotApplicable + reasons) in Identity.Authorization Abstractions, platform domain-free. - ExternalAuthorizationOptions under Authorization:ExternalDecision: disabled by default (zero-overhead no-op), HTTPS-only endpoints, OAuth2 client-credentials token endpoint + client id/secret, timeout, decision cache TTL, FailMode Observe|Enforce (Enforce default: unavailable external decisions deny, fail-closed). Enabled config is validated on host startup. - HttpExternalAuthorizationDecisionProvider: token acquisition with single-flight caching, JSON decision requests, tolerant response mapping, never-throw contract, short-TTL decision cache. - EffectivePermissionResolverService consults the provider for every locally-allowed permission after all local layers and evaluation extensions, immediately before DENY-WINS: external deny overrides a local allow; external allow/not-applicable/missing never grants nor overrides a local deny (veto-only, grants stay local); static wildcard non-deniable; throwing provider fails the permission closed. - docs: effective-permission-resolution.md layer 9 + precedence table + config/fail modes + deferred SAML authorization-assertion integration; authorization-options-configuration.md cross-reference; disabled section in appsettings.json. - tests: provider wire/fail-mode/cache units, resolver-chain precedence integration, options validation and DI wiring (HttpMessageHandler double, no real outbound calls). Fixes #146 * test(authorization): mock resource-grant layer in external-decision resolver tests Resource-scoped resolution exercises layer 8 (IResourcePermissionService); without a setup Moq returns null and the resolver foreach throws NRE. Returns an empty grant list like the base resolver test suite does. * chore(authorization): suppress CodeQL clear-text false positives on user/tenant Guid logging CodeQL taints the user id from the SystemAccountId comparison into the established audit logging of user/tenant Guids (same-rule alerts 917-919 are already open on develop for this file). The six PR-flagged sinks are log statements; suppress with inline codeql comments rather than dropping audit context. * chore(lint): prune suppressions orphaned by develop merge Five eslint-disable entries no longer match any reported violation after merging origin/develop (capability-based course checks replaced the braced legacy guards they had suppressed). Re-ran with --prune-suppressions; lint and the suppression budget ratchet pass. * fix(features): satisfy brace and parameter hygiene findings * fix(security): document guarded outbound fetch sites (SSRF FP annotations) * style(scripts): conform pnpm-process and QuickJS sync scripts to lint conventions * chore(client): regenerate OpenAPI client from CI artifact to clear array-style drift * test(payments): serialize Stripe intent creation tests against process-wide configuration * build(deps): bump @stripe/react-stripe-js from 6.10.0 to 7.0.0 Bumps [@stripe/react-stripe-js](https://github.com/stripe/react-stripe-js) from 6.10.0 to 7.0.0. - [Release notes](https://github.com/stripe/react-stripe-js/releases) - [Changelog](https://github.com/stripe/react-stripe-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/stripe/react-stripe-js/compare/v6.10.0...v7.0.0) --- updated-dependencies: - dependency-name: "@stripe/react-stripe-js" dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * fix(authorization): place CodeQL suppressions on their own line above the flagged sink CodeQL inline suppression requires a standalone // codeql[rule-id] comment on the line immediately before the alert location; trailing comments are not honored. * refactor(auth): extract IApiKeyRepository for API-key credential persistence Introduce IApiKeyRepository/ApiKeyRepository mirroring the sibling credential repositories (WebAuthn, ExternalLogin, TrustedDevice, RefreshToken) and move ApiKeyAuthenticationHandler plus the Create/ List/Revoke/Rotate command handlers off direct IApplicationDbContext onto the repository. - GetByKeyHashAsync returns a tracked entity so usage recording and lazy rotation finalization persist through the repository - RotateAsync links the replacement key and transitions the old key (grace window or immediate revocation) in a single save - Register scoped alongside the other credential repositories - Repository persistence tests (in-memory EF, sibling pattern), repository-backed auth-handler tests, and existing lifecycle/ coverage tests updated to the new seams Fixes #266 * fix(web): drop unnecessary non-null assertions in feed announcement parsers The type-aware eslint config merged via #698 flags match[N]! assertions in post-card.tsx (added with the #741 blog embeds) as unnecessary — the RegExpExecArray index access already resolves non-optional after the match guard. Removed the assertions and pruned the orphaned baseline entries; eslint, tsc and the lint budget pass. * feat(auth): notify owners of suspicious sign-ins via NotificationService Fire NotificationType.Security owner alerts (InApp always; Email per INotificationPreferenceService; Urgent) when the authentication risk pipeline confirms: high-risk step-up, brute force (including on failed attempts and on eventual success), and impossible travel on a successful sign-in. The Identity.Authentication module records redacted SuspiciousLoginDetectedV1 durable events gated by Authentication:SecurityNotifications:{Enabled,MinimumRiskLevel=High}; the host-side SuspiciousLoginAlertHandler consumes them within the inbox transaction (RefreshTokenReplayAlertHandler pattern) and queues once-only notifications keyed by the event id (referenceEntityId). TODO stubs in AuthenticationFailedEventHandler and RefreshTokenEventHandler now point at the durable security pipeline. Fixes #247 * fix(auth): rename shadowed scope in suspicious-login integration test * fix(auth): length-prefixed ACL cache keys, wire time-bound capability to the database evaluator CI on #746 exposed two design flaws in the original fix: 1. SHA-256 fingerprinting the resourceType/resourceId segments made the raw identifiers unrecoverable from the key, so substring-based invalidation patterns (CacheInvalidationServiceTests.InvalidateResourceAsync_InvalidatesPattern) could no longer match. Replace the fingerprints with length-prefixed components ({length}:{value}): the encoding stays injective (delimiter- bearing pairs such as ("a:b","c") vs ("a","b:c") still never collide) while the raw values remain embedded, so wildcard/substring invalidation keeps matching. All invalidation pattern builders already flow through AclCacheKeys.BuildResourceSegment and pick up the encoding unchanged. New tests: raw identifiers survive into the InvalidateResourceAsync pattern even when they contain delimiters, and pattern invalidation still evicts delimiter-bearing entries without over-matching delimiter-adjacent or longer-id neighbors. 2. DatabaseAccessControlListService defined EvaluateAccessTimeBoundAsync but never declared ITimeBoundAccessControlListEvaluation, so the cached wrapper's capability check failed at runtime and cached decisions carried no expiry boundary: the TTL clamp and the serve-time revalidation never engaged and time-lapsed grants kept serving cached allows (CachedAclStalePermissionSecurityTests on both evaluation paths). Declare the interface so production ACL evaluations report their earliest effective grant expiration. Also updates AuthorizationCoverageCompletionTests to the CacheAccessLevel (CachedAclDecision, bool) signature introduced with the time-bound guards, and the cache-configuration documentation to the new key format. * fix(auth): seed unique usernames in suspicious-login integration tests The five tests shared one display name, so UsernameSlug.Generate produced the same username and the shared PostgreSQL fixture rejected the second insert with IX_Users_Username. Pass an explicit per-test username like the replay-alert tests do. * feat(auth): remember-me persistent sessions across local, OAuth and Web3 sign-in (issue #265) - nullable RememberMe on all sign-in DTOs (local, polymorphic, OAuth variants, Web3) flowing through commands to issuance - new Jwt:PersistentRefreshTokenExpirationDays (default 30) alongside the standard lifetime; validated in JwtOptions/AuthenticationOptions/SessionPolicy and wired through JwtOptionsResolver and SecurityServiceCollectionExtensions - single policy-aware RefreshTokenLifetimeResolver replaces the hardcoded AddDays(7)/fallback chains in LocalAuthService, OAuthAuthService, Web3AuthService and the legacy AuthenticationEndpoint - persisted refresh-token rows now carry the caller-resolved deadline so persistent sessions survive rotation; rotation renews the originating duration instead of collapsing onto the standard lifetime - web: remember-me checkbox on both sign-in forms; client forwards rememberMe to the API and applies a session-vs-persistent cookie storage policy - tests: per-path expiry, rotation renewal, logout revocation, resolver matrix; regenerated OpenAPI client * feat(auth): record and manage fine-grained OAuth scope grants (#250) - ExternalLogin gains GrantedScopes (JSON array), ConsentedAt and ConsentVersion (OAuthConsentVersions.Current = 1); additive migration. - OAuthService.ResolveAuthorizationScopes single-sources the exact scope list authorization URLs embed; link (Google/Discord), callback and sign-in auto-link paths persist it with a consent stamp, re-recording consent only when the scope set changed (first-consent preserved). - HEAD /v1/auth/external-logins now also emits X-Granted-Scopes: 'provider=iso-consent-timestamp|consent-version|url-encoded-scopes'. - New GET /auth/external-logins/{provider}/link-preview (authorization- time consent screen data) and POST .../scopes:revoke (per-scope revocation, idempotent, audited via structured log; unlink remains the documented whole-provider full revocation). - Typed client regenerated; web connected-accounts card shows the consent record (date, terms version, scope chips) with per-scope revoke wired to a server action; en-US/pt-BR strings added. - Tests: persistence round-trip, callback/link recording, list shape, revocation (handler/controller/service/repository), header shape, and web consent-header parser unit tests. * feat(billing): comprehensive webhook security validation (#393) - Google Pay webhook processing: bearer-JWT (RS256) verification service, idempotent inbox service, command handler, configuration with startup key/tolerance validation - Security regression fix: BillingWebhooksController is no longer class-level [AllowAnonymous]; only the four provider callbacks stay anonymous (per-action registry entries), while webhook event inspection, manual retry, and the new security surface require the SystemAdmin policy - CIDR source-IP allowlist (IPv4/IPv6, fail-closed 403 when configured, disabled by default) enforced by WebhookSourceSecurityFilter before the payload is read - Webhook-specific rate limiting policy (per-IP fixed window, default 120/min) applied to all provider callbacks - Security events (signature failures, allowlist rejections, replays, blocks) published to the Compliance.Audit SecurityEventPipeline with taxonomy entries and best-effort delivery - Threshold-based suspicious-activity auto-blocking (config-gated, fail-open default) - GET /api/v1/billing/webhooks/security admin monitoring summary (allowlist state, blocked sources, pipeline delivery health) - Unit tests for every addition (CIDR math, allowlist, monitor, filter, publisher, verification service, Google Pay service, summary handler, controller authorization metadata, rate limiting policy, config validation, DI registration) * chore(openapi): regenerate client for the billing webhook security endpoint * fix(auth): record suspicious-login alerts outside the sign-in transaction The step-up response is a business failure and a denied attempt throws, so UseCaseOperationBehavior rolls back the request transaction in both cases and an outbox row written through the ambient scope was lost. The publisher now records through an independent service scope with its own database context - the same pattern RefreshTokenLifecycleRecorder uses for rejection audits - so the alert event commits regardless of the sign-in outcome. Add a unit test that a missing producer registration degrades to a logged no-op. * fix(ci): exclude benchmark harnesses from affected API test selection The affected-test selector's test-project pattern matched *PerformanceTests directories, which are BenchmarkDotNet executables (IsTestProject=false), not test suites. When a PR touched benchmark sources (e.g. #746), 'dotnet test' produced no TRX evidence and the API verify job failed with 'Missing test evidence'. Guard the selection and pin the behavior with a regression test. * fix(root): pnpm overrides * fix(dev): dev-fast quickjs * fix(api): data tests * chore(client): regenerate typed client from merged develop surface * test(security): adversarial deny-by-default validation suite for #327 Adds GameGuild.API.SecurityTests: HTTP-level adversarial scenarios against the real host (production JWT pipeline, real PostgreSQL) covering privilege escalation, tenant crossover, token forgery/tampering, unguarded-route sweeps, grant-revoke TOCTOU races, post-revocation stale-allow attempts and JIT/impersonation elevation seams. Ships the findings report at docs/security/deny-by-default-adversarial-validation.md documenting the exercise as an internal adversarial validation pass that an external professional pentest supersedes. * chore(openapi): regenerate client against merged develop * chore(client): refresh OpenAPI client metadata after develop merge * fix(ci): read pnpm security gate config from pnpm-workspace.yaml 931415eb49 moved the pnpm settings (patchedDependencies, onlyBuiltDependencies) from root package.json to pnpm-workspace.yaml, but the dependency security-gate tests still asserted against manifest.pnpm.*, so every job's install verification failed with 'Cannot read properties of undefined (reading patchedDependencies)' and killed Web/API/Economy/OpenAPI/Migration/Repository-policy checks on the release PR. The gate now parses the canonical pnpm-workspace.yaml sections (fail-closed on malformed entries) and the emception release policy asserts the same pinned values there. * fix(emception): re-pin toolchain lock hash after canonical curl overlay review Commit 709267e345 updated toolchain/toolchain.lock.json (canonical curl overlay + reviewed LLVM archive variant) without updating the pinned toolchainLockHash, failing 'adapter identity matches the reviewed toolchain lock and ABI' on every Emception-detected PR since Oct 7. Recompute the hash from the committed lock (serializeToolchainLock ordering) and update both pins (grading binding + server adapter). * fix(audit): map acknowledge transition violations to 409 Conflict Acknowledging a resolved alert threw SecurityAlertTransitionException, which escaped as 500 because the endpoint only caught UnauthorizedAccessException. Mirrors the resolve endpoint's handler and declares the 409 response type. * fix(ci): pull CI PostgreSQL from the public ECR Docker Hub mirror The four database-backed PR verify jobs (API verify, Economy Release Gate, Migration compatibility, OpenAPI client consistency) died on Docker Hub anonymous pull rate limits for postgres:17-alpine — GitHub's shared runner IP pool is throttled, and reruns lose the same dice roll. Pull the identical official image from public.ecr.aws (verified manifest) instead. Fixture expectations updated to match. * fix(audit): remove stray brace from acknowledge handler * feat(auth): sign-in compliance verification gate (issue #267) Adds an allow/challenge/deny compliance hook to local sign-in: - ISignInCompliancePolicy + SignInComplianceDecision/Outcome/Reasons and SignInComplianceContext in GameGuild.Identity.Authentication (domain-free); module registers a default allow-all policy via TryAdd. - LocalAuthService evaluates the policy after credential validation and tenant-access checks, strictly before token generation or session creation. Deny -> AccessDeniedException (generic 403) + Authentication.ComplianceDenied audit; Challenge -> step-up SignInResponse + audited; evaluation failures fail closed only in Enforce mode. - Authentication:Compliance:SignInGate config (Enabled=false default; Mode=Off/ChallengeOnly/Enforce); ChallengeOnly downgrades deny to step-up. - Host adapter KycAndHoldSignInCompliancePolicy (GameGuild.API/Core/Compliance) composes IKycService with IComplianceHoldStore.IsActiveAsync: active hold or KYC Rejected/Suspended -> Deny; Pending/InProgress/Expired/missing or lapsed approval -> Challenge; else Allow. - Audit events carry only coarse reason codes (no KYC detail leakage). - Tests: policy matrix units (API.UnitTests) + LocalAuthService gate integration incl. deny-before-session and challenge-before-token ordering (Identity.Authentication.UnitTests). * test(auth): finish client-certificate test fixes for #245 - Use X509CertificateLoader.LoadPkcs12 instead of the obsolete X509Certificate2(pfx) constructor (SYSLIB0057 path) in cert fixtures. - Issue test client certificates via the CertificateRequest.Create(issuer) overload instead of (issuerName, copiedKey), which disposed the authority key before the issued certificate was exported. - Replace cert Export(CertPem) (no such content type on this target) with PemEncoding-based PEM export in scheme registration tests. - Add missing 'using Xunit;' (project convention: explicit per-file usings) and await-using/nullable annotations flagged during the develop merge. - Merge latest develop (CI mirror/config fixes); no behavioral conflicts. * test(auth): seed four brute-force failures so the request reaches the analysis The lockout action filter (MaxFailedAttemptsPerHour=5) counts stored failures before the sign-in action runs and short-circuits a generic 401, so seeding five failures prevented the action - and its owner alert - from ever running. Seed four and let the request under test be the fifth failure, which is exactly when production raises the alert: the brute-force detector also counts the attempt the failing request records. * fix(auth): avoid nullable-annotation mismatch in compliance policy test mocks Result<T>.Success infers TValue from the argument, which dropped the nullable annotation and tripped CS8620 through Moq's Returns/ReturnsAsync. Use Result.Success<T?>(...) with Task.FromResult so the mock matches the Task<Result<UserKycVerification?>> contract warning-free. * test(audit): remove orphaned AuditAnomaly tests along with the deleted entity The adaptive-detection PR removed the dead AuditAnomaly entity; its unit tests and enum-count assertions referenced the deleted type and failed to compile. Anomaly detection is covered by the adaptive detection service and the durable security-event pipeline tests. * fix(auth): repair PR CI — cert fixture validity race, eager PEM fail-closed, OpenAPI client drift - Kestrel fixture / test factory: derive issued-leaf validity from the issuer certificate's own NotBefore/NotAfter. Computing leaf bounds from UtcNow at test time races CA creation by a second, making CertificateRequest.Create throw 'notAfter is later than issuerCertificate.NotAfter' (4 Kestrel integration failures in CI). - AddClientCertificateAuthentication(settings): parse the CA allowlist eagerly so an unparseable PEM entry fails closed at registration time, matching the documented contract and the fail-closed registration test (previously the parse ran inside the deferred options lambda and never threw during SetupAuthentication). - Regenerate OpenAPI typed client for CredentialType.Certificate and the /v1/oauth/token certificate-auth doc note (applies the exact drift the CI generator reported for this tree). * fix(ci): probe for free Testing Lab ports and mirror its PostgreSQL image The Testing Lab browser E2E picked one random port per band in 42000-44999, which sits inside Linux's default ephemeral range (32768-60999); when the runner's own outbound socket held the candidate the run aborted with 'PostgreSQL port NNNNN is already in use' and failed the required gate (observed on the release PR). Probe up to ten candidates per band before failing. Also pull its disposable postgres from the public ECR mirror like every other CI database, dodging the Docker Hub anonymous rate limit. * fix(security): remediate findings from adversarial deny-by-default validation - F-2 (critical): JIT elevation approve/deny/revoke/request handlers now derive the acting identity from IActorContextAccessor and reject body-supplied identities; approval additionally requires system- or same-tenant-admin authority (tenant from the stored elevation, never the body). Closes a self-approval-with-spoofed-reviewer privilege escalation found by the adversarial suite (unit tests: JitElevationActorIdentityGuardTests). - F-3 (high): map PermissionAuditLog (PermissionAuditLogConfiguration) and add migration AddPermissionAuditLog — the audited-permission-mutation invariant previously threw against a real database because the entity was never mapped and the table never existed. - harness: HS512 forge key padded to 512 bits; mutation endpoints probed with their real verb (405 masked the auth layer); delegated-admin probe body carries the validator-required allowedOperations; denial diagnostics include a response snippet. * fix(auth): register allow-all compliance policy by instance factory AllowAllSignInCompliancePolicy intentionally has a private constructor (stateless shared instance), which fails container scope validation when registered by implementation type — the CI host boots with ValidateOnBuild and could not construct it. Register the shared Instance through a factory instead. * test(auth): make the brute-force alert test self-diagnosing The suspicious-login PostgreSQL suite cannot run under the local build gate, so embed the pipeline's own diagnostics in the failure message: response body (distinguishes the lockout filter's 401 from the sign-in throw path), stored attempts, outbox rows for the user aggregate, and host log output captured by a recording logger provider. * test(security): probe mutation endpoints with contract-correct bodies TenantId is a value object: its JSON shape is { value: <guid> }. The flat GUID string died in model binding (400) before the authorization guard was exercised, so the self-grant and cross-tenant grant probes were testing nothing about authorization. * chore(client): refresh OpenAPI client metadata after develop merge * fix(billing): set up GetBlockedSources stub in pipeline-failure summary test Loose Moq mocks return null for IReadOnlyList<T> results; the handler's summary projection then failed with ArgumentNullException instead of exercising the intended pipeline-read-failure tolerance path. Stub the monitor explicitly, matching the other summary handler tests. * test(auth): bind Kestrel test host to 127.0.0.1:0 instead of ListenLocalhost(0) ListenLocalhost cannot allocate a dynamic port (it would have to bind the IPv4 and IPv6 loopback with a single port), so all four mTLS integration tests failed at host startup in CI with InvalidOperationException. Bind IPAddress.Loopback explicitly; the https URL is still taken from app.Urls. * feat(auth): deliver permission-expiration alert notifications and docs (issue #331) PermissionExpirationNotification was published by the worker but had zero INotificationHandler consumers, so expiring grants produced no user-visible alert. Add PermissionExpirationAlertHandler in the API composition root (same placement and pattern as RefreshTokenReplayAlertHandler): resolves the recipient via IUserRepository, queues an in-app Notification plus an email when the user has an address, maps Expired -> Urgent / Upcoming -> Normal priority, and honors per-channel notification preferences. Delivery is gated on Authorization:PermissionExpiration:Enabled; queueing failures are logged, not thrown (the publisher stamps dedup metadata before dispatch and has no redelivery mechanism). Also add docs/auth/permission-expiration.md covering grant-time defaults, per-type overrides, wildcard precedence, worker cadence and idempotency, notification delivery, administrative endpoints, remediation flows and best practices, following the monetization-permissions.md conventions. * test(auth): add PostgreSQL HTTP integration tests for monetization permission gates (issue #346) The monetization gates (monetization:view-analytics, monetization:configure, monetization:monetize) were enforced and unit-tested, but no integration test exercised them end-to-end. Add MonetizationPermissionHttpTests against the real HTTP pipeline and PostgreSQL: - ViewAnalytics (GET /api/metrics/product): 401 anonymous, 403 without the grant, 200 with it, cross-tenant grants fail closed, SystemAdmin bypass. - Configure (POST /v1/subscription-plans): 403 with the wrong monetization permission and with an expired grant, 201 with the grant (plan persisted), SystemAdmin bypass. - Monetize (PUT /v1/products/{id}/pricing): 403 without products:pricing:manage at the endpoint gate, fail-closed denial with no persistence when only the endpoint permission is present (the command-layer monetization:monetize gate refuses the dispatch), 200 with both gates satisfied, SystemAdmin bypass. - Decision audit: PermissionOperationType.Check rows are asserted for granted and denied endpoint-level decisions. Document the new suite in docs/auth/monetization-permissions.md. * fix(auth): keep adaptive anomaly in cold start until the baseline minimum Cold start ended after the first persisted observation instead of at MinimumObservations, so early assessments were scored against a one-sample learned model and reported LearnedBaseline. The assessment now stays on the cold-start fallback across the whole learning window while observations keep accumulating. Regenerates the OpenAPI client from the CI-captured swagger for the acknowledge 409 declaration. * chore: re-trigger PR verification workflow * fix(auth): align legacy sign-up handler with develop signature and cover remember-me in web tests - the develop merge left SignUp with an IConfiguration parameter that develop had removed (reflection-based handler test expects the 4-parameter shape); surface the module's policy-resolved RefreshTokenExpiresAt instead of recomputing a deadline in the endpoint shell - web sign-in tests now expect the rememberMe flag forwarded to signIn and a new test covers the remember-me opt-in checkbox * test(auth): surface mTLS integration failure diagnostics on CI The positive-path Kestrel client-certificate test returns Unauthorized on the Linux runner with no visibility into which stage denied the request (cert not negotiated / chain untrusted / EKU / binding). Make the test endpoint's 401 body re-run the chain policy and report certificate presence, chain status details, and the thumbprint comparison; capture server logs with an in-memory logger provider and dump both in the assertion message when the happy path fails. * test(auth): brute-force alert needs committed history above the lockout bar The attempt the failing request records shares the request transaction, which the sign-in failure rolls back, so the detector must see the threshold from committed history. Seed five committed failures and raise AuthenticationSecurity:MaxFailedAttemptsPerHour for this test so the lockout filter (same threshold) lets the request reach the action. Diagnostics now also dump the last captured host logs unfiltered. * ci: retrigger PR verify * fix(auth): correct #250 test defects flagged by CI (#760) - Handlers test: ResolveAuthorizationScopes mock setups now match the provider case-insensitively, mirroring the real OAuthService (the link-preview handler passes the caller's casing through); the previous exact-match setup returned Moq's empty-array default for 'Discord'. - Repository round-trip test: expected list contained a stray prose string ('JSON list must round-trip in order') leaked into the assertion; replaced with a real third scope token included in the serialized input so order preservation is genuinely asserted. - connected-accounts-card: wrap async onClick handlers in void arrows to satisfy @typescript-eslint/no-misused-promises (2 errors). * test(auth): select the mTLS client certificate explicitly on Linux CI diagnostics showed the Linux TLS client never offered the certificate (the server-side Connection.ClientCertificate stayed null while the handshake itself succeeded), i.e. automatic certificate selection declined to pick one. Install a LocalCertificateSelectionCallback that returns the fixture certificate unconditionally so the mTLS integration test is independent of platform-specific auto-selection rules. * chore(openapi): regenerate client metadata against merged develop * docs(auth): clarify the lockout-override comment in the brute-force test * fix(ci): retry container image pulls and restore the 25m integration budget Both Docker Hub and public ECR intermittently throttle GitHub-hosted runners' shared egress IPs (toomanyrequests), killing required-gate jobs at container start — postgres-app died this way twice tonight even via the mirror. Pull each CI image once with retry-and-exponential-backoff before docker run so container starts never reach the registry (Economy gate x4 containers, affected-API template, Testing Lab). Also remove a stray duplicate override that clamped the whole-solution API.IntegrationTests deadline back to 12m after the separate 25m integration budget was set — the suite needs ~17.6m and was killed at 12m01s on the release PR. The deadline fixture now pins the 25m budget. * fix(auth): map PermissionAuditLog so permission decision audits actually persist (issues #331, #346) The new MonetizationPermissionHttpTests failed in CI with 'Cannot create a DbSet for PermissionAuditLog because this type is not included in the model': the entity was written by PermissionAuditLogRepository against IApplicationDbContext but had no IEntityTypeConfiguration anywhere and no table in any migration, so every runtime audit write (endpoint Check decisions, expiration audits, permission mutations) failed and was swallowed by the callers' best-effort catches. Decision audit therefore never persisted. - Add PermissionAuditLogConfiguration (indexes on TenantId/UserId/Timestamp plus (UserId, PermissionType, Timestamp), TenantId value conversion per the AccessReviewCampaign pattern, OperationType as int, uncapped text payloads so an audit write can never fail on a caller-supplied string length). - Register it in AuthorizationModule.ConfigureAuthorizationModel and ship the AddPermissionAuditLogs migration; regenerate docs/schema.dbml (445 tables), verified with db:dbml:verify (0 drift) and db:dbml:check. - MonetizationPermissionHttpTests: seed the SystemAdmin actor as a user row so the Products.CreatorId foreign key holds for the bypass case. * chore(ci): retrigger PR Verify (#250) * test(auth): negotiate TLS 1.2 for the mTLS integration client Even with an explicit LocalCertificateSelectionCallback the Linux TLS stack leaves the server-side Connection.ClientCertificate null, which matches the known gap where optional client certificates negotiated under TLS 1.3 are not surfaced. Pin the test client to TLS 1.2 so the CertificateRequest/Certificate exchange follows the classic path. * docs(db): regenerate schema.dbml for the PermissionAuditLogs table develop's 20260923333152_AddPermissionAuditLog migration landed without a matching docs/schema.dbml regeneration; db:dbml:check reported the file stale. Regenerated against a clean migrated database (445 tables) — db:dbml:verify reports zero model drift and db:dbml:check now passes. * test(auth): stabilize remember-me lifetime tests against shared clock and replay containment - read SystemClock.UtcNow (the clock the services use) before each act instead of DateTime.UtcNow inside the assertions: parallel classes in this project freeze SystemClock globally (e.g. AuthenticationCoverage CompletionTests pins it two days back), which made the deadline assertions flake by microseconds or whole days - revoked refresh tokens are now denied through replay containment (RefreshTokenContainmentDenial) instead of UnauthorizedAccessException since the refresh-lifecycle rework: assert the containment outcome (Success=false, no tokens) after logout - Web3AuthServiceCovTests: mock the policy-aware 5-arg GenerateRefreshTokenAsync overload used by VerifyWeb3SignatureAsync * chore(ci): retrigger PR Verify run (#250) * test(auth): drive the mTLS integration test through an explicit SslStream The HttpClient stack swallowed the negotiation outcome: on the Linux runner the server saw no client certificate while the client reported nothing, leaving no signal to debug. Perform the TLS handshake with a raw SslStream so the test pins TargetHost, offers the fixture certificate through both ClientCertificates and an explicit selection callback, and reports the certificate the client actually negotiated in failure diagnostics. HTTP remains a plain GET/1.1 over the stream. * test(auth): assert the as-configured fail-closed denial contract for monetization gates CI round 2 showed the deny paths return 500 ProblemDetails, not 403: the endpoint-level ResourcePermissionAuthorizationFilter is registered only when Controllers:EnablePermissionAuthorizationFilter is true, which defaults to false and is set nowhere in the shipped configuration. The enforced gate for all three surfaces is the [AuthorizeRequest] permission on the dispatched command/query; its UnauthorizedAccessException is mapped by the shared exception pipeline to a fail-closed 5xx ProblemDetails. Pin the observed contract instead of an aspirational one: deny assertions expect InternalServerError with an explanatory remark in the test class docs, and add the load-bearing security assertions (no mutation persisted, no metrics payload leaked on denial). Endpoint Check decision-audit rows are not asserted because they are only produced by the (disabled) filter; document that configuration fact in docs/auth/monetization-permissions.md so the audit-logging section states exactly when Check rows occur. * chore(client): regenerate OpenAPI client from merged develop surface * fix(auth): engage the malformed-row fallback during refresh rotation Pass the raw stored CreatedAt into ResolveOriginatingLifetime instead of a clock-sanitized stand-in: legacy rows without a creation instant must fall back to the configured standard duration per the resolver contract, not renew for whatever lifetime happens to remain. Also relax the post-logout containment assertions to BeNullOrEmpty (SignInResponse initializes string fields to empty). * test(authz): pass role-attribution expectation as collection with because clause The denied-decisions test passed its explanatory string as a third expected element via the params overload of FluentAssertions Equal, so the assertion could never pass. Use the collection + because overload. * test(auth): require the client certificate during the mTLS handshake Client-side diagnostics showed the Linux TLS client never negotiated a certificate (SslStream.LocalCertificate stayed null even with an explicit selection callback), i.e. the deferred AllowCertificate request never took effect and the handler saw an anonymous connection. Requiring the certificate up front forces the TLS layer to run the CertificateRequest exchange during the handshake; the certificate-less negative case is now asserted at the TLS layer, while chain/binding/claims behavior stays covered by the handler and controller suites. * test(auth): pin cross-tenant monetization denial as 403 from the tenant guard CI round 3: 403/404 — the remaining assertion expected the cross-tenant deny to surface as the dispatch-level 500, but the tenant-validation layer rejects the non-member with 403 before the CQRS gate is reached. Pin the observed (and stricter) contract. * fix(ci): stop embedding the full needs graph in the required gate env The PR Required Gate step passed toJSON(needs) as an env var, which embeds classify's changedFiles output. On the release PR (2205 changed files, ~183KB) that single env var exceeded the kernel execve limit and bash could not even start — the gate failed with 'Argument list too long' while every child job was green. Pass each dependency's result string individually instead. * chore(client): regenerate typed client from merged surface (#250) After merging develop (billing webhook + compliance endpoints landed while this branch was in review), the OpenAPI capture was re-run locally against the merged tree (Release publish + swagger capture + pnpm generate --force). The generated .gen.ts outputs are byte-identical to the merge result; only the capture hash/timestamp in .metadata.json changed. * fix(auth): resolve the user id on failed sign-ins so brute-force alerts can fire The local sign-in flow only assigned userId after successful password verification, so on a wrong-password attempt against a KNOWN account userId stayed null: failed attempts were recorded without their user, risk analysis skipped the per-user anomaly block, and the brute-force owner alert - gated on userId.HasValue - could never be published. Assign the id as soon as the account is resolved; the password branches keep their existing behavior. Unit regression tests cover alert fired (brute force detected) and not fired (no anomaly) on wrong password. * chore(web): drop stale no-misused-promises suppression (#250) The connected-accounts-card async onClick handlers are now wrapped in void arrows, so the file no longer triggers the rule and ESLint's unused- suppression check (pass-on-unpruned-suppressions off) fails the lint gate. * fix(auth): persist cadence observation count on baseline upsert The UpsertAsync update path copied every learned estimator except CadenceObservationCount, so the persisted baseline kept the count from the initial insert (zero gaps observed) forever. ScoreLearned gates the cadence feature on CadenceObservationCount > 0, which permanently disabled cadence deviation detection after the first upsert. Copy the count alongside the other cadence estimators. * test(authz): construct a genuinely null-roles record for the normalization test CreateRecord coalesces an unset roles argument to the default attribution, so passing roles: null! could never exercise the null-roles normalization under test. Build the record inline with a null Roles value instead. * test(auth): mirror the Kestrel test-suite mTLS client pattern The upstream Kestrel suite hits exactly our failure mode and documents it: 'HttpClient might not send the certificate because it is invalid or it doesn't match any of the certificate authorities sent by the server in the SSL handshake.' Pin the client certificate through the SslStream constructor selection callback, authenticate with a random target host (avoiding the TLS session-resumption cache), and use RequireCertificate + AllowAnyClientCertificate() on the listener, matching the upstream green configuration. * test(auth): mark the alert-handler deleted user via DeletedAt directly SoftDelete() throws for unpersisted entities (Version == 0), which the CI run surfaced for the in-memory user in Handle_WhenUserIsDeleted. IsDeleted is derived from DeletedAt, so set the timestamp directly. * chore(client): regenerate typed client after develop merge (#250) Develop moved again during CI (#758 remember-me regenerated the surface); re-ran the local Release publish + swagger capture + pnpm generate --force against the merged tree. Only the capture hash and the remember-me types from develop's side carry over. * test(auth): associate the request key with issued client certificates CertificateRequest.Create(issuerCertificate, ...) signs the leaf with the issuer but — unlike CreateSelfSigned — does not associate the request's private key with the returned certificate. The fixture then exported the keyless certificate to PFX and re-imported it, so every client certificate carried a public key with no private key: the TLS client could never answer a CertificateRequest, which is why the server saw an anonymous connection on every runner and platform. Associate the key with CopyWithPrivateKey before persisting, in both the Kestrel fixture and the shared unit-test factory. * test(auth): align rotation fixtures with originating-lifetime renewal PR #758 (remember-me sessions) made refresh rotation renew the credential's originating lifetime instead of the configured standard lifetime. Its CI affected-set did not include the API integration test project, so two pre-existing rotation suites started failing for every PR that runs that project (reproduced on develop itself, run 38023739804 and the Economy gate): - RefreshTokenLifecyclePostgreSqlHttpTests seeded an 8-hour credential and expected the configured multi-day window on renewal. - RefreshTokenExpirationPostgreSqlHttpTests seeded a 23.25-hour credential and expected the configured 7-day window on the sliding 30-day-ceiling case. Seed those credentials with the full configured window so the assertions verify the renewal semantics that shipped with #758; non-sliding cases keep the short-lived credential the rotation must not extend. Widen the lifecycle suite's renewal tolerance from 1s to 5s: the second sequential rotation also renews the sub-second elapsed-since-issuance carried by the preserved auth-time CreatedAt. * test(auth): probe /cert-whoami over HTTP/1.0 The endpoint answered the raw HTTP/1.1 probe with a chunked body, so the JSON payload was prefixed with the hex chunk size and failed to parse. HTTP/1.0 keeps the response close-delimited. * fix(auth): renew refresh rotations at the configured lifetime, not the stored one PR #758 made rotation copy the stored originating duration (ExpiresAt - CreatedAt) into the replacement token. Any row whose stored lifetime differs from its configured class lifetime — short-lived or absolute-session-capped rows, externally seeded fixtures, rows minted under older configuration — renewed with that stale duration: an 8-hour stored row minted 8-hour replacements instead of the configured N days. Rotation now classifies the session's class and mints the replacement at the configured lifetime for that class: - originating duration strictly longer than the configured standard lifetime -> persistent (remember me) -> PersistentRefreshTokenExpirationDays - anything else (standard, legacy, malformed rows) -> standard RefreshTokenExpirationDays This restores the pre-#758 contract for standard sessions (the four failing PostgreSQL HTTP theories) while keeping the remember-me guarantee that persistent sessions do not collapse onto the standard lifetime. Adds RefreshTokenLifetimeResolver.ResolveRenewalDays as the single policy-aware renewal point plus regression unit tests covering short-lived, persistent, typed-options and malformed rows, and a service-level rotation test asserting configured-day lifetimes. * fix(auth): classify refresh rotation by originating policy, not stored span Rotation (#758 window) renewed tokens by replaying the stored row's CreatedAt→ExpiresAt span. Rotation preserves the originating CreatedAt while advancing ExpiresAt, so that span compounds elapsed time and drifts upward every rotation — the lifecycle/expiration integration suites observed replacements at +8h/+24h instead of the configured 1/7/30 days, failing four tests on the release PR. Classify the session by its originating policy instead (span ≥ persistent days ⇒ persistent lifetime, else standard) and renew at now + configured days. Drift-free, keeps the remember-me persistent contract (73/73 remember-me unit tests green), restores the configured-expiry contract (7/7 previously failing integration tests green). * test(auth): use contract JSON options in certificate credential DTO tests The converter round-trip test serialized with raw options (PascalCase, case-sensitive), so the emitted {"Type":..,"Thumbprint":..} JSON never matched the converter's camelCase "type"/"thumbprint" discriminators. Configure the test serializer the way the API does (camelCase naming policy + case-insensitive binding). * test(auth): guard rotation against copying short stored lifetimes over HTTP Adds a PostgreSQL HTTP theory asserting that a seeded 8-hour refresh row renews at each configured lifetime (1/7/30 days) on the real tokens:refresh endpoint, with an explicit guard that the seeded short window never leaks into the replacement or the reported deadline. Also ensures the integration suite is selected by the affected-test chooser so the four previously red theories run on this PR. * fix(auth): classify rotation persistence by the persistent threshold, not the standard one Sequential rotation preserves the originating CreatedAt while advancing ExpiresAt, so every successor's stored span is the configured window plus the sub-second elapsed time since issuance. The previous classification (span > standardDays) therefore flipped the second rotation of a standard N-day session to persistent and minted the replacement at the persistent lifetime (default 30 days) regardless of the configured N. Classify by the session's originating policy instead: only a stored span at or beyond the configured persistent lifetime renews at the persistent lifetime; every other row — standard sessions mid-rotation, legacy or malformed rows, and rows minted under different configuration — renews at the configured standard lifetime. Genuine persistent rows minted with exactly the persistent window still classify persistent (>=). * feat(security): revoke sessions and tokens on confirmed-compromise risk events SuspiciousLoginDetectedV1 gains a third durable consumer, RiskEventSessionRevocationHandler: on ImpossibleTravel and BruteForceDetected (confirmed account-compromise signals) it revokes every refresh token, terminates all sessions as SecurityViolation, bumps the user token version, and records a versioned token cutoff - mirroring RevokeAllUserTokensHandler and the refresh-replay containment path. LoginStepUpRequired stays notify-only. Gated by Authentication:RiskEventRevocation:Enabled (default on), idempotent per EventId through the durable inbox, audited through the central authentication audit pipeline. Fixes #279 * fix(security): compile fixes for risk-event revocation consumer tests * feat(auth): one-time email sign-in code modality for passwordless flows (#752) * feat(auth): one-time email sign-in code modality for passwordless flows (issue #252) Backend: - POST /v1/auth/email-code:request and /v1/auth/email-code:consume endpoints (AllowAnonymous, registered in AnonymousEndpointRegistry with justifications) - EmailCodeService: crypto-random six-digit codes stored only as SHA-256 digests, 10-minute TTL, five-attempt verify limit, 60-second resend throttle, constant-time digest comparison (dummy compare on unknown email) - Request/consume command handlers; consume reuses the authenticated session issuer so the code modality issues the same session/token pair as the magic link; enumeration-safe generic 200 on request - EmailCodeRequestedNotification + SendEmailCodeRequestedHandler queue the delivery row; new NotificationType.EmailCode classified transactional (always delivered, metadata protected at rest) - EmailCodeRenderer renders the one-time code email (transactional, no footer) Web: - /email-code page under (auth) following the magic-link patterns: email request step then six-digit code entry, consume via the credentials provider extension issuing the same auth session - sign-in page links the new modality; en-US/pt-BR messages Tests: service (TTL, attempts, single-use, digest-only storage, throttle, normalization), command handlers (enumeration-safe, throttled, cancellation, issuance), notification handler, renderer, web page/component/lib tests * fix(auth,web): keep email-code cache eviction on the cache clock; lint the flow component API: the stored entry carried an absolute expiration derived from the domain clock (SystemClock), but MemoryCache evicts on its own real-time clock. Whenever the domain clock disagreed with wall time (e.g. the fake TimeProvider pinned hours in the past in EmailCodeServiceTests), the real-time scanner evicted live entries immediately, so every verify fell through to 'Invalid code' and the digest/expiry assertions failed. Cache-level eviction is only a safety net for abandoned entries: use AbsoluteExpirationRelativeToNow (cache clock) with slack beyond the ten-minute TTL, while the logical lifetime stays enforced against SystemClock in VerifyEmailCodeAsync. Web: drop the unused CheckCircle2 import and wrap both async submit handlers in void-returning arrow functions (no-unused-vars, no-misused-promises). * chore(openapi): regenerate client for the email-code endpoints Captured swagger.json from the published API (same pipeline as scripts/ci/verify-openapi-surface.sh) and regenerated the typed client: postAuthEmailCodeRequest / postAuthEmailCodeConsume endpoints, auth module methods, and DTO types. * chore(openapi): refresh generated client metadata after develop merge * test(notifications): include EmailCode in the expected non-suppressible catalog set The one-time email sign-in code type is Transactional by design (always delivered, never suppressible), so the exhaustive catalog assertion must list it alongside EmailVerification/PasswordReset/MagicLink/TenantInvite. * test(api): align inbox receipt assertions with the third suspicious-login consumer The recorded-impossible-travel and step-up alert suites asserted a single inbox receipt per event; the new risk-event revocation consumer adds a second receipt (completed once, no-op for step-up). The disabled switch test now filters receipts client-side - EF cannot translate the StringComparison Contains overload. * feat(auth): config-gated generic OIDC federation SSO for enterprise identity providers (#771) * feat(auth): config-gated generic OIDC federation SSO for enterprise IdPs Implements the OIDC half of #268 (SAML deferred with documentation): - SharedKernel: OidcProviderOptions under Authentication:ExternalProviders:Oidc:<slug> (Enabled=false default, HTTPS Authority, ClientId/Secret, scopes default openid profile email, ClaimMapping sub/email/name/email_verified, optional EmailDomains) with fail-closed validation; 4-provider allowlist untouched. - OidcFederationService: .well-known/openid-configuration discovery with 15-min caching (issuer must equal authority), authorization-code exchange, ID-token validation per the LtiPlatformJwksService pattern (JWKS + TokenValidationParameters, RS256, iss/aud/lifetime) — no new packages. Email-domain gate; domain-to-provider discovery; end_session_endpoint logout forwarding. - OAuthAuthService.OidcSignInAsync reusing ResolveExternalUserAsync (link by oidc-<slug> + provider key, verified-email merge, JIT via User.CreateOAuthUser) with fail-closed MFA policy gate (amr 'mfa' proof required when policy demands); amr/acr surfaced on SignInResponse. - AuthController: /v1/auth/oidc/{slug}:sign-in-authorize + :sign-in-callback (anonymous + registry), GET /v1/auth/oidc:discover-provider (anonymous + registry), GET /v1/auth/oidc/{slug}:end-session-url (authorized). - SAML 2.0 deferral documented (Sustainsys vs ITfoxtec product decision). - Tests: options validation, stubbed-provider discovery caching + token failure modes (bad iss/aud/exp/signature/malformed), JIT/link/unverified-collision, MFA fail-closed, logout forwarding, controller + handler/validator coverage. * chore(openapi): regenerate typed client with OIDC federation endpoints Regenerated from the CI-captured swagger of this PR head (openapi artifact run 38019588554): 4 new /v1/auth/oidc endpoints and the SignInResponse amr/acr/MfaVerifiedByProvider fields. * fix(tests): OIDC test fixes for CI — fresh HttpClient per factory call, UnauthorizedObjectResult assertion, handler-owned state - StubOidcProvider factory now returns a new HttpClient per CreateClient call (the service disposes each client; reusing one caused ObjectDisposedException on the token/JWKS legs). - 401 controller assertion uses UnauthorizedObjectResult (what Unauthorized(object) returns). - OidcSignInCommandHandler test asserts the handler-generated Guid-N CSRF state. * ci: retrigger PR Verify for 352f6768e (synchronize event was not delivered) * ci: retrigger PR Verify (platform dropped two synchronize/reopened triggers) * docs: touch SAML deferral note (retrigger CI) * ci: retrigger workflows for #268 (web-flow push) * ci: retrigger PR Verify after GitHub Actions event backlog (05:08-05:31 UTC) * fix(auth): adapt OIDC federation to develop's scope-consent + remember-me surface develop (PR #250/#210 lineage) extended ResolveExternalUserAsync with granted-scope consent recording and CompleteSignInAsync with rememberMe. The OIDC flow now carries the provider's configured scopes onto the external-login consent trail and plumbs RememberMe through the callback DTO/command/controller; test ctor mocks updated for the two new OAuthAuthService dependencies. * fix(auth): reject malformed OIDC id_tokens before the JWKS round-trip A structurally unparseable token is malformed regardless of which key would have signed it, so CanReadToken + a full ReadToken parse now run before GetSigningKeysAsync and throw the 'is malformed' failure. Previously the malformed classification sat after the JWKS fetch and a three-segment garbage token (CanReadToken true, JSON parse failure) fell into the generic 'failed validation' bucket. Tests: the malformed case now also asserts JwksHits == 0 so the before-JWKS ordering is enforced, and the expired-token stub now keeps nbf < exp (JwtPayload refuses to construct otherwise, IDX12401). * chore(openapi): regenerate client with the OIDC callback remember-me flag Captured swagger.json from the published API (same pipeline as scripts/ci/verify-openapi-surface.sh) after the develop merge and regenerated the typed client: IdentityAuthenticationOidcCallbackRequestDto gains the optional rememberMe field. * feat(billing): named billing events, events API, and webhook retry worker Issue #396 (safe-default scope from verifier): - Named billing integration events following the UseCaseEventContract pattern: BillingWebhookProcessedV1, BillingWebhookFailedV1, BillingInvoicePaidV1, BillingSubscriptionRenewedV1, BillingSubscriptionCancelledV1. The four provider webhook commands now declare them as conditional events (replacing the generated 'no named cross-module event' fallback); publication rides the existing durable transport (IDurableEventProducer) and correlates with the emitting use-case operation. Best-effort from the webhook path: a recording failure never changes the processing outcome. - GET api/v{v}/billing/events (paged webhook inbox feed with status/provider/type/ date filters), GET {eventId}, GET outbox (paged named-event feed from the platform outbox read model scoped to Commerce.Billing) and GET outbox/{eventId}. Read-only over the existing stores; SystemAdmin policy per sibling billing controllers. - BillingWebhookRetryBackgroundService + BillingWebhookRetryWorker: polls failed inbox events past their exponential-backoff next-attempt time and requeues them through the existing RetryWebhookEventCommand path, honoring the retry ceiling (default 5, matching the manual retry path), with OTel metrics (GameGuild.Billing.WebhookRetry meter). - Tests: event contract architecture tests, publisher tests (correlation + tenant fallback + best-effort), retry worker scheduling/backoff/ceiling/isolation tests, controller authorization/paging tests, and query handler paging/filter tests. References #396 * chore(openapi): regenerate client on merged tree (billing events + OIDC federation) * feat(billing): register CQRS validation behavior and cache webhook security summary (#776) * feat(billing): register CQRS validation behavior and cache webhook security summary Issue #394 — closes the two remaining gaps from the verification audit: 1. Validation pipeline integration: register ValidationBehavior<,> as an IPipelineBehavior<,> in the API composition root (InfrastructureLayerExtensions), ordered BEFORE ResourceQuotaBehavior and UseCaseOperationBehavior so invalid commands short-circuit with RequestValidationException without consuming quotas and without emitting operation events. 2. Query caching strategy: GetBillingWebhookSecuritySummaryHandler now serves the SystemAdmin monitoring summary from a best-effort short-TTL (15s) ICacheService cache; WebhookSecurityEventPublisher evicts the cached summary whenever a webhook security event is published; Billing module registers an in-process ICacheService fallback when the host has not registered one. GetWebhookEvent stays uncached (by-ID freshness). Tests: mediator short-circuit (invalid command never reaches inner pipeline stages or the handler; valid command passes through), composition-root behavior ordering, cache miss/hit/TTL-expiry/eviction/fault-tolerance. * fix(billing): reconcile validation pipeline tests and slug fixture - Register the spy command handler under IRequestHandler<,> as well: the mediator resolves IRequestHandler<,> (AddCqrs registers both contracts), so the valid-command pass-through test could not find the handler. - Shorten the MonetizationPermissionHttpTests slug fixture so the -admin variant satisfies the CreateSubscriptionPlan validator's 50-char Slug limit, which the now-active ValidationBehavior enforces at dispatch time (400 instead of 201). --------- Co-authored-by: impl-agent <impl-agent@users.noreply.github.com> * test(api): response-level security header verification for CSP/HSTS defaults (#774) * test(api): response-level security header verification for CSP/HSTS defaults Adds SecurityHeadersHttpTests to GameGuild.API.IntegrationTests following the PostgreSqlHttpTests WebApplicationFactory pattern: - authenticated 200, anonymous 401 and ProblemDetails 404 responses carry the restrictive default CSP (default-src 'none'; frame-ancestors 'none'), X-Frame-Options DENY, nosniff, Referrer-Policy and Permissions-Policy - /documentation serves the separate Swagger CSP (Development factory) - sensitive /auth paths respond with no-store/no-cache cache directives - Production environment over HTTPS emits Strict-Transport-Security while the non-production host omits it (environment-overridden factories) Documents the shipped header defaults and reverse-proxy/HSTS implications in docs/api/authentication-options-configuration.md. Fixes #276 * test(ap…
…ub callback handler and POST contract (#233 #248) (#820) * wip: salvage at quota wall 2026-10-12 * fix(authn): compile fixes salvaged from quota wall — nullable mapping coalescing and wiring-test sender mock shapes * chore(openapi): regenerate typed client — orchestrated auth endpoints + GitHub callback POST contract (#233 #248) * test(authn): complete-flow fixture satisfies every required step so the derived NextStep is null * fix(authn): NextStep returns null when every required step is satisfied — FirstOrDefault over the non-nullable step enum defaulted to PrimaryCredential(0) instead * fix(authn): drop ambiguous cref from NextStep doc comment (CS0419 under warnings-as-errors) * test(authn): completed-flow branch model asserts the corrected NextStep semantics (null, not the enum default) * fix(authn): derive AuthenticationFlowState.IsComplete from step satisfaction IsComplete was a stored auto-property never set on freshly constructed domain states (only on the EF record), so a fully-satisfied flow read incomplete until persistence. Derive it like NextStep so the domain model is internally consistent; drop the now-redundant assignments. --------- Co-authored-by: Alexandre Tolstenko <tolstenko@gmail.com>
#410) (#822) * wip: salvage at quota wall 2026-10-12 * fix(credits): compile fixes salvaged from quota wall — named-arg casing and ActorContext required members after develop merge * fix(credits): correct paramref casing for Release warning-clean publish * chore(openapi): regenerate typed client — account credits, gifts, referrals endpoints (#410) * test(credits): idempotency case grants 150 and caps the tender at 70 — the 70-default grant made the replay assertion vacuous (fully-used grant) and without the cap the target is the full outstanding amount * fix(credits): CI round — leaderboard counts via translatable conditional sums; referral link base normalized to a path-terminated URL; IssueGrant returns CreatedResult; persistence tests persist the enqueued consume ledger and create due grants with future-then-swept expiries * fix(credits): leaderboard aggregates a narrow projection in memory — grouping predicates still do not translate on the persistence test provider * test(payments): correct UsageRate expectation — referrer reward grant counts as issued The rewarded referral grants the referrer 10 USD, so cross-currency issued totals 150, not 140: usage rate is 40/150 = 0.2667. --------- Co-authored-by: Alexandre Tolstenko <tolstenko@gmail.com>
The dts rollup worker crashed with ERR_WORKER_OUT_OF_MEMORY on CI once generated types crossed their current size; the main-process heap bump propagates to worker_threads.
The dts rollup worker crashed with ERR_WORKER_OUT_OF_MEMORY on CI once generated types crossed their current size; the main-process heap bump propagates to worker_threads.



Summary
Promotes
developtomain(166 commits, 396 files, +247,207/−1,046).Dominant themes since #598:
Merge state
mainis strictly behinddevelop(0 commits the other way) → clean merge, expected no conflicts.Verification
develop.mainand verify auth flows.