feat: jwt auth
This commit is contained in:
@@ -7,6 +7,7 @@ export const authGuard: CanActivateFn = (route, state) => {
|
||||
const api = inject(APIService);
|
||||
const router = inject(Router);
|
||||
|
||||
//TODO check for cookie
|
||||
return api.isAuthenticated$.pipe(
|
||||
map((isAuthenticated) => {
|
||||
if (isAuthenticated) {
|
||||
|
||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -10465,18 +10465,6 @@
|
||||
"node": ">= 14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-jose": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-jose/-/ts-jose-6.2.0.tgz",
|
||||
"integrity": "sha512-KbuVu70utxPDrfjbPyjEs63GLA6ogBZlr7joyFmO/IvNWjwINa4LoZa5LhG/lp9Y8zb8HEkSTaAOCdnqIeMGqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jose": "6.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-node": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||
@@ -11300,12 +11288,12 @@
|
||||
"cors": "^2.8.6",
|
||||
"dotenv": "^17.3.1",
|
||||
"express": "^5.2.1",
|
||||
"jose": "^6.2.0",
|
||||
"pg": "^8.20.0",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"sequelize": "^6.37.7",
|
||||
"sequelize-typescript": "^2.1.6",
|
||||
"ts-jose": "^6.2.0",
|
||||
"winston": "^3.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
7
server/.env.example
Normal file
7
server/.env.example
Normal file
@@ -0,0 +1,7 @@
|
||||
FM_PRIVATE_KEY='{"kty":"oct","k":"WoX65SSouJ4c8l_OxwWspo9H5dhPBq9sW0lsOJB6Ygc"}'
|
||||
NODE_ENV='dev'
|
||||
FM_DB_HOST
|
||||
FM_DB_PORT
|
||||
FM_DB_NAME
|
||||
FM_DB_USER
|
||||
FM_DB_PASSWORD
|
||||
@@ -14,6 +14,7 @@
|
||||
"serve": "node ../dist/out-tsc/server/index.js",
|
||||
"setup": "docker compose up -d",
|
||||
"teardown": "docker compose down",
|
||||
"keygen": "ts-node src/scripts/keygen.ts",
|
||||
"dev": "npm run setup && npm run node; npm run teardown"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -21,12 +22,12 @@
|
||||
"cors": "^2.8.6",
|
||||
"dotenv": "^17.3.1",
|
||||
"express": "^5.2.1",
|
||||
"jose": "^6.2.0",
|
||||
"pg": "^8.20.0",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"sequelize": "^6.37.7",
|
||||
"sequelize-typescript": "^2.1.6",
|
||||
"ts-jose": "^6.2.0",
|
||||
"winston": "^3.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -3,12 +3,14 @@ import cors from "cors";
|
||||
import cookieParser from "cookie-parser";
|
||||
import transactionsRouter from './routes/transactions';
|
||||
import authRouter from './routes/auth';
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config({quiet: true});
|
||||
import { db, testConnection } from "./util/db";
|
||||
import { logger } from "./util/logging";
|
||||
import { setKeyFromEnv } from "./util/auth";
|
||||
|
||||
const app: Express = express();
|
||||
// TODO replace with frontend URL
|
||||
app.use(cors({ origin: 'http://localhost:4200', credentials: true}));
|
||||
app.use(cors());
|
||||
app.use(cookieParser());
|
||||
app.use(express.json());
|
||||
|
||||
@@ -19,7 +21,7 @@ app.get("/api/health", (req: Request, res: Response) => {
|
||||
app.use('/api/transactions', transactionsRouter);
|
||||
app.use('/api/auth', authRouter);
|
||||
|
||||
const PORT: number = parseInt(process.env.PORT as string) || 3000;
|
||||
const PORT: number = parseInt(process.env.FM_PORT as string) || 3000;
|
||||
|
||||
async function startServer() {
|
||||
await testConnection();
|
||||
@@ -27,6 +29,7 @@ async function startServer() {
|
||||
// Sync models (use migrations in production!)
|
||||
// Use { force: true } to drop and recreate tables (development only!)
|
||||
await db.sync({ alter: true });
|
||||
await setKeyFromEnv();
|
||||
|
||||
app.listen(PORT, () => {
|
||||
logger.info(`🚀 Backend Server running on http://localhost:${PORT}`);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import express, { Request } from 'express';
|
||||
import express from 'express';
|
||||
import { logger } from '../util/logging';
|
||||
import User from '../model/user';
|
||||
import { getJWT, checkJWT } from '../util/auth';
|
||||
@@ -16,12 +16,11 @@ router.post('/login', async (req, res) => {
|
||||
if (!isMatch) return res.status(401).json({ message: 'Invalid credentials' });
|
||||
|
||||
// successfully authenticated
|
||||
// TODO change this for production setup
|
||||
res.cookie('jwt', getJWT(user), {
|
||||
let jwt = await getJWT(user);
|
||||
res.cookie('jwt', jwt, {
|
||||
httpOnly: true, // Prevent XSS
|
||||
secure: false, // HTTPS only
|
||||
sameSite: 'lax', // CSRF protection
|
||||
domain: '.localhost',
|
||||
secure: process.env.NODE_ENV === 'production',// HTTPS only
|
||||
sameSite: 'strict', // CSRF protection
|
||||
maxAge: 86400000, // 1 day
|
||||
});
|
||||
res.json({ message: 'Logged in successfully' });
|
||||
@@ -36,8 +35,8 @@ router.post('/logout', (req, res) => {
|
||||
res.json({ message: 'Logged out successfully' });
|
||||
});
|
||||
|
||||
router.get('/status', (req, res) => {
|
||||
if (checkJWT(req)){
|
||||
router.get('/status', async (req, res) => {
|
||||
if (await checkJWT(req)){
|
||||
return res.status(200).json({authenticated: true});
|
||||
}
|
||||
return res.status(401).json({authenticated: false});
|
||||
|
||||
11
server/src/scripts/keygen.ts
Normal file
11
server/src/scripts/keygen.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { exportJWK, generateSecret } from "jose"
|
||||
|
||||
async function printSecret(secret){
|
||||
const jwk = await exportJWK(secret);
|
||||
console.log('🔑Generated Secret:');
|
||||
console.log(JSON.stringify(jwk));
|
||||
}
|
||||
(async () => {
|
||||
const secret = await generateSecret('HS256', {extractable: true});
|
||||
await printSecret(secret);
|
||||
})();
|
||||
@@ -1,13 +1,31 @@
|
||||
import { Request } from "express"
|
||||
import User from "../model/user"
|
||||
import { JWT, JWK } from 'ts-jose';
|
||||
import { importJWK, SignJWT, jwtVerify } from "jose";
|
||||
|
||||
const privateKey = process.env.FM_PRIVATE_KEY;
|
||||
export function checkJWT(req: Request){
|
||||
// TODO check JWT
|
||||
return req.cookies.jwt
|
||||
|
||||
let key;
|
||||
|
||||
async function setKeyFromEnv() {
|
||||
key = await importJWK(JSON.parse(process.env.FM_PRIVATE_KEY));
|
||||
}
|
||||
|
||||
export function getJWT(user: User){
|
||||
return 'toekn'
|
||||
}
|
||||
async function checkJWT(req: Request){
|
||||
try {
|
||||
let jwt= await jwtVerify(req.cookies.jwt, key);
|
||||
const user = await User.findOne({where: { userID: jwt.payload.sub}});
|
||||
return user
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function getJWT(user: User){
|
||||
let jwt = await new SignJWT()
|
||||
.setSubject(user.userID)
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.sign(key);
|
||||
|
||||
return jwt
|
||||
}
|
||||
export {getJWT, checkJWT, setKeyFromEnv}
|
||||
@@ -6,11 +6,11 @@ import Transaction from '../model/transaction';
|
||||
// Initialize Sequelize
|
||||
const db = new Sequelize({
|
||||
dialect: 'postgres',
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: parseInt(process.env.DB_PORT || '5432'),
|
||||
database: process.env.DB_NAME || 'postgres',
|
||||
username: process.env.DB_USER || 'postgres',
|
||||
password: process.env.DB_PASSWORD || 'pass',
|
||||
host: process.env.FM_DB_HOST || 'localhost',
|
||||
port: parseInt(process.env.FM_DB_PORT || '5432'),
|
||||
database: process.env.FM_DB_NAME || 'postgres',
|
||||
username: process.env.FM_DB_USER || 'postgres',
|
||||
password: process.env.FM_DB_PASSWORD || 'pass',
|
||||
logging: logger.debug.bind(logger),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import winston, { format } from "winston";
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
const logger = winston.createLogger({
|
||||
level:'info',
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user