From f2c7f02480a00a27a3ce84f9a155c9377dfd5f2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Wed, 17 Feb 2016 11:38:01 +0100 Subject: [PATCH] Add component for signing S3 url --- .../ascribe_buttons/s3_download_button.js | 91 +++++++++++++++++++ .../ascribe_detail/media_container.js | 19 ++-- js/constants/api_urls.js | 3 +- js/constants/application_constants.js | 2 +- js/fetchers/s3_fetcher.js | 7 ++ js/utils/requests.js | 2 +- 6 files changed, 109 insertions(+), 15 deletions(-) create mode 100644 js/components/ascribe_buttons/s3_download_button.js diff --git a/js/components/ascribe_buttons/s3_download_button.js b/js/components/ascribe_buttons/s3_download_button.js new file mode 100644 index 00000000..453bc6a8 --- /dev/null +++ b/js/components/ascribe_buttons/s3_download_button.js @@ -0,0 +1,91 @@ +'use strict'; + +import React from 'react'; + +import S3Fetcher from '../../fetchers/s3_fetcher'; + +import Glyphicon from 'react-bootstrap/lib/Glyphicon'; + +import AppConstants from '../../constants/application_constants'; + +import { getLangText } from '../../utils/lang_utils'; +import { queryParamsToArgs } from '../../utils/url_utils'; + + +const { string } = React.PropTypes; + +const S3DownloadButton = React.createClass({ + propTypes: { + url: string, + title: string, + artistName: string, + fileExtension: string + }, + + getInitialState() { + return { + downloadUrl: null + }; + }, + + componentDidMount() { + /** + * Initially, we request a signed url from + * the backend + */ + this.signUrl(); + }, + + transformS3UrlToS3Key(url) { + return url.replace(`https://${AppConstants.cloudfrontDomain}/`, ''); + }, + + signUrl(next) { + const { url, title, artistName } = this.props; + + S3Fetcher + .signUrl(this.transformS3UrlToS3Key(url), title, artistName) + .then(({ signed_url: downloadUrl }) => this.setState({ downloadUrl }, next)) + .catch(console.logGlobal); + }, + + reSignUrl(event) { + /** + * The signed url, however can expire, which is why + * we need to renew it when it expires. + */ + const { downloadUrl } = this.state; + const { expires } = queryParamsToArgs(downloadUrl.split('?')[1]); + const nowInSeconds = new Date().getTime() / 1000; + + if(nowInSeconds > expires) { + event.preventDefault(); + this.signUrl(() => this.refs.downloadButton.getDOMNode().click()); + } + }, + + render() { + const { fileExtension, url } = this.props; + const { downloadUrl } = this.state; + + return ( + + + {/* + If it turns out that `fileExtension` is an empty string, we're just + using the label 'file'. + */} + {getLangText('Download')} .{fileExtension || 'file'} + + + ); + } +}); + +export default S3DownloadButton; \ No newline at end of file diff --git a/js/components/ascribe_detail/media_container.js b/js/components/ascribe_detail/media_container.js index e22069e1..5511fdb4 100644 --- a/js/components/ascribe_detail/media_container.js +++ b/js/components/ascribe_detail/media_container.js @@ -3,13 +3,14 @@ import React from 'react'; import Button from 'react-bootstrap/lib/Button'; -import Glyphicon from 'react-bootstrap/lib/Glyphicon'; import MediaPlayer from './../ascribe_media/media_player'; import FacebookShareButton from '../ascribe_social_share/facebook_share_button'; import TwitterShareButton from '../ascribe_social_share/twitter_share_button'; +import S3DownloadButton from '../ascribe_buttons/s3_download_button'; + import CollapsibleButton from './../ascribe_collapsible/collapsible_button'; import AclProxy from '../acl_proxy'; @@ -131,17 +132,11 @@ let MediaContainer = React.createClass({ show={['video', 'audio', 'image'].indexOf(mimetype) === -1 || content.acl.acl_download} aclObject={content.acl} aclName="acl_download"> - - {/* - If it turns out that `fileExtension` is an empty string, we're just - using the label 'file'. - */} - {getLangText('Download')} .{fileExtension || 'file'} - + {embed}

diff --git a/js/constants/api_urls.js b/js/constants/api_urls.js index f7bac317..29e37724 100644 --- a/js/constants/api_urls.js +++ b/js/constants/api_urls.js @@ -76,7 +76,8 @@ let ApiUrls = { 'webhooks': AppConstants.apiEndpoint + 'webhooks/', 'webhooks_events': AppConstants.apiEndpoint + 'webhooks/events/', 'whitelabel_settings': AppConstants.apiEndpoint + 'whitelabel/settings/${subdomain}/', - 'delete_s3_file': AppConstants.serverUrl + 's3/delete/' + 'delete_s3_file': AppConstants.serverUrl + 's3/delete/', + 'sign_url_s3': AppConstants.serverUrl + 's3/sign_url/' }; diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js index 332eee49..2fee15c3 100644 --- a/js/constants/application_constants.js +++ b/js/constants/application_constants.js @@ -119,7 +119,7 @@ const constants = { 'twitter': { 'sdkUrl': 'https://platform.twitter.com/widgets.js' }, - + 'cloudfrontDomain': 'd1qjsxua1o9x03.cloudfront.net', 'errorMessagesToIgnore': [ 'Authentication credentials were not provided.', 'Informations d\'authentification non fournies.' diff --git a/js/fetchers/s3_fetcher.js b/js/fetchers/s3_fetcher.js index e6f50b72..1a02727e 100644 --- a/js/fetchers/s3_fetcher.js +++ b/js/fetchers/s3_fetcher.js @@ -11,6 +11,13 @@ let S3Fetcher = { key, bucket }); + }, + signUrl(key, title, artistName) { + return requests.get('sign_url_s3', { + 'artist_name': artistName, + key, + title + }); } }; diff --git a/js/utils/requests.js b/js/utils/requests.js index fec31c00..5552731a 100644 --- a/js/utils/requests.js +++ b/js/utils/requests.js @@ -70,7 +70,7 @@ class Requests { } }).catch(reject); }); - } + }; } getUrl(url) {