Skip to content

Commit 954e3b0

Browse files
committed
Add session management and token verification with GraphQL integration
1 parent 4e07fb1 commit 954e3b0

23 files changed

Lines changed: 346 additions & 48 deletions

File tree

.github/copilot-instructions.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ You are an expert in TypeScript, Angular, and scalable web application developme
2626
- Prefer Reactive forms instead of Template-driven ones
2727
- Do NOT use `ngClass`, use `class` bindings instead
2828
- Do NOT use `ngStyle`, use `style` bindings instead
29+
- Use Angular Material components, avoid custom UI components when possible
30+
- Avoid CSS when a custom component is provided by Angular Material or us (e.g., app-flex)
2931

3032
## State Management
3133

@@ -45,3 +47,8 @@ You are an expert in TypeScript, Angular, and scalable web application developme
4547
- Design services around a single responsibility
4648
- Use the `providedIn: 'root'` option for singleton services
4749
- Use the `inject()` function instead of constructor injection
50+
51+
## Styling
52+
- Use Angular's built-in styling capabilities (e.g., component styles)
53+
- Prefer Angular Material CSS variables for theming
54+
- Avoid global styles; scope styles to components

PhantomDave.BankTracking.Api/Types/Queries/AccountQueries.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using PhantomDave.BankTracking.Api.Services;
22
using PhantomDave.BankTracking.Api.Types.ObjectTypes;
33
using HotChocolate.Authorization;
4+
using Microsoft.AspNetCore.Http;
45

56
namespace PhantomDave.BankTracking.Api.Types.Queries;
67

@@ -32,4 +33,12 @@ public async Task<IEnumerable<AccountType>> GetAccounts(
3233
var account = await accountService.GetAccountByEmail(email);
3334
return account != null ? AccountType.FromAccount(account) : null;
3435
}
36+
37+
[Authorize]
38+
public Task<bool> IsAValidJwt([Service] IHttpContextAccessor httpContextAccessor)
39+
{
40+
var user = httpContextAccessor.HttpContext?.User;
41+
var isAuthenticated = user?.Identity?.IsAuthenticated ?? false;
42+
return Task.FromResult(isAuthenticated);
43+
}
3544
}

compose.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
services:
22
backend:
3+
profiles:
4+
- "containerized"
35
image: banktrackingapplication
46
build:
57
context: .
@@ -8,6 +10,8 @@
810
- "5095:5095"
911

1012
frontend:
13+
profiles:
14+
- "containerized"
1115
image: banktrackingfrontend
1216
build:
1317
context: ./frontend

frontend/schema.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,5 @@ type Mutation {
4747
type Query {
4848
accountByEmail(email: String!): AccountType
4949
accounts: [AccountType!]!
50+
isAValidJwt: Boolean!
5051
}

frontend/src/app/app.config.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection, inject, importProvidersFrom } from '@angular/core';
22
import { provideRouter } from '@angular/router';
3-
43
import { routes } from './app.routes';
5-
import { provideHttpClient } from '@angular/common/http';
4+
import { provideHttpClient, HttpHeaders } from '@angular/common/http';
65
import { provideApollo } from 'apollo-angular';
76
import { HttpLink } from 'apollo-angular/http';
87
import { ApolloLink, InMemoryCache } from '@apollo/client';
9-
import { setContext } from '@apollo/client/link/context';
8+
import { SetContextLink } from '@apollo/client/link/context';
109
import { MatSnackBarModule } from '@angular/material/snack-bar';
1110
import { environment } from '../environments/environment';
1211

@@ -15,17 +14,20 @@ export const appConfig: ApplicationConfig = {
1514
provideBrowserGlobalErrorListeners(),
1615
provideZonelessChangeDetection(),
1716
importProvidersFrom(MatSnackBarModule),
18-
provideRouter(routes), provideHttpClient(), provideApollo(() => {
17+
provideRouter(routes),
18+
provideHttpClient(),
19+
provideApollo(() => {
1920
const httpLink = inject(HttpLink);
2021

21-
const authLink = setContext((_, { headers }) => {
22-
const token = localStorage.getItem('auth_token');
23-
return {
24-
headers: {
25-
...headers,
26-
...(token ? { Authorization: `Bearer ${token}` } : {})
27-
}
28-
} as Record<string, unknown>;
22+
const authLink = new SetContextLink((prevContext, _operation) => {
23+
const prev = prevContext?.headers;
24+
let headers = prev instanceof HttpHeaders ? prev : new HttpHeaders(prev ?? {});
25+
const token = localStorage.getItem('sessionData');
26+
if (token) {
27+
headers = headers.set('Authorization', `Bearer ${token}`);
28+
}
29+
30+
return { headers };
2931
});
3032

3133
return {

frontend/src/app/app.routes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import {LoginComponent} from '../components/welcome-layout/login-component/login
33
import {RegisterComponent} from '../components/welcome-layout/register-component/register-component';
44
import {HomeComponent} from '../components/home-component/home-component';
55
import {authenticateGuard} from '../guards/authenticate-guard';
6+
import {ConfiguratorComponent} from '../components/configurator/configurator/configurator-component';
67

78
export const routes: Routes = [
89
{path: '', redirectTo : 'login', pathMatch: 'full'},
910
{path: 'login', component: LoginComponent},
1011
{path: 'register', component: RegisterComponent },
1112
{path: 'home', component: HomeComponent, canActivate: [authenticateGuard]},
13+
{path: 'config', component: ConfiguratorComponent, canActivate: [authenticateGuard] },
1214
{path: '**', redirectTo : 'login' }
1315
];

frontend/src/app/app.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { Component, signal } from '@angular/core';
2-
import { RouterOutlet } from '@angular/router';
3-
import {MatSlideToggle} from '@angular/material/slide-toggle';
1+
import {Component, signal} from '@angular/core';
2+
import {RouterOutlet} from '@angular/router';
43
import {SideNavComponent} from '../components/side-nav-component/side-nav-component';
54

65
@Component({
@@ -15,7 +14,7 @@ export class App {
1514
isAuthenticated = signal(false);
1615

1716
constructor() {
18-
const token = localStorage.getItem('auth_token');
17+
const token = localStorage.getItem('sessionData');
1918
this.isAuthenticated.set(token !== null);
2019
}
2120
}

frontend/src/components/configurator/configurator/configurator-component.css

Whitespace-only changes.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<mat-card appearance="outlined">
2+
<mat-card-header>
3+
<mat-card-title>Configurations</mat-card-title>
4+
</mat-card-header>
5+
6+
<mat-card-actions align="end">
7+
<button mat-flat-button color="primary" (click)="onCreateClicked()">
8+
<mat-icon aria-hidden="true">add</mat-icon>
9+
Create New
10+
</button>
11+
</mat-card-actions>
12+
13+
<mat-card-content>
14+
<table mat-table [dataSource]="data()" class="mat-elevation-z8" style="width: 100%;">
15+
16+
<ng-container matColumnDef="name">
17+
<th mat-header-cell *matHeaderCellDef> Name </th>
18+
<td mat-cell *matCellDef="let element"> {{element.name}} </td>
19+
</ng-container>
20+
21+
<ng-container matColumnDef="description">
22+
<th mat-header-cell *matHeaderCellDef> Description </th>
23+
<td mat-cell *matCellDef="let element"> {{element.description}} </td>
24+
</ng-container>
25+
26+
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
27+
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
28+
</table>
29+
</mat-card-content>
30+
</mat-card>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {ChangeDetectionStrategy, Component, output, signal} from '@angular/core';
2+
import {MatTableModule} from '@angular/material/table';
3+
import {MatCardModule} from '@angular/material/card';
4+
import {MatButtonModule} from '@angular/material/button';
5+
import {MatIconModule} from '@angular/material/icon';
6+
7+
@Component({
8+
selector: 'app-configurator',
9+
imports: [MatTableModule, MatCardModule, MatButtonModule, MatIconModule],
10+
templateUrl: './configurator-component.html',
11+
styleUrl: './configurator-component.css',
12+
changeDetection: ChangeDetectionStrategy.OnPush,
13+
})
14+
export class ConfiguratorComponent {
15+
readonly data = signal([
16+
{
17+
name: 'Sample Configuration',
18+
description: 'This is a sample configuration for demonstration purposes.',
19+
},
20+
{
21+
name: 'Sample Configuration',
22+
description: 'This is a sample configuration for demonstration purposes.',
23+
},
24+
{
25+
name: 'Sample Configuration',
26+
description: 'This is a sample configuration for demonstration purposes.',
27+
},
28+
{
29+
name: 'Sample Configuration',
30+
description: 'This is a sample configuration for demonstration purposes.',
31+
},
32+
{
33+
name: 'Sample Configuration',
34+
description: 'This is a sample configuration for demonstration purposes.',
35+
},
36+
]);
37+
38+
displayedColumns: string[] = ['name', 'description'];
39+
40+
createConfiguration = output<void>();
41+
42+
onCreateClicked(): void {
43+
this.createConfiguration.emit();
44+
}
45+
}

0 commit comments

Comments
 (0)