Can I use Firebase Storage for images accessible only via my React web app? - reactjs

I'm creating a dating React web app where users can upload pictures of themselves to their user profile. I want to use Firebase storage. I want to protect the images so that they are only viewable when accessing from my web app by authenticated users - right now I get an image like this:
let storageRef = firebase.storage().ref(`images/${userid}/${userImageName}`);
// Get the download URL
storageRef.getDownloadURL().then(function(url) {
// Insert url into an <img> tag to "download"
})
This is great - but once I put the URL in the src attribute in the image tag anyone who views the source code can copy the URL and send it via email, text message, etc., making it "public". I have tried uploading images in base64 string using the putString() function also only for Firebase storage to yet again create a URL for it like a normal image upload when using the put() function.
So my question is - can I use Firebase Storage to store images and make them "private" so that only authenticated users of my web app are able to view them? Is there a way to get only the image data and use that to generate the images in the frontend/client so that no actual URLs are ever placed in the JS code?

The call to getDownloadURL() is protected by Security Rules, which means that if you write a rule like:
service firebase.storage {
match /b/{bucket}/o {
match /images/{userId}/{userImageName} {
// any authenticated user can read the bytes or get a download URL
allow read: if request.auth != null;
// only the given user can upload their photo
allow write: if request.auth.uid == userId;
}
}
}
They will not allow unauthenticated people to download URLs.
As for the second issue: once someone can see a file, assume that they have already downloaded/screenshotted it and can share it, even if the URL isn't publicly accessible (or even on the page). Viewing something is equivalent to downloading it, so there's really no difference where it's coming from as the end result can be the same.

Related

Single page applications and rendering private pages

I have two different account types in my application [user, admin], the two log in from the same page on the website: http://example.com/login, the log-in page sends the entered email and password to the server, the server then replies back with 200 OK HTTP code.
Now, the front-end of the Admin contains pages that a regular user should not see (think of the CMS pages), these pages must only be rendered to the admin, and shouldn't be bundled in the code and served to the regular users, even if their browsers will not render them.
because the front end of the Admin holds API endpoints that shouldn't go public (even though hitting them requires authentication).
Is it possible to make the server decide which pages (components) it should serve back to the client based on the user role in a single-page React application?
Is that available in Next JS or something like that?
I found that my question was similar to this one Is it insecure to include your login page in your single page application?, but I thought that this question deserves its own thread, because it's critically important and doesn't seem to be answered well on the internet anyway
For a Nextjs auth implementation with cookies and _middleware would look something like this:
export function middleware(req) {
const activeSession = req.headers.get('cookie');
const url = req.nextUrl.clone();
if (activeSession) {
if (req.nextUrl.pathname === '/login') {
url.pathname = '/';
return NextResponse.redirect(url);
}
return NextResponse.next();
}
url.pathname = '/login';
return NextResponse.rewrite(url);
}
You can set any cookie using any of the following methods:
https://github.com/vercel/next.js/tree/canary/examples/with-cookie-auth

retrive file from one drive from a next.js application

I am using react-onedrive-filepicker to retrieve file content from a selected file located on OneDrive.
My code is like:
<ReactOneDriveFilePicker
clientID={process.env.NEXT_PUBLIC_MICROSOFT_CLIENT_ID}
action="query"
multiSelect={false}
advanced={{
redirectUri: "https://graph.microsoft.com/v1.0/me/"
}}
onSuccess={(result) => {
alert(JSON.stringify(result));
}}
onCancel={(result) => {
alert(JSON.stringify(result));
}}
>
<Button className="btn btn-md btn-primary">MSOneDrive</Button>
</ReactOneDriveFilePicker>
I am actually getting this error
I am at a loss here, because I have no idea what URI is supposed to expect nor how to pass it... I have registered https://login.live.com/oauth20_desktop.srf on my Azure app, but it appears not to be the right address to show a user's OneDrive folder...
Where am I going wrong here?
I have also opened a issue on the react-onedrive-filepicker GitHub, but I am afraid the repo might be unmaintained..
EDIT
I am now passing the redirectUri value for MS Graph to the component, but it claims that redirect uri is not in the same domain as picker sdk... Now, it looks like a never-ending loop:
I cannot put the page URL where the picker is hosted, because it is a dynamic address...
but even if I put there the local URI, what's the point? I won't see the OneDrive files of my user...
What is impressive is how easy it is to make the Google picker work, and how incredibly difficult it is to make the MS work...
TL;DR
Your apps URL should be the path at which you can access your app in the browser. This URL has to be registered as redirect URI both in the Azure client configuration as well as in your ReactOneDriveFilePicker component.
To access Microsoft APIs, there are different steps to follow. First, you have to register your application with Microsoft. Then the user authorizes your application to access their data and only then you are allowed to access the files. Your struggles may actually come from the authorization step. Microsoft uses the OAuth protocol for managing access to their API resources (Docs: Auth and available auth flows), during which the user gets redirected to the Microsoft site, authorizes your app and gets redirected back to your application:
(diagram by ArchitectXChange.com)
Only then, you can access the API resources, in your case the OneDrive files.
The redirect URI now refers to the exact address your file picker is located at. Lets say you are running your application on localhost with the path /onedrive-filepicker, then your redirect URI will be http://localhost/onedrive-filepicker, since this is the place your button is located at. Now you have to put this URL into the app registration with Azure, so that Azure makes sure no one is kidding around on behalf of your application. And of course you have to confirm the redirect URI when calling the API using the redirectUri property.
You can find more on this in the OneDrive File Picker SDK docs and on their set up section.
EDIT:
If you have a dynamic url for your picker, you will need some fixed redirect route. This can be http://localhost/redirect. Before starting the Auth flow, you have to store the current state/route, e.g. inside local storage. After the redirect, you finish the authorization and redirect the user once again to the previously stored state/route.
The point on redirecting the user to your own application and not to their OneDrive is, that your application wants to access the files. If the user gets redirected to OneDrive, maybe they will see their files, but OneDrive will not know how to send the information back to your app. If you redirect to your app, it stays in control of the UI, can prompt the user to select a file and receive the selected file using the OneDrive SDK.
Maybe it will help you to follow along the Microsoft docs to use the SDK right away instead of building up on an undocumented third-party component.
Go to you client app you (I assume) opened in Azure, you need to register the base URI of your choice - you can see a full guide here:
https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app
Under "Add a redirect URI"

How to pass active storage images to email

I am using active storage in my Rails application. The frontend is on React. I am uploading an image via react uploader and sending that image to the backend API where I am storing it to S3 bucket using active storage.
This is the API that handles the image:
if image.attached?
opts = ImageUploader.resize_to_fill(blob: image.blob, width: 100)
variation = ActiveStorage::Variation.new(combine_options: opts)
variant = ActiveStorage::Variant.new(image.blob, variation)
return rails_representation_url(variant, only_path: true)
end
This returns the path in the following format:
/rails/active_storage/representations/...../file_name.png
This works all fine when I use this path inside the application, but I want to send this image in email, there the image breaks as the src attribute can not read this path. If I append http://localhost:3000 manually in the email through dev tools, I am able to see the image then.
Weirldy, in the email, I also see the path appended with http by itself like below:
http:/rails/active_storage/representations/...../file_name.png
I did a workaround and gave only_path to false. This way I was able to get back the complete URL but in the email I got duplicate http written somehow.
Desired Result:
Either the email also appends the host_url along the protocol or removes the http also so that I am able to pass the complete valid URL.
If there is a way to alter the api and somehow get the s3 endpoint directly that would really really work but I am not sure how to do so. It would return the url like this: //{bucketName}/uploads/file/id/image.jpeg
Any help please!

Sending files to Kloudless saver from clientside

I'm currently using a dropbox client js script to push zip files to a folder (in test, a couple of k, in production, a couple of hundred meg) - there currently isn't a server/back end, so am posting from an arraybuffer, not a server url.
var zip = new JSZip();
zip.file("test.txt", "Hello World\n");
var content = zip.generate({type:"arraybuffer"});
// ... code to pick a dropbox folder ...//
client.writeFile(url+"/"+fileName, content, function(error){ ... etc
This all works fine - client is able to write the binary file (which Dropbox's own Saver is unfortunately unable to do). I'm trying to see if Kloudless is able to perform the same, since I also need to support google, box, etc at some point. https://github.com/kloudless/file-explorer/'s documentation about its saver says the files are an array of urls ..
explorer({
...
files: [{
"url": "http://<your image url>",
"name": "filename.extension"
},
It doesn't seem to like local storage file references using URL.createObjectURL(blob), so I'm guessing the api is telling the remote services to pull the files rather than pushing their data.
You are correct that the Kloudless API backend servers stream the file from the URL to the final destination in whichever cloud service you would like the file to be uploaded to (e.g. a folder in a Dropbox account).
If the files are present only on the client-side, I would recommend using the Kloudless Chooser to instead prompt the user to choose a folder to save the files in, and then handle uploading the file data to that destination via the client-side manually.
To do this, refer to this example configuration: https://jsfiddle.net/PB565/139/embedded/
I have set retrieve_tokens to true so that my client-side JavaScript will receive not just the metadata of the folder the user chooses to upload the data to but also the Bearer token to use to gain access to the user's account. This allows the client-side JavaScript to then make upload or multipart upload requests to Kloudless to upload the file data to that folder. The advantage of multipart uploads is that an error uploading one chunk wouldn't require the whole upload to be retried.
Be sure to add the domain you are hosting the File Explorer on to your Kloudless App's Trusted Domains (on the App Details page) so that it can in fact receive the Bearer token in the response JS callback. In my JSFiddle example, I would have to add 'fiddle.jshell.net' to my app's list of Trusted Domains to be able to receive the Bearer token to perform further requests from the client side to the Kloudless API.

How to get a publicly accessible url for a timeline item attachment?

I wondered how could I get a publicly accessible url for a timeline item attachment (like an image) that doesn't require authentication to access it?
This is unfortunately not possible as image attachments are private to the user: they indeed require an access token to be retrieved.
The only way you could make the image publicly available is by providing some sort of proxy that would fetch the image using a stored token and returning it to the requester.

Resources