summary refs log tree commit diff
path: root/src/util
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2025-06-01 01:03:40 +0200
committerRory& <root@rory.gay>2025-06-01 01:03:40 +0200
commit99220d73469210f94493ef92a9edc64ab50eb0d9 (patch)
tree765781a300db5c1e1a15afaa8bd09f48712a5313 /src/util
parentInit test frontend (diff)
downloadnodejs-final-assignment-99220d73469210f94493ef92a9edc64ab50eb0d9.tar.xz
Add some tests
Diffstat (limited to 'src/util')
-rw-r--r--src/util/error.js36
-rw-r--r--src/util/jwtUtils.js34
-rw-r--r--src/util/jwtUtils.test.js35
-rw-r--r--src/util/secretUtils.js2
-rw-r--r--src/util/secretUtils.test.js23
5 files changed, 129 insertions, 1 deletions
diff --git a/src/util/error.js b/src/util/error.js
new file mode 100644

index 0000000..96f1deb --- /dev/null +++ b/src/util/error.js
@@ -0,0 +1,36 @@ +export class SafeNSoundError extends Error { + constructor(options) { + super(); + if (typeof options === 'string') { + this.errCode = options; + super.message = this.getDefaultMessage(); + } else if (typeof options === 'object') { + this.errCode = options.errCode || 'UNKNOWN_ERROR'; + super.message = options.message || this.getDefaultMessage(); + } else { + this.errCode = 'UNKNOWN_ERROR'; + this.message = + 'An unknown error occurred (invalid SafeNSoundError constructor options)'; + } + } + + getDefaultMessage() { + switch (this.type) { + case 'USER_NOT_FOUND': + return 'User not found'; + case 'INVALID_CREDENTIALS': + return 'Invalid credentials'; + case 'EMAIL_ALREADY_EXISTS': + return 'Email already exists'; + case 'PASSWORD_TOO_WEAK': + return 'Password is too weak'; + case 'TOKEN_EXPIRED': + return 'Token has expired'; + case 'UNAUTHORIZED': + return 'Unauthorized access'; + case 'UNKNOWN_ERROR': + default: + return 'An unknown error occurred'; + } + } +} diff --git a/src/util/jwtUtils.js b/src/util/jwtUtils.js
index 115c9c5..9031631 100644 --- a/src/util/jwtUtils.js +++ b/src/util/jwtUtils.js
@@ -1,6 +1,7 @@ import {existsSync} from 'fs'; import {readFile, writeFile} from "node:fs/promises"; import {generateKeyPairSync, createHash, createPublicKey, createPrivateKey} from 'node:crypto'; +import jwt from "jsonwebtoken"; let privateKey, publicKey, fingerprint; @@ -10,6 +11,8 @@ export async function initJwt() { throw new Error('JWT secret path is not defined in environment variables, or the directory does not exist.'); } + console.log(`[JWT] Initializing JWT with secret path: ${secretPath}`); + const privateKeyPath = `${secretPath}/jwt.key`; const publicKeyPath = `${secretPath}/jwt.key.pub`; @@ -55,9 +58,40 @@ const jwtOptions = { } export async function generateJwtToken(user) { + if (!privateKey) { + throw new Error('JWT private key is not initialized. Please call initJwt() first.'); + } + + const payload = { + sub: user._id.toString(), + username: user.username, + type: user.type, + iat: Math.floor(Date.now() / 1000) + }; + return new Promise((resolve, reject) => { + jwt.sign(payload, privateKey, jwtOptions, (err, token) => { + if (err) { + console.error('[JWT] Error generating token:', err); + return reject(err); + } + resolve(token); + }); + }); } export async function validateJwtToken(token) { + if (!publicKey) { + throw new Error('JWT public key is not initialized. Please call initJwt() first.'); + } + return new Promise((resolve, reject) => { + jwt.verify(token, publicKey, jwtOptions, (err, decoded) => { + if (err) { + console.error('[JWT] Token validation failed:', err); + return reject(err); + } + resolve(decoded); + }); + }); } \ No newline at end of file diff --git a/src/util/jwtUtils.test.js b/src/util/jwtUtils.test.js new file mode 100644
index 0000000..94a7d99 --- /dev/null +++ b/src/util/jwtUtils.test.js
@@ -0,0 +1,35 @@ +import { it } from 'node:test'; +import { initJwt, generateJwtToken, validateJwtToken } from './jwtUtils.js'; +import * as dotenv from 'dotenv'; +import { tmpdir } from 'node:os'; +import { mkdtemp } from 'node:fs/promises'; +import * as assert from 'node:assert'; +dotenv.config(); + +const user = { + _id: 'meow', + username: 'testuser' +}; + +await it('Should be able to generate new secrets', async () => { + process.env.JWT_SECRET_PATH = await mkdtemp(tmpdir()); + await initJwt(); +}); + +await it('Should be able to load the JWT utils', async () => { + process.env.JWT_SECRET_PATH = await mkdtemp(tmpdir()); + await initJwt(); + await initJwt(); +}); + +it('Should be able to create a JWT token', async () => { + await initJwt(); + await generateJwtToken(user); +}); + +it('Should be able to validate a JWT token', async () => { + await initJwt(); + const token = await generateJwtToken(user); + const result = await validateJwtToken(token); + console.log(result); +}); diff --git a/src/util/secretUtils.js b/src/util/secretUtils.js
index 92e1b1c..7394395 100644 --- a/src/util/secretUtils.js +++ b/src/util/secretUtils.js
@@ -3,7 +3,7 @@ import fs from 'node:fs/promises'; export async function readSecret(name, path) { console.log(`[SECRET] Reading secret "${name}" from path: ${path}`); if (!path) { - throw new Error('Path to secret file is required'); + throw new Error(`Path to secret file is required: ${name}`); } const content = await fs.readFile(path, 'utf8'); return content.trim(); diff --git a/src/util/secretUtils.test.js b/src/util/secretUtils.test.js new file mode 100644
index 0000000..d95d56f --- /dev/null +++ b/src/util/secretUtils.test.js
@@ -0,0 +1,23 @@ +import { it } from 'node:test'; +import * as dotenv from 'dotenv'; +import { tmpdir } from 'node:os'; +import * as assert from 'node:assert'; +import { readSecret } from '#util/secretUtils.js'; +import { writeFile, mkdtemp } from 'node:fs/promises'; + +dotenv.config(); + +await it('Should fail on nonexistant secret', async () => { + const secretPath = (await mkdtemp(tmpdir())) + '/secret'; + await assert.rejects(readSecret('Nonexistant secret', secretPath)); +}); + +await it('Should read secret from file', async () => { + const secretPath = (await mkdtemp(tmpdir())) + '/secret'; + await writeFile(secretPath, 'testsecret'); + await assert.doesNotReject(readSecret('Test secret', secretPath)); + await assert.equal( + await readSecret('Test secret', secretPath), + 'testsecret' + ); +});