Browse Source

Feature/separate Google OAuth and token authentication (#5915)

* Separate Google OAuth and token authentication

* Update changelog
pull/5417/merge
Germán Martín 2 weeks ago
committed by GitHub
parent
commit
66a3e319a8
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      CHANGELOG.md
  2. 12
      apps/api/src/app/info/info.service.ts
  3. 3
      apps/api/src/services/configuration/configuration.service.ts
  4. 3
      apps/api/src/services/interfaces/environment.interface.ts
  5. 15
      apps/client/src/app/components/header/header.component.ts
  6. 3
      apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts
  7. 67
      apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html
  8. 6
      apps/client/src/app/pages/features/features-page.component.ts
  9. 2
      apps/client/src/app/pages/features/features-page.html
  10. 1
      apps/client/src/app/pages/landing/landing-page.component.ts
  11. 12
      apps/client/src/app/pages/pricing/pricing-page.component.ts
  12. 2
      apps/client/src/app/pages/pricing/pricing-page.html
  13. 12
      apps/client/src/app/pages/register/register-page.component.ts
  14. 22
      apps/client/src/app/pages/register/register-page.html
  15. 5
      libs/common/src/lib/permissions.ts

5
CHANGELOG.md

@ -11,9 +11,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Refactored the get holding functionality in the portfolio service
- Changed the user data loading in the user detail dialog of the admin control panel’s users section to fetch data on demand
- Exposed the authentication with access token as an environment variable (`ENABLE_FEATURE_AUTH_TOKEN`)
- Improved the language localization for German (`de`)
- Upgraded `prisma` from version `6.18.0` to `6.19.0`
### Todo
- Rename the environment variable from `ENABLE_FEATURE_SOCIAL_LOGIN` to `ENABLE_FEATURE_AUTH_GOOGLE`
## 2.216.0 - 2025-11-10
### Changed

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

@ -51,6 +51,14 @@ export class InfoService {
const globalPermissions: string[] = [];
if (this.configurationService.get('ENABLE_FEATURE_AUTH_GOOGLE')) {
globalPermissions.push(permissions.enableAuthGoogle);
}
if (this.configurationService.get('ENABLE_FEATURE_AUTH_TOKEN')) {
globalPermissions.push(permissions.enableAuthToken);
}
if (this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX')) {
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
info.fearAndGreedDataSource = encodeDataSource(
@ -70,10 +78,6 @@ export class InfoService {
);
}
if (this.configurationService.get('ENABLE_FEATURE_SOCIAL_LOGIN')) {
globalPermissions.push(permissions.enableSocialLogin);
}
if (this.configurationService.get('ENABLE_FEATURE_STATISTICS')) {
globalPermissions.push(permissions.enableStatistics);
}

3
apps/api/src/services/configuration/configuration.service.ts

@ -40,9 +40,10 @@ export class ConfigurationService {
DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER: json({
default: []
}),
ENABLE_FEATURE_AUTH_GOOGLE: bool({ default: false }),
ENABLE_FEATURE_AUTH_TOKEN: bool({ default: true }),
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }),
ENABLE_FEATURE_READ_ONLY_MODE: bool({ default: false }),
ENABLE_FEATURE_SOCIAL_LOGIN: bool({ default: false }),
ENABLE_FEATURE_STATISTICS: bool({ default: false }),
ENABLE_FEATURE_SUBSCRIPTION: bool({ default: false }),
ENABLE_FEATURE_SYSTEM_MESSAGE: bool({ default: false }),

3
apps/api/src/services/interfaces/environment.interface.ts

@ -16,9 +16,10 @@ export interface Environment extends CleanedEnvAccessors {
DATA_SOURCE_IMPORT: string;
DATA_SOURCES: string[];
DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER: string[];
ENABLE_FEATURE_AUTH_GOOGLE: boolean;
ENABLE_FEATURE_AUTH_TOKEN: boolean;
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean;
ENABLE_FEATURE_READ_ONLY_MODE: boolean;
ENABLE_FEATURE_SOCIAL_LOGIN: boolean;
ENABLE_FEATURE_STATISTICS: boolean;
ENABLE_FEATURE_SUBSCRIPTION: boolean;
ENABLE_FEATURE_SYSTEM_MESSAGE: boolean;

15
apps/client/src/app/components/header/header.component.ts

@ -105,7 +105,8 @@ export class GfHeaderComponent implements OnChanges {
public hasFilters: boolean;
public hasImpersonationId: boolean;
public hasPermissionForSocialLogin: boolean;
public hasPermissionForAuthGoogle: boolean;
public hasPermissionForAuthToken: boolean;
public hasPermissionForSubscription: boolean;
public hasPermissionToAccessAdminControl: boolean;
public hasPermissionToAccessAssistant: boolean;
@ -165,9 +166,14 @@ export class GfHeaderComponent implements OnChanges {
public ngOnChanges() {
this.hasFilters = this.userService.hasFilters();
this.hasPermissionForSocialLogin = hasPermission(
this.hasPermissionForAuthGoogle = hasPermission(
this.info?.globalPermissions,
permissions.enableSocialLogin
permissions.enableAuthGoogle
);
this.hasPermissionForAuthToken = hasPermission(
this.info?.globalPermissions,
permissions.enableAuthToken
);
this.hasPermissionForSubscription = hasPermission(
@ -280,7 +286,8 @@ export class GfHeaderComponent implements OnChanges {
autoFocus: false,
data: {
accessToken: '',
hasPermissionToUseSocialLogin: this.hasPermissionForSocialLogin,
hasPermissionToUseAuthGoogle: this.hasPermissionForAuthGoogle,
hasPermissionToUseAuthToken: this.hasPermissionForAuthToken,
title: $localize`Sign in`
},
width: '30rem'

3
apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts

@ -1,5 +1,6 @@
export interface LoginWithAccessTokenDialogParams {
accessToken: string;
hasPermissionToUseSocialLogin: boolean;
hasPermissionToUseAuthGoogle: boolean;
hasPermissionToUseAuthToken: boolean;
title: string;
}

67
apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html

@ -3,28 +3,35 @@
<div class="py-3" mat-dialog-content>
<div class="align-items-center d-flex flex-column">
<form class="w-100">
<mat-form-field appearance="outline" class="without-hint w-100">
<mat-label i18n>Security Token</mat-label>
<input
matInput
[formControl]="accessTokenFormControl"
[type]="isAccessTokenHidden ? 'password' : 'text'"
(keydown.enter)="onLoginWithAccessToken(); $event.preventDefault()"
/>
<button
mat-button
matSuffix
type="button"
(click)="isAccessTokenHidden = !isAccessTokenHidden"
>
<ion-icon
[name]="isAccessTokenHidden ? 'eye-outline' : 'eye-off-outline'"
@if (data.hasPermissionToUseAuthToken) {
<mat-form-field appearance="outline" class="without-hint w-100">
<mat-label i18n>Security Token</mat-label>
<input
matInput
[formControl]="accessTokenFormControl"
[type]="isAccessTokenHidden ? 'password' : 'text'"
(keydown.enter)="onLoginWithAccessToken(); $event.preventDefault()"
/>
</button>
</mat-form-field>
<button
mat-button
matSuffix
type="button"
(click)="isAccessTokenHidden = !isAccessTokenHidden"
>
<ion-icon
[name]="isAccessTokenHidden ? 'eye-outline' : 'eye-off-outline'"
/>
</button>
</mat-form-field>
}
@if (data.hasPermissionToUseSocialLogin) {
@if (
data.hasPermissionToUseAuthGoogle && data.hasPermissionToUseAuthToken
) {
<div class="my-3 text-center text-muted" i18n>or</div>
}
@if (data.hasPermissionToUseAuthGoogle) {
<div class="d-flex flex-column">
<a
class="px-4 rounded-pill"
@ -49,15 +56,17 @@
>
</div>
<div>
<button
color="primary"
mat-flat-button
[disabled]="
!(accessTokenFormControl.dirty && accessTokenFormControl.valid)
"
(click)="onLoginWithAccessToken()"
>
<ng-container i18n>Sign in</ng-container>
</button>
@if (data.hasPermissionToUseAuthToken) {
<button
color="primary"
mat-flat-button
[disabled]="
!(accessTokenFormControl.dirty && accessTokenFormControl.valid)
"
(click)="onLoginWithAccessToken()"
>
<ng-container i18n>Sign in</ng-container>
</button>
}
</div>
</div>

6
apps/client/src/app/pages/features/features-page.component.ts

@ -25,6 +25,7 @@ import { Subject, takeUntil } from 'rxjs';
})
export class GfFeaturesPageComponent implements OnDestroy {
public hasPermissionForSubscription: boolean;
public hasPermissionToCreateUser: boolean;
public info: InfoItem;
public routerLinkRegister = publicRoutes.register.routerLink;
public routerLinkResources = publicRoutes.resources.routerLink;
@ -55,6 +56,11 @@ export class GfFeaturesPageComponent implements OnDestroy {
this.info?.globalPermissions,
permissions.enableSubscription
);
this.hasPermissionToCreateUser = hasPermission(
this.info?.globalPermissions,
permissions.createUserAccount
);
}
public ngOnDestroy() {

2
apps/client/src/app/pages/features/features-page.html

@ -309,7 +309,7 @@
</div>
</div>
</div>
@if (!user) {
@if (hasPermissionToCreateUser && !user) {
<div class="row">
<div class="col mt-3 text-center">
<a

1
apps/client/src/app/pages/landing/landing-page.component.ts

@ -127,6 +127,7 @@ export class GfLandingPageComponent implements OnDestroy, OnInit {
}
this.hasPermissionForDemo = !!demoAuthToken;
this.hasPermissionForStatistics = hasPermission(
globalPermissions,
permissions.enableStatistics

12
apps/client/src/app/pages/pricing/pricing-page.component.ts

@ -52,6 +52,7 @@ export class GfPricingPageComponent implements OnDestroy, OnInit {
public coupon: number;
public couponId: string;
public durationExtension: StringValue;
public hasPermissionToCreateUser: boolean;
public hasPermissionToUpdateUserSettings: boolean;
public importAndExportTooltipBasic = translate(
'DATA_IMPORT_AND_EXPORT_TOOLTIP_BASIC'
@ -100,11 +101,18 @@ export class GfPricingPageComponent implements OnDestroy, OnInit {
}
public ngOnInit() {
const { baseCurrency, subscriptionOffer } = this.dataService.fetchInfo();
this.baseCurrency = baseCurrency;
const { baseCurrency, globalPermissions, subscriptionOffer } =
this.dataService.fetchInfo();
this.baseCurrency = baseCurrency;
this.coupon = subscriptionOffer?.coupon;
this.durationExtension = subscriptionOffer?.durationExtension;
this.hasPermissionToCreateUser = hasPermission(
globalPermissions,
permissions.createUserAccount
);
this.label = subscriptionOffer?.label;
this.price = subscriptionOffer?.price;

2
apps/client/src/app/pages/pricing/pricing-page.html

@ -367,7 +367,7 @@
</p>
</div>
</div>
} @else if (!user) {
} @else if (hasPermissionToCreateUser && !user) {
<div class="row">
<div class="col mt-3 text-center">
<a

12
apps/client/src/app/pages/register/register-page.component.ts

@ -30,7 +30,8 @@ import { GfUserAccountRegistrationDialogComponent } from './user-account-registr
})
export class GfRegisterPageComponent implements OnDestroy, OnInit {
public deviceType: string;
public hasPermissionForSocialLogin: boolean;
public hasPermissionForAuthGoogle: boolean;
public hasPermissionForAuthToken: boolean;
public hasPermissionForSubscription: boolean;
public hasPermissionToCreateUser: boolean;
public historicalDataItems: LineChartItem[];
@ -55,9 +56,14 @@ export class GfRegisterPageComponent implements OnDestroy, OnInit {
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
this.hasPermissionForSocialLogin = hasPermission(
this.hasPermissionForAuthGoogle = hasPermission(
globalPermissions,
permissions.enableSocialLogin
permissions.enableAuthGoogle
);
this.hasPermissionForAuthToken = hasPermission(
globalPermissions,
permissions.enableAuthToken
);
this.hasPermissionForSubscription = hasPermission(

22
apps/client/src/app/pages/register/register-page.html

@ -18,16 +18,20 @@
<div class="button-container row">
<div class="align-items-center col d-flex justify-content-center">
<div class="py-5 text-center">
<button
class="d-inline-block"
color="primary"
mat-flat-button
(click)="openShowAccessTokenDialog()"
>
<ng-container i18n>Create Account</ng-container>
</button>
@if (hasPermissionForSocialLogin) {
@if (hasPermissionForAuthToken) {
<button
class="d-inline-block"
color="primary"
mat-flat-button
(click)="openShowAccessTokenDialog()"
>
<ng-container i18n>Create Account</ng-container>
</button>
}
@if (hasPermissionForAuthToken && hasPermissionForAuthGoogle) {
<div class="my-3 text-muted" i18n>or</div>
}
@if (hasPermissionForAuthGoogle) {
<a
class="px-4 rounded-pill w-100"
href="../api/v1/auth/google"

5
libs/common/src/lib/permissions.ts

@ -28,11 +28,12 @@ export const permissions = {
deleteTag: 'deleteTag',
deleteUser: 'deleteUser',
deleteWatchlistItem: 'deleteWatchlistItem',
enableAuthGoogle: 'enableAuthGoogle',
enableAuthToken: 'enableAuthToken',
enableDataProviderGhostfolio: 'enableDataProviderGhostfolio',
enableFearAndGreedIndex: 'enableFearAndGreedIndex',
enableImport: 'enableImport',
enableBlog: 'enableBlog',
enableSocialLogin: 'enableSocialLogin',
enableStatistics: 'enableStatistics',
enableSubscription: 'enableSubscription',
enableSubscriptionInterstitial: 'enableSubscriptionInterstitial',
@ -157,7 +158,7 @@ export function filterGlobalPermissions(
if (aUtmSource === 'ios') {
return globalPermissions.filter((permission) => {
return (
permission !== permissions.enableSocialLogin &&
permission !== permissions.enableAuthGoogle &&
permission !== permissions.enableSubscription
);
});

Loading…
Cancel
Save