diff --git a/CHANGELOG.md b/CHANGELOG.md index df372132a6..ed5ce677e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Upgraded `@internationalized/number` from version `3.6.7` to `3.6.8` -## 3.70.0 - 2026-09-14 +## 3.70.1 - 2026-09-14 ### Added @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the style of the activity type filter on the activities page (experimental) - Improved the style of the no activities info component - Improved the style of the selector in the benchmark comparator +- Improved the responsive layout of the holdings table in simplified mode - Improved the language localization for Chinese (`zh`) - Improved the language localization for German (`de`) - Upgraded `@codewithdan/observable-store` from version `2.2.15` to `3.0.0` @@ -30,6 +31,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Upgraded `papaparse` from version `5.5.3` to `5.7.0` - Upgraded `zone.js` from version `0.16.2` to `0.16.3` +### Fixed + +- Hardened the authentication with _OpenID Connect_ (`OIDC`) (experimental) + ## 3.69.0 - 2026-09-07 ### Changed diff --git a/apps/api/src/app/auth/oidc-state.store.ts b/apps/api/src/app/auth/oidc-state.store.ts index aebd99892f..0131d184d3 100644 --- a/apps/api/src/app/auth/oidc-state.store.ts +++ b/apps/api/src/app/auth/oidc-state.store.ts @@ -1,5 +1,6 @@ import type { Request } from 'express'; import ms from 'ms'; +import { randomBytes } from 'node:crypto'; import type { SessionStore, SessionStoreCallback, @@ -66,13 +67,20 @@ export class OidcStateStore implements SessionStore { const data = this.stateMap.get(handle); if (!data) { - return callback(null, undefined, undefined); + return callback( + new Error('Invalid OIDC state parameter'), + undefined, + undefined + ); } if (Date.now() - data.timestamp > this.STATE_EXPIRY_MS) { - // State has expired this.stateMap.delete(handle); - return callback(null, undefined, undefined); + return callback( + new Error('OIDC state has expired, please try again'), + undefined, + undefined + ); } // Remove state after verification (one-time use) @@ -106,10 +114,6 @@ export class OidcStateStore implements SessionStore { * Generate a cryptographically secure random handle */ private generateHandle() { - return ( - Math.random().toString(36).substring(2, 15) + - Math.random().toString(36).substring(2, 15) + - Date.now().toString(36) - ); + return randomBytes(32).toString('hex'); } } diff --git a/apps/api/src/app/subscription/subscription.service.ts b/apps/api/src/app/subscription/subscription.service.ts index 312b8c3aef..f5acbc3e33 100644 --- a/apps/api/src/app/subscription/subscription.service.ts +++ b/apps/api/src/app/subscription/subscription.service.ts @@ -58,17 +58,23 @@ export class SubscriptionService { } = (await this.propertyService.getByKey(PROPERTY_STRIPE_CONFIG)) ?? {}; - const subscriptionOffer = Object.values(subscriptionOffers).find( + const subscriptionOffersForPrice = Object.values(subscriptionOffers).filter( (subscriptionOffer) => { return subscriptionOffer.priceId === priceId; } ); - if (!subscriptionOffer) { + if (subscriptionOffersForPrice.length === 0) { throw new BadRequestException('Invalid priceId'); } - if (couponId && couponId !== subscriptionOffer.couponId) { + const subscriptionOffer = subscriptionOffersForPrice.find( + (subscriptionOffer) => { + return subscriptionOffer.couponId === couponId; + } + ); + + if (!subscriptionOffer) { throw new BadRequestException('Invalid couponId'); } diff --git a/apps/api/src/guards/oauth-callback.guard.ts b/apps/api/src/guards/oauth-callback.guard.ts index cedbccde12..a89d77daa0 100644 --- a/apps/api/src/guards/oauth-callback.guard.ts +++ b/apps/api/src/guards/oauth-callback.guard.ts @@ -5,11 +5,19 @@ export function OAuthCallbackGuard(strategy: string): Type { class OAuthCallbackGuardMixin extends AuthGuard(strategy) { private readonly logger = new Logger(OAuthCallbackGuard.name); - public override handleRequest(error: Error, user: any) { + public override handleRequest( + error: Error | null, + user: any, + info?: { message?: string } + ) { if (error) { this.logger.error( `Authentication with the ${strategy} strategy has failed: ${error.message}` ); + } else if (!user && info?.message) { + this.logger.warn( + `Authentication with the ${strategy} strategy was rejected: ${info.message}` + ); } // Do not throw, the callback handler redirects to the login page instead diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.html b/libs/ui/src/lib/holdings-table/holdings-table.component.html index 5bc76f646e..69be6f8961 100644 --- a/libs/ui/src/lib/holdings-table/holdings-table.component.html +++ b/libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -3,13 +3,19 @@ class="gf-table w-100" mat-table matSort + [class.simple]="mode() === 'simple'" [dataSource]="dataSource" [matSortActive]="sortActive()" [matSortDirection]="sortDirection()" > - + - + Name - -
+ +
{{ element.assetProfile.name }} @if (element.assetProfile.name === element.assetProfile.symbol) { ({{ element.assetProfile.assetSubClassLabel }}) @@ -160,7 +171,10 @@ - + Performance ± - +