summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/middlewares/authMiddleware.js23
-rw-r--r--src/api/middlewares/errorMiddleware.js1
-rw-r--r--src/api/routes/adminRoutes.js55
-rw-r--r--src/api/routes/alarmRoutes.js38
-rw-r--r--src/api/routes/assignedUserRoutes.js56
-rw-r--r--src/api/routes/auth/accountRoutes.js2
-rw-r--r--src/api/routes/index.js2
-rw-r--r--src/api/routes/statusRoute.js2
-rw-r--r--src/db/db.js2
-rw-r--r--src/db/dbAccess/user.js6
-rw-r--r--src/db/schemas/user.js23
-rw-r--r--src/dto/AlarmDto.js4
12 files changed, 177 insertions, 37 deletions
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); } }