user.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. let bcrypt = require('bcryptjs')
  2. let express = require('express')
  3. let router = express.Router()
  4. const Errors = require('../lib/errors.js')
  5. let { User, Post, AdminToken, Thread, Category } = require('../models')
  6. let pagination = require('../lib/pagination.js')
  7. function setUserSession(req, res, username, UserId, admin) {
  8. req.session.loggedIn = true
  9. req.session.username = username
  10. req.session.UserId = UserId
  11. res.cookie('username', username)
  12. //Not for security purposes, just so client side can determine
  13. //to show certain parts of ui or not (i.e. could trivially be spoofed
  14. //but the server would not accept any api requests)
  15. res.cookie('admin', !!admin)
  16. if(admin) { req.session.admin = true }
  17. }
  18. router.post('/', async (req, res) => {
  19. let user, adminUser, hash, token
  20. let validationErrors = []
  21. let userParams = {}
  22. try {
  23. //Validations
  24. if(req.body.username === undefined) {
  25. validationErrors.push(Errors.missingParameter('username'))
  26. } else {
  27. if(typeof req.body.username !== 'string') {
  28. validationErrors.push(Errors.invalidParameterType('username', 'string'))
  29. } if(req.body.username.length < 6) {
  30. validationErrors.push(Errors.parameterLengthTooSmall('username', 6))
  31. } if(req.body.username.length > 50) {
  32. validationErrors.push(Errors.parameterLengthTooLarge('username', 50))
  33. }
  34. }
  35. if(req.body.password === undefined) {
  36. validationErrors.push(Errors.missingParameter('password'))
  37. } else {
  38. if(typeof req.body.password !== 'string') {
  39. validationErrors.push(Errors.invalidParameterType('password', 'string'))
  40. } if(req.body.password.length < 6) {
  41. validationErrors.push(Errors.parameterLengthTooSmall('password', 6))
  42. } if(req.body.password.length > 100) {
  43. validationErrors.push(Errors.parameterLengthTooLarge('password', 100))
  44. }
  45. }
  46. if(req.body.token !== undefined && typeof req.body.token !== 'string') {
  47. validationErrors.push(Errors.invalidParameterType('token', 'string'))
  48. }
  49. if(req.body.admin !== undefined && typeof req.body.admin !== 'boolean') {
  50. validationErrors.push(Errors.invalidParameterType('admin', 'boolean'))
  51. }
  52. if(validationErrors.length) throw Errors.VALIDATION_ERROR
  53. if(req.body.admin && !req.body.token) {
  54. adminUser = await User.findOne({ where: {
  55. admin: true
  56. }})
  57. if(adminUser) {
  58. validationErrors.push(Errors.missingParameter('token'))
  59. throw Errors.VALIDATION_ERROR
  60. } else {
  61. userParams.admin = true
  62. }
  63. } else if(req.body.admin && req.body.token) {
  64. token = await AdminToken.findOne({ where: {
  65. token: req.body.token
  66. }})
  67. if(token && token.isValid()) {
  68. userParams.admin = true
  69. } else {
  70. throw Errors.invalidToken
  71. }
  72. }
  73. hash = await bcrypt.hash(req.body.password, 12)
  74. userParams.username = req.body.username
  75. userParams.hash = hash
  76. user = await User.create(userParams)
  77. if(req.body.token) {
  78. await token.destroy()
  79. }
  80. setUserSession(req, res, user.username, user.id, userParams.admin)
  81. res.json(user.toJSON())
  82. } catch (err) {
  83. if(err === Errors.VALIDATION_ERROR) {
  84. res.status(400)
  85. res.json({
  86. errors: validationErrors
  87. })
  88. } else if(err.name === 'SequelizeUniqueConstraintError') {
  89. res.status(400)
  90. res.json({
  91. errors: [Errors.accountAlreadyCreated]
  92. })
  93. } else if (err = Errors.invalidToken) {
  94. res.status(401)
  95. res.json({
  96. errors: [Errors.invalidToken]
  97. })
  98. } else {
  99. console.log(e)
  100. res.status(500)
  101. res.json({
  102. errors: [Errors.unknown]
  103. })
  104. }
  105. }
  106. })
  107. router.get('/:username', async (req, res) => {
  108. try {
  109. let queryObj = {
  110. attributes: { exclude: ['hash', 'id'] },
  111. where: { username: req.params.username }
  112. }
  113. if(req.query.posts) {
  114. let { from, limit } = pagination.getPaginationProps(req.query, true)
  115. let postInclude = {
  116. model: Post,
  117. include: Post.includeOptions(),
  118. limit,
  119. order: [['id', 'DESC']]
  120. }
  121. if(from !== null) {
  122. postInclude.where = { id: { $lte: from } }
  123. }
  124. queryObj.include = [postInclude]
  125. let user = await User.findOne(queryObj)
  126. if(!user) throw Errors.accountDoesNotExist
  127. let resUser = user.toJSON()
  128. resUser.meta = {}
  129. let nextId = await pagination.getNextIdDesc(Post, { userId: user.id }, resUser.Posts)
  130. if(nextId === null) {
  131. resUser.meta.nextURL = null
  132. resUser.meta.nextPostsCount = 0
  133. } else {
  134. resUser.meta.nextURL =
  135. `/api/v1/user/${user.username}?posts=true&limit=${limit}&from=${nextId - 1}`
  136. resUser.meta.nextPostsCount = await pagination.getNextCount(
  137. Post, resUser.Posts, limit,
  138. { UserId: user.id },
  139. true
  140. )
  141. }
  142. res.json(resUser)
  143. } else if(req.query.threads) {
  144. let queryString = ''
  145. Object.keys(req.query).forEach(query => {
  146. queryString += `&${query}=${req.query[query]}`
  147. })
  148. res.redirect('/api/v1/category/ALL?username=' + req.params.username + queryString)
  149. } else {
  150. let user = await User.findOne(queryObj)
  151. if(!user) throw Errors.accountDoesNotExist
  152. res.json(user.toJSON())
  153. }
  154. } catch (err) {
  155. if(err === Errors.accountDoesNotExist) {
  156. res.status(400)
  157. res.json({ errors: [err] })
  158. } else {
  159. console.log(err)
  160. res.status(500)
  161. res.json({
  162. errors: [Errors.unknown]
  163. })
  164. }
  165. }
  166. })
  167. router.post('/:username/login', async (req, res) => {
  168. let user, bcryptRes, validationErrors = []
  169. try {
  170. //Validations
  171. if(req.body.password === undefined) {
  172. validationErrors.push(Errors.missingParameter('password'))
  173. } else if(typeof req.body.password !== 'string') {
  174. validationErrors.push(Errors.invalidParameterType('password', 'string'))
  175. }
  176. if(validationErrors.length) throw Errors.VALIDATION_ERROR
  177. user = await User.findOne({
  178. where: {
  179. username: req.params.username,
  180. }
  181. })
  182. if(user) {
  183. bcryptRes = await bcrypt.compare(req.body.password, user.hash)
  184. if(bcryptRes) {
  185. setUserSession(req, res, user.username, user.id, user.admin)
  186. res.json({
  187. username: user.username,
  188. admin: user.admin,
  189. success: true
  190. })
  191. } else {
  192. res.status(401)
  193. res.json({
  194. errors: [Errors.invalidLoginCredentials]
  195. })
  196. }
  197. } else {
  198. res.status(401)
  199. res.json({
  200. errors: [Errors.invalidLoginCredentials]
  201. })
  202. }
  203. } catch (err) {
  204. if(err === Errors.VALIDATION_ERROR) {
  205. res.status(400)
  206. res.json({
  207. errors: validationErrors
  208. })
  209. } else {
  210. console.log(err)
  211. res.status(500)
  212. res.json({
  213. errors: [Errors.unknown]
  214. })
  215. }
  216. }
  217. })
  218. router.post('/:username/logout', async (req, res) => {
  219. req.session.destroy(() => {
  220. res.clearCookie('username')
  221. res.clearCookie('admin')
  222. res.json({
  223. success: true
  224. })
  225. })
  226. })
  227. router.all('*', (req, res, next) => {
  228. if(req.session.username) {
  229. next()
  230. } else {
  231. res.status(401)
  232. res.json({
  233. errors: [Errors.requestNotAuthorized]
  234. })
  235. }
  236. })
  237. router.put('/:username', async (req, res) => {
  238. let validationErrors = []
  239. try {
  240. if(req.session.username !== req.params.username) {
  241. validationErrors.push(Errors.requestNotAuthorized)
  242. throw validationErrors
  243. }
  244. if(req.body.description !== undefined) {
  245. if(typeof req.body.description !== 'string') {
  246. validationErrors.push(Errors.invalidParameterType('description', 'string'))
  247. } else if(req.body.description.length > 1024) {
  248. validationErrors.push(Errors.parameterLengthTooLarge('description', 1024))
  249. }
  250. if(validationErrors.length) throw validationErrors
  251. let user = await User.update({ description: req.body.description }, { where: {
  252. username: req.session.username
  253. }})
  254. res.json({ success: true })
  255. } else if(req.body.newPassword !== undefined) {
  256. if(req.body.currentPassword === undefined) {
  257. validationErrors.push(Errors.missingParameter('current password'))
  258. } if(typeof req.body.currentPassword !== 'string') {
  259. validationErrors.push(Errors.invalidParameterType('currentPassword', 'string'))
  260. } else if(req.body.currentPassword.length < 8) {
  261. validationErrors.push(Errors.parameterLengthTooSmall('current password', 7))
  262. }
  263. if(typeof req.body.newPassword !== 'string') {
  264. validationErrors.push(Errors.invalidParameterType('newPassword', 'string'))
  265. } else {
  266. if(req.body.newPassword.length > 1024) {
  267. validationErrors.push(Errors.parameterLengthTooLarge('new password', 1024))
  268. } if(req.body.newPassword.length < 8) {
  269. validationErrors.push(Errors.parameterLengthTooSmall('new password', 7))
  270. } if(req.body.newPassword === req.body.currentPassword) {
  271. validationErrors.push(Errors.passwordSame)
  272. }
  273. }
  274. if(validationErrors.length) throw validationErrors
  275. let user = await User.findOne({where: {
  276. username: req.session.username
  277. }})
  278. let bcryptRes = await bcrypt.compare(req.body.currentPassword, user.hash)
  279. if(bcryptRes) {
  280. let newHash = await bcrypt.hash(req.body.newPassword, 12)
  281. let user = await User.update({ hash: newHash }, { where: {
  282. username: req.session.username
  283. }})
  284. res.json({ success: true })
  285. } else {
  286. validationErrors.push(Errors.invalidLoginCredentials)
  287. throw validationErrors
  288. }
  289. } else {
  290. res.json({})
  291. }
  292. } catch (e) {
  293. if(validationErrors.length) {
  294. res.status(400)
  295. res.json({ errors: validationErrors })
  296. } else {
  297. console.log(e)
  298. res.status(500)
  299. res.json({errors: Errors.unknown })
  300. }
  301. }
  302. })
  303. router.delete('/:username', async (req, res) => {
  304. let validationErrors = []
  305. try {
  306. if(req.session.username !== req.params.username) {
  307. validationErrors.push(Errors.requestNotAuthorized)
  308. throw validationErrors
  309. }
  310. let user = await User.findOne({ where: {
  311. username: req.session.username
  312. }})
  313. await user.destroy()
  314. req.session.destroy(() => {
  315. res.clearCookie('username')
  316. res.clearCookie('admin')
  317. res.json({ success: true })
  318. })
  319. } catch (e) {
  320. if(validationErrors.length) {
  321. res.status(400)
  322. res.json({ errors: validationErrors })
  323. } else {
  324. console.log(e)
  325. res.status(500)
  326. res.json({errors: Errors.unknown })
  327. }
  328. }
  329. })
  330. module.exports = router