mirror of
https://github.com/HeyPuter/puter.git
synced 2026-05-29 12:50:59 +00:00
feat: add protected subdomains
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user