Skip to content

Commit 86fca17

Browse files
committed
feat: add protected subdomains
1 parent e7c0b83 commit 86fca17

File tree

5 files changed

+191
-11
lines changed

5 files changed

+191
-11
lines changed

packages/backend/src/api/APIError.js

+7
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ const { quot } = require("../util/strutil");
2828
*/
2929
module.exports = class APIError {
3030
static codes = {
31+
// General
32+
'unknown_error': {
33+
status: 500,
34+
message: () => `An unknown error occurred`,
35+
},
36+
37+
// Unorganized
3138
'item_with_same_name_exists': {
3239
status: 409,
3340
message: ({ entry_name }) => entry_name

packages/backend/src/routers/hosting/puter-site.js

+65-6
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const { Context } = require("../../util/context");
2424
const { NodeInternalIDSelector, NodePathSelector } = require("../../filesystem/node/selectors");
2525
const { TYPE_DIRECTORY } = require("../../filesystem/FSNodeContext");
2626
const { LLRead } = require("../../filesystem/ll_operations/ll_read");
27-
const { Actor, UserActorType } = require("../../services/auth/Actor");
27+
const { Actor, UserActorType, SiteActorType } = require("../../services/auth/Actor");
2828
const APIError = require("../../api/APIError");
2929

3030
class PuterSiteMiddleware extends AdvancedBase {
@@ -145,14 +145,60 @@ class PuterSiteMiddleware extends AdvancedBase {
145145
await target_node.get('name')
146146
);
147147
res.set('Content-Type', contentType);
148+
149+
const acl_config = {
150+
no_acl: true,
151+
actor: null,
152+
};
153+
154+
if ( site.protected ) {
155+
const svc_auth = req.services.get('auth');
156+
const token = req.query['puter.auth.token'];
157+
158+
acl_config.no_acl = false;
159+
160+
if ( ! token ) {
161+
const e = APIError.create('token_missing');
162+
return this.respond_error_({ req, res, e });
163+
}
164+
165+
const app_actor =
166+
await svc_auth.authenticate_from_token(token);
167+
168+
const user_actor =
169+
app_actor.get_related_actor(UserActorType);
170+
171+
const svc_permission = req.services.get('permission');
172+
const perm = await (async () => {
173+
if ( user_actor.type.user.id === site.user_id ) {
174+
return {};
175+
}
176+
177+
return await svc_permission.check(
178+
user_actor, `site:uid#${site.uuid}:access`
179+
);
180+
})();
181+
182+
if ( ! perm ) {
183+
const e = APIError.create('forbidden');
184+
return this.respond_error_({ req, res, e });
185+
}
186+
187+
const site_actor = await Actor.create(SiteActorType, { site });
188+
acl_config.actor = site_actor;
189+
190+
console.log('THE SITE ACTOR?', site_actor);
191+
192+
Object.freeze(acl_config);
193+
}
148194

149195
const ll_read = new LLRead();
196+
// const actor = Actor.adapt(req.user);
197+
console.log('what user?', req.user);
198+
console.log('what actor?', acl_config.actor);
150199
const stream = await ll_read.run({
151-
no_acl: true,
152-
actor: new Actor({
153-
user_uid: req.user ? req.user.uuid : null,
154-
type: new UserActorType({ user: req.user }),
155-
}),
200+
no_acl: acl_config.no_acl,
201+
actor: acl_config.actor,
156202
fsNode: target_node,
157203
});
158204

@@ -189,6 +235,19 @@ class PuterSiteMiddleware extends AdvancedBase {
189235

190236
return res.end();
191237
}
238+
239+
respond_error_ ({ req, res, e }) {
240+
if ( ! (e instanceof APIError) ) {
241+
// TODO: alarm here
242+
e = APIError.create('unknown_error');
243+
}
244+
245+
res.redirect(`${config.origin}?${e.querystringize({
246+
...(req.query['puter.app_instance_id'] ? {
247+
['error_from_within_iframe']: true,
248+
} : {})
249+
})}`);
250+
}
192251
}
193252

194253
module.exports = app => {

packages/backend/src/services/PuterSiteService.js

+71
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,76 @@
1616
* You should have received a copy of the GNU Affero General Public License
1717
* along with this program. If not, see <https://www.gnu.org/licenses/>.
1818
*/
19+
const { NodeInternalIDSelector, NodeUIDSelector } = require("../filesystem/node/selectors");
1920
const { Context } = require("../util/context");
21+
const { SiteActorType } = require("./auth/Actor");
22+
const { PermissionUtil, PermissionRewriter, PermissionImplicator } = require("./auth/PermissionService");
2023
const BaseService = require("./BaseService");
2124
const { DB_WRITE } = require("./database/consts");
2225

2326
class PuterSiteService extends BaseService {
2427
async _init () {
2528
const services = this.services;
2629
this.db = services.get('database').get(DB_WRITE, 'sites');
30+
31+
const svc_fs = services.get('filesystem');
32+
33+
// Rewrite site permissions specified by name
34+
const svc_permission = this.services.get('permission');
35+
svc_permission.register_rewriter(PermissionRewriter.create({
36+
matcher: permission => {
37+
if ( ! permission.startsWith('site:') ) return false;
38+
const [_, specifier] = PermissionUtil.split(permission);
39+
if ( specifier.startsWith('uid#') ) return false;
40+
return true;
41+
},
42+
rewriter: async permission => {
43+
const [_1, name, ...rest] = PermissionUtil.split(permission);
44+
const sd = await this.get_subdomain(name);
45+
return PermissionUtil.join(
46+
_1, `uid#${sd.uuid}`, ...rest,
47+
);
48+
},
49+
}));
50+
51+
// Imply that sites can read their own files
52+
svc_permission.register_implicator(PermissionImplicator.create({
53+
matcher: permission => {
54+
return permission.startsWith('fs:');
55+
},
56+
checker: async ({ actor, permission }) => {
57+
if ( !(actor.type instanceof SiteActorType) ) {
58+
return undefined;
59+
}
60+
61+
const [_, uid, lvl] = PermissionUtil.split(permission);
62+
const node = await svc_fs.node(new NodeUIDSelector(uid));
63+
64+
if ( !['read','list','see'].includes(lvl) ) {
65+
return undefined;
66+
}
67+
68+
if ( ! await node.exists() ) {
69+
return undefined;
70+
}
71+
72+
const site_node = await svc_fs.node(
73+
new NodeInternalIDSelector(
74+
'mysql',
75+
actor.type.site.root_dir_id,
76+
)
77+
);
78+
79+
if ( await site_node.is(node) ) {
80+
return {};
81+
}
82+
if ( await site_node.is_above(node) ) {
83+
return {};
84+
}
85+
86+
return undefined;
87+
},
88+
}));
2789
}
2890

2991
async get_subdomain (subdomain) {
@@ -40,6 +102,15 @@ class PuterSiteService extends BaseService {
40102
if ( rows.length === 0 ) return null;
41103
return rows[0];
42104
}
105+
106+
// async get_subdomain_by_uid (uid) {
107+
// const rows = await this.db.read(
108+
// `SELECT * FROM subdomains WHERE uuid = ? LIMIT 1`,
109+
// [uid]
110+
// );
111+
// if ( rows.length === 0 ) return null;
112+
// return rows[0];
113+
// }
43114
}
44115

45116
module.exports = {

packages/backend/src/services/auth/Actor.js

+12
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,17 @@ class AccessTokenActorType {
182182
}
183183
}
184184

185+
class SiteActorType {
186+
constructor (o, ...a) {
187+
for ( const k in o ) {
188+
this[k] = o[k];
189+
}
190+
}
191+
get uid () {
192+
return `site:` + this.site.name
193+
}
194+
}
195+
185196
Actor.adapt = function (actor) {
186197
actor = actor || Context.get('actor');
187198

@@ -208,4 +219,5 @@ module.exports = {
208219
UserActorType,
209220
AppUnderUserActorType,
210221
AccessTokenActorType,
222+
SiteActorType,
211223
}

packages/backend/src/services/auth/PermissionService.js

+36-5
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const { get_user, get_app } = require("../../helpers");
2020
const { Context } = require("../../util/context");
2121
const BaseService = require("../BaseService");
2222
const { DB_WRITE } = require("../database/consts");
23-
const { UserActorType, Actor, AppUnderUserActorType, AccessTokenActorType } = require("./Actor");
23+
const { UserActorType, Actor, AppUnderUserActorType, AccessTokenActorType, SiteActorType } = require("./Actor");
2424

2525
const default_implicit_user_app_permissions = {
2626
'driver:helloworld:greet': {},
@@ -214,15 +214,17 @@ class PermissionUtil {
214214
}
215215

216216
class PermissionService extends BaseService {
217-
async _init () {
218-
this.db = this.services.get('database').get(DB_WRITE, 'permissions');
219-
this._register_commands(this.services.get('commands'));
220-
217+
_construct () {
221218
this._permission_rewriters = [];
222219
this._permission_implicators = [];
223220
this._permission_exploders = [];
224221
}
225222

223+
async _init () {
224+
this.db = this.services.get('database').get(DB_WRITE, 'permissions');
225+
this._register_commands(this.services.get('commands'));
226+
}
227+
226228
async _rewrite_permission (permission) {
227229
for ( const rewriter of this._permission_rewriters ) {
228230
if ( ! rewriter.matches(permission) ) continue;
@@ -292,6 +294,12 @@ class PermissionService extends BaseService {
292294

293295
return await this.check_user_app_permission(actor, app_uid, permission);
294296
}
297+
298+
if ( actor.type instanceof SiteActorType ) {
299+
return await this.check_site_permission(actor, permission);
300+
}
301+
302+
console.log ('WHAT ACTOR TYPE THEN???', actor.type);
295303

296304
throw new Error('unrecognized actor type');
297305
}
@@ -421,6 +429,29 @@ class PermissionService extends BaseService {
421429

422430
return rows[0].extra;
423431
}
432+
433+
async check_site_permission (actor, permission) {
434+
permission = await this._rewrite_permission(permission);
435+
// const parent_perms = this.get_parent_permissions(permission);
436+
const parent_perms = await this.get_higher_permissions(permission);
437+
438+
// Check implicit permissions
439+
for ( const parent_perm of parent_perms ) {
440+
if ( implicit_user_permissions[parent_perm] ) {
441+
return implicit_user_permissions[parent_perm];
442+
}
443+
}
444+
445+
for ( const implicator of this._permission_implicators ) {
446+
if ( ! implicator.matches(permission) ) continue;
447+
const implied = await implicator.check({
448+
actor,
449+
permission,
450+
recurse: this.check.bind(this),
451+
});
452+
if ( implied ) return implied;
453+
}
454+
}
424455

425456
async grant_user_app_permission (actor, app_uid, permission, extra = {}, meta) {
426457
permission = await this._rewrite_permission(permission);

0 commit comments

Comments
 (0)