Merge pull request #830 from SISheogorath/feature/GDPR
GDPR compliant part 1
This commit is contained in:
13
lib/migrations/20180525153000-user-add-delete-token.js
Normal file
13
lib/migrations/20180525153000-user-add-delete-token.js
Normal file
@@ -0,0 +1,13 @@
|
||||
'use strict'
|
||||
module.exports = {
|
||||
up: function (queryInterface, Sequelize) {
|
||||
return queryInterface.addColumn('Users', 'deleteToken', {
|
||||
type: Sequelize.UUID,
|
||||
defaultValue: Sequelize.UUIDV4
|
||||
})
|
||||
},
|
||||
|
||||
down: function (queryInterface, Sequelize) {
|
||||
return queryInterface.removeColumn('Users', 'deleteToken')
|
||||
}
|
||||
}
|
||||
@@ -24,12 +24,16 @@ module.exports = function (sequelize, DataTypes) {
|
||||
Author.belongsTo(models.Note, {
|
||||
foreignKey: 'noteId',
|
||||
as: 'note',
|
||||
constraints: false
|
||||
constraints: false,
|
||||
onDelete: 'CASCADE',
|
||||
hooks: true
|
||||
})
|
||||
Author.belongsTo(models.User, {
|
||||
foreignKey: 'userId',
|
||||
as: 'user',
|
||||
constraints: false
|
||||
constraints: false,
|
||||
onDelete: 'CASCADE',
|
||||
hooks: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,13 +85,15 @@ module.exports = function (sequelize, DataTypes) {
|
||||
type: DataTypes.DATE
|
||||
}
|
||||
}, {
|
||||
paranoid: true,
|
||||
paranoid: false,
|
||||
classMethods: {
|
||||
associate: function (models) {
|
||||
Note.belongsTo(models.User, {
|
||||
foreignKey: 'ownerId',
|
||||
as: 'owner',
|
||||
constraints: false
|
||||
constraints: false,
|
||||
onDelete: 'CASCADE',
|
||||
hooks: true
|
||||
})
|
||||
Note.belongsTo(models.User, {
|
||||
foreignKey: 'lastchangeuserId',
|
||||
|
||||
@@ -102,7 +102,9 @@ module.exports = function (sequelize, DataTypes) {
|
||||
Revision.belongsTo(models.Note, {
|
||||
foreignKey: 'noteId',
|
||||
as: 'note',
|
||||
constraints: false
|
||||
constraints: false,
|
||||
onDelete: 'CASCADE',
|
||||
hooks: true
|
||||
})
|
||||
},
|
||||
getNoteRevisions: function (note, callback) {
|
||||
|
||||
@@ -31,6 +31,10 @@ module.exports = function (sequelize, DataTypes) {
|
||||
refreshToken: {
|
||||
type: DataTypes.TEXT
|
||||
},
|
||||
deleteToken: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: Sequelize.UUIDV4
|
||||
},
|
||||
email: {
|
||||
type: Sequelize.TEXT,
|
||||
validate: {
|
||||
@@ -66,6 +70,9 @@ module.exports = function (sequelize, DataTypes) {
|
||||
})
|
||||
},
|
||||
getProfile: function (user) {
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
return user.profile ? User.parseProfile(user.profile) : (user.email ? User.parseProfileByEmail(user.email) : null)
|
||||
},
|
||||
parseProfile: function (profile) {
|
||||
|
||||
@@ -486,11 +486,13 @@ function startConnection (socket) {
|
||||
for (var i = 0; i < note.authors.length; i++) {
|
||||
var author = note.authors[i]
|
||||
var profile = models.User.getProfile(author.user)
|
||||
authors[author.userId] = {
|
||||
userid: author.userId,
|
||||
color: author.color,
|
||||
photo: profile.photo,
|
||||
name: profile.name
|
||||
if (profile) {
|
||||
authors[author.userId] = {
|
||||
userid: author.userId,
|
||||
color: author.color,
|
||||
photo: profile.photo,
|
||||
name: profile.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// response
|
||||
// external modules
|
||||
var fs = require('fs')
|
||||
var path = require('path')
|
||||
var markdownpdf = require('markdown-pdf')
|
||||
var shortId = require('shortid')
|
||||
var querystring = require('querystring')
|
||||
@@ -61,7 +62,10 @@ function responseError (res, code, detail, msg) {
|
||||
}
|
||||
|
||||
function showIndex (req, res, next) {
|
||||
res.render(config.indexPath, {
|
||||
var authStatus = req.isAuthenticated()
|
||||
var deleteToken = ''
|
||||
|
||||
var data = {
|
||||
url: config.serverURL,
|
||||
useCDN: config.useCDN,
|
||||
allowAnonymous: config.allowAnonymous,
|
||||
@@ -81,10 +85,28 @@ function showIndex (req, res, next) {
|
||||
email: config.isEmailEnable,
|
||||
allowEmailRegister: config.allowEmailRegister,
|
||||
allowPDFExport: config.allowPDFExport,
|
||||
signin: req.isAuthenticated(),
|
||||
signin: authStatus,
|
||||
infoMessage: req.flash('info'),
|
||||
errorMessage: req.flash('error')
|
||||
})
|
||||
errorMessage: req.flash('error'),
|
||||
privacyStatement: fs.existsSync(path.join(config.docsPath, 'privacy.md')),
|
||||
termsOfUse: fs.existsSync(path.join(config.docsPath, 'terms-of-use.md')),
|
||||
deleteToken: deleteToken
|
||||
}
|
||||
|
||||
if (authStatus) {
|
||||
models.User.findOne({
|
||||
where: {
|
||||
id: req.user.id
|
||||
}
|
||||
}).then(function (user) {
|
||||
if (user) {
|
||||
data.deleteToken = user.deleteToken
|
||||
res.render(config.indexPath, data)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
res.render(config.indexPath, data)
|
||||
}
|
||||
}
|
||||
|
||||
function responseHackMD (res, note) {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
'use strict'
|
||||
|
||||
const archiver = require('archiver')
|
||||
const async = require('async')
|
||||
const Router = require('express').Router
|
||||
|
||||
const response = require('../response')
|
||||
const config = require('../config')
|
||||
const models = require('../models')
|
||||
const logger = require('../logger')
|
||||
const {generateAvatar} = require('../letter-avatars')
|
||||
@@ -36,6 +39,87 @@ UserRouter.get('/me', function (req, res) {
|
||||
}
|
||||
})
|
||||
|
||||
// delete the currently authenticated user
|
||||
UserRouter.get('/me/delete/:token?', function (req, res) {
|
||||
if (req.isAuthenticated()) {
|
||||
models.User.findOne({
|
||||
where: {
|
||||
id: req.user.id
|
||||
}
|
||||
}).then(function (user) {
|
||||
if (!user) {
|
||||
return response.errorNotFound(res)
|
||||
}
|
||||
if (user.deleteToken === req.params.token) {
|
||||
user.destroy().then(function () {
|
||||
res.redirect(config.serverURL + '/')
|
||||
})
|
||||
} else {
|
||||
return response.errorForbidden(res)
|
||||
}
|
||||
}).catch(function (err) {
|
||||
logger.error('delete user failed: ' + err)
|
||||
return response.errorInternalError(res)
|
||||
})
|
||||
} else {
|
||||
return response.errorForbidden(res)
|
||||
}
|
||||
})
|
||||
|
||||
// export the data of the authenticated user
|
||||
UserRouter.get('/me/export', function (req, res) {
|
||||
if (req.isAuthenticated()) {
|
||||
// let output = fs.createWriteStream(__dirname + '/example.zip');
|
||||
let archive = archiver('zip', {
|
||||
zlib: { level: 3 } // Sets the compression level.
|
||||
})
|
||||
res.setHeader('Content-Type', 'application/zip')
|
||||
res.attachment('archive.zip')
|
||||
archive.pipe(res)
|
||||
archive.on('error', function (err) {
|
||||
logger.error('export user data failed: ' + err)
|
||||
return response.errorInternalError(res)
|
||||
})
|
||||
models.User.findOne({
|
||||
where: {
|
||||
id: req.user.id
|
||||
}
|
||||
}).then(function (user) {
|
||||
models.Note.findAll({
|
||||
where: {
|
||||
ownerId: user.id
|
||||
}
|
||||
}).then(function (notes) {
|
||||
let list = []
|
||||
async.each(notes, function (note, callback) {
|
||||
let title
|
||||
let extension = ''
|
||||
do {
|
||||
title = note.title + extension
|
||||
extension++
|
||||
} while (list.indexOf(title) !== -1)
|
||||
|
||||
list.push(title)
|
||||
logger.debug('Write: ' + title + '.md')
|
||||
archive.append(Buffer.from(note.content), { name: title + '.md', date: note.lastchangeAt })
|
||||
callback(null, null)
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
return response.errorInternalError(res)
|
||||
}
|
||||
|
||||
archive.finalize()
|
||||
})
|
||||
})
|
||||
}).catch(function (err) {
|
||||
logger.error('export user data failed: ' + err)
|
||||
return response.errorInternalError(res)
|
||||
})
|
||||
} else {
|
||||
return response.errorForbidden(res)
|
||||
}
|
||||
})
|
||||
|
||||
UserRouter.get('/user/:username/avatar.svg', function (req, res, next) {
|
||||
res.setHeader('Content-Type', 'image/svg+xml')
|
||||
res.setHeader('Cache-Control', 'public, max-age=86400')
|
||||
|
||||
Reference in New Issue
Block a user