Skip to content

Commit

Permalink
feat: add group management endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
KernelDeimos committed Jun 27, 2024
1 parent 0014940 commit 4216346
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 6 deletions.
6 changes: 6 additions & 0 deletions packages/backend/src/CoreModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,12 @@ const install = async ({ services, app, useapi }) => {

const { ShareService } = require('./services/ShareService');
services.registerService('share', ShareService);

const { GroupService } = require('./services/auth/GroupService');
services.registerService('group', GroupService);

const { PermissionAPIService } = require('./services/PermissionAPIService');
services.registerService('__permission-api', PermissionAPIService);
}

const install_legacy = async ({ services }) => {
Expand Down
173 changes: 173 additions & 0 deletions packages/backend/src/services/PermissionAPIService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
const { APIError } = require("openai");
const configurable_auth = require("../middleware/configurable_auth");
const { Endpoint } = require("../util/expressutil");
const { whatis } = require("../util/langutil");
const BaseService = require("./BaseService");

class PermissionAPIService extends BaseService {
static MODULES = {
express: require('express'),
};

async ['__on_install.routes'] () {
const { app } = this.services.get('web-server');

app.use(require('../routers/auth/get-user-app-token'))
app.use(require('../routers/auth/grant-user-app'))
app.use(require('../routers/auth/revoke-user-app'))
app.use(require('../routers/auth/grant-user-user'));
app.use(require('../routers/auth/revoke-user-user'));
app.use(require('../routers/auth/list-permissions'))

// track: scoping iife
const r_group = (() => {
const require = this.require;
const express = require('express');
return express.Router()
})();

this.install_group_endpoints_({ router: r_group });
app.use('/group', r_group);
}

install_group_endpoints_ ({ router }) {
Endpoint({
route: '/create',
methods: ['POST'],
mw: [configurable_auth()],
handler: async (req, res) => {
const owner_user_id = req.user.id;

const extra = req.body.extra ?? {};
const metadata = req.body.metadata ?? {};
if ( whatis(extra) !== 'object' ) {
throw APIError.create('field_invalid', null, {
key: 'extra',
expected: 'object',
got: whatis(extra),
})
}
if ( whatis(metadata) !== 'object' ) {
throw APIError.create('field_invalid', null, {
key: 'metadata',
expected: 'object',
got: whatis(metadata),
})
}

const svc_group = this.services.get('group');
const uid = await svc_group.create({
owner_user_id,
// TODO: allow specifying these in request
extra: {},
metadata: {},
});

res.json({ uid });
}
}).attach(router);

Endpoint({
route: '/add-users',
methods: ['POST'],
mw: [configurable_auth()],
handler: async (req, res) => {
const svc_group = this.services.get('group')

// TODO: validate string and uuid for request

const group = await svc_group.get(
{ uid: req.body.uid });

if ( ! group ) {
throw APIError.create('entity_not_found', null, {
identifier: req.body.uid,
})
}

if ( group.owner_user_id !== req.user.id ) {
throw APIError.create('forbidden');
}

if ( whatis(req.body.users) !== 'array' ) {
throw APIError.create('field_invalid', null, {
key: 'users',
expected: 'array',
got: whatis(req.body.users),
});
}

for ( let i=0 ; i < req.body.users.length ; i++ ) {
const value = req.body.users[i];
if ( whatis(value) === 'string' ) continue;
throw APIError.create('field_invalid', null, {
key: `users[${i}]`,
expected: 'string',
got: whatis(value),
});
}

await svc_group.add_users({
uid: req.body.uid,
users: req.body.users,
});

res.json({});
}
}).attach(router);

// TODO: DRY: add-users is very similar
Endpoint({
route: '/remove-users',
methods: ['POST'],
mw: [configurable_auth()],
handler: async (req, res) => {
const svc_group = this.services.get('group')

// TODO: validate string and uuid for request

const group = await svc_group.get(
{ uid: req.body.uid });

if ( ! group ) {
throw APIError.create('entity_not_found', null, {
identifier: req.body.uid,
})
}

if ( group.owner_user_id !== req.user.id ) {
throw APIError.create('forbidden');
}

if ( whatis(req.body.users) !== 'array' ) {
throw APIError.create('field_invalid', null, {
key: 'users',
expected: 'array',
got: whatis(req.body.users),
});
}

for ( let i=0 ; i < req.body.users.length ; i++ ) {
const value = req.body.users[i];
if ( whatis(value) === 'string' ) continue;
throw APIError.create('field_invalid', null, {
key: `users[${i}]`,
expected: 'string',
got: whatis(value),
});
}

await svc_group.remove_users({
uid: req.body.uid,
users: req.body.users,
});

res.json({});
}
}).attach(router);
}
}

module.exports = {
PermissionAPIService,
};
6 changes: 0 additions & 6 deletions packages/backend/src/services/PuterAPIService.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,6 @@ class PuterAPIService extends BaseService {
app.use(require('../routers/query/app'))
app.use(require('../routers/change_username'))
require('../routers/change_email')(app);
app.use(require('../routers/auth/get-user-app-token'))
app.use(require('../routers/auth/grant-user-app'))
app.use(require('../routers/auth/revoke-user-app'))
app.use(require('../routers/auth/grant-user-user'));
app.use(require('../routers/auth/revoke-user-user'));
app.use(require('../routers/auth/list-permissions'))
app.use(require('../routers/auth/list-sessions'))
app.use(require('../routers/auth/revoke-session'))
app.use(require('../routers/auth/check-app'))
Expand Down
89 changes: 89 additions & 0 deletions packages/backend/src/services/auth/GroupService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
const BaseService = require("../BaseService");
const { DB_WRITE } = require("../database/consts");

class GroupService extends BaseService {
static MODULES = {
uuidv4: require('uuid').v4,
};

_init () {
this.db = this.services.get('database').get(DB_WRITE, 'permissions');
}

async get({ uid }) {
const [group] =
await this.db.read('SELECT * FROM `group` WHERE uid=?', [uid]);
if ( ! group ) return;
group.extra = this.db.case({
mysql: () => group.extra,
otherwise: () => JSON.parse(group.extra),
})();
group.metadata = this.db.case({
mysql: () => group.metadata,
otherwise: () => JSON.parse(group.metadata),
})();
return group;
}

async create ({ owner_user_id, extra, metadata }) {
extra = extra ?? {};
metadata = metadata ?? {};

const uid = this.modules.uuidv4();

await this.db.write(
'INSERT INTO `group` ' +
'(`uid`, `owner_user_id`, `extra`, `metadata`) ' +
'VALUES (?, ?, ?, ?)',
[
uid, owner_user_id,
JSON.stringify(extra),
JSON.stringify(metadata),
]
);

return uid;
}

async add_users ({ uid, users }) {
const question_marks =
'(' + Array(users.length).fill('?').join(', ') + ')';
await this.db.write(
'INSERT INTO `jct_user_group` ' +
'(user_id, group_id) ' +
'SELECT u.id, g.id FROM user u '+
'JOIN (SELECT id FROM `group` WHERE uid=?) g ON 1=1 ' +
'WHERE u.username IN ' +
question_marks,
[uid, ...users],
);
}

async remove_users ({ uid, users }) {
const question_marks =
'(' + Array(users.length).fill('?').join(', ') + ')';
/*
DELETE FROM `jct_user_group`
WHERE group_id = 1
AND user_id IN (
SELECT u.id
FROM user u
WHERE u.username IN ('user_that_shares', 'user_that_gets_shared_to')
);
*/
await this.db.write(
'DELETE FROM `jct_user_group` ' +
'WHERE group_id = (SELECT id FROM `group` WHERE uid=?) ' +
'AND user_id IN (' +
'SELECT u.id FROM user u ' +
'WHERE u.username IN ' +
question_marks +
')',
[uid, ...users],
);
}
}

module.exports = {
GroupService,
};

0 comments on commit 4216346

Please sign in to comment.