diff --git a/doc/devmeta/track-comments.md b/doc/devmeta/track-comments.md index 916d4581..91e43bf6 100644 --- a/doc/devmeta/track-comments.md +++ b/doc/devmeta/track-comments.md @@ -34,3 +34,15 @@ Comments beginning with `// track:`. See or there's a common behavior that should happen in between. The Sequence class is good for this so it might be a worthy migration. +- `track: opposite condition of sibling` + A sibling class, function, method, or other construct of + source code has a boolean expression which always evaluates + to the opposite of the one below this track comment. +- `track: null check before processing` + An object could be undefined or null, additional processing + occurs after a null check, and the unprocessed object is not + relevant to the rest of the code. If the code for obtaining + the object and processing it is moved to a function outside, + then the null check should result in a early return of null; + this code with the track comment may have additional logic + for the null/undefined case. diff --git a/packages/backend/src/api/APIError.js b/packages/backend/src/api/APIError.js index 20abbeee..c546f456 100644 --- a/packages/backend/src/api/APIError.js +++ b/packages/backend/src/api/APIError.js @@ -462,6 +462,11 @@ module.exports = class APIError { status: 422, message: 'Email must be confirmed to apply a share.', }, + 'no_need_to_request': { + status: 422, + message: 'This share is already valid for this user; ' + + 'POST to /apply for access.' + }, 'can_not_apply_to_this_user': { status: 422, message: 'This share can not be applied to this user.', diff --git a/packages/backend/src/services/ShareService.js b/packages/backend/src/services/ShareService.js index 6caaee6f..f7b6a6b9 100644 --- a/packages/backend/src/services/ShareService.js +++ b/packages/backend/src/services/ShareService.js @@ -6,6 +6,7 @@ const { whatis } = require("../util/langutil"); const { Actor, UserActorType } = require("./auth/Actor"); const BaseService = require("./BaseService"); const { DB_WRITE } = require("./database/consts"); +const { UsernameNotifSelector } = require("./NotificationService"); class ShareService extends BaseService { static MODULES = { @@ -85,16 +86,16 @@ class ShareService extends BaseService { uid: share_uid, }); + if ( ! share ) { + throw APIError.create('share_expired'); + } + share.data = this.db.case({ mysql: () => share.data, otherwise: () => JSON.parse(share.data ?? '{}'), })(); - if ( ! share ) { - throw APIError.create('share_expired'); - } - const actor = Actor.adapt(req.actor ?? req.user); if ( ! actor ) { // this shouldn't happen; auth should catch it @@ -137,6 +138,74 @@ class ShareService extends BaseService { }); } }).attach(router); + + Endpoint({ + route: '/request', + methods: ['POST'], + mw: [configurable_auth()], + handler: async (req, res) => { + const share_uid = req.body.uid; + + const share = await svc_share.get_share({ + uid: share_uid, + }); + + // track: null check before processing + if ( ! share ) { + throw APIError.create('share_expired'); + } + + share.data = this.db.case({ + mysql: () => share.data, + otherwise: () => + JSON.parse(share.data ?? '{}'), + })(); + + const actor = Actor.adapt(req.actor ?? req.user); + if ( ! actor ) { + // this shouldn't happen; auth should catch it + throw new Error('actor missing'); + } + + // track: opposite condition of sibling + // :: sibling: /apply endpoint + if ( + actor.type.user.email_confirmed && + actor.type.user.email === share.recipient_email + ) { + throw APIError.create('no_need_to_request'); + } + + const issuer_user = await get_user({ + id: share.issuer_user_id, + }); + + if ( ! issuer_user ) { + throw APIError.create('share_expired'); + } + + const svc_notification = this.services.get('notification'); + svc_notification.notify( + UsernameNotifSelector(issuer_user.username), + { + source: 'sharing', + title: `User ${actor.type.user.username} is ` + + `trying to open a share you sent to ` + + share.recipient_email, + template: 'user-requesting-share', + fields: { + username: actor.type.user.username, + intended_recipient: share.recipient_email, + permissions: share.data.permissions, + }, + } + ); + res.json({ + $: 'api:status-report', + status: 'success', + }); + } + }).attach(router); } async get_share ({ uid }) { diff --git a/packages/backend/src/services/drivers/DriverService.js b/packages/backend/src/services/drivers/DriverService.js index 70b60deb..8dbd898e 100644 --- a/packages/backend/src/services/drivers/DriverService.js +++ b/packages/backend/src/services/drivers/DriverService.js @@ -34,6 +34,11 @@ class DriverService extends BaseService { this.interfaces = require('./interfaces'); this.interface_to_implementation = {}; } + + _init () { + const svc_registry = this.services.get('registry'); + svc_registry.register_collection(''); + } register_driver (interface_name, implementation) { this.interface_to_implementation[interface_name] = implementation;