mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-06-23 15:30:34 -04:00
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
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<DocumentationFile>bin\Debug\API.xml</DocumentationFile>
|
<DocumentationFile>bin\Debug\API.xml</DocumentationFile>
|
||||||
|
<NoWarn>1701;1702;1591</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
@ -21,9 +21,7 @@ namespace API.Constants
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const string DownloadRole = "Download";
|
public const string DownloadRole = "Download";
|
||||||
|
|
||||||
public static readonly ImmutableArray<string> ValidRoles = new ImmutableArray<string>()
|
public static readonly ImmutableArray<string> ValidRoles =
|
||||||
{
|
ImmutableArray.Create(AdminRole, PlebRole, DownloadRole);
|
||||||
AdminRole, PlebRole, DownloadRole
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -394,6 +394,7 @@ namespace API.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[Authorize(Policy = "RequireAdminRole")]
|
[Authorize(Policy = "RequireAdminRole")]
|
||||||
[HttpPost("invite")]
|
[HttpPost("invite")]
|
||||||
public async Task<ActionResult<string>> InviteUser(InviteUserDto dto)
|
public async Task<ActionResult<string>> InviteUser(InviteUserDto dto)
|
||||||
@ -439,7 +440,7 @@ namespace API.Controllers
|
|||||||
var roleResult = await _userManager.AddToRoleAsync(user, role);
|
var roleResult = await _userManager.AddToRoleAsync(user, role);
|
||||||
if (!roleResult.Succeeded)
|
if (!roleResult.Succeeded)
|
||||||
return
|
return
|
||||||
BadRequest(roleResult.Errors); // TODO: Combine all these return BadRequest into one big thing
|
BadRequest(roleResult.Errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grant access to libraries
|
// Grant access to libraries
|
||||||
@ -482,7 +483,7 @@ namespace API.Controllers
|
|||||||
}
|
}
|
||||||
return Ok(emailLink);
|
return Ok(emailLink);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
_unitOfWork.UserRepository.Delete(user);
|
_unitOfWork.UserRepository.Delete(user);
|
||||||
await _unitOfWork.CommitAsync();
|
await _unitOfWork.CommitAsync();
|
||||||
@ -533,6 +534,56 @@ namespace API.Controllers
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpPost("confirm-password-reset")]
|
||||||
|
public async Task<ActionResult<string>> ConfirmForgotPassword(ConfirmPasswordResetDto dto)
|
||||||
|
{
|
||||||
|
var user = await _unitOfWork.UserRepository.GetUserByEmailAsync(dto.Email);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return BadRequest("Invalid Details");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _userManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, "ResetPassword", dto.Token);
|
||||||
|
if (!result) return BadRequest("Unable to reset password");
|
||||||
|
|
||||||
|
var errors = await _accountService.ChangeUserPassword(user, dto.Password);
|
||||||
|
return errors.Any() ? BadRequest(errors) : BadRequest("Unable to reset password");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will send user a link to update their password to their email or prompt them if not accessible
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="email"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpPost("forgot-password")]
|
||||||
|
public async Task<ActionResult<string>> ForgotPassword([FromQuery] string email)
|
||||||
|
{
|
||||||
|
var user = await _unitOfWork.UserRepository.GetUserByEmailAsync(email);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("There are no users with email: {Email} but user is requesting password reset", email);
|
||||||
|
return Ok("An email will be sent to the email if it exists in our database");
|
||||||
|
}
|
||||||
|
|
||||||
|
var emailLink = GenerateEmailLink(await _userManager.GeneratePasswordResetTokenAsync(user), "confirm-reset-password", user.Email);
|
||||||
|
_logger.LogInformation("[Forgot Password]: Email Link: {Link}", emailLink);
|
||||||
|
var host = _environment.IsDevelopment() ? "localhost:4200" : Request.Host.ToString();
|
||||||
|
if (await _emailService.CheckIfAccessible(host))
|
||||||
|
{
|
||||||
|
await _emailService.SendPasswordResetEmail(new PasswordResetEmailDto()
|
||||||
|
{
|
||||||
|
EmailAddress = user.Email,
|
||||||
|
ServerConfirmationLink = emailLink
|
||||||
|
});
|
||||||
|
return Ok("Email sent");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok("Your server is not accessible. The Link to reset your password is in the logs.");
|
||||||
|
}
|
||||||
|
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[HttpPost("confirm-migration-email")]
|
[HttpPost("confirm-migration-email")]
|
||||||
public async Task<ActionResult<UserDto>> ConfirmMigrationEmail(ConfirmMigrationEmailDto dto)
|
public async Task<ActionResult<UserDto>> ConfirmMigrationEmail(ConfirmMigrationEmailDto dto)
|
||||||
@ -570,10 +621,7 @@ namespace API.Controllers
|
|||||||
"This user needs to migrate. Have them log out and login to trigger a migration flow");
|
"This user needs to migrate. Have them log out and login to trigger a migration flow");
|
||||||
if (user.EmailConfirmed) return BadRequest("User already confirmed");
|
if (user.EmailConfirmed) return BadRequest("User already confirmed");
|
||||||
|
|
||||||
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
var emailLink = GenerateEmailLink(await _userManager.GenerateEmailConfirmationTokenAsync(user), "confirm-migration-email", user.Email);
|
||||||
var host = _environment.IsDevelopment() ? "localhost:4200" : Request.Host.ToString();
|
|
||||||
var emailLink =
|
|
||||||
$"{Request.Scheme}://{host}{Request.PathBase}/registration/confirm-migration-email?token={HttpUtility.UrlEncode(token)}&email={HttpUtility.UrlEncode(user.Email)}";
|
|
||||||
_logger.LogInformation("[Email Migration]: Email Link: {Link}", emailLink);
|
_logger.LogInformation("[Email Migration]: Email Link: {Link}", emailLink);
|
||||||
await _emailService.SendMigrationEmail(new EmailMigrationDto()
|
await _emailService.SendMigrationEmail(new EmailMigrationDto()
|
||||||
{
|
{
|
||||||
@ -586,6 +634,14 @@ namespace API.Controllers
|
|||||||
return Ok(emailLink);
|
return Ok(emailLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GenerateEmailLink(string token, string routePart, string email)
|
||||||
|
{
|
||||||
|
var host = _environment.IsDevelopment() ? "localhost:4200" : Request.Host.ToString();
|
||||||
|
var emailLink =
|
||||||
|
$"{Request.Scheme}://{host}{Request.PathBase}/registration/{routePart}?token={HttpUtility.UrlEncode(token)}&email={HttpUtility.UrlEncode(email)}";
|
||||||
|
return emailLink;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is similar to invite. Essentially we authenticate the user's password then go through invite email flow
|
/// This is similar to invite. Essentially we authenticate the user's password then go through invite email flow
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -622,9 +678,7 @@ namespace API.Controllers
|
|||||||
_unitOfWork.UserRepository.Update(user);
|
_unitOfWork.UserRepository.Update(user);
|
||||||
await _unitOfWork.CommitAsync();
|
await _unitOfWork.CommitAsync();
|
||||||
|
|
||||||
var host = _environment.IsDevelopment() ? "localhost:4200" : Request.Host.ToString();
|
var emailLink = GenerateEmailLink(await _userManager.GenerateEmailConfirmationTokenAsync(user), "confirm-migration-email", user.Email);
|
||||||
var emailLink =
|
|
||||||
$"{Request.Scheme}://{host}{Request.PathBase}/registration/confirm-migration-email?token={HttpUtility.UrlEncode(token)}&email={HttpUtility.UrlEncode(dto.Email)}";
|
|
||||||
_logger.LogInformation("[Email Migration]: Email Link: {Link}", emailLink);
|
_logger.LogInformation("[Email Migration]: Email Link: {Link}", emailLink);
|
||||||
if (dto.SendEmail)
|
if (dto.SendEmail)
|
||||||
{
|
{
|
||||||
|
14
API/DTOs/Account/ConfirmPasswordResetDto.cs
Normal file
14
API/DTOs/Account/ConfirmPasswordResetDto.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace API.DTOs.Account;
|
||||||
|
|
||||||
|
public class ConfirmPasswordResetDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public string Email { get; set; }
|
||||||
|
[Required]
|
||||||
|
public string Token { get; set; }
|
||||||
|
[Required]
|
||||||
|
[StringLength(32, MinimumLength = 6)]
|
||||||
|
public string Password { get; set; }
|
||||||
|
}
|
7
API/DTOs/Email/PasswordResetEmailDto.cs
Normal file
7
API/DTOs/Email/PasswordResetEmailDto.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace API.DTOs.Email;
|
||||||
|
|
||||||
|
public class PasswordResetEmailDto
|
||||||
|
{
|
||||||
|
public string EmailAddress { get; init; }
|
||||||
|
public string ServerConfirmationLink { get; init; }
|
||||||
|
}
|
@ -11,6 +11,5 @@ namespace API.DTOs
|
|||||||
[Required]
|
[Required]
|
||||||
[StringLength(32, MinimumLength = 6)]
|
[StringLength(32, MinimumLength = 6)]
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
public bool IsAdmin { get; init; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,7 +211,7 @@ public class UserRepository : IUserRepository
|
|||||||
|
|
||||||
public async Task<AppUser> GetUserByEmailAsync(string email)
|
public async Task<AppUser> GetUserByEmailAsync(string email)
|
||||||
{
|
{
|
||||||
return await _context.AppUser.SingleOrDefaultAsync(u => u.Email.Equals(email));
|
return await _context.AppUser.SingleOrDefaultAsync(u => u.Email.ToLower().Equals(email.ToLower()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<AppUser>> GetAdminUsersAsync()
|
public async Task<IEnumerable<AppUser>> GetAdminUsersAsync()
|
||||||
|
@ -15,6 +15,7 @@ public interface IEmailService
|
|||||||
Task SendConfirmationEmail(ConfirmationEmailDto data);
|
Task SendConfirmationEmail(ConfirmationEmailDto data);
|
||||||
Task<bool> CheckIfAccessible(string host);
|
Task<bool> CheckIfAccessible(string host);
|
||||||
Task SendMigrationEmail(EmailMigrationDto data);
|
Task SendMigrationEmail(EmailMigrationDto data);
|
||||||
|
Task SendPasswordResetEmail(PasswordResetEmailDto data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class EmailService : IEmailService
|
public class EmailService : IEmailService
|
||||||
@ -50,6 +51,11 @@ public class EmailService : IEmailService
|
|||||||
await SendEmailWithPost(ApiUrl + "/api/email/email-migration", data);
|
await SendEmailWithPost(ApiUrl + "/api/email/email-migration", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SendPasswordResetEmail(PasswordResetEmailDto data)
|
||||||
|
{
|
||||||
|
await SendEmailWithPost(ApiUrl + "/api/email/email-password-reset", data);
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task<bool> SendEmailWithGet(string url)
|
private static async Task<bool> SendEmailWithGet(string url)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -325,12 +325,8 @@ public class ReaderService : IReaderService
|
|||||||
if (volume == null) return nonSpecialChapters.First();
|
if (volume == null) return nonSpecialChapters.First();
|
||||||
|
|
||||||
var chapters = volume.Chapters.OrderBy(c => float.Parse(c.Number)).ToList();
|
var chapters = volume.Chapters.OrderBy(c => float.Parse(c.Number)).ToList();
|
||||||
foreach (var chapter in chapters.Where(chapter => chapter.PagesRead < chapter.Pages))
|
|
||||||
{
|
|
||||||
return chapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
return chapters.First();
|
return chapters.FirstOrDefault(chapter => chapter.PagesRead < chapter.Pages) ?? chapters.First();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@ namespace API.Services.Tasks
|
|||||||
b.FileName)));
|
b.FileName)));
|
||||||
|
|
||||||
|
|
||||||
var filesToDelete = allBookmarkFiles.ToList().Except(bookmarks).ToList();
|
var filesToDelete = allBookmarkFiles.AsEnumerable().Except(bookmarks).ToList();
|
||||||
_logger.LogDebug("[Bookmarks] Bookmark cleanup wants to delete {Count} files", filesToDelete.Count);
|
_logger.LogDebug("[Bookmarks] Bookmark cleanup wants to delete {Count} files", filesToDelete.Count);
|
||||||
|
|
||||||
if (filesToDelete.Count == 0) return;
|
if (filesToDelete.Count == 0) return;
|
||||||
|
@ -6,7 +6,7 @@ export interface Member {
|
|||||||
email: string;
|
email: string;
|
||||||
lastActive: string; // datetime
|
lastActive: string; // datetime
|
||||||
created: string; // datetime
|
created: string; // datetime
|
||||||
isAdmin: boolean;
|
//isAdmin: boolean;
|
||||||
roles: string[];
|
roles: string[];
|
||||||
libraries: Library[];
|
libraries: Library[];
|
||||||
}
|
}
|
@ -130,6 +130,14 @@ export class AccountService implements OnDestroy {
|
|||||||
return JSON.parse(atob(token.split('.')[1]));
|
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) {
|
resetPassword(username: string, password: string) {
|
||||||
return this.httpClient.post(this.baseUrl + 'account/reset-password', {username, password}, {responseType: 'json' as 'text'});
|
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) {
|
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) {
|
hasLibraryAccess(libraryId: number) {
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
{{member.lastActive | date: 'short'}}
|
{{member.lastActive | date: 'short'}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!member.isAdmin">Sharing: {{formatLibraries(member)}}</div>
|
<div *ngIf="!hasAdminRole(member)">Sharing: {{formatLibraries(member)}}</div>
|
||||||
<div>
|
<div>
|
||||||
Roles: <span *ngIf="getRoles(member).length === 0; else showRoles">None</span>
|
Roles: <span *ngIf="getRoles(member).length === 0; else showRoles">None</span>
|
||||||
<ng-template #showRoles>
|
<ng-template #showRoles>
|
||||||
|
@ -167,4 +167,5 @@ export class ManageUsersComponent implements OnInit, OnDestroy {
|
|||||||
getRoles(member: Member) {
|
getRoles(member: Member) {
|
||||||
return member.roles.filter(item => item != 'Pleb');
|
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>
|
<app-splash-container>
|
||||||
<ng-container title><h2>Register</h2></ng-container>
|
<ng-container title><h2>Register</h2></ng-container>
|
||||||
<ng-container body>
|
<ng-container body>
|
||||||
|
@ -38,8 +38,6 @@ export class RegisterComponent implements OnInit {
|
|||||||
this.accountService.register(model).subscribe((user) => {
|
this.accountService.register(model).subscribe((user) => {
|
||||||
this.toastr.success('Account registration complete');
|
this.toastr.success('Account registration complete');
|
||||||
this.router.navigateByUrl('login');
|
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 { RegisterComponent } from './register/register.component';
|
||||||
import { AddEmailToAccountMigrationModalComponent } from './add-email-to-account-migration-modal/add-email-to-account-migration-modal.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 { 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,
|
SplashContainerComponent,
|
||||||
RegisterComponent,
|
RegisterComponent,
|
||||||
AddEmailToAccountMigrationModalComponent,
|
AddEmailToAccountMigrationModalComponent,
|
||||||
ConfirmMigrationEmailComponent
|
ConfirmMigrationEmailComponent,
|
||||||
|
ResetPasswordComponent,
|
||||||
|
ConfirmResetPasswordComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
@ -2,7 +2,9 @@ import { NgModule } from '@angular/core';
|
|||||||
import { Routes, RouterModule } from '@angular/router';
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
import { ConfirmEmailComponent } from './confirm-email/confirm-email.component';
|
import { ConfirmEmailComponent } from './confirm-email/confirm-email.component';
|
||||||
import { ConfirmMigrationEmailComponent } from './confirm-migration-email/confirm-migration-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 { RegisterComponent } from './register/register.component';
|
||||||
|
import { ResetPasswordComponent } from './reset-password/reset-password.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@ -16,6 +18,14 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'register',
|
path: 'register',
|
||||||
component: RegisterComponent,
|
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";
|
@use "../../../theme/colors";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.login {
|
.login {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -72,6 +74,15 @@
|
|||||||
box-shadow: 0 0 0 0.2rem rgb(68 79 117 / 50%);
|
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 {
|
.invalid-feedback {
|
||||||
@ -79,7 +90,3 @@
|
|||||||
color: #343c59;
|
color: #343c59;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
|
||||||
background-color: #fff !important;
|
|
||||||
color: black;
|
|
||||||
}
|
|
@ -14,6 +14,10 @@
|
|||||||
<input class="form-control" formControlName="password" id="password" type="password" autofocus>
|
<input class="form-control" formControlName="password" id="password" type="password" autofocus>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="/registration/reset-password">Forgot Password?</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="float-right">
|
<div class="float-right">
|
||||||
<button class="btn btn-secondary alt" type="submit">Login</button>
|
<button class="btn btn-secondary alt" type="submit">Login</button>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user