Browse Source

Merge branch 'main' into task/upgrade-internationalized-number-to-version-3.6.8

pull/7876/head
Thomas Kaul 1 month ago
committed by GitHub
parent
commit
cd92d0946a
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 7
      CHANGELOG.md
  2. 20
      apps/api/src/app/auth/oidc-state.store.ts
  3. 12
      apps/api/src/app/subscription/subscription.service.ts
  4. 10
      apps/api/src/guards/oauth-callback.guard.ts
  5. 31
      libs/ui/src/lib/holdings-table/holdings-table.component.html
  6. 13
      libs/ui/src/lib/holdings-table/holdings-table.component.scss
  7. 4
      package-lock.json
  8. 2
      package.json

7
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

20
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');
}
}

12
apps/api/src/app/subscription/subscription.service.ts

@ -58,17 +58,23 @@ export class SubscriptionService {
} =
(await this.propertyService.getByKey<any>(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');
}

10
apps/api/src/guards/oauth-callback.guard.ts

@ -5,11 +5,19 @@ export function OAuthCallbackGuard(strategy: string): Type<IAuthGuard> {
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

31
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()"
>
<ng-container matColumnDef="icon" sticky>
<ng-container matColumnDef="icon" [sticky]="mode() === 'default'">
<th *matHeaderCellDef class="px-1" mat-header-cell></th>
<td *matCellDef="let element" class="px-1 text-center" mat-cell>
<td
*matCellDef="let element"
class="px-1 text-center"
mat-cell
[class.text-nowrap]="mode() === 'simple'"
>
<gf-entity-logo
[dataSource]="element.assetProfile.dataSource"
[symbol]="element.assetProfile.symbol"
@ -27,8 +33,13 @@
>
<ng-container i18n>Name</ng-container>
</th>
<td *matCellDef="let element" class="line-height-normal px-1" mat-cell>
<div class="text-truncate">
<td
*matCellDef="let element"
class="line-height-normal px-1"
mat-cell
[class.w-100]="mode() === 'simple'"
>
<div class="text-truncate" [title]="element.assetProfile.name">
{{ element.assetProfile.name }}
@if (element.assetProfile.name === element.assetProfile.symbol) {
<span>({{ element.assetProfile.assetSubClassLabel }})</span>
@ -160,7 +171,10 @@
</td>
</ng-container>
<ng-container matColumnDef="performanceInPercentage" stickyEnd>
<ng-container
matColumnDef="performanceInPercentage"
[stickyEnd]="mode() === 'default'"
>
<th
*matHeaderCellDef
class="justify-content-end px-1"
@ -170,7 +184,12 @@
<span class="d-none d-sm-block" i18n>Performance</span>
<span class="d-block d-sm-none" i18n-title title="Performance">±</span>
</th>
<td *matCellDef="let element" class="px-1" mat-cell>
<td
*matCellDef="let element"
class="px-1"
mat-cell
[class.text-nowrap]="mode() === 'simple'"
>
<div class="d-flex justify-content-end">
<gf-value
[colorizeSign]="true"

13
libs/ui/src/lib/holdings-table/holdings-table.component.scss

@ -1,3 +1,16 @@
:host {
display: block;
.gf-table {
&.simple {
.mat-column-icon,
.mat-column-performanceInPercentage {
width: min-content;
}
.mat-column-nameWithSymbol {
max-width: 0;
}
}
}
}

4
package-lock.json

@ -1,12 +1,12 @@
{
"name": "ghostfolio",
"version": "3.70.0",
"version": "3.70.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ghostfolio",
"version": "3.70.0",
"version": "3.70.1",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {

2
package.json

@ -1,6 +1,6 @@
{
"name": "ghostfolio",
"version": "3.70.0",
"version": "3.70.1",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio",

Loading…
Cancel
Save