Skip to content

Commit f6a4413

Browse files
committed
Merge branch 'main' of https://github.com/HeyPuter/puter into main
2 parents feabbaf + 3ae0081 commit f6a4413

File tree

8 files changed

+275
-2
lines changed

8 files changed

+275
-2
lines changed

doc/contributors/comment_prefixes.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Comment Prefixes
2+
3+
Comments have prefixes using
4+
[Conventional: Comments](https://conventionalcomments.org/)
5+
as a **loose** guideline, and using this markdown file as a
6+
the actual guideline.
7+
8+
This document will be updated on an _as-needed_ basis.
9+
10+
## The rules
11+
12+
- A comment line always looks like this:
13+
- A whitespace character
14+
- Optional prefix matching `/[a-z-]+\([a-z-]a+\):/`
15+
- A whitespace character
16+
- The comment
17+
- Formalized prefixes must follow the rules below
18+
- Any other prefix can be used. After some uses it
19+
might be good to formalize it, but that's not a hard rule.
20+
21+
## Formalized prefixes
22+
23+
- `todo:` is interchangable with the famous `TODO:`, **except:**
24+
when lowercase (`todo:`) it can include a scope: `todo(security):`.
25+
- `track:` is used to track common patterns.
26+
- Anything written after `track:` must be registered in
27+
[track-comments.md](../devmeta/track-comments.md)
28+

doc/devmeta/track-comments.md

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Track Comments
2+
3+
Comments beginning with `// track:`. See
4+
[comment_prefixes.md](../contributors/comment_prefixes.md)
5+
6+
## Track Comment Registry
7+
8+
- `track: type check`:
9+
A condition that's used to check the type of an imput.
10+
- `track: bounds check`:
11+
A condition that's used to check the bounds of an array
12+
or other list-like entity.
13+
- `track: ruleset`
14+
A series of conditions that early-return or `continue`
15+
- `track: object description in comment`
16+
A comment above the creation of some object which
17+
could potentially have a `description` property.
18+
This is especially relevant if the object is stored
19+
in some kind of registry where multiple objects
20+
could be listed in the console.
21+
- `track: slice a prefix`
22+
A common pattern where a prefix string is "sliced off"
23+
of another string to obtain a significant value, such
24+
as an indentifier.

packages/backend/src/CoreModule.js

+11
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
const { AdvancedBase } = require("@heyputer/puter-js-common");
2020
const { NotificationES } = require("./om/entitystorage/NotificationES");
21+
const { ProtectedAppES } = require("./om/entitystorage/ProtectedAppES");
2122
const { Context } = require('./util/context');
2223

2324

@@ -51,6 +52,12 @@ const install = async ({ services, app, useapi }) => {
5152

5253
def('puter.middlewares.auth', require('./middleware/auth2'));
5354
});
55+
56+
// === LIBRARIES ===
57+
const ArrayUtil = require('./libraries/ArrayUtil');
58+
services.registerService('util-array', ArrayUtil);
59+
60+
// === SERVICES ===
5461

5562
// /!\ IMPORTANT /!\
5663
// For new services, put the import immediate above the
@@ -153,6 +160,7 @@ const install = async ({ services, app, useapi }) => {
153160
WriteByOwnerOnlyES,
154161
ValidationES,
155162
SetOwnerES,
163+
ProtectedAppES,
156164
MaxLimitES, { max: 5000 },
157165
]),
158166
});
@@ -269,6 +277,9 @@ const install = async ({ services, app, useapi }) => {
269277

270278
const { NotificationService } = require('./services/NotificationService');
271279
services.registerService('notification', NotificationService);
280+
281+
const { ProtectedAppService } = require('./services/ProtectedAppService');
282+
services.registerService('__protected-app', ProtectedAppService);
272283
}
273284

274285
const install_legacy = async ({ services }) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const BaseService = require("../services/BaseService");
2+
3+
class Library extends BaseService {
4+
//
5+
}
6+
7+
module.exports = Library;

packages/backend/src/filesystem/FSOperationContext.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const { Context } = require('../util/context');
3333
module.exports = class FSOperationContext {
3434
// TODO: rename this.fs to this.access
3535
constructor (op_name, context, options) {
36-
// TRACK: fs:create-service
36+
// migration: fs:create-service
3737
// TODO: rename this.fs to this.access
3838
// NOTE: the 2nd parameter of this constructor
3939
// was called `fs` and was expected to be FSAccessContext.
@@ -75,7 +75,7 @@ module.exports = class FSOperationContext {
7575
this.doneReject = reject;
7676
});
7777

78-
// TRACK: arch:trace-service:move-outta-fs
78+
// migration: arch:trace-service:move-outta-fs
7979
if ( this.fs.traceService ) {
8080
// Set 'span_' to current active span
8181
const { context, trace } = require('@opentelemetry/api');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const Library = require("../definitions/Library");
2+
3+
class ArrayUtil extends Library {
4+
/**
5+
*
6+
* @param {*} marked_map
7+
* @param {*} subject
8+
*/
9+
remove_marked_items (marked_map, subject) {
10+
for ( let i=0 ; i < marked_map.length ; i++ ) {
11+
let ii = marked_map[i];
12+
// track: type check
13+
if ( ! Number.isInteger(ii) ) {
14+
throw new Error(
15+
'marked_map can only contain integers'
16+
);
17+
}
18+
// track: bounds check
19+
if ( ii < 0 && ii >= subject.length ) {
20+
throw new Error(
21+
'each item in `marked_map` must be within that bounds ' +
22+
'of `subject`'
23+
);
24+
}
25+
}
26+
27+
marked_map.sort((a, b) => b - a);
28+
29+
for ( let i=0 ; i < marked_map.length ; i++ ) {
30+
let ii = marked_map[i];
31+
subject.splice(ii, 1);
32+
}
33+
34+
return subject;
35+
}
36+
37+
_test ({ assert }) {
38+
// inner indices
39+
{
40+
const subject = [
41+
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
42+
// 0 1 2 3 4 5 6 7
43+
const marked_map = [2, 5];
44+
this.remove_marked_items(marked_map, subject);
45+
assert(() => subject.join('') === 'abdegh');
46+
}
47+
// left edge
48+
{
49+
const subject = [
50+
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
51+
// 0 1 2 3 4 5 6 7
52+
const marked_map = [0]
53+
this.remove_marked_items(marked_map, subject);
54+
assert(() => subject.join('') === 'bcdefgh');
55+
}
56+
// right edge
57+
{
58+
const subject = [
59+
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
60+
// 0 1 2 3 4 5 6 7
61+
const marked_map = [7]
62+
this.remove_marked_items(marked_map, subject);
63+
assert(() => subject.join('') === 'abcdefg');
64+
}
65+
// both edges
66+
{
67+
const subject = [
68+
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
69+
// 0 1 2 3 4 5 6 7
70+
const marked_map = [0, 7]
71+
this.remove_marked_items(marked_map, subject);
72+
assert(() => subject.join('') === 'bcdefg');
73+
}
74+
}
75+
}
76+
77+
module.exports = ArrayUtil;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
const { AppUnderUserActorType, UserActorType } = require("../../services/auth/Actor");
2+
const { Context } = require("../../util/context");
3+
const { BaseES } = require("./BaseES");
4+
5+
class ProtectedAppES extends BaseES {
6+
async select (options){
7+
const results = await this.upstream.select(options);
8+
9+
const actor = Context.get('actor');
10+
const services = Context.get('services');
11+
12+
const to_delete = [];
13+
for ( let i=0 ; i < results.length ; i++ ) {
14+
const entity = results[i];
15+
16+
if ( ! await this.check_({ actor, services }, entity) ) {
17+
continue;
18+
}
19+
20+
to_delete.push(i);
21+
}
22+
23+
const svc_utilArray = services.get('util-array');
24+
svc_utilArray.remove_marked_items(to_delete, results);
25+
26+
return results;
27+
}
28+
29+
async read (uid){
30+
const entity = await this.upstream.read(uid);
31+
if ( ! entity ) return null;
32+
33+
const actor = Context.get('actor');
34+
const services = Context.get('services');
35+
36+
if ( await this.check_({ actor, services }, entity) ) {
37+
return null;
38+
}
39+
40+
return entity;
41+
}
42+
43+
/**
44+
* returns true if the entity should not be sent downstream
45+
*/
46+
async check_ ({ actor, services }, entity) {
47+
// track: ruleset
48+
{
49+
// if it's not a protected app, no worries
50+
if ( ! await entity.get('protected') ) return;
51+
52+
// if actor is this app, no worries
53+
if (
54+
actor.type instanceof AppUnderUserActorType &&
55+
await entity.get('uid') === actor.type.app.uid
56+
) return;
57+
58+
// if actor is owner of this app, no worries
59+
if (
60+
actor.type instanceof UserActorType &&
61+
(await entity.get('owner')).id === actor.type.user.id
62+
) return;
63+
}
64+
65+
// now we need to check for permission
66+
const app_uid = await entity.get('uid');
67+
const svc_permission = services.get('permission');
68+
const permission_to_check = `app:uid#${app_uid}:access`;
69+
const perm = await svc_permission.check(
70+
actor, permission_to_check,
71+
);
72+
73+
if ( perm ) return;
74+
75+
// `true` here means "do not send downstream"
76+
return true;
77+
}
78+
};
79+
80+
module.exports = {
81+
ProtectedAppES,
82+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const { get_app } = require("../helpers");
2+
const { UserActorType } = require("./auth/Actor");
3+
const { PermissionImplicator, PermissionUtil } = require("./auth/PermissionService");
4+
const BaseService = require("./BaseService");
5+
6+
class ProtectedAppService extends BaseService {
7+
async _init () {
8+
const svc_permission = this.services.get('permission');
9+
10+
// track: object description in comment
11+
// Owner of procted app has implicit permission to access it
12+
svc_permission.register_implicator(PermissionImplicator.create({
13+
matcher: permission => {
14+
return permission.startsWith('app:');
15+
},
16+
checker: async ({ actor, permission }) => {
17+
if ( !(actor.type instanceof UserActorType) ) {
18+
return undefined;
19+
}
20+
21+
const parts = PermissionUtil.split(permission);
22+
if ( parts.length !== 3 ) return undefined;
23+
24+
const [_, uid_part, lvl] = parts;
25+
if ( lvl !== 'access' ) return undefined;
26+
27+
// track: slice a prefix
28+
const uid = uid_part.slice('uid#'.length);
29+
30+
const app = await get_app({ uid });
31+
32+
if ( app.owner_user_id !== actor.type.user.id ) {
33+
return undefined;
34+
}
35+
36+
return {};
37+
},
38+
}));
39+
}
40+
}
41+
42+
module.exports = {
43+
ProtectedAppService,
44+
};

0 commit comments

Comments
 (0)