diff --git a/src/api/middlewares/authMiddleware.js b/src/api/middlewares/authMiddleware.js
index b91449f..34a96f4 100644
--- a/src/api/middlewares/authMiddleware.js
+++ b/src/api/middlewares/authMiddleware.js
@@ -3,7 +3,7 @@ import { DbUser, UserType } from '#db/schemas/index.js';
import { SafeNSoundError } from '#util/error.js';
import { getUserById } from '#db/dbAccess/index.js';
-const shouldLogAuth = !!process.env['LOG_AUTH'];
+const shouldLogAuth = process.env['LOG_AUTH'] === 'true';
function logAuth(...params) {
if (shouldLogAuth) {
console.log('[AUTH]', ...params);
@@ -33,10 +33,25 @@ export async function useAuthentication(req, res, next) {
));
logAuth('Token data:', auth);
- req.user = await getUserById(auth.sub);
- logAuth('User data:', req.user);
+ if (auth) {
+ req.user = await getUserById(auth.sub);
+ logAuth('User data:', req.user);
- req.device = req.user.devices.find(device => device.id === auth.deviceId);
+ if (req.user) {
+ req.device = req.user.devices.find(
+ device => device.id === auth.deviceId
+ );
+ logAuth('Device data:', req.device);
+
+ if (req.device) {
+ if (req.device.lastSeen < Date.now() - 1000) {
+ logAuth('Updating device last seen for', req.device.id);
+ req.device.lastSeen = Date.now();
+ await req.user.save();
+ }
+ }
+ }
+ }
next();
}
diff --git a/src/api/middlewares/errorMiddleware.js b/src/api/middlewares/errorMiddleware.js
index d66c31d..2ca31db 100644
--- a/src/api/middlewares/errorMiddleware.js
+++ b/src/api/middlewares/errorMiddleware.js
@@ -22,7 +22,6 @@ export function handleErrors(err, req, res, _next) {
}
if (err instanceof SafeNSoundError) {
- console.log('meow');
console.error(err.stack);
res.status(500).json(err);
} else {
diff --git a/src/api/routes/adminRoutes.js b/src/api/routes/adminRoutes.js
new file mode 100644
index 0000000..7a1d1e2
--- /dev/null
+++ b/src/api/routes/adminRoutes.js
@@ -0,0 +1,55 @@
+import {
+ requireMonitor,
+ requireUser,
+ requireRole,
+ requireAdmin
+} from '#api/middlewares/index.js';
+import { DbUser, UserType } from '#db/schemas/index.js';
+import { RouteMethod } from '#api/RouteDescription.js';
+import { getUserById } from '#db/dbAccess/index.js';
+import { AlarmDto } from '#dto/AlarmDto.js';
+
+/**
+ * @type {RouteDescription}
+ */
+export const adminGetUserIdsRoute = {
+ path: '/admin/allUserIds',
+ methods: {
+ get: new RouteMethod({
+ middlewares: [requireAdmin],
+ description: 'Get all user IDs',
+ async method(req, res) {
+ // streaming json array
+ res.status(200);
+ res.write('[\n');
+
+ const users = DbUser.find().lean().cursor();
+ for await (const user of users) {
+ res.write(JSON.stringify(user._id) + ',\n');
+ }
+
+ res.write(']');
+ res.end();
+ }
+ })
+ }
+};
+export const adminMonitorAllRoute = {
+ path: '/admin/monitorAllUsers',
+ methods: {
+ post: new RouteMethod({
+ middlewares: [requireAdmin],
+ description: 'Monitor all users',
+ async method(req, res) {
+ const users = await DbUser.find({ type: UserType.USER }).lean();
+ const monitoredUsers = users.map(user => user._id);
+
+ // Update the admin's monitoredUsers
+ req.user.monitoredUsers = monitoredUsers;
+ await req.user.save();
+
+ res.status(204).send();
+ }
+ })
+ }
+};
diff --git a/src/api/routes/alarmRoutes.js b/src/api/routes/alarmRoutes.js
index 23b79c1..ae5a88a 100644
--- a/src/api/routes/alarmRoutes.js
+++ b/src/api/routes/alarmRoutes.js
@@ -3,7 +3,7 @@ import {
requireUser,
requireRole
} from '#api/middlewares/index.js';
-import { UserType } from '#db/schemas/index.js';
+import { DbUser, UserType } from '#db/schemas/index.js';
import { RouteMethod } from '#api/RouteDescription.js';
import { getUserById } from '#db/dbAccess/index.js';
import { AlarmDto } from '#dto/AlarmDto.js';
@@ -12,7 +12,7 @@ import { AlarmDto } from '#dto/AlarmDto.js';
* @type {RouteDescription}
*/
export const alarmByUserRoute = {
- path: '/alarm/:id',
+ path: '/user/:id/alarm',
methods: {
get: new RouteMethod({
middlewares: [requireMonitor],
@@ -27,8 +27,10 @@ export const alarmByUserRoute = {
description: 'Clear the alarm for a monitored user',
async method(req, res) {
const user = await getUserById(req.params.id);
- user.alarm = null;
- await user.save();
+ if (user.alarm) {
+ user.alarm = null;
+ await user.save();
+ }
res.status(204).send();
}
})
@@ -45,15 +47,24 @@ export const alarmListRoute = {
middlewares: [requireMonitor],
description: 'Get a list of all alarms for monitored users',
async method(req, res) {
- console.log(req.user.monitoredUsers);
- const alarms = {};
- for (const userId of req.user.monitoredUsers) {
- const user = await getUserById(userId);
+ // execute the query asynchronously and manually construct a response, for scaling reasons
+ const users = DbUser.find({
+ _id: { $in: req.user.monitoredUsers }
+ })
+ .lean()
+ .cursor();
+ res.status(200);
+ res.write('{\n');
+ for await (const user of users) {
if (user.alarm) {
- alarms[userId] = user.alarm;
+ // alarms[user._id] = user.alarm;
+ res.write(
+ `"${user._id}": ${JSON.stringify(user.alarm)},\n`
+ );
}
}
- res.send(alarms);
+ res.write('}');
+ res.end();
}
})
}
@@ -76,7 +87,6 @@ export const alarmRoute = {
middlewares: [requireUser],
description: 'Raise an alarm',
async method(req, res) {
- console.log(req.body);
req.user.alarm = await AlarmDto.create(req.body);
await req.user.save();
res.status(204).send();
@@ -86,8 +96,10 @@ export const alarmRoute = {
middlewares: [requireUser],
description: 'Clear alarm',
async method(req, res) {
- req.user.alarm = null;
- await req.user.save();
+ if (req.user.alarm) {
+ req.user.alarm = null;
+ await req.user.save();
+ }
res.status(204).send();
}
})
diff --git a/src/api/routes/assignedUserRoutes.js b/src/api/routes/assignedUserRoutes.js
new file mode 100644
index 0000000..dac9b13
--- /dev/null
+++ b/src/api/routes/assignedUserRoutes.js
@@ -0,0 +1,56 @@
+import { getUserById } from '#db/dbAccess/index.js';
+import { requireMonitor } from '#api/middlewares/index.js';
+import { RouteMethod } from '#api/RouteDescription.js';
+import { SafeNSoundError } from '#util/error.js';
+
+/**
+ * @type {RouteDescription}
+ */
+export const assignedUserRoute = {
+ path: '/monitor/assignedUsers',
+ methods: {
+ get: new RouteMethod({
+ middlewares: [requireMonitor],
+ description: 'Get assigned users',
+ async method(req, res) {
+ res.send(req.user.monitoredUsers);
+ }
+ }),
+ patch: {
+ middlewares: [requireMonitor],
+ description: 'Add an assigned user by ID',
+ async method(req, res) {
+ if (!req.body.userId) {
+ throw new SafeNSoundError({
+ errCode: 'MISSING_FIELD_ERROR',
+ message: 'User ID is required',
+ field: 'userId'
+ });
+ }
+
+ if (req.user.monitoredUsers.includes(req.body.userId)) {
+ throw new SafeNSoundError({
+ errCode: 'DUPLICATE_KEY_ERROR',
+ message: 'User is already assigned'
+ });
+ }
+
+ req.user.monitoredUsers.push(req.body.userId);
+ await req.user.save();
+ res.status(204).send();
+ }
+ },
+ delete: {
+ middlewares: [requireMonitor],
+ description: 'Remove an assigned user by ID',
+ async method(req, res) {
+ // noinspection EqualityComparisonWithCoercionJS
+ req.user.monitoredUsers = req.user.monitoredUsers.filter(
+ userId => userId != req.body.userId
+ );
+ await req.user.save();
+ res.status(204).send();
+ }
+ }
+ }
+};
diff --git a/src/api/routes/auth/accountRoutes.js b/src/api/routes/auth/accountRoutes.js
index 547110e..34592ed 100644
--- a/src/api/routes/auth/accountRoutes.js
+++ b/src/api/routes/auth/accountRoutes.js
@@ -2,6 +2,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';
+import { requireAuth } from '#api/middlewares/index.js';
/**
* @type {RouteDescription}
@@ -101,6 +102,7 @@ export const whoAmI = {
methods: {
get: new RouteMethod({
description: 'Get current user',
+ middlewares: [requireAuth],
async method(req, res) {
const data = await WhoAmIDto.create({
userId: req.auth.sub,
diff --git a/src/api/routes/index.js b/src/api/routes/index.js
index 4feeb11..bc6c853 100644
--- a/src/api/routes/index.js
+++ b/src/api/routes/index.js
@@ -4,3 +4,5 @@ export * from './indexRoute.js';
export * from './auth/index.js';
export * from './budgetRoutes.js';
export * from './alarmRoutes.js';
+export * from './assignedUserRoutes.js';
+export * from './adminRoutes.js';
diff --git a/src/api/routes/statusRoute.js b/src/api/routes/statusRoute.js
index 2c111a8..8ccbf7d 100644
--- a/src/api/routes/statusRoute.js
+++ b/src/api/routes/statusRoute.js
@@ -13,7 +13,7 @@ export const statusRoute = {
const status = {
status: 'ok',
timestamp: new Date().toISOString(),
- users: await User.countDocuments()
+ users: await DbUser.countDocuments()
};
res.status(200).json(status);
diff --git a/src/db/db.js b/src/db/db.js
index 2035731..9cbba80 100644
--- a/src/db/db.js
+++ b/src/db/db.js
@@ -7,7 +7,7 @@ export async function initDb() {
process.env['DATABASE_SECRET_PATH']
);
- if (process.env['LOG_QUERIES']) mongoose.set('debug', true);
+ if (process.env['LOG_QUERIES'] === 'true') mongoose.set('debug', true);
try {
const res = await connect(connectionString);
diff --git a/src/db/dbAccess/user.js b/src/db/dbAccess/user.js
index 3bb06b6..69a83e4 100644
--- a/src/db/dbAccess/user.js
+++ b/src/db/dbAccess/user.js
@@ -16,7 +16,6 @@ export async function getUserById(id) {
});
}
- console.log(user);
return user;
}
@@ -32,7 +31,6 @@ async function getUserByAuth(data) {
user = await DbUser.findOne({ username: data.username });
}
- console.log('user', user);
if (!user) {
// Sneaky: prevent user enumeration
throw new SafeNSoundError({
@@ -60,7 +58,7 @@ export async function registerUser(data) {
if (!(data instanceof RegisterDto))
throw new Error('Invalid data type. Expected RegisterDto.');
- const salt = await genSalt(12);
+ const salt = await genSalt(1);
const passwordHash = await hash(data.password, salt);
if (!passwordHash) {
throw new Error('Failed to hash password.');
@@ -88,7 +86,7 @@ export async function deleteUser(data) {
export async function loginUser(data, deviceName) {
const user = await getUserByAuth(data);
const device = await user.devices.create({
- name: deviceName
+ name: deviceName ?? 'Unknown Device'
});
user.devices.push(device);
diff --git a/src/db/schemas/user.js b/src/db/schemas/user.js
index 7680319..7a4b2f4 100644
--- a/src/db/schemas/user.js
+++ b/src/db/schemas/user.js
@@ -30,18 +30,21 @@ export const deviceSchema = new Schema({
}
});
-export const alarmSchema = new Schema({
- createdAt: {
- type: Date,
- default: Date.now,
- immutable: true
+export const alarmSchema = new Schema(
+ {
+ reason: {
+ type: String,
+ enum: Object.values(AlarmType),
+ required: true
+ }
},
- reason: {
- type: String,
- enum: Object.values(AlarmType),
- required: true
+ {
+ timestamps: {
+ createdAt: true,
+ updatedAt: false
+ }
}
-});
+);
/**
* User schema for MongoDB.
diff --git a/src/dto/AlarmDto.js b/src/dto/AlarmDto.js
index bec4e18..281311d 100644
--- a/src/dto/AlarmDto.js
+++ b/src/dto/AlarmDto.js
@@ -22,8 +22,6 @@ export class AlarmDto {
}
}
- var res = await AlarmDto.schema.validateAsync(obj);
- console.log({ res, obj });
- return res;
+ return await AlarmDto.schema.validateAsync(obj);
}
}
|