From 7d2eb68914b5fc53a4e3dbf8fe22c4f67b8bff7f Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Mon, 26 Jun 2023 11:31:22 +0300 Subject: [PATCH 1/2] Add refreshToken and accessToken in userInfo. Add property for clientManagementAPI. Create jsonValidator --- login/adminLoginGuard.guard.ts | 4 +++- login/loginGuard.guard.ts | 8 +++++--- login/utils/helper.class.ts | 4 ++-- reload/reload.component.ts | 2 ++ services/user-management.service.ts | 8 +++++++- utils/properties/env-properties.ts | 1 + utils/string-utils.class.ts | 15 +++++++++++++++ 7 files changed, 35 insertions(+), 7 deletions(-) diff --git a/login/adminLoginGuard.guard.ts b/login/adminLoginGuard.guard.ts index 0548fb3b..53b2ec94 100644 --- a/login/adminLoginGuard.guard.ts +++ b/login/adminLoginGuard.guard.ts @@ -13,7 +13,9 @@ import {LoginErrorCodes} from './utils/guardHelper.class'; import {UserManagementService} from "../services/user-management.service"; import {map, tap} from "rxjs/operators"; -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class AdminLoginGuard implements CanActivate, CanActivateChild { constructor(private router: Router, diff --git a/login/loginGuard.guard.ts b/login/loginGuard.guard.ts index 59e72592..0c9f737c 100644 --- a/login/loginGuard.guard.ts +++ b/login/loginGuard.guard.ts @@ -6,15 +6,17 @@ import { CanLoad, Route, Router, - RouterStateSnapshot, UrlTree + RouterStateSnapshot, + UrlTree } from '@angular/router'; import {Observable} from 'rxjs'; -import {Session} from './utils/helper.class'; import {LoginErrorCodes} from './utils/guardHelper.class'; import {map, tap} from "rxjs/operators"; import {UserManagementService} from "../services/user-management.service"; -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class LoginGuard implements CanActivate, CanLoad, CanActivateChild { constructor(private router: Router, diff --git a/login/utils/helper.class.ts b/login/utils/helper.class.ts index ca98fd5c..7594fac1 100644 --- a/login/utils/helper.class.ts +++ b/login/utils/helper.class.ts @@ -6,8 +6,8 @@ export class User { fullname: string; expirationDate: number; role: string[]; - jwt: string; - + accessToken?: string; + refreshToken?: string; } export class Session { diff --git a/reload/reload.component.ts b/reload/reload.component.ts index e86df79c..833b43ce 100644 --- a/reload/reload.component.ts +++ b/reload/reload.component.ts @@ -16,6 +16,7 @@ export class ReloadComponent { public ngOnInit() { let URL = Session.getReloadUrl(); + console.log(URL); if (URL && URL["path"] && URL["path"] != "") { let url: string = URL["path"]; let host = URL["host"]; @@ -23,6 +24,7 @@ export class ReloadComponent { if (host == (location.protocol + "//" + location.host)) { let baseUrl = (document && document.getElementsByTagName('base')) ? document.getElementsByTagName('base')[0].href.split(document.location.host)[1] : "/"; url = (baseUrl.length > 1 && url.indexOf(baseUrl) != -1) ? ("/" + url.split(baseUrl)[1]) : url; + console.log(url); if (paramsObject) { Session.setReloadUrl("", "", "", ""); if(URL['fragment'] && URL['fragment'] !== '') { diff --git a/services/user-management.service.ts b/services/user-management.service.ts index 9291cc6a..0b942edb 100644 --- a/services/user-management.service.ts +++ b/services/user-management.service.ts @@ -54,13 +54,19 @@ export class UserManagementService { } }); } - + private parseUserInfo(info: any) { const user: User = new User(); user.id = (info.sub && info.sub.indexOf('@')) ? info.sub.substring(0, info.sub.indexOf('@')) : info.sub; user.firstname = (info.given_name) ? info.given_name : ""; user.lastname = (info.family_name) ? info.family_name : ""; user.email = info.email.toLowerCase(); // TODO remove, is a quick fix + if(info.accessToken) { + user.accessToken = info.accessToken; + } + if(info.refreshToken) { + user.refreshToken = info.refreshToken; + } user.fullname = (info.name) ? info.name : ""; if (user.fullname == "") { if (user.firstname != "") { diff --git a/utils/properties/env-properties.ts b/utils/properties/env-properties.ts index cacc7cb1..4b1279bf 100644 --- a/utils/properties/env-properties.ts +++ b/utils/properties/env-properties.ts @@ -64,6 +64,7 @@ export interface EnvProperties { registryUrl?: string; logoutUrl?: string; userInfoUrl?: string; + clientManagementUrl?: string, cookieDomain?: string; feedbackmail?: string; feedbackmailForMissingEntities?: string; diff --git a/utils/string-utils.class.ts b/utils/string-utils.class.ts index 61d45b48..b1dab0d3 100644 --- a/utils/string-utils.class.ts +++ b/utils/string-utils.class.ts @@ -266,6 +266,8 @@ export class StringUtils { '[a-zA-Z0-9]+\\.[^\\s]{2,}'; public static routeRegex = '^[a-zA-Z0-9\/][a-zA-Z0-9\/-]*$'; + + public static jsonRegex = /^[\],:{}\s]*$/; public static urlPrefix(url: string): string { if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//")) { @@ -325,6 +327,19 @@ export class StringUtils { public static urlValidator(): ValidatorFn { return Validators.pattern(StringUtils.urlRegex); } + + public static jsonValidator(error: string = ''): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if(control.value) { + let test = control.getRawValue().replace(/\\["\\\/bfnrtu]/g, '@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, ''); + console.log('test') + if(!new RegExp(this.jsonRegex).test(test)) { + return {error: 'Please provide a valid JSON.' + error} + } + } + return null; + }; + } public static validatorType(options: string[]): ValidatorFn { return (control: AbstractControl): { [key: string]: boolean } | null => { From 0face4b57f291ea0168fe0d8fe32b51ebb1850d8 Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Mon, 26 Jun 2023 15:09:37 +0300 Subject: [PATCH 2/2] Fix an undefined error in searchInput of Input component. Add multi emails in invitation of manager and members and optimize subscriber invite. --- .../role-users/role-users.component.html | 11 +- .../users/role-users/role-users.component.ts | 110 +++++++++++++----- sharedComponents/input/input.component.ts | 8 +- .../subscriber-invite.component.ts | 13 ++- 4 files changed, 104 insertions(+), 38 deletions(-) diff --git a/dashboard/users/role-users/role-users.component.html b/dashboard/users/role-users/role-users.component.html index cdf80b45..6fb1d94f 100644 --- a/dashboard/users/role-users/role-users.component.html +++ b/dashboard/users/role-users/role-users.component.html @@ -95,13 +95,16 @@ + [okDisabled]="!valid">
-
-
+
+
+
Separate emails with commas
+
diff --git a/dashboard/users/role-users/role-users.component.ts b/dashboard/users/role-users/role-users.component.ts index 9adb749d..665a8303 100644 --- a/dashboard/users/role-users/role-users.component.ts +++ b/dashboard/users/role-users/role-users.component.ts @@ -1,5 +1,21 @@ -import {Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild} from '@angular/core'; -import {UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms'; +import { + ChangeDetectorRef, + Component, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, + ViewChild +} from '@angular/core'; +import { + UntypedFormArray, + UntypedFormBuilder, + UntypedFormControl, + UntypedFormGroup, + ValidatorFn, + Validators +} from '@angular/forms'; import {AlertModal} from "../../../utils/modal/alert"; import {UserRegistryService} from "../../../services/user-registry.service"; import {EnvProperties} from "../../../utils/properties/env-properties"; @@ -9,9 +25,22 @@ import {UserManagementService} from "../../../services/user-management.service"; import {Router} from "@angular/router"; import {StringUtils} from "../../../utils/string-utils.class"; import {NotificationService} from "../../../notifications/notification.service"; -import {Subscription} from "rxjs"; +import {forkJoin, of, Subscription} from "rxjs"; import {NotificationHandler} from "../../../utils/notification-handler"; import {ClearCacheService} from "../../../services/clear-cache.service"; +import {catchError, map, tap} from "rxjs/operators"; +import notification = CKEDITOR.plugins.notification; +import {InputComponent} from "../../../sharedComponents/input/input.component"; + +class InvitationResponse { + email: string; + invitation: any; + + constructor(email, invitation) { + this.email = email; + this.invitation = invitation; + } +} @Component({ selector: 'role-users', @@ -49,7 +78,8 @@ export class RoleUsersComponent implements OnInit, OnDestroy, OnChanges { public loadActive: boolean = true; public loadPending: boolean = true; public selectedUser: string = null; - public invited: UntypedFormControl; + public emailsForm: UntypedFormArray; + public validators: ValidatorFn[] = [Validators.email]; public properties: EnvProperties = properties; public exists: boolean = true; public roleFb: UntypedFormGroup; @@ -60,6 +90,7 @@ export class RoleUsersComponent implements OnInit, OnDestroy, OnChanges { /** Search */ filterForm: UntypedFormGroup; @ViewChild('inviteModal') inviteModal: AlertModal; + @ViewChild('emailInput') emailInput: InputComponent; @ViewChild('deleteModal') deleteModal: AlertModal; @ViewChild('deletePendingModal') deletePendingModal: AlertModal; @ViewChild('createRoleModal') createRoleModal: AlertModal; @@ -69,6 +100,7 @@ export class RoleUsersComponent implements OnInit, OnDestroy, OnChanges { private clearCacheService: ClearCacheService, private notificationService: NotificationService, private router: Router, + private cdr: ChangeDetectorRef, private fb: UntypedFormBuilder) { } @@ -172,7 +204,8 @@ export class RoleUsersComponent implements OnInit, OnDestroy, OnChanges { this.inviteModal.alertTitle = 'Invite ' + this.role; this.inviteModal.okButtonLeft = false; this.inviteModal.okButtonText = 'Send'; - this.invited = this.fb.control('', [Validators.required, Validators.email]); + this.emailsForm = this.fb.array([], Validators.required); + this.cdr.detectChanges(); this.inviteModal.open(); } @@ -224,33 +257,56 @@ export class RoleUsersComponent implements OnInit, OnDestroy, OnChanges { this.loadPending = false; })); } - + + get valid() { + return this.emailsForm && this.emailsForm.valid || (this.emailInput && this.emailInput.searchControl.getRawValue() && this.emailInput.searchControl.valid); + } + invite() { + if(this.emailInput.searchControl.getRawValue() && this.emailInput.searchControl.valid) { + this.emailInput.add(null,true); + } this.showCurrent = false; this.loadPending = true; - this.selectedUser = this.invited.value; - let details = { - link: this.link, - email: this.emailComposer(this.name, this.invited.value, this.role) - } - this.subs.push(this.userRegistryService.invite(this.type, this.id, details, this.role).subscribe(invitation => { - if (!this.pending.includes(this.invited.value)) { - this.pending.push(this.invited.value); + let current = null; + let invitations = (>this.emailsForm.value).map(email => { + current = email; + return this.userRegistryService.invite(this.type, this.id, { + link: this.link, + email: this.emailComposer(this.name, email, this.role) + }, this.role).pipe(map(invitation => new InvitationResponse(email, invitation), catchError(error => { + return of(new InvitationResponse(current, null)); + }))); + }); + this.subs.push(forkJoin(invitations).subscribe(responses => { + let notifications = responses.map(response => { + if(response.invitation) { + NotificationHandler.rise(StringUtils.capitalize(this.role) + ' invitation to ' + response.email + ' has been sent'); + if (!this.pending.includes(response.email)) { + this.pending.push(response.email); + } + if(this.notificationFn) { + return this.notificationService.sendNotification(this.notificationFn(this.name, response.email, this.role, response.invitation)).pipe(map(notification => { + return notification; + }), tap(() => { + NotificationHandler.rise('A notification has been sent successfully to ' + response.email); + }), catchError( error => { + NotificationHandler.rise('An error has occurred while sending a notification to ' + response.email + '. Please try again later', 'danger'); + return of(null); + })); + } else { + return of(null); + } + } else { + NotificationHandler.rise('An error has occurred while sending the invitation mail to ' + response.email + '.Please try again later', 'danger'); + return of(null); + } + }); + this.subs.push(forkJoin(notifications).subscribe(() => { this.pendingPage = Math.ceil(this.pending.length / this.pageSize); this.filterPendingBySearch(this.filterForm.value.pending); - } - if (this.notificationFn) { - this.subs.push(this.notificationService.sendNotification(this.notificationFn(this.name, this.invited.value, this.role, invitation)).subscribe(notification => { - NotificationHandler.rise('A notification has been sent successfully'); - }, error => { - NotificationHandler.rise('An error has occurred. Please try again later', 'danger'); - })); - } - NotificationHandler.rise(StringUtils.capitalize(this.role) + ' invitation to ' + this.selectedUser + ' has been sent'); - this.loadPending = false; - }, error => { - NotificationHandler.rise('An error has occurred. Please try again later', 'danger'); - this.loadPending = false; + this.loadPending = false; + })) })); } diff --git a/sharedComponents/input/input.component.ts b/sharedComponents/input/input.component.ts index 98f4b9c4..7b889d2f 100644 --- a/sharedComponents/input/input.component.ts +++ b/sharedComponents/input/input.component.ts @@ -405,7 +405,7 @@ export class InputComponent implements OnInit, OnDestroy, AfterViewInit, OnChang } click(event: ClickEvent) { - this.focus(!event.clicked, event); + this.focus(!event.clicked); } ngOnInit() { @@ -532,8 +532,10 @@ export class InputComponent implements OnInit, OnDestroy, AfterViewInit, OnChang if (this.focused) { this.open(true); setTimeout(() => { - this.searchInput.nativeElement.focus(); - this.searchInput.nativeElement.value = value; + if(this.searchInput) { + this.searchInput.nativeElement.focus(); + this.searchInput.nativeElement.value = value; + } }, 0); } })); diff --git a/sharedComponents/subscriber-invite/subscriber-invite.component.ts b/sharedComponents/subscriber-invite/subscriber-invite.component.ts index 515fc97b..863ac1e3 100644 --- a/sharedComponents/subscriber-invite/subscriber-invite.component.ts +++ b/sharedComponents/subscriber-invite/subscriber-invite.component.ts @@ -1,4 +1,4 @@ -import {Component, Input, OnDestroy, OnInit} from "@angular/core"; +import {Component, Input, OnDestroy, OnInit, ViewChild} from "@angular/core"; import {AbstractControl, FormBuilder, FormGroup, ValidationErrors, ValidatorFn, Validators} from "@angular/forms"; import {Subscriber} from "rxjs"; import {StringUtils} from "../../utils/string-utils.class"; @@ -11,13 +11,14 @@ import {EmailService} from "../../utils/email/email.service"; import {properties} from "../../../../environments/environment"; import {CommunityInfo} from "../../connect/community/communityInfo"; import {NotificationHandler} from "../../utils/notification-handler"; +import {InputComponent} from "../input/input.component"; @Component({ selector: 'subscriber-invite', template: `
-
Separate emails with commas
@@ -61,7 +62,8 @@ export class SubscriberInviteComponent implements OnInit, OnDestroy { public validators: ValidatorFn[] = [Validators.email]; public loading: boolean = true; private subscriptions: any[] = []; - + @ViewChild('emailInput') emailInput: InputComponent; + constructor(private fb: FormBuilder, private emailService: EmailService, private communityService: CommunityService) { @@ -105,6 +107,9 @@ export class SubscriberInviteComponent implements OnInit, OnDestroy { } invite() { + if(this.emailInput.searchControl.getRawValue() && this.emailInput.searchControl.valid) { + this.emailInput.add(null,true); + } this.loading = true; this.body.paragraphs = this.inviteForm.getRawValue().message; this.email.body = Composer.formatEmailBodyForInvitation(this.body); @@ -129,6 +134,6 @@ export class SubscriberInviteComponent implements OnInit, OnDestroy { } get valid() { - return !this.loading && this.inviteForm && this.inviteForm.valid; + return !this.loading && this.inviteForm && this.inviteForm.valid || (this.emailInput && this.emailInput.searchControl.getRawValue() && this.emailInput.searchControl.valid); } }