From cd568f0a639b1fcef8e66f6f17306d663e5571e4 Mon Sep 17 00:00:00 2001 From: eneller Date: Sun, 8 Mar 2026 19:02:08 +0100 Subject: [PATCH] begin auth cookie --- .../app/screens/screen-login/screen-login.ts | 2 +- client/src/app/services/api.ts | 14 +++++++++- package-lock.json | 19 +++++++++----- server/package.json | 1 + server/src/index.ts | 4 ++- server/src/routes/auth.ts | 26 ++++++++++++++++++- 6 files changed, 56 insertions(+), 10 deletions(-) diff --git a/client/src/app/screens/screen-login/screen-login.ts b/client/src/app/screens/screen-login/screen-login.ts index 04b90be..15ea6ca 100644 --- a/client/src/app/screens/screen-login/screen-login.ts +++ b/client/src/app/screens/screen-login/screen-login.ts @@ -36,7 +36,7 @@ export class ScreenLogin { this.api.login(this.loginForm.value.username, this.loginForm.value.password).subscribe({ next: () => { - //this.router.navigate(['']); + this.router.navigate(['']); }, error: (err) => { this.error = err.error?.message || 'Login failed. Please try again.'; diff --git a/client/src/app/services/api.ts b/client/src/app/services/api.ts index f720f94..121bdda 100644 --- a/client/src/app/services/api.ts +++ b/client/src/app/services/api.ts @@ -1,6 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; +import { BehaviorSubject, catchError, map, Observable, of, tap } from 'rxjs'; import Transaction from '@model/transaction' @Injectable({ @@ -8,6 +8,8 @@ import Transaction from '@model/transaction' }) export class APIService { private apiUrl = 'http://localhost:3000/api' + private isAuthenticatedSubject = new BehaviorSubject(false); + isAuthenticated$ = this.isAuthenticatedSubject.asObservable(); constructor(private http: HttpClient){} @@ -20,4 +22,14 @@ export class APIService { logout(): Observable{ return this.http.post(this.apiUrl + '/auth/logout', {}); } + checkAuthStatus(): Observable { + return this.http.get(`${this.apiUrl}/auth/status`, { withCredentials: true }).pipe( + map(() => true), + catchError(() => of(false)), + tap({ + next: () => this.isAuthenticatedSubject.next(true), + error: () => this.isAuthenticatedSubject.next(false), + }) + ); + } } diff --git a/package-lock.json b/package-lock.json index a4d92f0..6ba4116 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,7 @@ "license": "GPL-3.0", "workspaces": [ "client", - "server", - "shared" + "server" ], "devDependencies": { "@types/node": "^25.3.2", @@ -4278,6 +4277,16 @@ "@types/node": "*" } }, + "node_modules/@types/cookie-parser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz", + "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/express": "*" + } + }, "node_modules/@types/cors": { "version": "2.8.19", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", @@ -9863,10 +9872,6 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, - "node_modules/shared": { - "resolved": "shared", - "link": true - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -11304,6 +11309,7 @@ "winston": "^3.19.0" }, "devDependencies": { + "@types/cookie-parser": "^1.4.10", "@types/cors": "^2.8.19", "@types/express": "^5.0.6", "@types/node": "^25.3.5", @@ -11319,6 +11325,7 @@ }, "shared": { "version": "1.0.0", + "extraneous": true, "license": "GPL-3.0", "devDependencies": {} } diff --git a/server/package.json b/server/package.json index 14da443..98cdc05 100644 --- a/server/package.json +++ b/server/package.json @@ -30,6 +30,7 @@ "winston": "^3.19.0" }, "devDependencies": { + "@types/cookie-parser": "^1.4.10", "@types/cors": "^2.8.19", "@types/express": "^5.0.6", "@types/node": "^25.3.5", diff --git a/server/src/index.ts b/server/src/index.ts index c186e67..abdd633 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,12 +1,14 @@ import express, { Express, Request, Response } from "express"; import cors from "cors"; +import cookieParser from "cookie-parser"; import transactionsRouter from './routes/transactions'; import authRouter from './routes/auth'; import { db, testConnection } from "./util/db"; import { logger } from "./util/logging"; const app: Express = express(); -app.use(cors()); +app.use(cors({ origin: 'http://localhost:4200', credentials: true})); +app.use(cookieParser()); app.use(express.json()); app.get("/api/health", (req: Request, res: Response) => { diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index e9a2d81..42991a8 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -1,4 +1,4 @@ -import express from 'express'; +import express, { Request } from 'express'; import { logger } from '../util/logging'; import User from '../model/user'; @@ -13,6 +13,16 @@ router.post('/login', async (req, res) => { //TODO hash passwords //const isMatch = await bcrypt.compare(password, user.passwordHash); if (!isMatch) return res.status(401).json({ message: 'Invalid credentials' }); + + // successfully authenticated + res.cookie('jwt', 'toekn', { + /* + httpOnly: true, // Prevent XSS + secure: true, // HTTPS only + sameSite: 'strict', // CSRF protection + */ + maxAge: 86400000, // 1 day + }); res.json({ message: 'Logged in successfully' }); }catch (err) { logger.error('Failed to authenticate:', err); @@ -25,4 +35,18 @@ router.post('/logout', (req, res) => { res.json({ message: 'Logged out successfully' }); }); +router.get('/status', (req, res) => { + + console.log(req.cookies); + if (isAuthenticated(req)){ + return res.status(200).json({authenticated: true}); + } + return res.status(401).json({authenticated: false}); +}) + +function isAuthenticated(req: Request){ + // TODO check JWT + return req.cookies.jwt +} + export default router;