1

I'm trying to implement FineUploader React library into my React app to upload files to my Azure Blob Storage.

Looks like for some reason, FineUploader is getting the blob storage URI wrong.

This is how I instanciate a FineUploader in my test component:

import React, { Component } from 'react';

import FineUploaderAzure from 'fine-uploader-wrappers/azure'
import Gallery from './gallery/index';

const uploader = new FineUploaderAzure({
    options: {
        cors: {
            expected: true,
            sendCredentials: true
        },
        signature: {
            endpoint: 'https://myapp.com/api/getsas'
        },
        request: {
            endpoint: 'https://myaccount.blob.core.windows.net/test-container'
        },
        uploadSuccess: {
            endpoint: 'https://myapp.com/api/done'
        }
    }
})

class Index extends Component {
    render() {
        return (
            <Gallery uploader={uploader} />
        )
    }
}

export default Index;

Here's the error I'm seeing in the console. Looks like FineUploader is using the wrong URI for the blob storage. enter image description here

Any idea what may be causing this?

UPDATE: As per @GauravMantri's suggestion, I changed endpoint to containerUrl in the options section but that didn't seem to help either. Here's what it looks like:

const uploader = new FineUploaderAzure({
    options: {
        cors: {
            expected: true,
            sendCredentials: true
        },
        signature: {
            endpoint: 'https://myapp.com/api/getsas'
        },
        request: {
            containerUrl: 'https://myaccount.blob.core.windows.net/test-container'
        },
        uploadSuccess: {
            endpoint: 'https://myapp.com/api/done'
        }
    }
})

Here's the SAS I'm getting when I send a request through Postman: The request I'm sending is:

http://example.com/api/files/get/sas?blobUri=https://myaccount.blob.core.windows.net/test-container/test.txt&_method=put

And here's the SAS I receive:

"?sv=2017-04-17&sr=b&sig=7pXTnI2r8uGyZms12T9cRvHg1XlLI53ZJtwPUwGElnY%3D&st=2017-12-28T14%3A02%3A56Z&se=2017-12-28T14%3A22%3A56Z&sp=w"
Sam
  • 26,817
  • 58
  • 206
  • 383
  • A few things: 1) When looking at request options here: https://docs.fineuploader.com/quickstart/02-setting_options-azure.html, the parameter name under `request` is `endpoint` however when I look here: https://docs.fineuploader.com/api/options-azure.html#request-option, the parameter name is `containerUrl`. Not sure if this is causing this problem. 2) Can you share the SAS returned by your service (obviously obfuscating account name and other details)? – Gaurav Mantri Dec 24 '17 at 06:35
  • @GauravMantri Sorry for the late response. I've tried changing the parameter to `containerUrl` but that didn't seem to help. I've updated the original post to show you what my `options` section looks like. I've also posted the `SAS` I receive from my backend. Thank you for your help. – Sam Dec 28 '17 at 14:17
  • No worries Sam :). A few things I noticed: 1) For some reason the SAS token passed contains double quotes (`"`) which gets escaped into `%22`. 2) Even though you're generating SAS for a file named `test.txt`, that name is not included in your upload request (from the Chrome error screenshot). – Gaurav Mantri Dec 28 '17 at 14:24
  • So my API is returning a `string`. Should it not be wrapped with double quotes? – Sam Dec 28 '17 at 14:31
  • That's correct. But what I am not able to understand is why the double quotes are also included in your request URL. Are you manually putting the double quotes in the SAS token in your API layer? – Gaurav Mantri Dec 28 '17 at 14:35
  • Oh I see what you're saying. I am not manually doing anything. Now, I'm updating the server code and will give you the URL in a few minutes so that you can test it for yourself. You can upload a test file and see the error. Also, as per your suggestion, I'm now using `containerUrl`. – Sam Dec 28 '17 at 14:38
  • You're right about the double quotes. Don't know why they're there. Please go ahead and upload a test file and see it for yourself. Here's the URL: https://ingrid.cool/home/test – Sam Dec 28 '17 at 14:44
  • For some reason I am not able to use this link. I am not even getting the SAS token back. However I looked at the code and tried to call the API endpoint directly and using that I was able to get the SAS token and it is enclosed in double quotes (which should not be the case). – Gaurav Mantri Dec 28 '17 at 15:04
  • I get the SAS token in quotes when I use Postman too. Maybe I should put the SAS into a `JSON` object. What about your concern about not having the file name in the SAS? Any idea what may be causing that? – Sam Dec 28 '17 at 15:07
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/162102/discussion-between-gaurav-mantri-and-sam). – Gaurav Mantri Dec 28 '17 at 15:08
  • @GauravMantri You have an answer to a post here on SO from 2014. It describes the same problem that I'm having. Here's the post: https://stackoverflow.com/questions/25038429/azure-shared-access-signature-signature-did-not-match I haven't yet been able to figure out how to add `restype=container` to my SAS. Do you think this is the solution to the issue I'm having? Other than that, everything looks correct. – Sam Dec 28 '17 at 22:20
  • I was able to make it work :). You were on the right track. I don’t think the link you mentioned will solve your problem. Let me post my code as an answer in a little bit. – Gaurav Mantri Dec 29 '17 at 02:34

1 Answers1

1

I was able to make it work. Basically there're a few things that one need to keep in mind:

  1. In your FineUploader config, you will need endpoint attribute and that should have the URL of the blob container where you want to upload. This is how configuration looks like in my code:

    var uploader = new qq.azure.FineUploader({
        debug: true,
        element: document.getElementById("uploader"),
        cors: {
            expected: true,
            sendCredentials: false
        },
        signature: {
            endpoint: 'http://localhost:63194/users/sas'
        },
        request: {
            endpoint: 'https://account-name.blob.core.windows.net/container-name'
        },
    
    })
    
  2. The API for getting Shared Access Signature (SAS) should return blob URL + SAS Token. The blobUrl parameter to the API should be the absolute URL of the blob. This is the code I used for API (please don't use this as is because the code below does not take into consideration the _method parameter):

    [Route("sas")]
    [HttpGet]
    public async Task<HttpResponseMessage> Sas(string blobUri)
    {
        var credentials = new StorageCredentials("account-name", "account-key");
        var blob = new CloudBlockBlob(new Uri(blobUri), credentials);
        var sasParameters = new SharedAccessBlobPolicy()
        {
            SharedAccessExpiryTime = DateTime.UtcNow.AddHours(1),
            Permissions = SharedAccessBlobPermissions.Write
        };
        var sasToken = blob.GetSharedAccessSignature(sasParameters);
        var returnValue = blob.Uri.AbsoluteUri + sasToken;
        var resp = new HttpResponseMessage(HttpStatusCode.OK);
        resp.Content = new StringContent(returnValue, System.Text.Encoding.UTF8, "text/plain");
        return resp;
    }
    

I downloaded Fine Uploader Azure related files from here: https://fineuploader.com/customize.html and used it to create a simple HTML page to test it. Here's what my HTML page looks like:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="fine-uploader-gallery.min.css" rel="stylesheet">
    <script src="azure.fine-uploader.min.js""></script>
    <script type="text/template" id="qq-template">
        <div class="qq-uploader-selector qq-uploader qq-gallery" qq-drop-area-text="Drop files here">
            <div class="qq-total-progress-bar-container-selector qq-total-progress-bar-container">
                <div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar"></div>
            </div>
            <div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>
                <span class="qq-upload-drop-area-text-selector"></span>
            </div>
            <div class="qq-upload-button-selector qq-upload-button">
                <div>Upload a file</div>
            </div>
            <span class="qq-drop-processing-selector qq-drop-processing">
                <span>Processing dropped files...</span>
                <span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
            </span>
            <ul class="qq-upload-list-selector qq-upload-list" role="region" aria-live="polite" aria-relevant="additions removals">
                <li>
                    <span role="status" class="qq-upload-status-text-selector qq-upload-status-text"></span>
                    <div class="qq-progress-bar-container-selector qq-progress-bar-container">
                        <div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-progress-bar-selector qq-progress-bar"></div>
                    </div>
                    <span class="qq-upload-spinner-selector qq-upload-spinner"></span>
                    <div class="qq-thumbnail-wrapper">
                        <img class="qq-thumbnail-selector" qq-max-size="120" qq-server-scale>
                    </div>
                    <button type="button" class="qq-upload-cancel-selector qq-upload-cancel">X</button>
                    <button type="button" class="qq-upload-retry-selector qq-upload-retry">
                        <span class="qq-btn qq-retry-icon" aria-label="Retry"></span>
                        Retry
                    </button>

                    <div class="qq-file-info">
                        <div class="qq-file-name">
                            <span class="qq-upload-file-selector qq-upload-file"></span>
                            <span class="qq-edit-filename-icon-selector qq-btn qq-edit-filename-icon" aria-label="Edit filename"></span>
                        </div>
                        <input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">
                        <span class="qq-upload-size-selector qq-upload-size"></span>
                        <button type="button" class="qq-btn qq-upload-delete-selector qq-upload-delete">
                            <span class="qq-btn qq-delete-icon" aria-label="Delete"></span>
                        </button>
                        <button type="button" class="qq-btn qq-upload-pause-selector qq-upload-pause">
                            <span class="qq-btn qq-pause-icon" aria-label="Pause"></span>
                        </button>
                        <button type="button" class="qq-btn qq-upload-continue-selector qq-upload-continue">
                            <span class="qq-btn qq-continue-icon" aria-label="Continue"></span>
                        </button>
                    </div>
                </li>
            </ul>

            <dialog class="qq-alert-dialog-selector">
                <div class="qq-dialog-message-selector"></div>
                <div class="qq-dialog-buttons">
                    <button type="button" class="qq-cancel-button-selector">Close</button>
                </div>
            </dialog>

            <dialog class="qq-confirm-dialog-selector">
                <div class="qq-dialog-message-selector"></div>
                <div class="qq-dialog-buttons">
                    <button type="button" class="qq-cancel-button-selector">No</button>
                    <button type="button" class="qq-ok-button-selector">Yes</button>
                </div>
            </dialog>

            <dialog class="qq-prompt-dialog-selector">
                <div class="qq-dialog-message-selector"></div>
                <input type="text">
                <div class="qq-dialog-buttons">
                    <button type="button" class="qq-cancel-button-selector">Cancel</button>
                    <button type="button" class="qq-ok-button-selector">Ok</button>
                </div>
            </dialog>
        </div>
    </script>

    <title>Fine Uploader Gallery UI</title>
</head>
<body>
    <div id="uploader"></div>
    <script>
        // Some options to pass to the uploader are discussed on the next page
        var uploader = new qq.azure.FineUploader({
            debug: true,
            element: document.getElementById("uploader"),
            cors: {
                expected: true,
                sendCredentials: false
            },
            signature: {
                endpoint: 'http://localhost:63194/users/sas'
            },
            request: {
                endpoint: 'https://account-name.blob.core.windows.net/container-name'
            },

        })
    </script>
</body>
</html> 

Once I ran this code, I was able to upload files in my blob container without any problems.

Gaurav Mantri
  • 128,066
  • 12
  • 206
  • 241