Reference: https://googlecloudplatform.github.io/google-cloud-node/#/docs/storage/0.8.0/storage/file?method=getSignedUrl
This is extremely strange. I did set my service account as having read permission of the storage objects.
What is going on ?
server:
snapshot.forEach(function(childSnapshot){
titleArray.push(childSnapshot.val().title);
usernameArray.push(childSnapshot.val().username);
keyArray.push(childSnapshot.key);
var file = bucket.file(childSnapshot.val().image);
var config = {
action: 'read',
expires: Date.now() + 10000,
contentType: 'image/png'
};
file.getSignedUrl(config, function(err, url) {
if (err) {
console.error(err);
return;
}
imageArray.push(url);
if (imageArray.length == 9) {
res.render("home", {keyArray: keyArray, titleArray: titleArray, usernameArray: usernameArray, imageArray: imageArray});
}
});
});
client:
$(".homeImage").each(function(i) {
var row = $(this)
row.attr('id', i);
if (i == 4) {
} else {
$("#"+i).css('background-image', "url('" + imageArray[i] + "')");
}
});
Response:
This is extremely strange since I thought signed URLs were supposed to authenticate my request as being sent by my service account.
The error message implies that your request doesn't have any authentication associated with it. For a Signed URL that would mean that the GoogleAccessId/Signature/Expires query parameters are not set.
I'd debug print the image array server side and client side to see where it is getting lost.
Edit: In this case it looks like & was being replaced with & somewhere.
Related
I had this weird going on.
I'm using Jersey for API end-point, HTML and javascript front end. When I authenticate user, server will response 303 to tell the client the user is authenticated and to redirect to the said page (based on user group which the user belongs to), which I put it in Location header.
But when my server response with code 303, the client (html with pure javascript in this case) receive code of 200. But if I change the code to something else, let say 401, the client receive it correctly. This happened only if server response with code 303.
Why I return 303? I'm not pretty sure myself, thought it is the right way to do it, I might just return 200, but I try to do it the proper way as much as I know and can. But that is for another time, suggestion are welcome.
And even when I try to receive the Location from header and token from cookies, it return null. As if something happened to the response from server to client. I don't have anything that change response.
Looking at the application log, everything went fine, nothing miss behave.
It was working fine, but suddenly this weird behavior happened. I already clear browser cache, clean build and deploy the app, restart tomcat, and even restart my dev machine, nothing solve it.
I not found anything related with google. My way of code things might not the right way as I'm quite new with REST.
My javascript:
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if(xhr.status === 200) {
console.log("Login API return 200");
console.log("Token: " + xhr.getResponseHeader("Token"));
} else if (xhr.status === 303) {
console.log("Status 303 with response text " + xhr.responseText);
console.log("Status 303 with redirect header of " + xhr.getResponseHeader("redirect"));
// redirect user to the page
window.location.href = xhr.getResponseHeader("Location");
} else if (xhr.status === 401) {
console.log('Unauthorized access to server.');
document.getElementById("expired").style.display = "none";
document.getElementById("credential").style.display = "block";
} else {
console.log("Returned status is " + xhr.status);
console.log("Response text is " + xhr.responseText);
}
}
};
xhr.open('POST', URL + "/api/v1/users/login");
xhr.setRequestHeader("authorization", authHeader);
xhr.send();
My java:
#POST
#Path("/login")
#Produces(MediaType.TEXT_PLAIN)
public Response authUser(#Context HttpHeaders httpHeaders,
#Context HttpServletRequest request) {
log.debug("Server name : " + request.getServerName());
String authHeader = httpHeaders.getRequestHeader("authorization").get(0);
String encodeAuth = authHeader.substring(authHeader.indexOf(' ') + 1);
String decodeAuth = new String(Base64.getDecoder().decode(encodeAuth));
String username = decodeAuth.substring(0, decodeAuth.indexOf(':'));
String password = decodeAuth.substring(decodeAuth.indexOf(':') + 1);
User user = new User(username);
log.debug("Username : " + username);
log.debug("Password : " + password);
// Check user
if (!user.login(username,password)) {
// This is a response for the ajax for unauthorized login
log.warn("Can not authenticate user, either wrong username or password.");
// Return Unauthorized response
return Response
.status(401)
.entity("Unauthorized")
.build();
}
log.debug("Authentication success. Proceed with token and cookie creation");
// Create token
String token = TokenStore.getInstance().putToken(username);
// Get user's group
String userGroup = TokenStore.getInstance().getGroup(token);
log.debug("Token created for the user is " + token);
// Create cookie
NewCookie cookie = new NewCookie("Token",token,"/app/", request.getServerName(),"User token",1800,false);
/* Create redirect URL.
* uriInfo.getBaseUriBuilder() will return http://<host>:<port>/app/api/ which is not
* desired in this scenario, the below is to eliminate /api/ from the URI
* to get only http://<host>:<port>/app
*/
String uri = uriInfo.getBaseUriBuilder().toString();
// nthLastIndexOf(2, "/", uri) is a helper method to get the nth occurrence of a character in a string, from the last character.
int secondLast = nthLastIndexOf(2, "/", uri);
String base = uri.substring(0,secondLast);
String redirect = "";
if(null != userGroup) switch (userGroup) {
case "groupA":
redirect = base + "/pageA";
break;
case "groupB":
redirect = base + "/pageB";
break;
case "groupC":
redirect = base + "/pageC";
break;
default:
break;
}
URI re = null;
log.debug("Created URI for redirect is " + redirect);
try {
re = new URI(redirect);
} catch (URISyntaxException syntax) {
log.error("Cannot convert string to URI", syntax);
} catch (Exception e) {
log.error("Cannot convert string to URI", e);
}
log.debug("Return response 303.");
// Return redirect response
return Response
.status(303)
.entity(token)
.header("Location", redirect)
.cookie(cookie)
.build();
}
I'm trying to do a post request onto my api, the api works perfectly ( I am able to post files, but not through a url), but now I'm trying to post through an url.
this is the code I have now, I removed some lines that aren't relevant to the question or were for testing.
request({
url: url + "gettoken"
, json: true
}, function (error, response, body) {
user = body;
var rs = fs.createReadStream(up.url);
var ws = request.post(url + "upload?token=" + `${user.token}&key=${user.key}&filename=${filename}`);
ws.on('drain', function () {
rs.resume();
});
rs.on('end', function () {
console.log(filename);
});
ws.on('error', function (err) {
console.error('cannot send file ' + err);
});
rs.pipe(ws);
})
Can anyone please help me.
So the idea is to upload a file that's located at up.url to another server at url + "upload?...".
Since fs.createReadStream is meant to read local files, and not URL's, you need something that can create a stream from a URL (or rather, retrieve that URL and stream the response).
You can also use request for that:
request({
url: url + "gettoken",
json: true
}, function (error, response, body) {
const user = body;
const rs = request.get(up.url);
const ws = request.post(url + "upload?token=" + `${user.token}&key=${user.key}&filename=${filename}`);
rs.on('end', function () {
console.log(filename);
});
ws.on('error', function (err) {
console.error('cannot send file ' + err);
});
rs.pipe(ws);
});
Typically, file uploads work through multipart/form-data, but your code doesn't suggest that being used here. If it is, the code would become something like this:
const ws = request.post(url + "upload?token=" + `${user.token}&key=${user.key}&filename=${filename}`, {
formData : {
the_file : rs
}
});
// no `rs.pipe(ws)`
I'm using the twitter npm package in an attempt to stream tweets from specified accounts.
I'm having trouble navigating the twitter api docs, so I'm a little confused.
I can hit the REST endpoint to get the specified user tweets info with:
var client = new Twitter({});
client.get('statuses/user_timeline', { screen_name: user }, function(error, tweets) {
if(error) throw error;
console.log(tweets);
});
How do I stream the tweets? Is it even possible? If not, how else could I accomplish this? I would like this to be as responsive and immediate as possible.
Figured it out...
var stream = client.stream('statuses/filter', { follow: userId });
stream.on('data', function(event) {
console.log(event && event.text);
});
This will client stream reader will display the tweets as they are made automatically.
Unfortunately, the screen_name of the user cannot be used, so you'll have to find that out beforehand.
I wrote the code below and was able to get last 20 tweets of a user and save them in a file log.txt.
var Twitter = require('twitter');
var fs = require("fs");
//Your keys go inside {}
var client = new Twitter({});
//These are the parameters for the API call
var params = {
screen_name: 'damiengold', //use damiengold instead of #damiengold
count: 20
};
//Perform the API call and return data
client.get('statuses/user_timeline.json', params, function(error, tweets, response) {
var dataToFile;
if (!error) {
for (var i = 0; i < tweets.length; i++) {
console.log(tweets[i].created_at + " " + tweets[i].text);
console.log("-------------------------------");
dataToFile = tweets[i].created_at + " " + tweets[i].text + "\n-----------\n";
//This block of code will append tweets to the file called "log.txt".
fs.appendFile("log.txt", dataToFile, function(err) {
// If the code experiences any errors it will log the error to the console.
if (err) {
return console.log(err);
}
})
}
}
console.log("Last 20 tweets are now displayed on the screen and saved in log.txt.");
});
I am using ADAL JS for authenticating the users against Azure AD. And as I am new to ADAL JS, I started reading with following articles, which I find very informative:
Introducing ADAL JS v1
ADAL JavaScript and AngularJS – Deep Dive
After reading the articles, I had the impression that ADAL JS intercepts the service calls and if the service url is registered as one of the endpoint in AuthenticationContext configuration, it attaches the JWT token as Authentication Bearer information.
However, I found the same is not happening in my case. And after some digging, it seemed to me that it is only possible, if adal-angular counter part is also used, which I am not using currently, simply because my web application is not based on Angular.
Please let me know if my understanding is correct or not. If I need to add the bearer information explicitly, the same can be done, but I am more concerned whether I am missing some out-of-the-box facility or not.
Additional Details: My present configuration looks like following:
private endpoints: any = {
"https://myhost/api": "here_goes_client_id"
}
...
private config: any;
private authContext: any = undefined;
....
this.config = {
tenant: "my_tenant.onmicrosoft.com",
clientId: "client_id_of_app_in_tenant_ad",
postLogoutRedirectUri: window.location.origin,
cacheLocation: "sessionStorage",
endpoints: this.endpoints
};
this.authContext = new (window["AuthenticationContext"])(this.config);
Also on server-side (WebApi), Authentication configuration (Startup.Auth) is as follows:
public void ConfigureOAuth(IAppBuilder app, HttpConfiguration httpConfig)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = "my_tenant.onmicrosoft.com",
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = "client_id_of_app_in_tenant_ad"
}
});
}
However, the Authorization is always null in request.Headers.
UPDATE: It seems that the same applies for auto-renewal of tokens as well; when used in conjunction with adal-angular, the renewal of token works seamlessly by calling AuthenticationContext.acquireToken(resource, callback) under the hood. Please correct me if I am wrong.
After reading the articles, I had the impression that ADAL JS intercepts the service calls and if the service url is registered as one of the endpoint in AuthenticationContext configuration, it attaches the JWT token as Authentication Bearer information.
This will work only if your application is angular based. As you mentioned, the logic for this lives in adal-angular.
If, however, you want to stick to pure JS, you will not get the automatic "get-access-token-and-attach-it-to-header" support. You can use acquireToken(resource, callback api to get a token for the endpoint. But you will have to do some work in the controller that is sending the request to the api.
This might give you some idea: https://github.com/Azure-Samples/active-directory-javascript-singlepageapp-dotnet-webapi/blob/master/TodoSPA/App/Scripts/Ctrls/todoListCtrl.js. This sample does not uses angular.
ADAL.JS is incompatible with v2.0 implicit flow. I could not get it working since I set my project up recently and don't think projects are backwards compatible.
This was very confusing and took me a long time to figure out that I was mixing up the versions, and can't use ADAL.JS with v2.0. Once I removed it, things went much smoother, just did a couple of XHR requests and a popup window, no magic actually required!
Here is code for v2:
function testNoADAL() {
var clientId = "..guid..";
var redirectUrl = "..your one.."
var authServer = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?";
var responseType = "token";
var stateParam = Math.random() * new Date().getTime();
var authUrl = authServer +
"response_type=" + encodeURI(responseType) +
"&client_id=" + encodeURI(clientId) +
"&scope=" + encodeURI("https://outlook.office.com/Mail.ReadWrite") +
"&redirect_uri=" + encodeURI(redirectUrl) +
"&state=" + stateParam;
var popupWindow = window.open(authUrl, "Login", 'width=' + 300 + ', height=' + 600 + ', top=' + 10 + ', left=' + 10 + ',location=no,toolbar=yes');
if (popupWindow.focus) {
popupWindow.focus();
}
}
Note: redirectUrl will appear in popup window, needs to have code in it to pass location hash, such as this:
<script>window.opener.processMicrosoftAuthResultUrl(location.hash);window.close();</script>
function processMicrosoftAuthResultUrl(hash) {
if (hash.indexOf("#") == 0) {
hash = hash.substr(1);
}
var obj = getUrlParameters(hash);
if (obj.error) {
if (obj.error == "invalid_resource") {
errorDialog("Your Office 365 needs to be configured to enable access to Outlook Mail.");
} else {
errorDialog("ADAL: " + obj.error_description);
}
} else {
if (obj.access_token) {
console.log("ADAL got access token!");
var token = obj.access_token;
var url = "https://outlook.office.com/api/v2.0/me/MailFolders/Inbox/messages";
$.ajax({
type: "GET",
url: url,
headers: {
'Authorization': 'Bearer ' + token,
},
}).done(function (data) {
console.log("got data!", data);
var message = "Your latest email is: " + data.value[0].Subject + " from " + data.value[0].From.EmailAddress.Name+ " on " + df_FmtDateTime(new Date(data.value[0].ReceivedDateTime));
alertDialog(message);
}).fail(function () {
console.error('Error getting todo list data')
});
}
}
}
function getUrlParameters(url) {
// get querystring and turn it into an object
if (!url) return {};
if (url.indexOf("?") > -1) {
url = url.split("?")[1];
}
if (url.indexOf("#") > -1) {
url = url.split("#")[0];
}
if (!url) return {};
url = url.split('&')
var b = {};
for (var i = 0; i < url.length; ++i) {
var p = url[i].split('=', 2);
if (p.length == 1) {
b[p[0]] = "";
} else {
b[decodeURIComponent(p[0])] = decodeURIComponent(p[1].replace(/\+/g, " "));
}
}
return b;
}
I am creating a login application with node.js, I seem to have ran into a knowledge deficit in the area of transferring strings from the server to html.
I posted my current code at jsfiddle.
My application verifies the credentials to the mysql table then generates a basic token that contains the username password and the ip address of the user.
In the last block of code, where the client html posts to the server, I have two segments where you see send to basic user page and send to admin page.
I have attempted to research this subject, but i get nothing pertinent to the situation. can anyone guide me in the right direction on sending the user to the admin or user page while sending the token alongside of it?
As well, how can the express server send data to the client, for example
on the page, I want the database to hold pertinant information regarding the user, like address and phone number. How can this information be transmitted from the server to the client via html?
app.post('/', urlencodedParser, function (req, res) {
var date = new Date();
con.query("SELECT * from users WHERE username=" + con.escape(req.body.username) + " AND password=" + con.escape(req.body.password), function (err, rows, fields) {
if (err) {
console.log(err);
} else {
var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
if (rows == '' && rows == '') {
console.log('User Failed to login to the server with #'.red + con.escape(req.body.username) + ':' + con.escape(req.body.password));
res.sendFile(__dirname + '/admin/failure.html');
} else {
var isadmin = rows[0].admin;
var cryptomap = [req.body.username + ',' + req.body.password + ',' + ip];
var strcryptomap = cryptomap.toString(); // convert array to string
var token = encrypt(strcryptomap);
console.log(token + ' SENT'.red);
var backto = decrypt(token); //decr
var arr = backto.toString().split(","); // SPLITTING STRING TO SATISFY /VERIFY *************************************************
console.log(arr[0] + ' has valid token, encryption succsessful'.green);
con.query('UPDATE users SET crypto=' + con.escape(token) + 'WHERE username=' + con.escape(req.body.username), function (err, rows, fields) {
if (err) {
res.send(500);
} else {
console.log('Updated Crypto for ' + req.body.username);
if (isadmin == 0) {
// send to basic user page
res.send('USER');
} else {
//send to admin user page
res.sendto('http://google.com/?' + token);
}
}
});
}
}
});
});
To start, I'll answer the actual question you are asking.
The way I normally handle what you are trying to accomplish, is by using an ajax POST from the front end with the users credentials(https of course, never send credentials using http), have the server authenticate the user, create the token and respond to the ajax post with the token. From here, if the authentication was successful and the server responded with a token and whatever other information you wanted to get, you have a few options for storing it, I personally use cookies. After the token is stored, let the front end handle the redirect.
Having said all of that, I would definitely read up on authentication principles and the different ways your system can be attacked. I see a couple of red flags dealing with pretty basic authentication ideas/strategies.
Edit : Here is an example AJAX post to a login API endpoint that responds with a token and saves the username and token to cookies. Obviously your result data in the success function may be organized differently than mine but can be accessed in the same way. You can send whatever data you would like back in this result object and redirect accordingly
var loginData = {
username : $('#loginUsername').val(),
password : $('#loginPassword').val()
}
$.ajax({
type : "POST",
url : [your-endpoint-url],
data : loginData ,
success : function(result) {
setCookie('appUN', result.username);
setCookie('appTok', result.token);
location.href = '/dashboard';
},
error : function(result) {
location.href = '/login/Error';
}
});
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + 10800000);
var expires = "expires="+d.toUTCString();
var path = "path=/";
document.cookie = cname + "=" + cvalue + "; " + expires + ";" + path;
}
To actually send the data back to the client from the server, in your API endpoint, you would do all of your logic to check the users credentials and if their credentials were valid, you could create a token and return something like
res.json({
username: username,
token: token
});
and this JSON object will be available in the success function as shown above.
If the users credentials were invalid, you could return something like
res.status(400).json({Message : "The username or password is incorrect"});
and because of the 400 status, it will be caught by the error function of your AJAX request