Skip to content

Commit 532b338

Browse files
feat: add forgot/reset password feature and update related components
1 parent e70f4c7 commit 532b338

File tree

11 files changed

+230
-0
lines changed

11 files changed

+230
-0
lines changed

src/app/account/edit-profile/edit-profile.component.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,25 @@
22
<div fxFlex.gt-sm="20"></div>
33
<div fxFlex class="form-container">
44
<f-edit-profile-form mode="edit"></f-edit-profile-form>
5+
6+
<mat-divider></mat-divider>
7+
8+
<section class="change-password">
9+
<h2>Change Password</h2>
10+
<form (ngSubmit)="changePassword()" #pwForm="ngForm" class="flex flex-col">
11+
<mat-form-field appearance="outline">
12+
<mat-label>Current Password</mat-label>
13+
<input matInput type="password" name="current_password" required [(ngModel)]="current_password" />
14+
</mat-form-field>
15+
<mat-form-field appearance="outline">
16+
<mat-label>New Password</mat-label>
17+
<input matInput type="password" name="new_password" required [(ngModel)]="new_password" />
18+
</mat-form-field>
19+
<button mat-flat-button color="primary" [disabled]="pwForm.invalid || changing">
20+
Update Password
21+
</button>
22+
</form>
23+
</section>
524
</div>
625
<div fxFlex.gt-sm="20"></div>
726
</div>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,33 @@
11
import { Component } from '@angular/core';
2+
import { AuthenticationService } from 'src/app/api/services/authentication.service';
3+
import { AlertService } from 'src/app/common/services/alert.service';
24

35
@Component({
46
selector: 'f-edit-profile',
57
templateUrl: './edit-profile.component.html',
68
styleUrls: ['./edit-profile.component.scss'],
79
})
810
export class EditProfileComponent {
11+
current_password: string = '';
12+
new_password: string = '';
13+
changing = false;
14+
15+
constructor(private auth: AuthenticationService, private alerts: AlertService) {}
16+
17+
changePassword(): void {
18+
if (!this.current_password || !this.new_password) return;
19+
this.changing = true;
20+
this.auth.changePassword(this.current_password, this.new_password).subscribe({
21+
next: () => {
22+
this.changing = false;
23+
this.current_password = '';
24+
this.new_password = '';
25+
this.alerts.success('Password updated.', 6000);
26+
},
27+
error: (err) => {
28+
this.changing = false;
29+
this.alerts.error(err, 6000);
30+
},
31+
});
32+
}
933
}

src/app/api/services/authentication.service.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,4 +191,45 @@ export class AuthenticationService {
191191
setTimeout(() => this.router.stateService.go('timeout'), 500);
192192
}
193193
}
194+
195+
// Account registration (local auth only)
196+
public register(data: {
197+
username: string;
198+
email: string;
199+
first_name: string;
200+
last_name: string;
201+
password: string;
202+
}): Observable<any> {
203+
const url = `${this.AUTH_URL}/register`;
204+
return this.httpClient.post(url, data).pipe(
205+
map((response: any) => {
206+
const user: User = this.userService.cache.getOrCreate(
207+
response['user']['id'],
208+
this.userService,
209+
response['user'],
210+
);
211+
user.authenticationToken = response['auth_token'];
212+
this.tryChangeUser(user, true);
213+
setTimeout(() => this.updateAuth(), 1000 * 60 * 60);
214+
}),
215+
);
216+
}
217+
218+
// Forgot password (request reset email/token)
219+
public forgotPassword(email: string): Observable<any> {
220+
const url = `${this.AUTH_URL}/password/forgot`;
221+
return this.httpClient.post(url, {email});
222+
}
223+
224+
// Reset password using token from email/link
225+
public resetPassword(token: string, password: string): Observable<any> {
226+
const url = `${this.AUTH_URL}/password/reset`;
227+
return this.httpClient.post(url, {token, password});
228+
}
229+
230+
// Change password for logged-in user
231+
public changePassword(current_password: string, new_password: string): Observable<any> {
232+
const url = `${this.AUTH_URL}/password`;
233+
return this.httpClient.put(url, {current_password, new_password});
234+
}
194235
}

src/app/doubtfire-angular.module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ import {ObjectSelectComponent} from './common/obect-select/object-select.compone
179179
import {WelcomeComponent} from './welcome/welcome.component';
180180
import {HeroSidebarComponent} from './common/hero-sidebar/hero-sidebar.component';
181181
import {SignInComponent} from './sessions/states/sign-in/sign-in.component';
182+
import {ForgotPasswordComponent} from './sessions/states/forgot-password/forgot-password.component';
183+
import {ResetPasswordComponent} from './sessions/states/reset-password/reset-password.component';
182184
import {EditProfileFormComponent} from './common/edit-profile-form/edit-profile-form.component';
183185
import {TransitionHooksService} from './sessions/transition-hooks.service';
184186
import {EditProfileComponent} from './account/edit-profile/edit-profile.component';
@@ -298,6 +300,8 @@ import {GradeService} from './common/services/grade.service';
298300
AcceptEulaComponent,
299301
HeroSidebarComponent,
300302
SignInComponent,
303+
ForgotPasswordComponent,
304+
ResetPasswordComponent,
301305
EditProfileFormComponent,
302306
EditProfileComponent,
303307
UserBadgeComponent,

src/app/doubtfire.states.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import {InstitutionSettingsComponent} from './admin/institution-settings/institu
33
import {HomeComponent} from './home/states/home/home.component';
44
import {WelcomeComponent} from './welcome/welcome.component';
55
import {SignInComponent} from './sessions/states/sign-in/sign-in.component';
6+
import {ForgotPasswordComponent} from './sessions/states/forgot-password/forgot-password.component';
7+
import {ResetPasswordComponent} from './sessions/states/reset-password/reset-password.component';
68
import {EditProfileComponent} from './account/edit-profile/edit-profile.component';
79
import {TeachingPeriodListComponent} from './admin/states/teaching-periods/teaching-period-list/teaching-period-list.component';
810
import {AcceptEulaComponent} from './eula/accept-eula/accept-eula.component';
@@ -300,6 +302,18 @@ export const doubtfireStates = [
300302
HomeState,
301303
WelcomeState,
302304
SignInState,
305+
{
306+
name: 'forgot_password',
307+
url: '/forgot_password',
308+
views: {main: {component: ForgotPasswordComponent}},
309+
data: {pageTitle: 'Forgot Password'},
310+
},
311+
{
312+
name: 'reset_password',
313+
url: '/reset_password?token',
314+
views: {main: {component: ResetPasswordComponent}},
315+
data: {pageTitle: 'Reset Password'},
316+
},
303317
EditProfileState,
304318
EulaState,
305319
usersState,
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<section class="container">
2+
<h1>Forgot Password</h1>
3+
<form (ngSubmit)="submit()" #form="ngForm" class="flex flex-col">
4+
<mat-form-field appearance="outline">
5+
<mat-label>Email</mat-label>
6+
<input matInput name="email" required [(ngModel)]="email" />
7+
</mat-form-field>
8+
<button mat-flat-button color="primary" [disabled]="form.invalid || submitting">
9+
Send Reset Link
10+
</button>
11+
</form>
12+
<p *ngIf="submitted">If an account exists, a reset link was sent.</p>
13+
</section>
14+
15+
16+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
section.container {
2+
max-width: 480px;
3+
margin: 0 auto;
4+
}
5+
6+
7+
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import {Component} from '@angular/core';
2+
import {AuthenticationService} from 'src/app/api/services/authentication.service';
3+
import {AlertService} from 'src/app/common/services/alert.service';
4+
5+
@Component({
6+
selector: 'f-forgot-password',
7+
templateUrl: './forgot-password.component.html',
8+
styleUrls: ['./forgot-password.component.scss'],
9+
})
10+
export class ForgotPasswordComponent {
11+
email: string = '';
12+
submitting = false;
13+
submitted = false;
14+
15+
constructor(private auth: AuthenticationService, private alerts: AlertService) {}
16+
17+
submit(): void {
18+
if (!this.email) return;
19+
this.submitting = true;
20+
this.auth.forgotPassword(this.email).subscribe({
21+
next: () => {
22+
this.submitting = false;
23+
this.submitted = true;
24+
this.alerts.success('If an account exists, a reset link was sent.', 6000);
25+
},
26+
error: () => {
27+
this.submitting = false;
28+
this.submitted = true;
29+
// same message to avoid enumeration
30+
this.alerts.success('If an account exists, a reset link was sent.', 6000);
31+
},
32+
});
33+
}
34+
}
35+
36+
37+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<section class="container">
2+
<h1>Reset Password</h1>
3+
<form (ngSubmit)="submit()" #form="ngForm" class="flex flex-col">
4+
<mat-form-field appearance="outline">
5+
<mat-label>New Password</mat-label>
6+
<input matInput type="password" name="password" required [(ngModel)]="password" />
7+
</mat-form-field>
8+
<button mat-flat-button color="primary" [disabled]="form.invalid || submitting">
9+
Reset Password
10+
</button>
11+
</form>
12+
</section>
13+
14+
15+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
section.container {
2+
max-width: 480px;
3+
margin: 0 auto;
4+
}
5+
6+
7+

0 commit comments

Comments
 (0)