Forgot Password (#1017)
* Implemented forgot password flow. Fixed a bug in manage user where admins were showing the Sharing With section. * Cleaned up the reset password flow. * Reverted some debug code * Fixed an issue with invites due to ImmutableArray not being set.
This commit is contained in:
parent
8564378b77
commit
8ff123e06c
27 changed files with 271 additions and 38 deletions
|
@ -6,7 +6,7 @@ export interface Member {
|
|||
email: string;
|
||||
lastActive: string; // datetime
|
||||
created: string; // datetime
|
||||
isAdmin: boolean;
|
||||
//isAdmin: boolean;
|
||||
roles: string[];
|
||||
libraries: Library[];
|
||||
}
|
|
@ -130,6 +130,14 @@ export class AccountService implements OnDestroy {
|
|||
return JSON.parse(atob(token.split('.')[1]));
|
||||
}
|
||||
|
||||
requestResetPasswordEmail(email: string) {
|
||||
return this.httpClient.post<string>(this.baseUrl + 'account/forgot-password?email=' + encodeURIComponent(email), {}, {responseType: 'text' as 'json'});
|
||||
}
|
||||
|
||||
confirmResetPasswordEmail(model: {email: string, token: string, password: string}) {
|
||||
return this.httpClient.post(this.baseUrl + 'account/confirm-password-reset', model);
|
||||
}
|
||||
|
||||
resetPassword(username: string, password: string) {
|
||||
return this.httpClient.post(this.baseUrl + 'account/reset-password', {username, password}, {responseType: 'json' as 'text'});
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ export class MemberService {
|
|||
}
|
||||
|
||||
deleteMember(username: string) {
|
||||
return this.httpClient.delete(this.baseUrl + 'users/delete-user?username=' + username);
|
||||
return this.httpClient.delete(this.baseUrl + 'users/delete-user?username=' + encodeURIComponent(username));
|
||||
}
|
||||
|
||||
hasLibraryAccess(libraryId: number) {
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
{{member.lastActive | date: 'short'}}
|
||||
</ng-template>
|
||||
</div>
|
||||
<div *ngIf="!member.isAdmin">Sharing: {{formatLibraries(member)}}</div>
|
||||
<div *ngIf="!hasAdminRole(member)">Sharing: {{formatLibraries(member)}}</div>
|
||||
<div>
|
||||
Roles: <span *ngIf="getRoles(member).length === 0; else showRoles">None</span>
|
||||
<ng-template #showRoles>
|
||||
|
|
|
@ -167,4 +167,5 @@ export class ManageUsersComponent implements OnInit, OnDestroy {
|
|||
getRoles(member: Member) {
|
||||
return member.roles.filter(item => item != 'Pleb');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<app-splash-container>
|
||||
<ng-container title><h2>Password Reset</h2></ng-container>
|
||||
<ng-container body>
|
||||
<p>Enter the email of your account. We will send you an email </p>
|
||||
<form [formGroup]="registerForm" (ngSubmit)="submit()">
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="passwordTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #passwordTooltip>
|
||||
Password must be between 6 and 32 characters in length
|
||||
</ng-template>
|
||||
<span class="sr-only" id="password-help"><ng-container [ngTemplateOutlet]="passwordTooltip"></ng-container></span>
|
||||
<input id="password" class="form-control" maxlength="32" minlength="6" formControlName="password" type="password" aria-describedby="password-help">
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="registerForm.dirty || registerForm.touched">
|
||||
<div *ngIf="registerForm.get('password')?.errors?.required">
|
||||
This field is required
|
||||
</div>
|
||||
<div *ngIf="registerForm.get('password')?.errors?.minlength || registerForm.get('password')?.errors?.maxLength">
|
||||
Password must be between 6 and 32 characters in length
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="float-right">
|
||||
<button class="btn btn-secondary alt" type="submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</ng-container>
|
||||
</app-splash-container>
|
|
@ -0,0 +1,50 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormGroup, FormControl, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { AccountService } from 'src/app/_services/account.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-confirm-reset-password',
|
||||
templateUrl: './confirm-reset-password.component.html',
|
||||
styleUrls: ['./confirm-reset-password.component.scss']
|
||||
})
|
||||
export class ConfirmResetPasswordComponent implements OnInit {
|
||||
|
||||
token: string = '';
|
||||
registerForm: FormGroup = new FormGroup({
|
||||
email: new FormControl('', [Validators.required, Validators.email]),
|
||||
password: new FormControl('', [Validators.required, Validators.maxLength(32), Validators.minLength(6)]),
|
||||
});
|
||||
|
||||
constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService, private toastr: ToastrService) {
|
||||
const token = this.route.snapshot.queryParamMap.get('token');
|
||||
const email = this.route.snapshot.queryParamMap.get('email');
|
||||
if (token == undefined || token === '' || token === null) {
|
||||
// This is not a valid url, redirect to login
|
||||
this.toastr.error('Invalid reset password url');
|
||||
this.router.navigateByUrl('login');
|
||||
return;
|
||||
}
|
||||
|
||||
this.token = token;
|
||||
this.registerForm.get('email')?.setValue(email);
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
submit() {
|
||||
const model = this.registerForm.getRawValue();
|
||||
model.token = this.token;
|
||||
this.accountService.confirmResetPasswordEmail(model).subscribe(() => {
|
||||
this.toastr.success("Password reset");
|
||||
this.router.navigateByUrl('login');
|
||||
}, err => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,10 +1,3 @@
|
|||
<!--
|
||||
<div class="text-danger" *ngIf="errors.length > 0">
|
||||
<p>Errors:</p>
|
||||
<ul>
|
||||
<li *ngFor="let error of errors">{{error}}</li>
|
||||
</ul>
|
||||
</div> -->
|
||||
<app-splash-container>
|
||||
<ng-container title><h2>Register</h2></ng-container>
|
||||
<ng-container body>
|
||||
|
|
|
@ -38,8 +38,6 @@ export class RegisterComponent implements OnInit {
|
|||
this.accountService.register(model).subscribe((user) => {
|
||||
this.toastr.success('Account registration complete');
|
||||
this.router.navigateByUrl('login');
|
||||
}, err => {
|
||||
// TODO: Handle errors
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ import { SplashContainerComponent } from './splash-container/splash-container.co
|
|||
import { RegisterComponent } from './register/register.component';
|
||||
import { AddEmailToAccountMigrationModalComponent } from './add-email-to-account-migration-modal/add-email-to-account-migration-modal.component';
|
||||
import { ConfirmMigrationEmailComponent } from './confirm-migration-email/confirm-migration-email.component';
|
||||
import { ResetPasswordComponent } from './reset-password/reset-password.component';
|
||||
import { ConfirmResetPasswordComponent } from './confirm-reset-password/confirm-reset-password.component';
|
||||
|
||||
|
||||
|
||||
|
@ -17,7 +19,9 @@ import { ConfirmMigrationEmailComponent } from './confirm-migration-email/confir
|
|||
SplashContainerComponent,
|
||||
RegisterComponent,
|
||||
AddEmailToAccountMigrationModalComponent,
|
||||
ConfirmMigrationEmailComponent
|
||||
ConfirmMigrationEmailComponent,
|
||||
ResetPasswordComponent,
|
||||
ConfirmResetPasswordComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
|
|
@ -2,7 +2,9 @@ import { NgModule } from '@angular/core';
|
|||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { ConfirmEmailComponent } from './confirm-email/confirm-email.component';
|
||||
import { ConfirmMigrationEmailComponent } from './confirm-migration-email/confirm-migration-email.component';
|
||||
import { ConfirmResetPasswordComponent } from './confirm-reset-password/confirm-reset-password.component';
|
||||
import { RegisterComponent } from './register/register.component';
|
||||
import { ResetPasswordComponent } from './reset-password/reset-password.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
|
@ -16,6 +18,14 @@ const routes: Routes = [
|
|||
{
|
||||
path: 'register',
|
||||
component: RegisterComponent,
|
||||
},
|
||||
{
|
||||
path: 'reset-password',
|
||||
component: ResetPasswordComponent
|
||||
},
|
||||
{
|
||||
path: 'confirm-reset-password',
|
||||
component: ConfirmResetPasswordComponent
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<app-splash-container>
|
||||
<ng-container title><h2>Password Reset</h2></ng-container>
|
||||
<ng-container body>
|
||||
<p>Enter the email of your account. We will send you an email </p>
|
||||
<form [formGroup]="registerForm" (ngSubmit)="submit()">
|
||||
<div class="form-group" style="width:100%">
|
||||
<label for="email">Email</label>
|
||||
<input class="form-control" type="email" id="email" formControlName="email" required>
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="registerForm.dirty || registerForm.touched">
|
||||
<div *ngIf="registerForm.get('email')?.errors?.required">
|
||||
This field is required
|
||||
</div>
|
||||
<div *ngIf="registerForm.get('email')?.errors?.email">
|
||||
This must be a valid email address
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="float-right">
|
||||
<button class="btn btn-secondary alt" type="submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</ng-container>
|
||||
</app-splash-container>
|
|
@ -0,0 +1,31 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormGroup, FormControl, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { AccountService } from 'src/app/_services/account.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-reset-password',
|
||||
templateUrl: './reset-password.component.html',
|
||||
styleUrls: ['./reset-password.component.scss']
|
||||
})
|
||||
export class ResetPasswordComponent implements OnInit {
|
||||
|
||||
registerForm: FormGroup = new FormGroup({
|
||||
email: new FormControl('', [Validators.required, Validators.email]),
|
||||
});
|
||||
|
||||
constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService, private toastr: ToastrService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
submit() {
|
||||
const model = this.registerForm.get('email')?.value;
|
||||
this.accountService.requestResetPasswordEmail(model).subscribe((resp: string) => {
|
||||
this.toastr.info(resp);
|
||||
this.router.navigateByUrl('login');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
@use "../../../theme/colors";
|
||||
|
||||
|
||||
|
||||
.login {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -72,6 +74,15 @@
|
|||
box-shadow: 0 0 0 0.2rem rgb(68 79 117 / 50%);
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep input {
|
||||
background-color: #fff !important;
|
||||
color: black;
|
||||
}
|
||||
|
||||
::ng-deep a {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.invalid-feedback {
|
||||
|
@ -79,7 +90,3 @@
|
|||
color: #343c59;
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: #fff !important;
|
||||
color: black;
|
||||
}
|
|
@ -13,6 +13,10 @@
|
|||
<label for="password">Password</label>
|
||||
<input class="form-control" formControlName="password" id="password" type="password" autofocus>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a href="/registration/reset-password">Forgot Password?</a>
|
||||
</div>
|
||||
|
||||
<div class="float-right">
|
||||
<button class="btn btn-secondary alt" type="submit">Login</button>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue