Auth Email Rework (#1567)

* Hooked up Send to for Series and volumes and fixed a bug where Email Service errors weren't propagating to the UI layer.

When performing actions on series detail, don't disable the button anymore.

* Added send to action to volumes

* Fixed a bug where .kavitaignore wasn't being applied at library root level

* Added a notification for when a device is being sent a file.

* Added a check in forgot password for users that do not have an email set or aren't confirmed.

* Added a new api for change email and moved change password directly into new Account tab (styling and logic needs testing)

* Save approx scroll position like with jump key, but on normal click of card.

* Implemented the ability to change your email address or set one. This requires a 2 step process using a confirmation token. This needs polishing and css.

* Removed an unused directive from codebase

* Fixed up some typos on publicly

* Updated query for Pending Invites to also check if the user account has not logged in at least once.

* Cleaned up the css for validate email change

* Hooked in an indicator to tell user that a user has an unconfirmed email

* Cleaned up code smells
This commit is contained in:
Joe Milazzo 2022-10-01 08:23:35 -05:00 committed by GitHub
parent 3792ac3421
commit 5f17c2fb73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 816 additions and 274 deletions

View file

@ -0,0 +1,18 @@
<app-splash-container>
<ng-container title><h2>Validate Email Change</h2></ng-container>
<ng-container body>
<p *ngIf="!confirmed; else confirmedMessage">Please wait while your email update is validated.</p>
<ng-template #confirmedMessage>
<div class="card">
<div class="card-body">
<div class="card-title">
<h3><i class="fa-regular fa-circle-check me-2" style="font-size: 1.8rem" aria-hidden="true"></i>Success!</h3>
</div>
<p>Your email has been validated and is now changed within Kavita. You will be redirected to login.</p>
</div>
</div>
</ng-template>
</ng-container>
</app-splash-container>

View file

@ -0,0 +1,7 @@
.card-body {
padding: 0px 0px;
}
.card {
background-color: var(--primary-color);
}

View file

@ -0,0 +1,55 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import { AccountService } from 'src/app/_services/account.service';
import { NavService } from 'src/app/_services/nav.service';
import { ThemeService } from 'src/app/_services/theme.service';
/**
* This component just validates the email via API then redirects to login
*/
@Component({
selector: 'app-confirm-email-change',
templateUrl: './confirm-email-change.component.html',
styleUrls: ['./confirm-email-change.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ConfirmEmailChangeComponent implements OnInit {
email: string = '';
token: string = '';
confirmed: boolean = false;
constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService,
private toastr: ToastrService, private themeService: ThemeService, private navService: NavService,
private readonly cdRef: ChangeDetectorRef) {
this.navService.hideSideNav();
this.themeService.setTheme(this.themeService.defaultTheme);
const token = this.route.snapshot.queryParamMap.get('token');
const email = this.route.snapshot.queryParamMap.get('email');
if (this.isNullOrEmpty(token) || this.isNullOrEmpty(email)) {
// This is not a valid url, redirect to login
this.toastr.error('Invalid confirmation url');
this.router.navigateByUrl('login');
return;
}
this.token = token!;
this.email = email!;
}
ngOnInit(): void {
this.accountService.confirmEmailUpdate({email: this.email, token: this.token}).subscribe((errors) => {
this.confirmed = true;
this.cdRef.markForCheck();
setTimeout(() => this.router.navigateByUrl('login'), 2000);
});
}
isNullOrEmpty(v: string | null | undefined) {
return v == undefined || v === '' || v === null;
}
}

View file

@ -38,19 +38,23 @@ export class ConfirmEmailComponent {
const token = this.route.snapshot.queryParamMap.get('token');
const email = this.route.snapshot.queryParamMap.get('email');
this.cdRef.markForCheck();
if (token == undefined || token === '' || token === null) {
if (this.isNullOrEmpty(token) || this.isNullOrEmpty(email)) {
// This is not a valid url, redirect to login
this.toastr.error('Invalid confirmation email');
this.toastr.error('Invalid confirmation url');
this.router.navigateByUrl('login');
return;
}
this.token = token;
this.token = token!;
this.registerForm.get('email')?.setValue(email || '');
this.cdRef.markForCheck();
}
isNullOrEmpty(v: string | null | undefined) {
return v == undefined || v === '' || v === null;
}
submit() {
let model = this.registerForm.getRawValue();
const model = this.registerForm.getRawValue();
model.token = this.token;
this.accountService.confirmEmail(model).subscribe((user) => {
this.toastr.success('Account registration complete');

View file

@ -16,7 +16,7 @@
<div class="mb-3" style="width:100%">
<label for="email" class="form-label">Email</label>&nbsp;<i class="fa fa-info-circle" placement="right" [ngbTooltip]="emailTooltip" role="button" tabindex="0"></i>
<ng-template #emailTooltip>Email does not have to be valid, it is used for forgot password flow. It is not sent outside the server unless forgot password is used without a custom email service host.</ng-template>
<ng-template #emailTooltip>Email is optional and provides acccess to forgot password. It is not sent outside the server unless forgot password is used without a custom email service host.</ng-template>
<span class="visually-hidden" id="email-help">
<ng-container [ngTemplateOutlet]="emailTooltip"></ng-container>
</span>

View file

@ -18,7 +18,7 @@ import { MemberService } from 'src/app/_services/member.service';
export class RegisterComponent implements OnInit {
registerForm: FormGroup = new FormGroup({
email: new FormControl('', [Validators.required, Validators.email]),
email: new FormControl('', [Validators.email]),
username: new FormControl('', [Validators.required]),
password: new FormControl('', [Validators.required, Validators.maxLength(32), Validators.minLength(6)]),
});

View file

@ -11,6 +11,7 @@ import { ConfirmMigrationEmailComponent } from './confirm-migration-email/confir
import { ResetPasswordComponent } from './reset-password/reset-password.component';
import { ConfirmResetPasswordComponent } from './confirm-reset-password/confirm-reset-password.component';
import { UserLoginComponent } from './user-login/user-login.component';
import { ConfirmEmailChangeComponent } from './confirm-email-change/confirm-email-change.component';
@ -23,7 +24,8 @@ import { UserLoginComponent } from './user-login/user-login.component';
ConfirmMigrationEmailComponent,
ResetPasswordComponent,
ConfirmResetPasswordComponent,
UserLoginComponent
UserLoginComponent,
ConfirmEmailChangeComponent
],
imports: [
CommonModule,

View file

@ -1,5 +1,6 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ConfirmEmailChangeComponent } from './confirm-email-change/confirm-email-change.component';
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';
@ -24,6 +25,10 @@ const routes: Routes = [
path: 'confirm-migration-email',
component: ConfirmMigrationEmailComponent,
},
{
path: 'confirm-email-update',
component: ConfirmEmailChangeComponent,
},
{
path: 'register',
component: RegisterComponent,