mirror of
https://github.com/HeyPuter/puter.git
synced 2025-02-02 14:18:43 +08:00
feat: update share endpoint to support more things
This commit is contained in:
parent
18f9959776
commit
dd5fde5130
@ -30,9 +30,10 @@ anywhere.
|
||||
## Specification
|
||||
|
||||
- The `"$"` key indicates a type (or class) of object
|
||||
- Any key beginning with `$` is a **meta-key**
|
||||
- Any other key beginning with `$` is a **meta-key**
|
||||
- Other keys are not allowed to contain `$`
|
||||
- `"$version"` must follow [semver](https://semver.org/)
|
||||
- Keys with multiple `"$"` symbols are reserved for future use
|
||||
|
||||
## Alternative Representations
|
||||
|
||||
|
@ -57,6 +57,9 @@ const install = async ({ services, app, useapi }) => {
|
||||
const ArrayUtil = require('./libraries/ArrayUtil');
|
||||
services.registerService('util-array', ArrayUtil);
|
||||
|
||||
const LibTypeTagged = require('./libraries/LibTypeTagged');
|
||||
services.registerService('lib-type-tagged', LibTypeTagged);
|
||||
|
||||
// === SERVICES ===
|
||||
|
||||
// /!\ IMPORTANT /!\
|
||||
|
@ -33,6 +33,29 @@ module.exports = class APIError {
|
||||
status: 500,
|
||||
message: () => `An unknown error occurred`,
|
||||
},
|
||||
'format_error': {
|
||||
status: 400,
|
||||
message: ({ message }) => `format error: ${message}`,
|
||||
},
|
||||
'temp_error': {
|
||||
status: 400,
|
||||
message: ({ message }) => `error: ${message}`,
|
||||
},
|
||||
|
||||
// Things
|
||||
'disallowed_thing': {
|
||||
status: 400,
|
||||
message: ({ thing_type, accepted }) =>
|
||||
`Request contained a ${quot(thing_type)} in a ` +
|
||||
`place where ${quot(thing_type)} isn't accepted` +
|
||||
(
|
||||
accepted
|
||||
? '; ' +
|
||||
'accepted types are: ' +
|
||||
accepted.map(v => quot(v)).join(', ')
|
||||
: ''
|
||||
) + '.'
|
||||
},
|
||||
|
||||
// Unorganized
|
||||
'item_with_same_name_exists': {
|
||||
|
88
packages/backend/src/libraries/LibTypeTagged.js
Normal file
88
packages/backend/src/libraries/LibTypeTagged.js
Normal file
@ -0,0 +1,88 @@
|
||||
const Library = require("../definitions/Library");
|
||||
const { whatis } = require("../util/langutil");
|
||||
|
||||
class LibTypeTagged extends Library {
|
||||
process (o) {
|
||||
const could_be = whatis(o) === 'object' || Array.isArray(o);
|
||||
if ( ! could_be ) return {
|
||||
$: 'error',
|
||||
code: 'invalid-type',
|
||||
message: 'should be object or array',
|
||||
};
|
||||
|
||||
const intermediate = this.get_intermediate_(o);
|
||||
|
||||
if ( ! intermediate.type ) return {
|
||||
$: 'error',
|
||||
code: 'missing-type-param',
|
||||
message: 'type parameter is missing',
|
||||
};
|
||||
|
||||
return this.intermediate_to_standard_(intermediate);
|
||||
}
|
||||
|
||||
intermediate_to_standard_ (intermediate) {
|
||||
const out = {};
|
||||
out.$ = intermediate.type;
|
||||
for ( const k in intermediate.meta ) {
|
||||
out['$' + k] = intermediate.meta[k];
|
||||
}
|
||||
for ( const k in intermediate.body ) {
|
||||
out[k] = intermediate.body[k];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
get_intermediate_ (o) {
|
||||
if ( Array.isArray(o) ) {
|
||||
return this.process_array_(o);
|
||||
}
|
||||
|
||||
if ( o['$'] === '$meta-body' ) {
|
||||
return this.process_structured_(o);
|
||||
}
|
||||
|
||||
return this.process_standard_(o);
|
||||
}
|
||||
|
||||
process_array_ (a) {
|
||||
if ( a.length <= 1 || a.length > 3 ) return {
|
||||
$: 'error',
|
||||
code: 'invalid-array-length',
|
||||
message: 'tag-typed arrays should have 1-3 elements',
|
||||
};
|
||||
|
||||
const [type, body = {}, meta = {}] = a;
|
||||
|
||||
return { $: '$', type, body, meta };
|
||||
}
|
||||
|
||||
process_structured_ (o) {
|
||||
if ( ! o.hasOwnProperty('type') ) return {
|
||||
$: 'error',
|
||||
code: 'missing-type-property',
|
||||
message: 'missing "type" property'
|
||||
};
|
||||
|
||||
return { $: '$', ...o };
|
||||
}
|
||||
|
||||
process_standard_ (o) {
|
||||
const type = o.$;
|
||||
const meta = {};
|
||||
const body = {};
|
||||
|
||||
for ( const k in o ) {
|
||||
if ( k === '$' ) continue;
|
||||
if ( k.startsWith('$') ) {
|
||||
meta[k.slice(1)] = o[k];
|
||||
} else {
|
||||
body[k] = o[k];
|
||||
}
|
||||
}
|
||||
|
||||
return { $: '$', type, meta, body };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LibTypeTagged;
|
@ -3,7 +3,7 @@ const { Endpoint } = require('../util/expressutil');
|
||||
|
||||
const validator = require('validator');
|
||||
const APIError = require('../api/APIError');
|
||||
const { get_user } = require('../helpers');
|
||||
const { get_user, get_app } = require('../helpers');
|
||||
const { Context } = require('../util/context');
|
||||
const auth2 = require('../middleware/auth2');
|
||||
const config = require('../config');
|
||||
@ -140,6 +140,8 @@ const v0_2 = async (req, res) => {
|
||||
const svc_permission = req.services.get('permission');
|
||||
const svc_notification = req.services.get('notification');
|
||||
|
||||
const lib_typeTagged = req.services.get('lib-type-tagged');
|
||||
|
||||
const actor = Context.get('actor');
|
||||
|
||||
// === Request Validators ===
|
||||
@ -170,12 +172,12 @@ const v0_2 = async (req, res) => {
|
||||
return recipients;
|
||||
});
|
||||
|
||||
const validate_paths = UtilFn(paths => {
|
||||
const validate_shares = UtilFn(shares => {
|
||||
// Single-values get adapted into an array
|
||||
if ( ! Array.isArray(paths) ) {
|
||||
paths = [paths];
|
||||
if ( ! Array.isArray(shares) ) {
|
||||
shares = [shares];
|
||||
}
|
||||
return paths;
|
||||
return shares;
|
||||
})
|
||||
|
||||
// === Request Values ===
|
||||
@ -184,8 +186,8 @@ const v0_2 = async (req, res) => {
|
||||
validate_mode.if(req.body.mode) ?? false;
|
||||
const req_recipients =
|
||||
validate_recipients.if(req.body.recipients) ?? [];
|
||||
const req_paths =
|
||||
validate_paths.if(req.body.paths) ?? [];
|
||||
const req_shares =
|
||||
validate_shares.if(req.body.shares) ?? [];
|
||||
|
||||
// === State Values ===
|
||||
|
||||
@ -198,10 +200,10 @@ const v0_2 = async (req, res) => {
|
||||
// Results
|
||||
status: null,
|
||||
recipients: Array(req_recipients.length).fill(null),
|
||||
paths: Array(req_paths.length).fill(null),
|
||||
shares: Array(req_shares.length).fill(null),
|
||||
}
|
||||
const recipients_work = new WorkList();
|
||||
const fsitems_work = new WorkList();
|
||||
const shares_work = new WorkList();
|
||||
|
||||
// const assert_work_item = (wut, item) => {
|
||||
// if ( item.$ !== wut ) {
|
||||
@ -221,11 +223,11 @@ const v0_2 = async (req, res) => {
|
||||
result.recipients[i] = result.recipients[i].serialize();
|
||||
}
|
||||
}
|
||||
for ( let i=0 ; i < result.paths.length ; i++ ) {
|
||||
if ( ! result.paths[i] ) continue;
|
||||
if ( result.paths[i] instanceof APIError ) {
|
||||
for ( let i=0 ; i < result.shares.length ; i++ ) {
|
||||
if ( ! result.shares[i] ) continue;
|
||||
if ( result.shares[i] instanceof APIError ) {
|
||||
result.status = 'mixed';
|
||||
result.paths[i] = result.paths[i].serialize();
|
||||
result.shares[i] = result.shares[i].serialize();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -234,7 +236,7 @@ const v0_2 = async (req, res) => {
|
||||
console.log('OK');
|
||||
if (
|
||||
result.recipients.some(v => v !== null) ||
|
||||
result.paths.some(v => v !== null)
|
||||
result.shares.some(v => v !== null)
|
||||
) {
|
||||
console.log('DOESNT THIS??')
|
||||
serialize_result();
|
||||
@ -329,74 +331,104 @@ const v0_2 = async (req, res) => {
|
||||
// --- Process Paths ---
|
||||
|
||||
// Expect: at least one path
|
||||
if ( req_paths.length < 1 ) {
|
||||
if ( req_shares.length < 1 ) {
|
||||
throw APIError.create('field_invalid', null, {
|
||||
key: 'paths',
|
||||
key: 'shares',
|
||||
expected: 'at least one',
|
||||
got: 'none',
|
||||
})
|
||||
}
|
||||
|
||||
for ( let i=0 ; i < req_paths.length ; i++ ) {
|
||||
const value = req_paths[i];
|
||||
fsitems_work.push({ i, value });
|
||||
for ( let i=0 ; i < req_shares.length ; i++ ) {
|
||||
const value = req_shares[i];
|
||||
shares_work.push({ i, value });
|
||||
}
|
||||
fsitems_work.lockin();
|
||||
shares_work.lockin();
|
||||
|
||||
for ( const item of fsitems_work.list() ) {
|
||||
for ( const item of shares_work.list() ) {
|
||||
const { i } = item;
|
||||
let { value } = item;
|
||||
|
||||
// adapt all strings to objects
|
||||
if ( typeof value === 'string' ) {
|
||||
value = { path: value };
|
||||
}
|
||||
|
||||
if ( whatis(value) !== 'object' ) {
|
||||
const thing = lib_typeTagged.process(value);
|
||||
if ( thing.$ === 'error' ) {
|
||||
item.invalid = true;
|
||||
result.paths[i] =
|
||||
APIError.create('invalid_path', null, {
|
||||
path: item.path,
|
||||
value,
|
||||
result.shares[i] =
|
||||
APIError.create('format_error', null, {
|
||||
message: thing.message
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const errors = [];
|
||||
if ( ! value.path ) {
|
||||
errors.push('`path` is required');
|
||||
console.log('thing?', thing);
|
||||
|
||||
const allowed_things = ['fs-share', 'app-share'];
|
||||
if ( ! allowed_things.includes(thing.$) ) {
|
||||
APIError.create('disallowed_thing', null, {
|
||||
thing: thing.$,
|
||||
accepted: allowed_things,
|
||||
})
|
||||
}
|
||||
let access = value.access;
|
||||
if ( access ) {
|
||||
if ( ! ['read','write'].includes(access) ) {
|
||||
errors.push('`access` should be `read` or `write`');
|
||||
|
||||
if ( thing.$ === 'fs-share' ) {
|
||||
item.type = 'fs';
|
||||
const errors = [];
|
||||
if ( ! thing.path ) {
|
||||
errors.push('`path` is required');
|
||||
}
|
||||
} else access = 'read';
|
||||
let access = thing.access;
|
||||
if ( access ) {
|
||||
if ( ! ['read','write'].includes(access) ) {
|
||||
errors.push('`access` should be `read` or `write`');
|
||||
}
|
||||
} else access = 'read';
|
||||
|
||||
if ( errors.length ) {
|
||||
item.invalid = true;
|
||||
result.paths[item.i] =
|
||||
APIError.create('field_errors', null, {
|
||||
path: item.path,
|
||||
errors
|
||||
});
|
||||
continue;
|
||||
if ( errors.length ) {
|
||||
item.invalid = true;
|
||||
result.shares[item.i] =
|
||||
APIError.create('field_errors', null, {
|
||||
key: `shares[${item.i}]`,
|
||||
errors
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
item.path = thing.path;
|
||||
item.permission = PermissionUtil.join('fs', thing.path, access);
|
||||
}
|
||||
|
||||
item.path = value.path;
|
||||
item.permission = PermissionUtil.join('fs', value.path, access);
|
||||
if ( thing.$ === 'app-share' ) {
|
||||
item.type = 'app';
|
||||
const errors = [];
|
||||
if ( ! thing.uid && thing.name ) {
|
||||
errors.push('`uid` or `name` is required');
|
||||
}
|
||||
|
||||
if ( errors.length ) {
|
||||
item.invalid = true;
|
||||
result.shares[item.i] =
|
||||
APIError.create('field_errors', null, {
|
||||
key: `shares[${item.i}]`,
|
||||
errors
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
item.permission = PermissionUtil.join('app', thing.path, 'access');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
fsitems_work.clear_invalid();
|
||||
shares_work.clear_invalid();
|
||||
|
||||
for ( const item of fsitems_work.list() ) {
|
||||
for ( const item of shares_work.list() ) {
|
||||
if ( item.type !== 'fs' ) continue;
|
||||
const node = await (new FSNodeParam('path')).consolidate({
|
||||
req, getParam: () => item.path
|
||||
});
|
||||
|
||||
if ( ! await node.exists() ) {
|
||||
item.invalid = true;
|
||||
result.paths[item.i] = APIError.create('subject_does_not_exist', {
|
||||
result.shares[item.i] = APIError.create('subject_does_not_exist', {
|
||||
path: item.path,
|
||||
})
|
||||
continue;
|
||||
@ -417,12 +449,12 @@ const v0_2 = async (req, res) => {
|
||||
item.email_link = email_link;
|
||||
}
|
||||
|
||||
fsitems_work.clear_invalid();
|
||||
shares_work.clear_invalid();
|
||||
|
||||
// Mark files as successful; further errors will be
|
||||
// reported on recipients instead.
|
||||
for ( const item of fsitems_work.list() ) {
|
||||
result.paths[item.i] =
|
||||
for ( const item of shares_work.list() ) {
|
||||
result.shares[item.i] =
|
||||
{
|
||||
$: 'api:status-report',
|
||||
status: 'success',
|
||||
@ -452,11 +484,11 @@ const v0_2 = async (req, res) => {
|
||||
|
||||
const username = recipient_item.user.username;
|
||||
|
||||
for ( const path_item of fsitems_work.list() ) {
|
||||
for ( const share_item of shares_work.list() ) {
|
||||
await svc_permission.grant_user_user_permission(
|
||||
actor,
|
||||
username,
|
||||
path_item.permission,
|
||||
share_item.permission,
|
||||
);
|
||||
}
|
||||
|
||||
@ -478,7 +510,7 @@ const v0_2 = async (req, res) => {
|
||||
*/
|
||||
|
||||
const files = []; {
|
||||
for ( const path_item of fsitems_work.list() ) {
|
||||
for ( const path_item of shares_work.list() ) {
|
||||
files.push(
|
||||
await path_item.node.getSafeEntry(),
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user