From 5d214c7b52887b594af6be497f1892baf7d77679 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Wed, 12 Jun 2024 17:10:48 -0400 Subject: [PATCH] feat: add /share/file-by-username endpoint --- packages/backend/src/api/APIError.js | 6 ++ packages/backend/src/routers/share.js | 90 +++++++++++++++++++ packages/backend/src/services/EmailService.js | 12 ++- .../backend/src/services/PuterAPIService.js | 1 + .../src/services/auth/PermissionService.js | 20 +++++ 5 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 packages/backend/src/routers/share.js diff --git a/packages/backend/src/api/APIError.js b/packages/backend/src/api/APIError.js index 738f3b5a7..bb0b48313 100644 --- a/packages/backend/src/api/APIError.js +++ b/packages/backend/src/api/APIError.js @@ -388,6 +388,12 @@ module.exports = class APIError { message: ({ identifier }) => `Entity not found: ${quot(identifier)}`, }, + // Share + 'user_does_not_exist': { + status: 422, + message: ({ username }) => `The user ${quot(username)} does not exist.` + }, + // Chat // TODO: specifying these errors here might be a violation // of separation of concerns. Services could register their diff --git a/packages/backend/src/routers/share.js b/packages/backend/src/routers/share.js new file mode 100644 index 000000000..32ad004be --- /dev/null +++ b/packages/backend/src/routers/share.js @@ -0,0 +1,90 @@ +const express = require('express'); +const { Endpoint } = require('../util/expressutil'); + +const validator = require('validator'); +const APIError = require('../api/APIError'); +const { get_user } = require('../helpers'); +const { Context } = require('../util/context'); +const auth2 = require('../middleware/auth2'); +const config = require('../config'); + +const { PermissionUtil } = require('../services/auth/PermissionService'); + +const uuidv4 = require('uuid').v4; + +const router = express.Router(); +router.use(auth2); + +Endpoint({ + route: '/file-by-username', + methods: ['POST'], + handler: async (req, res) => { + const svc_token = req.services.get('token'); + const svc_email = req.services.get('email'); + const svc_permission = req.services.get('permission'); + + console.log('which actor exists?', + req.actor, + Context.get('actor')) + const actor = Context.get('actor'); + + const username = req.body.username; + if ( ! (typeof username === 'string') ) { + throw APIError.create('field_invalid', null, { + key: 'username', + expected: 'string', + got: typeof username, + }); + } + + let path = req.body.path; + if ( ! (typeof path === 'string') ) { + throw APIError.create('field_invalid', null, { + key: 'path', + expected: 'string', + got: typeof path, + }); + } + + const access_level = req.body.access_level || 'write'; + if ( ! ['read','write'].includes(access_level) ) { + throw APIError.create('field_invalid', null, { + key: 'access_level', + expected: '"read" or "write"', + }); + } + + const recipient = await get_user({ username }); + if ( ! recipient ) { + throw APIError.create('user_does_not_exist', null, { + username + }); + } + + await svc_permission.grant_user_user_permission( + actor, username, + PermissionUtil.join('fs', path, access_level), + {}, {}, + ); + + if ( path.startsWith('/') ) path = path.slice(1); + const link = `${config.origin}/show/${path}`; + + const email_values = { + link, + susername: req.user.username, + ...(recipient ? { + rusername: recipient.username, + } : {}), + }; + + const email_tmpl = recipient ? + 'share_existing_user' : 'share_new_user'; + + await svc_email.send_email({ email: recipient.email }, email_tmpl, email_values); + + res.send({}); + }, +}).attach(router); + +module.exports = router; diff --git a/packages/backend/src/services/EmailService.js b/packages/backend/src/services/EmailService.js index 207dc847a..a0859d96a 100644 --- a/packages/backend/src/services/EmailService.js +++ b/packages/backend/src/services/EmailService.js @@ -130,7 +130,17 @@ If this was not you, please contact support@puter.com immediately.

Sincerely,

Puter

` - } + }, + // TODO: revise email contents + 'share_existing_user': { + subject: 'Puter share from {{susername}}', + html: ` +

Hi there {{rusername}},

+

{{link}}

+

Sincerely,

+

Puter

+ ` + }, } class Emailservice extends BaseService { diff --git a/packages/backend/src/services/PuterAPIService.js b/packages/backend/src/services/PuterAPIService.js index f8191a320..21b499d09 100644 --- a/packages/backend/src/services/PuterAPIService.js +++ b/packages/backend/src/services/PuterAPIService.js @@ -76,6 +76,7 @@ class PuterAPIService extends BaseService { app.use(require('../routers/healthcheck')) app.use(require('../routers/test')) app.use(require('../routers/update-taskbar-items')) + app.use('/share', require('../routers/share')); require('../routers/whoami')(app); } diff --git a/packages/backend/src/services/auth/PermissionService.js b/packages/backend/src/services/auth/PermissionService.js index 0819050e7..498534d4f 100644 --- a/packages/backend/src/services/auth/PermissionService.js +++ b/packages/backend/src/services/auth/PermissionService.js @@ -158,6 +158,19 @@ class PermissionUtil { } return unescaped_str; } + + static escape_permission_component (component) { + let escaped_str = ''; + for ( let i = 0 ; i < component.length ; i++ ) { + const c = component[i]; + if ( c === ':' ) { + escaped_str += '\\C'; + continue; + } + escaped_str += c; + } + return escaped_str; + } static split (permission) { return permission @@ -165,6 +178,13 @@ class PermissionUtil { .map(PermissionUtil.unescape_permission_component) ; } + + static join (...components) { + return components + .map(PermissionUtil.escape_permission_component) + .join(':') + ; + } } class PermissionService extends BaseService {