Download and save MJPEG with http command (Javascript) - javascript

I'm using a hikvision IP camera that streams 30 MJPEG images per second to a certain http url and Javascript Reactjs with nodejs and express as backend.
Also hikvision provides a url to snap the camera image when you open the link.
Example link:
http://192.168.0.109/ISAPI/Streaming/channels/1/picture
I want to download that image and store it as a local file on my computer, I know how to store it but I haven't been able to download the image programatically.
I followed the next guide to get those API endpoints (stream and snapshot):
HIKVISION TUTORIAL
My question is, how do I fetch or download that image ?
I have tried with fetch without success.
Not sure but as long as I understand it requires a basic digest authorization and I haven't find how to fetch with digest auth. If I open the link directly on my browser, a pop up prompts and ask me for my username and password.
Everytime I try to fetch the response is :
GET http://192.168.0.109/ISAPI/Streaming/channels/1/picture net::ERR_ABORTED 401 (Unauthorized)
There is also some parameters to this API command on documentation that includes a json format that I have tried without success:
Also, as you can see on HIKVISION TUTORIAL there is an url to get the stream, I'm able to reproduce that MJPEG stream on front-end with the next code with no issues:
<img
width={"90%"}
height={"60%"}
alt="stream"
src={"http://192.168.0.109/ISAPI/Streaming/channels/102/httpPreview"}
id="cam1"
/>

net::ERR_ABORTED 401 (Unauthorized)
Based on the error you presented, I suspect that you have set a username/password.
The documentation (that you linked to in your question) explains that if you have set a username/password, then you need to use Basic auth:
http://<username>:<password>#<IP address of IPC>:<HTTP
port>/ISAPI/Streaming/channels/1/picture
So, if the local IP address that you're using is 192.168.0.109, then the URL format would be:
http://<username>:<password>#192.168.0.109/ISAPI/Streaming/channels/1/picture
and <username> and <password> would be your actual username and password.
Note that this URL format is deprecated in many environments. You can send the auth data in the request headers instead:
function setBasicAuthHeader (headers, username, password) {
// In Node:
const encoded = Buffer.from(`${username}:${password}`).toString('base64');
// In a browser/deno:
// const encoded = window.btoa(`${username}:${password}`);
headers.set('Authorization', `Basic ${encoded}`);
}
const username = 'me';
const password = 'secret';
const headers = new Headers();
setBasicAuthHeader(headers, username, password));
// Use headers in your request...

Related

Failing to fetch my local API running in localhost (Amazon Cloud 9)

I have a simple flask server that returns a JSON (flask automatically do that when you return a python dict) when it receives a GET request to / endpoint.
It's running on my 5000 port:
I know it's running and reachable as I managed to request to it, and receive a valid response, using curl, twice:
Both requests are logged into the server logs as well, on second print.
I'm trying to fetch to my server from a html/js script as:
const URL = "http://127.0.0.1:5000/"
fetch( URL )
.then(response=>response.json())
.then(json=>console.log(json))
or
const URL = "http://localhost:5000/"
fetch( URL )
.then(response=>response.json())
.then(json=>console.log(json))
But I get the same error twice:
1: GET http://localhost:5000/ net::ERR_CONNECTION_REFUSED
2: Uncaught (in promise) TypeError: Failed to fetch
I know the code itself works because I managed to fetch Githubs API:
const URL = "https://api.github.com/users/nluizsoliveira"
fetch( URL )
.then(response=>response.json())
.then(json=>console.log(json))
I'm not sure why I can't fetch to my localhost. I think it has something to do with how cloud9 deals with ports. When removing http://localhost from the url:
const URL = ":5000"
fetch( URL )
.then(response=>response.json())
.then(json=>console.log(json))
the snippet also fails, but it seems that the request is somehow appending the url to my C9 url.
Have someone faced that situation before?
Thanks a lot in advance!
EDIT:
Just to clarify, i'm not running that js/html (directly) on my browser tab. I'm running it on the C9's built in browser, which is available through "preview running application":
With AWS Cloud 9 Preview, AWS gives you a private link like https://12a34567b8cd9012345ef67abcd890e1.vfs.cloud9.us-east-2.amazonaws.com which gives you access to your application. To get that link click preview application and copy it from the browser tab.
Use that link in your code instead of localhost. AWS Documentation:Preview a running application.

Add token to src tag requests Angular 8

I'm creating an website where users can load another website i'm developing after logging in. When authorized, the users gets a JWT token, and this token is sent in the header with every request, just like any webapplication with JWT token authorization.
The problem is that I only want to send the sensitive page content to the user that is authorized. The authorization in the backend is already working with normal API calls, but I can't intercept calls made by the system itself when encountering a "src" tag for example.
The HTML file is requested like this (for convenience the header token is added here instead of in a seperate interceptor file):
redirect(response) {
console.log(response);
if(response.isAdmin) {
const headers = new HttpHeaders().set("Authorization", "Bearer " + response.token);
this.http.get("AdminPage", {headers: headers, responseType: "text"}).subscribe(response => {
// response is the HTML file
const win = window.open("", "_self");
win.document.documentElement.innerHTML = response;
});
} else {
// The users page will be loaded here just like above, with another path
// TODO
}
}
The HTML file is loaded, but when it encounters the "src" tags in it, it will request the files from the server, but the server won't respond because there is no JWT token in the request.
Is there any way to intercept this calls too? Or is there a better way to accomplish this?
(I don't know if its relevant, but my backend is written in ASPNET.Core)
You can put it as Get parameter like that
<img src="http://img.com?token=<token>"/>
Or you can use cookies like #matt suggested
It turns out it is really easy. You can just store your JWT token inside a cookie using the following code inside the controller in the backend (in ASPNET.Core):
CookieOptions cookieOptions = new CookieOptions();
cookieOptions.HttpOnly = true;
Response.Cookies.Append("auth_token", user.Token, cookieOptions);
And you can retreive the token inside the authorization middleware like this:
string token = context.Request.Cookies["auth_token"];
It is also more secure then storing it inside local/session storage because of XSS attacks.
I've read about it here:
https://www.c-sharpcorner.com/article/asp-net-core-working-with-cookie/
And here:
https://blog.logrocket.com/jwt-authentication-best-practices/
With the cookie, you don't even need to intercept the http request in the frontend, because the cookie is sent automatically with every request.
credits to #Matt Davis

Is there a way to attach JSON Web Tokens to an HTML form POST request?

I'm building a blog using node.js (no Express) where users can comment on posts. I require users to log in before they can comment, so I'm using JSON Web Tokens (created via the jsonwebtoken node module) to authenticate their log in status. When they successfully log in, a cookie containing the JWT is added to the page response header, like this:
res.writeHead(302, {"Set-Cookie": `jwt=${MYTOKENISHERE}; max-age=9000; HttpOnly`,
Location: "/blog/blog.html"
});
res.end();
When I inspect the webpage's cookies in my browser, I can see the encoded JWT - so far, so good:
Link to screenshot: https://i.ibb.co/MBzw5jX/cookies.png
The problem comes when a logged-in user tries to post a comment. I'm handling this with a POST request via an HTML form, but for some reason the JWT doesn't appear in the request object that reaches my server/router. Here is a snippet from my router code:
const cookie = require('cookie');
if (method === "POST") {
if (request.url.includes("/create/comment")) {
let cookies = cookie.parse(request.headers.cookie);
console.log("COOKIES :", cookies);
}
}
//Expected console.log() output:
COOKIES: {
jwt: [THE ENCODED JWT STRING],
_ga: 'GA1.1.1615891668.1553812077', // random google analytics cookie
gid: 'GA1.1.1919987325.1555742391' // random google analytics cookie
}
// Actual console.log() output:
COOKIES: {
_ga: 'GA1.1.1615891668.1553812077', // random google analytics cookie
gid: 'GA1.1.1919987325.1555742391' // random google analytics cookie
}
As you can see, the JWT is missing. I have tried replacing the HTML form request with an XmlHttpRequest from the DOM, but I still get the same result. The JWT appears fine in GET request headers, I'm only having this issue with POST requests.
What is the best way to pass an encoded JSON Web Token string from the client to the server?

How to stop React-Native-Windows from asking for HTTP Basic Auth credentials

I'm using React Native with the plugin for the Universal Windows Platform to access remote resources on a REST server.
When doing a fetch request for a resource that requires authorization via HTTP Basic Auth, I can provide the request with an additional "Authorization" header and everything works fine as long as the credentials are correct.
If the credentials are wrong I'm presented with a Windows-native login prompt (similar to the one when connecting to a remote computer). This prompt is not managed by my app, but automatically seems to pop up when the underlying network connection detects a 401 Unauthorized server response.
Here is what I do inside React Native:
let encodedCredentials = new Buffer(this.state.username + ":" + this.state.password).toString("base64");
let response = await fetch(this.state.serverUrl, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': "Basic " + encodedCredentials,
}
});
let responseJson = await response.text();
alert(responseJson);
The server response, when provided with incorrect credentials, includes:
Status Code: 401 Unauthorized
WWW-Authenticate: Basic realm="iOSResource"
Note that the native login prompt seems to delay the fetch request as a whole. I can enter wrong credentials and confirm the prompt multiple times without the alert firing once. Only when the prompt is explicitly cancelled, the correct credentials are entered or wrong credentials have been tried a couple of times the fetch await continues.
Unfortunately this brings up another issue: When entering wrong credentials in the popup prompt a couple of times and it finally "gives up", I can supply whatever credentials I want to the fetch request, it will not supply my own authorization header to the server in any future requests. The data sent will be stuck to whatever I entered in the prompt before it closed. In this case it does not bring up the prompt again and the request just immediately fails. That leaves me unable to correct the credentials in my own app, because they are simply not sent within the request. I have confirmed this by inspecting the outgoing data in Wireshark.
I guess Windows seems to transparently tamper with the network request to intercept special response codes and re-prompt credentials if necessary before returning the request result to the actual caller for the first time.
I want to deal with incorrect credentials in the app, instead of causing Windows to intercept requests. Is there a way to suppress this native prompt and immediately proceed with my own code in case the Basic Auth fails?
Edit: The behavior is exactly the same when using Axios instead of plain fetch. Seems like both ultimately do a XMLHttpRequest, which is filtered the same way.

How to store basic auth credentials in browser cache after XHR

Context
I have a friend, his name is Bob.
Bob have a server with an application running & accessible only in local. To access this application from the outside world, Bob installed & configured a reverse proxy with nginx and the module auth_basic.
Each request go through the authentication process by the reverse proxy. Two cases :
If a HTTP GET request contain valid HTTP header parameter Authorization: Basic base64credentials, then the reverse proxy access the local application and response accordingly. Each sub-request will not require a new authentication because the browser cache the credentials and send them in every request automatically until we close the browser.
If a HTTP GET request doesn't contain valid HTTP header parameter, the reverse proxy respond directly with the HTTP header WWW-Authenticate: Basic realm="User Visible Realm". Then the browser automatically show a dialog box to enter credentials.
Everything works fine until here. It's like expected from basic auth specification.
Problem
Bob doesn't like the default dialog box from the browser and want a nice html page with a form. He configurate the nginx server to have his own html form.
The problem is that the HTML submit process for a form, by default, doesn't send the Authorization header parameter. Bob need to use a XMLHttpRequest. He implement it and receive a good 200 HTTP response from the server when the credentials are good.
Unlike the default form behavior, with the XMLHttpRequest, the browser doesn't cache the credentials automatically in case of success. So each sub-request display again the login form :'(
Bob can't change the front code of the local application to send by himself the credentials in each request (as with a SPA). Indeed, he doesn't have access to this app. He just have access to nginx conf and his own html login form. So storage is useless here.
Questions
Is it possible for bob to make the browser cache the credentials after receive the XHR response ?
(The goal is to behave like the default behavior even when he use a XMLHttpRequest)
EDIT :
Further explanation
The local app is running on localhost. Bob didn't develop this app and he can't edit it. This app doesn't provide authentication and Bob used the basic_auth module of nginx as a reverse proxy to authenticate people.
It works good but use the default behavior of browsers which implement Basic Auth specification. This behavior display an ugly form and cache the credentials when success. If Bob provide his own form the behavior go, which is normal because the Basic Auth specification require specific header parameter (Authorization: Basic ...) that HTML form can't provide. Bob need to use XHR to provide this parameter.
The question is, how get back the good behavior of the browser with XHR ?
We can only use JS on login.html and not on the local app. Here is the workflow :
HTTP GET request to the server
Server doesn't find Authorization parameter OR credentials are wrong
Server respond login.html
User provide credentials by form. XHR is emitted with Authorization parameter.
Server find Authorization parameter AND credentials are valid
Server give back the local app entry file (for example index.html)
Browser read index.html and want request other files (img, css, js...)
These sub requests will fail because no credentials provide in these requests.
If ugly default form use, the credentials are cached automatically and it works.
I precise also that a solution would be to replace nginx basic auth reverse proxy by a real backend app and another authentication system (with cookie for example which are send automatically) which would work as a reverse proxy but it is not the question asked.
EDIT 2 :
Why Bob can't use storage solution ?
In the ulgy form scenario, he doesn't have HTML login file. When the browser client ask a request to the server, the server only response a HTTP response with the WWW-Authenticate header but without HTML content. The simple fact to have this header parameter display a form. Just putting the good credentials will send back a 200 HTTP Response and the browser will cache the credentials and send it in every request with the HTTP header Authorization: Basic.
In the login.html scenario, after a success login, we need to send back in every request the HTTP header Authorization: Basic (not a cookie, because it's how work Basic Auth spec and Bob doesn't have any backend, just the nginx module). It's possible to send this header from the login.html because we can attach JS on it. But then, the next pages respond by the server will be HTML files from the local app, where Bob doesn't have access to their HTML and can't attach JS on them to provide header Authorization: Basic for the next requests. A cookie could be stored from the login.html file, but this cookie need to be retrieved from the other pages and used to send header Authorization: Basic, which is impossible because Bob doesn't have access to the JS of these pages.
Thank you in advance.
Since you're already using ajax, just have javascript set and read a cookie:
(I use jQuery here, for simplicity, replace the ajax call with the appropriate syntax if you're not using jQuery):
function getCookie(cookiename) {
/* a function to find a cookie based on its name */
var r = document.cookie.match('\\b' + cookiename + "=([^;]*)\\b");
// document.cookie returns all cookies for this url
return r ? r[1] : undefined;
// return the regex capture if it has content, otherwise return undefined
}
function getData(auth_basic) {
$.ajax({
url: 'url_of_nginx...',
headers: {
'Authorization': 'Basic ' + auth_basic
} // send auth header on xmlhttprequest GET
}).next(function ajaxSuccess(data) {
// data from the nginx
document.cookie = '_auth_cookie=' + auth_basic;
// store my auth in a cookie
}, function ajaxFailed(jqXHR) {
// do something on failure, like
showLoginForm()
});
}
function showLoginForm() {
/* function to render your form */
// attach an event handler to form submission
$('#submit_button_id').click(function form_submitted(login_evt) {
// I clicked login
login_evt.preventDefault(); // don't really submit the form
// get my field form values
username = $('#username_input_field').val();
password = $('#password_input_field').val();
// I base64 the auth string
var auth_basic = btoa(username + ':' + password);
// try to auth
getData(auth_basic);
});
}
var auth_cookie = getCookie('_auth_cookie');
if (auth_cookie === undefined) {
// I have no cookie
showLoginForm()
} else {
getData(auth_cookie)
}

Categories

Resources