Browse Source

Refactor authentication permissions and update feature flags in environment configuration

pull/5915/head
Germán Martín 2 months ago
committed by Thomas Kaul
parent
commit
49e4516855
  1. 9
      .env.example
  2. 16
      apps/api/src/app/info/info.service.ts
  3. 4
      apps/api/src/services/interfaces/environment.interface.ts
  4. 28
      apps/client/src/app/components/admin-overview/admin-overview.html
  5. 6
      apps/client/src/app/components/header/header.component.html
  6. 8
      apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html
  7. 6
      apps/client/src/app/pages/features/features-page.component.ts
  8. 2
      apps/client/src/app/pages/features/features-page.html
  9. 3
      apps/client/src/app/pages/landing/landing-page.component.ts
  10. 6
      apps/client/src/app/pages/landing/landing-page.html
  11. 14
      apps/client/src/app/pages/pricing/pricing-page.component.ts
  12. 2
      apps/client/src/app/pages/pricing/pricing-page.html
  13. 24
      apps/client/src/app/pages/register/register-page.html
  14. 4
      libs/common/src/lib/permissions.ts

9
.env.example

@ -14,12 +14,3 @@ POSTGRES_PASSWORD=<INSERT_POSTGRES_PASSWORD>
ACCESS_TOKEN_SALT=<INSERT_RANDOM_STRING> ACCESS_TOKEN_SALT=<INSERT_RANDOM_STRING>
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?connect_timeout=300&sslmode=prefer DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?connect_timeout=300&sslmode=prefer
JWT_SECRET_KEY=<INSERT_RANDOM_STRING> JWT_SECRET_KEY=<INSERT_RANDOM_STRING>
# AUTHENTICATION
# Enable authentication with Google OAuth (default: false)
# ENABLE_FEATURE_AUTH_GOOGLE=true
# GOOGLE_CLIENT_ID=<INSERT_GOOGLE_CLIENT_ID>
# GOOGLE_SECRET=<INSERT_GOOGLE_SECRET>
# Enable authentication with Security Token (default: true)
# ENABLE_FEATURE_AUTH_TOKEN=true

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

@ -51,6 +51,14 @@ export class InfoService {
const globalPermissions: string[] = []; 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_FEAR_AND_GREED_INDEX')) {
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
info.fearAndGreedDataSource = encodeDataSource( info.fearAndGreedDataSource = encodeDataSource(
@ -64,14 +72,6 @@ export class InfoService {
globalPermissions.push(permissions.enableFearAndGreedIndex); globalPermissions.push(permissions.enableFearAndGreedIndex);
} }
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_READ_ONLY_MODE')) { if (this.configurationService.get('ENABLE_FEATURE_READ_ONLY_MODE')) {
isReadOnlyMode = await this.propertyService.getByKey<boolean>( isReadOnlyMode = await this.propertyService.getByKey<boolean>(
PROPERTY_IS_READ_ONLY_MODE PROPERTY_IS_READ_ONLY_MODE

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

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

28
apps/client/src/app/components/admin-overview/admin-overview.html

@ -30,23 +30,19 @@
} }
</div> </div>
</div> </div>
@if (hasPermissionForAuthToken) { <div class="d-flex my-3">
<div class="d-flex my-3"> <div class="w-50" i18n>User Signup</div>
<div class="w-50" i18n>User Signup</div> <div class="w-50">
<div class="w-50"> <mat-slide-toggle
<mat-slide-toggle color="primary"
color="primary" hideIcon="true"
hideIcon="true" [checked]="
[checked]=" info.globalPermissions.includes(permissions.createUserAccount)
info.globalPermissions.includes( "
permissions.createUserAccount (change)="onEnableUserSignupModeChange($event)"
) />
"
(change)="onEnableUserSignupModeChange($event)"
/>
</div>
</div> </div>
} </div>
@if (hasPermissionToToggleReadOnlyMode) { @if (hasPermissionToToggleReadOnlyMode) {
<div class="d-flex my-3"> <div class="d-flex my-3">
<div class="w-50" i18n>Read-only Mode</div> <div class="w-50" i18n>Read-only Mode</div>

6
apps/client/src/app/components/header/header.component.html

@ -422,11 +422,7 @@
<ng-container i18n>Sign in</ng-container> <ng-container i18n>Sign in</ng-container>
</button> </button>
</li> </li>
@if ( @if (currentRoute !== 'register' && hasPermissionToCreateUser) {
currentRoute !== 'register' &&
hasPermissionToCreateUser &&
hasPermissionForAuthToken
) {
<li class="list-inline-item ml-1"> <li class="list-inline-item ml-1">
<a <a
class="d-none d-sm-block" class="d-none d-sm-block"

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

@ -51,11 +51,9 @@
<div mat-dialog-actions> <div mat-dialog-actions>
<div class="flex-grow-1"> <div class="flex-grow-1">
@if (data.hasPermissionToUseAuthToken) { <mat-checkbox color="primary" i18n (change)="onChangeStaySignedIn($event)"
<mat-checkbox color="primary" i18n (change)="onChangeStaySignedIn($event)" >Stay signed in</mat-checkbox
>Stay signed in</mat-checkbox >
>
}
</div> </div>
<div> <div>
@if (data.hasPermissionToUseAuthToken) { @if (data.hasPermissionToUseAuthToken) {

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

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

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

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

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

@ -127,12 +127,13 @@ export class GfLandingPageComponent implements OnDestroy, OnInit {
}; };
} }
this.hasPermissionForDemo = !!demoAuthToken;
this.hasPermissionForAuthToken = hasPermission( this.hasPermissionForAuthToken = hasPermission(
globalPermissions, globalPermissions,
permissions.enableAuthToken permissions.enableAuthToken
); );
this.hasPermissionForDemo = !!demoAuthToken;
this.hasPermissionForStatistics = hasPermission( this.hasPermissionForStatistics = hasPermission(
globalPermissions, globalPermissions,
permissions.enableStatistics permissions.enableStatistics

6
apps/client/src/app/pages/landing/landing-page.html

@ -32,7 +32,7 @@
<div class="container"> <div class="container">
<div class="button-container mb-5 row"> <div class="button-container mb-5 row">
<div class="align-items-center col d-flex justify-content-center"> <div class="align-items-center col d-flex justify-content-center">
@if (hasPermissionToCreateUser && hasPermissionForAuthToken) { @if (hasPermissionToCreateUser) {
<a <a
color="primary" color="primary"
i18n i18n
@ -42,7 +42,7 @@
> >
} }
@if (hasPermissionForDemo) { @if (hasPermissionForDemo) {
@if (hasPermissionToCreateUser && hasPermissionForAuthToken) { @if (hasPermissionToCreateUser) {
<div class="mx-3 text-muted" i18n>or</div> <div class="mx-3 text-muted" i18n>or</div>
} }
<a i18n mat-stroked-button [routerLink]="routerLinkDemo">Live Demo</a> <a i18n mat-stroked-button [routerLink]="routerLinkDemo">Live Demo</a>
@ -323,7 +323,7 @@
</div> </div>
} }
@if (hasPermissionToCreateUser && hasPermissionForAuthToken) { @if (hasPermissionToCreateUser) {
<div class="row my-5"> <div class="row my-5">
<div class="col"> <div class="col">
<h2 class="h4 mb-1 text-center" i18n> <h2 class="h4 mb-1 text-center" i18n>

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

@ -53,6 +53,7 @@ export class GfPricingPageComponent implements OnDestroy, OnInit {
public couponId: string; public couponId: string;
public durationExtension: StringValue; public durationExtension: StringValue;
public hasPermissionForAuthToken: boolean; public hasPermissionForAuthToken: boolean;
public hasPermissionToCreateUser: boolean;
public hasPermissionToUpdateUserSettings: boolean; public hasPermissionToUpdateUserSettings: boolean;
public importAndExportTooltipBasic = translate( public importAndExportTooltipBasic = translate(
'DATA_IMPORT_AND_EXPORT_TOOLTIP_BASIC' 'DATA_IMPORT_AND_EXPORT_TOOLTIP_BASIC'
@ -103,17 +104,22 @@ export class GfPricingPageComponent implements OnDestroy, OnInit {
public ngOnInit() { public ngOnInit() {
const { baseCurrency, globalPermissions, subscriptionOffer } = const { baseCurrency, globalPermissions, subscriptionOffer } =
this.dataService.fetchInfo(); this.dataService.fetchInfo();
this.baseCurrency = baseCurrency; this.baseCurrency = baseCurrency;
this.coupon = subscriptionOffer?.coupon;
this.durationExtension = subscriptionOffer?.durationExtension;
this.label = subscriptionOffer?.label;
this.price = subscriptionOffer?.price;
this.hasPermissionForAuthToken = hasPermission( this.hasPermissionForAuthToken = hasPermission(
globalPermissions, globalPermissions,
permissions.enableAuthToken permissions.enableAuthToken
); );
this.coupon = subscriptionOffer?.coupon; this.hasPermissionToCreateUser = hasPermission(
this.durationExtension = subscriptionOffer?.durationExtension; globalPermissions,
this.label = subscriptionOffer?.label; permissions.createUserAccount
this.price = subscriptionOffer?.price; );
this.userService.stateChanged this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))

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

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

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

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

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

@ -28,12 +28,12 @@ export const permissions = {
deleteTag: 'deleteTag', deleteTag: 'deleteTag',
deleteUser: 'deleteUser', deleteUser: 'deleteUser',
deleteWatchlistItem: 'deleteWatchlistItem', deleteWatchlistItem: 'deleteWatchlistItem',
enableAuthGoogle: 'enableAuthGoogle',
enableAuthToken: 'enableAuthToken',
enableDataProviderGhostfolio: 'enableDataProviderGhostfolio', enableDataProviderGhostfolio: 'enableDataProviderGhostfolio',
enableFearAndGreedIndex: 'enableFearAndGreedIndex', enableFearAndGreedIndex: 'enableFearAndGreedIndex',
enableImport: 'enableImport', enableImport: 'enableImport',
enableBlog: 'enableBlog', enableBlog: 'enableBlog',
enableAuthGoogle: 'enableAuthGoogle',
enableAuthToken: 'enableAuthToken',
enableStatistics: 'enableStatistics', enableStatistics: 'enableStatistics',
enableSubscription: 'enableSubscription', enableSubscription: 'enableSubscription',
enableSubscriptionInterstitial: 'enableSubscriptionInterstitial', enableSubscriptionInterstitial: 'enableSubscriptionInterstitial',

Loading…
Cancel
Save