This commit is contained in:
2
go.mod
2
go.mod
@ -13,6 +13,8 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||||
|
github.com/go-swiss/compress v0.0.0-20231015173048-c7b565746931 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||||
|
4
go.sum
4
go.sum
@ -1,3 +1,7 @@
|
|||||||
|
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||||
|
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
|
github.com/go-swiss/compress v0.0.0-20231015173048-c7b565746931 h1:4GONJghYPtbCcPDZXWhbgKgbK8tfmv/C7su6O72AZWw=
|
||||||
|
github.com/go-swiss/compress v0.0.0-20231015173048-c7b565746931/go.mod h1:atoBfZRTinNQQlYfu42MCp8E1yoKWhmohXj71lgRtfU=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
@ -48,6 +48,22 @@ func (s *Service) Signup(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Printf("Error: %v", err)
|
log.Printf("Error: %v", err)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
token, err := s.createJWT(&user)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := json.Marshal(&LoginResponse{
|
||||||
|
Token: token,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error: ", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signup handles user signup by decoding request body, hashing the password, and saving user data to the database.
|
// Signup handles user signup by decoding request body, hashing the password, and saving user data to the database.
|
||||||
@ -86,7 +102,7 @@ func (s *Service) ChangePassword(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login handles user login by decoding request body, verifying credentials, and returning a JWT token.
|
// Login handles user login by decoding request body, verifying credentials, and returning a JWT token.
|
||||||
|
11
web/package-lock.json
generated
11
web/package-lock.json
generated
@ -20,6 +20,7 @@
|
|||||||
"marked": "^15.0.8",
|
"marked": "^15.0.8",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
|
"tailwind-merge": "^3.3.0",
|
||||||
"tailwindcss": "^4.1.3",
|
"tailwindcss": "^4.1.3",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.15.0"
|
"zone.js": "~0.15.0"
|
||||||
@ -13581,6 +13582,16 @@
|
|||||||
"node": ">=0.10"
|
"node": ">=0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tailwind-merge": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/dcastil"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "4.1.3",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.3.tgz",
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
"marked": "^15.0.8",
|
"marked": "^15.0.8",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
|
"tailwind-merge": "^3.3.0",
|
||||||
"tailwindcss": "^4.1.3",
|
"tailwindcss": "^4.1.3",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.15.0"
|
"zone.js": "~0.15.0"
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<nav class="flex gap-5">
|
<nav class="flex gap-5">
|
||||||
<a routerLink="/dashboard" *ngIf="loggedIn()">posts</a>
|
<a routerLink="/dashboard" *ngIf="loggedIn()">posts</a>
|
||||||
|
<a routerLink="/dashboard/account" *ngIf="loggedIn()">account</a>
|
||||||
<button *ngIf="!loggedIn()" (click)="toggleLogin()">login</button>
|
<button *ngIf="!loggedIn()" (click)="toggleLogin()">login</button>
|
||||||
<button *ngIf="loggedIn()" (click)="logOut()">logout</button>
|
<button *ngIf="loggedIn()" (click)="logOut()">logout</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -24,6 +24,8 @@ describe('AppComponent', () => {
|
|||||||
const fixture = TestBed.createComponent(AppComponent);
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const compiled = fixture.nativeElement as HTMLElement;
|
const compiled = fixture.nativeElement as HTMLElement;
|
||||||
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, frontend');
|
expect(compiled.querySelector('h1')?.textContent).toContain(
|
||||||
|
'Hello, frontend',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,9 +3,9 @@ import { HomeComponent } from './routes/home/home.component';
|
|||||||
import { PostComponent } from './routes/post/post.component';
|
import { PostComponent } from './routes/post/post.component';
|
||||||
import { DashboardComponent } from './routes/dashboard/dashboard.component';
|
import { DashboardComponent } from './routes/dashboard/dashboard.component';
|
||||||
import { LoggedInGuard } from './shared/guards/logged-in.guard';
|
import { LoggedInGuard } from './shared/guards/logged-in.guard';
|
||||||
import { PostEditorComponent } from './components/post-editor/post-editor.component';
|
|
||||||
import { CreatePostComponent } from './routes/post/create-post/create-post.component';
|
import { CreatePostComponent } from './routes/post/create-post/create-post.component';
|
||||||
import { UpdatePostComponent } from './routes/post/update-post/update-post.component';
|
import { UpdatePostComponent } from './routes/post/update-post/update-post.component';
|
||||||
|
import { AccountComponent } from './routes/dashboard/account/account.component';
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{ path: '', component: HomeComponent },
|
{ path: '', component: HomeComponent },
|
||||||
@ -22,8 +22,11 @@ export const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'dashboard',
|
path: 'dashboard',
|
||||||
component: DashboardComponent,
|
|
||||||
canActivate: [LoggedInGuard],
|
canActivate: [LoggedInGuard],
|
||||||
|
children: [
|
||||||
|
{ path: 'account', component: AccountComponent },
|
||||||
|
{ path: '', component: DashboardComponent },
|
||||||
|
],
|
||||||
|
data: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{ path: 'tst', component: PostEditorComponent },
|
|
||||||
];
|
];
|
||||||
|
8
web/src/app/components/button/button.component.html
Normal file
8
web/src/app/components/button/button.component.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<button
|
||||||
|
(click)="(click)"
|
||||||
|
[disabled]="disabled"
|
||||||
|
[class]="getStyle()"
|
||||||
|
[routerLink]="link ? link : null"
|
||||||
|
>
|
||||||
|
<ng-content />
|
||||||
|
</button>
|
23
web/src/app/components/button/button.component.spec.ts
Normal file
23
web/src/app/components/button/button.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ButtonComponent } from './button.component';
|
||||||
|
|
||||||
|
describe('ButtonComponent', () => {
|
||||||
|
let component: ButtonComponent;
|
||||||
|
let fixture: ComponentFixture<ButtonComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [ButtonComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ButtonComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
33
web/src/app/components/button/button.component.ts
Normal file
33
web/src/app/components/button/button.component.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { NgIf } from '@angular/common';
|
||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
import { RouterLink } from '@angular/router';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-button',
|
||||||
|
imports: [NgIf, RouterLink],
|
||||||
|
templateUrl: './button.component.html',
|
||||||
|
})
|
||||||
|
export class ButtonComponent {
|
||||||
|
@Output() click = new EventEmitter<Event>();
|
||||||
|
@Input() disabled: boolean = false;
|
||||||
|
@Input() color: 'yellow' | 'orange' = 'yellow';
|
||||||
|
@Input() link: null | string = null;
|
||||||
|
|
||||||
|
getStyle() {
|
||||||
|
let col = 'a';
|
||||||
|
|
||||||
|
switch (this.color) {
|
||||||
|
case 'yellow':
|
||||||
|
col = 'bg-amber-400';
|
||||||
|
break;
|
||||||
|
case 'orange':
|
||||||
|
col = 'bg-orange-400';
|
||||||
|
}
|
||||||
|
|
||||||
|
return twMerge(
|
||||||
|
'p-1 px-5 transition-colors rounded-full hover:bg-amber-500 ',
|
||||||
|
col,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
<form [formGroup]="form" (ngSubmit)="submit()" class="grid grid-cols-3 gap-5">
|
||||||
|
<label>Password:</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
formControlName="password"
|
||||||
|
class="p-1 border-2 rounded-md col-span-2"
|
||||||
|
/>
|
||||||
|
@if (form.invalid && this.form.touched) {
|
||||||
|
<p class="col-span-3 text-red-600 italic">
|
||||||
|
Password must be at least 6 chars.
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
required
|
||||||
|
[class.bg-gray-500]="form.invalid"
|
||||||
|
class="col-start-3 col-span-1 bg-blue-300 p-2 rounded-md drop-shadow-md hover:drop-shadow-xl active:bg-blue-600 disabled:bg-gray-500"
|
||||||
|
>
|
||||||
|
Change Password
|
||||||
|
</button>
|
||||||
|
</form>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ChangePasswordComponent } from './change-password.component';
|
||||||
|
|
||||||
|
describe('ChangePasswordComponent', () => {
|
||||||
|
let component: ChangePasswordComponent;
|
||||||
|
let fixture: ComponentFixture<ChangePasswordComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [ChangePasswordComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ChangePasswordComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,39 @@
|
|||||||
|
import { Component, EventEmitter, inject, Output, output } from '@angular/core';
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormGroup,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
Validators,
|
||||||
|
} from '@angular/forms';
|
||||||
|
import { AuthService } from '../../shared/services/auth.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-change-password',
|
||||||
|
imports: [ReactiveFormsModule],
|
||||||
|
templateUrl: './change-password.component.html',
|
||||||
|
})
|
||||||
|
export class ChangePasswordComponent {
|
||||||
|
@Output() done = new EventEmitter();
|
||||||
|
private auth = inject(AuthService);
|
||||||
|
form = new FormGroup({
|
||||||
|
password: new FormControl('', [
|
||||||
|
Validators.required,
|
||||||
|
Validators.minLength(6),
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
if (this.form.invalid) {
|
||||||
|
this.form.markAllAsTouched();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.auth
|
||||||
|
.changePassword(this.form.controls.password.value!)
|
||||||
|
.then((success) => {
|
||||||
|
if (success) {
|
||||||
|
this.done.emit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
<div
|
<div
|
||||||
*ngIf="open === true"
|
*ngIf="open === true"
|
||||||
class="fixed flex top-0 justify-center items-center w-screen h-screen z-50 backdrop-blur-md bg-black/25"
|
class="fixed flex top-0 right-0 left-0 justify-center items-center w-screen h-screen z-50 backdrop-blur-md bg-black/25"
|
||||||
>
|
>
|
||||||
<div class="p-10 bg-white drop-shadow-md rounded-md">
|
<div class="p-10 bg-white drop-shadow-md rounded-md">
|
||||||
<button (click)="toggleOpen()" class="absolute top-0 right-1 p-3">X</button>
|
<button (click)="toggleOpen()" class="absolute top-0 right-1 p-3">X</button>
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
<h1 class="text-2xl">Actions</h1>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-5 mt-5">
|
||||||
|
<app-button (click)="auth.logout()">logout</app-button>
|
||||||
|
<app-button (click)="changePW()">Change Password</app-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<app-modal [(open)]="changePWOpen">
|
||||||
|
<app-change-password (done)="changePWOpen = false" />
|
||||||
|
</app-modal>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AccountComponent } from './account.component';
|
||||||
|
|
||||||
|
describe('AccountComponent', () => {
|
||||||
|
let component: AccountComponent;
|
||||||
|
let fixture: ComponentFixture<AccountComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [AccountComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AccountComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
19
web/src/app/routes/dashboard/account/account.component.ts
Normal file
19
web/src/app/routes/dashboard/account/account.component.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||||
|
import { AuthService } from '../../../shared/services/auth.service';
|
||||||
|
import { ButtonComponent } from '../../../components/button/button.component';
|
||||||
|
import { ModalComponent } from '../../../components/modal/modal.component';
|
||||||
|
import { ChangePasswordComponent } from '../../../components/change-password/change-password.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-account',
|
||||||
|
imports: [ButtonComponent, ModalComponent, ChangePasswordComponent],
|
||||||
|
templateUrl: './account.component.html',
|
||||||
|
})
|
||||||
|
export class AccountComponent {
|
||||||
|
auth = inject(AuthService);
|
||||||
|
changePWOpen: boolean = false;
|
||||||
|
|
||||||
|
changePW() {
|
||||||
|
this.changePWOpen = true;
|
||||||
|
}
|
||||||
|
}
|
@ -18,17 +18,18 @@
|
|||||||
<p class="col-start-1 col-span-4 row-start-2 row-span-2">
|
<p class="col-start-1 col-span-4 row-start-2 row-span-2">
|
||||||
<strong>TL;DR; </strong>{{ post.tldr }}
|
<strong>TL;DR; </strong>{{ post.tldr }}
|
||||||
</p>
|
</p>
|
||||||
<a
|
<app-button
|
||||||
[routerLink]="`/post/${post.id}/edit`"
|
[link]="`/post/${post.id}/edit`"
|
||||||
class="col-start-5 row-start-1 bg-amber-400 rounded-full p-1 px-5 hover:bg-amber-500 active:bg-amber-600 transition-colors text-center"
|
class="col-start-5 row-start-1"
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</a>
|
</app-button>
|
||||||
<button
|
<app-button
|
||||||
(click)="delete(post.id)"
|
(click)="delete(post.id)"
|
||||||
class="col-start-5 row-start-3 bg-orange-400 rounded-full p-1 px-5 hover:bg-amber-500 active:bg-orange-600 transition-colors"
|
class="col-start-5 row-start-3"
|
||||||
|
color="orange"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</app-button>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { Component, effect, inject } from '@angular/core';
|
import { Component, effect, inject } from '@angular/core';
|
||||||
import { PostEditorComponent } from '../../components/post-editor/post-editor.component';
|
|
||||||
import { NgFor } from '@angular/common';
|
import { NgFor } from '@angular/common';
|
||||||
import { PostsService } from '../../shared/services/posts.service';
|
import { PostsService } from '../../shared/services/posts.service';
|
||||||
import { RouterLink, RouterOutlet } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
|
import { ButtonComponent } from '../../components/button/button.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-admin',
|
selector: 'app-admin',
|
||||||
imports: [NgFor, RouterLink],
|
imports: [NgFor, RouterLink, ButtonComponent],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
templateUrl: './dashboard.component.html',
|
templateUrl: './dashboard.component.html',
|
||||||
})
|
})
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { LoginResponse, User, Claims } from '../interfaces/auth';
|
import { LoginResponse, User, Claims } from '../interfaces/auth';
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
|
import { ActivatedRouteSnapshot, Router } from '@angular/router';
|
||||||
|
|
||||||
const JWT_KEY = 'token';
|
const JWT_KEY = 'token';
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ const JWT_KEY = 'token';
|
|||||||
})
|
})
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
|
private router = inject(Router);
|
||||||
jwt: WritableSignal<string | null> = signal(null);
|
jwt: WritableSignal<string | null> = signal(null);
|
||||||
claims: WritableSignal<Claims | null> = signal(null);
|
claims: WritableSignal<Claims | null> = signal(null);
|
||||||
timeout: any | null = null;
|
timeout: any | null = null;
|
||||||
@ -76,9 +78,32 @@ export class AuthService {
|
|||||||
.post<LoginResponse>(`${environment.apiRoot}/auth/login`, user)
|
.post<LoginResponse>(`${environment.apiRoot}/auth/login`, user)
|
||||||
.subscribe((res) => this.jwt.set(res.token));
|
.subscribe((res) => this.jwt.set(res.token));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signup(user: User) {
|
||||||
|
this.http
|
||||||
|
.post<LoginResponse>(`${environment.apiRoot}/auth/signup`, user)
|
||||||
|
.subscribe((res) => this.jwt.set(res.token));
|
||||||
|
}
|
||||||
|
|
||||||
|
changePassword(password: string): Promise<boolean> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.http
|
||||||
|
.put(`${environment.apiRoot}/auth/password`, { password: password })
|
||||||
|
.subscribe({
|
||||||
|
complete: () => resolve(true),
|
||||||
|
error: () => resolve(false),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
this.http.delete(`${environment.apiRoot}/auth/logout`).subscribe(() => {
|
this.http.delete(`${environment.apiRoot}/auth/logout`).subscribe(() => {
|
||||||
this.jwt.set(null);
|
this.jwt.set(null);
|
||||||
|
|
||||||
|
// move away if protected
|
||||||
|
if (isOnProtectedRoute(this.router.routerState.snapshot.root)) {
|
||||||
|
this.router.navigateByUrl('/');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,3 +136,11 @@ function extractExpiration(token: string): Date {
|
|||||||
const jwt = parseJwt(token);
|
const jwt = parseJwt(token);
|
||||||
return new Date(jwt.exp * 1000);
|
return new Date(jwt.exp * 1000);
|
||||||
}
|
}
|
||||||
|
function isOnProtectedRoute(route: ActivatedRouteSnapshot): boolean {
|
||||||
|
// If this segment has the flag, return true
|
||||||
|
if (route.data && route.data['requiresAuth']) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Recursively check children
|
||||||
|
return route.children.some((child) => isOnProtectedRoute(child));
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user