summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorChristoph (Sheogorath) Kern2018-06-17 23:33:57 +0200
committerGitHub2018-06-17 23:33:57 +0200
commit56d78a7d6c2b49bb194cba05d1a5ff5e4b54a983 (patch)
tree009a0c047d9b537548c0a4a741d60ade9b2ee6ea /lib
parentf36b10abb2368459e4ba546d826c16b3232f4ba0 (diff)
parentfce735e833f91a0f1d17c518b65c4c724d1a4b4d (diff)
Merge pull request #830 from SISheogorath/feature/GDPR
GDPR compliant part 1
Diffstat (limited to 'lib')
-rw-r--r--lib/migrations/20180525153000-user-add-delete-token.js13
-rw-r--r--lib/models/author.js8
-rw-r--r--lib/models/note.js6
-rw-r--r--lib/models/revision.js4
-rw-r--r--lib/models/user.js7
-rw-r--r--lib/realtime.js12
-rw-r--r--lib/response.js30
-rw-r--r--lib/web/userRouter.js84
8 files changed, 150 insertions, 14 deletions
diff --git a/lib/migrations/20180525153000-user-add-delete-token.js b/lib/migrations/20180525153000-user-add-delete-token.js
new file mode 100644
index 00000000..642fa5d4
--- /dev/null
+++ b/lib/migrations/20180525153000-user-add-delete-token.js
@@ -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')
+ }
+}
diff --git a/lib/models/author.js b/lib/models/author.js
index 8b4f74e5..03f832a4 100644
--- a/lib/models/author.js
+++ b/lib/models/author.js
@@ -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
})
}
}
diff --git a/lib/models/note.js b/lib/models/note.js
index 2a048e37..7d8e9625 100644
--- a/lib/models/note.js
+++ b/lib/models/note.js
@@ -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',
diff --git a/lib/models/revision.js b/lib/models/revision.js
index 9ecd14dc..8bc95cb1 100644
--- a/lib/models/revision.js
+++ b/lib/models/revision.js
@@ -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) {
diff --git a/lib/models/user.js b/lib/models/user.js
index 0f6acd65..5dd13869 100644
--- a/lib/models/user.js
+++ b/lib/models/user.js
@@ -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) {
diff --git a/lib/realtime.js b/lib/realtime.js
index 070bde2d..f6c62d4e 100644
--- a/lib/realtime.js
+++ b/lib/realtime.js
@@ -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
+ }
}
}
diff --git a/lib/response.js b/lib/response.js
index 89ade7a7..4cfa9a74 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -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) {
diff --git a/lib/web/userRouter.js b/lib/web/userRouter.js
index 963961c7..db786d53 100644
--- a/lib/web/userRouter.js
+++ b/lib/web/userRouter.js
@@ -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')