Letting visitors try out user-only features that write to DB - ruby-on-rails

My site lets people create database entries (which most rails apps do), and I realized that there's a huge drop-off from landing on the site to actually signing up to try it. Basically the service lets users build their own document by combining different components. I'm thinking about adding an interface where visitors who are not yet registered can try out the features (building stuff) and ask them to sign up at the last stage, when they're about to publish their document.
First thing that comes to mind is use HTML5 local storage, but then another idea came to mind: maybe I could create a temporary user whenever a visitor tries out the features, and then later remove them from the database if they don't sign in. I'm not sure if this is safe, but this seems like it might be easier than dealing with all the local storage issues.
What would be the best practice for this type of situation?

HTML5 storage would be an option, tho most likely a lot of client side coding.
Other options would be to have a duplicate table of these 'demo' documents which you can clear every now and again for users that did not sign up. You could also just store the document in the user session, as you don't need it permanently stored, and then store it in the database once they have signed up.

Related

Notify users PDF creation is ready and can be downloaded

Users can create a PDF in my app which takes some time to generate, so it has to be done in a background job. No problem, but then there is a delay and the user must be notified that the PDF is ready.
So the first choice is to send an email with a download link or a push notification in the app itself. My preference is the push notification, so I guess ActionCable is the way to go? My app runs on Heroku, so is ActionCable also a good choice then or is another solution preferable?
Then there is another consideration, where to store the generated PDF until the user downloads it? I could upload it to Azure/S3/etc with ActiveStorage, or I could store it temporarily in an app folder and delete it after download. My preference is to do the last, because the PDF is there only for a few minutes and therefore the hassle to store it in the cloud is not really needed?
You have a very broad question here, which is very much dependent on the overall user needs and experience you want them to have.
I'll start with the simplest part, in terms of temporary storage of the PDF. There are several things to bear in mind here.
I would say that from a scalability, and application security standpoint, storing the PDF to the cloud is the way to go. Opening up writable directories on your application server carries a risk. Also, if you ever need to scale to more than one server, this will not work. Deleting items from cloud storage is not hard with the appropriate APIs.
Is it essential for the user to be authenticated in some way to download the PDF? This is more challenging if you push the PDF to a cloud bucket (unless you have the PDF named with a very complex, unguessable name, that name only accessed through the authenticated application). If the data is less sensitive, then your email notification can show the link directly, but you won't know easily if a user has retrieved the PDF and it is now ready to be deleted.
In terms of notification, I'd go with email for several reasons. Simplicity is the main one. Do you have experience with ActionCable? It appears simple on the surface, but there are many things to bear in mind when using it: infrastructure and UI being the major ones. Also, from a user experience perspective, are users likely to hang around in the application waiting for the PDF to be completed? What happens if they logout? How will they know the PDF is available?
If the timescale for generation of the PDF is short and absolutely optimized scalability is not a big deal, you could consider a simpler mechanism that checks for user notifications (a simple query onto a user_notifications table for example) for every user action, and use a flash or some other session flag that the UI can check and use to asynchronously retrieve the notification.
Just ideas. Impossible to give definitive answers.

Storage of user data

When looking at how websites such as Facebook stores profile images, the URLs seem to use randomly generated value. For example, Google's Facebook page's profile picture page has the following URL:
https://scontent-lhr3-1.xx.fbcdn.net/hprofile-xft1/v/t1.0-1/p160x160/11990418_442606765926870_215300303224956260_n.png?oh=28cb5dd4717b7174eed44ca5279a2e37&oe=579938A8
However why not just organise it like so:
https://scontent-lhr3-1.xx.fbcdn.net/{{ profile_id }}/50x50.png
Clearly this would be much easier in terms of storage and simplicity. Am I missing something? Thanks.
Companies like Facebook have fairly intense CDNs. They may look like randomly generated urls but they aren't, each individual route is on purpose and programed to be handled in that manner.
They aren't after simplicity of storage like you would be if you were just using a FTP to connect to a basic marketing website server. While you may put all your images in a /images folder, Facebook is much too complex for this. Dozens of different types of applications accessing hundreds if not thousands of CDNs and servers world wide.
If you ever build a web app, such as a Ruby on Rails app, and you work with a services such as AWS (Amazon Web Services) you'll also encounter what seems like nonsensical urls. But it's all part of the fast delivery network provided within the architecture. Every time you "push" your app up to the server new urls are generated for each unique resource automatically, css files, JavaScript files, image files, etc all dynamically created. You don't have to type in each of these unique urls individually each time you publish the app, the code simply knows where to look for those as a part of the publishing process.
Example: you tell the web app to look for
//= require jquery
and it returns you http://example.com/assets/jquery-eb3e278249152b5b5d5170b73d9dbf52.js?body=1 in your header.
It doesn't matter that the url is more complex than it should be, the application recognizes it, and that's all that matters.
Simply put, I think it can boil down to two main reasons: Security and Cache:
Security - Adding these long unpredictable hashes prevent others from guessing photo URLs and makes it pretty hard to download photos you aren't supposed to.
Consider what would happen if I could easily guess your profile photo URL and download it, even when you explicitly chose to share it only with friends.
Cache - by adding "random" query params to each photo, you make sure each photo instance gets its own URL. Thus you can store the photo in browser's cache for a long time, knowing that whenever you replace it with a new one, the new photo will have a fresh URL and the browser won't keep showing you the old photo.
If you were to keep the same URL for each user's profile photo (e.g. https://scontent-lhr3-1.xx.fbcdn.net/{{ profile_id }}/50x50.png), and then upload a new photo, either one of these can happen:
If you stored the photo in browser's cache for a long time, the browser will keep showing you the cached version (as long as URL is the same, and cache hasn't expired, there's no need to re-download the image).
If, instead, you only keep the image in cache for short period of time, you end up hitting your server much more then actually needed, increasing the load and hurting performance.
I hope this clarifies it.
With your route scheme, how would you avoid strangers to access the pictures of a private account? The hash also prevent bots to downloads all the pictures.
I get your pain :-) I might not stay with describing how this problem could appear more, but rather let me speak of a solution. Well it is normal that in general code while dealing with hashed value or even base64ed value it seems likes mess to deal with, but with an identifier to explain along, it does not remain much!
I use to work in a company where we use to collate Facebook post, using Graph API get its Insights Object and extract information from it for easy passing around within UI and sending back to our Redis cache store; and once we defined a data-structure in TaffyDB how an object organization is going to look like, everything just made sense with its ability to query the useful finite from long junk looking stream of minified Javascript stream
Refer: http://www.taffydb.com/
The extra values in the URL are useful to:
Track access. This is like when a newspaper appends "&homepage" vs. "&email" to an article URL, so their system knows how a reader found the page.
Avoid abuse and control access. Imagine that a user loaded a small, popular pornographic image into a profile image. They could then hijack the CDN to be a free web host for their porn site. But that code is used internally by the CDN to limit the number of views.

How do I achieve single sign on and data sharing across 2 rails apps?

I am looking to set up 2 rails apps (with the same tld) which have single sign on and share some user data. If I have railsapp.com I will have the second app set up as otherapp.railsapp.com or railsapp.com/otherapp. I will most likely have railsapp.com handle registration/login etc (open to suggestion if this is not the best solution).
So lets say I sign up and upload an avatar and start accumulating user points on the main-app, I can then browse to the other-app and my profile there has the correct avatar and points total. is there an easy way to achieve this? Do the available SSO solutions create the user in the second app with the same user ID? if not, how are they tied together? (ie how can I query the other app for information I would like to be shared across the 2 - user points and avatar) I was initially looking at sharing a database or at least the user table between the 2 apps, but I can't help thinking there must be an easier solution?
I think the simplest solution is if you set the cookie on the .railsapp.com domain, then it should be sent when you do requests to otherapp.railsapp.com or any other subdomain (just stressing that as it might be a security concern). Remember to mark the cookie as secure!
And a extra bit you might need to make this work, is to store authentication tokens on a database so they can be shared between the two apps.
Disclaimer: I don't have much experience with Rails anymore, so I'm not sure if some of the frameworks like Devise can do something like this out of the box.
 Edit
Got curious and ... google had the answer: http://codetheory.in/rails-devise-omniauth-sso/

Keeping users uploaded documents private

I believe this question is platform/technology independent, however I am using Ruby on Rails with the carrierwave gem.
Users upload documents to my site, and I need to keep them private. I am exploring the different options available to me, along with their advantages and disadvantages.
Option 1
Obfuscate urls to images to make them impossible to guess.
This would be relatively simple to implement and fast to serve up. However, if a url was made public by whatever means, security is lost.
Option 2
Have documents accessed through some sort of intermediate step that requires authentication. This would have improved security over option 1, but would place additional load on the server. A page containing previews of a number of uploaded documents would hammer the server.
Are there any other options available to me? Have I made any mistakes with my claims, or missed any important points?
I think the best option you have is to have a "key" for your documents. You can generate a key, with a certain lifetime, and when you go on /document/name/access_key, you find the record matching and return the file associated with the record. Never exposing the real URL.

Rails - Store unique data for each open tab/window

I have an application that has different data sets depending on which company the user has currently selected (dropdown box on sidebar currently used to set a session variable).
My client has expressed a desire to have the ability to work on multiple different data sets from a single browser simultaneously. Hence, sessions no longer cut it.
Googling seems to imply get or post data along with every request is the way, which was my first guess. Is there a better/easier/rails way to achieve this?
You have a few options here, but as you point out, the session system won't work for you since it is global across all instances of the same browser.
The standard approach is to add something to the URL that identifies the context in which to execute. This could be as simple as a prefix like /companyx/users instead of /users where you're fetching the company slug and using that as a scope. Generally you do this by having a controller base class that does this work for you, then inherit from that for all other controllers that will be affected the same way.
Another approach is to move the company identifying component from the URL to the host name. This is common amongst software-as-a-service providers because it makes sharding your application much easier. Instead of myapp.com/companyx/users you'd have companyx.myapp.com/users. This has the advantage of preserving the existing URL structure, and when you have large amounts of data, you can partition your app by customer into different databases without a lot of headache.
The answer you found with tagging all the URLs using a GET token or a POST field is not going to work very well. For one, it's messy, and secondly, a site with every link being a POST is very annoying to work with as it makes navigating with the back-button or forcing a reload troublesome. The reason it has seen use is because out of the box PHP and ASP do not have support routes, so people have had to make do.
You can create a temporary database table, or use a key-value database and store all data you need in it. The uniq key can be used as a window id. Furthermore, you have to add this window id to each link. So you can receive the corresponding data for each browser tab out of the database and store it in the session, object,...
If you have an object, lets say #data, you can store it in the database using Marshal.dump and get it back with Marshal.load.

Resources