IEEE.org     |     IEEE Xplore Digital Library     |     IEEE Standards     |     IEEE Spectrum     |     More Sites

Commit 6cea5920 authored by Douglas Williams's avatar Douglas Williams
Browse files

Merge branch 'randa-merge' into 'main'

UX updates and bug fixes

See merge request !18
parents 065aa24e 5b990abc
......@@ -9,8 +9,6 @@ namespace OpenCredentialPublisher.Data.Dtos.Account_Manage
{
public class ProfileInputDto
{
[Display(Name = "Username")]
public string Username { get; set; }
[Phone]
[Display(Name = "Phone number")]
......
......@@ -32,13 +32,13 @@ namespace OpenCredentialPublisher.Data.Models
if (dictionary.ContainsKey("L"))
{
sb.Append($"{dictionary["L"]}");
sb.Append($", {dictionary["L"]}");
if (dictionary.ContainsKey("S"))
sb.Append($", {dictionary["S"]}");
}
if (dictionary.ContainsKey("C"))
sb.Append($"{dictionary["C"]}");
sb.Append($", {dictionary["C"]}");
if (sb.Length == 0 && dictionary.ContainsKey("CN"))
sb.Append($"{dictionary["CN"]}");
......
......@@ -9,10 +9,6 @@ namespace OpenCredentialPublisher.Data.ViewModels.nG
{
public class RegisterAccountVM
{
[Required]
[MaxLength(255)]
[Display(Name = "Username")]
public string UserName { get; set; }
[Required]
[MaxLength(255)]
[Display(Name = "Displayable Name")]
......
......@@ -223,6 +223,7 @@ namespace OpenCredentialPublisher.Services.Implementations
public async Task<ClrModel> GetSingleClrAsync(int id)
{
var clr = await _context.Clrs.AsNoTracking()
.Include(c => c.CredentialPackage)
.Include(c => c.Learner)
.Include(c => c.Publisher)
.Include(c => c.SmartResume)
......
......@@ -66,7 +66,7 @@ namespace OpenCredentialPublisher.Services.Implementations
throw new Exception("There was a problem saving your profile image to your account.");
}
public async Task<bool> DeleteImageFromBlobAsync(string filename)
public async Task<bool> DeleteImageFromBlobAsync(string location)
{
var container = new BlobContainerClient(_publicBlobOptions.StorageConnectionString, BlobContainerName);
if (!(await container.ExistsAsync()))
......@@ -76,13 +76,13 @@ namespace OpenCredentialPublisher.Services.Implementations
}
try
{
BlobClient blob = container.GetBlobClient(filename);
var storageAccount = AzureBlobStoreService.ParseStorageAccountUrl(location);
BlobClient blob = container.GetBlobClient(storageAccount.filename);
return await blob.DeleteIfExistsAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, $"There was a problem deleting {filename} from {BlobContainerName}");
_logger.LogError(ex, $"There was a problem deleting {location} from {BlobContainerName}");
}
return false;
}
......
......@@ -397,7 +397,7 @@ namespace OpenCredentialPublisher.Services.Implementations
return new RevocationListModel(revocations, "Revocation list refreshed, no changes.");
}
}
return new RevocationListModel(null, error: "Could not retrieve RevocationList.");
return new RevocationListModel(null, error: "Could not retrieve revocation list.");
}
public async Task<List<RevocationModel>> GetSavedRevocationListAsync(string userId, int sourceId)
......
......@@ -595,6 +595,8 @@ namespace OpenCredentialPublisher.Services.Implementations
(int)CredentialRequestStepEnum.PendingCredentialDefinitionEndorsement)));
}
await _blobStoreService.StoreAsync($"{message.Thread.Thid}.json", JsonConvert.SerializeObject(message), "creddefendorsement");
var agentContext = await GetAgentContextAsync();
var notification = new CredentialDefinitionNeedsEndorsementNotification(agentContext.IssuerDid, agentContext.IssuerVerKey, message.Thread.Thid, message.CredDefId, message.CredDefJson);
await _queueService.SendMessageAsync(CredentialDefinitionNeedsEndorsementNotification.QueueName, JsonConvert.SerializeObject(notification));
......
......@@ -10,7 +10,7 @@
"prefix": "app",
"schematics": {},
"architect": {
"build": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"progress": false,
......@@ -21,7 +21,8 @@
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/assets",
"src/web.config"
"src/web.config",
"src/silent-renew.html"
],
"styles": [
"../wwwroot/lib/fontawesome-pro-5.15.1-web/css/all.css",
......
......@@ -14,6 +14,8 @@ const routerOptions: ExtraOptions = {
anchorScrolling: 'enabled',
};
export const protectedRoutes = new RegExp(/credentials|sources|links|wallets|account/, 'i');
export const appRoutes: Routes = [
{
path: '',
......@@ -78,7 +80,7 @@ export const appRoutes: Routes = [
/****************************************************
* Unprotected routes *
****************************************************/
{
path: 'public',
loadChildren: () => import('./modules/public/public.module').then(m =>m.PublicModule)
......@@ -95,7 +97,7 @@ export const appRoutes: Routes = [
loadChildren: () => import('./modules/public/public.module').then(m =>m.PublicModule)
}]
},
{
path: 'Verifier',
loadChildren: () => import('./modules/verifier/verifier.module').then(m =>m.VerifierModule)
......@@ -112,7 +114,7 @@ export const appRoutes: Routes = [
path: 'Search',
loadChildren: () => import('./modules/search/search.module').then(m =>m.SearchModule)
},
{
path: 'sources-callback',
component: SourcesCallbackComponent,
......@@ -122,7 +124,7 @@ export const appRoutes: Routes = [
path: 'sources-error',
component: SourcesErrorComponent
},
];
@NgModule({
......
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Component, HostListener, OnDestroy, OnInit } from '@angular/core';
import { Event as NavigationEvent, NavigationStart, Router } from '@angular/router';
import { AppService } from '@core/services/app.service';
import { environment } from '@environment/environment';
import { Idle } from '@ng-idle/core';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { protectedRoutes } from './app-routing.module';
import { AuthService } from './auth/auth-client.service';
import { LoginService } from './auth/login.service';
import { TimeoutService } from './services/timeout.service';
......@@ -16,7 +17,7 @@ import { TimeoutService } from './services/timeout.service';
})
export class AppComponent implements OnInit, OnDestroy {
title = 'Open Credential Publisher';
envName = environment.name;
......@@ -36,7 +37,7 @@ export class AppComponent implements OnInit, OnDestroy {
(event: NavigationEvent) => {
if(event instanceof NavigationStart) {
this.currentUrl = event.url;
if (this.debug) console.log(this.currentUrl);
if (this.debug) console.log(this.currentUrl, protectedRoutes.test(this.currentUrl) ? 'protected' : 'not protected');
}
});
}
......@@ -51,55 +52,33 @@ export class AppComponent implements OnInit, OnDestroy {
});
this.authService.clearStaleStorage();
this.authService.silentRenewError.pipe(untilDestroyed(this)).subscribe(ev => {
if (this.debug) {
console.log(`Current url is ${this.currentUrl} and silent renew error is ${ev}`);
}
})
this.authService.checkLogin();
this.authService.signIn();
// if (environment.debug) console.log('AppComponent ngOnInit');
// this.loginService.checkAuthIncludingServer().subscribe((result) => {
// if (environment.debug) console.log("Auth Result: ", result);
// }, (error) => {
// if (environment.debug) {
// console.log(error);
// }
// });
// if (environment.debug) {
// this.eventService
// .registerForEvents()
// .pipe(filter((notification) => notification.type === EventTypes.ConfigLoaded))
// .subscribe((config) => {
// // console.log('ConfigLoaded', config);
// });
// this.eventService
// .registerForEvents()
// .subscribe(notification => console.log(notification));
// }
// this.eventService
// .registerForEvents()
// .pipe(filter((notification) => notification.type === EventTypes.NewAuthenticationResult))
// .subscribe((result: OidcClientNotification<AuthStateResult>) => {
// if (environment.debug) {
// console.log(`Auth State (isAuthenticated: ${result.value?.isAuthenticated}) (isRenewProcess: ${result.value?.isRenewProcess})`);
// }
// this.loginService.reportAuthState(result.value);
// });
if (protectedRoutes.test(this.currentUrl)) {
this.loginService.signOut();
}
});
this.authService.checkLogin();
}
@HostListener('login_required')
onLoginRequired() {
if (this.debug) console.log("onLoginRequired event caught");
if (protectedRoutes.test(this.currentUrl)) {
this.loginService.signOut();
}
}
ngOnDestroy() {
this.navigationEvents$.unsubscribe();
}
getData():any {
if (environment.debug) console.log('AppComponent getData');
}
}
......@@ -15,7 +15,7 @@ export class AuthService {
private _loggedInBehavior: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public isLoggedIn$: Observable<boolean> = this._loggedInBehavior.asObservable();
public accessTokenExpiring: EventEmitter<any> = new EventEmitter<any>();
public accessTokenExpired: EventEmitter<any> = new EventEmitter<any>();
public silentRenewError: EventEmitter<any> = new EventEmitter<any>();
......@@ -26,7 +26,7 @@ export class AuthService {
constructor(private authSettings: AuthSettings) {
this._userManager = new UserManager(authSettings);
this._userManager.events.addAccessTokenExpired((ev) => {
if (environment.debug) console.log("Access token expired");
......@@ -59,7 +59,7 @@ export class AuthService {
if (environment.debug) console.log("User is signed in");
this.userSignedIn.emit(true);
});
this._userManager.events.addUserSessionChanged(() => {
if (environment.debug) console.log("User session changed");
this.userSessionChanged.emit();
......@@ -99,7 +99,7 @@ export class AuthService {
obs.next(false);
}
})
}
getAccessToken() {
......@@ -111,12 +111,7 @@ export class AuthService {
}
checkLogin(): Promise<boolean> {
if (this._user?.expired === false)
{
this.startSilentRenew();
return Promise.resolve(true);
}
else if (this._user?.expired) {
if (this._user != null) {
return this.signIn();
}
return Promise.resolve(false);
......@@ -185,4 +180,4 @@ export class AuthService {
}
}
\ No newline at end of file
}
import { HttpErrorResponse } from '@angular/common/http';
import { ErrorHandler, Injectable } from '@angular/core';
import { ErrorHandler, Injectable, NgZone } from '@angular/core';
import { ErrorService } from '@core/services/error.service';
import { ApiOkResponse } from '@shared/models/apiOkResponse';
import { MessageService } from 'primeng/api';
import { take } from 'rxjs/operators';
import { LogService } from './logerror.service';
@Injectable()
@Injectable({
providedIn: 'root'
})
export class GlobalErrorHandler extends ErrorHandler {
private errorId: string;
private debug = false;
constructor(private logService: LogService, public messageService: MessageService, public errorService: ErrorService) {
constructor(private logService: LogService, public messageService: MessageService, public errorService: ErrorService, private zone: NgZone) {
super();
}
handleError(error: Error | HttpErrorResponse) {
super.handleError(error);
/* eslint-disable no-console */
if (error instanceof HttpErrorResponse) {
if (this.debug) console.log(`GlobalErrorHandler.handleError HttpErrorResponse: ${JSON.stringify(error)}`);
console.error(error);
this.logService.logNgError(`${this.errorService.getServerMessage(error)} from Server`)
.pipe(take(1)).subscribe(data => {
this.logService.logNgError(`${this.errorService.getServerMessage(error)} from Server`).subscribe(data => {
if (this.debug) console.log(`GlobalErrorHandler.handleError logNgError returned:${JSON.stringify(data)}`);
if (data.statusCode == 200) {
this.messageService.add({
key: 'main', severity: 'error', summary: 'Unhandled Error'
, detail: `ErrorId: ${(<ApiOkResponse>data).result}\n\n ${this.errorService.getClientMessage(error)} at ${this.errorService.getClientStack(error)}`, sticky: false
});
} else {
this.messageService.add({
key: 'main', severity: 'error', summary: 'Unable to log Unhandled Error'
, detail: `An error occurred trying to log an Http Error to the server.\n\n ${this.errorService.getClientMessage(error)} at ${this.errorService.getClientStack(error)}`, sticky: false
});
this.zone.run(() => {
this.messageService.add({
key: 'main', severity: 'error', summary: 'Unhandled Error'
, detail: `ErrorId: ${(<ApiOkResponse>data).result}\n\n ${this.errorService.getClientMessage(error)} at ${this.errorService.getClientStack(error)}`, sticky: false
});
}, this);
}
});
} else {
if (this.debug) console.log(`GlobalErrorHandler.handleError Error: ${JSON.stringify(error)}`);
console.error(error);
this.logService.logNgError(`${this.errorService.getClientMessage(error)} at ${this.errorService.getClientStack(error)}`)
.pipe(take(1)).subscribe(data => {
.subscribe(data => {
if (this.debug) console.log(`GlobalErrorHandler.handleError logNgError returned:${JSON.stringify(data)}`);
if (data.statusCode == 200) {
this.messageService.add({
key: 'main', severity: 'error', summary: 'Unhandled Error'
, detail: `ErrorId: ${(<ApiOkResponse>data).result}\n\n ${this.errorService.getClientMessage(error)} at ${this.errorService.getClientStack(error)}`, sticky: false
});
} else {
this.messageService.add({
key: 'main', severity: 'error', summary: 'Unable to log Unhandled Error'
, detail: `An error occurred trying to log an Angular Error to the server.\n\n ${this.errorService.getClientMessage(error)} at ${this.errorService.getClientStack(error)}`, sticky: false
});
this.zone.run(() => {
this.messageService.add({
key: 'main', severity: 'error', summary: 'Unhandled Error'
, detail: `ErrorId: ${(<ApiOkResponse>data).result}\n\n ${this.errorService.getClientMessage(error)} at ${this.errorService.getClientStack(error)}`, sticky: false
});
}, this);
}
});
}
......
......@@ -6,6 +6,7 @@ import { AccessComponent } from './access.component';
import { accessRouter } from './access.router';
import { AzEmailCredentialComponent } from './pages/az-email-credential/az-email-credential.component';
import { AzLoginWithProofComponent } from './pages/az-login-with-proof/az-login-with-proof.component';
import { ConfirmEmailChangeComponent } from './pages/confirm-email-change/confirm-email-change.component';
import { EmailConfirmationComponent } from './pages/email-confirmation/email-confirmation.component';
import { EmailCredentialComponent } from './pages/email-credential/email-credential.component';
import { EmailVerificationComponent } from './pages/email-verification/email-verification.component';
......@@ -17,6 +18,7 @@ import { LoginWithRecoveryComponent } from './pages/login-with-recovery/login-wi
import { LogoutComponent } from './pages/logout/logout.component';
import { RegisterAccountComponent } from './pages/register-account/register.component';
import { RegisterConfirmationComponent } from './pages/register-confirmation/register-confirmation.component';
import { ResendConfirmationComponent } from './pages/resend-confirmation/resend-confirmation.component';
import { ResetPasswordConfirmationComponent } from './pages/reset-password/reset-password-confirmation.component';
import { ResetPasswordComponent } from './pages/reset-password/reset-password.component';
import { SetPasswordConfirmationComponent } from './pages/set-password/set-password-confirmation.component';
......@@ -38,8 +40,10 @@ import { ResetPasswordResolver } from './services/reset-password-resolver.servic
AccessComponent,
LoginFormComponent,
LogoutComponent,
ConfirmEmailChangeComponent,
ForgotPasswordComponent,
ForgotPasswordConfirmationComponent,
ResendConfirmationComponent,
ResetPasswordComponent,
ResetPasswordConfirmationComponent,
RegisterAccountComponent,
......
......@@ -2,6 +2,7 @@ import { RouterModule, Routes } from '@angular/router';
import { AccessComponent } from './access.component';
import { AzEmailCredentialComponent } from './pages/az-email-credential/az-email-credential.component';
import { AzLoginWithProofComponent } from './pages/az-login-with-proof/az-login-with-proof.component';
import { ConfirmEmailChangeComponent } from './pages/confirm-email-change/confirm-email-change.component';
import { EmailConfirmationComponent } from './pages/email-confirmation/email-confirmation.component';
import { EmailCredentialComponent } from './pages/email-credential/email-credential.component';
import { EmailVerificationComponent } from './pages/email-verification/email-verification.component';
......@@ -14,6 +15,7 @@ import { LoginWith2faComponent } from './pages/login-with2fa/login-with2fa.compo
import { LogoutComponent } from './pages/logout/logout.component';
import { RegisterAccountComponent } from './pages/register-account/register.component';
import { RegisterConfirmationComponent } from './pages/register-confirmation/register-confirmation.component';
import { ResendConfirmationComponent } from './pages/resend-confirmation/resend-confirmation.component';
import { ResetPasswordConfirmationComponent } from './pages/reset-password/reset-password-confirmation.component';
import { ResetPasswordComponent } from './pages/reset-password/reset-password.component';
......@@ -23,6 +25,10 @@ export const accessRoutes: Routes = [
component: AccessComponent,
data: { hideNavBar: true },
children: [
{
path: 'resend-confirmation', component: ResendConfirmationComponent,
data: { hideNavBar: true }
},
{
path: 'forgot-password', component: ForgotPasswordComponent,
data: { hideNavBar: true }
......@@ -87,6 +93,7 @@ export const accessRoutes: Routes = [
path: 'register', component: RegisterAccountComponent,
data: { hideNavBar: true }
},
{path: 'confirm-email-change', component: ConfirmEmailChangeComponent},
{
path: '',
component: LoginFormComponent,
......
<div *ngIf="showSpinner == true; else theContent" class="d-flex flex-column">
<app-spinner id="profile-spinner" [message]="message"></app-spinner>
</div>
<ng-template #theContent>
<section>
<span class="h1 mb-3">Confirm Email Change</span>
<ng-container *ngIf="statusMessage != null && statusMessage != ''">
<ng-container *ngIf="!isError">
<div class="alert alert-success alert-dismissible mt-3">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
{{statusMessage}}
</div>
<a [routerLink]="['/access/login']" class="my-3">Please login using your updated email address</a>
</ng-container>
<ng-container *ngIf="isError">
<div class="alert alert-danger alert-dismissible mt-3">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
{{statusMessage}}
</div>
</ng-container>
</ng-container>
</section>
</ng-template>
......@@ -2,7 +2,7 @@
<h2>Forgot your password?</h2>
<h4>Enter your email.</h4>
<hr />
<form method="post" #f="ngForm" novalidate (ngSubmit)="forgot(f)">
<form method="post" #f="ngForm" (ngSubmit)="forgot(f)">
<div *ngIf="modelErrors && modelErrors.length > 0">
<div class="alert alert-danger mt-2">
<div *ngFor="let msg of modelErrors">{{msg}}</div>
......@@ -12,7 +12,7 @@
<input name="email" class="form-control" placeholder="Email" [ngModel]="forgotPasswordForm.email" #email="ngModel" tmFocus validateEmail />
<small [hidden]="email.valid || (email.pristine && !submitted)" class="text-danger">Please enter a valid email</small>
</div>
<button type="submit" class="btn btn-outline-primary mr-3" id="btn-forgot-password">Submit</button>
<button type="submit" class="btn btn-outline-primary mr-3" id="btn-forgot-password" [disabled]="buttonSpinner">Submit <span class="spinner-border spinner-border-sm" role="status" *ngIf="buttonSpinner" aria-hidden="true"></span></button>
<a id="login" [routerLink]="['/access/login']">CANCEL</a>
</form>
</section>
\ No newline at end of file
</section>
......@@ -13,6 +13,7 @@ export class ForgotPasswordComponent implements OnInit {
submitted = false;
forgotPasswordForm:ForgotPasswordModel = { email: '' };
modelErrors = [];
buttonSpinner = false;
constructor(private accessServices: AccessService, private router: Router) { }
......@@ -20,16 +21,24 @@ export class ForgotPasswordComponent implements OnInit {
}
forgot({value, valid} : { value: ForgotPasswordModel; valid: boolean}) {
if (value.email == null || !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value.email)) {
this.modelErrors = [ 'Please enter a valid email.'];
return;
}
this.submitted = true;
this.modelErrors = [];
if (valid) {
this.buttonSpinner = true;
this.accessServices.forgotPassword(value.email).subscribe((response: PostResponseModel) => {
if (response.hasError) {
this.modelErrors = response.errorMessages;
this.buttonSpinner = false;
}
else {
this.router.navigate(['/access/forgot-password-confirmation']);
}
}, (error) => {
this.buttonSpinner = false;
});
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment