Ask for credentials before showing my swagger - node.js

I'm trying to add security to my API swagger endpont. I have created my API using node.js and express and swagger-ui-express module. The problem is that anyone is able to access to my swagger endpoint. So, to solve this, I thought about adding a basic auth before showing swagger content.
Example of implementing basic auth on endpoint:
app.get('/users', (req, res) => {
let user = auth(req)
if (user === undefined || user['name'] !== 'admin' || user['pass'] !== 'adminpass') {
res.statusCode = 401
res.setHeader('WWW-Authenticate', 'Basic realm="Node"')
res.end('Unauthorized')
} else {
res.status(200).send('Return all users');
}
});
That same example I want to add in swagger's endpoint:
const swaggerUi = require('swagger-ui-express');
const YAML = require('yamljs');
const swaggerDocument = YAML.load('./swagger.yaml');
const swaggerOptions = {
swaggerDefinition: {
info: {
version: "1.0.0",
title: "Customer API",
description: "Customer API Information",
contact: {
name: "Amazing Developer"
},
servers: ["http://localhost:3000"]
}
},
// ['.routes/*.js']
apis: ["index.js"]
};
const swaggerDocs = swaggerJsDoc(swaggerOptions);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
Can anyone help me? I tried to do it but it doesn't work. I even don't know if it is possible.
SOLUTION:
app.use('/api-docs', function(req, res, next){
let user = auth(req)
if (user === undefined || user['name'] !== 'admin' || user['pass'] !== 'adminpass') {
res.statusCode = 401
res.setHeader('WWW-Authenticate', 'Basic realm="Node"')
res.end('Unauthorized')
} else {
next();
}
}, swaggerUi.serve, swaggerUi.setup(swaggerDocument));
Edit: for those asking, auth is a function that takes the base64 encoded credentaials from the request header, decodes them and returns an object. Like follows:
const auth = (req) => {
const authorizationHeader = req.headers.authorization;
const base64 = authorizationHeader.substr(6);
const credentials = Buffer.from(base64, 'base64').toString();
const [name, pass] = credentials.split(':');
return { name, pass };
}

app.use('/api-docs', function(req, res, next){
let user = auth(req)
if (user === undefined || user['name'] !== 'admin' || user['pass'] !== 'adminpass') {
res.statusCode = 401
res.setHeader('WWW-Authenticate', 'Basic realm="Node"')
res.end('Unauthorized')
} else {
next();
}
}, swaggerUi.serve, swaggerUi.setup(swaggerDocument));

Related

catch error: Node.js + Google Cloud Functions

please help - can't debug this function, with empty proxy it works, but when i'm using any socks5 proxy (format for ex.: socks5://username:pass#1.2.3.4:1234) - fails, but I don't understand where is a problem, how to debug. Any advice would be appreciated. Thanks :)
package.json
"dependencies": {
"#google-cloud/firestore": "5.0.1",
"make-fetch-happen": "9.1.0"
}
index.js
const fetch = require('make-fetch-happen');
const Firestore = require("#google-cloud/firestore");
const firestore = new Firestore({
projectId: process.env.FIRESTORE_PROJECT_ID,
timestampsInSnapshots: true,
});
async function getSegments(appId, apiKey, proxy) {
const opts = {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${apiKey}`
}
}
if (proxy) {
opts['proxy'] = proxy
}
const resp = await fetch(`https://onesignal.com/api/v1/apps/${appId}/segments`, opts)
const data = await resp.json()
return data.segments
}
exports.checkProxy = async (req, res) => {
res.set('Access-Control-Allow-Origin', '*')
if (req.method === 'OPTIONS') {
res.set('Access-Control-Allow-Methods', 'POST')
res.set('Access-Control-Allow-Headers', 'Content-Type')
res.set('Access-Control-Max-Age', '3600')
res.status(204).send('')
} else {
if (!req.body.hasOwnProperty('appIds') || !req.body.appIds || req.body.appIds.length === 0) {
res.status(403).send('appIds is required')
return
}
let resp = []
for (const appId of req.body.appIds) {
const appSnap = await firestore.collection('apps').doc(appId).get()
if (appSnap.empty) {
res.status(404).send('app not found')
return
}
const app = appSnap.data()
try {
const campaignData = await getSegments(app.appId, app.apiKey, app.proxy)
resp.push({error: false, app: app.name, appId: app.id})
} catch (e) {
resp.push({error: e, app: app.name, appId: app.id})
}
}
res.status(200).send(resp)
}
}
The problem was solved simply by updating the make-fetch-happen module to version 10.1+. Thanks to the person who gave me a minus without responding - you made the world a better place dude.

Server-Sent Events (SSE) problem with SSL/HTTPS

Hello I am developing a web application in React that receives SSE data from an Express server with Nginx.
SERVER.JS
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const crypto = require('crypto');
const app = express();
var lastClientRes = null;
function eventsHandler(req, res, next) {
const headers = {
'Content-Type': 'text/event-stream',
'Connection': 'keep-alive',
'Cache-Control': 'no-cache'
};
res.writeHead(200, headers);
const clientId = Date.now();
const newClient = {
id: clientId,
nonce: null,
cart: null,
res
};
requests.push(newClient);
const data = `data: ${JSON.stringify({client: clientId})}\n\n`;
res.write(data);
req.on('close', () => {
console.log(`${clientId} Connection closed`);
clients = clients.filter(c => c.id !== clientId);
});
}
function sendEventsToAll(newNest) {
clients.forEach(c => c.res.write(`data: ${JSON.stringify(newNest)}\n\n`))
}
async function addCart(req, res) {
const newCart = req.body;
requests.forEach(r => {
if(newCart.client == r.id){
var nonce = crypto.randomBytes(16).toString('base64');
r.nonce = nonce;
r.cart = newCart.cart;
r.res.write(`data: ${JSON.stringify({nonce: nonce})}\n\n`);
}
})
}
async function addCart(req, res) {
const newCart = req.body;
requests.forEach(r => {
if(newCart.client == r.id){
var nonce = crypto.randomBytes(16).toString('base64');
r.nonce = nonce;
r.cart = newCart.cart;
r.res.write(`data: ${JSON.stringify({nonce: nonce})}\n\n`);
}
})
}
async function confirmCart(req, res){
var nonce = req.body.nonce;
var found = -1;
requests.forEach((item, i) => {
if(item.nonce == nonce){
found = i;
return;
}
});
if(found)
{
console.log("OK");
requests[found].res.write(`data: ${JSON.stringify({confirm: true})}\n\n`);
}
}
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.post('/addCart', addCart);
app.post('/confirmCart', confirmCart);
app.get('/events', eventsHandler);
app.get('/status', (req, res) => res.json({clients: clients.length}));
const PORT = 3001;
let requests= [];
let clients = [];
let nests = [];
app.listen(PORT, () => console.log(`SSE service listening on port ${PORT}`));
INDEX:JS
import React from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
jsonCart: "",
cartNonce: "",
clientId: "",
cartConfirmed: false,
cart: Array(),
timerId: null,
listening: false,
cartConfermato: ""
};
}
buy(){
if (!this.state.listening) {
const events = new EventSource('https://api.myDomain.com/events', );
events.onmessage = (event) => {
const parsedData = JSON.parse(event.data);
console.log(event.data);
if(parsedData.client != null)
{
this.setState({
clientId: parsedData.client,
});
this.sendCart();
}
if(parsedData.nonce != null)
this.setState({
cartNonce: parsedData.nonce,
});
if(parsedData.confirm == true)
this.setState({
cartNonce: "",
cartConfermato: "Il carrello è stato confermato!"
});
};
this.setState({
listening: true
});
}
}
sendCart(){
var cart = JSON.stringify(this.state.cart.slice());
this.setState({
jsonCart: cart
});
axios.post(`https://api.myDomain.com/addCart`, {client: this.state.clientId, cart: cart});
}
*** ... ***
const events = new EventSource('https://api.myDomain.com/events', );
axios.post(https://api.myDomain.com/addCart, {client:
this.state.clientId, cart: cart});
In http everything works perfectly but if I set https generating the certificates with certbot I no longer receive "events" from the express server.
The only errors that appears in the chrome console is this
I replaced sub.domain with my domain
These errors appear a few minutes after the first request
GET https://sub.domain.com/events net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 (OK)
2sub.domain.com/addCart:1 POST https://sub.domain.com/addCart 504 (Gateway Time-out)
(index):1 Access to XMLHttpRequest at 'https://sub.domain.com/addCart' from origin 'https://example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
createError.js:16 Uncaught (in promise) Error: Network Error
at e.exports (createError.js:16)
at XMLHttpRequest.p.onerror (xhr.js:83)
e.exports # createError.js:16
p.onerror # xhr.js:83
error (async)
(anonymous) # xhr.js:80
e.exports # xhr.js:12
e.exports # dispatchRequest.js:50
Promise.then (async)
u.request # Axios.js:61
r.forEach.u.<computed> # Axios.js:86
(anonymous) # bind.js:9
value # index.js:156
state.listening.EventSource.onmessage # index.js:121
index.js:114 {"client":1579885346578}
index.js:150 send
sub.domain.com/events:1 GET https://sub.domain.com/events net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 (OK)
2sub.domain.com/addCart:1 POST https://sub.domain.com/addCart net::ERR_ABORTED 504 (Gateway Time-out)
As Darren Cook described here
You need to disable buffering on your server-side.
Adding the "X-Accel-Buffering" parameter and set it to "no" in the response header fixed the issue for me.
const headers = {
'Content-Type': 'text/event-stream',
'Connection': 'keep-alive',
'Cache-Control': 'no-cache',
'X-Accel-Buffering': 'no'
};

Fetch API failed to Fetch during authentication, alongside CORS error

I have a button that lauches a fetch to my API that uses KOA and JWT. The javascript for the fetch initiated on click is:
<script>
function loginButton(user, pass) {
fetch('http://localhost:5454/api/login', {
method: "post",
headers: {
'Content-Type': "application/json"
},
body: JSON.stringify({
username: user,
password: pass
})
})
.then( (response) => {
console.log("Success")
})
.catch(e => console.log(e));
}
</script>
The code for my Authentication is:
router.post(`${BASE_URL}/login`, async (ctx) => {
const reqUsername = ctx.request.body.username
const reqPassword = ctx.request.body.password
const unauthorized = (ctx) => {
ctx.status = 401
ctx.body = {
error: 'Invalid username or password'
}
}
let attemptingUser
try {
attemptingUser = await Employee.findOne({ where: { username: reqUsername }})
if (attemptingUser != null && attemptingUser.password === reqPassword) {
ctx.status = 200
ctx.body = {
username: attemptingUser.username,
given_name: attemptingUser.given_name,
role: attemptingUser.role,
created_at: attemptingUser.createdAt,
updated_at: attemptingUser.updatedAt,
}
const token = jwt.sign({ username: attemptingUser.username, role: attemptingUser.role }, SECRET)
ctx.set("X-Auth", token)
} else {
unauthorized(ctx)
}
} catch(err) {
console.error(err)
console.error(`Failed to find username: ${reqUsername}`)
unauthorized(ctx)
}
})
The code for my KOA initiation is:
require('dotenv').config()
const Koa = require('koa')
const Router = require('koa-router')
const bodyParser = require('koa-bodyparser')
const baseRoutes = require('./routes')
const cors = require('#koa/cors');
const PORT = process.env.PORT || 8080
const app = new Koa()
app.use(bodyParser())
app.use(baseRoutes.routes())
app.use(cors());
app.listen(PORT, () => {
console.log(`Server listening on ${PORT}`)
})
Im using Port 8080 for my http-server and port 5454 for my npm server. I am getting a Failed to Fetch in the catch of the Fetch, as well as a CORS error related to not having a Access-Control-Allow-Origin header in the response header. I've tried a couple things and am ready to have a new set of eyes look at it, any tips?
Edit: I am successfully receiving the token in the X-Auth header, but for some reason it’s still throwing errors and I’d like to get them resolved before it spirals out of control.

Sending email using nodemailer not working

I have this sendMail cloud function here through i am trying to send a simple email. I am not sure what is the mistake i am doing but i keep getting 400 Bad request error on postman whenever i hit this function.
P.S i am adding correct credentials of my gmail account too
Here is my cloud function
const functions = require('firebase-functions');
const cors = require('cors')({origin: true});
const admin = require("firebase-admin");
const bodyParser = require("body-parser");
const nodemailer = require("nodemailer");
var smtpTransport = require('nodemailer-smtp-transport');
let transporter = nodemailer.createTransport(smtpTransport({
service: 'Gmail',
auth: {
user: 'abc#gmail.com',
pass: '12345'
}
}));
//Send email
exports.sendMail = functions.https.onRequest((request, responde) => {
// cors(req, res, () => {
// getting dest email by query string
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Methods', 'GET', 'POST');
res.set('Access-Control-Allow-Headers', 'Content-Type');
if(req.method === 'OPTIONS') {
res.end();
}
else
{
if(req.body.dest != null || req.body.dest != undefined) {
const dest = req.query.dest;
const mailOptions = {
from: 'Ehsan Nisar <ABC#gmail.com>',
to: dest,
subject: 'I\'M A PICKLE!!!', // email subject
html: `<p style="font-size: 16px;">Pickle Riiiiiiiiiiiiiiiick!!</p>
<br />
<img src="https://images.prod.meredith.com/product/fc8754735c8a9b4aebb786278e7265a5/1538025388228/l/rick-and-morty-pickle-rick-sticker" />
` // email content in HTML
};
// returning result
return transporter.sendMail(mailOptions, (erro, info) => {
if(erro){
return res.send(erro);
}
return res.send('Sended');
});
}
else {
res.send(400, {
"message": "All fields are required"
})
}
// });
}
});

How to put authentication on hapi-swagger documentation page so that only authentic user could see my documentation

I am making API with hapi-swagger and I have implemented basic-authentication. But even if user doesn't have authentication he can still view my documentation page. I want to prevent him from viewing my documentation page. How can I implement basic auth on swagger documentation page?
I want to hide this page and ask for authentication credentials before rendering documentation
const Hapi = require('#hapi/hapi');
const basicAuth = require('basic-auth');
const server = new Hapi.server({ port: process.env.port || 3005, host: "localhost" });
server.ext('onRequest', (req, h) => {
const route = req.url.pathname;
if (route === "/documentation") {
let user = basicAuth(req);
if (user === undefined || user['name'] !== 'username' || user['pass'] !== 'pwd') {
return h.response("Unauthorized")
.code(401)
.header('WWW-Authenticate', 'Basic realm="Node"')
.takeover()
}
}
return h.continue;
});
const startServer = async () => {
await server.register([
require('#hapi/vision'),
require('#hapi/inert'),
{
plugin: require('hapi-swagger'),
options: {
info: {
title: 'Doc',
},
schemes: ["http", "https"],
securityDefinitions: {},
security: [],
}
}]);
await server.start();
console.log(`Server running at: ${server.info.uri}`);
};
process.on('unhandledRejection', (err) => {
console.error(err);
console.error(err.stack);
process.exit(1);
});
startServer();
You need to set property auth on plugin registration.
Eg.
await server.register([
{
plugin: require('hapi-swagger'),
options: {
auth: 'my-oauth-strategy',
}
}]);

Resources