Created
September 11, 2018 21:04
-
-
Save asciimike/b79a9a1038037dd046b0ff627fe9e286 to your computer and use it in GitHub Desktop.
Example of a "serverless" system
This file contains 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
function logger(string) { | |
console.log("Logging [" + Date.now() + "]: " + string); | |
} |
This file contains 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
{ | |
"/users/:userId": { | |
"GET": { | |
"condition": false, | |
"action": "logger('getting a userId')" | |
}, | |
"PUT": { | |
"condition": "userId == req.auth.sub && data.name != null", | |
"action": "logger('putting a userId')" | |
} | |
} | |
} |
This file contains 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
var bodyParser = require('body-parser'); | |
var jwt_decode = require('jwt-decode'); | |
var express = require('express'); | |
var app = express(); | |
var utils = require('./utils.js'); | |
var RULES_FILE = 'rules.json'; | |
var ACTIONS_FILE = 'actions.js'; | |
// Authentication middleware | |
var authenticate = function(req, res, next) { | |
// Parse JWT if one exists | |
var token = req.query.token || '' | |
var auth; | |
if (token) { | |
auth = jwt_decode(token); | |
} | |
// Validate the JWT, left as an exercise for the reader | |
// Check that it's not expired | |
// Check the signature (cert from the issuer) | |
// If malformed or incorrect, send 401 | |
if (!auth) { | |
return res.status(401).jsonp({ error: 'Unauthenticated' }); | |
} | |
req.auth = auth; | |
next(); | |
}; | |
app.use(authenticate); | |
// Admin credential checks | |
function admin(req, res, next) { | |
if (req.auth.isAdmin != true) { | |
return res.status(401).jsonp({ error: 'Operation requires admin credentials' }); | |
} | |
next(); | |
} | |
// Handle action uploads | |
app.put('/.actions', admin, bodyParser.text({ type: 'text/javascript' }), function(req, res) { | |
utils.putFile(ACTIONS_FILE, new Buffer(req.body)).then(function() { | |
return res.status(200).jsonp("ok"); | |
}).catch(function(error) { | |
// Handle bad action deploys | |
return res.status(500).jsonp({ error: error }); | |
}); | |
}); | |
app.use(bodyParser.json()); | |
// Handle rules uploads | |
app.put('/.rules', admin, function(req, res) { | |
utils.putFile(RULES_FILE, JSON.stringify(req.body)).then(function() { | |
return res.status(200).jsonp("ok"); | |
}).catch(function(error) { | |
// Handle bad rules deploys | |
return res.status(500).jsonp({ error: error }); | |
}); | |
}); | |
// Authorization middleware | |
var authorize = function(req, res, next) { | |
// Fetch the rules | |
utils.getFile(RULES_FILE).then(function(rulesData) { | |
var rules = JSON.parse(rulesData); | |
// Add captured variables to the environment | |
var env = req.params; | |
env.req = req; | |
env.data = req.body; | |
// Find the correct condition, pass it along | |
var method = req.method || ''; | |
var path = req.route.path || ''; | |
var condition = rules[path][method]['condition'] || false; | |
req.rule_condition = condition; | |
req.rule_env = env; | |
// Find the correct action, pass it along | |
var action = rules[path][method]['action'] || ''; | |
req.rule_action = action; | |
// Eval the proper rule | |
var result = utils.pureEval(condition, env); | |
// If not allowed, send 403 | |
if (!result) { | |
return res.status(403).jsonp({ error: 'Unauthorized' }); | |
} | |
next(); | |
}).catch(function(error) { | |
// Handle bad rules fetches | |
return res.status(500).jsonp({ error: error }); | |
}); | |
} | |
// Action middleware | |
function act(req, res, next) { | |
// Fetch the action code | |
utils.getFile(ACTIONS_FILE).then(function(actions){ | |
// Construct our action | |
var action = actions + ";" + req.rule_action + ";" | |
// Eval the appropriate action in our environment | |
utils.pureEval(action, req.rule_env); | |
// Return 200, we're done! | |
return res.status(200).jsonp("ok"); | |
}).catch(function(error) { | |
// Handle bad action fetches | |
return res.status(500).jsonp({ error: error }); | |
}); | |
} | |
// Provided app logic | |
// Get a user profile | |
app.get('/users/:userId', authorize, function(req, res, next) { | |
var profileFile = "users/" + req.params.userId + ".json"; | |
utils.getFile(profileFile).then(function(userProfile) { | |
res.send(userProfile); | |
next(); | |
}).catch(function(error) { | |
// Handle bad profile fetches | |
return res.status(500).jsonp({ error: error }); | |
}); | |
}, act); | |
// Set a user profile | |
app.put('/users/:userId', authorize, function(req, res, next) { | |
var profileFile = "users/" + req.params.userId + ".json"; | |
utils.putFile(profileFile, JSON.stringify(req.body)).then(function() { | |
next(); | |
}).catch(function(error) { | |
// Handle bad profile puts | |
return res.status(500).jsonp({ error: error }); | |
}); | |
}, act); | |
// App server | |
var server = app.listen(process.env.PORT || '8080', function () { | |
console.log('App listening on port %s', server.address().port); | |
}); |
This file contains 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
# test getting a user profile | |
curl http://localhost:8080/users/mike\?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtaWtlIiwiZXhwIjoxMzkzMjg2ODkzLCJpYXQiOjEzOTMyNjg4OTN9.cTzfsadm223EmbhjyLLl_6k3EywTii3E0eM8OiL3pqs | |
# test putting a user profile | |
curl \ | |
-X PUT \ | |
-d '{"name": "mike"}' \ | |
-H "content-type: application/json" \ | |
http://localhost:8080/users/mike\?token\=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtaWtlIiwiZXhwIjoxMzkzMjg2ODkzLCJpYXQiOjEzOTMyNjg4OTN9.cTzfsadm223EmbhjyLLl_6k3EywTii3E0eM8OiL3pqs | |
# deploy rules | |
curl \ | |
-X PUT \ | |
-d @rules.json \ | |
-H "content-type: application/json" \ | |
http://localhost:8080/.rules\?token\=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc0FkbWluIjp0cnVlLCJzdWIiOiJtaWtlIiwiZXhwIjoxMzkzMjg2ODkzLCJpYXQiOjEzOTMyNjg4OTN9.uhnypJQyvZgX-37c0vKFP3b-KRSTnX24pso4_AMV394 | |
# deploy actions | |
curl \ | |
-X PUT \ | |
-d @actions.js \ | |
-H "content-type: text/javascript" \ | |
http://localhost:8080/.actions\?token\=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc0FkbWluIjp0cnVlLCJzdWIiOiJtaWtlIiwiZXhwIjoxMzkzMjg2ODkzLCJpYXQiOjEzOTMyNjg4OTN9.uhnypJQyvZgX-37c0vKFP3b-KRSTnX24pso4_AMV394 |
This file contains 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
var gcloud = require('google-cloud'); | |
var storage = gcloud.storage({ | |
projectId: '<your-project-id>', | |
}); | |
var BUCKET_NAME = '<your-project-id>.appspot.com'; | |
// Fetch file from GCS | |
function getFile(filename) { | |
return new Promise(function(resolve, reject) { | |
storage.bucket(BUCKET_NAME).file(filename).download(function(error, data) { | |
if (error) { | |
reject (error); | |
} | |
resolve(data); | |
}); | |
}); | |
} | |
// Upload file to GCS | |
function putFile(filename, data) { | |
return new Promise(function(resolve, reject) { | |
var uploadStream = storage.bucket(BUCKET_NAME).file(filename).createWriteStream(); | |
uploadStream.on('error', function(error) { | |
reject(error); | |
}).on('finish', function() { | |
resolve(); | |
}); | |
uploadStream.write(data); | |
uploadStream.end(); | |
}); | |
} | |
// Eval an expression with additional context | |
function pureEval(expr, ctx) { | |
with (ctx) { | |
return eval(expr); | |
} | |
} | |
module.exports = { | |
pureEval: pureEval, | |
getFile: getFile, | |
putFile: putFile, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment