├── microservices
├── .gitignore
├── auth-proxy
│ ├── app.js
│ ├── echo-server.js
│ └── package.json
├── gateway
│ ├── .gitignore
│ ├── app.js
│ ├── logger.js
│ ├── nginx.conf
│ └── package.json
├── microservice-1-webtask
│ ├── logger.js
│ └── server.js
└── microservice-1
│ ├── .gitignore
│ ├── package.json
│ ├── server.js
│ └── tickets.json
└── twofa
├── Flowchart.png
├── Flowchart.xml
└── backend
├── .gitignore
├── app.js
├── bin
└── www
├── package.json
├── public
└── stylesheets
│ └── style.css
├── routes
└── routes.js
├── users.json
└── views
├── login.hjs
├── strings.json
├── totp-input.hjs
└── totp-setup.hjs
/microservices/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/microservices/auth-proxy/app.js:
--------------------------------------------------------------------------------
1 | var http = require('http');
2 | var httpProxy = require('http-proxy');
3 | var jwt = require('jsonwebtoken');
4 |
5 | // User data. Valid users data that normally gets loaded from a database.
6 | var users = {
7 | "user1": {
8 | username: "user1",
9 | password: "user1pass"
10 | },
11 | "user2": {
12 | username: "user2",
13 | password: "user2pass"
14 | }
15 | };
16 |
17 | var secretKey = "super secret jwt key";
18 | var issuerStr = "Sample API Proxy"
19 |
20 | var proxy = httpProxy.createProxyServer({});
21 |
22 | function send401(res) {
23 | res.statusCode = 401;
24 | res.end();
25 | }
26 |
27 | function doLogin(req, res) {
28 | req.on('data', function(chunk) {
29 | try {
30 | var loginData = JSON.parse(chunk);
31 | var user = users[loginData.username];
32 | if(user && user.password === loginData.password) {
33 | var token = jwt.sign({}, secretKey, {
34 | subject: user.username,
35 | issuer: issuerStr
36 | });
37 |
38 | res.writeHeader(200, {
39 | 'Content-Length': token.length,
40 | 'Content-Type': "text/plain"
41 | });
42 | res.write(token);
43 | res.end;
44 | } else {
45 | send401(res);
46 | }
47 | } catch(err) {
48 | console.log(err);
49 | send401(res);
50 | }
51 | });
52 | }
53 |
54 | function validateAuth(data) {
55 | data = data.split(" ");
56 | if(data[0] !== "Bearer" || !data[1]) {
57 | return false;
58 | }
59 |
60 | var token = data[1];
61 | try {
62 | var payload = jwt.verify(token, secretKey);
63 | // Custom validation logic, in this case we just check that the
64 | // user exists
65 | if(users[payload.sub]) {
66 | return true;
67 | }
68 | } catch(err) {
69 | console.log(err);
70 | }
71 |
72 | return false;
73 | }
74 |
75 | var server = http.createServer(function(req, res) {
76 | if(req.url === "/login" && req.method === 'POST') {
77 | doLogin(req, res);
78 | return;
79 | }
80 |
81 | var authHeader = req.headers["authorization"];
82 | if(!authHeader || !validateAuth(authHeader)) {
83 | send401(res);
84 | return;
85 | }
86 |
87 | proxy.web(req, res, { target: "http://127.0.0.1:3001" });
88 | });
89 |
90 | console.log("Listening on port 3000");
91 | server.listen(3000);
92 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/microservices/auth-proxy/echo-server.js:
--------------------------------------------------------------------------------
1 | var http = require('http');
2 |
3 | http.createServer(function(req, res) {
4 | console.log(req.url);
5 | console.log(req.headers);
6 |
7 | req.on('data', function(chunk) {
8 | console.log(chunk);
9 | });
10 |
11 | res.statusCode = 200;
12 | res.end();
13 | }).listen(3001);
14 |
15 | console.log("Listening on port 3001");
16 |
17 |
18 |
--------------------------------------------------------------------------------
/microservices/auth-proxy/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "auth-proxy",
3 | "version": "1.0.0",
4 | "description": "Simple authentication proxy using JWT",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "Sebastian Peyrott",
10 | "license": "UNLICENSED",
11 | "dependencies": {
12 | "http-proxy": "^1.11.1",
13 | "jsonwebtoken": "^5.0.5"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/microservices/gateway/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | db
3 |
--------------------------------------------------------------------------------
/microservices/gateway/app.js:
--------------------------------------------------------------------------------
1 | var http = require('http');
2 | var url = require('url');
3 | var jwt = require('jsonwebtoken');
4 | var mongoose = require('mongoose');
5 | var morgan = require('morgan');
6 | var sprintf = require('sprintf');
7 | var Q = require('q');
8 | var _ = require('underscore');
9 | var amqp = require('amqp');
10 |
11 | var logger = require('./logger');
12 |
13 | var amqpHost = process.env.AMQP_HOST || 'amqp://gateway:gateway@127.0.0.1:5672';
14 |
15 | var httpLogger = morgan('combined', { stream: logger.stream });
16 |
17 | function toBase64(obj) {
18 | return new Buffer(JSON.stringify(obj)).toString('base64');
19 | }
20 |
21 | var amqpConn = amqp.createConnection({url: amqpHost});
22 |
23 | var userDb = mongoose.createConnection(process.env.USER_DB_URL ||
24 | 'mongodb://guest:guest@localhost:21017/test/users');
25 | var servicesDb = mongoose.createConnection(process.env.SERVICES_DB_URL ||
26 | 'mongodb://guest:guest@localhost:21017/test/services');
27 |
28 | // Mongoose user model
29 | var User = userDb.model('User', new mongoose.Schema ({
30 | username: String,
31 | password: String,
32 | roles: [ String ]
33 | }));
34 |
35 | var Service = servicesDb.model('Service', new mongoose.Schema ({
36 | name: String,
37 | url: String,
38 | endpoints: [ new mongoose.Schema({
39 | type: String,
40 | url: String
41 | }) ],
42 | authorizedRoles: [ String ]
43 | }));
44 |
45 | var secretKey = "super secret jwt key";
46 | var issuerStr = "Sample API Gateway"
47 |
48 | function send401(res) {
49 | res.statusCode = 401;
50 | res.end();
51 | }
52 |
53 | function send500(res) {
54 | res.statusCode = 500;
55 | res.end();
56 | }
57 |
58 | /* Get all pending data from HTTP request */
59 | function getData(req) {
60 | var result = Q.defer();
61 |
62 | var data = "";
63 | req.on('data', function(data_) {
64 | data += data_;
65 | if(data.length >= (1024 * 1024)) {
66 | data = "";
67 | result.reject("Bad request");
68 | }
69 | });
70 |
71 | req.on('end', function() {
72 | if(result.promise.isPending()) {
73 | try {
74 | result.resolve(data);
75 | } catch(err) {
76 | result.reject(err.toString());
77 | }
78 | }
79 | });
80 |
81 | return result.promise;
82 | }
83 |
84 | /*
85 | * Simple login: returns a JWT if login data is valid.
86 | */
87 | function doLogin(req, res) {
88 | getData(req).then(function(data) {
89 | try {
90 | var loginData = JSON.parse(data);
91 | User.findOne({ username: loginData.username }, function(err, user) {
92 | if(err) {
93 | logger.error(err);
94 | send401(res);
95 | return;
96 | }
97 |
98 | if(user.password === loginData.password) {
99 | var token = jwt.sign({}, secretKey, {
100 | subject: user.username,
101 | issuer: issuerStr
102 | });
103 |
104 | res.writeHeader(200, {
105 | 'Content-Length': token.length,
106 | 'Content-Type': "text/plain"
107 | });
108 | res.write(token);
109 | res.end();
110 | } else {
111 | send401(res);
112 | }
113 | }, 'users');
114 | } catch(err) {
115 | logger.error(err);
116 | send401(res);
117 | }
118 | }, function(err) {
119 | logger.error(err);
120 | send401(res);
121 | });
122 | }
123 |
124 | /*
125 | * Authentication validation using JWT. Strategy: find existing user.
126 | */
127 | function validateAuth(data, callback) {
128 | if(!data) {
129 | callback(null);
130 | return;
131 | }
132 |
133 | data = data.split(" ");
134 | if(data[0] !== "Bearer" || !data[1]) {
135 | callback(null);
136 | return;
137 | }
138 |
139 | var token = data[1];
140 | try {
141 | var payload = jwt.verify(token, secretKey);
142 | // Custom validation logic, in this case we just check that the
143 | // user exists
144 | User.findOne({ username: payload.sub }, function(err, user) {
145 | if(err) {
146 | logger.error(err);
147 | } else {
148 | callback({
149 | user: user,
150 | jwt: payload
151 | });
152 | }
153 | });
154 | } catch(err) {
155 | logger.error(err);
156 | callback(null);
157 | }
158 | }
159 |
160 | /*
161 | * Internal HTTP request, auth data is passed in headers.
162 | */
163 | function httpSend(oldReq, endpoint, data, deferred, isGet) {
164 | var parsedEndpoint = url.parse(endpoint);
165 |
166 | var options = {
167 | hostname: parsedEndpoint.hostname,
168 | port: parsedEndpoint.port,
169 | path: parsedEndpoint.path,
170 | method: isGet ? 'GET' : 'POST',
171 | headers: isGet ? {} : {
172 | 'Content-Type': 'application/json',
173 | 'Content-Length': data.length,
174 | 'GatewayAuth': toBase64(oldReq.authPayload)
175 | }
176 | };
177 |
178 | var req = http.request(options, function(res) {
179 | var resData = "";
180 | res.on('data', function (chunk) {
181 | resData += chunk;
182 | });
183 | res.on('end', function() {
184 | try {
185 | var json = JSON.parse(resData);
186 | deferred.resolve(json);
187 | } catch(err) {
188 | deferred.reject({
189 | req: oldReq,
190 | endpoint: endpoint,
191 | message: 'Invalid data format: ' + err.toString()
192 | });
193 | }
194 | });
195 | });
196 |
197 | req.on('error', function(e) {
198 | deferred.reject({
199 | req: oldReq,
200 | endpoint: endpoint,
201 | message: e.toString()
202 | });
203 | });
204 |
205 | if(!isGet && data) {
206 | req.write(data);
207 | }
208 | req.end();
209 | }
210 |
211 | /*
212 | * Internal HTTP request
213 | */
214 | function httpPromise(req, endpoint, isGet) {
215 | var result = Q.defer();
216 |
217 | function reject(msg) {
218 | result.reject({
219 | req: req,
220 | endpoint: endpoint,
221 | message: msg
222 | });
223 | }
224 |
225 | if(isGet) {
226 | httpSend(req, endpoint, null, result, isGet);
227 | } else {
228 | getData(req).then(function(data) {
229 | httpSend(req, endpoint, data, result, isGet);
230 | }, function(err) {
231 | reject(err);
232 | });
233 | }
234 |
235 | return result.promise;
236 | }
237 |
238 | function amqpSend(req, endpoint, data, result) {
239 | amqpConn.queue('', {
240 | exclusive: true
241 | }, function(queue) {
242 | queue.bind('#');
243 |
244 | queue.subscribe({ ack: true, prefetchCount: 1 },
245 | function(message, headers, deliveryInfo, messageObject) {
246 | messageObject.acknowledge();
247 |
248 | try {
249 | var json = JSON.parse(message);
250 | deferred.resolve(json);
251 | } catch(err) {
252 | deferred.reject({
253 | req: req,
254 | endpoint: endpoint,
255 | message: 'Invalid data format: ' + err.toString()
256 | });
257 | }
258 | }
259 | );
260 |
261 | //Default exchange
262 | var exchange = amqpConn.exchange();
263 | //Send data
264 | exchange.publish(endpoint, data ? data : {}, {
265 | headers: {
266 | 'GatewayAuth': toBase64(req.authPayload),
267 | },
268 | deliveryMode: 1, //non-persistent
269 | replyTo: queue.name,
270 | mandatory: true,
271 | immediate: true
272 | }, function(err) {
273 | if(err) {
274 | deferred.reject({
275 | req: req,
276 | endpoint: endpoint,
277 | message: 'Could not publish message to the default ' +
278 | 'AMQP exchange'
279 | });
280 | }
281 | });
282 | });
283 | }
284 |
285 | /*
286 | * Internal AMQP request
287 | */
288 | function amqpPromise(req, endpoint, isGet) {
289 | var result = Q.defer();
290 |
291 | function reject(msg) {
292 | result.reject({
293 | req: req,
294 | endpoint: endpoint,
295 | message: msg
296 | });
297 | }
298 |
299 | if(req.method === 'POST') {
300 | getData(req).then(function(data) {
301 | amqpSend(req, endpoint, data, result);
302 | }, function(err) {
303 | reject(err);
304 | });
305 | } else {
306 | amqpSend(req, endpoint, null, result);
307 | }
308 |
309 | return result.promise;
310 | }
311 |
312 | function roleCheck(user, service) {
313 | var intersection = _.intersection(user.roles, service.authorizedRoles);
314 | return intersection.length === service.authorizedRoles.length;
315 | }
316 |
317 | /*
318 | * Parses the request and dispatches multiple concurrent requests to each
319 | * internal endpoint. Results are aggregated and returned.
320 | */
321 | function serviceDispatch(req, res) {
322 | var parsedUrl = url.parse(req.url);
323 |
324 | Service.findOne({ url: parsedUrl.pathname }, function(err, service) {
325 | if(err) {
326 | logger.error(err);
327 | send500(res);
328 | return;
329 | }
330 |
331 | var authorized = roleCheck(req.context.authPayload.user, service);
332 | if(!authorized) {
333 | send401(res);
334 | return;
335 | }
336 |
337 | // Fanout all requests to all related endpoints.
338 | // Results are aggregated (more complex strategies are possible).
339 | var promises = [];
340 | service.endpoints.forEach(function(endpoint) {
341 | logger.debug(sprintf('Dispatching request from public endpoint ' +
342 | '%s to internal endpoint %s (%s)',
343 | req.url, endpoint.url, endpoint.type));
344 |
345 | switch(endpoint.type) {
346 | case 'http-get':
347 | case 'http-post':
348 | promises.push(httpPromise(req, endpoint.url,
349 | endpoint.type === 'http-get'));
350 | break;
351 | case 'amqp':
352 | promises.push(amqpPromise(req, endpoint.url));
353 | break;
354 | default:
355 | logger.error('Unknown endpoint type: ' + endpoint.type);
356 | }
357 | });
358 |
359 | //Aggregation strategy for multiple endpoints.
360 | Q.allSettled(promises).then(function(results) {
361 | var responseData = {};
362 |
363 | results.forEach(function(result) {
364 | if(result.state === 'fulfilled') {
365 | responseData = _.extend(responseData, result.value);
366 | } else {
367 | logger.error(result.reason.message);
368 | }
369 | });
370 |
371 | res.setHeader('Content-Type', 'application/json');
372 | res.end(JSON.stringify(responseData));
373 | });
374 | }, 'services');
375 | }
376 |
377 | var server = http.createServer(function(req, res) {
378 | httpLogger(req, res, function(){});
379 |
380 | // Login endpoint
381 | if(req.url === "/login" && req.method === 'POST') {
382 | doLogin(req, res);
383 | return;
384 | }
385 |
386 | // Authentication
387 | var authHeader = req.headers["authorization"];
388 | validateAuth(authHeader, function(authPayload) {
389 | if(!authPayload) {
390 | send401(res);
391 | return;
392 | }
393 |
394 | // We keep the authentication payload to pass it to
395 | // microservices decoded.
396 | req.context = {
397 | authPayload: authPayload
398 | };
399 |
400 | serviceDispatch(req, res);
401 | });
402 | });
403 |
404 | logger.info("Listening on port 3000");
405 | server.listen(3000);
406 |
407 |
408 |
409 |
410 |
--------------------------------------------------------------------------------
/microservices/gateway/logger.js:
--------------------------------------------------------------------------------
1 | var winston = require('winston');
2 | var winstonAmqp = require('winston-amqp');
3 |
4 | var amqpHost = process.env.AMQP_HOST || 'amqp://gateway:gateway@127.0.0.1:5672';
5 |
6 | winston.emitErrs = true;
7 | var logger = new winston.Logger({
8 | transports: [
9 | new winston.transports.Console({
10 | timestamp: true,
11 | level: process.env.GATEWAY_LOG_LEVEL || 'debug',
12 | handleExceptions: false,
13 | json: false,
14 | colorize: true
15 | }),
16 | new winstonAmqp.AMQP({
17 | name: 'gateway',
18 | level: process.env.GATEWAY_LOG_LEVEL || 'debug',
19 | host: amqpHost,
20 | exchange: 'log',
21 | routingKey: 'gateway'
22 | })
23 | ],
24 | exitOnError: false
25 | });
26 |
27 | logger.stream = {
28 | write: function(message, encoding) {
29 | logger.debug(message.replace(/\n$/, ''));
30 | }
31 | };
32 |
33 | module.exports = logger;
34 |
35 |
36 |
--------------------------------------------------------------------------------
/microservices/gateway/nginx.conf:
--------------------------------------------------------------------------------
1 |
2 | #user gateway;
3 | worker_processes 1;
4 |
5 | events {
6 | worker_connections 1024;
7 | }
8 |
9 | http {
10 | include mime.types;
11 | default_type application/json;
12 |
13 | keepalive_timeout 65;
14 |
15 | server {
16 | listen 443 ssl;
17 | server_name yourdomain.com;
18 |
19 | ssl_certificate cert.pem;
20 | ssl_certificate_key cert.key;
21 |
22 | ssl_session_cache shared:SSL:1m;
23 | ssl_session_timeout 5m;
24 |
25 | ssl_ciphers HIGH:!aNULL:!MD5;
26 | ssl_prefer_server_ciphers on;
27 |
28 | location public1.yourdomain.com {
29 | proxy_pass http://localhost:9000;
30 | }
31 |
32 | location public2.yourdomain.com {
33 | proxy_pass http://localhost:9001;
34 | }
35 |
36 | location public3.yourdomain.com {
37 | proxy_pass http://localhost:9002;
38 | }
39 | }
40 | }
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/microservices/gateway/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gateway",
3 | "version": "1.0.0",
4 | "description": "API gateway example",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "UNLICENSED",
11 | "dependencies": {
12 | "amqp": "^0.2.4",
13 | "amqp-winston": "^1.0.7",
14 | "http-proxy": "^1.11.2",
15 | "jsonwebtoken": "^5.0.5",
16 | "mongoose": "^4.1.5",
17 | "morgan": "^1.6.1",
18 | "q": "^1.4.1",
19 | "sprintf": "^0.1.5",
20 | "underscore": "^1.8.3",
21 | "winston": "^1.0.1",
22 | "winston-amqp": "0.0.3"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/microservices/microservice-1-webtask/logger.js:
--------------------------------------------------------------------------------
1 | var winston = require('winston');
2 | winston.emitErrs = true;
3 |
4 | var logger = new winston.Logger({
5 | transports: [
6 | new winston.transports.Console({
7 | timestamp: true,
8 | level: 'debug',
9 | handleExceptions: true,
10 | json: false,
11 | colorize: true
12 | })
13 | ],
14 | exitOnError: false
15 | });
16 |
17 | module.exports = logger;
18 | module.exports.stream = {
19 | write: function(message, encoding){
20 | logger.debug(message.replace(/\n$/, ''));
21 | }
22 | };
23 |
24 |
--------------------------------------------------------------------------------
/microservices/microservice-1-webtask/server.js:
--------------------------------------------------------------------------------
1 | var webtask = require('webtask-tools');
2 | var express = require('express');
3 | var morgan = require('morgan');
4 | var mongo = require('mongodb').MongoClient;
5 | var winston = require('winston');
6 |
7 | // Logging
8 | winston.emitErrs = true;
9 | var logger = new winston.Logger({
10 | transports: [
11 | new winston.transports.Console({
12 | timestamp: true,
13 | level: 'debug',
14 | handleExceptions: true,
15 | json: false,
16 | colorize: true
17 | })
18 | ],
19 | exitOnError: false
20 | });
21 |
22 | logger.stream = {
23 | write: function(message, encoding){
24 | logger.debug(message.replace(/\n$/, ''));
25 | }
26 | };
27 |
28 | // Express and middlewares
29 | var app = express();
30 | app.use(
31 | //Log requests
32 | morgan(':method :url :status :response-time ms - :res[content-length]', {
33 | stream: logger.stream
34 | })
35 | );
36 |
37 | var db;
38 | if(process.env.MONGO_URL) {
39 | mongo.connect(process.env.MONGO_URL, null, function(err, db_) {
40 | if(err) {
41 | logger.error(err);
42 | } else {
43 | db = db_;
44 | }
45 | });
46 | }
47 |
48 | app.use(function(req, res, next) {
49 | if(!db) {
50 | //Database not connected
51 | mongo.connect(process.env.MONGO_URL ||
52 | req.webtaskContext.data.MONGO_URL, null,
53 | function(err, db_) {
54 | if(err) {
55 | logger.error(err);
56 | res.sendStatus(500);
57 | } else {
58 | db = db_;
59 | next();
60 | }
61 | }
62 | );
63 | } else {
64 | next();
65 | }
66 | });
67 |
68 | // Actual query
69 | app.get('/tickets', function(req, res, next) {
70 | var collection = db.collection('tickets');
71 | collection.find().toArray(function(err, result) {
72 | if(err) {
73 | logger.error(err);
74 | res.sendStatus(500);
75 | return;
76 | }
77 | res.json(result);
78 | });
79 | });
80 |
81 | //Express to webtask adapter
82 | module.exports = require('webtask-tools').fromExpress(app);
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/microservices/microservice-1/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | db
3 |
--------------------------------------------------------------------------------
/microservices/microservice-1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "microservice-1",
3 | "version": "1.0.0",
4 | "description": "Sample microservice, queries \"tickets\"",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "UNLICENSED",
11 | "dependencies": {
12 | "express": "^4.13.3",
13 | "mongodb": "^2.0.42",
14 | "morgan": "^1.6.1",
15 | "winston": "^1.0.1"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/microservices/microservice-1/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var morgan = require('morgan');
3 | var http = require('http');
4 | var mongo = require('mongodb').MongoClient;
5 | var winston = require('winston');
6 |
7 | // Logging
8 | winston.emitErrs = true;
9 | var logger = new winston.Logger({
10 | transports: [
11 | new winston.transports.Console({
12 | timestamp: true,
13 | level: 'debug',
14 | handleExceptions: true,
15 | json: false,
16 | colorize: true
17 | })
18 | ],
19 | exitOnError: false
20 | });
21 |
22 | logger.stream = {
23 | write: function(message, encoding){
24 | logger.debug(message.replace(/\n$/, ''));
25 | }
26 | };
27 |
28 | // Express and middlewares
29 | var app = express();
30 | app.use(
31 | //Log requests
32 | morgan(':method :url :status :response-time ms - :res[content-length]', {
33 | stream: logger.stream
34 | })
35 | );
36 |
37 | var db;
38 | if(process.env.MONGO_URL) {
39 | mongo.connect(process.env.MONGO_URL, null, function(err, db_) {
40 | if(err) {
41 | logger.error(err);
42 | } else {
43 | db = db_;
44 | }
45 | });
46 | }
47 |
48 | app.use(function(req, res, next) {
49 | if(!db) {
50 | //Database not connected
51 | mongo.connect(process.env.MONGO_URL, null, function(err, db_) {
52 | if(err) {
53 | logger.error(err);
54 | res.sendStatus(500);
55 | } else {
56 | db = db_;
57 | next();
58 | }
59 | });
60 | } else {
61 | next();
62 | }
63 | });
64 |
65 | // Actual query
66 | app.get('/tickets', function(req, res, next) {
67 | var collection = db.collection('tickets');
68 | collection.find().toArray(function(err, result) {
69 | if(err) {
70 | logger.error(err);
71 | res.sendStatus(500);
72 | return;
73 | }
74 | res.json(result);
75 | });
76 | });
77 |
78 | // Standalone server setup
79 | var port = process.env.PORT || 3001;
80 | http.createServer(app).listen(port, function (err) {
81 | if (err) {
82 | logger.error(err);
83 | } else {
84 | logger.info('Listening on http://localhost:' + port);
85 | }
86 | });
87 |
88 |
89 |
--------------------------------------------------------------------------------
/microservices/microservice-1/tickets.json:
--------------------------------------------------------------------------------
1 | {"_id":{"$oid":"55e633b9fa7864d21a226052"},"assignedTo":"gonto","description":"Hey there, I would like my users to authenticate with Facebook and Twitter. I'm using AngularJS, how can I do this?","id":1000.0,"replies":[{"message":"\u003cp\u003eHi John, thanks for reaching out.\u003c/p\u003e\u003cp\u003eHave you seen our library for AngularJS?\u003c/p\u003e\u003cp\u003e\u003ca href=\"https://github.com/auth0/auth0-angular\"\u003ehttps://github.com/auth0/auth0-angular\u003c/a\u003e\u003c/p\u003e","user":"Gonto"}],"shortDescription":"Hey there, I would like my users to authenticate with....","status":"Open","title":"Social Authentication","userInitials":"JD"}
2 | {"_id":{"$oid":"55e633dcfa7864d21a226053"},"assignedTo":"","description":"Hey there, we would like to use Active Directory authentication in our iOS app, can you help?","id":1001.0,"shortDescription":"Hey there, we would like to use Active Directory...","status":"Open","title":"AD on iOS","userInitials":"KL"}
3 | {"_id":{"$oid":"55e633f0fa7864d21a226054"},"assignedTo":"","description":"Auth0 looks really useful but at the moment we're still in development phase. Is there any way we can use a free account or something similar?","id":1002.0,"replies":[{"message":"\u003cp\u003eHi Roger, thanks for reaching out.\u003c/p\u003e\u003cp\u003eOn auth0.com you can simply sign up for a free developer account. You will only need to upgrade this account to a paid account whenever you move to production.\u003c/p\u003e","user":"Gonto"}],"shortDescription":"Auth0 looks really useful but at the moment we're still in...","status":"Open","title":"Developer account?","userInitials":"R"}
4 |
--------------------------------------------------------------------------------
/twofa/Flowchart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/auth0-blog/mfa-and-microservices-blog-samples/d3fd5cc0a6a0d187a6efd12128b72eba425aa499/twofa/Flowchart.png
--------------------------------------------------------------------------------
/twofa/Flowchart.xml:
--------------------------------------------------------------------------------
1 |