Add UserProtectedEndpointsService

This commit is contained in:
KernelDeimos
2024-05-10 18:28:57 -04:00
parent 15dec21118
commit a89c9d59cf
5 changed files with 145 additions and 0 deletions
+3
View File
@@ -204,6 +204,9 @@ const install = async ({ services, app }) => {
const { OTPService } = require('./services/auth/OTPService');
services.registerService('otp', OTPService);
const { UserProtectedEndpointsService } = require("./services/web/UserProtectedEndpointsService");
services.registerService('__user-protected-endpoints', UserProtectedEndpointsService);
}
const install_legacy = async ({ services }) => {
+22
View File
@@ -340,6 +340,28 @@ module.exports = class APIError {
message: '2FA is already enabled.',
},
// protected endpoints
'too_many_requests': {
status: 429,
message: 'Too many requests.',
},
'user_tokens_only': {
status: 403,
message: 'This endpoint must be requested with a user session',
},
'temporary_accounts_not_allowed': {
status: 403,
message: 'Temporary accounts cannot perform this action',
},
'password_required': {
status: 400,
message: 'Password is required.',
},
'password_mismatch': {
status: 403,
message: 'Password does not match.',
},
// Object Mapping
'field_not_allowed_for_create': {
status: 400,
@@ -0,0 +1,7 @@
module.exports = {
route: '/change-password',
methods: ['POST'],
handler: async (req, res, next) => {
res.send('this is a test response');
}
};
@@ -0,0 +1,92 @@
const { get_user } = require("../../helpers");
const auth2 = require("../../middleware/auth2");
const { Context } = require("../../util/context");
const BaseService = require("../BaseService");
const { UserActorType } = require("../auth/Actor");
const { Endpoint } = require("../../util/expressutil");
const APIError = require("../../api/APIError.js");
/**
* This service registers endpoints that are protected by password authentication,
* excluding login. These endpoints are typically for actions that affect
* security settings on the user's account.
*/
class UserProtectedEndpointsService extends BaseService {
static MODULES = {
express: require('express'),
};
['__on_install.routes'] () {
const router = (() => {
const require = this.require;
const express = require('express');
return express.Router();
})()
const { app } = this.services.get('web-server');
app.use('/user-protected', router);
// Apply edge (unauthenticated) rate-limiting
router.use((req, res, next) => {
const svc_edgeRateLimit = req.services.get('edge-rate-limit');
if ( ! svc_edgeRateLimit.check(req.baseUrl + req.path) ) {
return APIError.create('too_many_requests').write(res);
}
next();
})
// Require authenticated session
router.use(auth2);
// Only allow user sessions, not API tokens for apps
router.use((req, res, next) => {
const actor = Context.get('actor');
if ( ! (actor.type instanceof UserActorType) ) {
return APIError.create('user_tokens_only').write(res);
}
next();
});
// Prioritize consistency for user object
router.use(async (req, res, next) => {
const user = await get_user({ id: req.user.id, force: true });
req.user = user;
next();
});
// Do not allow temporary users
router.use(async (req, res, next) => {
if ( req.user.password === null ) {
return APIError.create('temporary_account').write(res);
}
next();
});
// Require password in request
router.use(async (req, res, next) => {
if ( ! req.body.password ) {
return (APIError.create('password_required')).write(res);
}
const bcrypt = (() => {
const require = this.require;
return require('bcrypt');
})();
const user = await get_user({ id: req.user.id, force: true });
const isMatch = await bcrypt.compare(req.body.password, user.password);
if ( ! isMatch ) {
return APIError.create('password_mismatch').write(res);
}
next();
});
Endpoint(
require('../../routers/user-protected/change-password.js')
).attach(router);
}
}
module.exports = {
UserProtectedEndpointsService
};
+21
View File
@@ -0,0 +1,21 @@
const eggspress = require("../api/eggspress");
const Endpoint = function Endpoint (spec) {
return {
attach (route) {
const eggspress_options = {
allowedMethods: spec.methods ?? ['GET'],
};
const eggspress_router = eggspress(
spec.route,
eggspress_options,
spec.handler,
);
route.use(eggspress_router);
}
};
}
module.exports = {
Endpoint,
};