feat: add protected subdomains

This commit is contained in:
KernelDeimos
2024-06-18 22:11:52 -04:00
committed by Eric Dubé
parent e7c0b8320a
commit 86fca17fb1
5 changed files with 191 additions and 11 deletions
+7
View File
@@ -28,6 +28,13 @@ const { quot } = require("../util/strutil");
*/
module.exports = class APIError {
static codes = {
// General
'unknown_error': {
status: 500,
message: () => `An unknown error occurred`,
},
// Unorganized
'item_with_same_name_exists': {
status: 409,
message: ({ entry_name }) => entry_name
@@ -24,7 +24,7 @@ const { Context } = require("../../util/context");
const { NodeInternalIDSelector, NodePathSelector } = require("../../filesystem/node/selectors");
const { TYPE_DIRECTORY } = require("../../filesystem/FSNodeContext");
const { LLRead } = require("../../filesystem/ll_operations/ll_read");
const { Actor, UserActorType } = require("../../services/auth/Actor");
const { Actor, UserActorType, SiteActorType } = require("../../services/auth/Actor");
const APIError = require("../../api/APIError");
class PuterSiteMiddleware extends AdvancedBase {
@@ -145,14 +145,60 @@ class PuterSiteMiddleware extends AdvancedBase {
await target_node.get('name')
);
res.set('Content-Type', contentType);
const acl_config = {
no_acl: true,
actor: null,
};
if ( site.protected ) {
const svc_auth = req.services.get('auth');
const token = req.query['puter.auth.token'];
acl_config.no_acl = false;
if ( ! token ) {
const e = APIError.create('token_missing');
return this.respond_error_({ req, res, e });
}
const app_actor =
await svc_auth.authenticate_from_token(token);
const user_actor =
app_actor.get_related_actor(UserActorType);
const svc_permission = req.services.get('permission');
const perm = await (async () => {
if ( user_actor.type.user.id === site.user_id ) {
return {};
}
return await svc_permission.check(
user_actor, `site:uid#${site.uuid}:access`
);
})();
if ( ! perm ) {
const e = APIError.create('forbidden');
return this.respond_error_({ req, res, e });
}
const site_actor = await Actor.create(SiteActorType, { site });
acl_config.actor = site_actor;
console.log('THE SITE ACTOR?', site_actor);
Object.freeze(acl_config);
}
const ll_read = new LLRead();
// const actor = Actor.adapt(req.user);
console.log('what user?', req.user);
console.log('what actor?', acl_config.actor);
const stream = await ll_read.run({
no_acl: true,
actor: new Actor({
user_uid: req.user ? req.user.uuid : null,
type: new UserActorType({ user: req.user }),
}),
no_acl: acl_config.no_acl,
actor: acl_config.actor,
fsNode: target_node,
});
@@ -189,6 +235,19 @@ class PuterSiteMiddleware extends AdvancedBase {
return res.end();
}
respond_error_ ({ req, res, e }) {
if ( ! (e instanceof APIError) ) {
// TODO: alarm here
e = APIError.create('unknown_error');
}
res.redirect(`${config.origin}?${e.querystringize({
...(req.query['puter.app_instance_id'] ? {
['error_from_within_iframe']: true,
} : {})
})}`);
}
}
module.exports = app => {
@@ -16,7 +16,10 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { NodeInternalIDSelector, NodeUIDSelector } = require("../filesystem/node/selectors");
const { Context } = require("../util/context");
const { SiteActorType } = require("./auth/Actor");
const { PermissionUtil, PermissionRewriter, PermissionImplicator } = require("./auth/PermissionService");
const BaseService = require("./BaseService");
const { DB_WRITE } = require("./database/consts");
@@ -24,6 +27,65 @@ class PuterSiteService extends BaseService {
async _init () {
const services = this.services;
this.db = services.get('database').get(DB_WRITE, 'sites');
const svc_fs = services.get('filesystem');
// Rewrite site permissions specified by name
const svc_permission = this.services.get('permission');
svc_permission.register_rewriter(PermissionRewriter.create({
matcher: permission => {
if ( ! permission.startsWith('site:') ) return false;
const [_, specifier] = PermissionUtil.split(permission);
if ( specifier.startsWith('uid#') ) return false;
return true;
},
rewriter: async permission => {
const [_1, name, ...rest] = PermissionUtil.split(permission);
const sd = await this.get_subdomain(name);
return PermissionUtil.join(
_1, `uid#${sd.uuid}`, ...rest,
);
},
}));
// Imply that sites can read their own files
svc_permission.register_implicator(PermissionImplicator.create({
matcher: permission => {
return permission.startsWith('fs:');
},
checker: async ({ actor, permission }) => {
if ( !(actor.type instanceof SiteActorType) ) {
return undefined;
}
const [_, uid, lvl] = PermissionUtil.split(permission);
const node = await svc_fs.node(new NodeUIDSelector(uid));
if ( !['read','list','see'].includes(lvl) ) {
return undefined;
}
if ( ! await node.exists() ) {
return undefined;
}
const site_node = await svc_fs.node(
new NodeInternalIDSelector(
'mysql',
actor.type.site.root_dir_id,
)
);
if ( await site_node.is(node) ) {
return {};
}
if ( await site_node.is_above(node) ) {
return {};
}
return undefined;
},
}));
}
async get_subdomain (subdomain) {
@@ -40,6 +102,15 @@ class PuterSiteService extends BaseService {
if ( rows.length === 0 ) return null;
return rows[0];
}
// async get_subdomain_by_uid (uid) {
// const rows = await this.db.read(
// `SELECT * FROM subdomains WHERE uuid = ? LIMIT 1`,
// [uid]
// );
// if ( rows.length === 0 ) return null;
// return rows[0];
// }
}
module.exports = {
@@ -182,6 +182,17 @@ class AccessTokenActorType {
}
}
class SiteActorType {
constructor (o, ...a) {
for ( const k in o ) {
this[k] = o[k];
}
}
get uid () {
return `site:` + this.site.name
}
}
Actor.adapt = function (actor) {
actor = actor || Context.get('actor');
@@ -208,4 +219,5 @@ module.exports = {
UserActorType,
AppUnderUserActorType,
AccessTokenActorType,
SiteActorType,
}
@@ -20,7 +20,7 @@ const { get_user, get_app } = require("../../helpers");
const { Context } = require("../../util/context");
const BaseService = require("../BaseService");
const { DB_WRITE } = require("../database/consts");
const { UserActorType, Actor, AppUnderUserActorType, AccessTokenActorType } = require("./Actor");
const { UserActorType, Actor, AppUnderUserActorType, AccessTokenActorType, SiteActorType } = require("./Actor");
const default_implicit_user_app_permissions = {
'driver:helloworld:greet': {},
@@ -214,15 +214,17 @@ class PermissionUtil {
}
class PermissionService extends BaseService {
async _init () {
this.db = this.services.get('database').get(DB_WRITE, 'permissions');
this._register_commands(this.services.get('commands'));
_construct () {
this._permission_rewriters = [];
this._permission_implicators = [];
this._permission_exploders = [];
}
async _init () {
this.db = this.services.get('database').get(DB_WRITE, 'permissions');
this._register_commands(this.services.get('commands'));
}
async _rewrite_permission (permission) {
for ( const rewriter of this._permission_rewriters ) {
if ( ! rewriter.matches(permission) ) continue;
@@ -292,6 +294,12 @@ class PermissionService extends BaseService {
return await this.check_user_app_permission(actor, app_uid, permission);
}
if ( actor.type instanceof SiteActorType ) {
return await this.check_site_permission(actor, permission);
}
console.log ('WHAT ACTOR TYPE THEN???', actor.type);
throw new Error('unrecognized actor type');
}
@@ -421,6 +429,29 @@ class PermissionService extends BaseService {
return rows[0].extra;
}
async check_site_permission (actor, permission) {
permission = await this._rewrite_permission(permission);
// const parent_perms = this.get_parent_permissions(permission);
const parent_perms = await this.get_higher_permissions(permission);
// Check implicit permissions
for ( const parent_perm of parent_perms ) {
if ( implicit_user_permissions[parent_perm] ) {
return implicit_user_permissions[parent_perm];
}
}
for ( const implicator of this._permission_implicators ) {
if ( ! implicator.matches(permission) ) continue;
const implied = await implicator.check({
actor,
permission,
recurse: this.check.bind(this),
});
if ( implied ) return implied;
}
}
async grant_user_app_permission (actor, app_uid, permission, extra = {}, meta) {
permission = await this._rewrite_permission(permission);