-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
202 lines (174 loc) · 5.92 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
"use strict";
const METABASE_SITE_URL =
process.env.METABASE_SITE_URL || "http://localhost:3000";
const METABASE_JWT_SHARED_SECRET =
process.env.METABASE_JWT_SHARED_SECRET ||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
const METABASE_DASHBOARD_PATH =
process.env.METABASE_DASHBOARD_PATH || "/dashboard/1";
const mods = "logo=false";
/**
* Module dependencies.
*/
const express = require("express");
const hash = require("pbkdf2-password")();
const path = require("path");
const session = require("express-session");
const jwt = require("jsonwebtoken");
var app = (module.exports = express());
// config
app.set("view engine", "ejs");
app.set("views", path.join(__dirname, "views"));
// middleware
app.use(express.urlencoded({ extended: false }));
app.use(
session({
resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
secret: "shhhh, very secret",
})
);
// Session-persisted message middleware
app.use(function (req, res, next) {
var err = req.session.error;
var msg = req.session.success;
delete req.session.error;
delete req.session.success;
res.locals.message = "";
if (err) res.locals.message = '<p class="msg error">' + err + "</p>";
if (msg) res.locals.message = '<p class="msg success">' + msg + "</p>";
next();
});
// dummy database
var users = [
{
firstName: "Rene",
lastName: "Mueller",
email: "[email protected]",
accountId: 28,
accountName: "Customer-Acme",
},
{
firstName: "Cecilia",
lastName: "Stark",
email: "[email protected]",
accountId: 132,
accountName: "Customer-Fake",
},
];
// when you create a user, generate a salt
// and hash the password ('foobar' is the pass here)
hash({ password: "foobar" }, function (err, pass, salt, hash) {
if (err) throw err;
// store the salt & hash in the "db"
users.forEach((element) => {
element.salt = salt;
element.hash = hash;
});
});
function findUserbyEmail(email) {
var u = users.find((u) => u.email === email);
return u;
}
// Authenticate using our plain-object database of doom!
function authenticate(email, pass, fn) {
if (!module.parent) console.log("authenticating %s:%s", email, pass);
var user = findUserbyEmail(email);
// query the db for the given email
if (!user) return fn(null, null);
// apply the same algorithm to the POSTed password, applying
// the hash against the pass / salt, if there is a match we
// found the user
hash({ password: pass, salt: user.salt }, function (err, pass, salt, hash) {
if (err) return fn(err);
if (hash === user.hash) return fn(null, user);
fn(null, null);
});
}
function restrict(req, res, next) {
if (req.session.user) {
next();
} else {
req.session.returnTo = req.originalUrl;
req.session.error = "Access denied!";
res.redirect("/login");
}
}
const signUserToken = (user) =>
jwt.sign(
{
email: user.email,
first_name: user.firstName,
last_name: user.lastName,
account_id: user.accountId,
groups: [user.accountName],
exp: Math.round(Date.now() / 1000) + 60 * 10, // 10 minute expiration
},
METABASE_JWT_SHARED_SECRET
);
app.get("/", function (req, res) {
res.redirect("/analytics");
});
app.get("/analytics", restrict, function (req, res) {
var iframeUrl = `/sso/metabase?return_to=${METABASE_DASHBOARD_PATH}`;
res.send(
`<iframe src="${iframeUrl}" frameborder="0" width="1280" height="1000" allowtransparency></iframe>`
);
});
app.get("/logout", function (req, res) {
const mbLogoutUrl = new URL("/auth/logout", METABASE_SITE_URL);
// destroy the user's session to log them out
// will be re-created next request
req.session.destroy(function () {
// sign user out of Metabase by loading /auth/logout in a hidden iframe
res.send(`
You have been logged out. <a href="/login">Log in</a>
<iframe src="${mbLogoutUrl}" hidden></iframe>`);
});
});
app.get("/login", function (req, res) {
res.render("login");
});
app.post("/login", function (req, res, next) {
authenticate(req.body.email, req.body.password, function (err, user) {
if (err) return next(err);
if (user) {
// Regenerate session when signing in
// to prevent fixation
var returnTo = req.session.returnTo;
req.session.regenerate(function () {
// Store the user's primary key
// in the session store to be retrieved,
// or in this case the entire user object
req.session.user = user;
req.session.success =
"Authenticated as " +
user.firstName +
"" +
user.lastName +
' click to <a href="/logout">logout</a>. ' +
' click to access <a href="/analytics">analytics</a>';
res.redirect(returnTo || "/");
delete req.session.returnTo;
});
} else {
req.session.error =
"Authentication failed, please check your " +
" email and password." +
' (use "[email protected]" or "[email protected]" and password "foobar")';
res.redirect("/login");
}
});
});
app.get("/sso/metabase", restrict, (req, res) => {
const ssoUrl = new URL("/auth/sso", METABASE_SITE_URL);
ssoUrl.searchParams.set("jwt", signUserToken(req.session.user));
ssoUrl.searchParams.set("return_to", `${req.query.return_to ?? "/"}?${mods}`);
res.redirect(ssoUrl);
});
const PORT =
process.env.PORT || 8080;
if (!module.parent) {
app.listen(PORT);
console.log(`Express started serving on port ${PORT}`);
}