Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions src/app/adf-user-management/df-login/df-login.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import { UntilDestroy } from '@ngneat/until-destroy';
import { DfThemeService } from 'src/app/shared/services/df-theme.service';
import { CommonModule } from '@angular/common';
import { DfSnackbarService } from 'src/app/shared/services/df-snackbar.service';
import { PopupOverlayService } from 'src/app/shared/components/df-popup/popup-overlay.service';

@UntilDestroy({ checkProperties: true })
@Component({
selector: 'df-user-login',
Expand All @@ -58,6 +60,7 @@ import { DfSnackbarService } from 'src/app/shared/services/df-snackbar.service';
],
})
export class DfLoginComponent implements OnInit {
private readonly MINIMUM_PASSWORD_LENGTH = 17;
alertMsg = '';
showAlert = false;
alertType: AlertType = 'error';
Expand All @@ -78,7 +81,8 @@ export class DfLoginComponent implements OnInit {
private authService: DfAuthService,
private router: Router,
private themeService: DfThemeService,
private snackbarService: DfSnackbarService
private snackbarService: DfSnackbarService,
private popupOverlay: PopupOverlayService
) {
this.loginForm = this.fb.group({
services: [''],
Expand Down Expand Up @@ -132,6 +136,8 @@ export class DfLoginComponent implements OnInit {
if (this.loginForm.invalid) {
return;
}

const isPasswordTooShort = this.loginForm.value.password.length < this.MINIMUM_PASSWORD_LENGTH;
const credentials: LoginCredentials = {
password: this.loginForm.value.password,
};
Expand All @@ -143,17 +149,31 @@ export class DfLoginComponent implements OnInit {
} else {
credentials.email = this.loginForm.value.email;
}

this.authService
.login(credentials)
.pipe(
catchError(err => {
this.alertMsg = err.error.error.message;
this.showAlert = true;
if (err.status === 401 && isPasswordTooShort) {
this.popupOverlay.open({
message: `It looks like your password is too short. Our new system requires at least ${this.MINIMUM_PASSWORD_LENGTH} characters. Please reset your password to continue.`,
showRemindMeLater: false,
});
} else {
this.alertMsg = err.error?.error?.message || 'Login failed';
this.showAlert = true;
}
return throwError(() => new Error(err));
})
)
.subscribe(() => {
this.showAlert = false;
if (isPasswordTooShort) {
this.popupOverlay.open({
message: `Your current password is shorter than recommended (less than ${this.MINIMUM_PASSWORD_LENGTH} characters). For better security, we recommend updating your password to a longer one.`,
showRemindMeLater: true,
});
}
this.router.navigate([ROUTES.HOME]);
});
}
Expand Down
4 changes: 2 additions & 2 deletions src/app/adf-user-management/services/df-auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export class DfAuthService {
);
}

logout() {
logout(redirectTo: any[] = [ROUTES.AUTH, ROUTES.LOGIN]) {
this.http
.delete(
this.userDataService.userData?.isSysAdmin
Expand All @@ -114,7 +114,7 @@ export class DfAuthService {
.subscribe(() => {
this.userDataService.clearToken();
this.userDataService.userData = null;
this.router.navigate([ROUTES.AUTH, ROUTES.LOGIN]);
this.router.navigate(redirectTo);
});
}
}
3 changes: 1 addition & 2 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Component, OnInit } from '@angular/core';
import { DfLoadingSpinnerService } from './shared/services/df-loading-spinner.service';
import { NgIf, AsyncPipe } from '@angular/common';
import { RouterOutlet, Router, ActivatedRoute } from '@angular/router';
import { DfSideNavComponent } from './shared/components/df-side-nav/df-side-nav.component';
import { DfLicenseCheckService } from './shared/services/df-license-check.service';
import { UntilDestroy } from '@ngneat/until-destroy';
import { AuthService } from './shared/services/auth.service';
Expand All @@ -15,7 +14,7 @@ import { LoginResponse } from './shared/types/auth.types';
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [DfSideNavComponent, RouterOutlet, NgIf, AsyncPipe],
imports: [RouterOutlet, NgIf, AsyncPipe],
})
export class AppComponent implements OnInit {
title = 'df-admin-interface';
Expand Down
24 changes: 24 additions & 0 deletions src/app/shared/components/df-popup/df-popup.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<div class="popup-container">
<div class="popup">
<div class="popup-header">
<h2>{{ 'Password Security Notice' | transloco }}</h2>
</div>
<div class="popup-content">
<p>{{ message | transloco }}</p>
</div>
<div class="popup-actions">
<button *ngIf="showRemindMeLater"
mat-stroked-button
(click)="closePopup(false)"
type="button">
{{ 'Remind me later' | transloco }}
</button>
<button mat-flat-button
color="primary"
(click)="closePopup(true)"
type="button">
{{ 'Update Password Now' | transloco }}
</button>
</div>
</div>
</div>
110 changes: 110 additions & 0 deletions src/app/shared/components/df-popup/df-popup.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Popup Container
.popup-container {
display: flex;
justify-content: center;
align-items: center;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 10000;
}

.popup {
position: relative;
width: 90%;
max-width: 500px;
background: #ffffff;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
padding: 24px;
z-index: 10001;
animation: popupFadeIn 0.3s ease-out;

.popup-header {
margin-bottom: 20px;
text-align: center;

h2 {
margin: 0;
color: #333333;
font-size: 1.5rem;
font-weight: 600;
}
}

.popup-content {
margin-bottom: 24px;
text-align: center;

p {
margin: 8px 0;
color: #666666;
line-height: 1.5;
}
}

.popup-actions {
display: flex;
justify-content: center;
gap: 12px;

button {
min-width: 120px;
padding: 8px 16px;
font-weight: 500;
transition: all 0.2s ease;

&:hover {
transform: translateY(-1px);
}
}
}
}

@keyframes popupFadeIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

.actions {
display: flex;
flex-direction: row;
}

// Popup Header
.popup-header {
font-size: 18px;
font-weight: bold;
color: #6d4ec9; // Matches sidebar color
margin-bottom: 10px;
}

// Popup Content
.popup-content {
font-size: 14px;
margin-bottom: 15px;
}

// Close Button
.popup-close {
background: #6d4ec9; // Match theme
color: #fff;
border: none;
padding: 10px 15px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s ease;

&:hover {
background: #5a3bb3; // Darker on hover
}
}
42 changes: 42 additions & 0 deletions src/app/shared/components/df-popup/df-popup.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Component, Inject, Optional } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { TranslocoPipe } from '@ngneat/transloco';
import { Router } from '@angular/router';
import { ROUTES } from '../../types/routes';
import { PopupOverlayService } from './popup-overlay.service';
import { DfAuthService } from 'src/app/adf-user-management/services/df-auth.service';
import { POPUP_CONFIG, PopupConfig } from './popup-config';

@Component({
selector: 'df-popup',
templateUrl: './df-popup.component.html',
standalone: true,
styleUrls: ['./df-popup.component.scss'],
imports: [CommonModule, MatButtonModule, MatDialogModule, TranslocoPipe],
})
export class PopupComponent {
constructor(
private router: Router,
private popupOverlay: PopupOverlayService,
private authService: DfAuthService,
@Optional() @Inject(POPUP_CONFIG) public config: PopupConfig
) {}

get message() {
return this.config?.message ||
'Your current password is shorter than recommended (less than 17 characters). For better security, we recommend updating your password to a longer one.';
}
get showRemindMeLater() {
return this.config?.showRemindMeLater !== false;
}

closePopup(shouldRedirect = false) {
this.popupOverlay.close();
if (shouldRedirect) {
this.authService.logout();
this.router.navigate([ROUTES.AUTH, ROUTES.RESET_PASSWORD]);
}
}
}
8 changes: 8 additions & 0 deletions src/app/shared/components/df-popup/popup-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { InjectionToken } from '@angular/core';

export interface PopupConfig {
message: string;
showRemindMeLater: boolean;
}

export const POPUP_CONFIG = new InjectionToken<PopupConfig>('POPUP_CONFIG');
41 changes: 41 additions & 0 deletions src/app/shared/components/df-popup/popup-overlay.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Injectable, Injector } from '@angular/core';
import { Overlay, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import { PopupComponent } from './df-popup.component';
import { POPUP_CONFIG, PopupConfig } from './popup-config';

@Injectable({ providedIn: 'root' })
export class PopupOverlayService {
private overlayRef: OverlayRef | null = null;

constructor(
private overlay: Overlay,
private injector: Injector
) {}

open(config?: PopupConfig) {
if (this.overlayRef) return;
const injector = Injector.create({
providers: [{ provide: POPUP_CONFIG, useValue: config }],
parent: this.injector,
});
this.overlayRef = this.overlay.create({
hasBackdrop: true,
backdropClass: 'popup-backdrop',
positionStrategy: this.overlay
.position()
.global()
.centerHorizontally()
.centerVertically(),
scrollStrategy: this.overlay.scrollStrategies.block(),
});
const portal = new ComponentPortal(PopupComponent, null, injector);
this.overlayRef.attach(portal);
this.overlayRef.backdropClick().subscribe(() => this.close());
}

close() {
this.overlayRef?.dispose();
this.overlayRef = null;
}
}
26 changes: 26 additions & 0 deletions src/app/shared/services/popup.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({
providedIn: 'root'
})
export class PopupService {
private storageKey = 'showPasswordPopup';
private popupStateSubject = new BehaviorSubject<boolean>(false);
popupState$ = this.popupStateSubject.asObservable();

constructor() {
// Initialize from localStorage
const savedState = this.shouldShowPopup();
this.popupStateSubject.next(savedState);
}

setShowPopup(value: boolean): void {
localStorage.setItem(this.storageKey, JSON.stringify(value));
this.popupStateSubject.next(value);
}

shouldShowPopup(): boolean {
return JSON.parse(localStorage.getItem(this.storageKey) || 'false');
}
}
6 changes: 6 additions & 0 deletions src/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,9 @@ a {
.swagger-ui .opblock .opblock-section-header {
background: unset !important;
}

.popup-backdrop {
background: rgba(0, 0, 0, 0.6) !important;
backdrop-filter: blur(6px) !important;
-webkit-backdrop-filter: blur(6px) !important;
}