mirror of
https://github.com/HeyPuter/puter.git
synced 2025-02-02 23:28:39 +08:00
feat: add protected apps
This commit is contained in:
parent
7006dcc31c
commit
f2f3d6ff46
@ -18,6 +18,7 @@
|
||||
*/
|
||||
const { AdvancedBase } = require("@heyputer/puter-js-common");
|
||||
const { NotificationES } = require("./om/entitystorage/NotificationES");
|
||||
const { ProtectedAppES } = require("./om/entitystorage/ProtectedAppES");
|
||||
const { Context } = require('./util/context');
|
||||
|
||||
|
||||
@ -51,6 +52,12 @@ const install = async ({ services, app, useapi }) => {
|
||||
|
||||
def('puter.middlewares.auth', require('./middleware/auth2'));
|
||||
});
|
||||
|
||||
// === LIBRARIES ===
|
||||
const ArrayUtil = require('./libraries/ArrayUtil');
|
||||
services.registerService('util-array', ArrayUtil);
|
||||
|
||||
// === SERVICES ===
|
||||
|
||||
// /!\ IMPORTANT /!\
|
||||
// For new services, put the import immediate above the
|
||||
@ -153,6 +160,7 @@ const install = async ({ services, app, useapi }) => {
|
||||
WriteByOwnerOnlyES,
|
||||
ValidationES,
|
||||
SetOwnerES,
|
||||
ProtectedAppES,
|
||||
MaxLimitES, { max: 5000 },
|
||||
]),
|
||||
});
|
||||
@ -269,6 +277,9 @@ const install = async ({ services, app, useapi }) => {
|
||||
|
||||
const { NotificationService } = require('./services/NotificationService');
|
||||
services.registerService('notification', NotificationService);
|
||||
|
||||
const { ProtectedAppService } = require('./services/ProtectedAppService');
|
||||
services.registerService('__protected-app', ProtectedAppService);
|
||||
}
|
||||
|
||||
const install_legacy = async ({ services }) => {
|
||||
|
7
packages/backend/src/definitions/Library.js
Normal file
7
packages/backend/src/definitions/Library.js
Normal file
@ -0,0 +1,7 @@
|
||||
const BaseService = require("../services/BaseService");
|
||||
|
||||
class Library extends BaseService {
|
||||
//
|
||||
}
|
||||
|
||||
module.exports = Library;
|
77
packages/backend/src/libraries/ArrayUtil.js
Normal file
77
packages/backend/src/libraries/ArrayUtil.js
Normal file
@ -0,0 +1,77 @@
|
||||
const Library = require("../definitions/Library");
|
||||
|
||||
class ArrayUtil extends Library {
|
||||
/**
|
||||
*
|
||||
* @param {*} marked_map
|
||||
* @param {*} subject
|
||||
*/
|
||||
remove_marked_items (marked_map, subject) {
|
||||
for ( let i=0 ; i < marked_map.length ; i++ ) {
|
||||
let ii = marked_map[i];
|
||||
// track: type check
|
||||
if ( ! Number.isInteger(ii) ) {
|
||||
throw new Error(
|
||||
'marked_map can only contain integers'
|
||||
);
|
||||
}
|
||||
// track: bounds check
|
||||
if ( ii < 0 && ii >= subject.length ) {
|
||||
throw new Error(
|
||||
'each item in `marked_map` must be within that bounds ' +
|
||||
'of `subject`'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
marked_map.sort((a, b) => b - a);
|
||||
|
||||
for ( let i=0 ; i < marked_map.length ; i++ ) {
|
||||
let ii = marked_map[i];
|
||||
subject.splice(ii, 1);
|
||||
}
|
||||
|
||||
return subject;
|
||||
}
|
||||
|
||||
_test ({ assert }) {
|
||||
// inner indices
|
||||
{
|
||||
const subject = [
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
|
||||
// 0 1 2 3 4 5 6 7
|
||||
const marked_map = [2, 5];
|
||||
this.remove_marked_items(marked_map, subject);
|
||||
assert(() => subject.join('') === 'abdegh');
|
||||
}
|
||||
// left edge
|
||||
{
|
||||
const subject = [
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
|
||||
// 0 1 2 3 4 5 6 7
|
||||
const marked_map = [0]
|
||||
this.remove_marked_items(marked_map, subject);
|
||||
assert(() => subject.join('') === 'bcdefgh');
|
||||
}
|
||||
// right edge
|
||||
{
|
||||
const subject = [
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
|
||||
// 0 1 2 3 4 5 6 7
|
||||
const marked_map = [7]
|
||||
this.remove_marked_items(marked_map, subject);
|
||||
assert(() => subject.join('') === 'abcdefg');
|
||||
}
|
||||
// both edges
|
||||
{
|
||||
const subject = [
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
|
||||
// 0 1 2 3 4 5 6 7
|
||||
const marked_map = [0, 7]
|
||||
this.remove_marked_items(marked_map, subject);
|
||||
assert(() => subject.join('') === 'bcdefg');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ArrayUtil;
|
82
packages/backend/src/om/entitystorage/ProtectedAppES.js
Normal file
82
packages/backend/src/om/entitystorage/ProtectedAppES.js
Normal file
@ -0,0 +1,82 @@
|
||||
const { AppUnderUserActorType, UserActorType } = require("../../services/auth/Actor");
|
||||
const { Context } = require("../../util/context");
|
||||
const { BaseES } = require("./BaseES");
|
||||
|
||||
class ProtectedAppES extends BaseES {
|
||||
async select (options){
|
||||
const results = await this.upstream.select(options);
|
||||
|
||||
const actor = Context.get('actor');
|
||||
const services = Context.get('services');
|
||||
|
||||
const to_delete = [];
|
||||
for ( let i=0 ; i < results.length ; i++ ) {
|
||||
const entity = results[i];
|
||||
|
||||
if ( ! await this.check_({ actor, services }, entity) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
to_delete.push(i);
|
||||
}
|
||||
|
||||
const svc_utilArray = services.get('util-array');
|
||||
svc_utilArray.remove_marked_items(to_delete, results);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async read (uid){
|
||||
const entity = await this.upstream.read(uid);
|
||||
if ( ! entity ) return null;
|
||||
|
||||
const actor = Context.get('actor');
|
||||
const services = Context.get('services');
|
||||
|
||||
if ( await this.check_({ actor, services }, entity) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if the entity should not be sent downstream
|
||||
*/
|
||||
async check_ ({ actor, services }, entity) {
|
||||
// track: ruleset
|
||||
{
|
||||
// if it's not a protected app, no worries
|
||||
if ( ! await entity.get('protected') ) return;
|
||||
|
||||
// if actor is this app, no worries
|
||||
if (
|
||||
actor.type instanceof AppUnderUserActorType &&
|
||||
await entity.get('uid') === actor.type.app.uid
|
||||
) return;
|
||||
|
||||
// if actor is owner of this app, no worries
|
||||
if (
|
||||
actor.type instanceof UserActorType &&
|
||||
(await entity.get('owner')).id === actor.type.user.id
|
||||
) return;
|
||||
}
|
||||
|
||||
// now we need to check for permission
|
||||
const app_uid = await entity.get('uid');
|
||||
const svc_permission = services.get('permission');
|
||||
const permission_to_check = `app:uid#${app_uid}:access`;
|
||||
const perm = await svc_permission.check(
|
||||
actor, permission_to_check,
|
||||
);
|
||||
|
||||
if ( perm ) return;
|
||||
|
||||
// `true` here means "do not send downstream"
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
ProtectedAppES,
|
||||
};
|
44
packages/backend/src/services/ProtectedAppService.js
Normal file
44
packages/backend/src/services/ProtectedAppService.js
Normal file
@ -0,0 +1,44 @@
|
||||
const { get_app } = require("../helpers");
|
||||
const { UserActorType } = require("./auth/Actor");
|
||||
const { PermissionImplicator, PermissionUtil } = require("./auth/PermissionService");
|
||||
const BaseService = require("./BaseService");
|
||||
|
||||
class ProtectedAppService extends BaseService {
|
||||
async _init () {
|
||||
const svc_permission = this.services.get('permission');
|
||||
|
||||
// track: object description in comment
|
||||
// Owner of procted app has implicit permission to access it
|
||||
svc_permission.register_implicator(PermissionImplicator.create({
|
||||
matcher: permission => {
|
||||
return permission.startsWith('app:');
|
||||
},
|
||||
checker: async ({ actor, permission }) => {
|
||||
if ( !(actor.type instanceof UserActorType) ) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parts = PermissionUtil.split(permission);
|
||||
if ( parts.length !== 3 ) return undefined;
|
||||
|
||||
const [_, uid_part, lvl] = parts;
|
||||
if ( lvl !== 'access' ) return undefined;
|
||||
|
||||
// track: slice a prefix
|
||||
const uid = uid_part.slice('uid#'.length);
|
||||
|
||||
const app = await get_app({ uid });
|
||||
|
||||
if ( app.owner_user_id !== actor.type.user.id ) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ProtectedAppService,
|
||||
};
|
Loading…
Reference in New Issue
Block a user