mirror of
https://github.com/HeyPuter/puter.git
synced 2026-09-26 23:26:04 +00:00
Merge branch 'main' of https://github.com/HeyPuter/puter into main
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# Comment Prefixes
|
||||
|
||||
Comments have prefixes using
|
||||
[Conventional: Comments](https://conventionalcomments.org/)
|
||||
as a **loose** guideline, and using this markdown file as a
|
||||
the actual guideline.
|
||||
|
||||
This document will be updated on an _as-needed_ basis.
|
||||
|
||||
## The rules
|
||||
|
||||
- A comment line always looks like this:
|
||||
- A whitespace character
|
||||
- Optional prefix matching `/[a-z-]+\([a-z-]a+\):/`
|
||||
- A whitespace character
|
||||
- The comment
|
||||
- Formalized prefixes must follow the rules below
|
||||
- Any other prefix can be used. After some uses it
|
||||
might be good to formalize it, but that's not a hard rule.
|
||||
|
||||
## Formalized prefixes
|
||||
|
||||
- `todo:` is interchangable with the famous `TODO:`, **except:**
|
||||
when lowercase (`todo:`) it can include a scope: `todo(security):`.
|
||||
- `track:` is used to track common patterns.
|
||||
- Anything written after `track:` must be registered in
|
||||
[track-comments.md](../devmeta/track-comments.md)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Track Comments
|
||||
|
||||
Comments beginning with `// track:`. See
|
||||
[comment_prefixes.md](../contributors/comment_prefixes.md)
|
||||
|
||||
## Track Comment Registry
|
||||
|
||||
- `track: type check`:
|
||||
A condition that's used to check the type of an imput.
|
||||
- `track: bounds check`:
|
||||
A condition that's used to check the bounds of an array
|
||||
or other list-like entity.
|
||||
- `track: ruleset`
|
||||
A series of conditions that early-return or `continue`
|
||||
- `track: object description in comment`
|
||||
A comment above the creation of some object which
|
||||
could potentially have a `description` property.
|
||||
This is especially relevant if the object is stored
|
||||
in some kind of registry where multiple objects
|
||||
could be listed in the console.
|
||||
- `track: slice a prefix`
|
||||
A common pattern where a prefix string is "sliced off"
|
||||
of another string to obtain a significant value, such
|
||||
as an indentifier.
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
const BaseService = require("../services/BaseService");
|
||||
|
||||
class Library extends BaseService {
|
||||
//
|
||||
}
|
||||
|
||||
module.exports = Library;
|
||||
@@ -33,7 +33,7 @@ const { Context } = require('../util/context');
|
||||
module.exports = class FSOperationContext {
|
||||
// TODO: rename this.fs to this.access
|
||||
constructor (op_name, context, options) {
|
||||
// TRACK: fs:create-service
|
||||
// migration: fs:create-service
|
||||
// TODO: rename this.fs to this.access
|
||||
// NOTE: the 2nd parameter of this constructor
|
||||
// was called `fs` and was expected to be FSAccessContext.
|
||||
@@ -75,7 +75,7 @@ module.exports = class FSOperationContext {
|
||||
this.doneReject = reject;
|
||||
});
|
||||
|
||||
// TRACK: arch:trace-service:move-outta-fs
|
||||
// migration: arch:trace-service:move-outta-fs
|
||||
if ( this.fs.traceService ) {
|
||||
// Set 'span_' to current active span
|
||||
const { context, trace } = require('@opentelemetry/api');
|
||||
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
Reference in New Issue
Block a user