Is it ok to add routes in an Express middleware? - express

I am building a small authentication middleware (because I want to!) but to work the way I wanted I found I was adding routes dynamically to the app, both for the POST back of the username/password as well as the GET for the authentication page script and styles.
Here it is:
function appAuth(auth, options) {
let oneHourMs = (60 * 60 * 1000);
let sessionStore = {};
let html = `<html>
<head>
<link rel="stylesheet" href="style.css" type="text/css">
<script src="index.js" type="module"></script>
</head>
<body>
</body>
</html>`;
return function (request, response, next) {
if (!request.app.authMiddlewarePOST_setup) {
request.app.post(request.path, upload.array(), async function (req, res) {
let data = req.body;
console.log("data", data);
let { username, password } = data;
if (auth(username, password)) {
let now = new Date();
let sessionId = await makeSessionId(now, getRemoteAddr(req));
sessionStore[sessionId] = {
lastTouch: now,
id: sessionId
};
res.cookie("sessionid", sessionId);
res.redirect(302, req,path);
}
else {
res.status(401).send(html);
}
});
request.app.authMiddlewarePOST_setup = true;
}
// Do authenticated session detection
let sessionId = request.cookies.sessionid;
if (sessionId === undefined) {
response.status(401).send(html);
return;
}
let sessionObject = sessionStore[sessionId];
if (sessionObject === undefined
|| isExpired(sessionObject.lastTouch, oneHourMs)) {
response.status(401).send(html);
return;
}
// Otherwise it's all ok.
next();
};
}
I was wondering:
is it ok to add routes like this?
is it better to add a route with an obfuscated path? for example:
app.post("/" + middlewareId + "/" + randomnumber, ...)
if I'm doing this I suppose I should dispose of the route as well?
what would be some alternatives to doing this?
The git repo is over here - I don't want to clutter up the post with even more superflous code.

Related

How add custom Service Worker on Flutter Web to receive push notification

I'm trying to add a custom service worker to read web push notifations from external service. The problem is that my custom service worker "worker.js" is not registered in application.
Next, the code:
WORKER.JS
console.log("Service worker loaded...");
self.addEventListener("install", event => {
console.log("Service worker installed");
});
self.addEventListener("activate", event => {
console.log("Service worker activated");
});
self.addEventListener('message', event => {
console.log(event)
});
self.addEventListener('push', function(e) {
console.log('push event',e)
const data = e.data
self.registration.showNotification(
// data.title,
"hello",
{
// body: data.body,
body:"how are you?",
}
);
})
INDEX.HTML (part)
<head>
...
...
<script>
// The value below is injected by flutter build, do not touch.
var serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
<!-- MY CUSTOM SERVICE WORKER INITS HERE -->
<script src="./client.js" defer></script>
</head>
<body>
<script>
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint(
{
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
}
}).then(function(engineInitializer) {
return engineInitializer.initializeEngine();
}).then(function(appRunner) {
return appRunner.runApp();
})
});
</script>
CLIENT.JS
if('serviceWorker' in navigator) {
registerServiceWorker().catch(console.log)
}
async function registerServiceWorker() {
console.log("Before register worker...")
const register = await navigator.serviceWorker.register('./worker.js', {
scope: '/'
});
const PUBLIC_VAPID_KEY = "ApiKeyGeneratedFromService";
function urlBase64ToUint8Array(base64String) {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
console.log("Before subscription")
const subscription = await register.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey:urlBase64ToUint8Array(PUBLIC_VAPID_KEY)});
console.log("subscription:", subscription.endpoint);
console.log("Before fecth")
await fetch("{UrlFromCustomService}/notifier/webpush/subscription?operation=subscribe", {
method: "POST",
body: JSON.stringify(subscription),
headers: {
"Content-Type": "application/json",
}
})
When I starts application, the "worker.js" get the fetch operation correctly, but the push notification never arrive. Notification permission is also displayed correctly.
When open service worker register...
It's like "flutter_service_worker" replaces my custom worker.
I can only have one service worker at a time ?
Is this the correct way to implement a custom service worker (WITHOUT FIREBASE)?

How can I access the logged in user name on Netlify?

I'm having trouble understanding how handle functions on Netlify. In particular, I want to access the user's id when they login.
I have enabled identity on my netlify site and I can login and log out.
<button data-netlify-identity-button id="login"></button>
I have created a function identity-login that I think should handle the user's details, but I cannot see how to utilise it on the web-page
// functions/identity-login.js
exports.handler = async function (event, context) {
const { identity, user } = context.clientContext;
console.log(identity, user)
return {
statusCode: 200,
body: 'hello'
}
};
The function endpoint is
https://silly-parrot.netlify.app/.netlify/functions/identity-login
I have this in the script on my page, but I don't know how to call it or if it's correct
async function apiCall() {
const url = `/.netlify/functions/identity-login`;
try {
const response = await fetch(url);
const data = await response;
console.log(data)
return data;
} catch (err) {
console.log(err);
}
}
What should I do?
I now realise that I was taking the wrong approach. It is not necessary to use the Netlify identity-login event. The netlifyIdentity object provides the necessary functionality for identifying when the user logs in or logs out and to discover whether or not the user is logged in when the page loads (init). The user identity is contained in the user.token.access_token
The following code is within my main js script (you will of course need to access the netlifyIdentity object)
<script type="text/javascript" src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
And then setup async functions to handle the authorisation events
<script>
var token = '';
var logged_in = false;
async function started() {
logged_in = false;
netlifyIdentity.on('init', async user => {
if(user) {
token = user.token.access_token;
logged_in = true;
}
console.log('init', logged_in, token);
})
netlifyIdentity.on('login', user => {
if(user) {
logged_in = true;
token = user.token.access_token;
}
console.log('log in', logged_in, token);
})
netlifyIdentity.on('logout', () => {
token = '';
logged_in = false;
console.log('log out', logged_in, token);
})
}
started()
</script>

Setup of swagger-ui-express with API Gateway

I am not able to figure out the correct setup of either my API Gateway or swagger-ui-express.
The lambda function runs successful and and return the html but the related resouces are not able to be loaded and get an 404 error.
const app = express();
const swaggerUI = require('swagger-ui-express');
const serverlessExpress = require('#vendia/serverless-express');
const Stage = process.env.Stage;
const apiId = process.env.apiId;
const options = {
customCss: '.swagger-ui .topbar { display: none }',
customCss: '.swagger-ui .topbar { background-color: red }'
}
let serverlessExpressInstance
async function asyncTask() {
// load latest spec from API Gateway export removed to simplicity reasons.
const swaggerDocument = spec;
console.log("load swagger file complete.");
return swaggerDocument
}
async function setup(event, context) {
const swaggerDocument = await asyncTask()
console.log(swaggerDocument)
app.use('/api-doc', swaggerUI.serveWithOptions({ redirect: false }));
app.use('/api-doc', swaggerUI.setup(swaggerDocument, options));
console.log("setup of swagger complete");
serverlessExpressInstance = serverlessExpress({ app })
return serverlessExpressInstance(event, context)
}
function handler(event, context) {
if (serverlessExpressInstance) return serverlessExpressInstance(event, context)
return setup(event, context)
}
exports.handler = handler
Setup on API Gateway is the following:
Both resources are pointing to the lambda function.
When I load the page via: https://.execute-api..amazonaws.com/dev/api-doc
The following errors are raised:
How can I ensure that the resources are loaded correctly via the correct path ...dev/api/doc/...
You're probbably getting these errors because swagger-ui is configured to look for the image/js resources and express doesn't serve these files. You could potentially use something like serverless-http like this, but I'm not really sure it's a good idea to try and force express into the response of an API gateway lambda function.
As an alternative I managed to get swagger up and running on an API Gateway lambda function, fetching the swagger JSON directly.
import { APIGateway } from "aws-sdk";
import { env } from "../../helpers/env";
const API_ID = env("API_ID", "");
export const handler = async () => {
const apiGateway = new APIGateway({
apiVersion: "2018-11-29",
});
const apiGatewayExport = await apiGateway
.getExport({
exportType: "swagger",
restApiId: API_ID,
stageName: "prod",
accepts: "application/json",
parameters: {
extensions: "apigateway",
},
})
.promise();
if (!apiGatewayExport.body) {
throw new Error("No body found in API Gateway Export");
}
return {
statusCode: 200,
headers: {
"Content-Type": "text/html",
},
body: generateSwaggerPageBody(apiGatewayExport.body?.toString()),
};
};
const generateSwaggerPageBody = (swaggerSpec: string) => `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist#3/swagger-ui.css">
</head>
<body>
<div id="swagger"></div>
<script src="https://unpkg.com/swagger-ui-dist#3/swagger-ui-bundle.js"></script>
<script>
SwaggerUIBundle({
dom_id: '#swagger',
spec: ${swaggerSpec}
});
</script>
</body>
</html>`;
I'm taking the API information directly from API Gateway via SDK and then I just return an html page with tags loading swagger directly - no express overhead needed.

Telegram Bot API - public download urls for files contain bot token

I need to get public url for file with telegram bot API. The problem with getFile method that it returns url in following format: https://api.telegram.org/file/bot<token>/<file_path> meaning I can't really share it in public because it contains my bot token, sharing this url wouldn't be secure.
Is it possible to get public url for file id that does not have my bot token in it? What are the alternatives?
I have created the following solution as a Proof Of Concept. Please check:
https://gist.github.com/gilpanal/099ff5fc94366fbaabd5e2fbedc7c86f
The idea is that you access to the binary data of your file through an intermediate API where your token is safe.
/*** server.js ***/
/* TESTED WITH NODE VERSION 14+ */
const express = require('express')
const app = express()
const https = require('https')
const port = process.env.PORT || 3000
// Use an Environment Variable to Secure Token Value
const BOT_TOKEN = <BOT_SECRET_TOKEN>
// For better CORS: https://expressjs.com/en/resources/middleware/cors.html
app.use( (req, res, next) => {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
next()
})
app.get('/', (req, res) => {
res.sendStatus(200)
})
// Inspired by: https://stackoverflow.com/a/21024737
app.get('/fileDownload', (req, res) => {
let uploadResponse = { ok: false, result: null, error: 404, description: 'Not Found' }
if (req._parsedUrl && req._parsedUrl.query) {
const tel_file_path = 'https://api.telegram.org/file/bot' + BOT_TOKEN + req._parsedUrl.query
https.get(tel_file_path, (response) => {
const data = []
response.on('data', (chunk) => {
data.push(chunk)
}).on('end', () => {
const buffer = Buffer.concat(data)
res.send(buffer)
})
})
} else {
res.sendStatus(uploadResponse)
}
})
app.listen(port)
/*** app.js ***/
const TEL_PATH = '/music/file_352.mp3'
const API_FILEDONWLOAD = 'http://localhost:3000/fileDownload?'
const load = () => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', API_FILEDONWLOAD + TEL_PATH, true)
xhr.responseType = 'arraybuffer'
xhr.send()
xhr.addEventListener('progress', (e) => {
console.log(`${e.type}: ${e.loaded} bytes transferred\n`)
})
xhr.addEventListener('load', (e) => {
const audioData = e.target.response || e.target.result
resolve(audioData)
})
xhr.addEventListener('error', () => {
reject(Error('Track ' + TEL_PATH + ' failed to load'))
})
})
}
load().then((audiData) => {
console.log(audiData)
}).catch((err) =>{
console.log(err)
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
BODY
<script src="app.js"></script>
</body>
</html>

Express js render error this.engine is not a function

I'm trying to develop a small API using express. Just want to have 2 views, which, in my case, means 2 html files. One accesing as default with "/" and the other with "/lessons", so we got 2 get controls plus another one which handles every other get input.
*Both files are in the "views" folder and their extension is: *.html
I have no problem accessing the "app.get("/lessons", function..." in fact I know I can acces to that because a simple "console.log(..)" command. The problem is that I got the next error when trying to render:
[TypeError: this.engine is not a function].
Could you help me? I can't understand where is the problem or what I'm doing wrong. I believe it's in the rendering function and has something to do with its configuration and the lessons.html file because index.html has no problem using the same approach.
var express = require('express');
var app = express();
var mod = require('./module');
app.use(express.static('public'));
app.use(express.static('views'));
var port = process.env.PORT || 8080;
app.listen(port, function() {
console.log('Node.js listening on port ' + port);
});
app.get("/", function(req, res) {
console.log("Passed through /");
res.render('index.html');
});
app.get("/lessons", function(req, res) {
console.log("passed through lessons");
res.render('lessons.html', function(err, html) {
if(err) {
console.log(err);
}
res.send(html);
});
//I have tried to to use just: res.render('lessons.html');
});
app.get("*", function(req, res) {
var usageReq = false;
var urlPassed = req.url;
urlPassed = urlPassed.substring(1, urlPassed.length); //remove first "/"
var expected = mod.seeIfExpected(urlPassed); //returns url(if url) or num(if number) or na(if it doesn't match any)
mod.processInfo(expected, urlPassed, function(answer) {
if (answer.found == false && answer.jsonRes == true && answer.info != "inserted") {
res.json({
"error": answer.info
});
} else {
if (answer.jsonRes == true) {
res.json({
"long_url": answer.url,
"short_url": answer.id
});
} else { // go to url
var newUrl = "https://" + answer.url;
res.redirect(newUrl);
}
}
});
});