diff --git a/package.json b/package.json index ba097038..04d7b820 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ }, "dependencies": { "@tommoor/slate-drop-or-paste-images": "0.5.1", + "aws-sdk": "^2.135.0", "babel-core": "^6.24.1", "babel-eslint": "^7.2.3", "babel-loader": "6.2.5", diff --git a/server/api/auth.js b/server/api/auth.js index 42752b47..51304e5d 100644 --- a/server/api/auth.js +++ b/server/api/auth.js @@ -47,6 +47,10 @@ router.post('auth.slack', async ctx => { await team.createFirstCollection(user.id); } + // Update user's avatar + await user.updateAvatar(); + await user.save(); + ctx.body = { data: { user: await presentUser(ctx, user), diff --git a/server/migrations/20171019071915-user-avatar-url.js b/server/migrations/20171019071915-user-avatar-url.js new file mode 100644 index 00000000..5be75d52 --- /dev/null +++ b/server/migrations/20171019071915-user-avatar-url.js @@ -0,0 +1,12 @@ +module.exports = { + up: function(queryInterface, Sequelize) { + queryInterface.addColumn('users', 'avatarUrl', { + type: Sequelize.TEXT, + allowNull: true, + }); + }, + + down: function(queryInterface, Sequelize) { + queryInterface.removeColumn('users', 'avatarUrl'); + }, +}; diff --git a/server/models/User.js b/server/models/User.js index 69158780..a2f29488 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -2,6 +2,7 @@ import crypto from 'crypto'; import bcrypt from 'bcrypt'; import { DataTypes, sequelize, encryptedFields } from '../sequelize'; +import { uploadToS3FromUrl } from '../utils/s3'; import JWT from 'jsonwebtoken'; @@ -18,6 +19,7 @@ const User = sequelize.define( email: { type: DataTypes.STRING }, username: { type: DataTypes.STRING }, name: DataTypes.STRING, + avatarUrl: { type: DataTypes.STRING, allowNull: true }, password: DataTypes.VIRTUAL, passwordDigest: DataTypes.STRING, isAdmin: DataTypes.BOOLEAN, @@ -66,6 +68,12 @@ User.prototype.verifyPassword = function(password) { }); }); }; +User.prototype.updateAvatar = async function() { + this.avatarUrl = await uploadToS3FromUrl( + this.slackData.image_192, + `avatars/${this.id}` + ); +}; const setRandomJwtSecret = model => { model.jwtSecret = crypto.randomBytes(64).toString('hex'); diff --git a/server/presenters/user.js b/server/presenters/user.js index f545cd66..1d064ace 100644 --- a/server/presenters/user.js +++ b/server/presenters/user.js @@ -8,7 +8,8 @@ function present(ctx: Object, user: User) { id: user.id, username: user.username, name: user.name, - avatarUrl: user.slackData ? user.slackData.image_192 : null, + avatarUrl: user.avatarUrl || + (user.slackData ? user.slackData.image_192 : null), }; } diff --git a/server/slack.js b/server/slack.js index 62bb75eb..ab45d669 100644 --- a/server/slack.js +++ b/server/slack.js @@ -15,7 +15,6 @@ export async function request(endpoint: string, body: Object) { } catch (e) { throw httpErrors.BadRequest(); } - console.log('DATA', data); if (!data.ok) throw httpErrors.BadRequest(data.error); return data; diff --git a/server/utils/s3.js b/server/utils/s3.js index 65351a98..1837f7d2 100644 --- a/server/utils/s3.js +++ b/server/utils/s3.js @@ -1,5 +1,13 @@ +// @flow import crypto from 'crypto'; import moment from 'moment'; +import AWS from 'aws-sdk'; +import fetch from 'isomorphic-fetch'; + +AWS.config.update({ + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, +}); const makePolicy = () => { const policy = { @@ -19,7 +27,7 @@ const makePolicy = () => { return new Buffer(JSON.stringify(policy)).toString('base64'); }; -const signPolicy = policy => { +const signPolicy = (policy: any) => { const signature = crypto .createHmac('sha1', process.env.AWS_SECRET_ACCESS_KEY) .update(policy) @@ -28,4 +36,25 @@ const signPolicy = policy => { return signature; }; -export { makePolicy, signPolicy }; +const uploadToS3FromUrl = async (url: string, key: string) => { + const s3 = new AWS.S3(); + + try { + const res = await fetch(url); + const buffer = await res.buffer(); + await s3 + .putObject({ + Bucket: process.env.AWS_S3_UPLOAD_BUCKET_NAME, + Key: key, + ContentType: res.headers['content-type'], + ContentLength: res.headers['content-length'], + Body: buffer, + }) + .promise(); + return `https://s3.amazonaws.com/${process.env.AWS_S3_UPLOAD_BUCKET_NAME}/${key}`; + } catch (_e) { + return undefined; + } +}; + +export { makePolicy, signPolicy, uploadToS3FromUrl }; diff --git a/yarn.lock b/yarn.lock index 10b24544..d70cdf87 100644 --- a/yarn.lock +++ b/yarn.lock @@ -339,6 +339,21 @@ autoprefixer@^6.3.1: postcss "^5.2.16" postcss-value-parser "^3.2.3" +aws-sdk@^2.135.0: + version "2.135.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.135.0.tgz#81f4a47b99212f2f236bf5b11b0b3a3a02086db4" + dependencies: + buffer "4.9.1" + crypto-browserify "1.0.9" + events "^1.1.1" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.1.0" + xml2js "0.4.17" + xmlbuilder "4.2.1" + aws-sign2@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" @@ -1268,7 +1283,7 @@ buffer-xor@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" -buffer@^4.3.0, buffer@^4.9.0: +buffer@4.9.1, buffer@^4.3.0, buffer@^4.9.0: version "4.9.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" dependencies: @@ -1959,6 +1974,10 @@ cryptiles@2.x.x: dependencies: boom "2.x.x" +crypto-browserify@1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-1.0.9.tgz#cc5449685dfb85eb11c9828acc7cb87ab5bbfcc0" + crypto-browserify@^3.11.0: version "3.11.1" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.1.tgz#948945efc6757a400d6e5e5af47194d10064279f" @@ -2923,7 +2942,7 @@ event-stream@~3.3.0: stream-combiner "~0.0.4" through "~2.3.1" -events@^1.0.0: +events@^1.0.0, events@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" @@ -4816,6 +4835,10 @@ jest-validate@^20.0.3: leven "^2.1.0" pretty-format "^20.0.3" +jmespath@0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" + joi@^6.10.1, joi@~6.10.1: version "6.10.1" resolved "https://registry.yarnpkg.com/joi/-/joi-6.10.1.tgz#4d50c318079122000fe5f16af1ff8e1917b77e06" @@ -7806,7 +7829,11 @@ sane@~1.6.0: walker "~1.0.5" watch "~0.10.0" -sax@^1.2.1, sax@~1.2.1: +sax@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + +sax@>=0.6.0, sax@^1.2.1, sax@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -8849,16 +8876,16 @@ url-to-options@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" +url@0.10.3, url@~0.10.1: + version "0.10.3" + resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" dependencies: punycode "1.3.2" querystring "0.2.0" -url@~0.10.1: - version "0.10.3" - resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" +url@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" dependencies: punycode "1.3.2" querystring "0.2.0" @@ -8899,14 +8926,14 @@ uuid@2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.2.tgz#48bd5698f0677e3c7901a1c46ef15b1643794726" +uuid@3.1.0, uuid@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" + uuid@^2.0.1, uuid@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" -uuid@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" - v8flags@^2.0.2: version "2.1.1" resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" @@ -9202,6 +9229,19 @@ xml-name-validator@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" +xml2js@0.4.17: + version "0.4.17" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.17.tgz#17be93eaae3f3b779359c795b419705a8817e868" + dependencies: + sax ">=0.6.0" + xmlbuilder "^4.1.0" + +xmlbuilder@4.2.1, xmlbuilder@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-4.2.1.tgz#aa58a3041a066f90eaa16c2f5389ff19f3f461a5" + dependencies: + lodash "^4.0.0" + "xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"