Skip to content

Commit 309bf38

Browse files
Readme notes and code formatting
links and formatting
2 parents ebd143d + 0bca5e5 commit 309bf38

File tree

2 files changed

+128
-137
lines changed

2 files changed

+128
-137
lines changed

README.md

+4-15
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Metabase Node.js interactive embedding sample
22

3-
Sample code for the Metabase Node.js Interactive Embedding Quickstart LINK.
3+
This repo includes sample code referenced in the [quick start guide](https://www.metabase.com/learn/customer-facing-analytics/interactive-embedding-quick-start) for setting up interactive embedding with JWT.
44

5-
You'll need a paid version of Metabase up and running. If you're not sure where to start, sign up for Metabase Cloud: Pro. LINK.
5+
You'll need a Pro or Enterprise version of Metabase up and running. If you're not sure where to start, sign up for [Pro Cloud](https://www.metabase.com/pricing).
66

77
## Set up your Metabase
88

@@ -88,20 +88,9 @@ user: [email protected]
8888
password: foobar
8989
```
9090

91-
## Set up groups
91+
## Set up groups and data sandboxing
9292

93-
TODO
94-
95-
Create groups `Customer Acme` and `Customer Fake` and configure permissions so they can access the collection in which the dashboard is located. Also, setup data sandboxing on the Invoices table filtering on `accountId`.
96-
97-
Under SSO then activate group membership syncing and map `Customer-Acme` and `Customer-Fake` to the groups you've created.
98-
8. You should be able to sign in with the two users and see the dashboard. If not, check the collection permissions for their respective groups.
99-
100-
## Set up sandboxing
101-
102-
TODO
103-
104-
Both users should be able to see the same dashboard but with different data, beacuse of sandboxing.
93+
Check out our [quick start guide](https://www.metabase.com/learn/customer-facing-analytics/interactive-embedding-quick-start) to set up interactive embedding with JWT and data sandboxing.
10594

10695
## Reporting issues
10796

index.js

+124-122
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
"use strict";
22

33
const METABASE_SITE_URL =
4-
process.env.METABASE_SITE_URL || "http://localhost:3000";
4+
process.env.METABASE_SITE_URL || "http://localhost:3000";
55
const METABASE_JWT_SHARED_SECRET =
6-
process.env.METABASE_JWT_SHARED_SECRET ||
7-
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
8-
const mods = 'logo=false&top_nav=false&search=false&new_button=false&side_nav=false&header=false&additional_info=false&breadcrumbs=false&action_buttons=false'
6+
process.env.METABASE_JWT_SHARED_SECRET ||
7+
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
8+
9+
const mods = "logo=false";
10+
911
/**
1012
* Module dependencies.
1113
*/
@@ -28,173 +30,173 @@ app.set("views", path.join(__dirname, "views"));
2830

2931
app.use(express.urlencoded({ extended: false }));
3032
app.use(
31-
session({
32-
resave: false, // don't save session if unmodified
33-
saveUninitialized: false, // don't create session until something stored
34-
secret: "shhhh, very secret",
35-
})
33+
session({
34+
resave: false, // don't save session if unmodified
35+
saveUninitialized: false, // don't create session until something stored
36+
secret: "shhhh, very secret",
37+
})
3638
);
3739

3840
// Session-persisted message middleware
3941

4042
app.use(function (req, res, next) {
41-
var err = req.session.error;
42-
var msg = req.session.success;
43-
delete req.session.error;
44-
delete req.session.success;
45-
res.locals.message = "";
46-
if (err) res.locals.message = '<p class="msg error">' + err + "</p>";
47-
if (msg) res.locals.message = '<p class="msg success">' + msg + "</p>";
48-
next();
43+
var err = req.session.error;
44+
var msg = req.session.success;
45+
delete req.session.error;
46+
delete req.session.success;
47+
res.locals.message = "";
48+
if (err) res.locals.message = '<p class="msg error">' + err + "</p>";
49+
if (msg) res.locals.message = '<p class="msg success">' + msg + "</p>";
50+
next();
4951
});
5052

5153
// dummy database
5254

5355
var users = [
54-
{
55-
firstName: "Rene",
56-
lastName: "Mueller",
57-
58-
accountId: 28,
59-
accountName: "Customer-Acme",
60-
},
61-
{
62-
firstName: "Cecilia",
63-
lastName: "Stark",
64-
65-
accountId: 132,
66-
accountName: "Customer-Fake",
67-
},
56+
{
57+
firstName: "Rene",
58+
lastName: "Mueller",
59+
60+
accountId: 28,
61+
accountName: "Customer-Acme",
62+
},
63+
{
64+
firstName: "Cecilia",
65+
lastName: "Stark",
66+
67+
accountId: 132,
68+
accountName: "Customer-Fake",
69+
},
6870
];
6971

7072
// when you create a user, generate a salt
7173
// and hash the password ('foobar' is the pass here)
7274

7375
hash({ password: "foobar" }, function (err, pass, salt, hash) {
74-
if (err) throw err;
75-
// store the salt & hash in the "db"
76-
users.forEach((element) => {
77-
element.salt = salt;
78-
element.hash = hash;
79-
});
76+
if (err) throw err;
77+
// store the salt & hash in the "db"
78+
users.forEach((element) => {
79+
element.salt = salt;
80+
element.hash = hash;
81+
});
8082
});
8183

8284
function findUserbyEmail(email) {
83-
var u = users.find((u) => u.email === email);
84-
return u;
85+
var u = users.find((u) => u.email === email);
86+
return u;
8587
}
8688

8789
// Authenticate using our plain-object database of doom!
8890

8991
function authenticate(email, pass, fn) {
90-
if (!module.parent) console.log("authenticating %s:%s", email, pass);
91-
var user = findUserbyEmail(email);
92-
// query the db for the given email
93-
if (!user) return fn(null, null);
94-
// apply the same algorithm to the POSTed password, applying
95-
// the hash against the pass / salt, if there is a match we
96-
// found the user
97-
hash({ password: pass, salt: user.salt }, function (err, pass, salt, hash) {
98-
if (err) return fn(err);
99-
if (hash === user.hash) return fn(null, user);
100-
fn(null, null);
101-
});
92+
if (!module.parent) console.log("authenticating %s:%s", email, pass);
93+
var user = findUserbyEmail(email);
94+
// query the db for the given email
95+
if (!user) return fn(null, null);
96+
// apply the same algorithm to the POSTed password, applying
97+
// the hash against the pass / salt, if there is a match we
98+
// found the user
99+
hash({ password: pass, salt: user.salt }, function (err, pass, salt, hash) {
100+
if (err) return fn(err);
101+
if (hash === user.hash) return fn(null, user);
102+
fn(null, null);
103+
});
102104
}
103105

104106
function restrict(req, res, next) {
105-
if (req.session.user) {
106-
next();
107-
} else {
108-
req.session.returnTo = req.originalUrl;
109-
req.session.error = "Access denied!";
110-
res.redirect("/login");
111-
}
107+
if (req.session.user) {
108+
next();
109+
} else {
110+
req.session.returnTo = req.originalUrl;
111+
req.session.error = "Access denied!";
112+
res.redirect("/login");
113+
}
112114
}
113115

114116
const signUserToken = (user) =>
115-
jwt.sign(
116-
{
117-
email: user.email,
118-
first_name: user.firstName,
119-
last_name: user.lastName,
120-
account_id: user.accountId,
121-
groups: [user.accountName],
122-
exp: Math.round(Date.now() / 1000) + 60 * 10, // 10 minute expiration
123-
},
124-
METABASE_JWT_SHARED_SECRET
125-
);
117+
jwt.sign(
118+
{
119+
email: user.email,
120+
first_name: user.firstName,
121+
last_name: user.lastName,
122+
account_id: user.accountId,
123+
groups: [user.accountName],
124+
exp: Math.round(Date.now() / 1000) + 60 * 10, // 10 minute expiration
125+
},
126+
METABASE_JWT_SHARED_SECRET
127+
);
126128

127129
app.get("/", function (req, res) {
128-
res.redirect("/analytics");
130+
res.redirect("/analytics");
129131
});
130132

131133
app.get("/analytics", restrict, function (req, res) {
132-
// replace ID "1" with the ID number in the path of your dashboard in Metabase.
133-
const METABASE_DASHBOARD_PATH = "/dashboard/1";
134-
var iframeUrl = `/sso/metabase?return_to=${METABASE_DASHBOARD_PATH}`;
135-
res.send(
136-
`<iframe src="${iframeUrl}" frameborder="0" width="1280" height="1000" allowtransparency></iframe>`
137-
);
134+
// replace ID "1" with the ID number in the path of your dashboard in Metabase.
135+
const METABASE_DASHBOARD_PATH = "/dashboard/1";
136+
var iframeUrl = `/sso/metabase?return_to=${METABASE_DASHBOARD_PATH}`;
137+
res.send(
138+
`<iframe src="${iframeUrl}" frameborder="0" width="1280" height="1000" allowtransparency></iframe>`
139+
);
138140
});
139141

140142
app.get("/logout", function (req, res) {
141-
// destroy the user's session to log them out
142-
// will be re-created next request
143-
req.session.destroy(function () {
144-
res.redirect("/");
145-
});
143+
// destroy the user's session to log them out
144+
// will be re-created next request
145+
req.session.destroy(function () {
146+
res.redirect("/");
147+
});
146148
});
147149

148150
app.get("/login", function (req, res) {
149-
res.render("login");
151+
res.render("login");
150152
});
151153

152154
app.post("/login", function (req, res, next) {
153-
authenticate(req.body.email, req.body.password, function (err, user) {
154-
if (err) return next(err);
155-
if (user) {
156-
// Regenerate session when signing in
157-
// to prevent fixation
158-
var returnTo = req.session.returnTo;
159-
req.session.regenerate(function () {
160-
// Store the user's primary key
161-
// in the session store to be retrieved,
162-
// or in this case the entire user object
163-
req.session.user = user;
164-
req.session.success =
165-
"Authenticated as " +
166-
user.firstName +
167-
"" +
168-
user.lastName +
169-
' click to <a href="/logout">logout</a>. ' +
170-
' click to access <a href="/analytics">analytics</a>';
171-
res.redirect(returnTo || "/");
172-
delete req.session.returnTo;
173-
});
174-
} else {
175-
req.session.error =
176-
"Authentication failed, please check your " +
177-
" email and password." +
178-
' (use "[email protected]" or "[email protected]" and password "foobar")';
179-
res.redirect("/login");
180-
}
181-
});
155+
authenticate(req.body.email, req.body.password, function (err, user) {
156+
if (err) return next(err);
157+
if (user) {
158+
// Regenerate session when signing in
159+
// to prevent fixation
160+
var returnTo = req.session.returnTo;
161+
req.session.regenerate(function () {
162+
// Store the user's primary key
163+
// in the session store to be retrieved,
164+
// or in this case the entire user object
165+
req.session.user = user;
166+
req.session.success =
167+
"Authenticated as " +
168+
user.firstName +
169+
"" +
170+
user.lastName +
171+
' click to <a href="/logout">logout</a>. ' +
172+
' click to access <a href="/analytics">analytics</a>';
173+
res.redirect(returnTo || "/");
174+
delete req.session.returnTo;
175+
});
176+
} else {
177+
req.session.error =
178+
"Authentication failed, please check your " +
179+
" email and password." +
180+
' (use "[email protected]" or "[email protected]" and password "foobar")';
181+
res.redirect("/login");
182+
}
183+
});
182184
});
183185

184186
app.get("/sso/metabase", restrict, (req, res) => {
185-
res.redirect(
186-
url.format({
187-
pathname: `${METABASE_SITE_URL}/auth/sso`,
188-
query: {
189-
jwt: signUserToken(req.session.user),
190-
return_to: `${req.query.return_to || "/"}?${mods}`,
191-
},
192-
})
193-
);
187+
res.redirect(
188+
url.format({
189+
pathname: `${METABASE_SITE_URL}/auth/sso`,
190+
query: {
191+
jwt: signUserToken(req.session.user),
192+
return_to: `${req.query.return_to || "/"}?${mods}`,
193+
},
194+
})
195+
);
194196
});
195197

196198
const PORT = 8080;
197199
if (!module.parent) {
198-
app.listen(PORT);
199-
console.log(`Express started serving on port ${PORT}`);
200+
app.listen(PORT);
201+
console.log(`Express started serving on port ${PORT}`);
200202
}

0 commit comments

Comments
 (0)