begin auth cookie

This commit is contained in:
eneller
2026-03-08 19:02:08 +01:00
parent f5ae9ac9e6
commit cd568f0a63
6 changed files with 56 additions and 10 deletions

View File

@@ -36,7 +36,7 @@ export class ScreenLogin {
this.api.login(this.loginForm.value.username, this.loginForm.value.password).subscribe({ this.api.login(this.loginForm.value.username, this.loginForm.value.password).subscribe({
next: () => { next: () => {
//this.router.navigate(['']); this.router.navigate(['']);
}, },
error: (err) => { error: (err) => {
this.error = err.error?.message || 'Login failed. Please try again.'; this.error = err.error?.message || 'Login failed. Please try again.';

View File

@@ -1,6 +1,6 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { BehaviorSubject, catchError, map, Observable, of, tap } from 'rxjs';
import Transaction from '@model/transaction' import Transaction from '@model/transaction'
@Injectable({ @Injectable({
@@ -8,6 +8,8 @@ import Transaction from '@model/transaction'
}) })
export class APIService { export class APIService {
private apiUrl = 'http://localhost:3000/api' private apiUrl = 'http://localhost:3000/api'
private isAuthenticatedSubject = new BehaviorSubject<boolean>(false);
isAuthenticated$ = this.isAuthenticatedSubject.asObservable();
constructor(private http: HttpClient){} constructor(private http: HttpClient){}
@@ -20,4 +22,14 @@ export class APIService {
logout(): Observable<any>{ logout(): Observable<any>{
return this.http.post(this.apiUrl + '/auth/logout', {}); return this.http.post(this.apiUrl + '/auth/logout', {});
} }
checkAuthStatus(): Observable<boolean> {
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),
})
);
}
} }

19
package-lock.json generated
View File

@@ -10,8 +10,7 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"workspaces": [ "workspaces": [
"client", "client",
"server", "server"
"shared"
], ],
"devDependencies": { "devDependencies": {
"@types/node": "^25.3.2", "@types/node": "^25.3.2",
@@ -4278,6 +4277,16 @@
"@types/node": "*" "@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": { "node_modules/@types/cors": {
"version": "2.8.19", "version": "2.8.19",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
@@ -9863,10 +9872,6 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/shared": {
"resolved": "shared",
"link": true
},
"node_modules/shebang-command": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -11304,6 +11309,7 @@
"winston": "^3.19.0" "winston": "^3.19.0"
}, },
"devDependencies": { "devDependencies": {
"@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.19", "@types/cors": "^2.8.19",
"@types/express": "^5.0.6", "@types/express": "^5.0.6",
"@types/node": "^25.3.5", "@types/node": "^25.3.5",
@@ -11319,6 +11325,7 @@
}, },
"shared": { "shared": {
"version": "1.0.0", "version": "1.0.0",
"extraneous": true,
"license": "GPL-3.0", "license": "GPL-3.0",
"devDependencies": {} "devDependencies": {}
} }

View File

@@ -30,6 +30,7 @@
"winston": "^3.19.0" "winston": "^3.19.0"
}, },
"devDependencies": { "devDependencies": {
"@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.19", "@types/cors": "^2.8.19",
"@types/express": "^5.0.6", "@types/express": "^5.0.6",
"@types/node": "^25.3.5", "@types/node": "^25.3.5",

View File

@@ -1,12 +1,14 @@
import express, { Express, Request, Response } from "express"; import express, { Express, Request, Response } from "express";
import cors from "cors"; import cors from "cors";
import cookieParser from "cookie-parser";
import transactionsRouter from './routes/transactions'; import transactionsRouter from './routes/transactions';
import authRouter from './routes/auth'; import authRouter from './routes/auth';
import { db, testConnection } from "./util/db"; import { db, testConnection } from "./util/db";
import { logger } from "./util/logging"; import { logger } from "./util/logging";
const app: Express = express(); const app: Express = express();
app.use(cors()); app.use(cors({ origin: 'http://localhost:4200', credentials: true}));
app.use(cookieParser());
app.use(express.json()); app.use(express.json());
app.get("/api/health", (req: Request, res: Response) => { app.get("/api/health", (req: Request, res: Response) => {

View File

@@ -1,4 +1,4 @@
import express from 'express'; import express, { Request } from 'express';
import { logger } from '../util/logging'; import { logger } from '../util/logging';
import User from '../model/user'; import User from '../model/user';
@@ -13,6 +13,16 @@ router.post('/login', async (req, res) => {
//TODO hash passwords //TODO hash passwords
//const isMatch = await bcrypt.compare(password, user.passwordHash); //const isMatch = await bcrypt.compare(password, user.passwordHash);
if (!isMatch) return res.status(401).json({ message: 'Invalid credentials' }); 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' }); res.json({ message: 'Logged in successfully' });
}catch (err) { }catch (err) {
logger.error('Failed to authenticate:', err); logger.error('Failed to authenticate:', err);
@@ -25,4 +35,18 @@ router.post('/logout', (req, res) => {
res.json({ message: 'Logged out successfully' }); 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; export default router;