Last active
April 19, 2018 19:24
-
-
Save jda0/5c0fc6aacf1891ced869 to your computer and use it in GitHub Desktop.
flyt.server
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
### | |
REDIS SCHEMA | |
------------ | |
SET @domain.al@email key | |
STRING @domain.ae@email token | |
HASH @domain.as@key A acl, S secret | |
HASH @domain.u@email N name, A acl, [T trustee], [X delete] | |
ZSET @domain.uf@email reminder_timestamp rid | |
ZSET @domain.su timestamp email | |
HASH @domain.rb@rid T timestamp, U update_timestamp, C type, B body, A authors (CSV) | |
HASH @domain.rc@cid I rid, T timestamp, B body, A authors | |
ZSET @domain.rs@rid score pid | |
HASH @domain.p@pid N name, G gid, S score, S@tp score@tp | |
ZSET @domain.pf@gid update_timestamp rid | |
SET @domain.pl@gid pid | |
HASH @domain.g@gid N name, C colour, S score, S@tp score@tp | |
ZSET @domain.gf@gid.@tp update_timestamp rid | |
SET @domain.gl gid | |
STRING @domain.cr next_rid | |
STRING @domain.cc next_cid | |
STRING @domain.cp next_pid | |
STRING @domain.cg next_gid | |
LIST @domain.tl timestamp+time_interval | |
STRING @domain.ti time_interval (days) | |
SET d domain | |
### | |
config = require './config' | |
bodyParser = require 'body-parser' | |
crypto = require 'crypto' | |
express = require 'express' | |
mandrill = require 'mandrill-api/mandrill' | |
moment = require 'moment' | |
redis = require 'ioredis' | |
validator = require 'validator' | |
app = express() | |
db = new Redis | |
port: config.REDIS_PORT or 6379 | |
host: config.REDIS_HOST or '127.0.0.1' | |
password: config.REDIS_PASS | |
db: config.REDIS_DB or 0 | |
email = new mandrill.Mandrill config.MANDRILL_KEY | |
json = bodyParser.json() | |
auth = (acl) -> | |
return ((req, res, next) -> | |
if req.params.domain and validator.isAlphanumeric req.params.domain | |
if req.body.email and validator.isEmail req.body.email | |
if req.body.api_key?.length is config.API_SECRET_LENGTH * 2 and | |
validator.isHexadecimal req.body.api_key and | |
req.body.api_secret?.length is config.API_SECRET_LENGTH * 2 and | |
validator.isHexadecimal req.body.api_secret | |
db.multi() | |
.sismember "#{req.params.domain}.al#{req.body.email}", | |
req.body.api_key | |
.hmget "#{req.params.domain}.as#{req.body.api_key}", 'S', 'A' | |
.exec (e, r) -> | |
if e | |
return res.status(500).json error: 'db_error' | |
if r[0][1] is 1 and r[1][1] is req.body.api_secret | |
if not acl or acl is r[1][2] | |
db.expire "#{req.params.domain}.as#{req.body.api_key}", | |
config.API_KEY_EXPIRE, (e, r) -> | |
if e or r isnt 1 | |
return res.status(500).json error: 'db_error' | |
next() | |
else | |
return res.status(401).json error: 'insufficient_permissions' | |
else | |
return res.status(401).json error: 'bad_credentials' | |
else if req.body.token?.length is config.ETOKEN_LENGTH and | |
validator.isHexadecimal req.body.token | |
db.get "#{req.params.domain}.ae#{req.body.email}", (e, r) -> | |
if e | |
return res.status(500).json error: 'db_error' | |
if r and r is req.body | |
user = {} | |
db.hmget "#{req.params.domain}.u#{req.body.email}", | |
'N', 'A', 'T', 'X' (e, r) -> | |
if e | |
return res.status(500).json 'db_error' | |
if r[3] | |
db.multi() | |
.sismember 'd', req.params.domain | |
.expire "#{req.params.domain}.u#{req.body.email}", | |
config.NEW_USER_EXPIRE | |
.hdel "#{req.params.domain}.u#{req.body.email}", 'X' | |
.zadd "#{req.params.domain}.su", | |
moment().utc().valueOf(), req.body.email | |
.exec (e, r) -> | |
if e | |
return res.status(500).json error: 'db_error' | |
if not r[0][1] | |
db.multi() | |
.persist "#{req.params.domain}.ti" | |
.set "#{req.params.cr}", 1 | |
.set "#{req.params.cc}", 1 | |
.set "#{req.params.cp}", 1 | |
.set "#{req.params.cg}", 1 | |
.exec (e, r) -> | |
if e | |
return res.status(500).json error: 'db_error' | |
if r[2] | |
user.name: r[0] | |
user.acl: r[1] | |
crypto.randomBytes config.API_SECRET_LENGTH, (e, b) -> | |
if e | |
user.api_key = crypto.pseudoRandomBytes(config.API_KEY_LENGTH) | |
.toString 'hex' | |
else | |
user.api_key = b.toString 'hex' | |
crypto.randomBytes config.API_SECRET_LENGTH, (e, b) -> | |
if e | |
user.api_secret = crypto.pseudoRandomBytes(config.API_SECRET_LENGTH) | |
.toString 'hex' | |
else | |
user.api_secret = b.toString 'hex' | |
db.multi() | |
.del "#{req.params.domain}.ae#{req.body.email}" | |
.hmset "#{req.params.domain}.as#{user.api_key}", | |
'A', user.acl, | |
'S', user.api_secret | |
.expire "#{req.params.domain}.as#{user.api_key}", | |
config.API_KEY_EXPIRE | |
.sadd "#{req.params.domain}.al#{req.body.email}", | |
user.api_key | |
.exec (e, r) -> | |
if e | |
return res.status(500).json error: 'db_error' | |
return res.json result: user | |
else | |
return res.status(401).json 'untrusted' | |
else | |
return res.status(401).json error: 'bad_token' | |
else | |
db.hget "#{req.params.domain}.u#{req.body.email}", 'N', (e, r) -> | |
if e | |
return res.status(500).json error: 'db_error' | |
if r | |
return sendToken req.params.domain, req.body.email, r, res | |
else | |
return res.status(401).json error: 'not_found' | |
else | |
return res.status(401).json error: 'bad_credentials' | |
else | |
return res.status(400).json error: 'bad_domain' | |
)() | |
sendToken = (domain, email, name, res) -> | |
crypto.randomBytes config.API_SECRET_LENGTH, (e, b) -> | |
if e | |
t = crypto.pseudoRandomBytes(config.API_KEY_LENGTH) | |
.toString 'hex' | |
else | |
t = b.toString 'hex' | |
db.setex "#{domain}.ae#{email}", | |
config.ETOKEN_EXPIRE, t, (e, r) -> | |
if e | |
return res.status(500).json error: 'db_error' | |
app.render 'email.jade', | |
logo: "#{req.protocol}://#{req.get 'host'}/static/asset/elogo.png" | |
name: name.replace(/\ /g, '\u00a0') | |
key: key | |
expiry: expiry.format('ddd Do MMMM, ha [UTC]') | |
link: "#{req.protocol}://#{req.get 'host'}/?i= | |
#{encodeURIComponent email}&key=#{key}" | |
, (er, render) -> | |
edata = | |
text: "Welcome back, #{name}.\n\n | |
Your key is #{key}, and is valid until | |
#{moment().utc().add(1, 'days').format 'ddd Do MMMM, ha'}.\n | |
You can enter the system directly here: | |
#{req.protocol}://#{req.get 'host'}/auth?i=#{encodeURIComponent email}&key=#{key}\n\n | |
Kind regards,\n | |
James Daly, Developer, Flyt" | |
from_name: 'Flyt' | |
from_email: config.MANDRILL_EMAIL | |
to: [ | |
name: r | |
email: req.body.email | |
] | |
subject: 'Login to Flyt' | |
important: false | |
tags: ['flyt.token', "flyt:#{domain}"] | |
if not er | |
edata.html = render | |
email.messages.send message: edata, (r) -> | |
if r[0].status is 'sent' | |
return res.json result: 'sent' | |
else | |
return res.status(500).json | |
error: 'send_error' | |
result: r[0].status + ' ' + r[0].reject_reason | |
app.post 'api/:domain/auth', json, auth | |
app.post 'api/:domain/register', json, (req, res) -> | |
if req.params.domain and validator.isAlphanumeric req.params.domain | |
if req.body.email and validator.isEmail req.body.email and | |
req.body.name?.length > 3 and req.body.acl and | |
(req.body.name.split(' ').every (v) -> validator.isAlpha v) and | |
validator.isInt req.body.acl | |
db.exists "#{req.params.domain}.u#{req.body.email}", (e, r) -> | |
if e | |
return res.status(500).json error: 'db_error' | |
if r | |
return res.status(401).json error: 'user_exists' | |
else | |
db.multi() | |
.hmset "#{req.params.domain}.u#{req.body.email}", | |
'N', req.body.name, | |
'A', req.body.acl, | |
'X', 1 | |
.expire "#{req.params.domain}.u#{req.body.email}", | |
config.ETOKEN_EXPIRE + 3600 | |
.exec (e, r) -> | |
if e | |
return res.status(500).json error: 'db_error' | |
else | |
return sendToken req.params.domain, | |
req.body.email, req.body.name, res | |
else | |
return res.status(401).json error: 'bad_credentials' | |
else | |
return res.status(400).json error: 'bad_domain' | |
app.post 'api/:domain/trust', json, auth(config.ACL_FLAGS.TRUST_USERS), (req, res) -> | |
if req.body.user and validator.isEmail req.body.user | |
db.multi() | |
.hsetnx "#{req.params.domain}.u#{req.body.user}", 'T', req.body.email | |
.persist "#{req.params.domain}.u#{req.body.user}" | |
.exec (e, r)-> | |
if e | |
return res.status(500).json error: 'db_error' | |
else | |
return res.json result: 'OK' | |
app.post 'api/:domain/setup', json, (req, res) -> | |
if req.params.domain and validator.isAlphanumeric req.params.domain | |
db.sismember 'd', req.params.domain, (e, r) -> | |
if e | |
return res.status(500).json error: 'db_error' | |
if r | |
return res.status(400).json error: 'domain_exists' | |
else | |
if req.body.time_interval and validator.isInt req.params.time_interval | |
if req.body.email and validator.isEmail req.body.email and | |
req.body.name?.length > 3 and | |
(req.body.name.split(' ').every (v) -> validator.isAlpha v) | |
db.multi() | |
.setex "#{req.params.domain}.ti", | |
config.ETOKEN_EXPIRE + 3600, req.body.time_interval | |
.hmset "#{req.params.domain}.u#{req.body.email}", | |
'N', req.body.name, | |
'A', config.roles['Admin'], | |
'T', req.body.email | |
'X', 1 | |
.expire "#{req.params.domain}.u#{req.body.email}", | |
config.ETOKEN_EXPIRE + 3600 | |
.exec (e, r) -> | |
if e | |
return res.status(500).json error: 'db_error' | |
else | |
return sendToken req.params.domain, | |
req.body.email, req.body.name, res | |
else | |
return res.status(401).json error: 'bad_credentials' | |
else | |
return res.status(400).json error: 'bad_info' | |
else | |
return res.status(400).json error: 'bad_domain' | |
app.use '/static', express.static '/public' | |
app.get '/:domain', (req, res) -> | |
db.sismember 'd', req.params.domain, (e, r) -> | |
if e | |
return res.status(500).sendfile '/public/500.html' | |
if r | |
res.sendfile '/public/app.html' | |
else | |
res.sendfile '/public/bad_domain.html' | |
app.get '/', (req, res) -> | |
res.sendfile '/public/index.html' | |
app.listen process.env.PORT or config.DEFAULT_PORT or 80 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment