Last active
January 10, 2018 01:05
-
-
Save renatoargh/c0178f8504caee8bf439294a640076c3 to your computer and use it in GitHub Desktop.
Example code for data isolation with node.js, express and sequelize
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* gettasks.js */ | |
| app.get('/tasks', async (req, res, next) => { | |
| const { user } = req | |
| const { Task, TaskType } = req.models | |
| try { | |
| const tasks = await Task.findAll({ | |
| where: { userId: user.id }, | |
| include: { | |
| model: TaskType, | |
| as: 'taskType' | |
| } | |
| }) | |
| res.json(tasks) | |
| } catch (err) { | |
| next(err) | |
| } | |
| }) | |
| /* Full code example at https://github.com/renatoargh/data-isolation-example */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* index-1.js */ | |
| const express = require('express') | |
| const sequelizeMiddleware = require('./middlewares/sequelize') | |
| const authMiddleware = require('./middlewares/auth') | |
| const app = express() | |
| app.use(sequelizeMiddleware()) // 1 | |
| app.use(authMiddleware) // 2 | |
| app.get('/tasks', async (req, res, next) => { | |
| // 3 - Code to access database - see below | |
| }) | |
| app.listen(9090, () => { | |
| console.log('data-isolation-example running on port 9090') | |
| }) | |
| /* Full code example at https://github.com/renatoargh/data-isolation-example */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* index.js */ | |
| const express = require('express') | |
| const sequelizeMiddleware = require('./middlewares/sequelize') | |
| const authMiddleware = require('./middlewares/auth') | |
| const app = express() | |
| app.use(sequelizeMiddleware()) | |
| app.use(authMiddleware) | |
| app.get('/tasks', async (req, res, next) => { | |
| const { user } = req | |
| const { Task, TaskType } = req.models | |
| try { | |
| const tasks = await Task.findAll({ | |
| where: { userId: user.id }, | |
| include: { | |
| model: TaskType, | |
| as: 'taskType' | |
| } | |
| }) | |
| res.json(tasks) | |
| } catch (err) { | |
| next(err) | |
| } | |
| }) | |
| app.listen(9090, () => { | |
| console.log('data-isolation-example running on port 9090') | |
| }) | |
| /* Full code example at https://github.com/renatoargh/data-isolation-example */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* middlewares-auth.js */ | |
| module.exports = async (req, res, next) => { | |
| const { User } = req.models | |
| let { authorization = '' } = req.headers | |
| authorization = authorization.replace('Basic ', '') | |
| authorization = Buffer.from(authorization, 'base64') | |
| authorization = authorization.toString('ascii') | |
| const [ email, password ] = authorization.split(':') | |
| try { | |
| const user = await User.findOne({ where: { email } }) // 1 | |
| if (!user || user.password !== password) { | |
| res.status(403).json({}) | |
| } | |
| req.user = user | |
| req.changeSchema(user.dedicatedSchema) // 2 | |
| next() | |
| } catch (err) { | |
| next(err) | |
| } | |
| } | |
| /* Full code example at https://github.com/renatoargh/data-isolation-example */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* middlewares-sequelize.js */ | |
| const path = require('path') | |
| const fs = require('fs') | |
| const Sequelize = require('sequelize') | |
| const { CONNECTION_STRING } = process.env | |
| const modelsFolder = path.join(__dirname, '../models') | |
| const models = {} | |
| module.exports = () => { | |
| const sequelize = new Sequelize(CONNECTION_STRING, { | |
| dialect: 'mssql', | |
| dialectOptions: { encrypt: true } | |
| }) | |
| fs.readdirSync(modelsFolder).forEach(modelPath => { | |
| modelPath = path.join(modelsFolder, modelPath) | |
| let model = require(modelPath) | |
| model = sequelize.import(modelPath, model) | |
| model = model.changeSchema('shared') | |
| models[model.name] = model | |
| }) | |
| Object.values(models) | |
| .filter(m => m.associate) | |
| .forEach(m => m.associate(models)) | |
| return (req, res, next) => { | |
| req.models = models | |
| req.changeSchema = schema => { | |
| Object.keys(req.models) | |
| .forEach(modelName => { | |
| const model = req.models[modelName] | |
| req.models[modelName] = model.changeSchema(schema) | |
| }) | |
| } | |
| next() | |
| } | |
| } | |
| /* Full code example at https://github.com/renatoargh/data-isolation-example */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* models-task.js */ | |
| module.exports = (sequelize, DataTypes) => { | |
| const Task = sequelize.define('Task', { | |
| id: { | |
| type: DataTypes.UUIDV4, | |
| allowNull: false, | |
| primaryKey: true, | |
| field: 'Id' | |
| }, | |
| description: { | |
| type: DataTypes.STRING(100), | |
| allowNull: false, | |
| field: 'Description' | |
| }, | |
| done: { | |
| type: DataTypes.BOOLEAN, | |
| allowNull: false, | |
| default: false, | |
| field: 'Done' | |
| }, | |
| taskTypeId: { | |
| type: DataTypes.INTEGER, | |
| allowNull: true, | |
| field: 'TaskTypeId' | |
| }, | |
| userId: { | |
| type: DataTypes.INTEGER, | |
| allowNull: true, | |
| field: 'UserId' | |
| } | |
| }, { | |
| freezeTableName: true, | |
| tableName: 'Tasks', | |
| createdAt: false, | |
| updatedAt: false, | |
| classMethods: { | |
| changeSchema: schema => Task.schema(schema), | |
| associate: models => { | |
| Task.belongsTo(models.TaskType, { | |
| as: 'taskType', | |
| foreignKey: 'taskTypeId' | |
| }) | |
| } | |
| } | |
| }) | |
| return Task | |
| } | |
| /* Full code example at https://github.com/renatoargh/data-isolation-example */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* models-tasktype.js */ | |
| module.exports = (sequelize, DataTypes) => { | |
| const TaskType = sequelize.define('TaskType', { | |
| id: { | |
| type: DataTypes.INTEGER, | |
| allowNull: false, | |
| primaryKey: true, | |
| field: 'Id' | |
| }, | |
| description: { | |
| type: DataTypes.STRING(100), | |
| allowNull: false, | |
| field: 'Description' | |
| } | |
| }, { | |
| freezeTableName: true, | |
| tableName: 'TaskTypes', | |
| createdAt: false, | |
| updatedAt: false, | |
| classMethods: { | |
| changeSchema: () => TaskType.schema('shared') | |
| } | |
| }) | |
| return TaskType | |
| } | |
| /* Full code example at https://github.com/renatoargh/data-isolation-example */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* models-user.js */ | |
| module.exports = (sequelize, DataTypes) => { | |
| const User = sequelize.define('User', { | |
| id: { | |
| type: DataTypes.UUIDV4, | |
| allowNull: false, | |
| primaryKey: true, | |
| field: 'Id' | |
| }, | |
| email: { | |
| type: DataTypes.STRING(100), | |
| allowNull: false, | |
| field: 'Email' | |
| }, | |
| password: { | |
| type: DataTypes.STRING(100), | |
| allowNull: false, | |
| field: 'Password' | |
| }, | |
| dedicatedSchema: { | |
| type: DataTypes.STRING, | |
| allowNull: true, | |
| field: 'DedicatedSchema' | |
| } | |
| }, { | |
| freezeTableName: true, | |
| tableName: 'Users', | |
| createdAt: false, | |
| updatedAt: false, | |
| classMethods: { | |
| changeSchema: () => User.schema('shared') | |
| } | |
| }) | |
| return User | |
| } | |
| /* Full code example at https://github.com/renatoargh/data-isolation-example */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* sql-create-tenant-schema.sql */ | |
| CREATE SCHEMA "tenant-1"; | |
| CREATE TABLE "tenant-1".Tasks ( | |
| Id uniqueidentifier NOT NULL, | |
| Description varchar(100) NOT NULL, | |
| Done bit NOT NULL DEFAULT ((0)), | |
| TaskTypeId int NOT NULL, | |
| UserId uniqueidentifier NOT NULL, | |
| CONSTRAINT Tasks_PK PRIMARY KEY (Id) | |
| ) | |
| INSERT INTO "tenant-1".Tasks | |
| VALUES ( | |
| NEWID(), | |
| 'Create database schemas', | |
| 0, | |
| 1, | |
| (SELECT Id FROM "shared".Users WHERE DedicatedSchema='tenant-1') | |
| ) | |
| INSERT INTO "tenant-1".Tasks | |
| VALUES ( | |
| NEWID(), | |
| 'Create express app', | |
| 0, | |
| 2, | |
| (SELECT Id FROM "shared".Users WHERE DedicatedSchema='tenant-1') | |
| ) | |
| CREATE SCHEMA "tenant-2"; | |
| CREATE TABLE "tenant-2".Tasks ( | |
| Id uniqueidentifier NOT NULL, | |
| Description varchar(100) NOT NULL, | |
| Done bit NOT NULL DEFAULT ((0)), | |
| TaskTypeId int NOT NULL, | |
| UserId uniqueidentifier NOT NULL, | |
| CONSTRAINT Tasks_PK PRIMARY KEY (Id) | |
| ) | |
| INSERT INTO "tenant-2".Tasks | |
| VALUES ( | |
| NEWID(), | |
| 'Create tables', | |
| 0, | |
| 1, | |
| (SELECT Id FROM "shared".Users WHERE DedicatedSchema='tenant-2') | |
| ) | |
| INSERT INTO "tenant-2".Tasks | |
| VALUES ( | |
| NEWID(), | |
| 'Create the UI', | |
| 0, | |
| 3, | |
| (SELECT Id FROM "shared".Users WHERE DedicatedSchema='tenant-2') | |
| ) | |
| /* Full code example at https://github.com/renatoargh/data-isolation-example */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* sql-stored-procedure-migration-example.js */ | |
| const serialQueryExecuter = require('../serialQueryExecuter') | |
| module.exports = { | |
| description: 'Creates `ListTasks` procedure and `ClosedTasks` view', | |
| up: (schema, sequelize) => { | |
| const createProcedure = ` | |
| CREATE PROCEDURE [${schema}].ListTasks AS | |
| BEGIN | |
| SELECT * FROM [${schema}].Tasks | |
| END | |
| ` | |
| const createView = ` | |
| CREATE VIEW [${schema}].ClosedTasks AS | |
| BEGIN | |
| SELECT * FROM [${schema}].Tasks WHERE Done=1 | |
| END | |
| ` | |
| return serialQueryExecuter({ | |
| queries: [ | |
| createProcedure, | |
| createView | |
| ], | |
| sequelize: sequelize | |
| }) | |
| } | |
| } | |
| /* Full code example at https://github.com/renatoargh/data-isolation-example */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment