Build a Voice Notes App — Part 2, User System

David Guan
3 min readOct 7, 2019

Part 1, Text to Speech Pipeline described my attempt for generating web-accessible audio files from text contents.
This article is about the first iteration to make the app supports login/sign-up as well as API and UI for listing/creating the audios.

DEMO

It has many areas(including but not limited to styling) to improve, but the basic functions are there, you can visit the live demo here.

DEMO of the result.

User management via Auth0

Choosed Auth0 to save time and reduce security risks, integrated it via its Single Page App SDK, which has a quick start tutorial.
On the client-side, the code is like:

function updateUi(auth0) {
auth0.isAuthenticated().then(authed => {
// Omitting code for updating UI,
// e.g., toggle some buttons' disable state.
if (authed) {
auth0.getTokenSilently().then(accToken => {
// Use the accToken for API interactions.
});
}
});
}
createAuth0Client(config).then(auth0 => {
updateUi(auth0);
const currentPage = location.origin + location.pathname;
loginBtn.addEventListener("click", () => {
auth0.loginWithRedirect({ redirect_uri: currentPage });
});
logoutBtn.addEventListener("click", () => {
auth0.logout({ returnTo: currentPage });
});
const query = location.search;
// A successful login would bring the code and state paras.
if (query.includes("code=") && query.includes("state=")) {
auth0.handleRedirectCallback().then(() => {
history.replaceState(
{},
document.title,
currentPage
);
updateUi(auth0);
});
}
});

You can visit here for the full source code.

API(via Node.js and Express)

The accToken from the previous section would be very handy on API interactions, that token is a JSON Web Token, Auth0 signs it with a private key, and we can use the public key(which is okay to get leaked) along with some other known information to verify whether a request is legit.

The verification can be done inside an Express middleware, the validateRequest below is just an example of how that middleware could be written:

const jwt = require("jsonwebtoken");
const JwksClient = require("jwks-rsa");
const auth0Conifg = {
domain: "davidguan.auth0.com",
audience: "https://davidguan.app/voice-notes-app/api",
jwksUri: "https://davidguan.auth0.com/.well-known/jwks.json"
};
const jwksClient = JwksClient({
jwksUri: auth0Conifg.jwksUri
});
function getJwtKey(header, callback) {
jwksClient.getSigningKey(header.kid, function(err, key) {
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
const jwtOptions = {
audience: auth0Conifg.audience,
issuer: `https://${auth0Conifg.domain}/`,
algorithm: ["RS256"]
};
function validateRequest(req, res, next) {
const authParts = (req.headers["authorization"] || "").split(" ");
if (authParts.length === 2 && authParts[0] === "Bearer") {
jwt.verify(authParts[1], getJwtKey, jwtOptions, (err, decoded) => {
if (err) {
res.statusCode = 401;
res.send({ msg: "Unauthorized" });
return;
}
req.userId = decoded.sub;
next();
});
return;
}
res.statusCode = 401;
res.send({ msg: "Unauthorized" });
}

With validateRequest, we can forbid malicious requests and get the user ID(decoded.sub).

Exposed GET and PUT for the “voice notes” resource, as:

const bodyParser = require("body-parser");const jsonParser = bodyParser.json();
app.post("/voice-notes", validateRequest, jsonParser, (req, res) => {
const { content, title } = req.body;
if (!content || !title) {
res.statusCode = 400;
return res.send({ msg: "Bad Request" });
}
createVoiceNote(title, content, req.userId)
.then(() => {
res.send();
})
.catch(() => {
res.statusCode = 500;
res.send({ msg: "Internal Server Error" });
});
});
app.get("/voice-notes", validateRequest, (req, res) => {
getVoiceNotes(req.userId)
.then(notes => {
res.send(notes);
})
.catch(() => {
res.statusCode = 500;
res.send({ msg: "Internal Server Error" });
});
});

Omitted code for createVoiceNote and getVoiceNotes, as Part 1 is mainly focused on them.

UI
The whole UI stuff is very simple right now, just three files, their source code links: the HTML file, the CSS file, and the Javascript file.
The third-party dependencies are bootstrap and the auth0 library mentioned before.

Later on, I will introduce a build tool(e.g., webpack) for managing modules(instead of a big script file to do all the stuff) and supporting more browsers.
Maybe a UI component library(e.g., React) for code maintainability.

Random Notes

  1. As my personal API server already has other stuff running, I decided to give Nginx a try as the reverse proxy and found it super easy to set up, followed the guide and everything works just fine.
  2. pm2 logs helped a lot during the debugging process 😅

What’s next?

  • Introducing proper unit tests before continuing any further.
  • Introducing a proper DB(e.g., dynamo DB) as I’m currently using S3 only for all the persistent storage.
  • New features, e.g., deleting notes should be viable through UI.

--

--

David Guan

I program machines to do web and graphics stuff, also write about them.