Authenticating to S3 from Meteor Mobile Application - javascript

I have a Meteor Mobile app that accesses a lot of photos stored in my S3 bucket. These photos are user uploaded and change frequently. I don't want these photos to be accessible to anyone that isn't using my app. (ie: these photos are only viewable from my application and going to the url directly in a browser won't load them).
What is the best way to accomplish this? AWS Cognito seems to be the logical choice, but it doesn't seem easy to implement and I'm not exactly sure how to authenticate to AWS from the client once it gets a Cognito identity.
My other thought was putting a read only AWS Key on every url and authenticating that way, but that's almost pointless. It would be really easy to find out the key and secret.
EDIT:
To be specific, the URLs for the images are in a Mongo collection and I pass them into a template. So, the S3 resources are just loaded up with an image tag (<img src="). Something like AWS STS sounds like a great option, but I don't know of a way to pass the tokens in the headers when I'm loading them like this. Doing them as a pre-signed query string seems inefficient.
Another option is to restrict access with the referrer header, like this issue. But like Martijn said, it isn't really a secure way of doing it.

After some research and testing I solved this myself. My ultimate solution was to use the referer header to limit access to my S3 bucket. I created a more secure and detailed solution (see below), but it came with a performance hit that wouldn't work for my app. My app is based around viewing photos and videos, and not being able to have them load near instantly wasn't in the cards. Although, I feel like it could be a sufficient solution for most use-cases. Because my app isn't highly sensitive, the referer header is sufficient for me. Here is how to use the http header referer to limit access to a bucket.
Solution using Amazon's STS:
First, you need to have the AWS SDK on both the server and the client. There was no up to date packages for Meteor available, so I created my own. (I'll publish it shortly and put a link here once I do.)
On the server, you must use credentials that have the ability to assume a role. The role to be assumed must have a Trust Relationship with the user that is assuming the role. Article on using IAM. - Article on using credentials with SDK
In the server.js file I created a Meteor Method that I can call from the client. It first checks if a user is logged in. If that's true, it checks to see if it's current temp-credentials are expiring in the next 5 minutes. If they are, I issue new credentials and either write them to the user document or return them as a callback. If they aren't expiring in the next 5 minutes, I return their current temp-credentials.
You must use Meteor.bindEnvironmentfor the callback. See docs
Meteor.methods({
'awsKey': function(){
if (Meteor.userId()){
var user = Meteor.userId();
var now = moment(new Date());
var userDoc = Meteor.users.findOne({_id: user});
var expire = moment(userDoc.aws.expiration);
var fiveMinutes = 5 * 60 * 1000;
var fut = new Future();
if(moment.duration(expire.diff(now))._milliseconds < fiveMinutes ){
var params = {
RoleArn: 'arn:aws:iam::556754141176:role/RoleToAssume',
RoleSessionName: 'SessionName',
DurationSeconds: 3600 //1 Hour
};
var sts = new AWS.STS();
sts.assumeRole(params, Meteor.bindEnvironment((err, data) => {
if (err){
fut.throw(new Error(err));
}else{
Meteor.users.update({_id: user}, {$set: {aws: {accessKey: data.Credentials.AccessKeyId, secretKey: data.Credentials.SecretAccessKey, sessionToken: data.Credentials.SessionToken, expiration: data.Credentials.Expiration}}});
fut.return(data.Credentials);
}
}));
return fut.wait();
}else{
return userDoc.aws;
}
}
}
}
});
Then you can invoke this method manually or in an setInterval on Meteor.startup.
Meteor.setInterval(function(){
if(Meteor.userId()){
Meteor.call('awsKey', function(err, data){
if (err){
console.log(err);
}else{
if(data.accessKey){
Session.set('accessKey', data.accessKey);
Session.set('secretKey', data.secretKey);
Session.set('sessionToken', data.sessionToken);
}else{
Session.set('accessKey', data.AccessKeyId);
Session.set('secretKey', data.SecretAccessKey);
Session.set('sessionToken', data.SessionToken);
}
}
});
}
}, 300000); //5 Minute interval
This way just sets the keys in a Session variable from the callback. You could do this by querying the user's document to get them as well.
Then, you can use these temporary credentials to get a signed URL for the object you are trying to access in your bucket.
I put this in a template helper by passing the object name to it in the template:
{{getAwsUrl imageName}}
Template.templateName.helpers({
'getAwsUrl': function(filename){
var accessKey = Session.get('accessKey');
var secretKey = Session.get('secretKey');
var sessionToken = Session.get('sessionToken');
var filename = filename;
var params = {Bucket: 'bucketName', Key: filename, Expires: 6000};
new AWS.S3({accessKeyId: accessKey, secretAccessKey: secretKey, sessionToken: sessionToken, region: 'us-west-2'}).getSignedUrl('getObject', params, function (err, url) {
if (err) {
console.log("Error:" +err);
}else{
result = url;
}
});
return result;
}
});
That's all there is to it! I'm sure this can be refined to be better, but this is just what I came up with in testing it really fast. Like I said, it should work in most use cases. My particular one didn't. For some reason, when you tried to toggle the visibility: visible|hidden; on an img src of these signedURLs they would take a lot longer to load than just setting the URL directly. It must be because Amazon has to decrypt the signed URL on their side before return the object.
Thanks to Mikkel for the direction.

Related

Use git credential manager to fetch azure devops api instead of personal access token

I am trying to fetch git azure devops api to get information about repositories and branches in js.
In order to achieve that, I made a little application with the following code :
$(document).ready(function() {
var personalToken = btoa(':'+'<personnalAccessToken>');
fetch('https://dev.azure.com/<company>/<project>/_apis/git/repositories?api-version=5.1', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
'Authorization': 'Basic '+ personalToken
}
}).then(function(response) {
return response.json();
}).then(function(repositories) {
console.log("There are "+repositories.count+" repositories");
}).catch(function(error) {
console.log('Fetch error: ' + error.message);
});
This code is working great but as you can see there is my personnalAccessToken writen directly inside the code... which is really bad...
When I am using git in command line, I don't have to specify any credential information because I use git credential manager for windows. Which means my personnalAccessToken is already stored, cached and automatically used everytime I use a git command, like clone, etc.
So, I would like my js code to use the same thing, I would like it to use my stored credentials automatically to fetch the api without being required to set my personnalAccessToken in code.
I have already searched for hours but can't find out if it is possible.
I have already searched for hours but can't find out if it is
possible.
Sorry but as I know it's impossible. The way you're calling the Rest API is similar to use Invoke-RestMethod to call rest api in Powershell.
In both these two scenarios, the process will try to fetch PAT for authentication in current session/context and it won't even try to search the cache in Git Credential Manager.
You should distinguish the difference between accessing Azure Devops service via Rest API and by Code:
Rest API:
POST https://dev.azure.com/{organization}/{project}/{team}/_apis/wit/wiql?api-version=5.1
Request Body:
{
"query": "Select [System.Id], [System.Title], [System.State] From WorkItems Where [System.WorkItemType] = 'Task' AND [State] <> 'Closed' AND [State] <> 'Removed' order by [Microsoft.VSTS.Common.Priority] asc, [System.CreatedDate] desc"
}
Corresponding Code in C#:
VssConnection connection = new VssConnection(new Uri(azureDevOpsOrganizationUrl), new VssClientCredentials());
//create http client and query for resutls
WorkItemTrackingHttpClient witClient = connection.GetClient<WorkItemTrackingHttpClient>();
Wiql query = new Wiql() { Query = "SELECT [Id], [Title], [State] FROM workitems WHERE [Work Item Type] = 'Bug' AND [Assigned To] = #Me" };
WorkItemQueryResult queryResults = witClient.QueryByWiqlAsync(query).Result;
Maybe you can consider using a limited PAT, limit its scope to Code only:
I know there exists other Authentication mechanism
:
For Interactive JavaScript project: ADALJS and Microsoft-supported Client Libraries.
You can give it a try but I'm not sure if it works for you since you're not using real Code way to access the Azure Devops Service... Hope it makes some help :)
If you have the script set up in an Azure Runbook you can set it as an encrypted variable there and have it pull it from there before running rather than having it directly written into the code.
$encryptedPatVarName = "ADO_PAT"
$adoPat = Get-AutomationVariable -Name $encryptedPatVarName
$adoPatToken = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($adoPat)"))
$adoHeader = #{authorization = "Basic $adoPatToken"}
The above is the Powershell version of it. I have seen some people do it with other

Node basic auth with loading credentials from Database

I am using NodeJs on server and I need to add some basic auth function, which asks the user for the credentials before any file (like html or js files) is downloaded to the user's browser. When I tried to view the cource code when I am not log in, I can't be able to access it like in this.
This works fine with basic-auth module, but only for one username and password.
app.use(basicAuth('username', 'password'));
And there is the problem - I need to load this credentions from Database and check if the user is admin and compare his encrypted password.
I tryed this:
// ****
var basicAuth = require('basic-auth');
// ****
app.use(function (req, res, next) {
var user = basicAuth(req);
if (user) {
var c = mysql.createConnection(db);
var q = "SELECT * FROM user WHERE email = '" + user.name + "' AND admin = 1;";
c.query(q, function (error, result) {
c.end();
if (error) {
console.log(error);
res.set('WWW-Authenticate', 'Basic realm="example"');
return res.status(401).send();
} else {
if (result.length === 1 &&
bcrypt.compareSync(user.pass, result[0].password)) {
return next();
} else {
res.set('WWW-Authenticate', 'Basic realm="example"');
return res.status(401).send();
}
}
});
} else {
res.set('WWW-Authenticate', 'Basic realm="example"');
return res.status(401).send();
}
});
This works fine, but this function is called every time, when the browser communicates with the server, which is about 70 times on loading simple page (It is not asking the user for the credentials again, "only" run this function). This function lasts about 200ms on every request (because of the query and password comparing), in sum the loading lasts about 15 seconds, which is not acceptable.
Can you someone please help me, how to figured out this problem or recommend me some other NodeJs library, which can help me with that?
Thank you.
So if you store you hash in the DB and the Admin Flag in your DB, why don't you do the following Select request :
var q = "SELECT * FROM user WHERE email = '" + user.name + "' AND password = '" + bcrypt.hashSync(user.pass) + "' AND admin = '1';";
If you get only one result it is OK, otherwise it is refused...
If I understand your question, every request to the server is running your authentication, causing page loads to be slow. I don't understand how someone can log in if all the routes are behind the basic auth check you are doing, but that is besides the point.
The way I set my node apps up are to create an array of functions for auth and pass these in to the specific routes where I require authentication, this way it is being run for each resource I may be serving. For example(psuedocode-ish):
// this is the authentication function array, each function in the array
// will be run when this array is called on a route, require next()
// after successful auth to continue on through the array/route handlers
// for your example, adminAuth would be the anonymous
// function in your app.use method
var auth = [adminAuth];
// some random route examples
// this is an unprotected route
app.get('/login', loginHandler);
// these are protected routes and will have auth called
app.get('/api/users', auth, getUserHandler);
app.post('/api/user', auth, postUserHandler);
So in conclusion, you can put the auth call only on protected resources, this way images, html, js, css, etc can be served without running the auth check everytime.
There are a few issues with your code. I recognize that you need to do the password check every request, but you don't necessarily need to hit the database every time. Assuming that you must do HTTP Basic (not recommended anymore, and I hope if you are, that you're doing it over HTTPS), then one major improvement you can make is if you create some kind of fast data cache. The easiest form of this would be in-memory (assuming you're OK with each instance of your app keeping its own copy), but you could also use something like memcached or redis for it instead depending on your use case.
A really trivial implementation would be just keeping an in-memory JS object with a key equal to the username and the value equal to the data returned from the db. Something like:
var userCache = {}; // something local to the module is the easiest way
Then, when you are going to query the db, you just check the userCache first:
if(userCache[user.name]) // check password
else
// do your query and insert a successful result into the userCache for later.
Just doing that should significantly reduce your response times per resource, though again for a production solution you'll probably want multi-tier caching (e.g. an in-memory cache and a redis or memcached cache to help performance across multiple instances of your app).
Second, you should use the callback version of the bcrypt check. It won't be faster on a given request, but it may give your application more overall request capacity, which should help.
Third, your actual SQL query is a bad idea for three reasons. First, in most databases, you'll have worse performance for using a SELECT * vs selecting the specific fields you want. This is both on the DB side (query cache optimization) as well as on the Node side (time to deal with fields getting converted / cast between a string response and a JS object for example). Second, the string concatenation you're doing is an invitation for SQL Injection attacks. Look into using parameterized queries instead. Lastly, if you know your users will be unique, you should also add a LIMIT 1 to the query, since you're only expecting one result and again the DB can do some optimization around that.

How do I solve the AWS error: "The difference between the request time and the current time is too large" front-end only?

I currently have an app that allows uploads to AWS S3, the upload is handled purely from the front-end of the app in js with the aws-sdk. We had some users facing this issue mentioned in the title (The difference between the request time and the current time is too large) which prevents them from uploading properly.
I am aware of the solutions provided here, but I was wondering if there was anything I could do to make sure this doesn't happen again for any of the users, with front-end changes only. Is there a way I can make my request be in sync properly ?
I tried having some users sync their clock, but it either did not work or they didn't do it properly. Unfortunately I cannot rely on the user to fix this.
I faced similar issue with S3 upload and on Cognito. Try adding correctClockSkew: true in the config block where you set accessKeyId and secretAccessKey. Like below:
AWS.config.update({
accessKeyId: awsCredentials.ACCESS_KEY_ID,
secretAccessKey: awsCredentials.SECRET_KEY_ID,
correctClockSkew: true,
});
From:
https://github.com/aws/aws-sdk-js/issues/399#issuecomment-233057244
Try this:
AWS.events.on('retry', function(response) {
if (response.error.name === 'RequestTimeTooSkewed') {
console.error('User time is not correct. Handling error!');
console.log('AWS systemClockOffset:', AWS.config.systemClockOffset);
var serverTime = Date.parse(response.httpResponse.headers['date']);
var timeNow = new Date().getTime();
console.log('AWS timestamp:', new Date(serverTime));
console.log('Browser timestamp:', new Date(timeNow));
AWS.config.systemClockOffset = Math.abs(timeNow - serverTime);
response.error.retryable = true;
console.log('Setting systemClockOffset to:', AWS.config.systemClockOffset);
console.log('Retrying uploading to S3 once more...');
}
});
You detect how much the clock is off by, set AWS.config.systemClockOffset to the difference, and retry

Parse Server Node.js SDK: Alternative to Parse.User.become?

I want to completely dissociate my client app from Parse server, to ease the switch to other Baas/custom backend in the future. As such, all client request will point to a node.js server who will make the request to Parse on behalf of the user.
Client <--> Node.js Server <--> Parse Server
As such, I need the node.js server to be able to switch between users so I can keep the context of their authentification.
I know how to authentificate, then keep the sessionToken of the user, and I ve seen during my research than the "accepted" solution to this problem was to call Parse.User.disableUnsafeCurrentUser, then using Parse.User.become() to switch the current user to the one making a request.
But that feels hackish, and I m pretty sure it will, sooner or later, lead to a race condition where the current user is switched before the request is made to Parse.
Another solution I found was to not care about Parse.User, and use the masterKey to save everything by the server, but that would make the server responsible of the ACL.
Is there a way to make request from different user other than thoses two?
Any request to the backend (query.find(), object.save(), etc) takes an optional options parameter as the final argument. This lets you specify extra permissions levels, such as forcing the master key or using a specific session token.
If you have the session token, your server code can make a request on behalf of that user, preserving ACL permissions.
Let's assume you have a table of Item objects, where we rely on ACLs to ensure that a user can only retrieve his own Items. The following code would use an explicit session token and only return the Items the user can see:
// fetch items visible to the user associate with `token`
fetchItems(token) {
new Parse.Query('Item')
.find({ sessionToken: token })
.then((results) => {
// do something with the items
});
}
become() was really designed for the Parse Cloud Code environment, where each request lives in a sandbox, and you can rely on a global current user for each request. It doesn't really make sense in a Node.js app, and we'll probably deprecate it.
I recently wrote a NodeJS application and had the same problem. I found that the combination of Parse.User.disableUnsafeCurrentUser and Parse.User.become() was not only hackish, but also caused several other problems I wasn't able to anticipate.
So here's what I did: I used
Parse.Cloud.useMasterKey(); and then loaded the current user by session ID as if it was a regular user object. It looked something like this:
module.exports = function(req, res, next) {
var Parse = req.app.locals.parse, query;
res.locals.parse = Parse;
if (req.session.userid === undefined) {
res.locals.user = undefined;
return next();
}
Parse.Cloud.useMasterKey();
query = new Parse.Query(Parse.User);
query.equalTo("objectId", req.session.userid);
query.first().then(function(result) {
res.locals.user = result;
return next();
}, function(err) {
res.locals.user = undefined;
console.error("error recovering user " + req.session.userid);
return next();
});
};
This code can obviously be optimized, but you can see the general idea. Upside: It works! Downside: No more use of Parse.User.current(), and the need to take special care in the backend that no conditions occur where someone overwrites data without permission.

Access Meteor.userId from outside a method/publish

I'm currently writing a server-centric package for Meteor, and the relevant code looks something like this:
__meteor_bootstrap__.app.stack.unshift({
route: route_final,
handle: function (req,res, next) {
res.writeHead(200, {'Content-Type': 'text/json'});
res.end("Print current user here");
return;
}.future ()
});
This is obviously a relatively hacky way of doing things, but I need to create a RESTful API.
How can I access Meteor.userId() from here? The docs say it can only be accessed from inside a method or publish. Is there any way around that?
Things I've tried:
Capture it from a publish using Meteor.publish("user", function() { user = this.userId() });
Get the token + user id from the cookies and authenticate it myself using something like Meteor.users.findOne({_id:userId,"services.resume.loginTokens.token":logintoken});
Create a method called get_user_id and call it from inside my code below.
The thing that you need to target first is that to get something that can identify the user from headers (especially because you want to get the username at a point where no javascript can run).
Meteor stores session data for logins in localStorage, which can only be accessed via javascript. So it can't check who is logged in until the page has loaded and the headers have been passed.
To do this you need to also store the user data as a cookie as well as on localStorage:
client side js - using cookie setCookie and getCookie functions from w3schools.com
Deps.autorun(function() {
if(Accounts.loginServicesConfigured() && Meteor.userId()) {
setCookie("meteor_userid",Meteor.userId(),30);
setCookie("meteor_logintoken",localStorage.getItem("Meteor.loginToken"),30);
}
});
server side route
handle: function (req,res, next) {
//Parse cookies using get_cookies function from : http://stackoverflow.com/questions/3393854/get-and-set-a-single-cookie-with-node-js-http-server
var userId = get_cookies(req)['meteor_usserid'];
var loginToken = get_cookies(req)['meteor_logintoken'];
var user = Meteor.users.findOne({_id:userId, "services.resume.loginTokens.token":loginToken});
var loggedInUser = (user)?user.username : "Not logged in";
res.writeHead(200, {'Content-Type': 'text/json'});
res.end("Print current user here - " + loggedInUser)
return;
}.future ()
The cookie allows the server to check who is logged in before the page is rendered. It is set as soon as the user is logged in, reactively using Deps.autorun
My solution was inspired by the server part of #Akshat's method. Since I'm making a RESTful API, I just pass the userId/loginToken in every time (either as a param, cookie or header).
For anyone interested, I bundled it as a package: https://github.com/gkoberger/meteor-reststop

Categories

Resources