feat: add group management endpoints

This commit is contained in:
KernelDeimos
2024-06-26 16:21:32 -04:00
committed by Eric Dubé
parent 00149402e0
commit 4216346384
4 changed files with 268 additions and 6 deletions

View File

@@ -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 }) => {

View File

@@ -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,
};

View File

@@ -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'))

View File

@@ -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,
};