Save avatars to le cloud in beforeSave hooks
Added encryption to uploads Updated icon for team settings
This commit is contained in:
@ -7,6 +7,7 @@ import {
|
|||||||
CodeIcon,
|
CodeIcon,
|
||||||
UserIcon,
|
UserIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
|
TeamIcon,
|
||||||
} from 'outline-icons';
|
} from 'outline-icons';
|
||||||
|
|
||||||
import Flex from 'shared/components/Flex';
|
import Flex from 'shared/components/Flex';
|
||||||
@ -55,7 +56,7 @@ class SettingsSidebar extends React.Component<Props> {
|
|||||||
<Section>
|
<Section>
|
||||||
<Header>Team</Header>
|
<Header>Team</Header>
|
||||||
{user.isAdmin && (
|
{user.isAdmin && (
|
||||||
<SidebarLink to="/settings/details" icon={<SettingsIcon />}>
|
<SidebarLink to="/settings/details" icon={<TeamIcon />}>
|
||||||
Details
|
Details
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
)}
|
)}
|
||||||
|
@ -45,7 +45,7 @@ class DropToImport extends React.Component<Props> {
|
|||||||
const asset = await uploadFile(imageBlob, { name: this.file.name });
|
const asset = await uploadFile(imageBlob, { name: this.file.name });
|
||||||
this.props.onSuccess(asset.url);
|
this.props.onSuccess(asset.url);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.props.onError(err);
|
this.props.onError(err.message);
|
||||||
} finally {
|
} finally {
|
||||||
this.isUploading = false;
|
this.isUploading = false;
|
||||||
this.isCropping = false;
|
this.isCropping = false;
|
||||||
|
@ -133,7 +133,7 @@
|
|||||||
"nodemailer": "^4.4.0",
|
"nodemailer": "^4.4.0",
|
||||||
"normalize.css": "^7.0.0",
|
"normalize.css": "^7.0.0",
|
||||||
"normalizr": "2.0.1",
|
"normalizr": "2.0.1",
|
||||||
"outline-icons": "^1.1.0",
|
"outline-icons": "^1.2.0",
|
||||||
"oy-vey": "^0.10.0",
|
"oy-vey": "^0.10.0",
|
||||||
"pg": "^6.1.5",
|
"pg": "^6.1.5",
|
||||||
"pg-hstore": "2.3.2",
|
"pg-hstore": "2.3.2",
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
import uuid from 'uuid';
|
||||||
import { DataTypes, sequelize, Op } from '../sequelize';
|
import { DataTypes, sequelize, Op } from '../sequelize';
|
||||||
|
import { publicS3Endpoint, uploadToS3FromUrl } from '../utils/s3';
|
||||||
import Collection from './Collection';
|
import Collection from './Collection';
|
||||||
import User from './User';
|
import User from './User';
|
||||||
|
|
||||||
@ -33,6 +35,18 @@ Team.associate = models => {
|
|||||||
Team.hasMany(models.User, { as: 'users' });
|
Team.hasMany(models.User, { as: 'users' });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const uploadAvatar = async model => {
|
||||||
|
const endpoint = publicS3Endpoint();
|
||||||
|
|
||||||
|
if (model.avatarUrl && !model.avatarUrl.startsWith(endpoint)) {
|
||||||
|
const newUrl = await uploadToS3FromUrl(
|
||||||
|
model.avatarUrl,
|
||||||
|
`avatars/${model.id}/${uuid.v4()}`
|
||||||
|
);
|
||||||
|
if (newUrl) model.avatarUrl = newUrl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Team.prototype.createFirstCollection = async function(userId) {
|
Team.prototype.createFirstCollection = async function(userId) {
|
||||||
return await Collection.create({
|
return await Collection.create({
|
||||||
name: 'General',
|
name: 'General',
|
||||||
@ -82,4 +96,6 @@ Team.prototype.activateUser = async function(user: User, admin: User) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Team.beforeSave(uploadAvatar);
|
||||||
|
|
||||||
export default Team;
|
export default Team;
|
||||||
|
@ -4,7 +4,7 @@ import bcrypt from 'bcrypt';
|
|||||||
import uuid from 'uuid';
|
import uuid from 'uuid';
|
||||||
import JWT from 'jsonwebtoken';
|
import JWT from 'jsonwebtoken';
|
||||||
import { DataTypes, sequelize, encryptedFields } from '../sequelize';
|
import { DataTypes, sequelize, encryptedFields } from '../sequelize';
|
||||||
import { uploadToS3FromUrl } from '../utils/s3';
|
import { publicS3Endpoint, uploadToS3FromUrl } from '../utils/s3';
|
||||||
import { sendEmail } from '../mailer';
|
import { sendEmail } from '../mailer';
|
||||||
|
|
||||||
const BCRYPT_COST = process.env.NODE_ENV !== 'production' ? 4 : 12;
|
const BCRYPT_COST = process.env.NODE_ENV !== 'production' ? 4 : 12;
|
||||||
@ -57,9 +57,7 @@ User.associate = models => {
|
|||||||
User.prototype.getJwtToken = function() {
|
User.prototype.getJwtToken = function() {
|
||||||
return JWT.sign({ id: this.id }, this.jwtSecret);
|
return JWT.sign({ id: this.id }, this.jwtSecret);
|
||||||
};
|
};
|
||||||
User.prototype.getTeam = async function() {
|
|
||||||
return this.team;
|
|
||||||
};
|
|
||||||
User.prototype.verifyPassword = function(password) {
|
User.prototype.verifyPassword = function(password) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!this.passwordDigest) {
|
if (!this.passwordDigest) {
|
||||||
@ -77,17 +75,23 @@ User.prototype.verifyPassword = function(password) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
User.prototype.updateAvatar = async function() {
|
|
||||||
this.avatarUrl = await uploadToS3FromUrl(
|
const uploadAvatar = async model => {
|
||||||
this.slackData.image_192,
|
const endpoint = publicS3Endpoint();
|
||||||
`avatars/${this.id}/${uuid.v4()}`
|
|
||||||
);
|
if (model.avatarUrl && !model.avatarUrl.startsWith(endpoint)) {
|
||||||
|
const newUrl = await uploadToS3FromUrl(
|
||||||
|
model.avatarUrl,
|
||||||
|
`avatars/${model.id}/${uuid.v4()}`
|
||||||
|
);
|
||||||
|
if (newUrl) model.avatarUrl = newUrl;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setRandomJwtSecret = model => {
|
const setRandomJwtSecret = model => {
|
||||||
model.jwtSecret = crypto.randomBytes(64).toString('hex');
|
model.jwtSecret = crypto.randomBytes(64).toString('hex');
|
||||||
};
|
};
|
||||||
const hashPassword = function hashPassword(model) {
|
const hashPassword = model => {
|
||||||
if (!model.password) {
|
if (!model.password) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -106,6 +110,7 @@ const hashPassword = function hashPassword(model) {
|
|||||||
};
|
};
|
||||||
User.beforeCreate(hashPassword);
|
User.beforeCreate(hashPassword);
|
||||||
User.beforeUpdate(hashPassword);
|
User.beforeUpdate(hashPassword);
|
||||||
|
User.beforeSave(uploadAvatar);
|
||||||
User.beforeCreate(setRandomJwtSecret);
|
User.beforeCreate(setRandomJwtSecret);
|
||||||
User.afterCreate(user => sendEmail('welcome', user.email));
|
User.afterCreate(user => sendEmail('welcome', user.email));
|
||||||
|
|
||||||
|
@ -68,6 +68,7 @@ export const uploadToS3FromUrl = async (url: string, key: string) => {
|
|||||||
Key: key,
|
Key: key,
|
||||||
ContentType: res.headers['content-type'],
|
ContentType: res.headers['content-type'],
|
||||||
ContentLength: res.headers['content-length'],
|
ContentLength: res.headers['content-length'],
|
||||||
|
ServerSideEncryption: 'AES256',
|
||||||
Body: buffer,
|
Body: buffer,
|
||||||
})
|
})
|
||||||
.promise();
|
.promise();
|
||||||
|
@ -7326,9 +7326,9 @@ outline-icons@^1.0.0:
|
|||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/outline-icons/-/outline-icons-1.0.3.tgz#f0928a8bbc7e7ff4ea6762eee8fb2995d477941e"
|
resolved "https://registry.yarnpkg.com/outline-icons/-/outline-icons-1.0.3.tgz#f0928a8bbc7e7ff4ea6762eee8fb2995d477941e"
|
||||||
|
|
||||||
outline-icons@^1.1.0:
|
outline-icons@^1.2.0:
|
||||||
version "1.1.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/outline-icons/-/outline-icons-1.1.0.tgz#08eb188a97a1aa8970a4dded7841c3d8b96b8577"
|
resolved "https://registry.yarnpkg.com/outline-icons/-/outline-icons-1.2.0.tgz#8a0e0e9e9b98336470228837c4933ba10297fcf5"
|
||||||
|
|
||||||
oy-vey@^0.10.0:
|
oy-vey@^0.10.0:
|
||||||
version "0.10.0"
|
version "0.10.0"
|
||||||
|
Reference in New Issue
Block a user