Browse Source

feat(auth): add support for access token login configuration and handling

pull/5912/head
Germán Martín 2 months ago
parent
commit
421bc3d040
  1. 4
      .env.example
  2. 7
      apps/api/src/app/info/info.service.ts
  3. 11
      apps/api/src/app/user/user.controller.ts
  4. 1
      apps/api/src/services/configuration/configuration.service.ts
  5. 1
      apps/api/src/services/interfaces/environment.interface.ts
  6. 1
      apps/client/src/app/components/header/header.component.ts
  7. 1
      apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts
  8. 68
      apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html
  9. 1
      libs/common/src/lib/interfaces/info-item.interface.ts

4
.env.example

@ -20,6 +20,10 @@ ROOT_URL=https://<your_domain>
# Enable social login (Google, OIDC, etc.) # Enable social login (Google, OIDC, etc.)
# ENABLE_FEATURE_SOCIAL_LOGIN=true # ENABLE_FEATURE_SOCIAL_LOGIN=true
# Enable access token login (anonymous login)
# Set to false to disable login by access token when using OAuth providers
ENABLE_ACCESS_TOKEN_LOGIN=true
# OIDC AUTHENTICATION (Optional) # OIDC AUTHENTICATION (Optional)
# Enable/disable OIDC authentication # Enable/disable OIDC authentication
OIDC_ENABLED=false OIDC_ENABLED=false

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

@ -128,7 +128,11 @@ export class InfoService {
this.subscriptionService.getSubscriptionOffer({ key: 'default' }) this.subscriptionService.getSubscriptionOffer({ key: 'default' })
]); ]);
if (isUserSignupEnabled) { const isAccessTokenLoginEnabled = this.configurationService.get(
'ENABLE_ACCESS_TOKEN_LOGIN'
);
if (isUserSignupEnabled && isAccessTokenLoginEnabled) {
globalPermissions.push(permissions.createUserAccount); globalPermissions.push(permissions.createUserAccount);
} }
@ -137,6 +141,7 @@ export class InfoService {
benchmarks, benchmarks,
demoAuthToken, demoAuthToken,
globalPermissions, globalPermissions,
isAccessTokenLoginEnabled,
isReadOnlyMode, isReadOnlyMode,
platforms, platforms,
statistics, statistics,

11
apps/api/src/app/user/user.controller.ts

@ -126,6 +126,17 @@ export class UserController {
); );
} }
const isAccessTokenLoginEnabled = this.configurationService.get(
'ENABLE_ACCESS_TOKEN_LOGIN'
);
if (!isAccessTokenLoginEnabled) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const hasAdmin = await this.userService.hasAdmin(); const hasAdmin = await this.userService.hasAdmin();
const { accessToken, id, role } = await this.userService.createUser({ const { accessToken, id, role } = await this.userService.createUser({

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

@ -40,6 +40,7 @@ export class ConfigurationService {
DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER: json({ DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER: json({
default: [] default: []
}), }),
ENABLE_ACCESS_TOKEN_LOGIN: bool({ default: true }),
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }), ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }),
ENABLE_FEATURE_READ_ONLY_MODE: bool({ default: false }), ENABLE_FEATURE_READ_ONLY_MODE: bool({ default: false }),
ENABLE_FEATURE_SOCIAL_LOGIN: bool({ default: false }), ENABLE_FEATURE_SOCIAL_LOGIN: bool({ default: false }),

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

@ -16,6 +16,7 @@ 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_ACCESS_TOKEN_LOGIN: boolean;
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean; ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean;
ENABLE_FEATURE_READ_ONLY_MODE: boolean; ENABLE_FEATURE_READ_ONLY_MODE: boolean;
ENABLE_FEATURE_SOCIAL_LOGIN: boolean; ENABLE_FEATURE_SOCIAL_LOGIN: boolean;

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

@ -280,6 +280,7 @@ export class GfHeaderComponent implements OnChanges {
data: { data: {
accessToken: '', accessToken: '',
hasPermissionToUseSocialLogin: this.hasPermissionForSocialLogin, hasPermissionToUseSocialLogin: this.hasPermissionForSocialLogin,
isAccessTokenLoginEnabled: this.info?.isAccessTokenLoginEnabled,
socialLoginProviders: this.info?.socialLoginProviders, socialLoginProviders: this.info?.socialLoginProviders,
title: $localize`Sign in` title: $localize`Sign in`
}, },

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

@ -1,6 +1,7 @@
export interface LoginWithAccessTokenDialogParams { export interface LoginWithAccessTokenDialogParams {
accessToken: string; accessToken: string;
hasPermissionToUseSocialLogin: boolean; hasPermissionToUseSocialLogin: boolean;
isAccessTokenLoginEnabled?: boolean;
socialLoginProviders?: string[]; socialLoginProviders?: string[];
title: string; title: string;
} }

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

@ -3,28 +3,32 @@
<div class="py-3" mat-dialog-content> <div class="py-3" mat-dialog-content>
<div class="align-items-center d-flex flex-column"> <div class="align-items-center d-flex flex-column">
<form class="w-100"> <form class="w-100">
<mat-form-field appearance="outline" class="without-hint w-100"> @if (data.isAccessTokenLoginEnabled !== false) {
<mat-label i18n>Security Token</mat-label> <mat-form-field appearance="outline" class="without-hint w-100">
<input <mat-label i18n>Security Token</mat-label>
matInput <input
[formControl]="accessTokenFormControl" matInput
[type]="isAccessTokenHidden ? 'password' : 'text'" [formControl]="accessTokenFormControl"
(keydown.enter)="onLoginWithAccessToken(); $event.preventDefault()" [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'"
/> />
</button> <button
</mat-form-field> 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.hasPermissionToUseSocialLogin) {
<div class="my-3 text-center text-muted" i18n>or</div> @if (data.isAccessTokenLoginEnabled !== false) {
<div class="my-3 text-center text-muted" i18n>or</div>
}
<div class="d-flex flex-column gap-2"> <div class="d-flex flex-column gap-2">
@if (data.socialLoginProviders?.includes('google')) { @if (data.socialLoginProviders?.includes('google')) {
<a <a
@ -59,16 +63,18 @@
>Stay signed in</mat-checkbox >Stay signed in</mat-checkbox
> >
</div> </div>
<div> @if (data.isAccessTokenLoginEnabled !== false) {
<button <div>
color="primary" <button
mat-flat-button color="primary"
[disabled]=" mat-flat-button
!(accessTokenFormControl.dirty && accessTokenFormControl.valid) [disabled]="
" !(accessTokenFormControl.dirty && accessTokenFormControl.valid)
(click)="onLoginWithAccessToken()" "
> (click)="onLoginWithAccessToken()"
<ng-container i18n>Sign in</ng-container> >
</button> <ng-container i18n>Sign in</ng-container>
</div> </button>
</div>
}
</div> </div>

1
libs/common/src/lib/interfaces/info-item.interface.ts

@ -11,6 +11,7 @@ export interface InfoItem {
demoAuthToken: string; demoAuthToken: string;
fearAndGreedDataSource?: string; fearAndGreedDataSource?: string;
globalPermissions: string[]; globalPermissions: string[];
isAccessTokenLoginEnabled?: boolean;
isDataGatheringEnabled?: string; isDataGatheringEnabled?: string;
isReadOnlyMode?: boolean; isReadOnlyMode?: boolean;
platforms: Platform[]; platforms: Platform[];

Loading…
Cancel
Save