diff --git a/src/api/middlewares/authMiddleware.js b/src/api/middlewares/authMiddleware.js
index 3a71e45..13d0d27 100644
--- a/src/api/middlewares/authMiddleware.js
+++ b/src/api/middlewares/authMiddleware.js
@@ -1,17 +1,68 @@
import { validateJwtToken } from '#util/jwtUtils.js';
import { DbUser, UserType } from '#db/schemas/index.js';
+import { SafeNSoundError } from '#util/error.js';
+
+const shouldLogAuth = !!process.env['LOG_AUTH'];
+function logAuth(...params) {
+ if (shouldLogAuth) {
+ console.log('[AUTH]', ...params);
+ }
+}
+
+function getTokenFromHeader(authHeader) {
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
+ return null;
+ }
+ const parts = authHeader.split(' ');
+ return {
+ scheme: parts[0],
+ token: parts[1]
+ };
+}
+
+export async function useAuthentication(req, res, next) {
+ if (!req.headers.authorization) {
+ logAuth('Request to', req.path, 'without auth');
+ next();
+ return;
+ }
+
+ const auth = (req.auth = await validateJwtToken(
+ getTokenFromHeader(req.headers.authorization).token
+ ));
+ logAuth('Token data:', auth);
+
+ // req.user = auth;
+ next();
+}
+
+export async function requireAuth(req, res, next) {
+ if (!req.auth) {
+ logAuth('Unauthorized request to', req.path);
+ res.status(401).send(
+ new SafeNSoundError({
+ errCode: 'UNAUTHORIZED',
+ message: 'Unauthorized'
+ })
+ );
+ return;
+ }
+
+ next();
+}
/**
* @param options {AuthValidationOptions}
* @returns {(function(*, *, *): void)|*}
*/
-export function validateAuth(options) {
+export function requireRole(options) {
return async function (req, res, next) {
- const auth = (req.auth = validateJwtToken(req.headers.authorization));
- if (!auth) {
- res.status(401).send('Unauthorized');
- return;
- }
+ res.status(401).send(
+ new SafeNSoundError({
+ errCode: 'UNAUTHORIZED',
+ message: 'Unauthorized'
+ })
+ );
const user = (req.user = await DbUser.findById(auth.id).exec());
@@ -22,7 +73,12 @@ export function validateAuth(options) {
}
if (options.roles && !options.roles.includes(user.type)) {
- res.status(401).send('Unauthorized');
+ res.status(401).send(
+ new SafeNSoundError({
+ errCode: 'UNAUTHORIZED',
+ message: 'Unauthorized'
+ })
+ );
return;
}
@@ -30,11 +86,10 @@ export function validateAuth(options) {
};
}
-export const requireAuth = validateAuth({});
-export const requireAdmin = validateAuth({ roles: [UserType.ADMIN] });
-export const requireMonitor = validateAuth({ roles: [UserType.MONITOR] });
-export const requireUser = validateAuth({ roles: [UserType.USER] });
-export const requireUserOrMonitor = validateAuth({
+export const requireAdmin = requireRole({ roles: [UserType.ADMIN] });
+export const requireMonitor = requireRole({ roles: [UserType.MONITOR] });
+export const requireUser = requireRole({ roles: [UserType.USER] });
+export const requireUserOrMonitor = requireRole({
roles: [UserType.USER, UserType.MONITOR]
});
diff --git a/src/api/routes.js b/src/api/routes.js
index b91e7ba..1853b57 100644
--- a/src/api/routes.js
+++ b/src/api/routes.js
@@ -2,34 +2,60 @@ import * as routes from './routes/index.js';
import * as url from 'node:url';
import express from 'express';
+let dumpHttpEndpoints = false;
+
+function logPrefixed(...args) {
+ console.log('%', ...args);
+}
+
+function logHttpHeader() {
+ if (!dumpHttpEndpoints) return;
+ logPrefixed('@baseUrl=http://localhost:3000');
+ logPrefixed('@username=admin');
+ logPrefixed('@password=admin');
+ logPrefixed('@email=admin@example.com');
+ logPrefixed('@userType=admin');
+ logPrefixed('');
+ logPrefixed('');
+}
+
function logHttpEntry(method, route, routeMethod) {
- if (routeMethod.description) console.log('%', '#', routeMethod.description);
+ console.log(
+ 'Registering',
+ method.toUpperCase(),
+ route + ':',
+ routeMethod.description || 'No description provided'
+ );
+ if (!dumpHttpEndpoints) return;
+
+ if (routeMethod.description) logPrefixed('#', routeMethod.description);
+
+ logPrefixed(method, '{{baseUrl}}' + route, 'HTTP/1.1');
- console.log('%', method, '{{baseUrl}}' + route, 'HTTP/1.1');
if (routeMethod.exampleHeaders) {
for (var key of Object.keys(routeMethod.exampleHeaders)) {
- console.log('%', `${key}: ${routeMethod.exampleHeaders[key]}`);
+ logPrefixed(`${key}: ${routeMethod.exampleHeaders[key]}`);
}
}
+
if (routeMethod.exampleBody) {
- console.log('%', 'Content-Type: application/json');
- console.log('% ');
- console.log(
- '%',
+ logPrefixed('Content-Type: application/json');
+ logPrefixed('');
+ logPrefixed(
JSON.stringify(routeMethod.exampleBody, null, 4).replaceAll(
'\n',
'\n% '
)
);
- console.log('% ');
+ logPrefixed('');
}
- console.log('% ');
- console.log('%', '###');
+
+ logPrefixed('');
+ logPrefixed('###');
}
export function registerRoutes(app) {
- // http file header:
- console.log('%', '');
+ logHttpHeader();
let routeCount = 0;
Object.keys(routes).forEach(routeName => {
@@ -46,11 +72,6 @@ export function registerRoutes(app) {
const routeMethod = route.methods[routeMethodName];
if (routeMethod === undefined) return;
- console.log(
- 'Registering',
- routeMethodName.toUpperCase(),
- route.path
- );
logHttpEntry(
routeMethodName.toUpperCase(),
route.path,
@@ -67,10 +88,12 @@ export function registerRoutes(app) {
console.log(`Registered ${routeCount} routes.`);
}
+// Check if the script is run directly, to create the routes.html file.
if (import.meta.url.startsWith('file:')) {
const modulePath = url.fileURLToPath(import.meta.url);
if (process.argv[1] === modulePath) {
const app = express();
+ dumpHttpEndpoints = true;
registerRoutes(app);
process.exit(1);
}
diff --git a/src/api/routes/alarmRoutes.js b/src/api/routes/alarmRoutes.js
index 9739f4f..f62aa6c 100644
--- a/src/api/routes/alarmRoutes.js
+++ b/src/api/routes/alarmRoutes.js
@@ -1,7 +1,7 @@
import {
requireMonitor,
requireUser,
- validateAuth
+ requireRole
} from '#api/middlewares/index.js';
import { UserType } from '#db/schemas/index.js';
import { RouteMethod } from '#api/RouteDescription.js';
diff --git a/src/api/routes/auth/accountRoutes.js b/src/api/routes/auth/accountRoutes.js
index a2181d1..547110e 100644
--- a/src/api/routes/auth/accountRoutes.js
+++ b/src/api/routes/auth/accountRoutes.js
@@ -1,6 +1,7 @@
import { deleteUser, loginUser, registerUser } from '#db/index.js';
import { AuthDto, RegisterDto } from '#dto/index.js';
import { RouteDescription, RouteMethod } from '#api/RouteDescription.js';
+import { WhoAmIDto } from '#dto/auth/WhoAmIDto.js';
/**
* @type {RouteDescription}
@@ -91,3 +92,23 @@ export const deleteRoute = {
})
}
};
+
+/**
+ * @type {RouteDescription}
+ */
+export const whoAmI = {
+ path: '/auth/whoami',
+ methods: {
+ get: new RouteMethod({
+ description: 'Get current user',
+ async method(req, res) {
+ const data = await WhoAmIDto.create({
+ userId: req.auth.sub,
+ deviceId: req.auth.deviceId,
+ type: req.auth.type
+ });
+ res.send(data);
+ }
+ })
+ }
+};
diff --git a/src/api/routes/auth/adminAccountRoutes.js b/src/api/routes/auth/adminAccountRoutes.js
index 2153945..13cca53 100644
--- a/src/api/routes/auth/adminAccountRoutes.js
+++ b/src/api/routes/auth/adminAccountRoutes.js
@@ -1,6 +1,6 @@
import { deleteUser, loginUser, registerUser, UserType } from '#db/index.js';
import { AuthDto, RegisterDto } from '#dto/index.js';
-import { requireAdmin, validateAuth } from '#api/middlewares/index.js';
+import { requireAdmin, requireRole } from '#api/middlewares/index.js';
import { RouteDescription, RouteMethod } from '#api/RouteDescription.js';
/**
diff --git a/src/api/routes/auth/deviceRoutes.js b/src/api/routes/auth/deviceRoutes.js
index 849a48c..40090e8 100644
--- a/src/api/routes/auth/deviceRoutes.js
+++ b/src/api/routes/auth/deviceRoutes.js
@@ -1,6 +1,6 @@
import { registerUser } from '#db/index.js';
import { RegisterDto } from '#dto/index.js';
-import { validateAuth } from '#api/middlewares/index.js';
+import { requireRole } from '#api/middlewares/index.js';
import { RouteDescription, RouteMethod } from '#api/RouteDescription.js';
/**
@@ -10,7 +10,7 @@ export const getDevicesRoute = {
path: '/auth/devices',
methods: {
get: new RouteMethod({
- middlewares: [validateAuth({})],
+ middlewares: [requireRole({})],
async method(req, res) {
const data = await RegisterDto.create(req.body);
const registerResult = await registerUser(data);
diff --git a/src/api/routes/budgetRoutes.js b/src/api/routes/budgetRoutes.js
index d7ebde4..4ad4897 100644
--- a/src/api/routes/budgetRoutes.js
+++ b/src/api/routes/budgetRoutes.js
@@ -1,7 +1,7 @@
import {
requireMonitor,
requireUser,
- validateAuth
+ requireRole
} from '#api/middlewares/index.js';
import { UserType } from '#db/schemas/index.js';
import {
@@ -10,25 +10,30 @@ import {
RouteMethodList
} from '#api/RouteDescription.js';
import { getUserById } from '#db/dbAccess/index.js';
+import { SafeNSoundError } from '#util/error.js';
/**
* @type {RouteDescription}
*/
export const getBudgetByUserRoute = {
path: '/budget/:id',
- methods: new RouteMethodList({
+ methods: {
get: new RouteMethod({
middlewares: [requireMonitor],
async method(req, res) {
if (req.user.type !== UserType.ADMIN) {
if (!req.user.monitoredUsers.includes(req.params.id))
- throw new Error('meow');
+ throw new SafeNSoundError({
+ errCode: 'UNAUTHORIZED',
+ message:
+ "You do not have permission to access this user's budget."
+ });
}
- //if (!req.)
- // const user = await getUserById(req.);
+ const user = await getUserById(req.params.id);
+ res.send({ balance: user.balance });
}
})
- })
+ }
};
/**
diff --git a/src/api/start.js b/src/api/start.js
index dcd5cf3..fcc4a28 100644
--- a/src/api/start.js
+++ b/src/api/start.js
@@ -1,6 +1,6 @@
import express from 'express';
import { registerRoutes } from './routes.js';
-import { useCors, useLogging } from './middlewares/index.js';
+import { useAuthentication, useCors, useLogging } from './middlewares/index.js';
import { initDb } from '#db/index.js';
import { initJwt } from '#util/index.js';
import { handleErrors } from '#api/middlewares/errorMiddleware.js';
@@ -15,6 +15,7 @@ await initJwt();
// Configure Express
app.use(express.json());
app.use(useCors);
+app.use(useAuthentication);
app.disable('x-powered-by');
app.set('json spaces', 2);
|