mirror of
https://github.com/HeyPuter/puter.git
synced 2026-05-06 09:30:49 +00:00
Add UserProtectedEndpointsService
This commit is contained in:
@@ -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 }) => {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
Reference in New Issue
Block a user