(async() => { const Sequelize = require("sequelize"); const Op = Sequelize.Op const sequelize = new Sequelize("test", "","",{ host: 'localhost', dialect: 'mysql', pool: { max: 5, min: 0, idle: 10000 }, //logging: false }); const User = sequelize.define("user", { login: Sequelize.STRING, password: Sequelize.STRING, }) const Content = sequelize.define("content", { title: Sequelize.STRING, data: Sequelize.TEXT }) async function filldb(){ await sequelize.sync() console.log('synced') let [vasya, petya, kolya] = await Promise.all([ User.create({login: "Vasya", password: "qwe"}), User.create({login: "Petya", password: "qwe"}), User.create({login: "Kolya", password: "qwe"}), ]) let groupSlice = await Slice.create({ permission: 'group', model: 'group', slice: [`${vasya.id}`, `${petya.id}`] }) let [vasyaSlice, petyaSlice, kolyaSlice] = await Promise.all([ Slice.create({model: 'user', permission: 'user', modelId: vasya.id, slice: ["user", `#${groupSlice.id}`]}), Slice.create({model: 'user', permission: 'user', modelId: petya.id, slice: ["user", `#${groupSlice.id}`]}), Slice.create({model: 'user', permission: 'user', modelId: kolya.id, slice: ["user" ]}) ]) let [hiddenContent, roleContent, groupContent] = await Promise.all([ Content.create({title: 'Hidden', data: 'HIDDEN'}), Content.create({title: 'Role', data: 'ROLE'}), Content.create({title: 'Group', data: 'GROUP'}), ]) let [hiddenSlice, roleSlice, groupContentSlice] = await Promise.all([ Slice.create({model: 'contents', permission: 'read', modelId: hiddenContent.id, slice: [], ownerId: kolya.id}), Slice.create({model: 'contents', permission: 'read', modelId: roleContent.id, slice: ["user"], ownerId: vasya.id}), Slice.create({model: 'contents', permission: 'read', modelId: groupContent.id, slice: [`#${groupSlice.id}`], ownerId: petya.id}), ]) let createSlice = await Slice.create({model: 'contents', permission: 'create', slice: ['user']}) } //filldb() // const Slice = sequelize.define("slice",{ permission: Sequelize.STRING, //create, update, delete, read, etc model: Sequelize.STRING, modelId: Sequelize.INTEGER, ownerId: Sequelize.INTEGER, //plain list of: "tags" like: admin, manager, user, anon, User can be tagged by this word in string list variable //OR: just userId. //OR, if negative number (or hash #100500) - other slice id (use abs to get proper table id) //this way optimizing slice: {type: Sequelize.TEXT, //PROBABLY STRING get(){ if (this._slice) return this._slice let result = [] for (let item of this.getDataValue("slice").split(",")){ if (!result.includes(item)){ result.push(item) } } this._slice = result; return this._slice }, set(newValue){ //TODO: update users before with groups newValue = ("length" in newValue) ? newValue.join(",") : newValue return this.setDataValue("slice", newValue) } } },{ getterMethods: { async all(){ if (this._all) return this._all this._subSlicesList = this.slice.filter(id => id[0] == "#" || id[0] == "-") //detect - or # .map( id => +id.substr(1)) //cut first char + toInt this._roles = this.slice.filter(id => id[0] >= 'a' && id[0] <= 'z' ) this._userIds = this.slice.filter(id => id[0] >= '0' && id[0] <= '9' ) if (this._subSlicesList.length) { let subSlices = await Slice.findAll({where: {id: {[Op.in]: this._subSlicesList}}}) if (subSlices) for (let subSlice of subSlices){ this._subSlicesList = this._subSlicesList.filter(id => subSlice.id !== id) let subSliceAll = await subSlice.all this._subSlicesList = [...this._subSlicesList,... subSliceAll.slices] this._roles = [...this._roles, ...subSliceAll.roles] this._userIds = [...this._userIds, ...subSliceAll.userIds] } } this._all = {slices: this._subSlicesList, roles: this._roles, userIds: this._userIds} return this._all }, async allRoles(){ return (await this.all).roles }, async allSlices(){ return (await this.all).slices }, async allUserIds(){ return (await this.all).userIds }, roles(){ return this.slice.filter(id => id[0] >= 'a' && id[0] <= 'z' ) }, groups(){ return this.slice.filter(id => id[0] == "#" || id[0] == "-") //detect - or # .map( id => +id.substr(1)) //cut first char + toInt } }, indexes: [ { fields: ["modelId", "model", "permission"] }, ] }) function sliced(model){ return async userId => { let user = await User.findByPk(userId) if (!user) throw new ReferenceError(`user with userId ${userId} doesn't exists`) let userSlice = await Slice.findOne({where: { model: 'user', //TODO configure this modelId: userId, permission: 'user' }}) let [userRoles, userGroups] = [userSlice.roles, userSlice.groups] //console.log(userRoles, userGroups) let mapMethodToPermission = { read: ["count", "findAll", "findAndCountAll", "findByPk", "findOne", "max", "min", "sum"], write: [ "destroy","update",], create: ["create", "findCreateFind", "findOrCreate","upsert" ] } function writeHook(instance, options){ return sequelize.Promise.reject(new ReferenceError("No Permissions")); } //sequelize.addHook('beforeCreate', (...params) => console.log(params)) let modelProxy = new Proxy(model, { get(model, method){ let found = false for (var permission in mapMethodToPermission) { if (mapMethodToPermission[permission].includes(method)){ found = true break; } } if (!found){ console.log(`not found ${method}`) return model[method] } console.log('PERMISSION', permission) let checker = async slice => { if (!slice) return false console.log('CHECKER', 'slice ok') if (slice.ownerId === userId) return true console.log('CHECKER', 'not owner', await slice.allRoles, userRoles) let intersect = (await slice.allRoles).filter(role => userRoles.includes(role)) console.log('CHECKER', intersect) return (intersect.length || (await slice.allUserIds).includes(userId + '')) } let wrapSave = instance => { let save = instance.save instance.save = async function(...params){ let readSlice = await Slice.findOne({where: { model: model.getTableName(), modelId: instance.id, permission: 'read' }}) if (!readSlice) throw ReferenceError('No access to write') if (readSlice.ownerId === userId){ console.log('SAVE because owner') return save.apply(instance,params) } let writeSlice = await Slice.findOne({where: { model: model.getTableName(), modelId: instance.id, permission: 'write' }}) if (await checker(writeSlice)){ return save.apply(instance, params) } throw ReferenceError('No access to write') return null; } } let wrappers = { async read(...params){ console.log('read wrapper') let result = await model[method](...params) if (result instanceof Array){ let ids = result.map(instance => instance.id) let slices = await Slice.findAll({where: { model: model.getTableName(), modelId: {[Op.in]: ids}, permission, }}) let filteredResult = [] for (let slice of slices){ let instance = result.filter(instance => instance.id === slice.modelId)[0] //role check if (await checker(slice)) { wrapSave(instance) filteredResult.push(instance) continue; } } return filteredResult } if ('id' in result){ //one record let slice = await Slice.findOne({where: { model: model.getTableName(), modelId: result.id, permission, }}) wrapSave(result) return (await checker(slice)) ? result : null; } }, async create(...params){ let createSlice = await Slice.findOne({where: {model: model.getTableName(), permission}}) if (await checker(createSlice)){ console.log('CHECKER YAYA') let result = await model[method](...params) if ('id' in result){ //new record let newReadSlice = await Slice.create({ model: model.getTableName(), modelId: result.id, ownerId: userId, permission: 'read', slice: createSlice.slice, //default read permissions from create }) wrapSave(result) return result } } return null }, async write(...params){ console.log('WRITE', method) } } return wrappers[permission] }, }) return modelProxy } } let SlicedContent = await sliced(Content)(2) console.log(JSON.stringify(await SlicedContent.findAll({}),null, 4)) //let newContent = await SlicedContent.create({title: "SLiced", data: "SLICED"}) //console.log(newContent) // let content = await SlicedContent.findByPk(7) content.data = `SLICED by WRITE permission for groupzzz #1` content.save() //newContent.data = `SLICED ${newContent.id}` //await newContent.save() let delay = ms => new Promise(r => setTimeout(r, ms)) })()