From d0b8572f4647293f989ace72d0cd24bfbf6a71b1 Mon Sep 17 00:00:00 2001 From: joostdecock Date: Mon, 14 Nov 2022 18:26:20 +0100 Subject: [PATCH] wip(backend): Cloning of people --- sites/backend/src/controllers/person.mjs | 16 +++--- sites/backend/src/controllers/user.mjs | 4 +- sites/backend/src/models/person.mjs | 55 +++++++++++++++++---- sites/backend/src/models/user.mjs | 4 +- sites/backend/src/routes/person.mjs | 16 +++--- sites/backend/tests/person.mjs | 62 +++++++++++++++++++++++- 6 files changed, 127 insertions(+), 30 deletions(-) diff --git a/sites/backend/src/controllers/person.mjs b/sites/backend/src/controllers/person.mjs index bdf32ee52a4..e6081b3a5c2 100644 --- a/sites/backend/src/controllers/person.mjs +++ b/sites/backend/src/controllers/person.mjs @@ -8,7 +8,7 @@ export function PersonController() {} */ PersonController.prototype.create = async (req, res, tools) => { const Person = new PersonModel(tools) - await Person.create(req) + await Person.guardedCreate(req) return Person.sendResponse(res) } @@ -19,7 +19,7 @@ PersonController.prototype.create = async (req, res, tools) => { */ PersonController.prototype.read = async (req, res, tools) => { const Person = new PersonModel(tools) - await Person.readForReturn({ id: parseInt(req.params.id) }, req.user) + await Person.guardedRead(req) return Person.sendResponse(res) } @@ -50,9 +50,9 @@ PersonController.prototype.delete = async (req, res, tools) => { * Clone a person * See: https://freesewing.dev/reference/backend/api */ -//PersonController.prototype.clone = async (req, res, tools) => { -// const Person = new PersonModel(tools) -// await Person.unsafeUpdate(req) -// -// return Person.sendResponse(res) -//} +PersonController.prototype.clone = async (req, res, tools) => { + const Person = new PersonModel(tools) + await Person.guardedClone(req) + + return Person.sendResponse(res) +} diff --git a/sites/backend/src/controllers/user.mjs b/sites/backend/src/controllers/user.mjs index e1c20d96f84..6eaaf889af6 100644 --- a/sites/backend/src/controllers/user.mjs +++ b/sites/backend/src/controllers/user.mjs @@ -10,7 +10,7 @@ export function UserController() {} */ UserController.prototype.signup = async (req, res, tools) => { const User = new UserModel(tools) - await User.create(req) + await User.guardedCreate(req) return User.sendResponse(res) } @@ -48,7 +48,7 @@ UserController.prototype.login = async function (req, res, tools) { */ UserController.prototype.whoami = async (req, res, tools) => { const User = new UserModel(tools) - await User.readForReturn({ id: req.user.uid }) + await User.guardedRead({ id: req.user.uid }) return User.sendResponse(res) } diff --git a/sites/backend/src/models/person.mjs b/sites/backend/src/models/person.mjs index f2dfe25bc1c..c75d4de7047 100644 --- a/sites/backend/src/models/person.mjs +++ b/sites/backend/src/models/person.mjs @@ -12,7 +12,7 @@ export function PersonModel(tools) { return this } -PersonModel.prototype.create = async function ({ body, user }) { +PersonModel.prototype.guardedCreate = async function ({ body, user }) { if (user.level < 3) return this.setResponse(403, 'insufficientAccessLevel') if (Object.keys(body) < 1) return this.setResponse(400, 'postBodyMissing') if (!body.name || typeof body.name !== 'string') return this.setResponse(400, 'nameMissing') @@ -28,12 +28,7 @@ PersonModel.prototype.create = async function ({ body, user }) { data.img = this.config.avatars.person // Create record - try { - this.record = await this.prisma.person.create({ data: this.cloak(data) }) - } catch (err) { - log.warn(err, 'Could not create person') - return this.setResponse(500, 'createPersonFailed') - } + await this.unguardedCreate(data) // Update img? (now that we have the ID) const img = @@ -49,6 +44,17 @@ PersonModel.prototype.create = async function ({ body, user }) { return this.setResponse(201, 'created', { person: this.asPerson() }) } +PersonModel.prototype.unguardedCreate = async function (data) { + try { + this.record = await this.prisma.person.create({ data: this.cloak(data) }) + } catch (err) { + log.warn(err, 'Could not create person') + return this.setResponse(500, 'createPersonFailed') + } + + return this +} + /* * Loads a person from the database based on the where clause you pass it * @@ -72,11 +78,11 @@ PersonModel.prototype.read = async function (where) { * * Stores result in this.record */ -PersonModel.prototype.readForReturn = async function (where, user) { +PersonModel.prototype.guardedRead = async function ({ params, user }) { if (user.level < 1) return this.setResponse(403, 'insufficientAccessLevel') if (user.iss && user.status < 1) return this.setResponse(403, 'accountStatusLacking') - await this.read(where) + await this.read({ id: parseInt(params.id) }) if (this.record.userId !== user.uid && user.level < 5) { return this.setResponse(403, 'insufficientAccessLevel') } @@ -87,6 +93,37 @@ PersonModel.prototype.readForReturn = async function (where, user) { }) } +/* + * Clones a person + * In addition prepares it for returning the person data + * + * Stores result in this.record + */ +PersonModel.prototype.guardedClone = async function ({ params, user }) { + if (user.level < 3) return this.setResponse(403, 'insufficientAccessLevel') + if (user.iss && user.status < 1) return this.setResponse(403, 'accountStatusLacking') + + await this.read({ id: parseInt(params.id) }) + if (this.record.userId !== user.uid && !this.record.public && user.level < 5) { + return this.setResponse(403, 'insufficientAccessLevel') + } + + // Clone person + const data = this.asPerson() + delete data.id + data.name += ` (cloned from #${this.record.id})` + data.notes += ` (Note: This person was cloned from person #${this.record.id})` + await this.unguardedCreate(data) + + // Update unencrypted data + this.reveal() + + return this.setResponse(200, false, { + result: 'success', + person: this.asPerson(), + }) +} + /* * Helper method to decrypt at-rest data */ diff --git a/sites/backend/src/models/user.mjs b/sites/backend/src/models/user.mjs index 663976cc7a0..8f5ac2d0159 100644 --- a/sites/backend/src/models/user.mjs +++ b/sites/backend/src/models/user.mjs @@ -67,7 +67,7 @@ UserModel.prototype.cloak = function (data) { * * Stores result in this.record */ -UserModel.prototype.readForReturn = async function (where) { +UserModel.prototype.guardedRead = async function (where) { await this.read(where) return this.setResponse(200, false, { @@ -136,7 +136,7 @@ UserModel.prototype.setExists = function () { /* * Creates a user+confirmation and sends out signup email */ -UserModel.prototype.create = async function ({ body }) { +UserModel.prototype.guardedCreate = async function ({ body }) { if (Object.keys(body) < 1) return this.setResponse(400, 'postBodyMissing') if (!body.email) return this.setResponse(400, 'emailMissing') if (!body.language) return this.setResponse(400, 'languageMissing') diff --git a/sites/backend/src/routes/person.mjs b/sites/backend/src/routes/person.mjs index 0abb71cb512..1ee0609dc55 100644 --- a/sites/backend/src/routes/person.mjs +++ b/sites/backend/src/routes/person.mjs @@ -15,6 +15,14 @@ export function personRoutes(tools) { Person.create(req, res, tools) ) + // Clone person + app.post('/people/:id/clone/jwt', passport.authenticate(...jwt), (req, res) => + Person.clone(req, res, tools) + ) + app.post('/people/:id/clone/key', passport.authenticate(...bsc), (req, res) => + Person.clone(req, res, tools) + ) + // Read person app.get('/people/:id/jwt', passport.authenticate(...jwt), (req, res) => Person.read(req, res, tools) @@ -31,14 +39,6 @@ export function personRoutes(tools) { Person.update(req, res, tools) ) - // Clone person - app.put('/people/:id/clone/jwt', passport.authenticate(...jwt), (req, res) => - Person.clone(req, res, tools) - ) - app.put('/people/:id/clone/key', passport.authenticate(...bsc), (req, res) => - Person.clone(req, res, tools) - ) - // Delete person app.delete('/people/:id/jwt', passport.authenticate(...jwt), (req, res) => Person.delete(req, res, tools) diff --git a/sites/backend/tests/person.mjs b/sites/backend/tests/person.mjs index 312632056f2..84060c2a82d 100644 --- a/sites/backend/tests/person.mjs +++ b/sites/backend/tests/person.mjs @@ -95,7 +95,7 @@ export const personTests = async (chai, config, expect, store) => { for (const field of ['imperial', 'public']) { it(`${store.icon('person', auth)} Should update the ${field} field (${auth})`, (done) => { const data = {} - const val = false + const val = !store.person[auth][field] data[field] = val chai .request(config.api) @@ -115,6 +115,7 @@ export const personTests = async (chai, config, expect, store) => { expect(res.status).to.equal(200) expect(res.body.result).to.equal(`success`) expect(res.body.person[field]).to.equal(val) + store.person[auth][field] = val done() }) }) @@ -310,6 +311,65 @@ export const personTests = async (chai, config, expect, store) => { }) }) + //it(`${store.icon( + // 'person', + // auth + //)} Should clone a person (${auth})`, (done) => { + // chai + // .request(config.api) + // .post(`/people/${store.person[auth].id}/clone/${auth}`) + // .set( + // 'Authorization', + // auth === 'jwt' + // ? 'Bearer ' + store.account.token + // : 'Basic ' + + // new Buffer( + // `${store.account.apikey.key}:${store.account.apikey.secret}` + // ).toString('base64') + // ) + // .end((err, res) => { + // expect(err === null).to.equal(true) + // expect(res.status).to.equal(200) + // expect(res.body.result).to.equal(`success`) + // expect(typeof res.body.error).to.equal(`undefined`) + // expect(typeof res.body.person.id).to.equal(`number`) + // done() + // }) + //}) + + it(`${store.icon( + 'person', + auth + )} Should (not) clone a public person across accounts (${auth})`, (done) => { + chai + .request(config.api) + .post(`/people/${store.person[auth].id}/clone/${auth}`) + .set( + 'Authorization', + auth === 'jwt' + ? 'Bearer ' + store.altaccount.token + : 'Basic ' + + new Buffer( + `${store.altaccount.apikey.key}:${store.altaccount.apikey.secret}` + ).toString('base64') + ) + .end((err, res) => { + if (store.person[auth].public) { + expect(err === null).to.equal(true) + expect(res.status).to.equal(200) + expect(res.body.result).to.equal(`success`) + expect(typeof res.body.error).to.equal(`undefined`) + expect(typeof res.body.person.id).to.equal(`number`) + } else { + expect(err === null).to.equal(true) + expect(res.status).to.equal(403) + expect(res.body.result).to.equal(`error`) + expect(res.body.error).to.equal(`insufficientAccessLevel`) + } + done() + }) + }) + // TODO: // - Clone person // - Clone person accross accounts of they are public